FreeRDP
Loading...
Searching...
No Matches
shadow_server.c
1
21#include <freerdp/config.h>
22
23#include <errno.h>
24#include <stdint.h>
25
26#include <winpr/assert.h>
27#include <winpr/crt.h>
28#include <winpr/ssl.h>
29#include <winpr/path.h>
30#include <winpr/cmdline.h>
31#include <winpr/winsock.h>
32
33#include <freerdp/log.h>
34#include <freerdp/version.h>
35
36#include <winpr/tools/makecert.h>
37
38#ifndef _WIN32
39#include <sys/select.h>
40#include <signal.h>
41#endif
42
43#include "shadow.h"
44
45#define TAG SERVER_TAG("shadow")
46
47static const char bind_address[] = "bind-address,";
48
49#define fail_at(arg, rc) fail_at_((arg), (rc), __FILE__, __func__, __LINE__)
50static int fail_at_(const COMMAND_LINE_ARGUMENT_A* arg, int rc, const char* file, const char* fkt,
51 size_t line)
52{
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,
58 arg->Value, rc);
59 return rc;
60}
61
62WINPR_ATTR_NODISCARD
63static int command_line_compare(const void* pa, const void* pb)
64{
65 const COMMAND_LINE_ARGUMENT_A* a = pa;
66 const COMMAND_LINE_ARGUMENT_A* b = pb;
67
68 if (!a && !b)
69 return 0;
70 if (!a)
71 return -1;
72 if (!b)
73 return 1;
74
75 return strcmp(a->Name, b->Name);
76}
77
78WINPR_ATTR_NODISCARD
79static int shadow_server_print_command_line_help(int argc, char** argv,
80 const COMMAND_LINE_ARGUMENT_A* largs)
81{
82 if ((argc < 1) || !largs || !argv)
83 return -1;
84
85 {
86 char* path = winpr_GetConfigFilePath(TRUE, "SAM");
87 printf("Usage: %s [options]\n", argv[0]);
88 printf("\n");
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 "
95 "compiled in "
96 "support for PAM)\n\n");
97 printf("Syntax:\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");
101 printf("\n");
102 free(path);
103 }
104
105 // TODO: Sort arguments
106 size_t nrArgs = 0;
107 {
108 const COMMAND_LINE_ARGUMENT_A* arg = largs;
109 while (arg->Name != nullptr)
110 {
111 nrArgs++;
112 arg++;
113 }
114 nrArgs++;
115 }
116 COMMAND_LINE_ARGUMENT_A* args_copy = calloc(nrArgs, sizeof(COMMAND_LINE_ARGUMENT_A));
117 if (!args_copy)
118 return -1;
119 memcpy(args_copy, largs, nrArgs * sizeof(COMMAND_LINE_ARGUMENT_A));
120 qsort(args_copy, nrArgs - 1, sizeof(COMMAND_LINE_ARGUMENT_A), command_line_compare);
121
122 const COMMAND_LINE_ARGUMENT_A* arg = args_copy;
123
124 int rc = -1;
125 do
126 {
127 if (arg->Flags & COMMAND_LINE_VALUE_FLAG)
128 {
129 printf(" %s", "/");
130 printf("%-20s\n", arg->Name);
131 printf("\t%s\n", arg->Text);
132 }
133 else if ((arg->Flags & COMMAND_LINE_VALUE_REQUIRED) ||
134 (arg->Flags & COMMAND_LINE_VALUE_OPTIONAL))
135 {
136 printf(" %s", "/");
137
138 if (arg->Format)
139 {
140 const size_t length = (strlen(arg->Name) + strlen(arg->Format) + 2);
141 char* str = (char*)calloc(length + 1, sizeof(char));
142
143 if (!str)
144 goto fail;
145
146 (void)sprintf_s(str, length + 1, "%s:%s", arg->Name, arg->Format);
147 (void)printf("%-20s\n", str);
148 free(str);
149 }
150 else
151 {
152 printf("%-20s\n", arg->Name);
153 }
154
155 printf("\t%s\n", arg->Text);
156 }
157 else if (arg->Flags & COMMAND_LINE_VALUE_BOOL)
158 {
159 const size_t length = strlen(arg->Name) + 32;
160 char* str = calloc(length + 1, sizeof(char));
161
162 if (!str)
163 goto fail;
164
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);
170
171 free(str);
172 }
173 } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr);
174
175 rc = 1;
176fail:
177 free(args_copy);
178 return rc;
179}
180
181int shadow_server_command_line_status_print(rdpShadowServer* server, int argc, char** argv,
182 int status, const COMMAND_LINE_ARGUMENT_A* cargs)
183{
184 WINPR_UNUSED(server);
185
186 if (status == COMMAND_LINE_STATUS_PRINT_VERSION)
187 {
188 printf("FreeRDP version %s (git %s)\n", FREERDP_VERSION_FULL, FREERDP_GIT_REVISION);
189 return COMMAND_LINE_STATUS_PRINT_VERSION;
190 }
191 else if (status == COMMAND_LINE_STATUS_PRINT_BUILDCONFIG)
192 {
193 printf("%s\n", freerdp_get_build_config());
194 return COMMAND_LINE_STATUS_PRINT_BUILDCONFIG;
195 }
196 else if (status == COMMAND_LINE_STATUS_PRINT)
197 {
198 return COMMAND_LINE_STATUS_PRINT;
199 }
200 else if (status < 0)
201 {
202 if (shadow_server_print_command_line_help(argc, argv, cargs) < 0)
203 return -1;
204
205 return COMMAND_LINE_STATUS_PRINT_HELP;
206 }
207
208 return 1;
209}
210
211int shadow_server_parse_command_line(rdpShadowServer* server, int argc, char** argv,
213{
214 int status = 0;
215 DWORD flags = 0;
216 const COMMAND_LINE_ARGUMENT_A* arg = nullptr;
217 rdpSettings* settings = server->settings;
218
219 if ((argc < 2) || !argv || !cargs)
220 return 1;
221
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);
226
227 if (status < 0)
228 return status;
229
230 arg = cargs;
231 errno = 0;
232
233 do
234 {
235 if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
236 continue;
237
238 CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "port")
239 {
240 long val = strtol(arg->Value, nullptr, 0);
241
242 if ((errno != 0) || (val <= 0) || (val > UINT16_MAX))
243 return fail_at(arg, COMMAND_LINE_ERROR);
244
245 server->port = (DWORD)val;
246 }
247 CommandLineSwitchCase(arg, "ipc-socket")
248 {
249 /* /bind-address is incompatible */
250 if (server->ipcSocket)
251 return fail_at(arg, COMMAND_LINE_ERROR);
252 server->ipcSocket = _strdup(arg->Value);
253
254 if (!server->ipcSocket)
255 return fail_at(arg, COMMAND_LINE_ERROR);
256 }
257 CommandLineSwitchCase(arg, "bind-address")
258 {
259 int rc = 0;
260 size_t len = strlen(arg->Value) + sizeof(bind_address);
261 /* /ipc-socket is incompatible */
262 if (server->ipcSocket)
263 return fail_at(arg, COMMAND_LINE_ERROR);
264 server->ipcSocket = calloc(len, sizeof(CHAR));
265
266 if (!server->ipcSocket)
267 return fail_at(arg, COMMAND_LINE_ERROR);
268
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);
272 }
273 CommandLineSwitchCase(arg, "may-view")
274 {
275 server->mayView = arg->Value != nullptr;
276 }
277 CommandLineSwitchCase(arg, "bitmap-compat")
278 {
279 server->SupportMultiRectBitmapUpdates = arg->Value == nullptr;
280 }
281 CommandLineSwitchCase(arg, "may-interact")
282 {
283 server->mayInteract = arg->Value != nullptr;
284 }
285 CommandLineSwitchCase(arg, "server-side-cursor")
286 {
287 server->ShowMouseCursor = arg->Value != nullptr;
288 }
289 CommandLineSwitchCase(arg, "mouse-relative")
290 {
291 const BOOL val = arg->Value != nullptr;
292 if (!freerdp_settings_set_bool(settings, FreeRDP_MouseUseRelativeMove, val) ||
293 !freerdp_settings_set_bool(settings, FreeRDP_HasRelativeMouseEvent, val))
294 return fail_at(arg, COMMAND_LINE_ERROR);
295 }
296 CommandLineSwitchCase(arg, "max-connections")
297 {
298 errno = 0;
299 unsigned long val = strtoul(arg->Value, nullptr, 0);
300
301 if ((errno != 0) || (val > UINT32_MAX))
302 return fail_at(arg, COMMAND_LINE_ERROR);
303 server->maxClientsConnected = val;
304 }
305 CommandLineSwitchCase(arg, "rect")
306 {
307 char* p = nullptr;
308 char* tok[4];
309 long x = -1;
310 long y = -1;
311 long w = -1;
312 long h = -1;
313 char* str = _strdup(arg->Value);
314
315 if (!str)
316 return fail_at(arg, COMMAND_LINE_ERROR);
317
318 tok[0] = p = str;
319 p = strchr(p + 1, ',');
320
321 if (!p)
322 {
323 free(str);
324 return fail_at(arg, COMMAND_LINE_ERROR);
325 }
326
327 *p++ = '\0';
328 tok[1] = p;
329 p = strchr(p + 1, ',');
330
331 if (!p)
332 {
333 free(str);
334 return fail_at(arg, COMMAND_LINE_ERROR);
335 }
336
337 *p++ = '\0';
338 tok[2] = p;
339 p = strchr(p + 1, ',');
340
341 if (!p)
342 {
343 free(str);
344 return fail_at(arg, COMMAND_LINE_ERROR);
345 }
346
347 *p++ = '\0';
348 tok[3] = p;
349 x = strtol(tok[0], nullptr, 0);
350
351 if (errno != 0)
352 goto fail;
353
354 y = strtol(tok[1], nullptr, 0);
355
356 if (errno != 0)
357 goto fail;
358
359 w = strtol(tok[2], nullptr, 0);
360
361 if (errno != 0)
362 goto fail;
363
364 h = strtol(tok[3], nullptr, 0);
365
366 if (errno != 0)
367 goto fail;
368
369 fail:
370 free(str);
371
372 if ((x < 0) || (y < 0) || (w < 1) || (h < 1) || (errno != 0))
373 return fail_at(arg, COMMAND_LINE_ERROR);
374
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;
383 }
384 CommandLineSwitchCase(arg, "auth")
385 {
386 server->authentication = arg->Value != nullptr;
387 }
388 CommandLineSwitchCase(arg, "remote-guard")
389 {
390 if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteCredentialGuard,
391 arg->Value != nullptr))
392 return fail_at(arg, COMMAND_LINE_ERROR);
393 }
394 CommandLineSwitchCase(arg, "restricted-admin")
395 {
396 if (!freerdp_settings_set_bool(settings, FreeRDP_RestrictedAdminModeSupported,
397 arg->Value != nullptr))
398 return fail_at(arg, COMMAND_LINE_ERROR);
399 }
400 CommandLineSwitchCase(arg, "vmconnect")
401 {
402 if (!freerdp_settings_set_bool(settings, FreeRDP_VmConnectMode, arg->Value != nullptr))
403 return fail_at(arg, COMMAND_LINE_ERROR);
404 }
405 CommandLineSwitchCase(arg, "sec")
406 {
407 if (strcmp("rdp", arg->Value) == 0) /* Standard RDP */
408 {
409 if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, TRUE))
410 return fail_at(arg, COMMAND_LINE_ERROR);
411 if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, FALSE))
412 return fail_at(arg, COMMAND_LINE_ERROR);
413 if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE))
414 return fail_at(arg, COMMAND_LINE_ERROR);
415 if (!freerdp_settings_set_bool(settings, FreeRDP_ExtSecurity, FALSE))
416 return fail_at(arg, COMMAND_LINE_ERROR);
417 if (!freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, TRUE))
418 return fail_at(arg, COMMAND_LINE_ERROR);
419 }
420 else if (strcmp("tls", arg->Value) == 0) /* TLS */
421 {
422 if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, FALSE))
423 return fail_at(arg, COMMAND_LINE_ERROR);
424 if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, TRUE))
425 return fail_at(arg, COMMAND_LINE_ERROR);
426 if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE))
427 return fail_at(arg, COMMAND_LINE_ERROR);
428 if (!freerdp_settings_set_bool(settings, FreeRDP_ExtSecurity, FALSE))
429 return fail_at(arg, COMMAND_LINE_ERROR);
430 }
431 else if (strcmp("nla", arg->Value) == 0) /* NLA */
432 {
433 if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, FALSE))
434 return fail_at(arg, COMMAND_LINE_ERROR);
435 if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, FALSE))
436 return fail_at(arg, COMMAND_LINE_ERROR);
437 if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, TRUE))
438 return fail_at(arg, COMMAND_LINE_ERROR);
439 if (!freerdp_settings_set_bool(settings, FreeRDP_ExtSecurity, FALSE))
440 return fail_at(arg, COMMAND_LINE_ERROR);
441 }
442 else if (strcmp("ext", arg->Value) == 0) /* NLA Extended */
443 {
444 if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, FALSE))
445 return fail_at(arg, COMMAND_LINE_ERROR);
446 if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, FALSE))
447 return fail_at(arg, COMMAND_LINE_ERROR);
448 if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE))
449 return fail_at(arg, COMMAND_LINE_ERROR);
450 if (!freerdp_settings_set_bool(settings, FreeRDP_ExtSecurity, TRUE))
451 return fail_at(arg, COMMAND_LINE_ERROR);
452 }
453 else
454 {
455 WLog_ERR(TAG, "unknown protocol security: %s", arg->Value);
456 return fail_at(arg, COMMAND_LINE_ERROR_UNEXPECTED_VALUE);
457 }
458 }
459 CommandLineSwitchCase(arg, "sec-rdp")
460 {
461 if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, arg->Value != nullptr))
462 return fail_at(arg, COMMAND_LINE_ERROR);
463 }
464 CommandLineSwitchCase(arg, "sec-tls")
465 {
466 if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, arg->Value != nullptr))
467 return fail_at(arg, COMMAND_LINE_ERROR);
468 }
469 CommandLineSwitchCase(arg, "sec-nla")
470 {
471 if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, arg->Value != nullptr))
472 return fail_at(arg, COMMAND_LINE_ERROR);
473 }
474 CommandLineSwitchCase(arg, "sec-ext")
475 {
476 if (!freerdp_settings_set_bool(settings, FreeRDP_ExtSecurity, arg->Value != nullptr))
477 return fail_at(arg, COMMAND_LINE_ERROR);
478 }
479 CommandLineSwitchCase(arg, "sam-file")
480 {
481 if (!freerdp_settings_set_string(settings, FreeRDP_NtlmSamFile, arg->Value))
482 return fail_at(arg, COMMAND_LINE_ERROR);
483 }
484 CommandLineSwitchCase(arg, "log-level")
485 {
486 wLog* root = WLog_GetRoot();
487
488 if (!WLog_SetStringLogLevel(root, arg->Value))
489 return fail_at(arg, COMMAND_LINE_ERROR);
490 }
491 CommandLineSwitchCase(arg, "log-filters")
492 {
493 if (!WLog_AddStringLogFilters(arg->Value))
494 return fail_at(arg, COMMAND_LINE_ERROR);
495 }
496 CommandLineSwitchCase(arg, "nsc")
497 {
498 if (!freerdp_settings_set_bool(settings, FreeRDP_NSCodec, arg->Value != nullptr))
499 return fail_at(arg, COMMAND_LINE_ERROR);
500 }
501 CommandLineSwitchCase(arg, "rfx")
502 {
503 if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, arg->Value != nullptr))
504 return fail_at(arg, COMMAND_LINE_ERROR);
505 }
506 CommandLineSwitchCase(arg, "gfx")
507 {
508 if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGraphicsPipeline,
509 arg->Value != nullptr))
510 return fail_at(arg, COMMAND_LINE_ERROR);
511 }
512 CommandLineSwitchCase(arg, "gfx-progressive")
513 {
514 if (!freerdp_settings_set_bool(settings, FreeRDP_GfxProgressive, arg->Value != nullptr))
515 return fail_at(arg, COMMAND_LINE_ERROR);
516 }
517#if defined(WITH_GFX_AV1)
518 CommandLineSwitchCase(arg, "gfx-av1")
519 {
520 BOOL parseFailed = FALSE;
521 BOOL enabled = TRUE;
522 UINT32 profile = freerdp_settings_get_uint32(settings, FreeRDP_GfxCodecAV1Profile);
523 if (arg->Value)
524 {
525 size_t count = 0;
526 char** args = CommandLineParseCommaSeparatedValues(arg->Value, &count);
527 WINPR_ASSERT(args || (count == 0));
528 for (size_t x = 0; x < count; x++)
529 {
530 const char* cur = args[x];
531 if (strncmp("profile:", cur, 8) == 0)
532 {
533 const char* val = &cur[8];
534 if (strcmp("high", val) == 0)
535 profile = 1;
536 else if (strcmp("low", val) == 0)
537 profile = 0;
538 else
539 {
540 const unsigned long v = strtoul(val, nullptr, 0);
541 if ((errno != 0) || (v >= UINT32_MAX))
542 {
543 parseFailed = TRUE;
544 break;
545 }
546 else
547 profile = WINPR_ASSERTING_INT_CAST(UINT32, v);
548 }
549 }
550 else if (strcmp("off", cur) == 0)
551 {
552 enabled = FALSE;
553 }
554 else
555 {
556 parseFailed = TRUE;
557 break;
558 }
559 }
560 CommandLineParserFree(args);
561 }
562 if (parseFailed)
563 return fail_at(arg, COMMAND_LINE_ERROR);
564 if (!freerdp_settings_set_bool(settings, FreeRDP_GfxCodecAV1, enabled))
565 return fail_at(arg, COMMAND_LINE_ERROR);
566 if (!freerdp_settings_set_uint32(settings, FreeRDP_GfxCodecAV1Profile, profile))
567 return fail_at(arg, COMMAND_LINE_ERROR);
568 }
569#endif
570 CommandLineSwitchCase(arg, "gfx-rfx")
571 {
572 if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, arg->Value != nullptr))
573 return fail_at(arg, COMMAND_LINE_ERROR);
574 }
575 CommandLineSwitchCase(arg, "gfx-planar")
576 {
577 if (!freerdp_settings_set_bool(settings, FreeRDP_GfxPlanar, arg->Value != nullptr))
578 return fail_at(arg, COMMAND_LINE_ERROR);
579 }
580 CommandLineSwitchCase(arg, "gfx-avc420")
581 {
582 if (!freerdp_settings_set_bool(settings, FreeRDP_GfxH264, arg->Value != nullptr))
583 return fail_at(arg, COMMAND_LINE_ERROR);
584 }
585 CommandLineSwitchCase(arg, "gfx-avc444")
586 {
587 if (!freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444v2, arg->Value != nullptr))
588 return fail_at(arg, COMMAND_LINE_ERROR);
589 if (!freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444, arg->Value != nullptr))
590 return fail_at(arg, COMMAND_LINE_ERROR);
591 }
592 CommandLineSwitchCase(arg, "keytab")
593 {
594 if (!freerdp_settings_set_string(settings, FreeRDP_KerberosKeytab, arg->Value))
595 return fail_at(arg, COMMAND_LINE_ERROR);
596 }
597 CommandLineSwitchCase(arg, "ccache")
598 {
599 if (!freerdp_settings_set_string(settings, FreeRDP_KerberosCache, arg->Value))
600 return fail_at(arg, COMMAND_LINE_ERROR);
601 }
602 CommandLineSwitchCase(arg, "tls-secrets-file")
603 {
604 if (!freerdp_settings_set_string(settings, FreeRDP_TlsSecretsFile, arg->Value))
605 return fail_at(arg, COMMAND_LINE_ERROR);
606 }
607 CommandLineSwitchDefault(arg)
608 {
609 }
610 CommandLineSwitchEnd(arg)
611 } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr);
612
613 arg = CommandLineFindArgumentA(cargs, "monitors");
614
615 if (arg && (arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
616 {
617 UINT32 numMonitors = 0;
618 MONITOR_DEF monitors[16] = WINPR_C_ARRAY_INIT;
619 numMonitors = shadow_enum_monitors(monitors, 16);
620
621 if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
622 {
623 /* Select monitors. errno may hold anything from what ran before
624 * (enumerating the monitors makes system calls of its own) */
625 errno = 0;
626 long val = strtol(arg->Value, nullptr, 0);
627
628 if ((val < 0) || (errno != 0) || ((UINT32)val >= numMonitors))
629 {
630 WLog_ERR(TAG, "no monitor '%s': there are %" PRIu32 " (/monitors lists them)",
631 arg->Value, numMonitors);
632 status = COMMAND_LINE_STATUS_PRINT;
633 }
634
635 server->selectedMonitor = (UINT32)val;
636 }
637 else
638 {
639 /* List monitors */
640
641 for (UINT32 index = 0; index < numMonitors; index++)
642 {
643 const MONITOR_DEF* monitor = &monitors[index];
644 const INT64 width = monitor->right - monitor->left + 1;
645 const INT64 height = monitor->bottom - monitor->top + 1;
646 WLog_INFO(
647 TAG, " %s [%" PRIu32 "] %" PRId64 "x%" PRId64 "\t+%" PRId32 "+%" PRId32 "",
648 (monitor->flags == 1) ? "*" : " ", index, width, height, monitor->left,
649 monitor->top);
650 }
651
652 status = COMMAND_LINE_STATUS_PRINT;
653 }
654 }
655
656 /* If we want to disable authentication we need to ensure that NLA security
657 * is not activated. Only TLS and RDP security allow anonymous login.
658 */
659 if (!server->authentication && !freerdp_settings_get_bool(settings, FreeRDP_VmConnectMode))
660 {
661 if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE))
662 return COMMAND_LINE_ERROR;
663 if (!freerdp_settings_set_bool(settings, FreeRDP_ExtSecurity, FALSE))
664 return COMMAND_LINE_ERROR;
665 }
666 return status;
667}
668
669WINPR_ATTR_NODISCARD
670static DWORD WINAPI shadow_server_thread(LPVOID arg)
671{
672 rdpShadowServer* server = (rdpShadowServer*)arg;
673 BOOL running = TRUE;
674 DWORD status = 0;
675 freerdp_listener* listener = server->listener;
676 if (shadow_subsystem_start(server->subsystem) < 0)
677 running = FALSE;
678
679 while (running)
680 {
681 HANDLE events[MAXIMUM_WAIT_OBJECTS] = WINPR_C_ARRAY_INIT;
682 DWORD nCount = 0;
683 events[nCount++] = server->StopEvent;
684 nCount += listener->GetEventHandles(listener, &events[nCount], ARRAYSIZE(events) - nCount);
685
686 if (nCount <= 1)
687 {
688 WLog_ERR(TAG, "Failed to get FreeRDP file descriptor");
689 break;
690 }
691
692 status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
693
694 switch (status)
695 {
696 case WAIT_FAILED:
697 case WAIT_OBJECT_0:
698 running = FALSE;
699 break;
700
701 default:
702 {
703 if (!listener->CheckFileDescriptor(listener))
704 {
705 WLog_ERR(TAG, "Failed to check FreeRDP file descriptor");
706 running = FALSE;
707 }
708 else
709 {
710#ifdef _WIN32
711 Sleep(100); /* FIXME: listener event handles */
712#endif
713 }
714 }
715 break;
716 }
717 }
718
719 listener->Close(listener);
720 shadow_subsystem_stop(server->subsystem);
721
722 /* Signal to the clients that server is being stopped and wait for them
723 * to disconnect. */
724 if (shadow_client_boardcast_quit(server, 0))
725 {
726 while (ArrayList_Count(server->clients) > 0)
727 {
728 Sleep(100);
729 }
730 }
731
732 ExitThread(0);
733 return 0;
734}
735
736WINPR_ATTR_NODISCARD
737static BOOL open_port(rdpShadowServer* server, char* address)
738{
739 BOOL status = 0;
740 char* modaddr = address;
741
742 if (modaddr)
743 {
744 if (modaddr[0] == '[')
745 {
746 char* end = strchr(address, ']');
747 if (!end)
748 {
749 WLog_ERR(TAG, "Could not parse bind-address %s", address);
750 return -1;
751 }
752 *end++ = '\0';
753 if (strlen(end) > 0)
754 {
755 WLog_ERR(TAG, "Excess data after IPv6 address: '%s'", end);
756 return -1;
757 }
758 modaddr++;
759 }
760 }
761 status = server->listener->Open(server->listener, modaddr, (UINT16)server->port);
762
763 if (!status)
764 {
765 WLog_ERR(TAG,
766 "Problem creating TCP listener. (Port already used or insufficient permissions?)");
767 }
768
769 return status;
770}
771
772int shadow_server_start(rdpShadowServer* server)
773{
774 BOOL ipc = 0;
775 BOOL status = 0;
776 WSADATA wsaData;
777
778 if (!server)
779 return -1;
780
781 if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
782 return -1;
783
784#ifndef _WIN32
785 (void)signal(SIGPIPE, SIG_IGN);
786#endif
787 server->screen = shadow_screen_new(server);
788
789 if (!server->screen)
790 {
791 WLog_ERR(TAG, "screen_new failed");
792 return -1;
793 }
794
795 server->capture = shadow_capture_new(server);
796
797 if (!server->capture)
798 {
799 WLog_ERR(TAG, "capture_new failed");
800 return -1;
801 }
802
803 /* Bind magic:
804 *
805 * empty ... bind TCP all
806 * <local path> ... bind local (IPC)
807 * bind-socket,<address> ... bind TCP to specified interface
808 */
809 ipc = server->ipcSocket && (strncmp(bind_address, server->ipcSocket,
810 strnlen(bind_address, sizeof(bind_address))) != 0);
811 if (!ipc)
812 {
813 size_t count = 0;
814
815 char** ptr = CommandLineParseCommaSeparatedValuesEx(nullptr, server->ipcSocket, &count);
816 if (!ptr || (count <= 1))
817 {
818 if (server->ipcSocket == nullptr)
819 {
820 if (!open_port(server, nullptr))
821 {
822 CommandLineParserFree(ptr);
823 return -1;
824 }
825 }
826 else
827 {
828 CommandLineParserFree(ptr);
829 return -1;
830 }
831 }
832
833 WINPR_ASSERT(ptr || (count == 0));
834 for (size_t x = 1; x < count; x++)
835 {
836 BOOL success = open_port(server, ptr[x]);
837 if (!success)
838 {
839 CommandLineParserFree(ptr);
840 return -1;
841 }
842 }
843 CommandLineParserFree(ptr);
844 }
845 else
846 {
847 status = server->listener->OpenLocal(server->listener, server->ipcSocket);
848
849 if (!status)
850 {
851 WLog_ERR(TAG, "Problem creating local socket listener. (Port already used or "
852 "insufficient permissions?)");
853 return -1;
854 }
855 }
856
857 if (!(server->thread =
858 CreateThread(nullptr, 0, shadow_server_thread, (void*)server, 0, nullptr)))
859 {
860 return -1;
861 }
862
863 return 0;
864}
865
866int shadow_server_stop(rdpShadowServer* server)
867{
868 if (!server)
869 return -1;
870
871 if (server->thread)
872 {
873 (void)SetEvent(server->StopEvent);
874 (void)WaitForSingleObject(server->thread, INFINITE);
875 (void)CloseHandle(server->thread);
876 server->thread = nullptr;
877 if (server->listener && server->listener->Close)
878 server->listener->Close(server->listener);
879 }
880
881 if (server->screen)
882 {
883 shadow_screen_free(server->screen);
884 server->screen = nullptr;
885 }
886
887 if (server->capture)
888 {
889 shadow_capture_free(server->capture);
890 server->capture = nullptr;
891 }
892
893 return 0;
894}
895
896WINPR_ATTR_NODISCARD
897static int shadow_server_init_config_path(rdpShadowServer* server)
898{
899 if (!server->ConfigPath)
900 {
901 char* configHome = freerdp_settings_get_config_path();
902
903 if (configHome)
904 {
905 if (!winpr_PathFileExists(configHome) && !winpr_PathMakePath(configHome, nullptr))
906 {
907 WLog_ERR(TAG, "Failed to create directory '%s'", configHome);
908 free(configHome);
909 return -1;
910 }
911
912 server->ConfigPath = configHome;
913 }
914 }
915
916 if (!server->ConfigPath)
917 return -1; /* no usable config path */
918
919 return 1;
920}
921
922#if defined(WITH_WINPR_TOOLS)
923WINPR_ATTR_NODISCARD
924static BOOL shadow_server_create_certificate(rdpShadowServer* server, const char* filepath)
925{
926 BOOL rc = FALSE;
927 char* makecert_argv[6] = { "makecert", "-rdp", "-live", "-silent", "-y", "5" };
928
929 WINPR_STATIC_ASSERT(ARRAYSIZE(makecert_argv) <= INT_MAX);
930 const size_t makecert_argc = ARRAYSIZE(makecert_argv);
931
932 MAKECERT_CONTEXT* makecert = makecert_context_new();
933
934 if (!makecert)
935 goto out_fail;
936
937 if (makecert_context_process(makecert, (int)makecert_argc, makecert_argv) < 0)
938 goto out_fail;
939
940 if (makecert_context_set_output_file_name(makecert, "shadow") != 1)
941 goto out_fail;
942
943 WINPR_ASSERT(server);
944 WINPR_ASSERT(filepath);
945 if (!winpr_PathFileExists(server->CertificateFile))
946 {
947 if (makecert_context_output_certificate_file(makecert, filepath) != 1)
948 goto out_fail;
949 }
950
951 if (!winpr_PathFileExists(server->PrivateKeyFile))
952 {
953 if (makecert_context_output_private_key_file(makecert, filepath) != 1)
954 goto out_fail;
955 }
956 rc = TRUE;
957out_fail:
958 makecert_context_free(makecert);
959 return rc;
960}
961#endif
962
963WINPR_ATTR_NODISCARD
964static BOOL shadow_server_read_pem(rdpShadowServer* server)
965{
966 BOOL rc = FALSE;
967 WINPR_ASSERT(server);
968
969 rdpSettings* settings = server->settings;
970 WINPR_ASSERT(settings);
971
972 {
973 rdpPrivateKey* key = freerdp_key_new_from_file_enc(server->PrivateKeyFile, nullptr);
974 if (!key)
975 {
976 WLog_ERR(TAG, "Private Key PEM %s invalid, aborting", server->PrivateKeyFile);
977 return FALSE;
978 }
979
980 if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerRsaKey, key, 1))
981 {
982 WLog_ERR(TAG, "Private Key PEM %s valid, but some properties failed to be extracted",
983 server->PrivateKeyFile);
984 return FALSE;
985 }
986 }
987 {
988 rdpCertificate* cert = freerdp_certificate_new_from_file(server->CertificateFile);
989 if (!cert)
990 {
991 WLog_ERR(TAG, "Certificate PEM %s invalid, aborting", server->CertificateFile);
992 return FALSE;
993 }
994
995 if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerCertificate, cert, 1))
996 goto out_fail;
997
998 if (!freerdp_certificate_is_rdp_security_compatible(cert))
999 {
1000 if (!freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, FALSE))
1001 goto out_fail;
1002 if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, FALSE))
1003 goto out_fail;
1004 }
1005 }
1006 rc = TRUE;
1007
1008out_fail:
1009 WLog_ERR(TAG, "Certificate PEM %s valid, but some properties failed to be extracted",
1010 server->CertificateFile);
1011 return rc;
1012}
1013
1014WINPR_ATTR_NODISCARD
1015static BOOL shadow_server_init_certificate(rdpShadowServer* server)
1016{
1017 char* filepath = nullptr;
1018 BOOL ret = FALSE;
1019
1020 WINPR_ASSERT(server);
1021
1022 if (!winpr_PathFileExists(server->ConfigPath) &&
1023 !winpr_PathMakePath(server->ConfigPath, nullptr))
1024 {
1025 WLog_ERR(TAG, "Failed to create directory '%s'", server->ConfigPath);
1026 return FALSE;
1027 }
1028
1029 if (!(filepath = GetCombinedPath(server->ConfigPath, "shadow")))
1030 return FALSE;
1031
1032 if (!winpr_PathFileExists(filepath) && !winpr_PathMakePath(filepath, nullptr))
1033 {
1034 if (!winpr_PathMakePath(filepath, nullptr))
1035 {
1036 WLog_ERR(TAG, "Failed to create directory '%s'", filepath);
1037 goto out_fail;
1038 }
1039 }
1040
1041 server->CertificateFile = GetCombinedPath(filepath, "shadow.crt");
1042 server->PrivateKeyFile = GetCombinedPath(filepath, "shadow.key");
1043
1044 if (!server->CertificateFile || !server->PrivateKeyFile)
1045 goto out_fail;
1046
1047 if ((!winpr_PathFileExists(server->CertificateFile)) ||
1048 (!winpr_PathFileExists(server->PrivateKeyFile)))
1049 {
1050#if defined(WITH_WINPR_TOOLS)
1051 if (!shadow_server_create_certificate(server, filepath))
1052#else
1053 const char* cexist = winpr_PathFileExists(server->CertificateFile) ? "exists" : "not found";
1054 const char* pexist = winpr_PathFileExists(server->PrivateKeyFile) ? "exists" : "not found";
1055 WLog_ERR(TAG, "PEM files do not exist: cert: %s (%s), key: %s (%s)",
1056 server->CertificateFile, cexist, server->PrivateKeyFile, pexist);
1057#endif
1058 goto out_fail;
1059 }
1060
1061 ret = shadow_server_read_pem(server);
1062
1063out_fail:
1064 free(filepath);
1065 return ret;
1066}
1067
1068WINPR_ATTR_NODISCARD
1069static BOOL shadow_server_check_peer_restrictions(freerdp_listener* listener)
1070{
1071 WINPR_ASSERT(listener);
1072
1073 rdpShadowServer* server = (rdpShadowServer*)listener->info;
1074 WINPR_ASSERT(server);
1075
1076 if (server->maxClientsConnected > 0)
1077 {
1078 const size_t count = ArrayList_Count(server->clients);
1079 if (count >= server->maxClientsConnected)
1080 {
1081 WLog_WARN(TAG, "connection limit [%" PRIuz "] reached, discarding client",
1082 server->maxClientsConnected);
1083 return FALSE;
1084 }
1085 }
1086 return TRUE;
1087}
1088
1089int shadow_server_init(rdpShadowServer* server)
1090{
1091 int status = 0;
1092 if (!winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT))
1093 return -1;
1094 if (!WTSRegisterWtsApiFunctionTable(FreeRDP_InitWtsApi()))
1095 return -1;
1096
1097 if (!(server->clients = ArrayList_New(TRUE)))
1098 goto fail;
1099
1100 if (!(server->StopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr)))
1101 goto fail;
1102
1103 if (!InitializeCriticalSectionAndSpinCount(&(server->lock), 4000))
1104 goto fail;
1105
1106 status = shadow_server_init_config_path(server);
1107
1108 if (status < 0)
1109 goto fail;
1110
1111 if (!shadow_server_init_certificate(server))
1112 goto fail;
1113
1114 server->listener = freerdp_listener_new();
1115
1116 if (!server->listener)
1117 goto fail;
1118
1119 server->listener->info = (void*)server;
1120 server->listener->CheckPeerAcceptRestrictions = shadow_server_check_peer_restrictions;
1121 server->listener->PeerAccepted = shadow_client_accepted;
1122 server->subsystem = shadow_subsystem_new();
1123
1124 if (!server->subsystem)
1125 goto fail;
1126
1127 status = shadow_subsystem_init(server->subsystem, server);
1128 if (status < 0)
1129 goto fail;
1130
1131 return status;
1132
1133fail:
1134 shadow_server_uninit(server);
1135 WLog_ERR(TAG, "Failed to initialize shadow server");
1136 return -1;
1137}
1138
1139int shadow_server_uninit(rdpShadowServer* server)
1140{
1141 if (!server)
1142 return -1;
1143
1144 shadow_server_stop(server);
1145 shadow_subsystem_uninit(server->subsystem);
1146 shadow_subsystem_free(server->subsystem);
1147 server->subsystem = nullptr;
1148 freerdp_listener_free(server->listener);
1149 server->listener = nullptr;
1150 free(server->CertificateFile);
1151 server->CertificateFile = nullptr;
1152 free(server->PrivateKeyFile);
1153 server->PrivateKeyFile = nullptr;
1154 free(server->ConfigPath);
1155 server->ConfigPath = nullptr;
1156 DeleteCriticalSection(&(server->lock));
1157 (void)CloseHandle(server->StopEvent);
1158 server->StopEvent = nullptr;
1159 ArrayList_Free(server->clients);
1160 server->clients = nullptr;
1161 return 1;
1162}
1163
1164rdpShadowServer* shadow_server_new(void)
1165{
1166 rdpShadowServer* server = nullptr;
1167 server = (rdpShadowServer*)calloc(1, sizeof(rdpShadowServer));
1168
1169 if (!server)
1170 return nullptr;
1171
1172 server->SupportMultiRectBitmapUpdates = TRUE;
1173 server->port = 3389;
1174 server->mayView = TRUE;
1175 server->mayInteract = TRUE;
1176 server->h264RateControlMode = H264_RATECONTROL_VBR;
1177 server->h264BitRate = 10000000;
1178 server->h264FrameRate = 30;
1179 server->h264QP = 0;
1180 server->h264HwAccel = FALSE;
1181 server->authentication = TRUE;
1182#if defined(WITH_GFX_AV1)
1183 server->AV1BitRate = 3000; /* kbit/s */
1184 server->AV1RateControlMode = FREERDP_AV1_VBR;
1185#endif
1187 return server;
1188}
1189
1190void shadow_server_free(rdpShadowServer* server)
1191{
1192 if (!server)
1193 return;
1194
1195 free(server->ipcSocket);
1196 server->ipcSocket = nullptr;
1197 freerdp_settings_free(server->settings);
1198 server->settings = nullptr;
1199 free(server);
1200}
FREERDP_API rdpSettings * freerdp_settings_new(DWORD flags)
creates a new setting struct
WINPR_ATTR_NODISCARD 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
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_set_uint32(rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id, UINT32 val)
Sets a UINT32 settings value.
WINPR_ATTR_NODISCARD 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 UINT32 freerdp_settings_get_uint32(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id)
Returns a UINT32 settings value.
WINPR_ATTR_NODISCARD 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.
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_get_bool(const rdpSettings *settings, FreeRDP_Settings_Keys_Bool id)
Returns a boolean settings value.