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 */
624 long val = strtol(arg->Value, nullptr, 0);
625
626 if ((val < 0) || (errno != 0) || ((UINT32)val >= numMonitors))
627 status = COMMAND_LINE_STATUS_PRINT;
628
629 server->selectedMonitor = (UINT32)val;
630 }
631 else
632 {
633 /* List monitors */
634
635 for (UINT32 index = 0; index < numMonitors; index++)
636 {
637 const MONITOR_DEF* monitor = &monitors[index];
638 const INT64 width = monitor->right - monitor->left + 1;
639 const INT64 height = monitor->bottom - monitor->top + 1;
640 WLog_INFO(
641 TAG, " %s [%" PRIu32 "] %" PRId64 "x%" PRId64 "\t+%" PRId32 "+%" PRId32 "",
642 (monitor->flags == 1) ? "*" : " ", index, width, height, monitor->left,
643 monitor->top);
644 }
645
646 status = COMMAND_LINE_STATUS_PRINT;
647 }
648 }
649
650 /* If we want to disable authentication we need to ensure that NLA security
651 * is not activated. Only TLS and RDP security allow anonymous login.
652 */
653 if (!server->authentication && !freerdp_settings_get_bool(settings, FreeRDP_VmConnectMode))
654 {
655 if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE))
656 return COMMAND_LINE_ERROR;
657 if (!freerdp_settings_set_bool(settings, FreeRDP_ExtSecurity, FALSE))
658 return COMMAND_LINE_ERROR;
659 }
660 return status;
661}
662
663WINPR_ATTR_NODISCARD
664static DWORD WINAPI shadow_server_thread(LPVOID arg)
665{
666 rdpShadowServer* server = (rdpShadowServer*)arg;
667 BOOL running = TRUE;
668 DWORD status = 0;
669 freerdp_listener* listener = server->listener;
670 if (shadow_subsystem_start(server->subsystem) < 0)
671 running = FALSE;
672
673 while (running)
674 {
675 HANDLE events[MAXIMUM_WAIT_OBJECTS] = WINPR_C_ARRAY_INIT;
676 DWORD nCount = 0;
677 events[nCount++] = server->StopEvent;
678 nCount += listener->GetEventHandles(listener, &events[nCount], ARRAYSIZE(events) - nCount);
679
680 if (nCount <= 1)
681 {
682 WLog_ERR(TAG, "Failed to get FreeRDP file descriptor");
683 break;
684 }
685
686 status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
687
688 switch (status)
689 {
690 case WAIT_FAILED:
691 case WAIT_OBJECT_0:
692 running = FALSE;
693 break;
694
695 default:
696 {
697 if (!listener->CheckFileDescriptor(listener))
698 {
699 WLog_ERR(TAG, "Failed to check FreeRDP file descriptor");
700 running = FALSE;
701 }
702 else
703 {
704#ifdef _WIN32
705 Sleep(100); /* FIXME: listener event handles */
706#endif
707 }
708 }
709 break;
710 }
711 }
712
713 listener->Close(listener);
714 shadow_subsystem_stop(server->subsystem);
715
716 /* Signal to the clients that server is being stopped and wait for them
717 * to disconnect. */
718 if (shadow_client_boardcast_quit(server, 0))
719 {
720 while (ArrayList_Count(server->clients) > 0)
721 {
722 Sleep(100);
723 }
724 }
725
726 ExitThread(0);
727 return 0;
728}
729
730WINPR_ATTR_NODISCARD
731static BOOL open_port(rdpShadowServer* server, char* address)
732{
733 BOOL status = 0;
734 char* modaddr = address;
735
736 if (modaddr)
737 {
738 if (modaddr[0] == '[')
739 {
740 char* end = strchr(address, ']');
741 if (!end)
742 {
743 WLog_ERR(TAG, "Could not parse bind-address %s", address);
744 return -1;
745 }
746 *end++ = '\0';
747 if (strlen(end) > 0)
748 {
749 WLog_ERR(TAG, "Excess data after IPv6 address: '%s'", end);
750 return -1;
751 }
752 modaddr++;
753 }
754 }
755 status = server->listener->Open(server->listener, modaddr, (UINT16)server->port);
756
757 if (!status)
758 {
759 WLog_ERR(TAG,
760 "Problem creating TCP listener. (Port already used or insufficient permissions?)");
761 }
762
763 return status;
764}
765
766int shadow_server_start(rdpShadowServer* server)
767{
768 BOOL ipc = 0;
769 BOOL status = 0;
770 WSADATA wsaData;
771
772 if (!server)
773 return -1;
774
775 if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
776 return -1;
777
778#ifndef _WIN32
779 (void)signal(SIGPIPE, SIG_IGN);
780#endif
781 server->screen = shadow_screen_new(server);
782
783 if (!server->screen)
784 {
785 WLog_ERR(TAG, "screen_new failed");
786 return -1;
787 }
788
789 server->capture = shadow_capture_new(server);
790
791 if (!server->capture)
792 {
793 WLog_ERR(TAG, "capture_new failed");
794 return -1;
795 }
796
797 /* Bind magic:
798 *
799 * empty ... bind TCP all
800 * <local path> ... bind local (IPC)
801 * bind-socket,<address> ... bind TCP to specified interface
802 */
803 ipc = server->ipcSocket && (strncmp(bind_address, server->ipcSocket,
804 strnlen(bind_address, sizeof(bind_address))) != 0);
805 if (!ipc)
806 {
807 size_t count = 0;
808
809 char** ptr = CommandLineParseCommaSeparatedValuesEx(nullptr, server->ipcSocket, &count);
810 if (!ptr || (count <= 1))
811 {
812 if (server->ipcSocket == nullptr)
813 {
814 if (!open_port(server, nullptr))
815 {
816 CommandLineParserFree(ptr);
817 return -1;
818 }
819 }
820 else
821 {
822 CommandLineParserFree(ptr);
823 return -1;
824 }
825 }
826
827 WINPR_ASSERT(ptr || (count == 0));
828 for (size_t x = 1; x < count; x++)
829 {
830 BOOL success = open_port(server, ptr[x]);
831 if (!success)
832 {
833 CommandLineParserFree(ptr);
834 return -1;
835 }
836 }
837 CommandLineParserFree(ptr);
838 }
839 else
840 {
841 status = server->listener->OpenLocal(server->listener, server->ipcSocket);
842
843 if (!status)
844 {
845 WLog_ERR(TAG, "Problem creating local socket listener. (Port already used or "
846 "insufficient permissions?)");
847 return -1;
848 }
849 }
850
851 if (!(server->thread =
852 CreateThread(nullptr, 0, shadow_server_thread, (void*)server, 0, nullptr)))
853 {
854 return -1;
855 }
856
857 return 0;
858}
859
860int shadow_server_stop(rdpShadowServer* server)
861{
862 if (!server)
863 return -1;
864
865 if (server->thread)
866 {
867 (void)SetEvent(server->StopEvent);
868 (void)WaitForSingleObject(server->thread, INFINITE);
869 (void)CloseHandle(server->thread);
870 server->thread = nullptr;
871 if (server->listener && server->listener->Close)
872 server->listener->Close(server->listener);
873 }
874
875 if (server->screen)
876 {
877 shadow_screen_free(server->screen);
878 server->screen = nullptr;
879 }
880
881 if (server->capture)
882 {
883 shadow_capture_free(server->capture);
884 server->capture = nullptr;
885 }
886
887 return 0;
888}
889
890WINPR_ATTR_NODISCARD
891static int shadow_server_init_config_path(rdpShadowServer* server)
892{
893 if (!server->ConfigPath)
894 {
895 char* configHome = freerdp_settings_get_config_path();
896
897 if (configHome)
898 {
899 if (!winpr_PathFileExists(configHome) && !winpr_PathMakePath(configHome, nullptr))
900 {
901 WLog_ERR(TAG, "Failed to create directory '%s'", configHome);
902 free(configHome);
903 return -1;
904 }
905
906 server->ConfigPath = configHome;
907 }
908 }
909
910 if (!server->ConfigPath)
911 return -1; /* no usable config path */
912
913 return 1;
914}
915
916#if defined(WITH_WINPR_TOOLS)
917WINPR_ATTR_NODISCARD
918static BOOL shadow_server_create_certificate(rdpShadowServer* server, const char* filepath)
919{
920 BOOL rc = FALSE;
921 char* makecert_argv[6] = { "makecert", "-rdp", "-live", "-silent", "-y", "5" };
922
923 WINPR_STATIC_ASSERT(ARRAYSIZE(makecert_argv) <= INT_MAX);
924 const size_t makecert_argc = ARRAYSIZE(makecert_argv);
925
926 MAKECERT_CONTEXT* makecert = makecert_context_new();
927
928 if (!makecert)
929 goto out_fail;
930
931 if (makecert_context_process(makecert, (int)makecert_argc, makecert_argv) < 0)
932 goto out_fail;
933
934 if (makecert_context_set_output_file_name(makecert, "shadow") != 1)
935 goto out_fail;
936
937 WINPR_ASSERT(server);
938 WINPR_ASSERT(filepath);
939 if (!winpr_PathFileExists(server->CertificateFile))
940 {
941 if (makecert_context_output_certificate_file(makecert, filepath) != 1)
942 goto out_fail;
943 }
944
945 if (!winpr_PathFileExists(server->PrivateKeyFile))
946 {
947 if (makecert_context_output_private_key_file(makecert, filepath) != 1)
948 goto out_fail;
949 }
950 rc = TRUE;
951out_fail:
952 makecert_context_free(makecert);
953 return rc;
954}
955#endif
956
957WINPR_ATTR_NODISCARD
958static BOOL shadow_server_read_pem(rdpShadowServer* server)
959{
960 BOOL rc = FALSE;
961 WINPR_ASSERT(server);
962
963 rdpSettings* settings = server->settings;
964 WINPR_ASSERT(settings);
965
966 {
967 rdpPrivateKey* key = freerdp_key_new_from_file_enc(server->PrivateKeyFile, nullptr);
968 if (!key)
969 {
970 WLog_ERR(TAG, "Private Key PEM %s invalid, aborting", server->PrivateKeyFile);
971 return FALSE;
972 }
973
974 if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerRsaKey, key, 1))
975 {
976 WLog_ERR(TAG, "Private Key PEM %s valid, but some properties failed to be extracted",
977 server->PrivateKeyFile);
978 return FALSE;
979 }
980 }
981 {
982 rdpCertificate* cert = freerdp_certificate_new_from_file(server->CertificateFile);
983 if (!cert)
984 {
985 WLog_ERR(TAG, "Certificate PEM %s invalid, aborting", server->CertificateFile);
986 return FALSE;
987 }
988
989 if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerCertificate, cert, 1))
990 goto out_fail;
991
992 if (!freerdp_certificate_is_rdp_security_compatible(cert))
993 {
994 if (!freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, FALSE))
995 goto out_fail;
996 if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, FALSE))
997 goto out_fail;
998 }
999 }
1000 rc = TRUE;
1001
1002out_fail:
1003 WLog_ERR(TAG, "Certificate PEM %s valid, but some properties failed to be extracted",
1004 server->CertificateFile);
1005 return rc;
1006}
1007
1008WINPR_ATTR_NODISCARD
1009static BOOL shadow_server_init_certificate(rdpShadowServer* server)
1010{
1011 char* filepath = nullptr;
1012 BOOL ret = FALSE;
1013
1014 WINPR_ASSERT(server);
1015
1016 if (!winpr_PathFileExists(server->ConfigPath) &&
1017 !winpr_PathMakePath(server->ConfigPath, nullptr))
1018 {
1019 WLog_ERR(TAG, "Failed to create directory '%s'", server->ConfigPath);
1020 return FALSE;
1021 }
1022
1023 if (!(filepath = GetCombinedPath(server->ConfigPath, "shadow")))
1024 return FALSE;
1025
1026 if (!winpr_PathFileExists(filepath) && !winpr_PathMakePath(filepath, nullptr))
1027 {
1028 if (!winpr_PathMakePath(filepath, nullptr))
1029 {
1030 WLog_ERR(TAG, "Failed to create directory '%s'", filepath);
1031 goto out_fail;
1032 }
1033 }
1034
1035 server->CertificateFile = GetCombinedPath(filepath, "shadow.crt");
1036 server->PrivateKeyFile = GetCombinedPath(filepath, "shadow.key");
1037
1038 if (!server->CertificateFile || !server->PrivateKeyFile)
1039 goto out_fail;
1040
1041 if ((!winpr_PathFileExists(server->CertificateFile)) ||
1042 (!winpr_PathFileExists(server->PrivateKeyFile)))
1043 {
1044#if defined(WITH_WINPR_TOOLS)
1045 if (!shadow_server_create_certificate(server, filepath))
1046#else
1047 const char* cexist = winpr_PathFileExists(server->CertificateFile) ? "exists" : "not found";
1048 const char* pexist = winpr_PathFileExists(server->PrivateKeyFile) ? "exists" : "not found";
1049 WLog_ERR(TAG, "PEM files do not exist: cert: %s (%s), key: %s (%s)",
1050 server->CertificateFile, cexist, server->PrivateKeyFile, pexist);
1051#endif
1052 goto out_fail;
1053 }
1054
1055 ret = shadow_server_read_pem(server);
1056
1057out_fail:
1058 free(filepath);
1059 return ret;
1060}
1061
1062WINPR_ATTR_NODISCARD
1063static BOOL shadow_server_check_peer_restrictions(freerdp_listener* listener)
1064{
1065 WINPR_ASSERT(listener);
1066
1067 rdpShadowServer* server = (rdpShadowServer*)listener->info;
1068 WINPR_ASSERT(server);
1069
1070 if (server->maxClientsConnected > 0)
1071 {
1072 const size_t count = ArrayList_Count(server->clients);
1073 if (count >= server->maxClientsConnected)
1074 {
1075 WLog_WARN(TAG, "connection limit [%" PRIuz "] reached, discarding client",
1076 server->maxClientsConnected);
1077 return FALSE;
1078 }
1079 }
1080 return TRUE;
1081}
1082
1083int shadow_server_init(rdpShadowServer* server)
1084{
1085 int status = 0;
1086 if (!winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT))
1087 return -1;
1088 if (!WTSRegisterWtsApiFunctionTable(FreeRDP_InitWtsApi()))
1089 return -1;
1090
1091 if (!(server->clients = ArrayList_New(TRUE)))
1092 goto fail;
1093
1094 if (!(server->StopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr)))
1095 goto fail;
1096
1097 if (!InitializeCriticalSectionAndSpinCount(&(server->lock), 4000))
1098 goto fail;
1099
1100 status = shadow_server_init_config_path(server);
1101
1102 if (status < 0)
1103 goto fail;
1104
1105 if (!shadow_server_init_certificate(server))
1106 goto fail;
1107
1108 server->listener = freerdp_listener_new();
1109
1110 if (!server->listener)
1111 goto fail;
1112
1113 server->listener->info = (void*)server;
1114 server->listener->CheckPeerAcceptRestrictions = shadow_server_check_peer_restrictions;
1115 server->listener->PeerAccepted = shadow_client_accepted;
1116 server->subsystem = shadow_subsystem_new();
1117
1118 if (!server->subsystem)
1119 goto fail;
1120
1121 status = shadow_subsystem_init(server->subsystem, server);
1122 if (status < 0)
1123 goto fail;
1124
1125 return status;
1126
1127fail:
1128 shadow_server_uninit(server);
1129 WLog_ERR(TAG, "Failed to initialize shadow server");
1130 return -1;
1131}
1132
1133int shadow_server_uninit(rdpShadowServer* server)
1134{
1135 if (!server)
1136 return -1;
1137
1138 shadow_server_stop(server);
1139 shadow_subsystem_uninit(server->subsystem);
1140 shadow_subsystem_free(server->subsystem);
1141 server->subsystem = nullptr;
1142 freerdp_listener_free(server->listener);
1143 server->listener = nullptr;
1144 free(server->CertificateFile);
1145 server->CertificateFile = nullptr;
1146 free(server->PrivateKeyFile);
1147 server->PrivateKeyFile = nullptr;
1148 free(server->ConfigPath);
1149 server->ConfigPath = nullptr;
1150 DeleteCriticalSection(&(server->lock));
1151 (void)CloseHandle(server->StopEvent);
1152 server->StopEvent = nullptr;
1153 ArrayList_Free(server->clients);
1154 server->clients = nullptr;
1155 return 1;
1156}
1157
1158rdpShadowServer* shadow_server_new(void)
1159{
1160 rdpShadowServer* server = nullptr;
1161 server = (rdpShadowServer*)calloc(1, sizeof(rdpShadowServer));
1162
1163 if (!server)
1164 return nullptr;
1165
1166 server->SupportMultiRectBitmapUpdates = TRUE;
1167 server->port = 3389;
1168 server->mayView = TRUE;
1169 server->mayInteract = TRUE;
1170 server->h264RateControlMode = H264_RATECONTROL_VBR;
1171 server->h264BitRate = 10000000;
1172 server->h264FrameRate = 30;
1173 server->h264QP = 0;
1174 server->h264HwAccel = FALSE;
1175 server->authentication = TRUE;
1176#if defined(WITH_GFX_AV1)
1177 server->AV1BitRate = 500;
1178 server->AV1RateControlMode = FREERDP_AV1_VBR;
1179#endif
1181 return server;
1182}
1183
1184void shadow_server_free(rdpShadowServer* server)
1185{
1186 if (!server)
1187 return;
1188
1189 free(server->ipcSocket);
1190 server->ipcSocket = nullptr;
1191 freerdp_settings_free(server->settings);
1192 server->settings = nullptr;
1193 free(server);
1194}
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.