summaryrefslogtreecommitdiffstats
path: root/client/common
diff options
context:
space:
mode:
Diffstat (limited to 'client/common')
-rw-r--r--client/common/CMakeLists.txt110
-rw-r--r--client/common/client.c2156
-rw-r--r--client/common/client_cliprdr_file.c2556
-rw-r--r--client/common/cmdline.c5922
-rw-r--r--client/common/cmdline.h519
-rw-r--r--client/common/file.c2707
-rw-r--r--client/common/geometry.c43
-rw-r--r--client/common/man/CMakeLists.txt3
-rw-r--r--client/common/man/generate_argument_docbook.c210
-rw-r--r--client/common/smartcard_cli.c60
-rw-r--r--client/common/test/CMakeLists.txt30
-rw-r--r--client/common/test/TestClientChannels.c87
-rw-r--r--client/common/test/TestClientCmdLine.c263
-rw-r--r--client/common/test/TestClientRdpFile.c600
14 files changed, 15266 insertions, 0 deletions
diff --git a/client/common/CMakeLists.txt b/client/common/CMakeLists.txt
new file mode 100644
index 0000000..6040bf3
--- /dev/null
+++ b/client/common/CMakeLists.txt
@@ -0,0 +1,110 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Client Common
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set(MODULE_NAME "freerdp-client")
+set(MODULE_PREFIX "FREERDP_CLIENT")
+
+# Policy CMP0022: INTERFACE_LINK_LIBRARIES defines the link
+# interface. Run "cmake --help-policy CMP0022" for policy details. Use the
+# cmake_policy command to set the policy and suppress this warning.
+if(POLICY CMP0022)
+ cmake_policy(SET CMP0022 NEW)
+endif()
+
+set(SRCS
+ client.c
+ cmdline.c
+ cmdline.h
+ file.c
+ client_cliprdr_file.c
+ geometry.c
+ smartcard_cli.c)
+
+foreach(FREERDP_CHANNELS_CLIENT_SRC ${FREERDP_CHANNELS_CLIENT_SRCS})
+ get_filename_component(NINC ${FREERDP_CHANNELS_CLIENT_SRC} PATH)
+ include_directories(${NINC})
+ list(APPEND SRCS "${FREERDP_CHANNELS_CLIENT_SRC}")
+endforeach()
+
+if (NOT APPLE AND NOT WIN32 AND NOT ANDROID)
+ set(OPT_FUSE_DEFAULT ON)
+else()
+ set(OPT_FUSE_DEFAULT OFF)
+endif()
+
+option(WITH_FUSE "Build clipboard with FUSE file copy support" ${OPT_FUSE_DEFAULT})
+if(WITH_FUSE)
+ find_package(PkgConfig REQUIRED)
+
+ pkg_check_modules(FUSE3 REQUIRED fuse3)
+ include_directories(${FUSE3_INCLUDE_DIRS})
+ add_definitions(-DWITH_FUSE)
+ list(APPEND LIBS ${FUSE3_LIBRARIES})
+
+ add_definitions(-D_FILE_OFFSET_BITS=64)
+endif()
+
+# On windows create dll version information.
+# Vendor, product and year are already set in top level CMakeLists.txt
+if (WIN32 AND BUILD_SHARED_LIBS)
+ set (RC_VERSION_MAJOR ${FREERDP_VERSION_MAJOR})
+ set (RC_VERSION_MINOR ${FREERDP_VERSION_MINOR})
+ set (RC_VERSION_BUILD ${FREERDP_VERSION_REVISION})
+ set (RC_VERSION_FILE "${CMAKE_SHARED_LIBRARY_PREFIX}${MODULE_NAME}${FREERDP_API_VERSION}${CMAKE_SHARED_LIBRARY_SUFFIX}" )
+
+ configure_file(
+ ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+list (APPEND SRCS ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+endif()
+
+include_directories(${OPENSSL_INCLUDE_DIR})
+
+add_library(${MODULE_NAME} ${SRCS})
+
+set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME ${MODULE_NAME}${FREERDP_API_VERSION})
+if (WITH_LIBRARY_VERSIONING)
+ set_target_properties(${MODULE_NAME} PROPERTIES VERSION ${FREERDP_VERSION} SOVERSION ${FREERDP_API_VERSION})
+endif()
+
+list(APPEND LIBS freerdp winpr)
+
+target_include_directories(${MODULE_NAME} INTERFACE $<INSTALL_INTERFACE:include>)
+target_link_libraries(${MODULE_NAME} PRIVATE ${FREERDP_CHANNELS_CLIENT_LIBS})
+target_link_libraries(${MODULE_NAME} PUBLIC ${LIBS})
+
+install(TARGETS ${MODULE_NAME} COMPONENT libraries EXPORT FreeRDP-ClientTargets
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+if (WITH_DEBUG_SYMBOLS AND MSVC AND BUILD_SHARED_LIBS)
+ get_target_property(OUTPUT_FILENAME ${MODULE_NAME} OUTPUT_NAME)
+ install(FILES ${CMAKE_PDB_BINARY_DIR}/${OUTPUT_FILENAME}.pdb DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT symbols)
+endif()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Client/Common")
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
+
+if (WITH_MANPAGES)
+ add_subdirectory(man)
+endif()
diff --git a/client/common/client.c b/client/common/client.c
new file mode 100644
index 0000000..9d6ec03
--- /dev/null
+++ b/client/common/client.c
@@ -0,0 +1,2156 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Client Common
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+#include <float.h>
+
+#include <freerdp/client.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/addin.h>
+#include <freerdp/assistance.h>
+#include <freerdp/client/file.h>
+#include <freerdp/utils/passphrase.h>
+#include <freerdp/client/cmdline.h>
+#include <freerdp/client/channels.h>
+#include <freerdp/utils/smartcardlogon.h>
+
+#if defined(CHANNEL_AINPUT_CLIENT)
+#include <freerdp/client/ainput.h>
+#include <freerdp/channels/ainput.h>
+#endif
+
+#if defined(CHANNEL_VIDEO_CLIENT)
+#include <freerdp/client/video.h>
+#include <freerdp/channels/video.h>
+#endif
+
+#if defined(CHANNEL_RDPGFX_CLIENT)
+#include <freerdp/client/rdpgfx.h>
+#include <freerdp/channels/rdpgfx.h>
+#include <freerdp/gdi/gfx.h>
+#endif
+
+#if defined(CHANNEL_GEOMETRY_CLIENT)
+#include <freerdp/client/geometry.h>
+#include <freerdp/channels/geometry.h>
+#endif
+
+#if defined(CHANNEL_GEOMETRY_CLIENT) || defined(CHANNEL_VIDEO_CLIENT)
+#include <freerdp/gdi/video.h>
+#endif
+
+#ifdef WITH_AAD
+#include <freerdp/utils/http.h>
+#include <freerdp/utils/aad.h>
+#endif
+
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("common")
+
+static void set_default_callbacks(freerdp* instance)
+{
+ WINPR_ASSERT(instance);
+ instance->AuthenticateEx = client_cli_authenticate_ex;
+ instance->ChooseSmartcard = client_cli_choose_smartcard;
+ instance->VerifyCertificateEx = client_cli_verify_certificate_ex;
+ instance->VerifyChangedCertificateEx = client_cli_verify_changed_certificate_ex;
+ instance->PresentGatewayMessage = client_cli_present_gateway_message;
+ instance->LogonErrorInfo = client_cli_logon_error_info;
+ instance->GetAccessToken = client_cli_get_access_token;
+ instance->RetryDialog = client_common_retry_dialog;
+}
+
+static BOOL freerdp_client_common_new(freerdp* instance, rdpContext* context)
+{
+ RDP_CLIENT_ENTRY_POINTS* pEntryPoints = NULL;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(context);
+
+ instance->LoadChannels = freerdp_client_load_channels;
+ set_default_callbacks(instance);
+
+ pEntryPoints = instance->pClientEntryPoints;
+ WINPR_ASSERT(pEntryPoints);
+ return IFCALLRESULT(TRUE, pEntryPoints->ClientNew, instance, context);
+}
+
+static void freerdp_client_common_free(freerdp* instance, rdpContext* context)
+{
+ RDP_CLIENT_ENTRY_POINTS* pEntryPoints = NULL;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(context);
+
+ pEntryPoints = instance->pClientEntryPoints;
+ WINPR_ASSERT(pEntryPoints);
+ IFCALL(pEntryPoints->ClientFree, instance, context);
+}
+
+/* Common API */
+
+rdpContext* freerdp_client_context_new(const RDP_CLIENT_ENTRY_POINTS* pEntryPoints)
+{
+ freerdp* instance = NULL;
+ rdpContext* context = NULL;
+
+ if (!pEntryPoints)
+ return NULL;
+
+ IFCALL(pEntryPoints->GlobalInit);
+ instance = freerdp_new();
+
+ if (!instance)
+ return NULL;
+
+ instance->ContextSize = pEntryPoints->ContextSize;
+ instance->ContextNew = freerdp_client_common_new;
+ instance->ContextFree = freerdp_client_common_free;
+ instance->pClientEntryPoints = (RDP_CLIENT_ENTRY_POINTS*)malloc(pEntryPoints->Size);
+
+ if (!instance->pClientEntryPoints)
+ goto out_fail;
+
+ CopyMemory(instance->pClientEntryPoints, pEntryPoints, pEntryPoints->Size);
+
+ if (!freerdp_context_new_ex(instance, pEntryPoints->settings))
+ goto out_fail2;
+
+ context = instance->context;
+ context->instance = instance;
+
+#if defined(WITH_CHANNELS)
+ if (freerdp_register_addin_provider(freerdp_channels_load_static_addin_entry, 0) !=
+ CHANNEL_RC_OK)
+ goto out_fail2;
+#endif
+
+ return context;
+out_fail2:
+ free(instance->pClientEntryPoints);
+out_fail:
+ freerdp_free(instance);
+ return NULL;
+}
+
+void freerdp_client_context_free(rdpContext* context)
+{
+ freerdp* instance = NULL;
+
+ if (!context)
+ return;
+
+ instance = context->instance;
+
+ if (instance)
+ {
+ RDP_CLIENT_ENTRY_POINTS* pEntryPoints = instance->pClientEntryPoints;
+ freerdp_context_free(instance);
+
+ if (pEntryPoints)
+ IFCALL(pEntryPoints->GlobalUninit);
+
+ free(instance->pClientEntryPoints);
+ freerdp_free(instance);
+ }
+}
+
+int freerdp_client_start(rdpContext* context)
+{
+ RDP_CLIENT_ENTRY_POINTS* pEntryPoints = NULL;
+
+ if (!context || !context->instance || !context->instance->pClientEntryPoints)
+ return ERROR_BAD_ARGUMENTS;
+
+ if (freerdp_settings_get_bool(context->settings, FreeRDP_UseCommonStdioCallbacks))
+ set_default_callbacks(context->instance);
+
+ pEntryPoints = context->instance->pClientEntryPoints;
+ return IFCALLRESULT(CHANNEL_RC_OK, pEntryPoints->ClientStart, context);
+}
+
+int freerdp_client_stop(rdpContext* context)
+{
+ RDP_CLIENT_ENTRY_POINTS* pEntryPoints = NULL;
+
+ if (!context || !context->instance || !context->instance->pClientEntryPoints)
+ return ERROR_BAD_ARGUMENTS;
+
+ pEntryPoints = context->instance->pClientEntryPoints;
+ return IFCALLRESULT(CHANNEL_RC_OK, pEntryPoints->ClientStop, context);
+}
+
+freerdp* freerdp_client_get_instance(rdpContext* context)
+{
+ if (!context || !context->instance)
+ return NULL;
+
+ return context->instance;
+}
+
+HANDLE freerdp_client_get_thread(rdpContext* context)
+{
+ if (!context)
+ return NULL;
+
+ return ((rdpClientContext*)context)->thread;
+}
+
+static BOOL freerdp_client_settings_post_process(rdpSettings* settings)
+{
+ /* Moved GatewayUseSameCredentials logic outside of cmdline.c, so
+ * that the rdp file also triggers this functionality */
+ if (freerdp_settings_get_bool(settings, FreeRDP_GatewayEnabled))
+ {
+ if (freerdp_settings_get_bool(settings, FreeRDP_GatewayUseSameCredentials))
+ {
+ const char* Username = freerdp_settings_get_string(settings, FreeRDP_Username);
+ const char* Domain = freerdp_settings_get_string(settings, FreeRDP_Domain);
+ if (Username)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayUsername, Username))
+ goto out_error;
+ }
+
+ if (Domain)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayDomain, Domain))
+ goto out_error;
+ }
+
+ if (freerdp_settings_get_string(settings, FreeRDP_Password))
+ {
+ if (!freerdp_settings_set_string(
+ settings, FreeRDP_GatewayPassword,
+ freerdp_settings_get_string(settings, FreeRDP_Password)))
+ goto out_error;
+ }
+ }
+ }
+
+ /* Moved logic for Multimon and Span monitors to force fullscreen, so
+ * that the rdp file also triggers this functionality */
+ if (freerdp_settings_get_bool(settings, FreeRDP_SpanMonitors))
+ {
+ freerdp_settings_set_bool(settings, FreeRDP_UseMultimon, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_Fullscreen, TRUE);
+ }
+ else if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
+ {
+ freerdp_settings_set_bool(settings, FreeRDP_Fullscreen, TRUE);
+ }
+
+ /* deal with the smartcard / smartcard logon stuff */
+ if (freerdp_settings_get_bool(settings, FreeRDP_SmartcardLogon))
+ {
+ freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_RedirectSmartCards, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE);
+ freerdp_settings_set_bool(settings, FreeRDP_PasswordIsSmartcardPin, TRUE);
+ }
+
+ return TRUE;
+out_error:
+ return FALSE;
+}
+
+int freerdp_client_settings_parse_command_line(rdpSettings* settings, int argc, char** argv,
+ BOOL allowUnknown)
+{
+ int status = 0;
+
+ if (argc < 1)
+ return 0;
+
+ if (!argv)
+ return -1;
+
+ status =
+ freerdp_client_settings_parse_command_line_arguments(settings, argc, argv, allowUnknown);
+
+ if (status < 0)
+ return status;
+
+ /* This function will call logic that is applicable to the settings
+ * from command line parsing AND the rdp file parsing */
+ if (!freerdp_client_settings_post_process(settings))
+ status = -1;
+
+ WLog_DBG(TAG, "This is %s %s", freerdp_get_version_string(), freerdp_get_build_config());
+ return status;
+}
+
+int freerdp_client_settings_parse_connection_file(rdpSettings* settings, const char* filename)
+{
+ rdpFile* file = NULL;
+ int ret = -1;
+ file = freerdp_client_rdp_file_new();
+
+ if (!file)
+ return -1;
+
+ if (!freerdp_client_parse_rdp_file(file, filename))
+ goto out;
+
+ if (!freerdp_client_populate_settings_from_rdp_file(file, settings))
+ goto out;
+
+ ret = 0;
+out:
+ freerdp_client_rdp_file_free(file);
+ return ret;
+}
+
+int freerdp_client_settings_parse_connection_file_buffer(rdpSettings* settings, const BYTE* buffer,
+ size_t size)
+{
+ rdpFile* file = NULL;
+ int status = -1;
+ file = freerdp_client_rdp_file_new();
+
+ if (!file)
+ return -1;
+
+ if (freerdp_client_parse_rdp_file_buffer(file, buffer, size) &&
+ freerdp_client_populate_settings_from_rdp_file(file, settings))
+ {
+ status = 0;
+ }
+
+ freerdp_client_rdp_file_free(file);
+ return status;
+}
+
+int freerdp_client_settings_write_connection_file(const rdpSettings* settings, const char* filename,
+ BOOL unicode)
+{
+ rdpFile* file = NULL;
+ int ret = -1;
+ file = freerdp_client_rdp_file_new();
+
+ if (!file)
+ return -1;
+
+ if (!freerdp_client_populate_rdp_file_from_settings(file, settings))
+ goto out;
+
+ if (!freerdp_client_write_rdp_file(file, filename, unicode))
+ goto out;
+
+ ret = 0;
+out:
+ freerdp_client_rdp_file_free(file);
+ return ret;
+}
+
+int freerdp_client_settings_parse_assistance_file(rdpSettings* settings, int argc, char* argv[])
+{
+ int status = 0;
+ int ret = -1;
+ char* filename = NULL;
+ char* password = NULL;
+ rdpAssistanceFile* file = NULL;
+
+ if (!settings || !argv || (argc < 2))
+ return -1;
+
+ filename = argv[1];
+
+ for (int x = 2; x < argc; x++)
+ {
+ const char* key = strstr(argv[x], "assistance:");
+
+ if (key)
+ password = strchr(key, ':') + 1;
+ }
+
+ file = freerdp_assistance_file_new();
+
+ if (!file)
+ return -1;
+
+ status = freerdp_assistance_parse_file(file, filename, password);
+
+ if (status < 0)
+ goto out;
+
+ if (!freerdp_assistance_populate_settings_from_assistance_file(file, settings))
+ goto out;
+
+ ret = 0;
+out:
+ freerdp_assistance_file_free(file);
+ return ret;
+}
+
+/** Callback set in the rdp_freerdp structure, and used to get the user's password,
+ * if required to establish the connection.
+ * This function is actually called in credssp_ntlmssp_client_init()
+ * @see rdp_server_accept_nego() and rdp_check_fds()
+ * @param instance - pointer to the rdp_freerdp structure that contains the connection settings
+ * @param username - unused
+ * @param password - on return: pointer to a character string that will be filled by the password
+ * entered by the user. Note that this character string will be allocated inside the function, and
+ * needs to be deallocated by the caller using free(), even in case this function fails.
+ * @param domain - unused
+ * @return TRUE if a password was successfully entered. See freerdp_passphrase_read() for more
+ * details.
+ */
+static BOOL client_cli_authenticate_raw(freerdp* instance, rdp_auth_reason reason, char** username,
+ char** password, char** domain)
+{
+ static const size_t password_size = 512;
+ const char* auth[] = { "Username: ", "Domain: ", "Password: " };
+ const char* authPin[] = { "Username: ", "Domain: ", "Smartcard-Pin: " };
+ const char* gw[] = { "GatewayUsername: ", "GatewayDomain: ", "GatewayPassword: " };
+ const char** prompt = NULL;
+ BOOL pinOnly = FALSE;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+ WINPR_ASSERT(instance->context->settings);
+
+ switch (reason)
+ {
+ case AUTH_SMARTCARD_PIN:
+ prompt = authPin;
+ pinOnly = TRUE;
+ break;
+ case AUTH_TLS:
+ case AUTH_RDP:
+ case AUTH_NLA:
+ prompt = auth;
+ break;
+ case GW_AUTH_HTTP:
+ case GW_AUTH_RDG:
+ case GW_AUTH_RPC:
+ prompt = gw;
+ break;
+ default:
+ return FALSE;
+ }
+
+ if (!username || !password || !domain)
+ return FALSE;
+
+ if (!*username && !pinOnly)
+ {
+ size_t username_size = 0;
+ printf("%s", prompt[0]);
+ fflush(stdout);
+
+ if (freerdp_interruptible_get_line(instance->context, username, &username_size, stdin) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "freerdp_interruptible_get_line returned %s [%d]",
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+ goto fail;
+ }
+
+ if (*username)
+ {
+ *username = StrSep(username, "\r");
+ *username = StrSep(username, "\n");
+ }
+ }
+
+ if (!*domain && !pinOnly)
+ {
+ size_t domain_size = 0;
+ printf("%s", prompt[1]);
+ fflush(stdout);
+
+ if (freerdp_interruptible_get_line(instance->context, domain, &domain_size, stdin) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "freerdp_interruptible_get_line returned %s [%d]",
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+ goto fail;
+ }
+
+ if (*domain)
+ {
+ *domain = StrSep(domain, "\r");
+ *domain = StrSep(domain, "\n");
+ }
+ }
+
+ if (!*password)
+ {
+ *password = calloc(password_size, sizeof(char));
+
+ if (!*password)
+ goto fail;
+
+ const BOOL fromStdin =
+ freerdp_settings_get_bool(instance->context->settings, FreeRDP_CredentialsFromStdin);
+ if (freerdp_passphrase_read(instance->context, prompt[2], *password, password_size,
+ fromStdin) == NULL)
+ goto fail;
+ }
+
+ return TRUE;
+fail:
+ free(*username);
+ free(*domain);
+ free(*password);
+ *username = NULL;
+ *domain = NULL;
+ *password = NULL;
+ return FALSE;
+}
+
+BOOL client_cli_authenticate_ex(freerdp* instance, char** username, char** password, char** domain,
+ rdp_auth_reason reason)
+{
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(username);
+ WINPR_ASSERT(password);
+ WINPR_ASSERT(domain);
+
+ switch (reason)
+ {
+ case AUTH_NLA:
+ break;
+
+ case AUTH_TLS:
+ case AUTH_RDP:
+ case AUTH_SMARTCARD_PIN: /* in this case password is pin code */
+ if ((*username) && (*password))
+ return TRUE;
+ break;
+ case GW_AUTH_HTTP:
+ case GW_AUTH_RDG:
+ case GW_AUTH_RPC:
+ break;
+ default:
+ return FALSE;
+ }
+
+ return client_cli_authenticate_raw(instance, reason, username, password, domain);
+}
+
+BOOL client_cli_choose_smartcard(freerdp* instance, SmartcardCertInfo** cert_list, DWORD count,
+ DWORD* choice, BOOL gateway)
+{
+ unsigned long answer = 0;
+ char* p = NULL;
+
+ printf("Multiple smartcards are available for use:\n");
+ for (DWORD i = 0; i < count; i++)
+ {
+ const SmartcardCertInfo* cert = cert_list[i];
+ char* reader = ConvertWCharToUtf8Alloc(cert->reader, NULL);
+ char* container_name = ConvertWCharToUtf8Alloc(cert->containerName, NULL);
+
+ printf("[%" PRIu32
+ "] %s\n\tReader: %s\n\tUser: %s@%s\n\tSubject: %s\n\tIssuer: %s\n\tUPN: %s\n",
+ i, container_name, reader, cert->userHint, cert->domainHint, cert->subject,
+ cert->issuer, cert->upn);
+
+ free(reader);
+ free(container_name);
+ }
+
+ while (1)
+ {
+ char input[10] = { 0 };
+
+ printf("\nChoose a smartcard to use for %s (0 - %" PRIu32 "): ",
+ gateway ? "gateway authentication" : "logon", count - 1);
+ fflush(stdout);
+ if (!fgets(input, 10, stdin))
+ {
+ WLog_ERR(TAG, "could not read from stdin");
+ return FALSE;
+ }
+
+ answer = strtoul(input, &p, 10);
+ if ((*p == '\n' && p != input) && answer < count)
+ {
+ *choice = answer;
+ return TRUE;
+ }
+ }
+}
+
+#if defined(WITH_FREERDP_DEPRECATED)
+BOOL client_cli_authenticate(freerdp* instance, char** username, char** password, char** domain)
+{
+ if (freerdp_settings_get_bool(instance->settings, FreeRDP_SmartcardLogon))
+ {
+ WLog_INFO(TAG, "Authentication via smartcard");
+ return TRUE;
+ }
+
+ return client_cli_authenticate_raw(instance, FALSE, username, password, domain);
+}
+
+BOOL client_cli_gw_authenticate(freerdp* instance, char** username, char** password, char** domain)
+{
+ return client_cli_authenticate_raw(instance, TRUE, username, password, domain);
+}
+#endif
+
+static DWORD client_cli_accept_certificate(freerdp* instance)
+{
+ int answer = 0;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+
+ const rdpSettings* settings = instance->context->settings;
+ WINPR_ASSERT(settings);
+
+ const BOOL fromStdin =
+ freerdp_settings_get_bool(instance->context->settings, FreeRDP_CredentialsFromStdin);
+ if (fromStdin)
+ return 0;
+
+ while (1)
+ {
+ printf("Do you trust the above certificate? (Y/T/N) ");
+ fflush(stdout);
+ answer = freerdp_interruptible_getc(instance->context, stdin);
+
+ if ((answer == EOF) || feof(stdin))
+ {
+ printf("\nError: Could not read answer from stdin.");
+
+ if (fromStdin)
+ printf(" - Run without parameter \"--from-stdin\" to set trust.");
+
+ printf("\n");
+ return 0;
+ }
+
+ switch (answer)
+ {
+ case 'y':
+ case 'Y':
+ answer = freerdp_interruptible_getc(instance->context, stdin);
+ if (answer == EOF)
+ return 0;
+ return 1;
+
+ case 't':
+ case 'T':
+ answer = freerdp_interruptible_getc(instance->context, stdin);
+ if (answer == EOF)
+ return 0;
+ return 2;
+
+ case 'n':
+ case 'N':
+ answer = freerdp_interruptible_getc(instance->context, stdin);
+ if (answer == EOF)
+ return 0;
+ return 0;
+
+ default:
+ break;
+ }
+
+ printf("\n");
+ }
+}
+
+/** Callback set in the rdp_freerdp structure, and used to make a certificate validation
+ * when the connection requires it.
+ * This function will actually be called by tls_verify_certificate().
+ * @see rdp_client_connect() and freerdp_tls_connect()
+ * @deprecated Use client_cli_verify_certificate_ex
+ * @param instance - pointer to the rdp_freerdp structure that contains the connection settings
+ * @param common_name
+ * @param subject
+ * @param issuer
+ * @param fingerprint
+ * @param host_mismatch Indicates the certificate host does not match.
+ * @return 1 if the certificate is trusted, 2 if temporary trusted, 0 otherwise.
+ */
+#if defined(WITH_FREERDP_DEPRECATED)
+DWORD client_cli_verify_certificate(freerdp* instance, const char* common_name, const char* subject,
+ const char* issuer, const char* fingerprint, BOOL host_mismatch)
+{
+ WINPR_UNUSED(common_name);
+ WINPR_UNUSED(host_mismatch);
+
+ printf("WARNING: This callback is deprecated, migrate to client_cli_verify_certificate_ex\n");
+ printf("Certificate details:\n");
+ printf("\tSubject: %s\n", subject);
+ printf("\tIssuer: %s\n", issuer);
+ printf("\tThumbprint: %s\n", fingerprint);
+ printf("The above X.509 certificate could not be verified, possibly because you do not have\n"
+ "the CA certificate in your certificate store, or the certificate has expired.\n"
+ "Please look at the OpenSSL documentation on how to add a private CA to the store.\n");
+ return client_cli_accept_certificate(instance);
+}
+#endif
+
+/** Callback set in the rdp_freerdp structure, and used to make a certificate validation
+ * when the connection requires it.
+ * This function will actually be called by tls_verify_certificate().
+ * @see rdp_client_connect() and freerdp_tls_connect()
+ * @param instance pointer to the rdp_freerdp structure that contains the connection settings
+ * @param host The host currently connecting to
+ * @param port The port currently connecting to
+ * @param common_name The common name of the certificate, should match host or an alias of it
+ * @param subject The subject of the certificate
+ * @param issuer The certificate issuer name
+ * @param fingerprint The fingerprint of the certificate
+ * @param flags See VERIFY_CERT_FLAG_* for possible values.
+ *
+ * @return 1 if the certificate is trusted, 2 if temporary trusted, 0 otherwise.
+ */
+DWORD client_cli_verify_certificate_ex(freerdp* instance, const char* host, UINT16 port,
+ const char* common_name, const char* subject,
+ const char* issuer, const char* fingerprint, DWORD flags)
+{
+ const char* type = "RDP-Server";
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+ WINPR_ASSERT(instance->context->settings);
+
+ if (flags & VERIFY_CERT_FLAG_GATEWAY)
+ type = "RDP-Gateway";
+
+ if (flags & VERIFY_CERT_FLAG_REDIRECT)
+ type = "RDP-Redirect";
+
+ printf("Certificate details for %s:%" PRIu16 " (%s):\n", host, port, type);
+ printf("\tCommon Name: %s\n", common_name);
+ printf("\tSubject: %s\n", subject);
+ printf("\tIssuer: %s\n", issuer);
+ /* Newer versions of FreeRDP allow exposing the whole PEM by setting
+ * FreeRDP_CertificateCallbackPreferPEM to TRUE
+ */
+ if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
+ {
+ printf("\t----------- Certificate --------------\n");
+ printf("%s\n", fingerprint);
+ printf("\t--------------------------------------\n");
+ }
+ else
+ printf("\tThumbprint: %s\n", fingerprint);
+
+ printf("The above X.509 certificate could not be verified, possibly because you do not have\n"
+ "the CA certificate in your certificate store, or the certificate has expired.\n"
+ "Please look at the OpenSSL documentation on how to add a private CA to the store.\n");
+ return client_cli_accept_certificate(instance);
+}
+
+/** Callback set in the rdp_freerdp structure, and used to make a certificate validation
+ * when a stored certificate does not match the remote counterpart.
+ * This function will actually be called by tls_verify_certificate().
+ * @see rdp_client_connect() and freerdp_tls_connect()
+ * @deprecated Use client_cli_verify_changed_certificate_ex
+ * @param instance - pointer to the rdp_freerdp structure that contains the connection settings
+ * @param common_name
+ * @param subject
+ * @param issuer
+ * @param fingerprint
+ * @param old_subject
+ * @param old_issuer
+ * @param old_fingerprint
+ * @return 1 if the certificate is trusted, 2 if temporary trusted, 0 otherwise.
+ */
+#if defined(WITH_FREERDP_DEPRECATED)
+DWORD client_cli_verify_changed_certificate(freerdp* instance, const char* common_name,
+ const char* subject, const char* issuer,
+ const char* fingerprint, const char* old_subject,
+ const char* old_issuer, const char* old_fingerprint)
+{
+ WINPR_UNUSED(common_name);
+
+ printf("WARNING: This callback is deprecated, migrate to "
+ "client_cli_verify_changed_certificate_ex\n");
+ printf("!!! Certificate has changed !!!\n");
+ printf("\n");
+ printf("New Certificate details:\n");
+ printf("\tSubject: %s\n", subject);
+ printf("\tIssuer: %s\n", issuer);
+ printf("\tThumbprint: %s\n", fingerprint);
+ printf("\n");
+ printf("Old Certificate details:\n");
+ printf("\tSubject: %s\n", old_subject);
+ printf("\tIssuer: %s\n", old_issuer);
+ printf("\tThumbprint: %s\n", old_fingerprint);
+ printf("\n");
+ printf("The above X.509 certificate does not match the certificate used for previous "
+ "connections.\n"
+ "This may indicate that the certificate has been tampered with.\n"
+ "Please contact the administrator of the RDP server and clarify.\n");
+ return client_cli_accept_certificate(instance);
+}
+#endif
+
+/** Callback set in the rdp_freerdp structure, and used to make a certificate validation
+ * when a stored certificate does not match the remote counterpart.
+ * This function will actually be called by tls_verify_certificate().
+ * @see rdp_client_connect() and freerdp_tls_connect()
+ * @param instance pointer to the rdp_freerdp structure that contains the connection
+ * settings
+ * @param host The host currently connecting to
+ * @param port The port currently connecting to
+ * @param common_name The common name of the certificate, should match host or an alias of it
+ * @param subject The subject of the certificate
+ * @param issuer The certificate issuer name
+ * @param fingerprint The fingerprint of the certificate
+ * @param old_subject The subject of the previous certificate
+ * @param old_issuer The previous certificate issuer name
+ * @param old_fingerprint The fingerprint of the previous certificate
+ * @param flags See VERIFY_CERT_FLAG_* for possible values.
+ *
+ * @return 1 if the certificate is trusted, 2 if temporary trusted, 0 otherwise.
+ */
+DWORD client_cli_verify_changed_certificate_ex(freerdp* instance, const char* host, UINT16 port,
+ const char* common_name, const char* subject,
+ const char* issuer, const char* fingerprint,
+ const char* old_subject, const char* old_issuer,
+ const char* old_fingerprint, DWORD flags)
+{
+ const char* type = "RDP-Server";
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+ WINPR_ASSERT(instance->context->settings);
+
+ if (flags & VERIFY_CERT_FLAG_GATEWAY)
+ type = "RDP-Gateway";
+
+ if (flags & VERIFY_CERT_FLAG_REDIRECT)
+ type = "RDP-Redirect";
+
+ printf("!!!Certificate for %s:%" PRIu16 " (%s) has changed!!!\n", host, port, type);
+ printf("\n");
+ printf("New Certificate details:\n");
+ printf("\tCommon Name: %s\n", common_name);
+ printf("\tSubject: %s\n", subject);
+ printf("\tIssuer: %s\n", issuer);
+ /* Newer versions of FreeRDP allow exposing the whole PEM by setting
+ * FreeRDP_CertificateCallbackPreferPEM to TRUE
+ */
+ if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
+ {
+ printf("\t----------- Certificate --------------\n");
+ printf("%s\n", fingerprint);
+ printf("\t--------------------------------------\n");
+ }
+ else
+ printf("\tThumbprint: %s\n", fingerprint);
+ printf("\n");
+ printf("Old Certificate details:\n");
+ printf("\tSubject: %s\n", old_subject);
+ printf("\tIssuer: %s\n", old_issuer);
+ /* Newer versions of FreeRDP allow exposing the whole PEM by setting
+ * FreeRDP_CertificateCallbackPreferPEM to TRUE
+ */
+ if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
+ {
+ printf("\t----------- Certificate --------------\n");
+ printf("%s\n", old_fingerprint);
+ printf("\t--------------------------------------\n");
+ }
+ else
+ printf("\tThumbprint: %s\n", old_fingerprint);
+ printf("\n");
+ if (flags & VERIFY_CERT_FLAG_MATCH_LEGACY_SHA1)
+ {
+ printf("\tA matching entry with legacy SHA1 was found in local known_hosts2 store.\n");
+ printf("\tIf you just upgraded from a FreeRDP version before 2.0 this is expected.\n");
+ printf("\tThe hashing algorithm has been upgraded from SHA1 to SHA256.\n");
+ printf("\tAll manually accepted certificates must be reconfirmed!\n");
+ printf("\n");
+ }
+ printf("The above X.509 certificate does not match the certificate used for previous "
+ "connections.\n"
+ "This may indicate that the certificate has been tampered with.\n"
+ "Please contact the administrator of the RDP server and clarify.\n");
+ return client_cli_accept_certificate(instance);
+}
+
+BOOL client_cli_present_gateway_message(freerdp* instance, UINT32 type, BOOL isDisplayMandatory,
+ BOOL isConsentMandatory, size_t length,
+ const WCHAR* message)
+{
+ int answer = 0;
+ const char* msgType = (type == GATEWAY_MESSAGE_CONSENT) ? "Consent message" : "Service message";
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+ WINPR_ASSERT(instance->context->settings);
+
+ if (!isDisplayMandatory && !isConsentMandatory)
+ return TRUE;
+
+ printf("%s:\n", msgType);
+#if defined(WIN32)
+ printf("%.*S\n", (int)length, message);
+#else
+ {
+ LPSTR msg = ConvertWCharNToUtf8Alloc(message, length / sizeof(WCHAR), NULL);
+ if (!msg)
+ {
+ printf("Failed to convert message!\n");
+ return FALSE;
+ }
+ printf("%s\n", msg);
+ free(msg);
+ }
+#endif
+
+ while (isConsentMandatory)
+ {
+ printf("I understand and agree to the terms of this policy (Y/N) \n");
+ fflush(stdout);
+ answer = freerdp_interruptible_getc(instance->context, stdin);
+
+ if ((answer == EOF) || feof(stdin))
+ {
+ printf("\nError: Could not read answer from stdin.\n");
+ return FALSE;
+ }
+
+ switch (answer)
+ {
+ case 'y':
+ case 'Y':
+ answer = freerdp_interruptible_getc(instance->context, stdin);
+ if (answer == EOF)
+ return FALSE;
+ return TRUE;
+
+ case 'n':
+ case 'N':
+ freerdp_interruptible_getc(instance->context, stdin);
+ return FALSE;
+
+ default:
+ break;
+ }
+
+ printf("\n");
+ }
+
+ return TRUE;
+}
+
+static char* extract_authorization_code(char* url)
+{
+ WINPR_ASSERT(url);
+
+ for (char* p = strchr(url, '?'); p++ != NULL; p = strchr(p, '&'))
+ {
+ if (strncmp(p, "code=", 5) != 0)
+ continue;
+
+ char* end = NULL;
+ p += 5;
+
+ end = strchr(p, '&');
+ if (end)
+ *end = 0;
+ else
+ end = strchr(p, '\0');
+
+ return p;
+ }
+
+ return NULL;
+}
+
+static BOOL client_cli_get_rdsaad_access_token(freerdp* instance, const char* scope,
+ const char* req_cnf, char** token)
+{
+ size_t size = 0;
+ char* url = NULL;
+ char* token_request = NULL;
+ const char* client_id = "a85cf173-4192-42f8-81fa-777a763e6e2c";
+ const char* redirect_uri =
+ "https%3A%2F%2Flogin.microsoftonline.com%2Fcommon%2Foauth2%2Fnativeclient";
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(scope);
+ WINPR_ASSERT(req_cnf);
+ WINPR_ASSERT(token);
+
+ *token = NULL;
+
+ printf("Browse to: https://login.microsoftonline.com/common/oauth2/v2.0/"
+ "authorize?client_id=%s&response_type="
+ "code&scope=%s&redirect_uri=%s"
+ "\n",
+ client_id, scope, redirect_uri);
+ printf("Paste redirect URL here: \n");
+
+ if (freerdp_interruptible_get_line(instance->context, &url, &size, stdin) < 0)
+ return FALSE;
+
+ BOOL rc = FALSE;
+ char* code = extract_authorization_code(url);
+ if (!code)
+ goto cleanup;
+
+ if (winpr_asprintf(&token_request, &size,
+ "grant_type=authorization_code&code=%s&client_id=%s&scope=%s&redirect_uri=%"
+ "s&req_cnf=%s",
+ code, client_id, scope, redirect_uri, req_cnf) <= 0)
+ goto cleanup;
+
+ rc = client_common_get_access_token(instance, token_request, token);
+
+cleanup:
+ free(token_request);
+ free(url);
+ return rc && (*token != NULL);
+}
+
+static BOOL client_cli_get_avd_access_token(freerdp* instance, char** token)
+{
+ size_t size = 0;
+ char* url = NULL;
+ char* token_request = NULL;
+ const char* client_id = "a85cf173-4192-42f8-81fa-777a763e6e2c";
+ const char* redirect_uri =
+ "https%3A%2F%2Flogin.microsoftonline.com%2Fcommon%2Foauth2%2Fnativeclient";
+ const char* scope = "https%3A%2F%2Fwww.wvd.microsoft.com%2F.default";
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(token);
+
+ *token = NULL;
+
+ printf("Browse to: https://login.microsoftonline.com/common/oauth2/v2.0/"
+ "authorize?client_id=%s&response_type="
+ "code&scope=%s&redirect_uri=%s"
+ "\n",
+ client_id, scope, redirect_uri);
+ printf("Paste redirect URL here: \n");
+
+ if (freerdp_interruptible_get_line(instance->context, &url, &size, stdin) < 0)
+ return FALSE;
+
+ BOOL rc = FALSE;
+ char* code = extract_authorization_code(url);
+ if (!code)
+ goto cleanup;
+
+ if (winpr_asprintf(
+ &token_request, &size,
+ "grant_type=authorization_code&code=%s&client_id=%s&scope=%s&redirect_uri=%s", code,
+ client_id, scope, redirect_uri) <= 0)
+ goto cleanup;
+
+ rc = client_common_get_access_token(instance, token_request, token);
+
+cleanup:
+ free(token_request);
+ free(url);
+ return rc && (*token != NULL);
+}
+
+BOOL client_cli_get_access_token(freerdp* instance, AccessTokenType tokenType, char** token,
+ size_t count, ...)
+{
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(token);
+ switch (tokenType)
+ {
+ case ACCESS_TOKEN_TYPE_AAD:
+ {
+ if (count < 2)
+ {
+ WLog_ERR(TAG,
+ "ACCESS_TOKEN_TYPE_AAD expected 2 additional arguments, but got %" PRIuz
+ ", aborting",
+ count);
+ return FALSE;
+ }
+ else if (count > 2)
+ WLog_WARN(TAG,
+ "ACCESS_TOKEN_TYPE_AAD expected 2 additional arguments, but got %" PRIuz
+ ", ignoring",
+ count);
+ va_list ap;
+ va_start(ap, count);
+ const char* scope = va_arg(ap, const char*);
+ const char* req_cnf = va_arg(ap, const char*);
+ const BOOL rc = client_cli_get_rdsaad_access_token(instance, scope, req_cnf, token);
+ va_end(ap);
+ return rc;
+ }
+ case ACCESS_TOKEN_TYPE_AVD:
+ if (count != 0)
+ WLog_WARN(TAG,
+ "ACCESS_TOKEN_TYPE_AVD expected 0 additional arguments, but got %" PRIuz
+ ", ignoring",
+ count);
+ return client_cli_get_avd_access_token(instance, token);
+ default:
+ WLog_ERR(TAG, "Unexpected value for AccessTokenType [%" PRIuz "], aborting", tokenType);
+ return FALSE;
+ }
+}
+
+BOOL client_common_get_access_token(freerdp* instance, const char* request, char** token)
+{
+#ifdef WITH_AAD
+ WINPR_ASSERT(request);
+ WINPR_ASSERT(token);
+
+ BOOL ret = FALSE;
+ long resp_code = 0;
+ BYTE* response = NULL;
+ size_t response_length = 0;
+
+ wLog* log = WLog_Get(TAG);
+
+ if (!freerdp_http_request("https://login.microsoftonline.com/common/oauth2/v2.0/token", request,
+ &resp_code, &response, &response_length))
+ {
+ WLog_ERR(TAG, "access token request failed");
+ return FALSE;
+ }
+
+ if (resp_code != HTTP_STATUS_OK)
+ {
+ char buffer[64] = { 0 };
+
+ WLog_Print(log, WLOG_ERROR,
+ "Server unwilling to provide access token; returned status code %s",
+ freerdp_http_status_string_format(resp_code, buffer, sizeof(buffer)));
+ if (response_length > 0)
+ WLog_Print(log, WLOG_ERROR, "[status message] %s", response);
+ goto cleanup;
+ }
+
+ *token = freerdp_utils_aad_get_access_token(log, (const char*)response, response_length);
+ if (*token)
+ ret = TRUE;
+
+cleanup:
+ free(response);
+ return ret;
+#else
+ return FALSE;
+#endif
+}
+
+SSIZE_T client_common_retry_dialog(freerdp* instance, const char* what, size_t current,
+ void* userarg)
+{
+ WINPR_UNUSED(instance);
+ WINPR_ASSERT(instance->context);
+ WINPR_UNUSED(userarg);
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(what);
+
+ if ((strcmp(what, "arm-transport") != 0) && (strcmp(what, "connection") != 0))
+ {
+ WLog_ERR(TAG, "Unknown module %s, aborting", what);
+ return -1;
+ }
+
+ if (current == 0)
+ {
+ if (strcmp(what, "arm-transport") == 0)
+ WLog_INFO(TAG, "[%s] Starting your VM. It may take up to 5 minutes", what);
+ }
+
+ const rdpSettings* settings = instance->context->settings;
+ const BOOL enabled = freerdp_settings_get_bool(settings, FreeRDP_AutoReconnectionEnabled);
+ if (!enabled)
+ {
+ WLog_WARN(TAG, "Automatic reconnection disabled, terminating. Try to connect again later");
+ return -1;
+ }
+
+ const size_t max = freerdp_settings_get_uint32(settings, FreeRDP_AutoReconnectMaxRetries);
+ const size_t delay = freerdp_settings_get_uint32(settings, FreeRDP_TcpConnectTimeout);
+ if (current >= max)
+ {
+ WLog_ERR(TAG,
+ "[%s] retries exceeded. Your VM failed to start. Try again later or contact your "
+ "tech support for help if this keeps happening.",
+ what);
+ return -1;
+ }
+
+ WLog_INFO(TAG, "[%s] retry %" PRIuz "/%" PRIuz ", delaying %" PRIuz "ms before next attempt",
+ what, current, max, delay);
+ return delay;
+}
+
+BOOL client_auto_reconnect(freerdp* instance)
+{
+ return client_auto_reconnect_ex(instance, NULL);
+}
+
+BOOL client_auto_reconnect_ex(freerdp* instance, BOOL (*window_events)(freerdp* instance))
+{
+ BOOL retry = TRUE;
+ UINT32 error = 0;
+ UINT32 numRetries = 0;
+ rdpSettings* settings = NULL;
+
+ if (!instance)
+ return FALSE;
+
+ WINPR_ASSERT(instance->context);
+
+ settings = instance->context->settings;
+ WINPR_ASSERT(settings);
+
+ const UINT32 maxRetries =
+ freerdp_settings_get_uint32(settings, FreeRDP_AutoReconnectMaxRetries);
+
+ /* Only auto reconnect on network disconnects. */
+ error = freerdp_error_info(instance);
+ switch (error)
+ {
+ case ERRINFO_GRAPHICS_SUBSYSTEM_FAILED:
+ /* A network disconnect was detected */
+ WLog_WARN(TAG, "Disconnected by server hitting a bug or resource limit [%s]",
+ freerdp_get_error_info_string(error));
+ break;
+ case ERRINFO_SUCCESS:
+ /* A network disconnect was detected */
+ WLog_INFO(TAG, "Network disconnect!");
+ break;
+ default:
+ return FALSE;
+ }
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_AutoReconnectionEnabled))
+ {
+ /* No auto-reconnect - just quit */
+ return FALSE;
+ }
+
+ switch (freerdp_get_last_error(instance->context))
+ {
+ case FREERDP_ERROR_CONNECT_CANCELLED:
+ WLog_WARN(TAG, "Connection aborted by user");
+ return FALSE;
+ default:
+ break;
+ }
+
+ /* Perform an auto-reconnect. */
+ while (retry)
+ {
+ /* Quit retrying if max retries has been exceeded */
+ if ((maxRetries > 0) && (numRetries++ >= maxRetries))
+ {
+ return FALSE;
+ }
+
+ /* Attempt the next reconnect */
+ WLog_INFO(TAG, "Attempting reconnect (%" PRIu32 " of %" PRIu32 ")", numRetries, maxRetries);
+
+ IFCALL(instance->RetryDialog, instance, "connection", numRetries, NULL);
+
+ if (freerdp_reconnect(instance))
+ return TRUE;
+
+ switch (freerdp_get_last_error(instance->context))
+ {
+ case FREERDP_ERROR_CONNECT_CANCELLED:
+ WLog_WARN(TAG, "Autoreconnect aborted by user");
+ return FALSE;
+ default:
+ break;
+ }
+ for (UINT32 x = 0; x < 50; x++)
+ {
+ if (!IFCALLRESULT(TRUE, window_events, instance))
+ return FALSE;
+
+ Sleep(10);
+ }
+ }
+
+ WLog_ERR(TAG, "Maximum reconnect retries exceeded");
+ return FALSE;
+}
+
+int freerdp_client_common_stop(rdpContext* context)
+{
+ rdpClientContext* cctx = (rdpClientContext*)context;
+ WINPR_ASSERT(cctx);
+
+ freerdp_abort_connect_context(&cctx->context);
+
+ if (cctx->thread)
+ {
+ WaitForSingleObject(cctx->thread, INFINITE);
+ CloseHandle(cctx->thread);
+ cctx->thread = NULL;
+ }
+
+ return 0;
+}
+
+#if defined(CHANNEL_ENCOMSP_CLIENT)
+BOOL freerdp_client_encomsp_toggle_control(EncomspClientContext* encomsp)
+{
+ rdpClientContext* cctx = NULL;
+ BOOL state = 0;
+
+ if (!encomsp)
+ return FALSE;
+
+ cctx = (rdpClientContext*)encomsp->custom;
+
+ state = cctx->controlToggle;
+ cctx->controlToggle = !cctx->controlToggle;
+ return freerdp_client_encomsp_set_control(encomsp, state);
+}
+
+BOOL freerdp_client_encomsp_set_control(EncomspClientContext* encomsp, BOOL control)
+{
+ ENCOMSP_CHANGE_PARTICIPANT_CONTROL_LEVEL_PDU pdu = { 0 };
+
+ if (!encomsp)
+ return FALSE;
+
+ pdu.ParticipantId = encomsp->participantId;
+ pdu.Flags = ENCOMSP_REQUEST_VIEW;
+
+ if (control)
+ pdu.Flags |= ENCOMSP_REQUEST_INTERACT;
+
+ encomsp->ChangeParticipantControlLevel(encomsp, &pdu);
+
+ return TRUE;
+}
+
+static UINT
+client_encomsp_participant_created(EncomspClientContext* context,
+ const ENCOMSP_PARTICIPANT_CREATED_PDU* participantCreated)
+{
+ rdpClientContext* cctx = NULL;
+ rdpSettings* settings = NULL;
+ BOOL request = 0;
+
+ if (!context || !context->custom || !participantCreated)
+ return ERROR_INVALID_PARAMETER;
+
+ cctx = (rdpClientContext*)context->custom;
+ WINPR_ASSERT(cctx);
+
+ settings = cctx->context.settings;
+ WINPR_ASSERT(settings);
+
+ if (participantCreated->Flags & ENCOMSP_IS_PARTICIPANT)
+ context->participantId = participantCreated->ParticipantId;
+
+ request = freerdp_settings_get_bool(settings, FreeRDP_RemoteAssistanceRequestControl);
+ if (request && (participantCreated->Flags & ENCOMSP_MAY_VIEW) &&
+ !(participantCreated->Flags & ENCOMSP_MAY_INTERACT))
+ {
+ if (!freerdp_client_encomsp_set_control(context, TRUE))
+ return ERROR_INTERNAL_ERROR;
+
+ /* if auto-request-control setting is enabled then only request control once upon connect,
+ * otherwise it will auto request control again every time server turns off control which
+ * is a bit annoying */
+ freerdp_settings_set_bool(settings, FreeRDP_RemoteAssistanceRequestControl, FALSE);
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static void client_encomsp_init(rdpClientContext* cctx, EncomspClientContext* encomsp)
+{
+ cctx->encomsp = encomsp;
+ encomsp->custom = (void*)cctx;
+ encomsp->ParticipantCreated = client_encomsp_participant_created;
+}
+
+static void client_encomsp_uninit(rdpClientContext* cctx, EncomspClientContext* encomsp)
+{
+ if (encomsp)
+ {
+ encomsp->custom = NULL;
+ encomsp->ParticipantCreated = NULL;
+ }
+
+ if (cctx)
+ cctx->encomsp = NULL;
+}
+#endif
+
+void freerdp_client_OnChannelConnectedEventHandler(void* context,
+ const ChannelConnectedEventArgs* e)
+{
+ rdpClientContext* cctx = (rdpClientContext*)context;
+
+ WINPR_ASSERT(cctx);
+ WINPR_ASSERT(e);
+
+ if (0)
+ {
+ }
+#if defined(CHANNEL_AINPUT_CLIENT)
+ else if (strcmp(e->name, AINPUT_DVC_CHANNEL_NAME) == 0)
+ cctx->ainput = (AInputClientContext*)e->pInterface;
+#endif
+#if defined(CHANNEL_RDPEI_CLIENT)
+ else if (strcmp(e->name, RDPEI_DVC_CHANNEL_NAME) == 0)
+ {
+ cctx->rdpei = (RdpeiClientContext*)e->pInterface;
+ }
+#endif
+#if defined(CHANNEL_RDPGFX_CLIENT)
+ else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0)
+ {
+ gdi_graphics_pipeline_init(cctx->context.gdi, (RdpgfxClientContext*)e->pInterface);
+ }
+#endif
+#if defined(CHANNEL_GEOMETRY_CLIENT)
+ else if (strcmp(e->name, GEOMETRY_DVC_CHANNEL_NAME) == 0)
+ {
+ gdi_video_geometry_init(cctx->context.gdi, (GeometryClientContext*)e->pInterface);
+ }
+#endif
+#if defined(CHANNEL_VIDEO_CLIENT)
+ else if (strcmp(e->name, VIDEO_CONTROL_DVC_CHANNEL_NAME) == 0)
+ {
+ gdi_video_control_init(cctx->context.gdi, (VideoClientContext*)e->pInterface);
+ }
+ else if (strcmp(e->name, VIDEO_DATA_DVC_CHANNEL_NAME) == 0)
+ {
+ gdi_video_data_init(cctx->context.gdi, (VideoClientContext*)e->pInterface);
+ }
+#endif
+#if defined(CHANNEL_ENCOMSP_CLIENT)
+ else if (strcmp(e->name, ENCOMSP_SVC_CHANNEL_NAME) == 0)
+ {
+ client_encomsp_init(cctx, (EncomspClientContext*)e->pInterface);
+ }
+#endif
+}
+
+void freerdp_client_OnChannelDisconnectedEventHandler(void* context,
+ const ChannelDisconnectedEventArgs* e)
+{
+ rdpClientContext* cctx = (rdpClientContext*)context;
+
+ WINPR_ASSERT(cctx);
+ WINPR_ASSERT(e);
+
+ if (0)
+ {
+ }
+#if defined(CHANNEL_AINPUT_CLIENT)
+ else if (strcmp(e->name, AINPUT_DVC_CHANNEL_NAME) == 0)
+ cctx->ainput = NULL;
+#endif
+#if defined(CHANNEL_RDPEI_CLIENT)
+ else if (strcmp(e->name, RDPEI_DVC_CHANNEL_NAME) == 0)
+ {
+ cctx->rdpei = NULL;
+ }
+#endif
+#if defined(CHANNEL_RDPGFX_CLIENT)
+ else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0)
+ {
+ gdi_graphics_pipeline_uninit(cctx->context.gdi, (RdpgfxClientContext*)e->pInterface);
+ }
+#endif
+#if defined(CHANNEL_GEOMETRY_CLIENT)
+ else if (strcmp(e->name, GEOMETRY_DVC_CHANNEL_NAME) == 0)
+ {
+ gdi_video_geometry_uninit(cctx->context.gdi, (GeometryClientContext*)e->pInterface);
+ }
+#endif
+#if defined(CHANNEL_VIDEO_CLIENT)
+ else if (strcmp(e->name, VIDEO_CONTROL_DVC_CHANNEL_NAME) == 0)
+ {
+ gdi_video_control_uninit(cctx->context.gdi, (VideoClientContext*)e->pInterface);
+ }
+ else if (strcmp(e->name, VIDEO_DATA_DVC_CHANNEL_NAME) == 0)
+ {
+ gdi_video_data_uninit(cctx->context.gdi, (VideoClientContext*)e->pInterface);
+ }
+#endif
+#if defined(CHANNEL_ENCOMSP_CLIENT)
+ else if (strcmp(e->name, ENCOMSP_SVC_CHANNEL_NAME) == 0)
+ {
+ client_encomsp_uninit(cctx, (EncomspClientContext*)e->pInterface);
+ }
+#endif
+}
+
+BOOL freerdp_client_send_wheel_event(rdpClientContext* cctx, UINT16 mflags)
+{
+ BOOL handled = FALSE;
+
+ WINPR_ASSERT(cctx);
+
+#if defined(CHANNEL_AINPUT_CLIENT)
+ if (cctx->ainput)
+ {
+ UINT rc = 0;
+ UINT64 flags = 0;
+ INT32 x = 0;
+ INT32 y = 0;
+ INT32 value = mflags & 0xFF;
+
+ if (mflags & PTR_FLAGS_WHEEL_NEGATIVE)
+ value = -1 * (0x100 - value);
+
+ /* We have discrete steps, scale this so we can also support high
+ * resolution wheels. */
+ value *= 0x10000;
+
+ if (mflags & PTR_FLAGS_WHEEL)
+ {
+ flags |= AINPUT_FLAGS_WHEEL;
+ y = value;
+ }
+
+ if (mflags & PTR_FLAGS_HWHEEL)
+ {
+ flags |= AINPUT_FLAGS_WHEEL;
+ x = value;
+ }
+
+ WINPR_ASSERT(cctx->ainput->AInputSendInputEvent);
+ rc = cctx->ainput->AInputSendInputEvent(cctx->ainput, flags, x, y);
+ if (rc == CHANNEL_RC_OK)
+ handled = TRUE;
+ }
+#endif
+
+ if (!handled)
+ freerdp_input_send_mouse_event(cctx->context.input, mflags, 0, 0);
+
+ return TRUE;
+}
+
+#if defined(CHANNEL_AINPUT_CLIENT)
+static INLINE BOOL ainput_send_diff_event(rdpClientContext* cctx, UINT64 flags, INT32 x, INT32 y)
+{
+ UINT rc = 0;
+
+ WINPR_ASSERT(cctx);
+ WINPR_ASSERT(cctx->ainput);
+ WINPR_ASSERT(cctx->ainput->AInputSendInputEvent);
+
+ rc = cctx->ainput->AInputSendInputEvent(cctx->ainput, flags, x, y);
+
+ return rc == CHANNEL_RC_OK;
+}
+#endif
+
+BOOL freerdp_client_send_button_event(rdpClientContext* cctx, BOOL relative, UINT16 mflags, INT32 x,
+ INT32 y)
+{
+ BOOL handled = FALSE;
+
+ WINPR_ASSERT(cctx);
+
+ const BOOL relativeInput = freerdp_client_use_relative_mouse_events(cctx);
+ if (relative && relativeInput)
+ {
+ return freerdp_input_send_rel_mouse_event(cctx->context.input, mflags, x, y);
+ }
+
+#if defined(CHANNEL_AINPUT_CLIENT)
+ if (cctx->ainput)
+ {
+ UINT64 flags = 0;
+
+ if (cctx->mouse_grabbed && relativeInput)
+ flags |= AINPUT_FLAGS_HAVE_REL;
+
+ if (relative)
+ flags |= AINPUT_FLAGS_REL;
+
+ if (mflags & PTR_FLAGS_DOWN)
+ flags |= AINPUT_FLAGS_DOWN;
+ if (mflags & PTR_FLAGS_BUTTON1)
+ flags |= AINPUT_FLAGS_BUTTON1;
+ if (mflags & PTR_FLAGS_BUTTON2)
+ flags |= AINPUT_FLAGS_BUTTON2;
+ if (mflags & PTR_FLAGS_BUTTON3)
+ flags |= AINPUT_FLAGS_BUTTON3;
+ if (mflags & PTR_FLAGS_MOVE)
+ flags |= AINPUT_FLAGS_MOVE;
+ handled = ainput_send_diff_event(cctx, flags, x, y);
+ }
+#endif
+
+ if (!handled)
+ {
+ if (relative)
+ {
+ cctx->lastX += x;
+ cctx->lastY += y;
+ WLog_WARN(TAG, "Relative mouse input channel not available, sending absolute!");
+ }
+ else
+ {
+ cctx->lastX = x;
+ cctx->lastY = y;
+ }
+ freerdp_input_send_mouse_event(cctx->context.input, mflags, (UINT16)cctx->lastX,
+ (UINT16)cctx->lastY);
+ }
+ return TRUE;
+}
+
+BOOL freerdp_client_send_extended_button_event(rdpClientContext* cctx, BOOL relative, UINT16 mflags,
+ INT32 x, INT32 y)
+{
+ BOOL handled = FALSE;
+ WINPR_ASSERT(cctx);
+
+ if (relative && freerdp_client_use_relative_mouse_events(cctx))
+ {
+ return freerdp_input_send_rel_mouse_event(cctx->context.input, mflags, x, y);
+ }
+
+#if defined(CHANNEL_AINPUT_CLIENT)
+ if (cctx->ainput)
+ {
+ UINT64 flags = 0;
+
+ if (relative)
+ flags |= AINPUT_FLAGS_REL;
+ if (mflags & PTR_XFLAGS_DOWN)
+ flags |= AINPUT_FLAGS_DOWN;
+ if (mflags & PTR_XFLAGS_BUTTON1)
+ flags |= AINPUT_XFLAGS_BUTTON1;
+ if (mflags & PTR_XFLAGS_BUTTON2)
+ flags |= AINPUT_XFLAGS_BUTTON2;
+
+ handled = ainput_send_diff_event(cctx, flags, x, y);
+ }
+#endif
+
+ if (!handled)
+ {
+ if (relative)
+ {
+ cctx->lastX += x;
+ cctx->lastY += y;
+ WLog_WARN(TAG, "Relative mouse input channel not available, sending absolute!");
+ }
+ else
+ {
+ cctx->lastX = x;
+ cctx->lastY = y;
+ }
+ freerdp_input_send_extended_mouse_event(cctx->context.input, mflags, (UINT16)cctx->lastX,
+ (UINT16)cctx->lastY);
+ }
+
+ return TRUE;
+}
+
+static BOOL freerdp_handle_touch_up(rdpClientContext* cctx, const FreeRDP_TouchContact* contact)
+{
+ WINPR_ASSERT(cctx);
+ WINPR_ASSERT(contact);
+
+#if defined(CHANNEL_RDPEI_CLIENT)
+ RdpeiClientContext* rdpei = cctx->rdpei;
+
+ if (!rdpei)
+ {
+ UINT16 flags = 0;
+ flags |= PTR_FLAGS_BUTTON1;
+
+ WINPR_ASSERT(contact->x <= UINT16_MAX);
+ WINPR_ASSERT(contact->y <= UINT16_MAX);
+ return freerdp_client_send_button_event(cctx, FALSE, flags, contact->x, contact->y);
+ }
+ else
+ {
+ int contactId = 0;
+
+ if (rdpei->TouchRawEvent)
+ {
+ const UINT32 flags = RDPINPUT_CONTACT_FLAG_UP;
+ const UINT32 contactFlags = ((contact->flags & FREERDP_TOUCH_HAS_PRESSURE) != 0)
+ ? CONTACT_DATA_PRESSURE_PRESENT
+ : 0;
+ // Ensure contact position is unchanged from "engaged" to "out of range" state
+ rdpei->TouchRawEvent(rdpei, contact->id, contact->x, contact->y, &contactId,
+ RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE |
+ RDPINPUT_CONTACT_FLAG_INCONTACT,
+ contactFlags, contact->pressure);
+ rdpei->TouchRawEvent(rdpei, contact->id, contact->x, contact->y, &contactId, flags,
+ contactFlags, contact->pressure);
+ }
+ else
+ {
+ WINPR_ASSERT(rdpei->TouchEnd);
+ rdpei->TouchEnd(rdpei, contact->id, contact->x, contact->y, &contactId);
+ }
+ }
+#else
+ WLog_WARN(TAG, "Touch event detected but RDPEI support not compiled in. Recompile with "
+ "-DWITH_CHANNELS=ON");
+#endif
+
+ return TRUE;
+}
+
+static BOOL freerdp_handle_touch_down(rdpClientContext* cctx, const FreeRDP_TouchContact* contact)
+{
+ WINPR_ASSERT(cctx);
+ WINPR_ASSERT(contact);
+
+#if defined(CHANNEL_RDPEI_CLIENT)
+ RdpeiClientContext* rdpei = cctx->rdpei;
+
+ // Emulate mouse click if touch is not possible, like in login screen
+ if (!rdpei)
+ {
+ UINT16 flags = 0;
+ flags |= PTR_FLAGS_DOWN;
+ flags |= PTR_FLAGS_MOVE;
+ flags |= PTR_FLAGS_BUTTON1;
+
+ WINPR_ASSERT(contact->x <= UINT16_MAX);
+ WINPR_ASSERT(contact->y <= UINT16_MAX);
+ return freerdp_client_send_button_event(cctx, FALSE, flags, contact->x, contact->y);
+ }
+ else
+ {
+ int contactId = 0;
+
+ if (rdpei->TouchRawEvent)
+ {
+ const UINT32 flags = RDPINPUT_CONTACT_FLAG_DOWN | RDPINPUT_CONTACT_FLAG_INRANGE |
+ RDPINPUT_CONTACT_FLAG_INCONTACT;
+ const UINT32 contactFlags = ((contact->flags & FREERDP_TOUCH_HAS_PRESSURE) != 0)
+ ? CONTACT_DATA_PRESSURE_PRESENT
+ : 0;
+ rdpei->TouchRawEvent(rdpei, contact->id, contact->x, contact->y, &contactId, flags,
+ contactFlags, contact->pressure);
+ }
+ else
+ {
+ WINPR_ASSERT(rdpei->TouchBegin);
+ rdpei->TouchBegin(rdpei, contact->id, contact->x, contact->y, &contactId);
+ }
+ }
+#else
+ WLog_WARN(TAG, "Touch event detected but RDPEI support not compiled in. Recompile with "
+ "-DWITH_CHANNELS=ON");
+#endif
+
+ return TRUE;
+}
+
+static BOOL freerdp_handle_touch_motion(rdpClientContext* cctx, const FreeRDP_TouchContact* contact)
+{
+ WINPR_ASSERT(cctx);
+ WINPR_ASSERT(contact);
+
+#if defined(CHANNEL_RDPEI_CLIENT)
+ RdpeiClientContext* rdpei = cctx->rdpei;
+
+ if (!rdpei)
+ {
+ UINT16 flags = 0;
+ flags |= PTR_FLAGS_MOVE;
+
+ WINPR_ASSERT(contact->x <= UINT16_MAX);
+ WINPR_ASSERT(contact->y <= UINT16_MAX);
+ return freerdp_client_send_button_event(cctx, FALSE, flags, contact->x, contact->y);
+ }
+ else
+ {
+ int contactId = 0;
+
+ if (rdpei->TouchRawEvent)
+ {
+ const UINT32 flags = RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE |
+ RDPINPUT_CONTACT_FLAG_INCONTACT;
+ const UINT32 contactFlags = ((contact->flags & FREERDP_TOUCH_HAS_PRESSURE) != 0)
+ ? CONTACT_DATA_PRESSURE_PRESENT
+ : 0;
+ rdpei->TouchRawEvent(rdpei, contact->id, contact->x, contact->y, &contactId, flags,
+ contactFlags, contact->pressure);
+ }
+ else
+ {
+ WINPR_ASSERT(rdpei->TouchUpdate);
+ rdpei->TouchUpdate(rdpei, contact->id, contact->x, contact->y, &contactId);
+ }
+ }
+#else
+ WLog_WARN(TAG, "Touch event detected but RDPEI support not compiled in. Recompile with "
+ "-DWITH_CHANNELS=ON");
+#endif
+
+ return TRUE;
+}
+
+static BOOL freerdp_client_touch_update(rdpClientContext* cctx, UINT32 flags, INT32 touchId,
+ UINT32 pressure, INT32 x, INT32 y,
+ FreeRDP_TouchContact* pcontact)
+{
+ WINPR_ASSERT(cctx);
+ WINPR_ASSERT(pcontact);
+
+ for (size_t i = 0; i < ARRAYSIZE(cctx->contacts); i++)
+ {
+ FreeRDP_TouchContact* contact = &cctx->contacts[i];
+
+ const BOOL newcontact = ((contact->id == 0) && ((flags & FREERDP_TOUCH_DOWN) != 0));
+ if (newcontact || (contact->id == touchId))
+ {
+ contact->id = touchId;
+ contact->flags = flags;
+ contact->pressure = pressure;
+ contact->x = x;
+ contact->y = y;
+
+ *pcontact = *contact;
+
+ const BOOL resetcontact = (flags & FREERDP_TOUCH_UP) != 0;
+ if (resetcontact)
+ {
+ FreeRDP_TouchContact empty = { 0 };
+ *contact = empty;
+ }
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+BOOL freerdp_client_handle_touch(rdpClientContext* cctx, UINT32 flags, INT32 finger,
+ UINT32 pressure, INT32 x, INT32 y)
+{
+ const UINT32 mask = FREERDP_TOUCH_DOWN | FREERDP_TOUCH_UP | FREERDP_TOUCH_MOTION;
+ WINPR_ASSERT(cctx);
+
+ FreeRDP_TouchContact contact = { 0 };
+
+ if (!freerdp_client_touch_update(cctx, flags, finger, pressure, x, y, &contact))
+ return FALSE;
+
+ switch (flags & mask)
+ {
+ case FREERDP_TOUCH_DOWN:
+ return freerdp_handle_touch_down(cctx, &contact);
+ case FREERDP_TOUCH_UP:
+ return freerdp_handle_touch_up(cctx, &contact);
+ case FREERDP_TOUCH_MOTION:
+ return freerdp_handle_touch_motion(cctx, &contact);
+ default:
+ WLog_WARN(TAG, "Unhandled FreeRDPTouchEventType %d, ignoring", flags);
+ return FALSE;
+ }
+}
+
+BOOL freerdp_client_load_channels(freerdp* instance)
+{
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(instance->context);
+
+ if (!freerdp_client_load_addins(instance->context->channels, instance->context->settings))
+ {
+ WLog_ERR(TAG, "Failed to load addins [%08" PRIx32 "]", GetLastError());
+ return FALSE;
+ }
+ return TRUE;
+}
+
+int client_cli_logon_error_info(freerdp* instance, UINT32 data, UINT32 type)
+{
+ const char* str_data = freerdp_get_logon_error_info_data(data);
+ const char* str_type = freerdp_get_logon_error_info_type(type);
+
+ if (!instance || !instance->context)
+ return -1;
+
+ WLog_INFO(TAG, "Logon Error Info %s [%s]", str_data, str_type);
+ return 1;
+}
+
+static FreeRDP_PenDevice* freerdp_client_get_pen(rdpClientContext* cctx, INT32 deviceid,
+ size_t* pos)
+{
+ WINPR_ASSERT(cctx);
+
+ for (size_t i = 0; i < ARRAYSIZE(cctx->pens); i++)
+ {
+ FreeRDP_PenDevice* pen = &cctx->pens[i];
+ if (deviceid == pen->deviceid)
+ {
+ if (pos)
+ *pos = i;
+ return pen;
+ }
+ }
+ return NULL;
+}
+
+static BOOL freerdp_client_register_pen(rdpClientContext* cctx, UINT32 flags, INT32 deviceid,
+ double pressure)
+{
+ static const INT32 null_deviceid = 0;
+
+ WINPR_ASSERT(cctx);
+ WINPR_ASSERT((flags & FREERDP_PEN_REGISTER) != 0);
+ if (freerdp_client_is_pen(cctx, deviceid))
+ {
+ WLog_WARN(TAG, "trying to double register pen device %" PRId32, deviceid);
+ return FALSE;
+ }
+
+ size_t pos = 0;
+ FreeRDP_PenDevice* pen = freerdp_client_get_pen(cctx, null_deviceid, &pos);
+ if (pen)
+ {
+ const FreeRDP_PenDevice empty = { 0 };
+ *pen = empty;
+
+ pen->deviceid = deviceid;
+ pen->max_pressure = pressure;
+ pen->flags = flags;
+
+ WLog_DBG(TAG, "registered pen at index %" PRIuz, pos);
+ return TRUE;
+ }
+
+ WLog_WARN(TAG, "No free slots for an additiona pen device, skipping");
+ return TRUE;
+}
+
+BOOL freerdp_client_handle_pen(rdpClientContext* cctx, UINT32 flags, INT32 deviceid, ...)
+{
+ if ((flags & FREERDP_PEN_REGISTER) != 0)
+ {
+ va_list args;
+
+ va_start(args, deviceid);
+ double pressure = va_arg(args, double);
+ va_end(args);
+ return freerdp_client_register_pen(cctx, flags, deviceid, pressure);
+ }
+ size_t pos = 0;
+ FreeRDP_PenDevice* pen = freerdp_client_get_pen(cctx, deviceid, &pos);
+ if (!pen)
+ {
+ WLog_WARN(TAG, "unregistered pen device %" PRId32 " event 0x%08" PRIx32, deviceid, flags);
+ return FALSE;
+ }
+
+ UINT32 fieldFlags = RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT;
+ UINT32 penFlags =
+ ((pen->flags & FREERDP_PEN_IS_INVERTED) != 0) ? RDPINPUT_PEN_FLAG_INVERTED : 0;
+
+ RdpeiClientContext* rdpei = cctx->rdpei;
+ WINPR_ASSERT(rdpei);
+
+ UINT32 normalizedpressure = 1024;
+ INT32 x = 0;
+ INT32 y = 0;
+ UINT16 rotation = 0;
+ INT16 tiltX = 0;
+ INT16 tiltY = 0;
+ va_list args;
+ va_start(args, deviceid);
+
+ x = va_arg(args, INT32);
+ y = va_arg(args, INT32);
+ if ((flags & FREERDP_PEN_HAS_PRESSURE) != 0)
+ {
+ const double pressure = va_arg(args, double);
+ normalizedpressure = (pressure * 1024) / pen->max_pressure;
+ WLog_DBG(TAG, "pen pressure %lf -> %" PRIu32, pressure, normalizedpressure);
+ fieldFlags |= RDPINPUT_PEN_CONTACT_PRESSURE_PRESENT;
+ }
+ if ((flags & FREERDP_PEN_HAS_ROTATION) != 0)
+ {
+ rotation = va_arg(args, unsigned);
+ fieldFlags |= RDPINPUT_PEN_CONTACT_ROTATION_PRESENT;
+ }
+ if ((flags & FREERDP_PEN_HAS_TILTX) != 0)
+ {
+ tiltX = va_arg(args, int);
+ fieldFlags |= RDPINPUT_PEN_CONTACT_TILTX_PRESENT;
+ }
+ if ((flags & FREERDP_PEN_HAS_TILTY) != 0)
+ {
+ tiltX = va_arg(args, int);
+ fieldFlags |= RDPINPUT_PEN_CONTACT_TILTY_PRESENT;
+ }
+ va_end(args);
+
+ if ((flags & FREERDP_PEN_PRESS) != 0)
+ {
+ // Ensure that only one button is pressed
+ if (pen->pressed)
+ flags = FREERDP_PEN_MOTION |
+ (flags & (UINT32) ~(FREERDP_PEN_PRESS | FREERDP_PEN_BARREL_PRESSED));
+ else if ((flags & FREERDP_PEN_BARREL_PRESSED) != 0)
+ pen->flags |= FREERDP_PEN_BARREL_PRESSED;
+ }
+ else if ((flags & FREERDP_PEN_RELEASE) != 0)
+ {
+ if (!pen->pressed ||
+ ((flags & FREERDP_PEN_BARREL_PRESSED) ^ (pen->flags & FREERDP_PEN_BARREL_PRESSED)))
+ flags = FREERDP_PEN_MOTION |
+ (flags & (UINT32) ~(FREERDP_PEN_RELEASE | FREERDP_PEN_BARREL_PRESSED));
+ else
+ pen->flags &= (UINT32)~FREERDP_PEN_BARREL_PRESSED;
+ }
+
+ flags |= pen->flags;
+ if ((flags & FREERDP_PEN_ERASER_PRESSED) != 0)
+ penFlags |= RDPINPUT_PEN_FLAG_ERASER_PRESSED;
+ if ((flags & FREERDP_PEN_BARREL_PRESSED) != 0)
+ penFlags |= RDPINPUT_PEN_FLAG_BARREL_PRESSED;
+
+ pen->last_x = x;
+ pen->last_y = y;
+ if ((flags & FREERDP_PEN_PRESS) != 0)
+ {
+ WLog_DBG(TAG, "Pen press %" PRId32, deviceid);
+ pen->hovering = FALSE;
+ pen->pressed = TRUE;
+
+ WINPR_ASSERT(rdpei->PenBegin);
+ const UINT rc = rdpei->PenBegin(rdpei, deviceid, fieldFlags, x, y, penFlags,
+ normalizedpressure, rotation, tiltX, tiltY);
+ return rc == CHANNEL_RC_OK;
+ }
+ else if ((flags & FREERDP_PEN_MOTION) != 0)
+ {
+ UINT rc = ERROR_INTERNAL_ERROR;
+ if (pen->pressed)
+ {
+ WLog_DBG(TAG, "Pen update %" PRId32, deviceid);
+
+ // TODO: what if no rotation is supported but tilt is?
+ WINPR_ASSERT(rdpei->PenUpdate);
+ rc = rdpei->PenUpdate(rdpei, deviceid, fieldFlags, x, y, penFlags, normalizedpressure,
+ rotation, tiltX, tiltY);
+ }
+ else if (pen->hovering)
+ {
+ WLog_DBG(TAG, "Pen hover update %" PRId32, deviceid);
+
+ WINPR_ASSERT(rdpei->PenHoverUpdate);
+ rc = rdpei->PenHoverUpdate(rdpei, deviceid, RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT, x, y,
+ penFlags, normalizedpressure, rotation, tiltX, tiltY);
+ }
+ else
+ {
+ WLog_DBG(TAG, "Pen hover begin %" PRId32, deviceid);
+ pen->hovering = TRUE;
+
+ WINPR_ASSERT(rdpei->PenHoverBegin);
+ rc = rdpei->PenHoverBegin(rdpei, deviceid, RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT, x, y,
+ penFlags, normalizedpressure, rotation, tiltX, tiltY);
+ }
+ return rc == CHANNEL_RC_OK;
+ }
+ else if ((flags & FREERDP_PEN_RELEASE) != 0)
+ {
+ WLog_DBG(TAG, "Pen release %" PRId32, deviceid);
+ pen->pressed = FALSE;
+ pen->hovering = TRUE;
+
+ WINPR_ASSERT(rdpei->PenUpdate);
+ const UINT rc = rdpei->PenUpdate(rdpei, deviceid, fieldFlags, x, y, penFlags,
+ normalizedpressure, rotation, tiltX, tiltY);
+ if (rc != CHANNEL_RC_OK)
+ return FALSE;
+ WINPR_ASSERT(rdpei->PenEnd);
+ const UINT re = rdpei->PenEnd(rdpei, deviceid, RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT, x, y,
+ penFlags, normalizedpressure, rotation, tiltX, tiltY);
+ return re == CHANNEL_RC_OK;
+ }
+
+ WLog_WARN(TAG, "Invalid pen %" PRId32 " flags 0x%08" PRIx32, deviceid, flags);
+ return FALSE;
+}
+
+BOOL freerdp_client_pen_cancel_all(rdpClientContext* cctx)
+{
+ WINPR_ASSERT(cctx);
+
+ RdpeiClientContext* rdpei = cctx->rdpei;
+
+ if (!rdpei)
+ return FALSE;
+
+ for (size_t i = 0; i < ARRAYSIZE(cctx->pens); i++)
+ {
+ FreeRDP_PenDevice* pen = &cctx->pens[i];
+ if (pen->hovering)
+ {
+ WLog_DBG(TAG, "unhover pen %" PRId32, pen->deviceid);
+ pen->hovering = FALSE;
+ rdpei->PenHoverCancel(rdpei, pen->deviceid, 0, pen->last_x, pen->last_y);
+ }
+ }
+ return TRUE;
+}
+
+BOOL freerdp_client_is_pen(rdpClientContext* cctx, INT32 deviceid)
+{
+ WINPR_ASSERT(cctx);
+
+ if (deviceid == 0)
+ return FALSE;
+
+ for (size_t x = 0; x < ARRAYSIZE(cctx->pens); x++)
+ {
+ const FreeRDP_PenDevice* pen = &cctx->pens[x];
+ if (pen->deviceid == deviceid)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+BOOL freerdp_client_use_relative_mouse_events(rdpClientContext* ccontext)
+{
+ WINPR_ASSERT(ccontext);
+
+ const rdpSettings* settings = ccontext->context.settings;
+ const BOOL useRelative = freerdp_settings_get_bool(settings, FreeRDP_MouseUseRelativeMove);
+ const BOOL haveRelative = freerdp_settings_get_bool(settings, FreeRDP_HasRelativeMouseEvent);
+ BOOL ainput = false;
+#if defined(CHANNEL_AINPUT_SERVER)
+ ainput = ccontext->ainput != NULL;
+#endif
+
+ return useRelative && (haveRelative || ainput);
+}
diff --git a/client/common/client_cliprdr_file.c b/client/common/client_cliprdr_file.c
new file mode 100644
index 0000000..9b3ee22
--- /dev/null
+++ b/client/common/client_cliprdr_file.c
@@ -0,0 +1,2556 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * X11 Clipboard Redirection
+ *
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2023 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2023 Pascal Nowack <Pascal.Nowack@gmx.de>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdlib.h>
+#include <errno.h>
+
+#ifdef WITH_FUSE
+#define FUSE_USE_VERSION 30
+#include <fuse_lowlevel.h>
+#endif
+
+#if defined(WITH_FUSE)
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <time.h>
+#endif
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/image.h>
+#include <winpr/stream.h>
+#include <winpr/clipboard.h>
+#include <winpr/path.h>
+
+#include <freerdp/utils/signal.h>
+#include <freerdp/log.h>
+#include <freerdp/client/cliprdr.h>
+#include <freerdp/channels/channels.h>
+#include <freerdp/channels/cliprdr.h>
+
+#include <freerdp/client/client_cliprdr_file.h>
+
+#define MAX_CLIP_DATA_DIR_LEN 10
+#define MAX_CLIPBOARD_FORMATS 255
+#define NO_CLIP_DATA_ID (UINT64_C(1) << 32)
+#define WIN32_FILETIME_TO_UNIX_EPOCH UINT64_C(11644473600)
+
+#ifdef WITH_DEBUG_CLIPRDR
+#define DEBUG_CLIPRDR(log, ...) WLog_Print(log, WLOG_DEBUG, __VA_ARGS__)
+#else
+#define DEBUG_CLIPRDR(log, ...) \
+ do \
+ { \
+ } while (0)
+#endif
+
+#if defined(WITH_FUSE)
+typedef enum eFuseLowlevelOperationType
+{
+ FUSE_LL_OPERATION_NONE,
+ FUSE_LL_OPERATION_LOOKUP,
+ FUSE_LL_OPERATION_GETATTR,
+ FUSE_LL_OPERATION_READ,
+} FuseLowlevelOperationType;
+
+typedef struct sCliprdrFuseFile CliprdrFuseFile;
+
+struct sCliprdrFuseFile
+{
+ CliprdrFuseFile* parent;
+ wArrayList* children;
+
+ char* filename;
+ char* filename_with_root;
+ UINT32 list_idx;
+ fuse_ino_t ino;
+
+ BOOL is_directory;
+ BOOL is_readonly;
+
+ BOOL has_size;
+ UINT64 size;
+
+ BOOL has_last_write_time;
+ UINT64 last_write_time_unix;
+
+ BOOL has_clip_data_id;
+ UINT32 clip_data_id;
+};
+
+typedef struct
+{
+ CliprdrFileContext* file_context;
+
+ CliprdrFuseFile* clip_data_dir;
+
+ BOOL has_clip_data_id;
+ UINT32 clip_data_id;
+} CliprdrFuseClipDataEntry;
+
+typedef struct
+{
+ CliprdrFileContext* file_context;
+
+ wArrayList* fuse_files;
+
+ BOOL all_files;
+ BOOL has_clip_data_id;
+ UINT32 clip_data_id;
+} FuseFileClearContext;
+
+typedef struct
+{
+ FuseLowlevelOperationType operation_type;
+ CliprdrFuseFile* fuse_file;
+ fuse_req_t fuse_req;
+ UINT32 stream_id;
+} CliprdrFuseRequest;
+
+typedef struct
+{
+ CliprdrFuseFile* parent;
+ char* parent_path;
+} CliprdrFuseFindParentContext;
+#endif
+
+typedef struct
+{
+ char* name;
+ FILE* fp;
+ INT64 size;
+ CliprdrFileContext* context;
+} CliprdrLocalFile;
+
+typedef struct
+{
+ UINT32 lockId;
+ BOOL locked;
+ size_t count;
+ CliprdrLocalFile* files;
+ CliprdrFileContext* context;
+} CliprdrLocalStream;
+
+struct cliprdr_file_context
+{
+#if defined(WITH_FUSE)
+ /* FUSE related**/
+ HANDLE fuse_start_sync;
+ HANDLE fuse_stop_sync;
+ HANDLE fuse_thread;
+ struct fuse_session* fuse_sess;
+#if FUSE_USE_VERSION < 30
+ struct fuse_chan* ch;
+#endif
+
+ wHashTable* inode_table;
+ wHashTable* clip_data_table;
+ wHashTable* request_table;
+
+ CliprdrFuseFile* root_dir;
+ CliprdrFuseClipDataEntry* clip_data_entry_without_id;
+ UINT32 current_clip_data_id;
+
+ fuse_ino_t next_ino;
+ UINT32 next_clip_data_id;
+ UINT32 next_stream_id;
+#endif
+
+ /* File clipping */
+ BOOL file_formats_registered;
+ UINT32 file_capability_flags;
+
+ UINT32 local_lock_id;
+
+ wHashTable* local_streams;
+ wLog* log;
+ void* clipboard;
+ CliprdrClientContext* context;
+ char* path;
+ char* exposed_path;
+ BYTE server_data_hash[WINPR_SHA256_DIGEST_LENGTH];
+ BYTE client_data_hash[WINPR_SHA256_DIGEST_LENGTH];
+};
+
+#if defined(WITH_FUSE)
+static void fuse_file_free(void* data)
+{
+ CliprdrFuseFile* fuse_file = data;
+
+ if (!fuse_file)
+ return;
+
+ ArrayList_Free(fuse_file->children);
+ free(fuse_file->filename_with_root);
+
+ free(fuse_file);
+}
+
+static CliprdrFuseFile* fuse_file_new(void)
+{
+ CliprdrFuseFile* fuse_file = NULL;
+
+ fuse_file = calloc(1, sizeof(CliprdrFuseFile));
+ if (!fuse_file)
+ return NULL;
+
+ fuse_file->children = ArrayList_New(FALSE);
+ if (!fuse_file->children)
+ {
+ free(fuse_file);
+ return NULL;
+ }
+
+ return fuse_file;
+}
+
+static void clip_data_entry_free(void* data)
+{
+ CliprdrFuseClipDataEntry* clip_data_entry = data;
+
+ if (!clip_data_entry)
+ return;
+
+ if (clip_data_entry->has_clip_data_id)
+ {
+ CliprdrFileContext* file_context = clip_data_entry->file_context;
+ CLIPRDR_UNLOCK_CLIPBOARD_DATA unlock_clipboard_data = { 0 };
+
+ WINPR_ASSERT(file_context);
+
+ unlock_clipboard_data.common.msgType = CB_UNLOCK_CLIPDATA;
+ unlock_clipboard_data.clipDataId = clip_data_entry->clip_data_id;
+
+ file_context->context->ClientUnlockClipboardData(file_context->context,
+ &unlock_clipboard_data);
+ clip_data_entry->has_clip_data_id = FALSE;
+
+ WLog_Print(file_context->log, WLOG_DEBUG, "Destroyed ClipDataEntry with id %u",
+ clip_data_entry->clip_data_id);
+ }
+
+ free(clip_data_entry);
+}
+
+static BOOL does_server_support_clipdata_locking(CliprdrFileContext* file_context)
+{
+ WINPR_ASSERT(file_context);
+
+ if (cliprdr_file_context_remote_get_flags(file_context) & CB_CAN_LOCK_CLIPDATA)
+ return TRUE;
+
+ return FALSE;
+}
+
+static UINT32 get_next_free_clip_data_id(CliprdrFileContext* file_context)
+{
+ UINT32 clip_data_id = 0;
+
+ WINPR_ASSERT(file_context);
+
+ HashTable_Lock(file_context->inode_table);
+ clip_data_id = file_context->next_clip_data_id;
+ while (clip_data_id == 0 ||
+ HashTable_GetItemValue(file_context->clip_data_table, (void*)(UINT_PTR)clip_data_id))
+ ++clip_data_id;
+
+ file_context->next_clip_data_id = clip_data_id + 1;
+ HashTable_Unlock(file_context->inode_table);
+
+ return clip_data_id;
+}
+
+static CliprdrFuseClipDataEntry* clip_data_entry_new(CliprdrFileContext* file_context,
+ BOOL needs_clip_data_id)
+{
+ CliprdrFuseClipDataEntry* clip_data_entry = NULL;
+ CLIPRDR_LOCK_CLIPBOARD_DATA lock_clipboard_data = { 0 };
+
+ WINPR_ASSERT(file_context);
+
+ clip_data_entry = calloc(1, sizeof(CliprdrFuseClipDataEntry));
+ if (!clip_data_entry)
+ return NULL;
+
+ clip_data_entry->file_context = file_context;
+ clip_data_entry->clip_data_id = get_next_free_clip_data_id(file_context);
+
+ if (!needs_clip_data_id)
+ return clip_data_entry;
+
+ lock_clipboard_data.common.msgType = CB_LOCK_CLIPDATA;
+ lock_clipboard_data.clipDataId = clip_data_entry->clip_data_id;
+
+ if (file_context->context->ClientLockClipboardData(file_context->context, &lock_clipboard_data))
+ {
+ HashTable_Lock(file_context->inode_table);
+ clip_data_entry_free(clip_data_entry);
+ HashTable_Unlock(file_context->inode_table);
+ return NULL;
+ }
+ clip_data_entry->has_clip_data_id = TRUE;
+
+ WLog_Print(file_context->log, WLOG_DEBUG, "Created ClipDataEntry with id %u",
+ clip_data_entry->clip_data_id);
+
+ return clip_data_entry;
+}
+
+static BOOL should_remove_fuse_file(CliprdrFuseFile* fuse_file, BOOL all_files,
+ BOOL has_clip_data_id, BOOL clip_data_id)
+{
+ if (all_files)
+ return TRUE;
+
+ if (fuse_file->ino == FUSE_ROOT_ID)
+ return FALSE;
+ if (!fuse_file->has_clip_data_id && !has_clip_data_id)
+ return TRUE;
+ if (fuse_file->has_clip_data_id && has_clip_data_id && fuse_file->clip_data_id == clip_data_id)
+ return TRUE;
+
+ return FALSE;
+}
+
+static BOOL maybe_clear_fuse_request(const void* key, void* value, void* arg)
+{
+ CliprdrFuseRequest* fuse_request = value;
+ FuseFileClearContext* clear_context = arg;
+ CliprdrFileContext* file_context = clear_context->file_context;
+ CliprdrFuseFile* fuse_file = fuse_request->fuse_file;
+
+ WINPR_ASSERT(file_context);
+
+ if (!should_remove_fuse_file(fuse_file, clear_context->all_files,
+ clear_context->has_clip_data_id, clear_context->clip_data_id))
+ return TRUE;
+
+ DEBUG_CLIPRDR(file_context->log, "Clearing FileContentsRequest for file \"%s\"",
+ fuse_file->filename_with_root);
+
+ fuse_reply_err(fuse_request->fuse_req, EIO);
+ HashTable_Remove(file_context->request_table, key);
+ free(fuse_request);
+
+ return TRUE;
+}
+
+static BOOL maybe_steal_inode(const void* key, void* value, void* arg)
+{
+ CliprdrFuseFile* fuse_file = value;
+ FuseFileClearContext* clear_context = arg;
+ CliprdrFileContext* file_context = clear_context->file_context;
+
+ WINPR_ASSERT(file_context);
+
+ if (should_remove_fuse_file(fuse_file, clear_context->all_files,
+ clear_context->has_clip_data_id, clear_context->clip_data_id))
+ {
+ if (!ArrayList_Append(clear_context->fuse_files, fuse_file))
+ WLog_Print(file_context->log, WLOG_ERROR,
+ "Failed to append FUSE file to list for deletion");
+
+ HashTable_Remove(file_context->inode_table, key);
+ }
+
+ return TRUE;
+}
+
+static BOOL notify_delete_child(void* data, size_t index, va_list ap)
+{
+ CliprdrFuseFile* child = data;
+
+ WINPR_ASSERT(child);
+
+ CliprdrFileContext* file_context = va_arg(ap, CliprdrFileContext*);
+ CliprdrFuseFile* parent = va_arg(ap, CliprdrFuseFile*);
+
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(parent);
+
+ WINPR_ASSERT(file_context->fuse_sess);
+ fuse_lowlevel_notify_delete(file_context->fuse_sess, parent->ino, child->ino, child->filename,
+ strlen(child->filename));
+
+ return TRUE;
+}
+
+static BOOL invalidate_inode(void* data, size_t index, va_list ap)
+{
+ CliprdrFuseFile* fuse_file = data;
+
+ WINPR_ASSERT(fuse_file);
+
+ CliprdrFileContext* file_context = va_arg(ap, CliprdrFileContext*);
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(file_context->fuse_sess);
+
+ ArrayList_ForEach(fuse_file->children, notify_delete_child, file_context, fuse_file);
+
+ DEBUG_CLIPRDR(file_context->log, "Invalidating inode %lu for file \"%s\"", fuse_file->ino,
+ fuse_file->filename);
+ fuse_lowlevel_notify_inval_inode(file_context->fuse_sess, fuse_file->ino, 0, 0);
+ WLog_Print(file_context->log, WLOG_DEBUG, "Inode %lu invalidated", fuse_file->ino);
+
+ return TRUE;
+}
+
+static void clear_selection(CliprdrFileContext* file_context, BOOL all_selections,
+ CliprdrFuseClipDataEntry* clip_data_entry)
+{
+ FuseFileClearContext clear_context = { 0 };
+ CliprdrFuseFile* root_dir = NULL;
+ CliprdrFuseFile* clip_data_dir = NULL;
+
+ WINPR_ASSERT(file_context);
+
+ root_dir = file_context->root_dir;
+ WINPR_ASSERT(root_dir);
+
+ clear_context.file_context = file_context;
+ clear_context.fuse_files = ArrayList_New(FALSE);
+ WINPR_ASSERT(clear_context.fuse_files);
+
+ wObject* aobj = ArrayList_Object(clear_context.fuse_files);
+ WINPR_ASSERT(aobj);
+ aobj->fnObjectFree = fuse_file_free;
+
+ if (clip_data_entry)
+ {
+ clip_data_dir = clip_data_entry->clip_data_dir;
+ clip_data_entry->clip_data_dir = NULL;
+
+ WINPR_ASSERT(clip_data_dir);
+
+ ArrayList_Remove(root_dir->children, clip_data_dir);
+
+ clear_context.has_clip_data_id = clip_data_dir->has_clip_data_id;
+ clear_context.clip_data_id = clip_data_dir->clip_data_id;
+ }
+ clear_context.all_files = all_selections;
+
+ if (clip_data_entry && clip_data_entry->has_clip_data_id)
+ WLog_Print(file_context->log, WLOG_DEBUG, "Clearing selection for clipDataId %u",
+ clip_data_entry->clip_data_id);
+ else
+ WLog_Print(file_context->log, WLOG_DEBUG, "Clearing selection%s",
+ all_selections ? "s" : "");
+
+ HashTable_Foreach(file_context->request_table, maybe_clear_fuse_request, &clear_context);
+ HashTable_Foreach(file_context->inode_table, maybe_steal_inode, &clear_context);
+ HashTable_Unlock(file_context->inode_table);
+
+ if (file_context->fuse_sess)
+ {
+ /*
+ * fuse_lowlevel_notify_inval_inode() is a blocking operation. If we receive a
+ * FUSE request (e.g. read()), then FUSE would block in read(), since the
+ * mutex of the inode_table would still be locked, if we wouldn't unlock it
+ * here.
+ * So, to avoid a deadlock here, unlock the mutex and reply all incoming
+ * operations with -ENOENT until the invalidation process is complete.
+ */
+ ArrayList_ForEach(clear_context.fuse_files, invalidate_inode, file_context);
+ if (clip_data_dir)
+ {
+ fuse_lowlevel_notify_delete(file_context->fuse_sess, file_context->root_dir->ino,
+ clip_data_dir->ino, clip_data_dir->filename,
+ strlen(clip_data_dir->filename));
+ }
+ }
+ ArrayList_Free(clear_context.fuse_files);
+
+ HashTable_Lock(file_context->inode_table);
+ if (clip_data_entry && clip_data_entry->has_clip_data_id)
+ WLog_Print(file_context->log, WLOG_DEBUG, "Selection cleared for clipDataId %u",
+ clip_data_entry->clip_data_id);
+ else
+ WLog_Print(file_context->log, WLOG_DEBUG, "Selection%s cleared", all_selections ? "s" : "");
+}
+
+static void clear_entry_selection(CliprdrFuseClipDataEntry* clip_data_entry)
+{
+ WINPR_ASSERT(clip_data_entry);
+
+ if (!clip_data_entry->clip_data_dir)
+ return;
+
+ clear_selection(clip_data_entry->file_context, FALSE, clip_data_entry);
+}
+
+static void clear_no_cdi_entry(CliprdrFileContext* file_context)
+{
+ WINPR_ASSERT(file_context);
+
+ if (!file_context->clip_data_entry_without_id)
+ return;
+
+ WINPR_ASSERT(file_context->inode_table);
+
+ HashTable_Lock(file_context->inode_table);
+ clear_entry_selection(file_context->clip_data_entry_without_id);
+
+ clip_data_entry_free(file_context->clip_data_entry_without_id);
+ file_context->clip_data_entry_without_id = NULL;
+ HashTable_Unlock(file_context->inode_table);
+}
+
+static BOOL clear_clip_data_entries(const void* key, void* value, void* arg)
+{
+ clear_entry_selection(value);
+
+ return TRUE;
+}
+
+static void clear_cdi_entries(CliprdrFileContext* file_context)
+{
+ WINPR_ASSERT(file_context);
+
+ HashTable_Lock(file_context->inode_table);
+ HashTable_Foreach(file_context->clip_data_table, clear_clip_data_entries, NULL);
+
+ HashTable_Clear(file_context->clip_data_table);
+ HashTable_Unlock(file_context->inode_table);
+}
+
+static UINT prepare_clip_data_entry_with_id(CliprdrFileContext* file_context)
+{
+ CliprdrFuseClipDataEntry* clip_data_entry = NULL;
+
+ WINPR_ASSERT(file_context);
+
+ clip_data_entry = clip_data_entry_new(file_context, TRUE);
+ if (!clip_data_entry)
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to create clipDataEntry");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ HashTable_Lock(file_context->inode_table);
+ if (!HashTable_Insert(file_context->clip_data_table,
+ (void*)(UINT_PTR)clip_data_entry->clip_data_id, clip_data_entry))
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to insert clipDataEntry");
+ clip_data_entry_free(clip_data_entry);
+ return ERROR_INTERNAL_ERROR;
+ }
+ HashTable_Unlock(file_context->inode_table);
+
+ // HashTable_Insert owns clip_data_entry
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc)
+ file_context->current_clip_data_id = clip_data_entry->clip_data_id;
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT prepare_clip_data_entry_without_id(CliprdrFileContext* file_context)
+{
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(!file_context->clip_data_entry_without_id);
+
+ file_context->clip_data_entry_without_id = clip_data_entry_new(file_context, FALSE);
+ if (!file_context->clip_data_entry_without_id)
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to create clipDataEntry");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return CHANNEL_RC_OK;
+}
+#endif
+
+UINT cliprdr_file_context_notify_new_server_format_list(CliprdrFileContext* file_context)
+{
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(file_context->context);
+
+#if defined(WITH_FUSE)
+ clear_no_cdi_entry(file_context);
+ /* TODO: assign timeouts to old locks instead */
+ clear_cdi_entries(file_context);
+
+ if (does_server_support_clipdata_locking(file_context))
+ return prepare_clip_data_entry_with_id(file_context);
+ else
+ return prepare_clip_data_entry_without_id(file_context);
+#else
+ return CHANNEL_RC_OK;
+#endif
+}
+
+UINT cliprdr_file_context_notify_new_client_format_list(CliprdrFileContext* file_context)
+{
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(file_context->context);
+
+#if defined(WITH_FUSE)
+ clear_no_cdi_entry(file_context);
+ /* TODO: assign timeouts to old locks instead */
+ clear_cdi_entries(file_context);
+#endif
+
+ return CHANNEL_RC_OK;
+}
+
+static CliprdrLocalStream* cliprdr_local_stream_new(CliprdrFileContext* context, UINT32 streamID,
+ const char* data, size_t size);
+static void cliprdr_file_session_terminate(CliprdrFileContext* file, BOOL stop_thread);
+static BOOL local_stream_discard(const void* key, void* value, void* arg);
+
+static void writelog(wLog* log, DWORD level, const char* fname, const char* fkt, size_t line, ...)
+{
+ if (!WLog_IsLevelActive(log, level))
+ return;
+
+ va_list ap;
+ va_start(ap, line);
+ WLog_PrintMessageVA(log, WLOG_MESSAGE_TEXT, level, line, fname, fkt, ap);
+ va_end(ap);
+}
+
+#if defined(WITH_FUSE)
+static void cliprdr_file_fuse_lookup(fuse_req_t req, fuse_ino_t parent, const char* name);
+static void cliprdr_file_fuse_getattr(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi);
+static void cliprdr_file_fuse_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+ struct fuse_file_info* fi);
+static void cliprdr_file_fuse_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+ struct fuse_file_info* fi);
+static void cliprdr_file_fuse_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi);
+static void cliprdr_file_fuse_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi);
+
+static const struct fuse_lowlevel_ops cliprdr_file_fuse_oper = {
+ .lookup = cliprdr_file_fuse_lookup,
+ .getattr = cliprdr_file_fuse_getattr,
+ .readdir = cliprdr_file_fuse_readdir,
+ .open = cliprdr_file_fuse_open,
+ .read = cliprdr_file_fuse_read,
+ .opendir = cliprdr_file_fuse_opendir,
+};
+
+static CliprdrFuseFile* get_fuse_file_by_ino(CliprdrFileContext* file_context, fuse_ino_t fuse_ino)
+{
+ WINPR_ASSERT(file_context);
+
+ return HashTable_GetItemValue(file_context->inode_table, (void*)(UINT_PTR)fuse_ino);
+}
+
+static CliprdrFuseFile* get_fuse_file_by_name_from_parent(CliprdrFileContext* file_context,
+ CliprdrFuseFile* parent, const char* name)
+{
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(parent);
+
+ for (size_t i = 0; i < ArrayList_Count(parent->children); ++i)
+ {
+ CliprdrFuseFile* child = ArrayList_GetItem(parent->children, i);
+
+ WINPR_ASSERT(child);
+
+ if (strcmp(name, child->filename) == 0)
+ return child;
+ }
+
+ DEBUG_CLIPRDR(file_context->log, "Requested file \"%s\" in directory \"%s\" does not exist",
+ name, parent->filename);
+
+ return NULL;
+}
+
+static CliprdrFuseRequest* cliprdr_fuse_request_new(CliprdrFileContext* file_context,
+ CliprdrFuseFile* fuse_file, fuse_req_t fuse_req,
+ FuseLowlevelOperationType operation_type)
+{
+ CliprdrFuseRequest* fuse_request = NULL;
+ UINT32 stream_id = file_context->next_stream_id;
+
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(fuse_file);
+
+ fuse_request = calloc(1, sizeof(CliprdrFuseRequest));
+ if (!fuse_request)
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to allocate FUSE request for file \"%s\"",
+ fuse_file->filename_with_root);
+ return NULL;
+ }
+
+ fuse_request->fuse_file = fuse_file;
+ fuse_request->fuse_req = fuse_req;
+ fuse_request->operation_type = operation_type;
+
+ while (stream_id == 0 ||
+ HashTable_GetItemValue(file_context->request_table, (void*)(UINT_PTR)stream_id))
+ ++stream_id;
+ fuse_request->stream_id = stream_id;
+
+ file_context->next_stream_id = stream_id + 1;
+
+ if (!HashTable_Insert(file_context->request_table, (void*)(UINT_PTR)fuse_request->stream_id,
+ fuse_request))
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to track FUSE request for file \"%s\"",
+ fuse_file->filename_with_root);
+ free(fuse_request);
+ return NULL;
+ }
+
+ return fuse_request;
+}
+
+static BOOL request_file_size_async(CliprdrFileContext* file_context, CliprdrFuseFile* fuse_file,
+ fuse_req_t fuse_req, FuseLowlevelOperationType operation_type)
+{
+ CliprdrFuseRequest* fuse_request = NULL;
+ CLIPRDR_FILE_CONTENTS_REQUEST file_contents_request = { 0 };
+
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(fuse_file);
+
+ fuse_request = cliprdr_fuse_request_new(file_context, fuse_file, fuse_req, operation_type);
+ if (!fuse_request)
+ return FALSE;
+
+ file_contents_request.common.msgType = CB_FILECONTENTS_REQUEST;
+ file_contents_request.streamId = fuse_request->stream_id;
+ file_contents_request.listIndex = fuse_file->list_idx;
+ file_contents_request.dwFlags = FILECONTENTS_SIZE;
+ file_contents_request.cbRequested = 0x8;
+ file_contents_request.haveClipDataId = fuse_file->has_clip_data_id;
+ file_contents_request.clipDataId = fuse_file->clip_data_id;
+
+ if (file_context->context->ClientFileContentsRequest(file_context->context,
+ &file_contents_request))
+ {
+ WLog_Print(file_context->log, WLOG_ERROR,
+ "Failed to send FileContentsRequest for file \"%s\"",
+ fuse_file->filename_with_root);
+ HashTable_Remove(file_context->request_table, (void*)(UINT_PTR)fuse_request->stream_id);
+ free(fuse_request);
+ return FALSE;
+ }
+ DEBUG_CLIPRDR(file_context->log, "Requested file size for file \"%s\" with stream id %u",
+ fuse_file->filename, fuse_request->stream_id);
+
+ return TRUE;
+}
+
+static void write_file_attributes(CliprdrFuseFile* fuse_file, struct stat* attr)
+{
+ memset(attr, 0, sizeof(struct stat));
+
+ if (!fuse_file)
+ return;
+
+ attr->st_ino = fuse_file->ino;
+ if (fuse_file->is_directory)
+ {
+ attr->st_mode = S_IFDIR | (fuse_file->is_readonly ? 0555 : 0755);
+ attr->st_nlink = 2;
+ }
+ else
+ {
+ attr->st_mode = S_IFREG | (fuse_file->is_readonly ? 0444 : 0644);
+ attr->st_nlink = 1;
+ attr->st_size = fuse_file->size;
+ }
+ attr->st_uid = getuid();
+ attr->st_gid = getgid();
+ attr->st_atime = attr->st_mtime = attr->st_ctime =
+ (fuse_file->has_last_write_time ? fuse_file->last_write_time_unix : time(NULL));
+}
+
+static void cliprdr_file_fuse_lookup(fuse_req_t fuse_req, fuse_ino_t parent_ino, const char* name)
+{
+ CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
+ CliprdrFuseFile* parent = NULL;
+ CliprdrFuseFile* fuse_file = NULL;
+ struct fuse_entry_param entry = { 0 };
+
+ WINPR_ASSERT(file_context);
+
+ HashTable_Lock(file_context->inode_table);
+ if (!(parent = get_fuse_file_by_ino(file_context, parent_ino)) || !parent->is_directory)
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, ENOENT);
+ return;
+ }
+ if (!(fuse_file = get_fuse_file_by_name_from_parent(file_context, parent, name)))
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, ENOENT);
+ return;
+ }
+
+ DEBUG_CLIPRDR(file_context->log, "lookup() has been called for \"%s\"", name);
+ DEBUG_CLIPRDR(file_context->log, "Parent is \"%s\", child is \"%s\"",
+ parent->filename_with_root, fuse_file->filename_with_root);
+
+ if (!fuse_file->is_directory && !fuse_file->has_size)
+ {
+ BOOL result = 0;
+
+ result =
+ request_file_size_async(file_context, fuse_file, fuse_req, FUSE_LL_OPERATION_LOOKUP);
+ HashTable_Unlock(file_context->inode_table);
+
+ if (!result)
+ fuse_reply_err(fuse_req, EIO);
+
+ return;
+ }
+
+ entry.ino = fuse_file->ino;
+ write_file_attributes(fuse_file, &entry.attr);
+ entry.attr_timeout = 1.0;
+ entry.entry_timeout = 1.0;
+ HashTable_Unlock(file_context->inode_table);
+
+ fuse_reply_entry(fuse_req, &entry);
+}
+
+static void cliprdr_file_fuse_getattr(fuse_req_t fuse_req, fuse_ino_t fuse_ino,
+ struct fuse_file_info* file_info)
+{
+ CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
+ CliprdrFuseFile* fuse_file = NULL;
+ struct stat attr = { 0 };
+
+ WINPR_ASSERT(file_context);
+
+ HashTable_Lock(file_context->inode_table);
+ if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino)))
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, ENOENT);
+ return;
+ }
+
+ DEBUG_CLIPRDR(file_context->log, "getattr() has been called for file \"%s\"",
+ fuse_file->filename_with_root);
+
+ if (!fuse_file->is_directory && !fuse_file->has_size)
+ {
+ BOOL result = 0;
+
+ result =
+ request_file_size_async(file_context, fuse_file, fuse_req, FUSE_LL_OPERATION_GETATTR);
+ HashTable_Unlock(file_context->inode_table);
+
+ if (!result)
+ fuse_reply_err(fuse_req, EIO);
+
+ return;
+ }
+
+ write_file_attributes(fuse_file, &attr);
+ HashTable_Unlock(file_context->inode_table);
+
+ fuse_reply_attr(fuse_req, &attr, 1.0);
+}
+
+static void cliprdr_file_fuse_open(fuse_req_t fuse_req, fuse_ino_t fuse_ino,
+ struct fuse_file_info* file_info)
+{
+ CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
+ CliprdrFuseFile* fuse_file = NULL;
+
+ WINPR_ASSERT(file_context);
+
+ HashTable_Lock(file_context->inode_table);
+ if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino)))
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, ENOENT);
+ return;
+ }
+ if (fuse_file->is_directory)
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, EISDIR);
+ return;
+ }
+ HashTable_Unlock(file_context->inode_table);
+
+ if ((file_info->flags & O_ACCMODE) != O_RDONLY)
+ {
+ fuse_reply_err(fuse_req, EACCES);
+ return;
+ }
+
+ /* Important for KDE to get file correctly */
+ file_info->direct_io = 1;
+
+ fuse_reply_open(fuse_req, file_info);
+}
+
+static BOOL request_file_range_async(CliprdrFileContext* file_context, CliprdrFuseFile* fuse_file,
+ fuse_req_t fuse_req, off_t offset, size_t requested_size)
+{
+ CliprdrFuseRequest* fuse_request = NULL;
+ CLIPRDR_FILE_CONTENTS_REQUEST file_contents_request = { 0 };
+
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(fuse_file);
+
+ fuse_request =
+ cliprdr_fuse_request_new(file_context, fuse_file, fuse_req, FUSE_LL_OPERATION_READ);
+ if (!fuse_request)
+ return FALSE;
+
+ file_contents_request.common.msgType = CB_FILECONTENTS_REQUEST;
+ file_contents_request.streamId = fuse_request->stream_id;
+ file_contents_request.listIndex = fuse_file->list_idx;
+ file_contents_request.dwFlags = FILECONTENTS_RANGE;
+ file_contents_request.nPositionLow = offset & 0xFFFFFFFF;
+ file_contents_request.nPositionHigh = offset >> 32 & 0xFFFFFFFF;
+ file_contents_request.cbRequested = requested_size;
+ file_contents_request.haveClipDataId = fuse_file->has_clip_data_id;
+ file_contents_request.clipDataId = fuse_file->clip_data_id;
+
+ if (file_context->context->ClientFileContentsRequest(file_context->context,
+ &file_contents_request))
+ {
+ WLog_Print(file_context->log, WLOG_ERROR,
+ "Failed to send FileContentsRequest for file \"%s\"",
+ fuse_file->filename_with_root);
+ HashTable_Remove(file_context->request_table, (void*)(UINT_PTR)fuse_request->stream_id);
+ return FALSE;
+ }
+
+ // file_context->request_table owns fuse_request
+ // NOLINTBEGIN(clang-analyzer-unix.Malloc)
+ DEBUG_CLIPRDR(
+ file_context->log,
+ "Requested file range (%zu Bytes at offset %lu) for file \"%s\" with stream id %u",
+ requested_size, offset, fuse_file->filename, fuse_request->stream_id);
+
+ return TRUE;
+ // NOLINTEND(clang-analyzer-unix.Malloc)
+}
+
+static void cliprdr_file_fuse_read(fuse_req_t fuse_req, fuse_ino_t fuse_ino, size_t size,
+ off_t offset, struct fuse_file_info* file_info)
+{
+ CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
+ CliprdrFuseFile* fuse_file = NULL;
+ BOOL result = 0;
+
+ WINPR_ASSERT(file_context);
+
+ HashTable_Lock(file_context->inode_table);
+ if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino)))
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, ENOENT);
+ return;
+ }
+ if (fuse_file->is_directory)
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, EISDIR);
+ return;
+ }
+ if (!fuse_file->has_size || offset > fuse_file->size)
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, EINVAL);
+ return;
+ }
+
+ size = MIN(size, 8 * 1024 * 1024);
+
+ result = request_file_range_async(file_context, fuse_file, fuse_req, offset, size);
+ HashTable_Unlock(file_context->inode_table);
+
+ if (!result)
+ fuse_reply_err(fuse_req, EIO);
+}
+
+static void cliprdr_file_fuse_opendir(fuse_req_t fuse_req, fuse_ino_t fuse_ino,
+ struct fuse_file_info* file_info)
+{
+ CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
+ CliprdrFuseFile* fuse_file = NULL;
+
+ WINPR_ASSERT(file_context);
+
+ HashTable_Lock(file_context->inode_table);
+ if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino)))
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, ENOENT);
+ return;
+ }
+ if (!fuse_file->is_directory)
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, ENOTDIR);
+ return;
+ }
+ HashTable_Unlock(file_context->inode_table);
+
+ if ((file_info->flags & O_ACCMODE) != O_RDONLY)
+ {
+ fuse_reply_err(fuse_req, EACCES);
+ return;
+ }
+
+ fuse_reply_open(fuse_req, file_info);
+}
+
+static void cliprdr_file_fuse_readdir(fuse_req_t fuse_req, fuse_ino_t fuse_ino, size_t max_size,
+ off_t offset, struct fuse_file_info* file_info)
+{
+ CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
+ CliprdrFuseFile* fuse_file = NULL;
+ CliprdrFuseFile* child = NULL;
+ struct stat attr = { 0 };
+ size_t written_size = 0;
+ size_t entry_size = 0;
+ char* filename = NULL;
+ char* buf = NULL;
+
+ WINPR_ASSERT(file_context);
+
+ HashTable_Lock(file_context->inode_table);
+ if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino)))
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, ENOENT);
+ return;
+ }
+ if (!fuse_file->is_directory)
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, ENOTDIR);
+ return;
+ }
+
+ DEBUG_CLIPRDR(file_context->log, "Reading directory \"%s\" at offset %lu",
+ fuse_file->filename_with_root, offset);
+
+ if (offset >= ArrayList_Count(fuse_file->children))
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_buf(fuse_req, NULL, 0);
+ return;
+ }
+
+ buf = calloc(max_size, sizeof(char));
+ if (!buf)
+ {
+ HashTable_Unlock(file_context->inode_table);
+ fuse_reply_err(fuse_req, ENOMEM);
+ return;
+ }
+ written_size = 0;
+
+ for (off_t i = offset; i < 2; ++i)
+ {
+ if (i == 0)
+ {
+ write_file_attributes(fuse_file, &attr);
+ filename = ".";
+ }
+ else if (i == 1)
+ {
+ write_file_attributes(fuse_file->parent, &attr);
+ attr.st_ino = fuse_file->parent ? attr.st_ino : FUSE_ROOT_ID;
+ attr.st_mode = fuse_file->parent ? attr.st_mode : 0555;
+ filename = "..";
+ }
+ else
+ {
+ WINPR_ASSERT(FALSE);
+ }
+
+ /**
+ * buf needs to be large enough to hold the entry. If it's not, then the
+ * entry is not filled in but the size of the entry is still returned.
+ */
+ entry_size = fuse_add_direntry(fuse_req, buf + written_size, max_size - written_size,
+ filename, &attr, i + 1);
+ if (entry_size > max_size - written_size)
+ break;
+
+ written_size += entry_size;
+ }
+
+ for (size_t j = 0, i = 2; j < ArrayList_Count(fuse_file->children); ++j, ++i)
+ {
+ if (i < offset)
+ continue;
+
+ child = ArrayList_GetItem(fuse_file->children, j);
+
+ write_file_attributes(child, &attr);
+ entry_size = fuse_add_direntry(fuse_req, buf + written_size, max_size - written_size,
+ child->filename, &attr, i + 1);
+ if (entry_size > max_size - written_size)
+ break;
+
+ written_size += entry_size;
+ }
+ HashTable_Unlock(file_context->inode_table);
+
+ fuse_reply_buf(fuse_req, buf, written_size);
+ free(buf);
+}
+
+static void fuse_abort(int sig, const char* signame, void* context)
+{
+ CliprdrFileContext* file = (CliprdrFileContext*)context;
+
+ if (file)
+ {
+ WLog_Print(file->log, WLOG_INFO, "signal %s [%d] aborting session", signame, sig);
+ cliprdr_file_session_terminate(file, FALSE);
+ }
+}
+
+static DWORD WINAPI cliprdr_file_fuse_thread(LPVOID arg)
+{
+ CliprdrFileContext* file = (CliprdrFileContext*)arg;
+
+ WINPR_ASSERT(file);
+
+ DEBUG_CLIPRDR(file->log, "Starting fuse with mountpoint '%s'", file->path);
+
+ struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
+ fuse_opt_add_arg(&args, file->path);
+ file->fuse_sess = fuse_session_new(&args, &cliprdr_file_fuse_oper,
+ sizeof(cliprdr_file_fuse_oper), (void*)file);
+ SetEvent(file->fuse_start_sync);
+
+ if (file->fuse_sess != NULL)
+ {
+ freerdp_add_signal_cleanup_handler(file, fuse_abort);
+ if (0 == fuse_session_mount(file->fuse_sess, file->path))
+ {
+ fuse_session_loop(file->fuse_sess);
+ fuse_session_unmount(file->fuse_sess);
+ }
+ freerdp_del_signal_cleanup_handler(file, fuse_abort);
+
+ WLog_Print(file->log, WLOG_DEBUG, "Waiting for FUSE stop sync");
+ if (WaitForSingleObject(file->fuse_stop_sync, INFINITE) == WAIT_FAILED)
+ WLog_Print(file->log, WLOG_ERROR, "Failed to wait for stop sync");
+ fuse_session_destroy(file->fuse_sess);
+ }
+ fuse_opt_free_args(&args);
+
+ DEBUG_CLIPRDR(file->log, "Quitting fuse with mountpoint '%s'", file->path);
+
+ ExitThread(0);
+ return 0;
+}
+
+static UINT cliprdr_file_context_server_file_contents_response(
+ CliprdrClientContext* cliprdr_context,
+ const CLIPRDR_FILE_CONTENTS_RESPONSE* file_contents_response)
+{
+ CliprdrFileContext* file_context = NULL;
+ CliprdrFuseRequest* fuse_request = NULL;
+ struct fuse_entry_param entry = { 0 };
+
+ WINPR_ASSERT(cliprdr_context);
+ WINPR_ASSERT(file_contents_response);
+
+ file_context = cliprdr_context->custom;
+ WINPR_ASSERT(file_context);
+
+ HashTable_Lock(file_context->inode_table);
+ fuse_request = HashTable_GetItemValue(file_context->request_table,
+ (void*)(UINT_PTR)file_contents_response->streamId);
+ if (!fuse_request)
+ {
+ HashTable_Unlock(file_context->inode_table);
+ return CHANNEL_RC_OK;
+ }
+ HashTable_Remove(file_context->request_table,
+ (void*)(UINT_PTR)file_contents_response->streamId);
+
+ if (!(file_contents_response->common.msgFlags & CB_RESPONSE_OK))
+ {
+ WLog_Print(file_context->log, WLOG_WARN,
+ "FileContentsRequests for file \"%s\" was unsuccessful",
+ fuse_request->fuse_file->filename);
+ HashTable_Unlock(file_context->inode_table);
+
+ fuse_reply_err(fuse_request->fuse_req, EIO);
+ free(fuse_request);
+ return CHANNEL_RC_OK;
+ }
+
+ if ((fuse_request->operation_type == FUSE_LL_OPERATION_LOOKUP ||
+ fuse_request->operation_type == FUSE_LL_OPERATION_GETATTR) &&
+ file_contents_response->cbRequested != sizeof(UINT64))
+ {
+ WLog_Print(file_context->log, WLOG_WARN,
+ "Received invalid file size for file \"%s\" from the client",
+ fuse_request->fuse_file->filename);
+ HashTable_Unlock(file_context->inode_table);
+
+ fuse_reply_err(fuse_request->fuse_req, EIO);
+ free(fuse_request);
+ return CHANNEL_RC_OK;
+ }
+
+ if (fuse_request->operation_type == FUSE_LL_OPERATION_LOOKUP ||
+ fuse_request->operation_type == FUSE_LL_OPERATION_GETATTR)
+ {
+ DEBUG_CLIPRDR(file_context->log, "Received file size for file \"%s\" with stream id %u",
+ fuse_request->fuse_file->filename, file_contents_response->streamId);
+
+ fuse_request->fuse_file->size = *((const UINT64*)file_contents_response->requestedData);
+ fuse_request->fuse_file->has_size = TRUE;
+
+ entry.ino = fuse_request->fuse_file->ino;
+ write_file_attributes(fuse_request->fuse_file, &entry.attr);
+ entry.attr_timeout = 1.0;
+ entry.entry_timeout = 1.0;
+ }
+ else if (fuse_request->operation_type == FUSE_LL_OPERATION_READ)
+ {
+ DEBUG_CLIPRDR(file_context->log, "Received file range for file \"%s\" with stream id %u",
+ fuse_request->fuse_file->filename, file_contents_response->streamId);
+ }
+ HashTable_Unlock(file_context->inode_table);
+
+ switch (fuse_request->operation_type)
+ {
+ case FUSE_LL_OPERATION_NONE:
+ break;
+ case FUSE_LL_OPERATION_LOOKUP:
+ fuse_reply_entry(fuse_request->fuse_req, &entry);
+ break;
+ case FUSE_LL_OPERATION_GETATTR:
+ fuse_reply_attr(fuse_request->fuse_req, &entry.attr, entry.attr_timeout);
+ break;
+ case FUSE_LL_OPERATION_READ:
+ fuse_reply_buf(fuse_request->fuse_req,
+ (const char*)file_contents_response->requestedData,
+ file_contents_response->cbRequested);
+ break;
+ }
+
+ free(fuse_request);
+
+ return CHANNEL_RC_OK;
+}
+#endif
+
+static UINT cliprdr_file_context_send_file_contents_failure(
+ CliprdrFileContext* file, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
+{
+ CLIPRDR_FILE_CONTENTS_RESPONSE response = { 0 };
+
+ WINPR_ASSERT(file);
+ WINPR_ASSERT(fileContentsRequest);
+
+ const UINT64 offset = (((UINT64)fileContentsRequest->nPositionHigh) << 32) |
+ ((UINT64)fileContentsRequest->nPositionLow);
+ writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__,
+ "server file contents request [lockID %" PRIu32 ", streamID %" PRIu32
+ ", index %" PRIu32 "] offset %" PRIu64 ", size %" PRIu32 " failed",
+ fileContentsRequest->clipDataId, fileContentsRequest->streamId,
+ fileContentsRequest->listIndex, offset, fileContentsRequest->cbRequested);
+
+ response.common.msgFlags = CB_RESPONSE_FAIL;
+ response.streamId = fileContentsRequest->streamId;
+
+ WINPR_ASSERT(file->context);
+ WINPR_ASSERT(file->context->ClientFileContentsResponse);
+ return file->context->ClientFileContentsResponse(file->context, &response);
+}
+
+static UINT
+cliprdr_file_context_send_contents_response(CliprdrFileContext* file,
+ const CLIPRDR_FILE_CONTENTS_REQUEST* request,
+ const void* data, size_t size)
+{
+ CLIPRDR_FILE_CONTENTS_RESPONSE response = { .streamId = request->streamId,
+ .requestedData = data,
+ .cbRequested = size,
+ .common.msgFlags = CB_RESPONSE_OK };
+
+ WINPR_ASSERT(request);
+ WINPR_ASSERT(file);
+
+ WLog_Print(file->log, WLOG_DEBUG, "send contents response streamID=%" PRIu32 ", size=%" PRIu32,
+ response.streamId, response.cbRequested);
+ WINPR_ASSERT(file->context);
+ WINPR_ASSERT(file->context->ClientFileContentsResponse);
+ return file->context->ClientFileContentsResponse(file->context, &response);
+}
+
+static BOOL dump_streams(const void* key, void* value, void* arg)
+{
+ const UINT32* ukey = key;
+ CliprdrLocalStream* cur = value;
+
+ writelog(cur->context->log, WLOG_WARN, __FILE__, __func__, __LINE__,
+ "[key %" PRIu32 "] lockID %" PRIu32 ", count %" PRIuz ", locked %d", *ukey,
+ cur->lockId, cur->count, cur->locked);
+ for (size_t x = 0; x < cur->count; x++)
+ {
+ const CliprdrLocalFile* file = &cur->files[x];
+ writelog(cur->context->log, WLOG_WARN, __FILE__, __func__, __LINE__, "file [%" PRIuz "] ",
+ x, file->name, file->size);
+ }
+ return TRUE;
+}
+
+static CliprdrLocalFile* file_info_for_request(CliprdrFileContext* file, UINT32 lockId,
+ UINT32 listIndex)
+{
+ WINPR_ASSERT(file);
+
+ CliprdrLocalStream* cur = HashTable_GetItemValue(file->local_streams, &lockId);
+ if (cur)
+ {
+ if (listIndex < cur->count)
+ {
+ CliprdrLocalFile* f = &cur->files[listIndex];
+ return f;
+ }
+ else
+ {
+ writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__,
+ "invalid entry index for lockID %" PRIu32 ", index %" PRIu32 " [count %" PRIu32
+ "] [locked %d]",
+ lockId, listIndex, cur->count, cur->locked);
+ }
+ }
+ else
+ {
+ writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__,
+ "missing entry for lockID %" PRIu32 ", index %" PRIu32, lockId, listIndex);
+ HashTable_Foreach(file->local_streams, dump_streams, file);
+ }
+
+ return NULL;
+}
+
+static CliprdrLocalFile* file_for_request(CliprdrFileContext* file, UINT32 lockId, UINT32 listIndex)
+{
+ CliprdrLocalFile* f = file_info_for_request(file, lockId, listIndex);
+ if (f)
+ {
+ if (!f->fp)
+ {
+ const char* name = f->name;
+ f->fp = winpr_fopen(name, "rb");
+ }
+ if (!f->fp)
+ {
+ char ebuffer[256] = { 0 };
+ writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__,
+ "[lockID %" PRIu32 ", index %" PRIu32
+ "] failed to open file '%s' [size %" PRId64 "] %s [%d]",
+ lockId, listIndex, f->name, f->size,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
+ return NULL;
+ }
+ }
+
+ return f;
+}
+
+static void cliprdr_local_file_try_close(CliprdrLocalFile* file, UINT res, UINT64 offset,
+ UINT64 size)
+{
+ WINPR_ASSERT(file);
+
+ if (res != 0)
+ {
+ WINPR_ASSERT(file->context);
+ WLog_Print(file->context->log, WLOG_DEBUG, "closing file %s after error %" PRIu32,
+ file->name, res);
+ }
+ else if (((file->size > 0) && (offset + size >= (UINT64)file->size)))
+ {
+ WINPR_ASSERT(file->context);
+ WLog_Print(file->context->log, WLOG_DEBUG, "closing file %s after read", file->name);
+ }
+ else
+ {
+ // TODO: we need to keep track of open files to avoid running out of file descriptors
+ // TODO: for the time being just close again.
+ }
+ if (file->fp)
+ fclose(file->fp);
+ file->fp = NULL;
+}
+
+static UINT cliprdr_file_context_server_file_size_request(
+ CliprdrFileContext* file, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
+{
+ WINPR_ASSERT(fileContentsRequest);
+
+ if (fileContentsRequest->cbRequested != sizeof(UINT64))
+ {
+ WLog_Print(file->log, WLOG_WARN, "unexpected FILECONTENTS_SIZE request: %" PRIu32 " bytes",
+ fileContentsRequest->cbRequested);
+ }
+
+ HashTable_Lock(file->local_streams);
+
+ UINT res = CHANNEL_RC_OK;
+ CliprdrLocalFile* rfile =
+ file_for_request(file, fileContentsRequest->clipDataId, fileContentsRequest->listIndex);
+ if (!rfile)
+ res = cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
+ else
+ {
+ if (_fseeki64(rfile->fp, 0, SEEK_END) < 0)
+ res = cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
+ else
+ {
+ const INT64 size = _ftelli64(rfile->fp);
+ rfile->size = size;
+ cliprdr_local_file_try_close(rfile, res, 0, 0);
+
+ res = cliprdr_file_context_send_contents_response(file, fileContentsRequest, &size,
+ sizeof(size));
+ }
+ }
+
+ HashTable_Unlock(file->local_streams);
+ return res;
+}
+
+static UINT cliprdr_file_context_server_file_range_request(
+ CliprdrFileContext* file, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
+{
+ BYTE* data = NULL;
+
+ WINPR_ASSERT(fileContentsRequest);
+
+ HashTable_Lock(file->local_streams);
+ const UINT64 offset = (((UINT64)fileContentsRequest->nPositionHigh) << 32) |
+ ((UINT64)fileContentsRequest->nPositionLow);
+
+ CliprdrLocalFile* rfile =
+ file_for_request(file, fileContentsRequest->clipDataId, fileContentsRequest->listIndex);
+ if (!rfile)
+ goto fail;
+
+ if (_fseeki64(rfile->fp, offset, SEEK_SET) < 0)
+ goto fail;
+
+ data = malloc(fileContentsRequest->cbRequested);
+ if (!data)
+ goto fail;
+
+ const size_t r = fread(data, 1, fileContentsRequest->cbRequested, rfile->fp);
+ const UINT rc = cliprdr_file_context_send_contents_response(file, fileContentsRequest, data, r);
+ free(data);
+
+ cliprdr_local_file_try_close(rfile, rc, offset, fileContentsRequest->cbRequested);
+ HashTable_Unlock(file->local_streams);
+ return rc;
+fail:
+ if (rfile)
+ cliprdr_local_file_try_close(rfile, ERROR_INTERNAL_ERROR, offset,
+ fileContentsRequest->cbRequested);
+ free(data);
+ HashTable_Unlock(file->local_streams);
+ return cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
+}
+
+static void cliprdr_local_stream_free(void* obj);
+
+static UINT change_lock(CliprdrFileContext* file, UINT32 lockId, BOOL lock)
+{
+ UINT rc = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(file);
+
+ HashTable_Lock(file->local_streams);
+ CliprdrLocalStream* stream = HashTable_GetItemValue(file->local_streams, &lockId);
+ if (lock && !stream)
+ {
+ stream = cliprdr_local_stream_new(file, lockId, NULL, 0);
+ if (!HashTable_Insert(file->local_streams, &lockId, stream))
+ {
+ rc = ERROR_INTERNAL_ERROR;
+ cliprdr_local_stream_free(stream);
+ stream = NULL;
+ }
+ file->local_lock_id = lockId;
+ }
+ if (stream)
+ {
+ stream->locked = lock;
+ stream->lockId = lockId;
+ }
+
+ if (!lock)
+ {
+ if (!HashTable_Foreach(file->local_streams, local_stream_discard, file))
+ rc = ERROR_INTERNAL_ERROR;
+ }
+ HashTable_Unlock(file->local_streams);
+ return rc;
+}
+
+static UINT cliprdr_file_context_lock(CliprdrClientContext* context,
+ const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(lockClipboardData);
+ CliprdrFileContext* file = (context->custom);
+ return change_lock(file, lockClipboardData->clipDataId, TRUE);
+}
+
+static UINT cliprdr_file_context_unlock(CliprdrClientContext* context,
+ const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(unlockClipboardData);
+ CliprdrFileContext* file = (context->custom);
+ return change_lock(file, unlockClipboardData->clipDataId, FALSE);
+}
+
+static UINT cliprdr_file_context_server_file_contents_request(
+ CliprdrClientContext* context, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
+{
+ UINT error = NO_ERROR;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(fileContentsRequest);
+
+ CliprdrFileContext* file = (context->custom);
+ WINPR_ASSERT(file);
+
+ /*
+ * MS-RDPECLIP 2.2.5.3 File Contents Request PDU (CLIPRDR_FILECONTENTS_REQUEST):
+ * The FILECONTENTS_SIZE and FILECONTENTS_RANGE flags MUST NOT be set at the same time.
+ */
+ if ((fileContentsRequest->dwFlags & (FILECONTENTS_SIZE | FILECONTENTS_RANGE)) ==
+ (FILECONTENTS_SIZE | FILECONTENTS_RANGE))
+ {
+ WLog_Print(file->log, WLOG_ERROR, "invalid CLIPRDR_FILECONTENTS_REQUEST.dwFlags");
+ return cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
+ }
+
+ if (fileContentsRequest->dwFlags & FILECONTENTS_SIZE)
+ error = cliprdr_file_context_server_file_size_request(file, fileContentsRequest);
+
+ if (fileContentsRequest->dwFlags & FILECONTENTS_RANGE)
+ error = cliprdr_file_context_server_file_range_request(file, fileContentsRequest);
+
+ if (error)
+ {
+ WLog_Print(file->log, WLOG_ERROR, "failed to handle CLIPRDR_FILECONTENTS_REQUEST: 0x%08X",
+ error);
+ return cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+BOOL cliprdr_file_context_init(CliprdrFileContext* file, CliprdrClientContext* cliprdr)
+{
+ WINPR_ASSERT(file);
+ WINPR_ASSERT(cliprdr);
+
+ cliprdr->custom = file;
+ file->context = cliprdr;
+
+ cliprdr->ServerLockClipboardData = cliprdr_file_context_lock;
+ cliprdr->ServerUnlockClipboardData = cliprdr_file_context_unlock;
+ cliprdr->ServerFileContentsRequest = cliprdr_file_context_server_file_contents_request;
+#if defined(WITH_FUSE)
+ cliprdr->ServerFileContentsResponse = cliprdr_file_context_server_file_contents_response;
+#endif
+
+ return TRUE;
+}
+
+#if defined(WITH_FUSE)
+static void clear_all_selections(CliprdrFileContext* file_context)
+{
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(file_context->inode_table);
+
+ HashTable_Lock(file_context->inode_table);
+ clear_selection(file_context, TRUE, NULL);
+
+ HashTable_Clear(file_context->clip_data_table);
+ HashTable_Unlock(file_context->inode_table);
+}
+#endif
+
+BOOL cliprdr_file_context_uninit(CliprdrFileContext* file, CliprdrClientContext* cliprdr)
+{
+ WINPR_ASSERT(file);
+ WINPR_ASSERT(cliprdr);
+
+ // Clear all data before the channel is closed
+ // the cleanup handlers are dependent on a working channel.
+#if defined(WITH_FUSE)
+ if (file->inode_table)
+ {
+ clear_no_cdi_entry(file);
+ clear_all_selections(file);
+ }
+#endif
+
+ HashTable_Clear(file->local_streams);
+
+ file->context = NULL;
+#if defined(WITH_FUSE)
+ cliprdr->ServerFileContentsResponse = NULL;
+#endif
+
+ return TRUE;
+}
+
+static BOOL cliprdr_file_content_changed_and_update(void* ihash, size_t hsize, const void* data,
+ size_t size)
+{
+
+ BYTE hash[WINPR_SHA256_DIGEST_LENGTH] = { 0 };
+
+ if (hsize < sizeof(hash))
+ return FALSE;
+
+ if (!winpr_Digest(WINPR_MD_SHA256, data, size, hash, sizeof(hash)))
+ return FALSE;
+
+ const BOOL changed = memcmp(hash, ihash, sizeof(hash)) != 0;
+ if (changed)
+ memcpy(ihash, hash, sizeof(hash));
+ return changed;
+}
+
+static BOOL cliprdr_file_server_content_changed_and_update(CliprdrFileContext* file,
+ const void* data, size_t size)
+{
+ WINPR_ASSERT(file);
+ return cliprdr_file_content_changed_and_update(file->server_data_hash,
+ sizeof(file->server_data_hash), data, size);
+}
+
+static BOOL cliprdr_file_client_content_changed_and_update(CliprdrFileContext* file,
+ const void* data, size_t size)
+{
+ WINPR_ASSERT(file);
+ return cliprdr_file_content_changed_and_update(file->client_data_hash,
+ sizeof(file->client_data_hash), data, size);
+}
+
+#if defined(WITH_FUSE)
+static fuse_ino_t get_next_free_inode(CliprdrFileContext* file_context)
+{
+ fuse_ino_t ino = 0;
+
+ WINPR_ASSERT(file_context);
+
+ ino = file_context->next_ino;
+ while (ino == 0 || ino == FUSE_ROOT_ID ||
+ HashTable_GetItemValue(file_context->inode_table, (void*)(UINT_PTR)ino))
+ ++ino;
+
+ file_context->next_ino = ino + 1;
+
+ return ino;
+}
+
+static CliprdrFuseFile* clip_data_dir_new(CliprdrFileContext* file_context, BOOL has_clip_data_id,
+ UINT32 clip_data_id)
+{
+ CliprdrFuseFile* root_dir = NULL;
+ CliprdrFuseFile* clip_data_dir = NULL;
+ size_t path_length = 0;
+
+ WINPR_ASSERT(file_context);
+
+ clip_data_dir = fuse_file_new();
+ if (!clip_data_dir)
+ return NULL;
+
+ path_length = 1 + MAX_CLIP_DATA_DIR_LEN + 1;
+
+ clip_data_dir->filename_with_root = calloc(path_length, sizeof(char));
+ if (!clip_data_dir->filename_with_root)
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to allocate filename");
+ fuse_file_free(clip_data_dir);
+ return NULL;
+ }
+
+ if (has_clip_data_id)
+ _snprintf(clip_data_dir->filename_with_root, path_length, "/%u", (unsigned)clip_data_id);
+ else
+ _snprintf(clip_data_dir->filename_with_root, path_length, "/%" PRIu64, NO_CLIP_DATA_ID);
+
+ clip_data_dir->filename = strrchr(clip_data_dir->filename_with_root, '/') + 1;
+
+ clip_data_dir->ino = get_next_free_inode(file_context);
+ clip_data_dir->is_directory = TRUE;
+ clip_data_dir->is_readonly = TRUE;
+ clip_data_dir->has_clip_data_id = has_clip_data_id;
+ clip_data_dir->clip_data_id = clip_data_id;
+
+ root_dir = file_context->root_dir;
+ if (!ArrayList_Append(root_dir->children, clip_data_dir))
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to append FUSE file");
+ fuse_file_free(clip_data_dir);
+ return NULL;
+ }
+ clip_data_dir->parent = root_dir;
+
+ if (!HashTable_Insert(file_context->inode_table, (void*)(UINT_PTR)clip_data_dir->ino,
+ clip_data_dir))
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to insert inode into inode table");
+ ArrayList_Remove(root_dir->children, clip_data_dir);
+ fuse_file_free(clip_data_dir);
+ return NULL;
+ }
+
+ return clip_data_dir;
+}
+
+static char* get_parent_path(const char* filepath)
+{
+ char* base = NULL;
+ size_t parent_path_length = 0;
+ char* parent_path = NULL;
+
+ base = strrchr(filepath, '/');
+ WINPR_ASSERT(base);
+
+ while (base > filepath && *base == '/')
+ --base;
+
+ parent_path_length = 1 + base - filepath;
+ parent_path = calloc(parent_path_length + 1, sizeof(char));
+ if (!parent_path)
+ return NULL;
+
+ memcpy(parent_path, filepath, parent_path_length);
+
+ return parent_path;
+}
+
+static BOOL is_fuse_file_not_parent(const void* key, void* value, void* arg)
+{
+ CliprdrFuseFile* fuse_file = value;
+ CliprdrFuseFindParentContext* find_context = arg;
+
+ if (!fuse_file->is_directory)
+ return TRUE;
+
+ if (strcmp(find_context->parent_path, fuse_file->filename_with_root) == 0)
+ {
+ find_context->parent = fuse_file;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static CliprdrFuseFile* get_parent_directory(CliprdrFileContext* file_context, const char* path)
+{
+ CliprdrFuseFindParentContext find_context = { 0 };
+
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(path);
+
+ find_context.parent_path = get_parent_path(path);
+ if (!find_context.parent_path)
+ return NULL;
+
+ WINPR_ASSERT(!find_context.parent);
+
+ if (HashTable_Foreach(file_context->inode_table, is_fuse_file_not_parent, &find_context))
+ {
+ free(find_context.parent_path);
+ return NULL;
+ }
+ WINPR_ASSERT(find_context.parent);
+
+ free(find_context.parent_path);
+
+ return find_context.parent;
+}
+
+static BOOL set_selection_for_clip_data_entry(CliprdrFileContext* file_context,
+ CliprdrFuseClipDataEntry* clip_data_entry,
+ FILEDESCRIPTORW* files, UINT32 n_files)
+{
+ CliprdrFuseFile* clip_data_dir = NULL;
+
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(clip_data_entry);
+ WINPR_ASSERT(files);
+
+ clip_data_dir = clip_data_entry->clip_data_dir;
+ WINPR_ASSERT(clip_data_dir);
+
+ if (clip_data_entry->has_clip_data_id)
+ WLog_Print(file_context->log, WLOG_DEBUG, "Setting selection for clipDataId %u",
+ clip_data_entry->clip_data_id);
+ else
+ WLog_Print(file_context->log, WLOG_DEBUG, "Setting selection");
+
+ // NOLINTBEGIN(clang-analyzer-unix.Malloc) HashTable_Insert owns fuse_file
+ for (UINT32 i = 0; i < n_files; ++i)
+ {
+ FILEDESCRIPTORW* file = &files[i];
+ CliprdrFuseFile* fuse_file = NULL;
+ char* filename = NULL;
+ size_t path_length = 0;
+
+ fuse_file = fuse_file_new();
+ if (!fuse_file)
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to create FUSE file");
+ clear_entry_selection(clip_data_entry);
+ return FALSE;
+ }
+
+ filename = ConvertWCharToUtf8Alloc(file->cFileName, NULL);
+ if (!filename)
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to convert filename");
+ fuse_file_free(fuse_file);
+ clear_entry_selection(clip_data_entry);
+ return FALSE;
+ }
+
+ for (size_t j = 0; filename[j]; ++j)
+ {
+ if (filename[j] == '\\')
+ filename[j] = '/';
+ }
+
+ path_length = strlen(clip_data_dir->filename_with_root) + 1 + strlen(filename) + 1;
+ fuse_file->filename_with_root = calloc(path_length, sizeof(char));
+ if (!fuse_file->filename_with_root)
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to allocate filename");
+ free(filename);
+ fuse_file_free(fuse_file);
+ clear_entry_selection(clip_data_entry);
+ return FALSE;
+ }
+
+ _snprintf(fuse_file->filename_with_root, path_length, "%s/%s",
+ clip_data_dir->filename_with_root, filename);
+ free(filename);
+
+ fuse_file->filename = strrchr(fuse_file->filename_with_root, '/') + 1;
+
+ fuse_file->parent = get_parent_directory(file_context, fuse_file->filename_with_root);
+ if (!fuse_file->parent)
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Found no parent for FUSE file");
+ fuse_file_free(fuse_file);
+ clear_entry_selection(clip_data_entry);
+ return FALSE;
+ }
+
+ if (!ArrayList_Append(fuse_file->parent->children, fuse_file))
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to append FUSE file");
+ fuse_file_free(fuse_file);
+ clear_entry_selection(clip_data_entry);
+ return FALSE;
+ }
+
+ fuse_file->list_idx = i;
+ fuse_file->ino = get_next_free_inode(file_context);
+ fuse_file->has_clip_data_id = clip_data_entry->has_clip_data_id;
+ fuse_file->clip_data_id = clip_data_entry->clip_data_id;
+ if (file->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ fuse_file->is_directory = TRUE;
+ if (file->dwFileAttributes & FILE_ATTRIBUTE_READONLY)
+ fuse_file->is_readonly = TRUE;
+ if (file->dwFlags & FD_FILESIZE)
+ {
+ fuse_file->size = ((UINT64)file->nFileSizeHigh << 32) + file->nFileSizeLow;
+ fuse_file->has_size = TRUE;
+ }
+ if (file->dwFlags & FD_WRITESTIME)
+ {
+ UINT64 filetime = 0;
+
+ filetime = file->ftLastWriteTime.dwHighDateTime;
+ filetime <<= 32;
+ filetime += file->ftLastWriteTime.dwLowDateTime;
+
+ fuse_file->last_write_time_unix =
+ filetime / (10 * 1000 * 1000) - WIN32_FILETIME_TO_UNIX_EPOCH;
+ fuse_file->has_last_write_time = TRUE;
+ }
+
+ if (!HashTable_Insert(file_context->inode_table, (void*)(UINT_PTR)fuse_file->ino,
+ fuse_file))
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to insert inode into inode table");
+ fuse_file_free(fuse_file);
+ clear_entry_selection(clip_data_entry);
+ return FALSE;
+ }
+ }
+ // NOLINTEND(clang-analyzer-unix.Malloc) HashTable_Insert owns fuse_file
+
+ if (clip_data_entry->has_clip_data_id)
+ WLog_Print(file_context->log, WLOG_DEBUG, "Selection set for clipDataId %u",
+ clip_data_entry->clip_data_id);
+ else
+ WLog_Print(file_context->log, WLOG_DEBUG, "Selection set");
+
+ return TRUE;
+}
+
+static BOOL update_exposed_path(CliprdrFileContext* file_context, wClipboard* clip,
+ CliprdrFuseClipDataEntry* clip_data_entry)
+{
+ wClipboardDelegate* delegate = NULL;
+ CliprdrFuseFile* clip_data_dir = NULL;
+
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(clip);
+ WINPR_ASSERT(clip_data_entry);
+
+ delegate = ClipboardGetDelegate(clip);
+ WINPR_ASSERT(delegate);
+
+ clip_data_dir = clip_data_entry->clip_data_dir;
+ WINPR_ASSERT(clip_data_dir);
+
+ free(file_context->exposed_path);
+ file_context->exposed_path = GetCombinedPath(file_context->path, clip_data_dir->filename);
+ if (file_context->exposed_path)
+ WLog_Print(file_context->log, WLOG_DEBUG, "Updated exposed path to \"%s\"",
+ file_context->exposed_path);
+
+ delegate->basePath = file_context->exposed_path;
+
+ return delegate->basePath != NULL;
+}
+#endif
+
+BOOL cliprdr_file_context_update_server_data(CliprdrFileContext* file_context, wClipboard* clip,
+ const void* data, size_t size)
+{
+#if defined(WITH_FUSE)
+ CliprdrFuseClipDataEntry* clip_data_entry = NULL;
+ FILEDESCRIPTORW* files = NULL;
+ UINT32 n_files = 0;
+
+ WINPR_ASSERT(file_context);
+ WINPR_ASSERT(clip);
+
+ if (cliprdr_parse_file_list(data, size, &files, &n_files))
+ {
+ WLog_Print(file_context->log, WLOG_ERROR, "Failed to parse file list");
+ return FALSE;
+ }
+
+ HashTable_Lock(file_context->inode_table);
+ if (does_server_support_clipdata_locking(file_context))
+ clip_data_entry = HashTable_GetItemValue(
+ file_context->clip_data_table, (void*)(UINT_PTR)file_context->current_clip_data_id);
+ else
+ clip_data_entry = file_context->clip_data_entry_without_id;
+
+ WINPR_ASSERT(clip_data_entry);
+
+ clear_entry_selection(clip_data_entry);
+ WINPR_ASSERT(!clip_data_entry->clip_data_dir);
+
+ clip_data_entry->clip_data_dir =
+ clip_data_dir_new(file_context, does_server_support_clipdata_locking(file_context),
+ file_context->current_clip_data_id);
+ if (!clip_data_entry->clip_data_dir)
+ {
+ HashTable_Unlock(file_context->inode_table);
+ free(files);
+ return FALSE;
+ }
+
+ if (!update_exposed_path(file_context, clip, clip_data_entry))
+ {
+ HashTable_Unlock(file_context->inode_table);
+ free(files);
+ return FALSE;
+ }
+
+ if (!set_selection_for_clip_data_entry(file_context, clip_data_entry, files, n_files))
+ {
+ HashTable_Unlock(file_context->inode_table);
+ free(files);
+ return FALSE;
+ }
+ HashTable_Unlock(file_context->inode_table);
+
+ return TRUE;
+#else
+ return FALSE;
+#endif
+}
+
+void* cliprdr_file_context_get_context(CliprdrFileContext* file)
+{
+ WINPR_ASSERT(file);
+ return file->clipboard;
+}
+
+void cliprdr_file_session_terminate(CliprdrFileContext* file, BOOL stop_thread)
+{
+ if (!file)
+ return;
+
+#if defined(WITH_FUSE)
+ WINPR_ASSERT(file->fuse_stop_sync);
+
+ WLog_Print(file->log, WLOG_DEBUG, "Setting FUSE exit flag");
+ if (file->fuse_sess)
+ fuse_session_exit(file->fuse_sess);
+
+ if (stop_thread)
+ {
+ WLog_Print(file->log, WLOG_DEBUG, "Setting FUSE stop event");
+ SetEvent(file->fuse_stop_sync);
+ }
+#endif
+ /* not elegant but works for umounting FUSE
+ fuse_chan must receive an oper buf to unblock fuse_session_receive_buf function.
+ */
+#if defined(WITH_FUSE)
+ WLog_Print(file->log, WLOG_DEBUG, "Forcing FUSE to check exit flag");
+#endif
+ winpr_PathFileExists(file->path);
+}
+
+void cliprdr_file_context_free(CliprdrFileContext* file)
+{
+ if (!file)
+ return;
+
+#if defined(WITH_FUSE)
+ if (file->inode_table)
+ {
+ clear_no_cdi_entry(file);
+ clear_all_selections(file);
+ }
+
+ if (file->fuse_thread)
+ {
+ WINPR_ASSERT(file->fuse_stop_sync);
+
+ WLog_Print(file->log, WLOG_DEBUG, "Stopping FUSE thread");
+ cliprdr_file_session_terminate(file, TRUE);
+
+ WLog_Print(file->log, WLOG_DEBUG, "Waiting on FUSE thread");
+ WaitForSingleObject(file->fuse_thread, INFINITE);
+ CloseHandle(file->fuse_thread);
+ }
+ if (file->fuse_stop_sync)
+ CloseHandle(file->fuse_stop_sync);
+ if (file->fuse_start_sync)
+ CloseHandle(file->fuse_start_sync);
+
+ HashTable_Free(file->request_table);
+ HashTable_Free(file->clip_data_table);
+ HashTable_Free(file->inode_table);
+#endif
+ HashTable_Free(file->local_streams);
+ winpr_RemoveDirectory(file->path);
+ free(file->path);
+ free(file->exposed_path);
+ free(file);
+}
+
+static BOOL create_base_path(CliprdrFileContext* file)
+{
+ WINPR_ASSERT(file);
+
+ char base[64] = { 0 };
+ _snprintf(base, sizeof(base), "com.freerdp.client.cliprdr.%" PRIu32, GetCurrentProcessId());
+
+ file->path = GetKnownSubPath(KNOWN_PATH_TEMP, base);
+ if (!file->path)
+ return FALSE;
+
+ if (!winpr_PathFileExists(file->path) && !winpr_PathMakePath(file->path, 0))
+ {
+ WLog_Print(file->log, WLOG_ERROR, "Failed to create directory '%s'", file->path);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void cliprdr_local_file_free(CliprdrLocalFile* file)
+{
+ const CliprdrLocalFile empty = { 0 };
+ if (!file)
+ return;
+ if (file->fp)
+ {
+ WLog_Print(file->context->log, WLOG_DEBUG, "closing file %s, discarding entry", file->name);
+ fclose(file->fp);
+ }
+ free(file->name);
+ *file = empty;
+}
+
+static BOOL cliprdr_local_file_new(CliprdrFileContext* context, CliprdrLocalFile* f,
+ const char* path)
+{
+ const CliprdrLocalFile empty = { 0 };
+ WINPR_ASSERT(f);
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(path);
+
+ *f = empty;
+ f->context = context;
+ f->name = winpr_str_url_decode(path, strlen(path));
+ if (!f->name)
+ goto fail;
+
+ return TRUE;
+fail:
+ cliprdr_local_file_free(f);
+ return FALSE;
+}
+
+static void cliprdr_local_files_free(CliprdrLocalStream* stream)
+{
+ WINPR_ASSERT(stream);
+
+ for (size_t x = 0; x < stream->count; x++)
+ cliprdr_local_file_free(&stream->files[x]);
+ free(stream->files);
+
+ stream->files = NULL;
+ stream->count = 0;
+}
+
+static void cliprdr_local_stream_free(void* obj)
+{
+ CliprdrLocalStream* stream = (CliprdrLocalStream*)obj;
+ if (stream)
+ cliprdr_local_files_free(stream);
+
+ free(stream);
+}
+
+static BOOL append_entry(CliprdrLocalStream* stream, const char* path)
+{
+ CliprdrLocalFile* tmp = realloc(stream->files, sizeof(CliprdrLocalFile) * (stream->count + 1));
+ if (!tmp)
+ return FALSE;
+ stream->files = tmp;
+ CliprdrLocalFile* f = &stream->files[stream->count++];
+
+ return cliprdr_local_file_new(stream->context, f, path);
+}
+
+static BOOL is_directory(const char* path)
+{
+ WCHAR* wpath = ConvertUtf8ToWCharAlloc(path, NULL);
+ if (!wpath)
+ return FALSE;
+
+ HANDLE hFile =
+ CreateFileW(wpath, 0, FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ free(wpath);
+
+ if (hFile == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ BY_HANDLE_FILE_INFORMATION fileInformation = { 0 };
+ const BOOL status = GetFileInformationByHandle(hFile, &fileInformation);
+ CloseHandle(hFile);
+ if (!status)
+ return FALSE;
+
+ return (fileInformation.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? TRUE : FALSE;
+}
+
+static BOOL add_directory(CliprdrLocalStream* stream, const char* path)
+{
+ char* wildcardpath = GetCombinedPath(path, "*");
+ if (!wildcardpath)
+ return FALSE;
+ WCHAR* wpath = ConvertUtf8ToWCharAlloc(wildcardpath, NULL);
+ free(wildcardpath);
+ if (!wpath)
+ return FALSE;
+
+ WIN32_FIND_DATAW FindFileData = { 0 };
+ HANDLE hFind = FindFirstFileW(wpath, &FindFileData);
+ free(wpath);
+
+ if (hFind == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ BOOL rc = FALSE;
+ char* next = NULL;
+
+ WCHAR dotbuffer[6] = { 0 };
+ WCHAR dotdotbuffer[6] = { 0 };
+ const WCHAR* dot = InitializeConstWCharFromUtf8(".", dotbuffer, ARRAYSIZE(dotbuffer));
+ const WCHAR* dotdot = InitializeConstWCharFromUtf8("..", dotdotbuffer, ARRAYSIZE(dotdotbuffer));
+ do
+ {
+ if (_wcscmp(FindFileData.cFileName, dot) == 0)
+ continue;
+ if (_wcscmp(FindFileData.cFileName, dotdot) == 0)
+ continue;
+
+ char cFileName[MAX_PATH] = { 0 };
+ ConvertWCharNToUtf8(FindFileData.cFileName, ARRAYSIZE(FindFileData.cFileName), cFileName,
+ ARRAYSIZE(cFileName));
+
+ free(next);
+ next = GetCombinedPath(path, cFileName);
+ if (!next)
+ goto fail;
+
+ if (!append_entry(stream, next))
+ goto fail;
+ if (is_directory(next))
+ {
+ if (!add_directory(stream, next))
+ goto fail;
+ }
+ } while (FindNextFileW(hFind, &FindFileData));
+
+ rc = TRUE;
+fail:
+ free(next);
+ FindClose(hFind);
+
+ return rc;
+}
+
+static BOOL cliprdr_local_stream_update(CliprdrLocalStream* stream, const char* data, size_t size)
+{
+ BOOL rc = FALSE;
+ WINPR_ASSERT(stream);
+ if (size == 0)
+ return TRUE;
+
+ cliprdr_local_files_free(stream);
+
+ stream->files = calloc(size, sizeof(CliprdrLocalFile));
+ if (!stream->files)
+ return FALSE;
+
+ char* copy = strndup(data, size);
+ if (!copy)
+ return FALSE;
+ char* ptr = strtok(copy, "\r\n");
+ while (ptr)
+ {
+ const char* name = ptr;
+ if (strncmp("file:///", ptr, 8) == 0)
+ name = &ptr[7];
+ else if (strncmp("file:/", ptr, 6) == 0)
+ name = &ptr[5];
+
+ if (!append_entry(stream, name))
+ goto fail;
+
+ if (is_directory(name))
+ {
+ const BOOL res = add_directory(stream, name);
+ if (!res)
+ goto fail;
+ }
+ ptr = strtok(NULL, "\r\n");
+ }
+
+ rc = TRUE;
+fail:
+ free(copy);
+ return rc;
+}
+
+CliprdrLocalStream* cliprdr_local_stream_new(CliprdrFileContext* context, UINT32 lockId,
+ const char* data, size_t size)
+{
+ WINPR_ASSERT(context);
+ CliprdrLocalStream* stream = calloc(1, sizeof(CliprdrLocalStream));
+ if (!stream)
+ return NULL;
+
+ stream->context = context;
+ if (!cliprdr_local_stream_update(stream, data, size))
+ goto fail;
+
+ stream->lockId = lockId;
+ return stream;
+
+fail:
+ cliprdr_local_stream_free(stream);
+ return NULL;
+}
+
+static UINT32 UINTPointerHash(const void* id)
+{
+ WINPR_ASSERT(id);
+ return *((const UINT32*)id);
+}
+
+static BOOL UINTPointerCompare(const void* pointer1, const void* pointer2)
+{
+ if (!pointer1 || !pointer2)
+ return pointer1 == pointer2;
+
+ const UINT32* a = pointer1;
+ const UINT32* b = pointer2;
+ return *a == *b;
+}
+
+static void* UINTPointerClone(const void* other)
+{
+ const UINT32* src = other;
+ if (!src)
+ return NULL;
+
+ UINT32* copy = calloc(1, sizeof(UINT32));
+ if (!copy)
+ return NULL;
+
+ *copy = *src;
+ return copy;
+}
+
+#if defined(WITH_FUSE)
+static CliprdrFuseFile* fuse_file_new_root(CliprdrFileContext* file_context)
+{
+ CliprdrFuseFile* root_dir = NULL;
+
+ root_dir = fuse_file_new();
+ if (!root_dir)
+ return NULL;
+
+ root_dir->filename_with_root = calloc(2, sizeof(char));
+ if (!root_dir->filename_with_root)
+ {
+ fuse_file_free(root_dir);
+ return NULL;
+ }
+
+ _snprintf(root_dir->filename_with_root, 2, "/");
+ root_dir->filename = root_dir->filename_with_root;
+
+ root_dir->ino = FUSE_ROOT_ID;
+ root_dir->is_directory = TRUE;
+ root_dir->is_readonly = TRUE;
+
+ if (!HashTable_Insert(file_context->inode_table, (void*)(UINT_PTR)root_dir->ino, root_dir))
+ {
+ fuse_file_free(root_dir);
+ return NULL;
+ }
+
+ return root_dir;
+}
+#endif
+
+CliprdrFileContext* cliprdr_file_context_new(void* context)
+{
+ CliprdrFileContext* file = calloc(1, sizeof(CliprdrFileContext));
+ if (!file)
+ return NULL;
+
+ file->log = WLog_Get(CLIENT_TAG("common.cliprdr.file"));
+ file->clipboard = context;
+
+ file->local_streams = HashTable_New(FALSE);
+ if (!file->local_streams)
+ goto fail;
+
+ if (!HashTable_SetHashFunction(file->local_streams, UINTPointerHash))
+ goto fail;
+
+ wObject* hkobj = HashTable_KeyObject(file->local_streams);
+ WINPR_ASSERT(hkobj);
+ hkobj->fnObjectEquals = UINTPointerCompare;
+ hkobj->fnObjectFree = free;
+ hkobj->fnObjectNew = UINTPointerClone;
+
+ wObject* hobj = HashTable_ValueObject(file->local_streams);
+ WINPR_ASSERT(hobj);
+ hobj->fnObjectFree = cliprdr_local_stream_free;
+
+#if defined(WITH_FUSE)
+ file->inode_table = HashTable_New(FALSE);
+ file->clip_data_table = HashTable_New(FALSE);
+ file->request_table = HashTable_New(FALSE);
+ if (!file->inode_table || !file->clip_data_table || !file->request_table)
+ goto fail;
+
+ wObject* ctobj = HashTable_ValueObject(file->clip_data_table);
+ WINPR_ASSERT(ctobj);
+ ctobj->fnObjectFree = clip_data_entry_free;
+
+ file->root_dir = fuse_file_new_root(file);
+ if (!file->root_dir)
+ goto fail;
+#endif
+
+ if (!create_base_path(file))
+ goto fail;
+
+#if defined(WITH_FUSE)
+ if (!(file->fuse_start_sync = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ goto fail;
+ if (!(file->fuse_stop_sync = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ goto fail;
+ if (!(file->fuse_thread = CreateThread(NULL, 0, cliprdr_file_fuse_thread, file, 0, NULL)))
+ goto fail;
+
+ if (WaitForSingleObject(file->fuse_start_sync, INFINITE) == WAIT_FAILED)
+ WLog_Print(file->log, WLOG_ERROR, "Failed to wait for start sync");
+#endif
+ return file;
+
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ cliprdr_file_context_free(file);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+BOOL local_stream_discard(const void* key, void* value, void* arg)
+{
+ CliprdrFileContext* file = arg;
+ CliprdrLocalStream* stream = value;
+ WINPR_ASSERT(file);
+ WINPR_ASSERT(stream);
+
+ if (!stream->locked)
+ HashTable_Remove(file->local_streams, key);
+ return TRUE;
+}
+
+BOOL cliprdr_file_context_clear(CliprdrFileContext* file)
+{
+ WINPR_ASSERT(file);
+
+ WLog_Print(file->log, WLOG_DEBUG, "clear file clipboard...");
+
+ HashTable_Lock(file->local_streams);
+ HashTable_Foreach(file->local_streams, local_stream_discard, file);
+ HashTable_Unlock(file->local_streams);
+
+ memset(file->server_data_hash, 0, sizeof(file->server_data_hash));
+ memset(file->client_data_hash, 0, sizeof(file->client_data_hash));
+ return TRUE;
+}
+
+BOOL cliprdr_file_context_update_client_data(CliprdrFileContext* file, const char* data,
+ size_t size)
+{
+ BOOL rc = FALSE;
+
+ WINPR_ASSERT(file);
+ if (!cliprdr_file_client_content_changed_and_update(file, data, size))
+ return TRUE;
+
+ if (!cliprdr_file_context_clear(file))
+ return FALSE;
+
+ UINT32 lockId = file->local_lock_id;
+
+ HashTable_Lock(file->local_streams);
+ CliprdrLocalStream* stream = HashTable_GetItemValue(file->local_streams, &lockId);
+
+ WLog_Print(file->log, WLOG_DEBUG, "update client file list (stream=%p)...", stream);
+ if (stream)
+ rc = cliprdr_local_stream_update(stream, data, size);
+ else
+ {
+ stream = cliprdr_local_stream_new(file, lockId, data, size);
+ rc = HashTable_Insert(file->local_streams, &stream->lockId, stream);
+ if (!rc)
+ cliprdr_local_stream_free(stream);
+ }
+ // HashTable_Insert owns stream
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc)
+ HashTable_Unlock(file->local_streams);
+ return rc;
+}
+
+UINT32 cliprdr_file_context_current_flags(CliprdrFileContext* file)
+{
+ WINPR_ASSERT(file);
+
+ if ((file->file_capability_flags & CB_STREAM_FILECLIP_ENABLED) == 0)
+ return 0;
+
+ if (!file->file_formats_registered)
+ return 0;
+
+ return CB_STREAM_FILECLIP_ENABLED | CB_FILECLIP_NO_FILE_PATHS |
+ CB_HUGE_FILE_SUPPORT_ENABLED; // | CB_CAN_LOCK_CLIPDATA;
+}
+
+BOOL cliprdr_file_context_set_locally_available(CliprdrFileContext* file, BOOL available)
+{
+ WINPR_ASSERT(file);
+ file->file_formats_registered = available;
+ return TRUE;
+}
+
+BOOL cliprdr_file_context_remote_set_flags(CliprdrFileContext* file, UINT32 flags)
+{
+ WINPR_ASSERT(file);
+ file->file_capability_flags = flags;
+ return TRUE;
+}
+
+UINT32 cliprdr_file_context_remote_get_flags(CliprdrFileContext* file)
+{
+ WINPR_ASSERT(file);
+ return file->file_capability_flags;
+}
+
+BOOL cliprdr_file_context_has_local_support(CliprdrFileContext* file)
+{
+ WINPR_UNUSED(file);
+
+#if defined(WITH_FUSE)
+ return TRUE;
+#else
+ return FALSE;
+#endif
+}
diff --git a/client/common/cmdline.c b/client/common/cmdline.c
new file mode 100644
index 0000000..2ce693b
--- /dev/null
+++ b/client/common/cmdline.c
@@ -0,0 +1,5922 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Client Command-Line Interface
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2014 Norbert Federa <norbert.federa@thincast.com>
+ * Copyright 2016 Armin Novak <armin.novak@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <ctype.h>
+#include <errno.h>
+
+#include <winpr/assert.h>
+#include <winpr/crt.h>
+#include <winpr/wlog.h>
+#include <winpr/path.h>
+#include <winpr/ncrypt.h>
+#include <winpr/environment.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/addin.h>
+#include <freerdp/settings.h>
+#include <freerdp/client.h>
+#include <freerdp/client/channels.h>
+#include <freerdp/channels/drdynvc.h>
+#include <freerdp/channels/cliprdr.h>
+#include <freerdp/channels/encomsp.h>
+#include <freerdp/channels/rdp2tcp.h>
+#include <freerdp/channels/remdesk.h>
+#include <freerdp/channels/rdpsnd.h>
+#include <freerdp/channels/disp.h>
+#include <freerdp/crypto/crypto.h>
+#include <freerdp/locale/keyboard.h>
+#include <freerdp/utils/passphrase.h>
+#include <freerdp/utils/proxy_utils.h>
+#include <freerdp/channels/urbdrc.h>
+#include <freerdp/channels/rdpdr.h>
+
+#if defined(CHANNEL_AINPUT_CLIENT)
+#include <freerdp/channels/ainput.h>
+#endif
+
+#include <freerdp/channels/audin.h>
+#include <freerdp/channels/echo.h>
+
+#include <freerdp/client/cmdline.h>
+#include <freerdp/version.h>
+#include <freerdp/client/utils/smartcard_cli.h>
+
+#include <openssl/tls1.h>
+#include "cmdline.h"
+
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("common.cmdline")
+
+static const char* option_starts_with(const char* what, const char* val);
+static BOOL option_ends_with(const char* str, const char* ext);
+static BOOL option_equals(const char* what, const char* val);
+
+static BOOL freerdp_client_print_codepages(const char* arg)
+{
+ size_t count = 0;
+ DWORD column = 2;
+ const char* filter = NULL;
+ RDP_CODEPAGE* pages = NULL;
+
+ if (arg)
+ {
+ filter = strchr(arg, ',');
+ if (!filter)
+ filter = arg;
+ else
+ filter++;
+ }
+ pages = freerdp_keyboard_get_matching_codepages(column, filter, &count);
+ if (!pages)
+ return TRUE;
+
+ printf("%-10s %-8s %-60s %-36s %-48s\n", "<id>", "<locale>", "<win langid>", "<language>",
+ "<country>");
+ for (size_t x = 0; x < count; x++)
+ {
+ const RDP_CODEPAGE* page = &pages[x];
+ char buffer[520] = { 0 };
+
+ if (strnlen(page->subLanguageSymbol, ARRAYSIZE(page->subLanguageSymbol)) > 0)
+ _snprintf(buffer, sizeof(buffer), "[%s|%s]", page->primaryLanguageSymbol,
+ page->subLanguageSymbol);
+ else
+ _snprintf(buffer, sizeof(buffer), "[%s]", page->primaryLanguageSymbol);
+ printf("id=0x%04" PRIx16 ": [%-6s] %-60s %-36s %-48s\n", page->id, page->locale, buffer,
+ page->primaryLanguage, page->subLanguage);
+ }
+ freerdp_codepages_free(pages);
+ return TRUE;
+}
+
+static BOOL freerdp_path_valid(const char* path, BOOL* special)
+{
+ const char DynamicDrives[] = "DynamicDrives";
+ BOOL isPath = FALSE;
+ BOOL isSpecial = 0;
+ if (!path)
+ return FALSE;
+
+ isSpecial =
+ (option_equals("*", path) || option_equals(DynamicDrives, path) || option_equals("%", path))
+ ? TRUE
+ : FALSE;
+ if (!isSpecial)
+ isPath = winpr_PathFileExists(path);
+
+ if (special)
+ *special = isSpecial;
+
+ return isSpecial || isPath;
+}
+
+static BOOL freerdp_sanitize_drive_name(char* name, const char* invalid, const char* replacement)
+{
+ if (!name || !invalid || !replacement)
+ return FALSE;
+ if (strlen(invalid) != strlen(replacement))
+ return FALSE;
+
+ while (*invalid != '\0')
+ {
+ const char what = *invalid++;
+ const char with = *replacement++;
+
+ char* cur = name;
+ while ((cur = strchr(cur, what)) != NULL)
+ *cur = with;
+ }
+ return TRUE;
+}
+
+static char* name_from_path(const char* path)
+{
+ const char* name = "NULL";
+ if (path)
+ {
+ if (option_equals("%", path))
+ name = "home";
+ else if (option_equals("*", path))
+ name = "hotplug-all";
+ else if (option_equals("DynamicDrives", path))
+ name = "hotplug";
+ else
+ name = path;
+ }
+ return _strdup(name);
+}
+
+static BOOL freerdp_client_add_drive(rdpSettings* settings, const char* path, const char* name)
+{
+ char* dname = NULL;
+ RDPDR_DEVICE* device = NULL;
+
+ if (name)
+ {
+ BOOL skip = FALSE;
+ if (path)
+ {
+ switch (path[0])
+ {
+ case '*':
+ case '%':
+ skip = TRUE;
+ break;
+ default:
+ break;
+ }
+ }
+ /* Path was entered as secondary argument, swap */
+ if (!skip && winpr_PathFileExists(name))
+ {
+ if (!winpr_PathFileExists(path) || (!PathIsRelativeA(name) && PathIsRelativeA(path)))
+ {
+ const char* tmp = path;
+ path = name;
+ name = tmp;
+ }
+ }
+ }
+
+ if (name)
+ dname = _strdup(name);
+ else /* We need a name to send to the server. */
+ dname = name_from_path(path);
+
+ if (freerdp_sanitize_drive_name(dname, "\\/", "__"))
+ {
+ const char* args[] = { dname, path };
+ device = freerdp_device_new(RDPDR_DTYP_FILESYSTEM, ARRAYSIZE(args), args);
+ }
+ free(dname);
+ if (!device)
+ goto fail;
+
+ if (!path)
+ goto fail;
+ else
+ {
+ BOOL isSpecial = FALSE;
+ BOOL isPath = freerdp_path_valid(path, &isSpecial);
+
+ if (!isPath && !isSpecial)
+ goto fail;
+ }
+
+ if (!freerdp_device_collection_add(settings, device))
+ goto fail;
+
+ return TRUE;
+
+fail:
+ freerdp_device_free(device);
+ return FALSE;
+}
+
+static BOOL value_to_int(const char* value, LONGLONG* result, LONGLONG min, LONGLONG max)
+{
+ long long rc = 0;
+
+ if (!value || !result)
+ return FALSE;
+
+ errno = 0;
+ rc = _strtoi64(value, NULL, 0);
+
+ if (errno != 0)
+ return FALSE;
+
+ if ((rc < min) || (rc > max))
+ return FALSE;
+
+ *result = rc;
+ return TRUE;
+}
+
+static BOOL value_to_uint(const char* value, ULONGLONG* result, ULONGLONG min, ULONGLONG max)
+{
+ unsigned long long rc = 0;
+
+ if (!value || !result)
+ return FALSE;
+
+ errno = 0;
+ rc = _strtoui64(value, NULL, 0);
+
+ if (errno != 0)
+ return FALSE;
+
+ if ((rc < min) || (rc > max))
+ return FALSE;
+
+ *result = rc;
+ return TRUE;
+}
+
+BOOL freerdp_client_print_version(void)
+{
+ printf("This is FreeRDP version %s (%s)\n", FREERDP_VERSION_FULL, FREERDP_GIT_REVISION);
+ return TRUE;
+}
+
+BOOL freerdp_client_print_buildconfig(void)
+{
+ printf("%s", freerdp_get_build_config());
+ return TRUE;
+}
+
+static void freerdp_client_print_scancodes(void)
+{
+ printf("RDP scancodes and their name for use with /kbd:remap\n");
+
+ for (UINT32 x = 0; x < UINT16_MAX; x++)
+ {
+ const char* name = freerdp_keyboard_scancode_name(x);
+ if (name)
+ printf("0x%04" PRIx32 " --> %s\n", x, name);
+ }
+}
+
+static BOOL is_delimiter(char c, const char* delimiters)
+{
+ char d = 0;
+ while ((d = *delimiters++) != '\0')
+ {
+ if (c == d)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static const char* get_last(const char* start, size_t len, const char* delimiters)
+{
+ const char* last = NULL;
+ for (size_t x = 0; x < len; x++)
+ {
+ char c = start[x];
+ if (is_delimiter(c, delimiters))
+ last = &start[x];
+ }
+ return last;
+}
+
+static SSIZE_T next_delimiter(const char* text, size_t len, size_t max, const char* delimiters)
+{
+ if (len < max)
+ return -1;
+
+ const char* last = get_last(text, max, delimiters);
+ if (!last)
+ return -1;
+
+ return (SSIZE_T)(last - text);
+}
+
+static SSIZE_T forced_newline_at(const char* text, size_t len, size_t limit,
+ const char* force_newline)
+{
+ char d = 0;
+ while ((d = *force_newline++) != '\0')
+ {
+ const char* tok = strchr(text, d);
+ if (tok)
+ {
+ const size_t offset = tok - text;
+ if ((offset > len) || (offset > limit))
+ continue;
+ return (SSIZE_T)(offset);
+ }
+ }
+ return -1;
+}
+
+static BOOL print_align(size_t start_offset, size_t* current)
+{
+ WINPR_ASSERT(current);
+ if (*current < start_offset)
+ {
+ const int rc = printf("%*c", (int)(start_offset - *current), ' ');
+ if (rc < 0)
+ return FALSE;
+ *current += (size_t)rc;
+ }
+ return TRUE;
+}
+
+static char* print_token(char* text, size_t start_offset, size_t* current, size_t limit,
+ const char* delimiters, const char* force_newline)
+{
+ int rc = 0;
+ const size_t tlen = strnlen(text, limit);
+ size_t len = tlen;
+ const SSIZE_T force_at = forced_newline_at(text, len, limit - *current, force_newline);
+ BOOL isForce = (force_at > 0);
+
+ if (isForce)
+ len = MIN(len, (size_t)force_at);
+
+ if (!print_align(start_offset, current))
+ return NULL;
+
+ const SSIZE_T delim = next_delimiter(text, len, limit - *current, delimiters);
+ const BOOL isDelim = delim > 0;
+ if (isDelim)
+ {
+ len = MIN(len, (size_t)delim + 1);
+ }
+
+ rc = printf("%.*s", (int)len, text);
+ if (rc < 0)
+ return NULL;
+
+ if (isForce || isDelim)
+ {
+ printf("\n");
+ *current = 0;
+
+ const size_t offset = len + (isForce ? 1 : 0);
+ return &text[offset];
+ }
+
+ *current += (size_t)rc;
+
+ if (tlen == (size_t)rc)
+ return NULL;
+ return &text[(size_t)rc];
+}
+
+static size_t print_optionals(const char* text, size_t start_offset, size_t current)
+{
+ const size_t limit = 80;
+ char* str = _strdup(text);
+ char* cur = str;
+
+ while ((cur = print_token(cur, start_offset + 1, &current, limit, "[], ", "\r\n")) != NULL)
+ ;
+
+ free(str);
+ return current;
+}
+
+static size_t print_description(const char* text, size_t start_offset, size_t current)
+{
+ const size_t limit = 80;
+ char* str = _strdup(text);
+ char* cur = str;
+
+ while ((cur = print_token(cur, start_offset, &current, limit, " ", "\r\n")) != NULL)
+ ;
+
+ free(str);
+ current += (size_t)printf("\n");
+ return current;
+}
+
+static int cmp_cmdline_args(const void* pva, const void* pvb)
+{
+ const COMMAND_LINE_ARGUMENT_A* a = (const COMMAND_LINE_ARGUMENT_A*)pva;
+ const COMMAND_LINE_ARGUMENT_A* b = (const COMMAND_LINE_ARGUMENT_A*)pvb;
+
+ if (!a->Name && !b->Name)
+ return 0;
+ if (!a->Name)
+ return 1;
+ if (!b->Name)
+ return -1;
+ return strcmp(a->Name, b->Name);
+}
+
+static void freerdp_client_print_command_line_args(COMMAND_LINE_ARGUMENT_A* parg, size_t count)
+{
+ if (!parg)
+ return;
+
+ qsort(parg, count, sizeof(COMMAND_LINE_ARGUMENT_A), cmp_cmdline_args);
+
+ const COMMAND_LINE_ARGUMENT_A* arg = parg;
+ do
+ {
+ int rc = 0;
+ size_t pos = 0;
+ const size_t description_offset = 30 + 8;
+
+ if (arg->Flags & (COMMAND_LINE_VALUE_BOOL | COMMAND_LINE_VALUE_FLAG))
+ {
+ if ((arg->Flags & ~COMMAND_LINE_VALUE_BOOL) == 0)
+ rc = printf(" %s%s", arg->Default ? "-" : "+", arg->Name);
+ else if ((arg->Flags & COMMAND_LINE_VALUE_OPTIONAL) != 0)
+ rc = printf(" [%s|/]%s", arg->Default ? "-" : "+", arg->Name);
+ else
+ {
+ rc = printf(" %s%s", arg->Default ? "-" : "+", arg->Name);
+ }
+ }
+ else
+ rc = printf(" /%s", arg->Name);
+
+ if (rc < 0)
+ return;
+ pos += (size_t)rc;
+
+ if ((arg->Flags & COMMAND_LINE_VALUE_REQUIRED) ||
+ (arg->Flags & COMMAND_LINE_VALUE_OPTIONAL))
+ {
+ if (arg->Format)
+ {
+ if (arg->Flags & COMMAND_LINE_VALUE_OPTIONAL)
+ {
+ rc = printf("[:");
+ if (rc < 0)
+ return;
+ pos += (size_t)rc;
+ pos = print_optionals(arg->Format, pos, pos);
+ rc = printf("]");
+ if (rc < 0)
+ return;
+ pos += (size_t)rc;
+ }
+ else
+ {
+ rc = printf(":");
+ if (rc < 0)
+ return;
+ pos += (size_t)rc;
+ pos = print_optionals(arg->Format, pos, pos);
+ }
+
+ if (pos > description_offset)
+ {
+ printf("\n");
+ pos = 0;
+ }
+ }
+ }
+
+ rc = printf("%*c", (int)(description_offset - pos), ' ');
+ if (rc < 0)
+ return;
+ pos += (size_t)rc;
+
+ if (arg->Flags & COMMAND_LINE_VALUE_BOOL)
+ {
+ rc = printf("%s ", arg->Default ? "Disable" : "Enable");
+ if (rc < 0)
+ return;
+ pos += (size_t)rc;
+ }
+
+ print_description(arg->Text, description_offset, pos);
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+}
+
+BOOL freerdp_client_print_command_line_help(int argc, char** argv)
+{
+ return freerdp_client_print_command_line_help_ex(argc, argv, NULL);
+}
+
+static COMMAND_LINE_ARGUMENT_A* create_merged_args(const COMMAND_LINE_ARGUMENT_A* custom,
+ SSIZE_T count, size_t* pcount)
+{
+ WINPR_ASSERT(pcount);
+ if (count < 0)
+ {
+ const COMMAND_LINE_ARGUMENT_A* cur = custom;
+ count = 0;
+ while (cur && cur->Name)
+ {
+ count++;
+ cur++;
+ }
+ }
+
+ COMMAND_LINE_ARGUMENT_A* largs =
+ calloc(count + ARRAYSIZE(global_cmd_args), sizeof(COMMAND_LINE_ARGUMENT_A));
+ *pcount = 0;
+ if (!largs)
+ return NULL;
+
+ size_t lcount = 0;
+ const COMMAND_LINE_ARGUMENT_A* cur = custom;
+ while (cur && cur->Name)
+ {
+ largs[lcount++] = *cur++;
+ }
+
+ cur = global_cmd_args;
+ while (cur && cur->Name)
+ {
+ largs[lcount++] = *cur++;
+ }
+ *pcount = lcount;
+ return largs;
+}
+
+BOOL freerdp_client_print_command_line_help_ex(int argc, char** argv,
+ const COMMAND_LINE_ARGUMENT_A* custom)
+{
+ const char* name = "FreeRDP";
+
+ /* allocate a merged copy of implementation defined and default arguments */
+ size_t lcount = 0;
+ COMMAND_LINE_ARGUMENT_A* largs = create_merged_args(custom, -1, &lcount);
+ if (!largs)
+ return FALSE;
+
+ if (argc > 0)
+ name = argv[0];
+
+ printf("\n");
+ printf("FreeRDP - A Free Remote Desktop Protocol Implementation\n");
+ printf("See www.freerdp.com for more information\n");
+ printf("\n");
+ printf("Usage: %s [file] [options] [/v:<server>[:port]]\n", argv[0]);
+ printf("\n");
+ printf("Syntax:\n");
+ printf(" /flag (enables flag)\n");
+ printf(" /option:<value> (specifies option with value)\n");
+ printf(" +toggle -toggle (enables or disables toggle, where '/' is a synonym of '+')\n");
+ printf("\n");
+
+ freerdp_client_print_command_line_args(largs, lcount);
+ free(largs);
+
+ printf("\n");
+ printf("Examples:\n");
+ printf(" %s connection.rdp /p:Pwd123! /f\n", name);
+ printf(" %s /u:CONTOSO\\JohnDoe /p:Pwd123! /v:rdp.contoso.com\n", name);
+ printf(" %s /u:JohnDoe /p:Pwd123! /w:1366 /h:768 /v:192.168.1.100:4489\n", name);
+ printf(" %s /u:JohnDoe /p:Pwd123! /vmconnect:C824F53E-95D2-46C6-9A18-23A5BB403532 "
+ "/v:192.168.1.100\n",
+ name);
+ printf(" %s /u:\\AzureAD\\user@corp.example /p:pwd /v:host\n", name);
+ printf("\n");
+ printf("Clipboard Redirection: +clipboard\n");
+ printf("\n");
+ printf("Drive Redirection: /drive:home,/home/user\n");
+ printf("Smartcard Redirection: /smartcard:<device>\n");
+ printf("Smartcard logon with Kerberos authentication: /smartcard-logon /sec:nla\n");
+
+ printf("Serial Port Redirection: /serial:<name>,<device>,[SerCx2|SerCx|Serial],[permissive]\n");
+ printf("Serial Port Redirection: /serial:COM1,/dev/ttyS0\n");
+ printf("Parallel Port Redirection: /parallel:<name>,<device>\n");
+ printf("Printer Redirection: /printer:<device>,<driver>,[default]\n");
+ printf("TCP redirection: /rdp2tcp:/usr/bin/rdp2tcp\n");
+ printf("\n");
+ printf("Audio Output Redirection: /sound:sys:oss,dev:1,format:1\n");
+ printf("Audio Output Redirection: /sound:sys:alsa\n");
+ printf("Audio Input Redirection: /microphone:sys:oss,dev:1,format:1\n");
+ printf("Audio Input Redirection: /microphone:sys:alsa\n");
+ printf("\n");
+ printf("Multimedia Redirection: /video\n");
+#ifdef CHANNEL_URBDRC_CLIENT
+ printf("USB Device Redirection: /usb:id:054c:0268#4669:6e6b,addr:04:0c\n");
+#endif
+ printf("\n");
+ printf("For Gateways, the https_proxy environment variable is respected:\n");
+#ifdef _WIN32
+ printf(" set HTTPS_PROXY=http://proxy.contoso.com:3128/\n");
+#else
+ printf(" export https_proxy=http://proxy.contoso.com:3128/\n");
+#endif
+ printf(" %s /g:rdp.contoso.com ...\n", name);
+ printf("\n");
+ printf("More documentation is coming, in the meantime consult source files\n");
+ printf("\n");
+ return TRUE;
+}
+
+static BOOL option_is_rdp_file(const char* option)
+{
+ WINPR_ASSERT(option);
+
+ if (option_ends_with(option, ".rdp"))
+ return TRUE;
+ if (option_ends_with(option, ".rdpw"))
+ return TRUE;
+ return FALSE;
+}
+
+static BOOL option_is_incident_file(const char* option)
+{
+ WINPR_ASSERT(option);
+
+ if (option_ends_with(option, ".msrcIncident"))
+ return TRUE;
+ return FALSE;
+}
+
+static int freerdp_client_command_line_pre_filter(void* context, int index, int argc, LPSTR* argv)
+{
+ if (index == 1)
+ {
+ size_t length = 0;
+ rdpSettings* settings = NULL;
+
+ if (argc <= index)
+ return -1;
+
+ length = strlen(argv[index]);
+
+ if (length > 4)
+ {
+ if (option_is_rdp_file(argv[index]))
+ {
+ settings = (rdpSettings*)context;
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_ConnectionFile, argv[index]))
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ return 1;
+ }
+ }
+
+ if (length > 13)
+ {
+ if (option_is_incident_file(argv[index]))
+ {
+ settings = (rdpSettings*)context;
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_AssistanceFile, argv[index]))
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+BOOL freerdp_client_add_device_channel(rdpSettings* settings, size_t count, const char** params)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(params);
+ WINPR_ASSERT(count > 0);
+
+ if (option_equals(params[0], "drive"))
+ {
+ BOOL rc = 0;
+ if (count < 2)
+ return FALSE;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return FALSE;
+ if (count < 3)
+ rc = freerdp_client_add_drive(settings, params[1], NULL);
+ else
+ rc = freerdp_client_add_drive(settings, params[2], params[1]);
+
+ return rc;
+ }
+ else if (option_equals(params[0], "printer"))
+ {
+ RDPDR_DEVICE* printer = NULL;
+
+ if (count < 1)
+ return FALSE;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectPrinters, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return FALSE;
+
+ printer = freerdp_device_new(RDPDR_DTYP_PRINT, count - 1, &params[1]);
+ if (!printer)
+ return FALSE;
+
+ if (!freerdp_device_collection_add(settings, printer))
+ {
+ freerdp_device_free(printer);
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+ else if (option_equals(params[0], "smartcard"))
+ {
+ RDPDR_DEVICE* smartcard = NULL;
+
+ if (count < 1)
+ return FALSE;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectSmartCards, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return FALSE;
+
+ smartcard = freerdp_device_new(RDPDR_DTYP_SMARTCARD, count - 1, &params[1]);
+
+ if (!smartcard)
+ return FALSE;
+
+ if (!freerdp_device_collection_add(settings, smartcard))
+ {
+ freerdp_device_free(smartcard);
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+ else if (option_equals(params[0], "serial"))
+ {
+ RDPDR_DEVICE* serial = NULL;
+
+ if (count < 1)
+ return FALSE;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectSerialPorts, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return FALSE;
+
+ serial = freerdp_device_new(RDPDR_DTYP_SERIAL, count - 1, &params[1]);
+
+ if (!serial)
+ return FALSE;
+
+ if (!freerdp_device_collection_add(settings, serial))
+ {
+ freerdp_device_free(serial);
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+ else if (option_equals(params[0], "parallel"))
+ {
+ RDPDR_DEVICE* parallel = NULL;
+
+ if (count < 1)
+ return FALSE;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectParallelPorts, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return FALSE;
+
+ parallel = freerdp_device_new(RDPDR_DTYP_PARALLEL, count - 1, &params[1]);
+
+ if (!parallel)
+ return FALSE;
+
+ if (!freerdp_device_collection_add(settings, parallel))
+ {
+ freerdp_device_free(parallel);
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+BOOL freerdp_client_del_static_channel(rdpSettings* settings, const char* name)
+{
+ return freerdp_static_channel_collection_del(settings, name);
+}
+
+BOOL freerdp_client_add_static_channel(rdpSettings* settings, size_t count, const char** params)
+{
+ ADDIN_ARGV* _args = NULL;
+
+ if (!settings || !params || !params[0] || (count > INT_MAX))
+ return FALSE;
+
+ if (freerdp_static_channel_collection_find(settings, params[0]))
+ return TRUE;
+
+ _args = freerdp_addin_argv_new(count, (const char**)params);
+
+ if (!_args)
+ return FALSE;
+
+ if (!freerdp_static_channel_collection_add(settings, _args))
+ goto fail;
+
+ return TRUE;
+fail:
+ freerdp_addin_argv_free(_args);
+ return FALSE;
+}
+
+BOOL freerdp_client_del_dynamic_channel(rdpSettings* settings, const char* name)
+{
+ return freerdp_dynamic_channel_collection_del(settings, name);
+}
+
+BOOL freerdp_client_add_dynamic_channel(rdpSettings* settings, size_t count, const char** params)
+{
+ ADDIN_ARGV* _args = NULL;
+
+ if (!settings || !params || !params[0] || (count > INT_MAX))
+ return FALSE;
+
+ if (freerdp_dynamic_channel_collection_find(settings, params[0]))
+ return TRUE;
+
+ _args = freerdp_addin_argv_new(count, params);
+
+ if (!_args)
+ return FALSE;
+
+ if (!freerdp_dynamic_channel_collection_add(settings, _args))
+ goto fail;
+
+ return TRUE;
+
+fail:
+ freerdp_addin_argv_free(_args);
+ return FALSE;
+}
+
+static BOOL read_pem_file(rdpSettings* settings, FreeRDP_Settings_Keys_String id, const char* file)
+{
+ size_t length = 0;
+ char* pem = crypto_read_pem(file, &length);
+ if (!pem || (length == 0))
+ return FALSE;
+
+ BOOL rc = freerdp_settings_set_string_len(settings, id, pem, length);
+ free(pem);
+ return rc;
+}
+
+/** @brief suboption type */
+typedef enum
+{
+ CMDLINE_SUBOPTION_STRING,
+ CMDLINE_SUBOPTION_FILE,
+} CmdLineSubOptionType;
+
+typedef BOOL (*CmdLineSubOptionCb)(const char* value, rdpSettings* settings);
+typedef struct
+{
+ const char* optname;
+ FreeRDP_Settings_Keys_String id;
+ CmdLineSubOptionType opttype;
+ CmdLineSubOptionCb cb;
+} CmdLineSubOptions;
+
+static BOOL parseSubOptions(rdpSettings* settings, const CmdLineSubOptions* opts, size_t count,
+ const char* arg)
+{
+ BOOL found = FALSE;
+
+ for (size_t xx = 0; xx < count; xx++)
+ {
+ const CmdLineSubOptions* opt = &opts[xx];
+
+ if (option_starts_with(opt->optname, arg))
+ {
+ const size_t optlen = strlen(opt->optname);
+ const char* val = &arg[optlen];
+ BOOL status = 0;
+
+ switch (opt->opttype)
+ {
+ case CMDLINE_SUBOPTION_STRING:
+ status = freerdp_settings_set_string(settings, opt->id, val);
+ break;
+ case CMDLINE_SUBOPTION_FILE:
+ status = read_pem_file(settings, opt->id, val);
+ break;
+ default:
+ WLog_ERR(TAG, "invalid subOption type");
+ return FALSE;
+ }
+
+ if (!status)
+ return FALSE;
+
+ if (opt->cb && !opt->cb(val, settings))
+ return FALSE;
+
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (!found)
+ WLog_ERR(TAG, "option %s not handled", arg);
+
+ return found;
+}
+
+static int freerdp_client_command_line_post_filter(void* context, COMMAND_LINE_ARGUMENT_A* arg)
+{
+ rdpSettings* settings = (rdpSettings*)context;
+ BOOL status = TRUE;
+ BOOL enable = arg->Value ? TRUE : FALSE;
+ union
+ {
+ char** p;
+ const char** pc;
+ } ptr;
+
+ CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "a")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+
+ if ((status = freerdp_client_add_device_channel(settings, count, ptr.pc)))
+ {
+ freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE);
+ }
+
+ free(ptr.p);
+ }
+ CommandLineSwitchCase(arg, "kerberos")
+ {
+ size_t count = 0;
+
+ ptr.p = CommandLineParseCommaSeparatedValuesEx("kerberos", arg->Value, &count);
+ if (ptr.pc)
+ {
+ const CmdLineSubOptions opts[] = {
+ { "kdc-url:", FreeRDP_KerberosKdcUrl, CMDLINE_SUBOPTION_STRING, NULL },
+ { "start-time:", FreeRDP_KerberosStartTime, CMDLINE_SUBOPTION_STRING, NULL },
+ { "lifetime:", FreeRDP_KerberosLifeTime, CMDLINE_SUBOPTION_STRING, NULL },
+ { "renewable-lifetime:", FreeRDP_KerberosRenewableLifeTime,
+ CMDLINE_SUBOPTION_STRING, NULL },
+ { "cache:", FreeRDP_KerberosCache, CMDLINE_SUBOPTION_STRING, NULL },
+ { "armor:", FreeRDP_KerberosArmor, CMDLINE_SUBOPTION_STRING, NULL },
+ { "pkinit-anchors:", FreeRDP_PkinitAnchors, CMDLINE_SUBOPTION_STRING, NULL },
+ { "pkcs11-module:", FreeRDP_Pkcs11Module, CMDLINE_SUBOPTION_STRING, NULL }
+ };
+
+ for (size_t x = 1; x < count; x++)
+ {
+ const char* cur = ptr.pc[x];
+ if (!parseSubOptions(settings, opts, ARRAYSIZE(opts), cur))
+ {
+ free(ptr.p);
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ }
+ }
+ free(ptr.p);
+ }
+
+ CommandLineSwitchCase(arg, "vc")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ status = freerdp_client_add_static_channel(settings, count, ptr.pc);
+ free(ptr.p);
+ }
+ CommandLineSwitchCase(arg, "dvc")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ status = freerdp_client_add_dynamic_channel(settings, count, ptr.pc);
+ free(ptr.p);
+ }
+ CommandLineSwitchCase(arg, "drive")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValuesEx(arg->Name, arg->Value, &count);
+ status = freerdp_client_add_device_channel(settings, count, ptr.pc);
+ free(ptr.p);
+ }
+ CommandLineSwitchCase(arg, "serial")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValuesEx(arg->Name, arg->Value, &count);
+ status = freerdp_client_add_device_channel(settings, count, ptr.pc);
+ free(ptr.p);
+ }
+ CommandLineSwitchCase(arg, "parallel")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValuesEx(arg->Name, arg->Value, &count);
+ status = freerdp_client_add_device_channel(settings, count, ptr.pc);
+ free(ptr.p);
+ }
+ CommandLineSwitchCase(arg, "smartcard")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValuesEx(arg->Name, arg->Value, &count);
+ status = freerdp_client_add_device_channel(settings, count, ptr.pc);
+ free(ptr.p);
+ }
+ CommandLineSwitchCase(arg, "printer")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValuesEx(arg->Name, arg->Value, &count);
+ status = freerdp_client_add_device_channel(settings, count, ptr.pc);
+ free(ptr.p);
+ }
+ CommandLineSwitchCase(arg, "usb")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValuesEx(URBDRC_CHANNEL_NAME, arg->Value, &count);
+ status = freerdp_client_add_dynamic_channel(settings, count, ptr.pc);
+ free(ptr.p);
+ }
+ CommandLineSwitchCase(arg, "multitouch")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_MultiTouchInput, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "gestures")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_MultiTouchGestures, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "echo")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportEchoChannel, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "ssh-agent")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportSSHAgentChannel, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "disp")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportDisplayControl, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "geometry")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGeometryTracking, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "video")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGeometryTracking,
+ enable)) /* this requires geometry tracking */
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportVideoOptimized, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "sound")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValuesEx(RDPSND_CHANNEL_NAME, arg->Value, &count);
+ status = freerdp_client_add_static_channel(settings, count, ptr.pc);
+ if (status)
+ {
+ status = freerdp_client_add_dynamic_channel(settings, count, ptr.pc);
+ }
+ free(ptr.p);
+ }
+ CommandLineSwitchCase(arg, "microphone")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValuesEx(AUDIN_CHANNEL_NAME, arg->Value, &count);
+ status = freerdp_client_add_dynamic_channel(settings, count, ptr.pc);
+ free(ptr.p);
+ }
+#if defined(CHANNEL_TSMF_CLIENT)
+ CommandLineSwitchCase(arg, "multimedia")
+ {
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValuesEx("tsmf", arg->Value, &count);
+ status = freerdp_client_add_dynamic_channel(settings, count, ptr.pc);
+ free(ptr.p);
+ }
+#endif
+ CommandLineSwitchCase(arg, "heartbeat")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportHeartbeatPdu, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "multitransport")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportMultitransport, enable))
+ return COMMAND_LINE_ERROR;
+
+ UINT32 flags = 0;
+ if (freerdp_settings_get_bool(settings, FreeRDP_SupportMultitransport))
+ flags =
+ (TRANSPORT_TYPE_UDP_FECR | TRANSPORT_TYPE_UDP_FECL | TRANSPORT_TYPE_UDP_PREFERRED);
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_MultitransportFlags, flags))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchEnd(arg)
+
+ return status
+ ? 1
+ : -1;
+}
+
+static BOOL freerdp_parse_username_ptr(const char* username, const char** user, size_t* userlen,
+ const char** domain, size_t* domainlen)
+{
+ const char* p = strchr(username, '\\');
+
+ *user = NULL;
+ *userlen = 0;
+
+ *domain = NULL;
+ *domainlen = 0;
+
+ if (p)
+ {
+ const size_t length = (size_t)(p - username);
+ *user = &p[1];
+ *userlen = strlen(*user);
+
+ *domain = username;
+ *domainlen = length;
+ }
+ else if (username)
+ {
+ /* Do not break up the name for '@'; both credSSP and the
+ * ClientInfo PDU expect 'user@corp.net' to be transmitted
+ * as username 'user@corp.net', domain empty (not NULL!).
+ */
+ *user = username;
+ *userlen = strlen(username);
+ }
+ else
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL freerdp_parse_username_settings(const char* username, rdpSettings* settings,
+ FreeRDP_Settings_Keys_String userID,
+ FreeRDP_Settings_Keys_String domainID)
+{
+ const char* user = NULL;
+ const char* domain = NULL;
+ size_t userlen = 0;
+ size_t domainlen = 0;
+
+ const BOOL rc = freerdp_parse_username_ptr(username, &user, &userlen, &domain, &domainlen);
+ if (!rc)
+ return FALSE;
+ if (!freerdp_settings_set_string_len(settings, userID, user, userlen))
+ return FALSE;
+ return freerdp_settings_set_string_len(settings, domainID, domain, domainlen);
+}
+
+BOOL freerdp_parse_username(const char* username, char** puser, char** pdomain)
+{
+ const char* user = NULL;
+ const char* domain = NULL;
+ size_t userlen = 0;
+ size_t domainlen = 0;
+
+ *puser = NULL;
+ *pdomain = NULL;
+
+ const BOOL rc = freerdp_parse_username_ptr(username, &user, &userlen, &domain, &domainlen);
+ if (!rc)
+ return FALSE;
+
+ if (userlen > 0)
+ {
+ *puser = strndup(user, userlen);
+ if (!*puser)
+ return FALSE;
+ }
+
+ if (domainlen > 0)
+ {
+ *pdomain = strndup(domain, domainlen);
+ if (!*pdomain)
+ {
+ free(*puser);
+ *puser = NULL;
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL freerdp_parse_hostname(const char* hostname, char** host, int* port)
+{
+ char* p = NULL;
+ p = strrchr(hostname, ':');
+
+ if (p)
+ {
+ size_t length = (size_t)(p - hostname);
+ LONGLONG val = 0;
+
+ if (!value_to_int(p + 1, &val, 1, UINT16_MAX))
+ return FALSE;
+
+ *host = (char*)calloc(length + 1UL, sizeof(char));
+
+ if (!(*host))
+ return FALSE;
+
+ CopyMemory(*host, hostname, length);
+ (*host)[length] = '\0';
+ *port = (UINT16)val;
+ }
+ else
+ {
+ *host = _strdup(hostname);
+
+ if (!(*host))
+ return FALSE;
+
+ *port = -1;
+ }
+
+ return TRUE;
+}
+
+static BOOL freerdp_apply_connection_type(rdpSettings* settings, UINT32 type)
+{
+ struct network_settings
+ {
+ FreeRDP_Settings_Keys_Bool id;
+ BOOL value[7];
+ };
+ const struct network_settings config[] = {
+ { FreeRDP_DisableWallpaper, { TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE } },
+ { FreeRDP_AllowFontSmoothing, { FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE } },
+ { FreeRDP_AllowDesktopComposition, { FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE } },
+ { FreeRDP_DisableFullWindowDrag, { TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE } },
+ { FreeRDP_DisableMenuAnims, { TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE } },
+ { FreeRDP_DisableThemes, { TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE } },
+ { FreeRDP_NetworkAutoDetect, { FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE } }
+ };
+
+ switch (type)
+ {
+ case CONNECTION_TYPE_MODEM:
+ case CONNECTION_TYPE_BROADBAND_LOW:
+ case CONNECTION_TYPE_BROADBAND_HIGH:
+ case CONNECTION_TYPE_SATELLITE:
+ case CONNECTION_TYPE_WAN:
+ case CONNECTION_TYPE_LAN:
+ case CONNECTION_TYPE_AUTODETECT:
+ break;
+ default:
+ WLog_WARN(TAG, "Invalid ConnectionType %" PRIu32 ", aborting", type);
+ return FALSE;
+ }
+
+ for (size_t x = 0; x < ARRAYSIZE(config); x++)
+ {
+ const struct network_settings* cur = &config[x];
+ if (!freerdp_settings_set_bool(settings, cur->id, cur->value[type - 1]))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+BOOL freerdp_set_connection_type(rdpSettings* settings, UINT32 type)
+{
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ConnectionType, type))
+ return FALSE;
+
+ switch (type)
+ {
+ case CONNECTION_TYPE_MODEM:
+ if (!freerdp_apply_connection_type(settings, type))
+ return FALSE;
+ break;
+ case CONNECTION_TYPE_BROADBAND_LOW:
+ if (!freerdp_apply_connection_type(settings, type))
+ return FALSE;
+ break;
+ case CONNECTION_TYPE_SATELLITE:
+ if (!freerdp_apply_connection_type(settings, type))
+ return FALSE;
+ break;
+ case CONNECTION_TYPE_BROADBAND_HIGH:
+ if (!freerdp_apply_connection_type(settings, type))
+ return FALSE;
+ break;
+ case CONNECTION_TYPE_WAN:
+ if (!freerdp_apply_connection_type(settings, type))
+ return FALSE;
+ break;
+ case CONNECTION_TYPE_LAN:
+ if (!freerdp_apply_connection_type(settings, type))
+ return FALSE;
+ break;
+ case CONNECTION_TYPE_AUTODETECT:
+ if (!freerdp_apply_connection_type(settings, type))
+ return FALSE;
+ /* Automatically activate GFX and RFX codec support */
+#ifdef WITH_GFX_H264
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444v2, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GfxH264, TRUE))
+ return FALSE;
+#endif
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_SupportGraphicsPipeline, TRUE))
+ return FALSE;
+ break;
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static UINT32 freerdp_get_keyboard_layout_for_type(const char* name, DWORD type)
+{
+ size_t count = 0;
+ RDP_KEYBOARD_LAYOUT* layouts =
+ freerdp_keyboard_get_layouts(RDP_KEYBOARD_LAYOUT_TYPE_STANDARD, &count);
+
+ if (!layouts || (count == 0))
+ return FALSE;
+
+ for (size_t x = 0; x < count; x++)
+ {
+ const RDP_KEYBOARD_LAYOUT* layout = &layouts[x];
+ if (option_equals(layout->name, name))
+ {
+ return layout->code;
+ }
+ }
+
+ freerdp_keyboard_layouts_free(layouts, count);
+ return 0;
+}
+
+static UINT32 freerdp_map_keyboard_layout_name_to_id(const char* name)
+{
+ const UINT32 variants[] = { RDP_KEYBOARD_LAYOUT_TYPE_STANDARD, RDP_KEYBOARD_LAYOUT_TYPE_VARIANT,
+ RDP_KEYBOARD_LAYOUT_TYPE_IME };
+
+ for (size_t x = 0; x < ARRAYSIZE(variants); x++)
+ {
+ UINT32 rc = freerdp_get_keyboard_layout_for_type(name, variants[x]);
+ if (rc > 0)
+ return rc;
+ }
+
+ return 0;
+}
+
+static int freerdp_detect_command_line_pre_filter(void* context, int index, int argc, LPSTR* argv)
+{
+ size_t length = 0;
+ WINPR_UNUSED(context);
+
+ if (index == 1)
+ {
+ if (argc < index)
+ return -1;
+
+ length = strlen(argv[index]);
+
+ if (length > 4)
+ {
+ if (option_is_rdp_file(argv[index]))
+ {
+ return 1;
+ }
+ }
+
+ if (length > 13)
+ {
+ if (option_is_incident_file(argv[index]))
+ {
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int freerdp_detect_windows_style_command_line_syntax(int argc, char** argv, size_t* count,
+ BOOL ignoreUnknown)
+{
+ int status = 0;
+ DWORD flags = 0;
+ int detect_status = 0;
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+ COMMAND_LINE_ARGUMENT_A largs[ARRAYSIZE(global_cmd_args)];
+ memcpy(largs, global_cmd_args, sizeof(global_cmd_args));
+
+ flags = COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_SILENCE_PARSER;
+ flags |= COMMAND_LINE_SIGIL_SLASH | COMMAND_LINE_SIGIL_PLUS_MINUS;
+
+ if (ignoreUnknown)
+ {
+ flags |= COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+ }
+
+ *count = 0;
+ detect_status = 0;
+ CommandLineClearArgumentsA(largs);
+ status = CommandLineParseArgumentsA(argc, argv, largs, flags, NULL,
+ freerdp_detect_command_line_pre_filter, NULL);
+
+ if (status < 0)
+ return status;
+
+ arg = largs;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
+ continue;
+
+ (*count)++;
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ if ((status <= COMMAND_LINE_ERROR) && (status >= COMMAND_LINE_ERROR_LAST))
+ detect_status = -1;
+
+ return detect_status;
+}
+
+static int freerdp_detect_posix_style_command_line_syntax(int argc, char** argv, size_t* count,
+ BOOL ignoreUnknown)
+{
+ int status = 0;
+ DWORD flags = 0;
+ int detect_status = 0;
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+ COMMAND_LINE_ARGUMENT_A largs[ARRAYSIZE(global_cmd_args)];
+ memcpy(largs, global_cmd_args, sizeof(global_cmd_args));
+
+ flags = COMMAND_LINE_SEPARATOR_SPACE | COMMAND_LINE_SILENCE_PARSER;
+ flags |= COMMAND_LINE_SIGIL_DASH | COMMAND_LINE_SIGIL_DOUBLE_DASH;
+ flags |= COMMAND_LINE_SIGIL_ENABLE_DISABLE;
+
+ if (ignoreUnknown)
+ {
+ flags |= COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+ }
+
+ *count = 0;
+ detect_status = 0;
+ CommandLineClearArgumentsA(largs);
+ status = CommandLineParseArgumentsA(argc, argv, largs, flags, NULL,
+ freerdp_detect_command_line_pre_filter, NULL);
+
+ if (status < 0)
+ return status;
+
+ arg = largs;
+
+ do
+ {
+ if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
+ continue;
+
+ (*count)++;
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ if ((status <= COMMAND_LINE_ERROR) && (status >= COMMAND_LINE_ERROR_LAST))
+ detect_status = -1;
+
+ return detect_status;
+}
+
+static BOOL freerdp_client_detect_command_line(int argc, char** argv, DWORD* flags)
+{
+ int posix_cli_status = 0;
+ size_t posix_cli_count = 0;
+ int windows_cli_status = 0;
+ size_t windows_cli_count = 0;
+ const BOOL ignoreUnknown = TRUE;
+ windows_cli_status = freerdp_detect_windows_style_command_line_syntax(
+ argc, argv, &windows_cli_count, ignoreUnknown);
+ posix_cli_status =
+ freerdp_detect_posix_style_command_line_syntax(argc, argv, &posix_cli_count, ignoreUnknown);
+
+ /* Default is POSIX syntax */
+ *flags = COMMAND_LINE_SEPARATOR_SPACE;
+ *flags |= COMMAND_LINE_SIGIL_DASH | COMMAND_LINE_SIGIL_DOUBLE_DASH;
+ *flags |= COMMAND_LINE_SIGIL_ENABLE_DISABLE;
+
+ if (posix_cli_status <= COMMAND_LINE_STATUS_PRINT)
+ return FALSE;
+
+ /* Check, if this may be windows style syntax... */
+ if ((windows_cli_count && (windows_cli_count >= posix_cli_count)) ||
+ (windows_cli_status <= COMMAND_LINE_STATUS_PRINT))
+ {
+ windows_cli_count = 1;
+ *flags = COMMAND_LINE_SEPARATOR_COLON;
+ *flags |= COMMAND_LINE_SIGIL_SLASH | COMMAND_LINE_SIGIL_PLUS_MINUS;
+ }
+
+ WLog_DBG(TAG, "windows: %d/%" PRIuz " posix: %d/%" PRIuz "", windows_cli_status,
+ windows_cli_count, posix_cli_status, posix_cli_count);
+ if ((posix_cli_count == 0) && (windows_cli_count == 0))
+ {
+ if ((posix_cli_status == COMMAND_LINE_ERROR) && (windows_cli_status == COMMAND_LINE_ERROR))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+int freerdp_client_settings_command_line_status_print(rdpSettings* settings, int status, int argc,
+ char** argv)
+{
+ return freerdp_client_settings_command_line_status_print_ex(settings, status, argc, argv, NULL);
+}
+
+static void freerdp_client_print_keyboard_type_list(const char* msg, DWORD type)
+{
+ size_t count = 0;
+ RDP_KEYBOARD_LAYOUT* layouts = NULL;
+ layouts = freerdp_keyboard_get_layouts(type, &count);
+
+ printf("\n%s\n", msg);
+
+ for (size_t x = 0; x < count; x++)
+ {
+ const RDP_KEYBOARD_LAYOUT* layout = &layouts[x];
+ printf("0x%08" PRIX32 "\t%s\n", layout->code, layout->name);
+ }
+
+ freerdp_keyboard_layouts_free(layouts, count);
+}
+
+static void freerdp_client_print_keyboard_list(void)
+{
+ freerdp_client_print_keyboard_type_list("Keyboard Layouts", RDP_KEYBOARD_LAYOUT_TYPE_STANDARD);
+ freerdp_client_print_keyboard_type_list("Keyboard Layout Variants",
+ RDP_KEYBOARD_LAYOUT_TYPE_VARIANT);
+ freerdp_client_print_keyboard_type_list("Keyboard Layout Variants",
+ RDP_KEYBOARD_LAYOUT_TYPE_IME);
+}
+
+static void freerdp_client_print_tune_list(const rdpSettings* settings)
+{
+ SSIZE_T type = 0;
+
+ printf("%s\t%50s\t%s\t%s", "<index>", "<key>", "<type>", "<default value>\n");
+ for (size_t x = 0; x < FreeRDP_Settings_StableAPI_MAX; x++)
+ {
+ const char* name = freerdp_settings_get_name_for_key(x);
+ type = freerdp_settings_get_type_for_key(x);
+
+ switch (type)
+ {
+ case RDP_SETTINGS_TYPE_BOOL:
+ printf("%" PRIuz "\t%50s\tBOOL\t%s\n", x, name,
+ freerdp_settings_get_bool(settings, (FreeRDP_Settings_Keys_Bool)x)
+ ? "TRUE"
+ : "FALSE");
+ break;
+ case RDP_SETTINGS_TYPE_UINT16:
+ printf("%" PRIuz "\t%50s\tUINT16\t%" PRIu16 "\n", x, name,
+ freerdp_settings_get_uint16(settings, (FreeRDP_Settings_Keys_UInt16)x));
+ break;
+ case RDP_SETTINGS_TYPE_INT16:
+ printf("%" PRIuz "\t%50s\tINT16\t%" PRId16 "\n", x, name,
+ freerdp_settings_get_int16(settings, (FreeRDP_Settings_Keys_Int16)x));
+ break;
+ case RDP_SETTINGS_TYPE_UINT32:
+ printf("%" PRIuz "\t%50s\tUINT32\t%" PRIu32 "\n", x, name,
+ freerdp_settings_get_uint32(settings, (FreeRDP_Settings_Keys_UInt32)x));
+ break;
+ case RDP_SETTINGS_TYPE_INT32:
+ printf("%" PRIuz "\t%50s\tINT32\t%" PRId32 "\n", x, name,
+ freerdp_settings_get_int32(settings, (FreeRDP_Settings_Keys_Int32)x));
+ break;
+ case RDP_SETTINGS_TYPE_UINT64:
+ printf("%" PRIuz "\t%50s\tUINT64\t%" PRIu64 "\n", x, name,
+ freerdp_settings_get_uint64(settings, (FreeRDP_Settings_Keys_UInt64)x));
+ break;
+ case RDP_SETTINGS_TYPE_INT64:
+ printf("%" PRIuz "\t%50s\tINT64\t%" PRId64 "\n", x, name,
+ freerdp_settings_get_int64(settings, (FreeRDP_Settings_Keys_Int64)x));
+ break;
+ case RDP_SETTINGS_TYPE_STRING:
+ printf("%" PRIuz "\t%50s\tSTRING\t%s"
+ "\n",
+ x, name,
+ freerdp_settings_get_string(settings, (FreeRDP_Settings_Keys_String)x));
+ break;
+ case RDP_SETTINGS_TYPE_POINTER:
+ printf("%" PRIuz "\t%50s\tPOINTER\t%p"
+ "\n",
+ x, name,
+ freerdp_settings_get_pointer(settings, (FreeRDP_Settings_Keys_Pointer)x));
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+int freerdp_client_settings_command_line_status_print_ex(rdpSettings* settings, int status,
+ int argc, char** argv,
+ const COMMAND_LINE_ARGUMENT_A* custom)
+{
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+ COMMAND_LINE_ARGUMENT_A largs[ARRAYSIZE(global_cmd_args)];
+ memcpy(largs, global_cmd_args, sizeof(global_cmd_args));
+
+ if (status == COMMAND_LINE_STATUS_PRINT_VERSION)
+ {
+ freerdp_client_print_version();
+ goto out;
+ }
+
+ if (status == COMMAND_LINE_STATUS_PRINT_BUILDCONFIG)
+ {
+ freerdp_client_print_version();
+ freerdp_client_print_buildconfig();
+ goto out;
+ }
+ else if (status == COMMAND_LINE_STATUS_PRINT)
+ {
+ CommandLineParseArgumentsA(argc, argv, largs, 0x112, NULL, NULL, NULL);
+
+ arg = CommandLineFindArgumentA(largs, "list");
+ WINPR_ASSERT(arg);
+
+ if (arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT)
+ {
+ if (option_equals("tune", arg->Value))
+ freerdp_client_print_tune_list(settings);
+ else if (option_equals("kbd", arg->Value))
+ freerdp_client_print_keyboard_list();
+ else if (option_starts_with("kbd-lang", arg->Value))
+ {
+ const char* val = NULL;
+ if (option_starts_with("kbd-lang:", arg->Value))
+ val = &arg->Value[9];
+ else if (!option_equals("kbd-lang", arg->Value))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (val && strchr(val, ','))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ freerdp_client_print_codepages(val);
+ }
+ else if (option_equals("kbd-scancode", arg->Value))
+ freerdp_client_print_scancodes();
+ else if (option_equals("monitor", arg->Value))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ListMonitors, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with("smartcard", arg->Value))
+ {
+ BOOL opts = FALSE;
+ if (option_starts_with("smartcard:", arg->Value))
+ opts = TRUE;
+ else if (!option_equals("smartcard", arg->Value))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (opts)
+ {
+ const char* sub = strchr(arg->Value, ':') + 1;
+ const CmdLineSubOptions options[] = {
+ { "pkinit-anchors:", FreeRDP_PkinitAnchors, CMDLINE_SUBOPTION_STRING,
+ NULL },
+ { "pkcs11-module:", FreeRDP_Pkcs11Module, CMDLINE_SUBOPTION_STRING, NULL }
+ };
+
+ size_t count = 0;
+
+ char** ptr = CommandLineParseCommaSeparatedValuesEx("smartcard", sub, &count);
+ if (!ptr)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ if (count < 2)
+ {
+ free(ptr);
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+
+ for (size_t x = 1; x < count; x++)
+ {
+ const char* cur = ptr[x];
+ if (!parseSubOptions(settings, options, ARRAYSIZE(options), cur))
+ {
+ free(ptr);
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ }
+
+ free(ptr);
+ }
+
+ freerdp_smartcard_list(settings);
+ }
+ else
+ {
+ freerdp_client_print_command_line_help_ex(argc, argv, custom);
+ return COMMAND_LINE_ERROR;
+ }
+ }
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ arg = CommandLineFindArgumentA(largs, "tune-list");
+ WINPR_ASSERT(arg);
+
+ if (arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT)
+ {
+ WLog_WARN(TAG, "Option /tune-list is deprecated, use /list:tune instead");
+ freerdp_client_print_tune_list(settings);
+ }
+
+ arg = CommandLineFindArgumentA(largs, "kbd-lang-list");
+ WINPR_ASSERT(arg);
+
+ if (arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT)
+ {
+ WLog_WARN(TAG, "Option /kbd-lang-list is deprecated, use /list:kbd-lang instead");
+ freerdp_client_print_codepages(arg->Value);
+ }
+
+ arg = CommandLineFindArgumentA(largs, "kbd-list");
+ WINPR_ASSERT(arg);
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ WLog_WARN(TAG, "Option /kbd-list is deprecated, use /list:kbd instead");
+ freerdp_client_print_keyboard_list();
+ }
+
+ arg = CommandLineFindArgumentA(largs, "monitor-list");
+ WINPR_ASSERT(arg);
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ WLog_WARN(TAG, "Option /monitor-list is deprecated, use /list:monitor instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ListMonitors, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+
+ arg = CommandLineFindArgumentA(largs, "smartcard-list");
+ WINPR_ASSERT(arg);
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ WLog_WARN(TAG, "Option /smartcard-list is deprecated, use /list:smartcard instead");
+ freerdp_smartcard_list(settings);
+ }
+
+ arg = CommandLineFindArgumentA(largs, "kbd-scancode-list");
+ WINPR_ASSERT(arg);
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ WLog_WARN(TAG,
+ "Option /kbd-scancode-list is deprecated, use /list:kbd-scancode instead");
+ freerdp_client_print_scancodes();
+ goto out;
+ }
+#endif
+ goto out;
+ }
+ else if (status < 0)
+ {
+ freerdp_client_print_command_line_help_ex(argc, argv, custom);
+ goto out;
+ }
+
+out:
+ if (status <= COMMAND_LINE_STATUS_PRINT && status >= COMMAND_LINE_STATUS_PRINT_LAST)
+ return 0;
+ return status;
+}
+
+/**
+ * parses a string value with the format <v1>x<v2>
+ *
+ * @param input input string
+ * @param v1 pointer to output v1
+ * @param v2 pointer to output v2
+ * @return if the parsing was successful
+ */
+static BOOL parseSizeValue(const char* input, unsigned long* v1, unsigned long* v2)
+{
+ const char* xcharpos = NULL;
+ char* endPtr = NULL;
+ unsigned long v = 0;
+ errno = 0;
+ v = strtoul(input, &endPtr, 10);
+
+ if ((v == 0 || v == ULONG_MAX) && (errno != 0))
+ return FALSE;
+
+ if (v1)
+ *v1 = v;
+
+ xcharpos = strchr(input, 'x');
+
+ if (!xcharpos || xcharpos != endPtr)
+ return FALSE;
+
+ errno = 0;
+ v = strtoul(xcharpos + 1, &endPtr, 10);
+
+ if ((v == 0 || v == ULONG_MAX) && (errno != 0))
+ return FALSE;
+
+ if (*endPtr != '\0')
+ return FALSE;
+
+ if (v2)
+ *v2 = v;
+
+ return TRUE;
+}
+
+static BOOL prepare_default_settings(rdpSettings* settings, COMMAND_LINE_ARGUMENT_A* args,
+ BOOL rdp_file)
+{
+ const char* arguments[] = { "network", "gfx", "rfx", "bpp" };
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(args);
+
+ if (rdp_file)
+ return FALSE;
+
+ for (size_t x = 0; x < ARRAYSIZE(arguments); x++)
+ {
+ const char* arg = arguments[x];
+ const COMMAND_LINE_ARGUMENT_A* p = CommandLineFindArgumentA(args, arg);
+ if (p && (p->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
+ return FALSE;
+ }
+
+ return freerdp_set_connection_type(settings, CONNECTION_TYPE_AUTODETECT);
+}
+
+static BOOL setSmartcardEmulation(const char* value, rdpSettings* settings)
+{
+ return freerdp_settings_set_bool(settings, FreeRDP_SmartcardEmulation, TRUE);
+}
+
+const char* option_starts_with(const char* what, const char* val)
+{
+ WINPR_ASSERT(what);
+ WINPR_ASSERT(val);
+ const size_t wlen = strlen(what);
+
+ if (_strnicmp(what, val, wlen) != 0)
+ return NULL;
+ return &val[wlen];
+}
+
+BOOL option_ends_with(const char* str, const char* ext)
+{
+ WINPR_ASSERT(str);
+ WINPR_ASSERT(ext);
+ const size_t strLen = strlen(str);
+ const size_t extLen = strlen(ext);
+
+ if (strLen < extLen)
+ return FALSE;
+
+ return _strnicmp(&str[strLen - extLen], ext, extLen) == 0;
+}
+
+BOOL option_equals(const char* what, const char* val)
+{
+ WINPR_ASSERT(what);
+ WINPR_ASSERT(val);
+ return _stricmp(what, val) == 0;
+}
+
+typedef enum
+{
+ PARSE_ON,
+ PARSE_OFF,
+ PARSE_NONE,
+ PARSE_FAIL
+} PARSE_ON_OFF_RESULT;
+
+static PARSE_ON_OFF_RESULT parse_on_off_option(const char* value)
+{
+ WINPR_ASSERT(value);
+ const char* sep = strchr(value, ':');
+ if (!sep)
+ return PARSE_NONE;
+ if (option_equals("on", &sep[1]))
+ return PARSE_ON;
+ if (option_equals("off", &sep[1]))
+ return PARSE_OFF;
+ return PARSE_FAIL;
+}
+
+typedef enum
+{
+ CLIP_DIR_PARSE_ALL,
+ CLIP_DIR_PARSE_OFF,
+ CLIP_DIR_PARSE_LOCAL,
+ CLIP_DIR_PARSE_REMOTE,
+ CLIP_DIR_PARSE_FAIL
+} PARSE_CLIP_DIR_RESULT;
+
+static PARSE_CLIP_DIR_RESULT parse_clip_direciton_to_option(const char* value)
+{
+ WINPR_ASSERT(value);
+ const char* sep = strchr(value, ':');
+ if (!sep)
+ return CLIP_DIR_PARSE_FAIL;
+ if (option_equals("all", &sep[1]))
+ return CLIP_DIR_PARSE_ALL;
+ if (option_equals("off", &sep[1]))
+ return CLIP_DIR_PARSE_OFF;
+ if (option_equals("local", &sep[1]))
+ return CLIP_DIR_PARSE_LOCAL;
+ if (option_equals("remote", &sep[1]))
+ return CLIP_DIR_PARSE_REMOTE;
+ return CLIP_DIR_PARSE_FAIL;
+}
+
+static int parse_tls_ciphers(rdpSettings* settings, const char* Value)
+{
+ const char* ciphers = NULL;
+ if (!Value)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (option_equals(Value, "netmon"))
+ {
+ ciphers = "ALL:!ECDH:!ADH:!DHE";
+ }
+ else if (option_equals(Value, "ma"))
+ {
+ ciphers = "AES128-SHA";
+ }
+ else
+ {
+ ciphers = Value;
+ }
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_AllowedTlsCiphers, ciphers))
+ return COMMAND_LINE_ERROR_MEMORY;
+ return 0;
+}
+
+static int parse_tls_seclevel(rdpSettings* settings, const char* Value)
+{
+ LONGLONG val = 0;
+
+ if (!value_to_int(Value, &val, 0, 5))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_TlsSecLevel, (UINT32)val))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ return 0;
+}
+
+static int parse_tls_secrets_file(rdpSettings* settings, const char* Value)
+{
+ if (!Value)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_TlsSecretsFile, Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ return 0;
+}
+
+static int parse_tls_enforce(rdpSettings* settings, const char* Value)
+{
+ UINT16 version = TLS1_2_VERSION;
+
+ if (Value)
+ {
+ struct map_t
+ {
+ const char* name;
+ UINT16 version;
+ };
+ const struct map_t map[] = {
+ { "1.0", TLS1_VERSION },
+ { "1.1", TLS1_1_VERSION },
+ { "1.2", TLS1_2_VERSION }
+#if defined(TLS1_3_VERSION)
+ ,
+ { "1.3", TLS1_3_VERSION }
+#endif
+ };
+
+ for (size_t x = 0; x < ARRAYSIZE(map); x++)
+ {
+ const struct map_t* cur = &map[x];
+ if (option_equals(cur->name, Value))
+ {
+ version = cur->version;
+ break;
+ }
+ }
+ }
+
+ if (!(freerdp_settings_set_uint16(settings, FreeRDP_TLSMinVersion, version) &&
+ freerdp_settings_set_uint16(settings, FreeRDP_TLSMaxVersion, version)))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ return 0;
+}
+
+static int parse_tls_cipher_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ int rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "tls")
+ {
+ if (option_starts_with("ciphers:", arg->Value))
+ rc = parse_tls_ciphers(settings, &arg->Value[8]);
+ else if (option_starts_with("seclevel:", arg->Value))
+ rc = parse_tls_seclevel(settings, &arg->Value[9]);
+ else if (option_starts_with("secrets-file:", arg->Value))
+ rc = parse_tls_secrets_file(settings, &arg->Value[13]);
+ else if (option_starts_with("enforce:", arg->Value))
+ rc = parse_tls_enforce(settings, &arg->Value[8]);
+ }
+
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ CommandLineSwitchCase(arg, "tls-ciphers")
+ {
+ WLog_WARN(TAG, "Option /tls-ciphers is deprecated, use /tls:ciphers instead");
+ rc = parse_tls_ciphers(settings, arg->Value);
+ }
+ CommandLineSwitchCase(arg, "tls-seclevel")
+ {
+ WLog_WARN(TAG, "Option /tls-seclevel is deprecated, use /tls:seclevel instead");
+ rc = parse_tls_seclevel(settings, arg->Value);
+ }
+ CommandLineSwitchCase(arg, "tls-secrets-file")
+ {
+ WLog_WARN(TAG, "Option /tls-secrets-file is deprecated, use /tls:secrets-file instead");
+ rc = parse_tls_secrets_file(settings, arg->Value);
+ }
+ CommandLineSwitchCase(arg, "enforce-tlsv1_2")
+ {
+ WLog_WARN(TAG, "Option /enforce-tlsv1_2 is deprecated, use /tls:enforce:1.2 instead");
+ rc = parse_tls_enforce(settings, "1.2");
+ }
+#endif
+ CommandLineSwitchDefault(arg)
+ {
+ }
+ CommandLineSwitchEnd(arg)
+
+ return rc;
+}
+
+static int parse_tls_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ size_t count = 0;
+ char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ for (size_t x = 0; x < count; x++)
+ {
+ COMMAND_LINE_ARGUMENT_A larg = *arg;
+ larg.Value = ptr[x];
+
+ int rc = parse_tls_cipher_options(settings, &larg);
+ if (rc != 0)
+ {
+ free(ptr);
+ return rc;
+ }
+ }
+ free(ptr);
+ return 0;
+}
+
+static int parse_gfx_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGraphicsPipeline, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ if (arg->Value)
+ {
+ int rc = CHANNEL_RC_OK;
+ size_t count = 0;
+ char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ if (!ptr || (count == 0))
+ rc = COMMAND_LINE_ERROR;
+ else
+ {
+ BOOL GfxH264 = FALSE;
+ BOOL GfxAVC444 = FALSE;
+ BOOL RemoteFxCodec = FALSE;
+ BOOL GfxProgressive = FALSE;
+ BOOL codecSelected = FALSE;
+
+ for (size_t x = 0; x < count; x++)
+ {
+ const char* val = ptr[x];
+#ifdef WITH_GFX_H264
+ if (option_starts_with("AVC444", val))
+ {
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val);
+ if (bval == PARSE_FAIL)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else
+ GfxAVC444 = bval != PARSE_OFF;
+ codecSelected = TRUE;
+ }
+ else if (option_starts_with("AVC420", val))
+ {
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val);
+ if (bval == PARSE_FAIL)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else
+ GfxH264 = bval != PARSE_OFF;
+ codecSelected = TRUE;
+ }
+ else
+#endif
+ if (option_starts_with("RFX", val))
+ {
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val);
+ if (bval == PARSE_FAIL)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else
+ RemoteFxCodec = bval != PARSE_OFF;
+ codecSelected = TRUE;
+ }
+ else if (option_starts_with("progressive", val))
+ {
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val);
+ if (bval == PARSE_FAIL)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else
+ GfxProgressive = bval != PARSE_OFF;
+ codecSelected = TRUE;
+ }
+ else if (option_starts_with("mask:", val))
+ {
+ ULONGLONG v = 0;
+ const char* uv = &val[5];
+ if (!value_to_uint(uv, &v, 0, UINT32_MAX))
+ rc = COMMAND_LINE_ERROR;
+ else
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_GfxCapsFilter, v))
+ rc = COMMAND_LINE_ERROR;
+ }
+ }
+ else if (option_starts_with("small-cache", val))
+ {
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val);
+ if (bval == PARSE_FAIL)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else if (!freerdp_settings_set_bool(settings, FreeRDP_GfxSmallCache,
+ bval != PARSE_OFF))
+ rc = COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with("thin-client", val))
+ {
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val);
+ if (bval == PARSE_FAIL)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else if (!freerdp_settings_set_bool(settings, FreeRDP_GfxThinClient,
+ bval != PARSE_OFF))
+ rc = COMMAND_LINE_ERROR;
+ if ((rc == CHANNEL_RC_OK) && (bval > 0))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxSmallCache,
+ bval != PARSE_OFF))
+ rc = COMMAND_LINE_ERROR;
+ }
+ }
+ }
+
+ if ((rc == CHANNEL_RC_OK) && codecSelected)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444, GfxAVC444))
+ rc = COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444v2, GfxAVC444))
+ rc = COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxH264, GfxH264))
+ rc = COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, RemoteFxCodec))
+ rc = COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxProgressive, GfxProgressive))
+ rc = COMMAND_LINE_ERROR;
+ }
+ }
+ free(ptr);
+ if (rc != CHANNEL_RC_OK)
+ return rc;
+ }
+ return CHANNEL_RC_OK;
+}
+
+static int parse_kbd_layout(rdpSettings* settings, const char* value)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(value);
+
+ int rc = 0;
+ LONGLONG ival = 0;
+ const BOOL isInt = value_to_int(value, &ival, 1, UINT32_MAX);
+ if (!isInt)
+ {
+ ival = freerdp_map_keyboard_layout_name_to_id(value);
+
+ if (ival == 0)
+ {
+ WLog_ERR(TAG, "Could not identify keyboard layout: %s", value);
+ WLog_ERR(TAG, "Use /list:kbd to list available layouts");
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ }
+
+ if (rc == 0)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardLayout, (UINT32)ival))
+ rc = COMMAND_LINE_ERROR;
+ }
+ return rc;
+}
+
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+static int parse_codec_cache_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (!arg->Value)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCacheV3Enabled, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ if (option_equals(arg->Value, "rfx"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (option_equals(arg->Value, "nsc"))
+ {
+ freerdp_settings_set_bool(settings, FreeRDP_NSCodec, TRUE);
+ }
+
+#if defined(WITH_JPEG)
+ else if (option_equals(arg->Value, "jpeg"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_JpegCodec, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ if (freerdp_settings_get_uint32(settings, FreeRDP_JpegQuality) == 0)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_JpegQuality, 75))
+ return COMMAND_LINE_ERROR;
+ }
+ }
+
+#endif
+}
+#endif
+
+static BOOL check_kbd_remap_valid(const char* token)
+{
+ DWORD key = 0;
+ DWORD value = 0;
+
+ WINPR_ASSERT(token);
+ /* The remapping is only allowed for scancodes, so maximum is 999=999 */
+ if (strlen(token) > 10)
+ return FALSE;
+
+ int rc = sscanf(token, "%" PRIu32 "=%" PRIu32, &key, &value);
+ if (rc != 2)
+ rc = sscanf(token, "%" PRIx32 "=%" PRIx32 "", &key, &value);
+ if (rc != 2)
+ rc = sscanf(token, "%" PRIu32 "=%" PRIx32, &key, &value);
+ if (rc != 2)
+ rc = sscanf(token, "%" PRIx32 "=%" PRIu32, &key, &value);
+ if (rc != 2)
+ {
+ WLog_WARN(TAG, "/kbd:remap invalid entry '%s'", token);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static int parse_host_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (!arg->Value)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ freerdp_settings_set_string(settings, FreeRDP_ServerHostname, NULL);
+ char* p = strchr(arg->Value, '[');
+
+ /* ipv4 */
+ if (!p)
+ {
+ const char scheme[] = "://";
+ const char* val = strstr(arg->Value, scheme);
+ if (val)
+ val += strnlen(scheme, sizeof(scheme));
+ else
+ val = arg->Value;
+ p = strchr(val, ':');
+
+ if (p)
+ {
+ LONGLONG val = 0;
+ size_t length = 0;
+
+ if (!value_to_int(&p[1], &val, 1, UINT16_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ length = (size_t)(p - arg->Value);
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, val))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ if (!freerdp_settings_set_string_len(settings, FreeRDP_ServerHostname, arg->Value,
+ length))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ else
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_ServerHostname, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ }
+ else /* ipv6 */
+ {
+ size_t length = 0;
+ char* p2 = strchr(arg->Value, ']');
+
+ /* not a valid [] ipv6 addr found */
+ if (!p2)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ length = (size_t)(p2 - p);
+ if (!freerdp_settings_set_string_len(settings, FreeRDP_ServerHostname, p + 1, length - 1))
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ if (*(p2 + 1) == ':')
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(&p2[2], &val, 0, UINT16_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, (UINT16)val))
+ return COMMAND_LINE_ERROR;
+ }
+
+ printf("hostname %s port %" PRIu32 "\n",
+ freerdp_settings_get_string(settings, FreeRDP_ServerHostname),
+ freerdp_settings_get_uint32(settings, FreeRDP_ServerPort));
+ }
+ return 0;
+}
+
+static int parse_redirect_prefer_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ size_t count = 0;
+ char* cur = arg->Value;
+ if (!arg->Value)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_RedirectionPreferType, 0))
+ return COMMAND_LINE_ERROR;
+
+ UINT32 value = 0;
+ do
+ {
+ UINT32 mask = 0;
+ char* next = strchr(cur, ',');
+
+ if (next)
+ {
+ *next = '\0';
+ next++;
+ }
+
+ if (option_equals("fqdn", cur))
+ mask = 0x06U;
+ else if (option_equals("ip", cur))
+ mask = 0x05U;
+ else if (option_equals("netbios", cur))
+ mask = 0x03U;
+ else
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ cur = next;
+ mask = (mask & 0x07);
+ value |= mask << (count * 3);
+ count++;
+ } while (cur != NULL);
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_RedirectionPreferType, value))
+ return COMMAND_LINE_ERROR;
+
+ if (count > 3)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ return 0;
+}
+
+static int parse_prevent_session_lock_options(rdpSettings* settings,
+ const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_FakeMouseMotionInterval, 180))
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 1, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_FakeMouseMotionInterval, (UINT32)val))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+
+ return 0;
+}
+
+static int parse_vmconnect_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_VmConnectMode, TRUE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, 2179))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NegotiateSecurityLayer, FALSE))
+ return COMMAND_LINE_ERROR;
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SendPreconnectionPdu, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_PreconnectionBlob, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ return 0;
+}
+
+static int parse_size_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ int status = 0;
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (!arg->Value)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ char* p = strchr(arg->Value, 'x');
+
+ if (p)
+ {
+ unsigned long w = 0;
+ unsigned long h = 0;
+
+ if (!parseSizeValue(arg->Value, &w, &h) || (w > UINT16_MAX) || (h > UINT16_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, (UINT32)w))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, (UINT32)h))
+ return COMMAND_LINE_ERROR;
+ }
+ else
+ {
+ char* str = _strdup(arg->Value);
+ if (!str)
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ p = strchr(str, '%');
+
+ if (p)
+ {
+ BOOL partial = FALSE;
+
+ status = COMMAND_LINE_ERROR;
+ if (strchr(p, 'w'))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_PercentScreenUseWidth, TRUE))
+ goto fail;
+ partial = TRUE;
+ }
+
+ if (strchr(p, 'h'))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_PercentScreenUseHeight, TRUE))
+ goto fail;
+ partial = TRUE;
+ }
+
+ if (!partial)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_PercentScreenUseWidth, TRUE))
+ goto fail;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_PercentScreenUseHeight, TRUE))
+ goto fail;
+ }
+
+ *p = '\0';
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(str, &val, 0, 100))
+ {
+ status = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ goto fail;
+ }
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_PercentScreen, (UINT32)val))
+ goto fail;
+ }
+
+ status = 0;
+ }
+
+ fail:
+ free(str);
+ }
+
+ return status;
+}
+
+static int parse_monitors_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ union
+ {
+ char** p;
+ const char** pc;
+ } ptr;
+ size_t count = 0;
+ UINT32* MonitorIds = NULL;
+ ptr.p = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+
+ if (!ptr.pc)
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ if (count > 16)
+ count = 16;
+
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_MonitorIds, NULL, count))
+ {
+ free(ptr.p);
+ return FALSE;
+ }
+
+ MonitorIds = freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorIds, 0);
+ for (UINT32 i = 0; i < count; i++)
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(ptr.pc[i], &val, 0, UINT16_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ MonitorIds[i] = (UINT32)val;
+ }
+
+ free(ptr.p);
+ }
+
+ return 0;
+}
+
+static int parse_dynamic_resolution_options(rdpSettings* settings,
+ const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing))
+ {
+ WLog_ERR(TAG, "Smart sizing and dynamic resolution are mutually exclusive options");
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportDisplayControl, TRUE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DynamicResolutionUpdate, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ return 0;
+}
+
+static int parse_smart_sizing_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate))
+ {
+ WLog_ERR(TAG, "Smart sizing and dynamic resolution are mutually exclusive options");
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SmartSizing, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ if (arg->Value)
+ {
+ unsigned long w = 0;
+ unsigned long h = 0;
+
+ if (!parseSizeValue(arg->Value, &w, &h) || (w > UINT16_MAX) || (h > UINT16_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_SmartSizingWidth, (UINT32)w))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_SmartSizingHeight, (UINT32)h))
+ return COMMAND_LINE_ERROR;
+ }
+ return 0;
+}
+
+static int parse_bpp_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ switch (val)
+ {
+ case 32:
+ case 24:
+ case 16:
+ case 15:
+ case 8:
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, (UINT32)val))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ break;
+
+ default:
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ return 0;
+}
+
+static int parse_kbd_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ int rc = CHANNEL_RC_OK;
+ size_t count = 0;
+ char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ if (!ptr || (count == 0))
+ rc = COMMAND_LINE_ERROR;
+ else
+ {
+ for (size_t x = 0; x < count; x++)
+ {
+ const char* val = ptr[x];
+
+ if (option_starts_with("remap:", val))
+ {
+ /* Append this new occurance to the already existing list */
+ char* now = _strdup(&val[6]);
+ const char* old =
+ freerdp_settings_get_string(settings, FreeRDP_KeyboardRemappingList);
+
+ /* Basic sanity test. Entries must be like <key>=<value>, e.g. 1=2 */
+ if (!check_kbd_remap_valid(now))
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else if (old)
+ {
+ const size_t olen = strlen(old);
+ const size_t alen = strlen(now);
+ const size_t tlen = olen + alen + 2;
+ char* tmp = calloc(tlen, sizeof(char));
+ if (!tmp)
+ rc = COMMAND_LINE_ERROR_MEMORY;
+ else
+ _snprintf(tmp, tlen, "%s,%s", old, now);
+ free(now);
+ now = tmp;
+ }
+
+ if (rc == 0)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_KeyboardRemappingList, now))
+ rc = COMMAND_LINE_ERROR;
+ }
+ free(now);
+ }
+ else if (option_starts_with("layout:", val))
+ {
+ rc = parse_kbd_layout(settings, &val[7]);
+ }
+ else if (option_starts_with("lang:", val))
+ {
+ LONGLONG ival = 0;
+ const BOOL isInt = value_to_int(&val[5], &ival, 1, UINT32_MAX);
+ if (!isInt)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardCodePage,
+ (UINT32)ival))
+ rc = COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with("type:", val))
+ {
+ LONGLONG ival = 0;
+ const BOOL isInt = value_to_int(&val[5], &ival, 1, UINT32_MAX);
+ if (!isInt)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardType, (UINT32)ival))
+ rc = COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with("subtype:", val))
+ {
+ LONGLONG ival = 0;
+ const BOOL isInt = value_to_int(&val[8], &ival, 1, UINT32_MAX);
+ if (!isInt)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardSubType,
+ (UINT32)ival))
+ rc = COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with("fn-key:", val))
+ {
+ LONGLONG ival = 0;
+ const BOOL isInt = value_to_int(&val[7], &ival, 1, UINT32_MAX);
+ if (!isInt)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardFunctionKey,
+ (UINT32)ival))
+ rc = COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with("unicode", val))
+ {
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val);
+ if (bval == PARSE_FAIL)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else if (!freerdp_settings_set_bool(settings, FreeRDP_UnicodeInput,
+ bval != PARSE_OFF))
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ else if (option_starts_with("pipe:", val))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UnicodeInput, TRUE))
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else if (!freerdp_settings_set_string(settings, FreeRDP_KeyboardPipeName, &val[5]))
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ else if (count == 1)
+ {
+ /* Legacy, allow /kbd:<value> for setting keyboard layout */
+ rc = parse_kbd_layout(settings, val);
+ }
+#endif
+ else
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (rc != 0)
+ break;
+ }
+ }
+ free(ptr);
+ return rc;
+}
+
+static int parse_proxy_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ /* initial value */
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ProxyType, PROXY_TYPE_HTTP))
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ const char* cur = arg->Value;
+
+ if (!cur)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ /* value is [scheme://][user:password@]hostname:port */
+ if (!proxy_parse_uri(settings, cur))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ else
+ {
+ WLog_ERR(TAG, "Option http-proxy needs argument.");
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ return 0;
+}
+
+static int parse_dump_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ BOOL failed = FALSE;
+ size_t count = 0;
+ char** args = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ if (!args || (count != 2))
+ failed = TRUE;
+ else
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_TransportDumpFile, args[1]))
+ failed = TRUE;
+ else if (option_equals(args[0], "replay"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TransportDump, FALSE))
+ failed = TRUE;
+ else if (!freerdp_settings_set_bool(settings, FreeRDP_TransportDumpReplay, TRUE))
+ failed = TRUE;
+ }
+ else if (option_equals(args[0], "record"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TransportDump, TRUE))
+ failed = TRUE;
+ else if (!freerdp_settings_set_bool(settings, FreeRDP_TransportDumpReplay, FALSE))
+ failed = TRUE;
+ }
+ else
+ {
+ failed = TRUE;
+ }
+ }
+ free(args);
+ if (failed)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ return 0;
+}
+
+static int parse_clipboard_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (arg->Value == BoolValueTrue || arg->Value == BoolValueFalse)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectClipboard,
+ (arg->Value == BoolValueTrue)))
+ return COMMAND_LINE_ERROR;
+ }
+ else
+ {
+ int rc = 0;
+ union
+ {
+ char** p;
+ const char** pc;
+ } ptr;
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ for (size_t x = 0; (x < count) && (rc == 0); x++)
+ {
+ const char* usesel = "use-selection:";
+
+ const char* cur = ptr.pc[x];
+ if (option_starts_with(usesel, cur))
+ {
+ const char* val = &cur[strlen(usesel)];
+ if (!freerdp_settings_set_string(settings, FreeRDP_ClipboardUseSelection, val))
+ rc = COMMAND_LINE_ERROR_MEMORY;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectClipboard, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with("direction-to", cur))
+ {
+ const UINT32 mask =
+ freerdp_settings_get_uint32(settings, FreeRDP_ClipboardFeatureMask) &
+ ~(CLIPRDR_FLAG_LOCAL_TO_REMOTE | CLIPRDR_FLAG_REMOTE_TO_LOCAL);
+ const PARSE_CLIP_DIR_RESULT bval = parse_clip_direciton_to_option(cur);
+ UINT32 bflags = 0;
+ switch (bval)
+ {
+ case CLIP_DIR_PARSE_ALL:
+ bflags |= CLIPRDR_FLAG_LOCAL_TO_REMOTE | CLIPRDR_FLAG_REMOTE_TO_LOCAL;
+ break;
+ case CLIP_DIR_PARSE_LOCAL:
+ bflags |= CLIPRDR_FLAG_REMOTE_TO_LOCAL;
+ break;
+ case CLIP_DIR_PARSE_REMOTE:
+ bflags |= CLIPRDR_FLAG_LOCAL_TO_REMOTE;
+ break;
+ case CLIP_DIR_PARSE_OFF:
+ break;
+ case CLIP_DIR_PARSE_FAIL:
+ default:
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ break;
+ }
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ClipboardFeatureMask,
+ mask | bflags))
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ else if (option_starts_with("files-to", cur))
+ {
+ const UINT32 mask =
+ freerdp_settings_get_uint32(settings, FreeRDP_ClipboardFeatureMask) &
+ ~(CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES | CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES);
+ const PARSE_CLIP_DIR_RESULT bval = parse_clip_direciton_to_option(cur);
+ UINT32 bflags = 0;
+ switch (bval)
+ {
+ case CLIP_DIR_PARSE_ALL:
+ bflags |=
+ CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES | CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES;
+ break;
+ case CLIP_DIR_PARSE_LOCAL:
+ bflags |= CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES;
+ break;
+ case CLIP_DIR_PARSE_REMOTE:
+ bflags |= CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES;
+ break;
+ case CLIP_DIR_PARSE_OFF:
+ break;
+ case CLIP_DIR_PARSE_FAIL:
+ default:
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ break;
+ }
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ClipboardFeatureMask,
+ mask | bflags))
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ else
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ free(ptr.p);
+
+ if (rc)
+ return rc;
+ }
+ return 0;
+}
+
+static int parse_audio_mode_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ switch (val)
+ {
+ case AUDIO_MODE_REDIRECT:
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AudioPlayback, TRUE))
+ return COMMAND_LINE_ERROR;
+ break;
+
+ case AUDIO_MODE_PLAY_ON_SERVER:
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteConsoleAudio, TRUE))
+ return COMMAND_LINE_ERROR;
+ break;
+
+ case AUDIO_MODE_NONE:
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AudioPlayback, FALSE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteConsoleAudio, FALSE))
+ return COMMAND_LINE_ERROR;
+ break;
+
+ default:
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ return 0;
+}
+
+static int parse_network_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ UINT32 type = 0;
+
+ if (option_equals(arg->Value, "modem"))
+ type = CONNECTION_TYPE_MODEM;
+ else if (option_equals(arg->Value, "broadband"))
+ type = CONNECTION_TYPE_BROADBAND_HIGH;
+ else if (option_equals(arg->Value, "broadband-low"))
+ type = CONNECTION_TYPE_BROADBAND_LOW;
+ else if (option_equals(arg->Value, "broadband-high"))
+ type = CONNECTION_TYPE_BROADBAND_HIGH;
+ else if (option_equals(arg->Value, "wan"))
+ type = CONNECTION_TYPE_WAN;
+ else if (option_equals(arg->Value, "lan"))
+ type = CONNECTION_TYPE_LAN;
+ else if ((option_equals(arg->Value, "autodetect")) || (option_equals(arg->Value, "auto")) ||
+ (option_equals(arg->Value, "detect")))
+ {
+ type = CONNECTION_TYPE_AUTODETECT;
+ }
+ else
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 1, 7))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ type = (UINT32)val;
+ }
+
+ if (!freerdp_set_connection_type(settings, type))
+ return COMMAND_LINE_ERROR;
+ return 0;
+}
+
+static int parse_sec_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ size_t count = 0;
+ char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ if (count == 0)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ FreeRDP_Settings_Keys_Bool singleOptionWithoutOnOff = FreeRDP_BOOL_UNUSED;
+ for (size_t x = 0; x < count; x++)
+ {
+ const char* cur = ptr[x];
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(cur);
+ if (bval == PARSE_FAIL)
+ {
+ free(ptr);
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+
+ const BOOL val = bval != PARSE_OFF;
+ FreeRDP_Settings_Keys_Bool id = FreeRDP_BOOL_UNUSED;
+ if (option_starts_with("rdp", cur)) /* Standard RDP */
+ {
+ id = FreeRDP_RdpSecurity;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, val))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ else if (option_starts_with("tls", cur)) /* TLS */
+ id = FreeRDP_TlsSecurity;
+ else if (option_starts_with("nla", cur)) /* NLA */
+ id = FreeRDP_NlaSecurity;
+ else if (option_starts_with("ext", cur)) /* NLA Extended */
+ id = FreeRDP_ExtSecurity;
+ else if (option_equals("aad", cur)) /* RDSAAD */
+ id = FreeRDP_AadSecurity;
+ else
+ {
+ WLog_ERR(TAG, "unknown protocol security: %s", arg->Value);
+ free(ptr);
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+
+ if ((bval == PARSE_NONE) && (count == 1))
+ singleOptionWithoutOnOff = id;
+ if (!freerdp_settings_set_bool(settings, id, val))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+
+ if (singleOptionWithoutOnOff != FreeRDP_BOOL_UNUSED)
+ {
+ const FreeRDP_Settings_Keys_Bool options[] = { FreeRDP_AadSecurity,
+ FreeRDP_UseRdpSecurityLayer,
+ FreeRDP_RdpSecurity, FreeRDP_NlaSecurity,
+ FreeRDP_TlsSecurity };
+
+ for (size_t i = 0; i < ARRAYSIZE(options); i++)
+ {
+ if (!freerdp_settings_set_bool(settings, options[i], FALSE))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+
+ if (!freerdp_settings_set_bool(settings, singleOptionWithoutOnOff, TRUE))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ if (singleOptionWithoutOnOff == FreeRDP_RdpSecurity)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, TRUE))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ }
+ free(ptr);
+ return 0;
+}
+
+static int parse_encryption_methods_options(rdpSettings* settings,
+ const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ union
+ {
+ char** p;
+ const char** pc;
+ } ptr;
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+
+ UINT32 EncryptionMethods = 0;
+ for (UINT32 i = 0; i < count; i++)
+ {
+ if (option_equals(ptr.pc[i], "40"))
+ EncryptionMethods |= ENCRYPTION_METHOD_40BIT;
+ else if (option_equals(ptr.pc[i], "56"))
+ EncryptionMethods |= ENCRYPTION_METHOD_56BIT;
+ else if (option_equals(ptr.pc[i], "128"))
+ EncryptionMethods |= ENCRYPTION_METHOD_128BIT;
+ else if (option_equals(ptr.pc[i], "FIPS"))
+ EncryptionMethods |= ENCRYPTION_METHOD_FIPS;
+ else
+ WLog_ERR(TAG, "unknown encryption method '%s'", ptr.pc[i]);
+ }
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_EncryptionMethods, EncryptionMethods))
+ return COMMAND_LINE_ERROR;
+ free(ptr.p);
+ }
+ return 0;
+}
+
+static int parse_cert_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ int rc = 0;
+ union
+ {
+ char** p;
+ const char** pc;
+ } ptr;
+ size_t count = 0;
+ ptr.p = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ for (size_t x = 0; (x < count) && (rc == 0); x++)
+ {
+ const char deny[] = "deny";
+ const char ignore[] = "ignore";
+ const char tofu[] = "tofu";
+ const char name[] = "name:";
+ const char fingerprints[] = "fingerprint:";
+
+ const char* cur = ptr.pc[x];
+ if (option_equals(deny, cur))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AutoDenyCertificate, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (option_equals(ignore, cur))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_IgnoreCertificate, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (option_equals(tofu, cur))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AutoAcceptCertificate, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with(name, cur))
+ {
+ const char* val = &cur[strnlen(name, sizeof(name))];
+ if (!freerdp_settings_set_string(settings, FreeRDP_CertificateName, val))
+ rc = COMMAND_LINE_ERROR_MEMORY;
+ }
+ else if (option_starts_with(fingerprints, cur))
+ {
+ const char* val = &cur[strnlen(fingerprints, sizeof(fingerprints))];
+ if (!freerdp_settings_append_string(settings, FreeRDP_CertificateAcceptedFingerprints,
+ ",", val))
+ rc = COMMAND_LINE_ERROR_MEMORY;
+ }
+ else
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ free(ptr.p);
+
+ return rc;
+}
+
+static int parse_mouse_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ size_t count = 0;
+ char** ptr = CommandLineParseCommaSeparatedValuesEx("mouse", arg->Value, &count);
+ UINT rc = 0;
+ if (ptr)
+ {
+ for (size_t x = 1; x < count; x++)
+ {
+ const char* cur = ptr[x];
+
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(cur);
+ if (bval == PARSE_FAIL)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else
+ {
+ const BOOL val = bval != PARSE_OFF;
+
+ if (option_starts_with("relative", cur))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_MouseUseRelativeMove, val))
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ else if (option_starts_with("grab", cur))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GrabMouse, val))
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ }
+
+ if (rc != 0)
+ break;
+ }
+ }
+ free(ptr);
+
+ return rc;
+}
+
+static int parse_floatbar_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ /* Defaults are enabled, visible, sticky, fullscreen */
+ UINT32 Floatbar = 0x0017;
+
+ if (arg->Value)
+ {
+ char* start = arg->Value;
+
+ do
+ {
+ char* cur = start;
+ start = strchr(start, ',');
+
+ if (start)
+ {
+ *start = '\0';
+ start = start + 1;
+ }
+
+ /* sticky:[on|off] */
+ if (option_starts_with("sticky:", cur))
+ {
+ Floatbar &= ~0x02u;
+
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(cur);
+ switch (bval)
+ {
+ case PARSE_ON:
+ case PARSE_NONE:
+ Floatbar |= 0x02u;
+ break;
+ case PARSE_OFF:
+ Floatbar &= ~0x02u;
+ break;
+ case PARSE_FAIL:
+ default:
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ }
+ /* default:[visible|hidden] */
+ else if (option_starts_with("default:", cur))
+ {
+ const char* val = cur + 8;
+ Floatbar &= ~0x04u;
+
+ if (option_equals("visible", val))
+ Floatbar |= 0x04u;
+ else if (option_equals("hidden", val))
+ Floatbar &= ~0x04u;
+ else
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ /* show:[always|fullscreen|window] */
+ else if (option_starts_with("show:", cur))
+ {
+ const char* val = cur + 5;
+ Floatbar &= ~0x30u;
+
+ if (option_equals("always", val))
+ Floatbar |= 0x30u;
+ else if (option_equals("fullscreen", val))
+ Floatbar |= 0x10u;
+ else if (option_equals("window", val))
+ Floatbar |= 0x20u;
+ else
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ else
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ } while (start);
+ }
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_Floatbar, Floatbar))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ return 0;
+}
+
+static int parse_reconnect_cookie_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ BYTE* base64 = NULL;
+ size_t length = 0;
+ if (!arg->Value)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ crypto_base64_decode((const char*)(arg->Value), strlen(arg->Value), &base64, &length);
+
+ if ((base64 != NULL) && (length == sizeof(ARC_SC_PRIVATE_PACKET)))
+ {
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_ServerAutoReconnectCookie, base64,
+ 1))
+ return COMMAND_LINE_ERROR;
+ }
+ else
+ {
+ WLog_ERR(TAG, "reconnect-cookie: invalid base64 '%s'", arg->Value);
+ }
+
+ free(base64);
+ return 0;
+}
+
+static int parse_scale_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 100, 180))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ switch (val)
+ {
+ case 100:
+ case 140:
+ case 180:
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopScaleFactor, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DeviceScaleFactor, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ break;
+
+ default:
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ return 0;
+}
+
+static int parse_scale_device_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 100, 180))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ switch (val)
+ {
+ case 100:
+ case 140:
+ case 180:
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DeviceScaleFactor, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ break;
+
+ default:
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ return 0;
+}
+
+static int parse_smartcard_logon_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ size_t count = 0;
+ union
+ {
+ char** p;
+ const char** pc;
+ } ptr;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SmartcardLogon, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ ptr.p = CommandLineParseCommaSeparatedValuesEx("smartcard-logon", arg->Value, &count);
+ if (ptr.pc)
+ {
+ const CmdLineSubOptions opts[] = {
+ { "cert:", FreeRDP_SmartcardCertificate, CMDLINE_SUBOPTION_FILE,
+ setSmartcardEmulation },
+ { "key:", FreeRDP_SmartcardPrivateKey, CMDLINE_SUBOPTION_FILE, setSmartcardEmulation },
+ { "pin:", FreeRDP_Password, CMDLINE_SUBOPTION_STRING, NULL },
+ { "csp:", FreeRDP_CspName, CMDLINE_SUBOPTION_STRING, NULL },
+ { "reader:", FreeRDP_ReaderName, CMDLINE_SUBOPTION_STRING, NULL },
+ { "card:", FreeRDP_CardName, CMDLINE_SUBOPTION_STRING, NULL },
+ { "container:", FreeRDP_ContainerName, CMDLINE_SUBOPTION_STRING, NULL }
+ };
+
+ for (size_t x = 1; x < count; x++)
+ {
+ const char* cur = ptr.pc[x];
+ if (!parseSubOptions(settings, opts, ARRAYSIZE(opts), cur))
+ {
+ free(ptr.p);
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ }
+ }
+ free(ptr.p);
+ return 0;
+}
+
+static int parse_tune_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ size_t count = 0;
+ union
+ {
+ char** p;
+ const char** pc;
+ } ptr;
+ ptr.p = CommandLineParseCommaSeparatedValuesEx("tune", arg->Value, &count);
+ if (!ptr.pc)
+ return COMMAND_LINE_ERROR;
+ for (size_t x = 1; x < count; x++)
+ {
+ const char* cur = ptr.pc[x];
+ char* sep = strchr(cur, ':');
+ if (!sep)
+ {
+ free(ptr.p);
+ return COMMAND_LINE_ERROR;
+ }
+ *sep++ = '\0';
+ if (!freerdp_settings_set_value_for_name(settings, cur, sep))
+ {
+ free(ptr.p);
+ return COMMAND_LINE_ERROR;
+ }
+ }
+
+ free(ptr.p);
+ return 0;
+}
+
+static int parse_app_option_program(rdpSettings* settings, const char* cmd)
+{
+ const FreeRDP_Settings_Keys_Bool ids[] = { FreeRDP_RemoteApplicationMode,
+ FreeRDP_RemoteAppLanguageBarSupported,
+ FreeRDP_Workarea, FreeRDP_DisableWallpaper,
+ FreeRDP_DisableFullWindowDrag };
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationProgram, cmd))
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ for (size_t y = 0; y < ARRAYSIZE(ids); y++)
+ {
+ if (!freerdp_settings_set_bool(settings, ids[y], TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ return CHANNEL_RC_OK;
+}
+
+static int parse_app_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ int rc = CHANNEL_RC_OK;
+ size_t count = 0;
+ char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ if (!ptr || (count == 0))
+ rc = COMMAND_LINE_ERROR;
+ else
+ {
+ struct app_map
+ {
+ const char* name;
+ FreeRDP_Settings_Keys_String id;
+ int (*fkt)(rdpSettings* settings, const char* value);
+ };
+ const struct app_map amap[] = {
+ { "program:", FreeRDP_RemoteApplicationProgram, parse_app_option_program },
+ { "workdir:", FreeRDP_RemoteApplicationWorkingDir, NULL },
+ { "name:", FreeRDP_RemoteApplicationName, NULL },
+ { "icon:", FreeRDP_RemoteApplicationIcon, NULL },
+ { "cmd:", FreeRDP_RemoteApplicationCmdLine, NULL },
+ { "file:", FreeRDP_RemoteApplicationFile, NULL },
+ { "guid:", FreeRDP_RemoteApplicationGuid, NULL },
+ };
+ for (size_t x = 0; x < count; x++)
+ {
+ BOOL handled = FALSE;
+ const char* val = ptr[x];
+
+ for (size_t y = 0; y < ARRAYSIZE(amap); y++)
+ {
+ const struct app_map* cur = &amap[y];
+ if (option_starts_with(cur->name, val))
+ {
+ const char* xval = &val[strlen(cur->name)];
+ if (cur->fkt)
+ rc = cur->fkt(settings, xval);
+ else if (!freerdp_settings_set_string(settings, cur->id, xval))
+ rc = COMMAND_LINE_ERROR_MEMORY;
+
+ handled = TRUE;
+ break;
+ }
+ }
+
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ if (!handled && (count == 1))
+ {
+ /* Legacy path, allow /app:command and /app:||command syntax */
+ rc = parse_app_option_program(settings, val);
+ }
+ else
+#endif
+ if (!handled)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (rc != 0)
+ break;
+ }
+ }
+
+ free(ptr);
+ return rc;
+}
+
+static int parse_cache_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ int rc = CHANNEL_RC_OK;
+ size_t count = 0;
+ char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ if (!ptr || (count == 0))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ for (size_t x = 0; x < count; x++)
+ {
+ const char* val = ptr[x];
+
+ if (option_starts_with("codec:", val))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCacheV3Enabled, TRUE))
+ rc = COMMAND_LINE_ERROR;
+ else if (option_equals(arg->Value, "rfx"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE))
+ rc = COMMAND_LINE_ERROR;
+ }
+ else if (option_equals(arg->Value, "nsc"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NSCodec, TRUE))
+ rc = COMMAND_LINE_ERROR;
+ }
+
+#if defined(WITH_JPEG)
+ else if (option_equals(arg->Value, "jpeg"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_JpegCodec, TRUE))
+ rc = COMMAND_LINE_ERROR;
+
+ if (freerdp_settings_get_uint32(settings, FreeRDP_JpegQuality) == 0)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_JpegQuality, 75))
+ return COMMAND_LINE_ERROR;
+ }
+ }
+
+#endif
+ }
+ else if (option_starts_with("persist-file:", val))
+ {
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_BitmapCachePersistFile, &val[13]))
+ rc = COMMAND_LINE_ERROR_MEMORY;
+ else if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCachePersistEnabled, TRUE))
+ rc = COMMAND_LINE_ERROR;
+ }
+ else
+ {
+ const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val);
+ if (bval == PARSE_FAIL)
+ rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ else
+ {
+ if (option_starts_with("bitmap", val))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCacheEnabled,
+ bval != PARSE_OFF))
+ rc = COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with("glyph", val))
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_GlyphSupportLevel,
+ bval != PARSE_OFF ? GLYPH_SUPPORT_FULL
+ : GLYPH_SUPPORT_NONE))
+ rc = COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with("persist", val))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCachePersistEnabled,
+ bval != PARSE_OFF))
+ rc = COMMAND_LINE_ERROR;
+ }
+ else if (option_starts_with("offscreen", val))
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_OffscreenSupportLevel,
+ bval != PARSE_OFF))
+ rc = COMMAND_LINE_ERROR;
+ }
+ }
+ }
+ }
+
+ free(ptr);
+ return rc;
+}
+
+static BOOL parse_gateway_host_option(rdpSettings* settings, const char* host)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(host);
+
+ char* name = NULL;
+ int port = -1;
+ if (!freerdp_parse_hostname(host, &name, &port))
+ return FALSE;
+ const BOOL rc = freerdp_settings_set_string(settings, FreeRDP_GatewayHostname, name);
+ free(name);
+ if (!rc)
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayUseSameCredentials, TRUE))
+ return FALSE;
+ if (!freerdp_set_gateway_usage_method(settings, TSC_PROXY_MODE_DIRECT))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL parse_gateway_cred_option(rdpSettings* settings, const char* value,
+ FreeRDP_Settings_Keys_String what)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(value);
+
+ switch (what)
+ {
+ case FreeRDP_GatewayUsername:
+ if (!freerdp_parse_username_settings(value, settings, FreeRDP_GatewayUsername,
+ FreeRDP_GatewayDomain))
+ return FALSE;
+ break;
+ default:
+ if (!freerdp_settings_set_string(settings, what, value))
+ return FALSE;
+ break;
+ }
+
+ return freerdp_settings_set_bool(settings, FreeRDP_GatewayUseSameCredentials, FALSE);
+}
+
+static BOOL parse_gateway_type_option(rdpSettings* settings, const char* value)
+{
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(value);
+
+ if (option_equals(value, "rpc"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayRpcTransport, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpTransport, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpUseWebsockets, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayArmTransport, FALSE))
+ return FALSE;
+ }
+ else
+ {
+ if (option_equals(value, "http"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayRpcTransport, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpTransport, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayArmTransport, FALSE))
+ return FALSE;
+ }
+ else if (option_equals(value, "auto"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayRpcTransport, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpTransport, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayArmTransport, FALSE))
+ return FALSE;
+ }
+ else if (option_equals(value, "arm"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayRpcTransport, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpTransport, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpUseWebsockets, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_GatewayArmTransport, TRUE))
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static BOOL parse_gateway_usage_option(rdpSettings* settings, const char* value)
+{
+ UINT32 type = 0;
+
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(value);
+
+ if (option_equals(value, "none"))
+ type = TSC_PROXY_MODE_NONE_DIRECT;
+ else if (option_equals(value, "direct"))
+ type = TSC_PROXY_MODE_DIRECT;
+ else if (option_equals(value, "detect"))
+ type = TSC_PROXY_MODE_DETECT;
+ else if (option_equals(value, "default"))
+ type = TSC_PROXY_MODE_DEFAULT;
+ else
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(value, &val, TSC_PROXY_MODE_NONE_DIRECT, TSC_PROXY_MODE_NONE_DETECT))
+ return FALSE;
+ }
+
+ return freerdp_set_gateway_usage_method(settings, type);
+}
+
+static BOOL parse_gateway_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg)
+{
+ BOOL rc = FALSE;
+
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(arg);
+
+ size_t count = 0;
+ char** args = CommandLineParseCommaSeparatedValues(arg->Value, &count);
+ if (count == 0)
+ return TRUE;
+ WINPR_ASSERT(args);
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayEnabled, TRUE))
+ goto fail;
+
+ BOOL allowHttpOpts = FALSE;
+ for (size_t x = 0; x < count; x++)
+ {
+ BOOL validOption = FALSE;
+ const char* argval = args[x];
+
+ WINPR_ASSERT(argval);
+
+ const char* gw = option_starts_with("g:", argval);
+ if (gw)
+ {
+ if (!parse_gateway_host_option(settings, gw))
+ goto fail;
+ validOption = TRUE;
+ allowHttpOpts = FALSE;
+ }
+
+ const char* gu = option_starts_with("u:", argval);
+ if (gu)
+ {
+ if (!parse_gateway_cred_option(settings, gu, FreeRDP_GatewayUsername))
+ goto fail;
+ validOption = TRUE;
+ allowHttpOpts = FALSE;
+ }
+
+ const char* gd = option_starts_with("d:", argval);
+ if (gd)
+ {
+ if (!parse_gateway_cred_option(settings, gd, FreeRDP_GatewayDomain))
+ goto fail;
+ validOption = TRUE;
+ allowHttpOpts = FALSE;
+ }
+
+ const char* gp = option_starts_with("p:", argval);
+ if (gp)
+ {
+ if (!parse_gateway_cred_option(settings, gp, FreeRDP_GatewayPassword))
+ goto fail;
+ validOption = TRUE;
+ allowHttpOpts = FALSE;
+ }
+
+ const char* gt = option_starts_with("type:", argval);
+ if (gt)
+ {
+ if (!parse_gateway_type_option(settings, gt))
+ goto fail;
+ validOption = TRUE;
+ allowHttpOpts = freerdp_settings_get_bool(settings, FreeRDP_GatewayHttpTransport);
+ }
+
+ const char* gat = option_starts_with("access-token:", argval);
+ if (gat)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAccessToken, gat))
+ goto fail;
+ validOption = TRUE;
+ allowHttpOpts = FALSE;
+ }
+
+ const char* bearer = option_starts_with("bearer:", argval);
+ if (bearer)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayHttpExtAuthBearer, bearer))
+ goto fail;
+ validOption = TRUE;
+ allowHttpOpts = FALSE;
+ }
+
+ const char* gwurl = option_starts_with("url:", argval);
+ if (gwurl)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayUrl, gwurl))
+ goto fail;
+ if (!freerdp_set_gateway_usage_method(settings, TSC_PROXY_MODE_DIRECT))
+ goto fail;
+ validOption = TRUE;
+ allowHttpOpts = FALSE;
+ }
+
+ const char* um = option_starts_with("usage-method:", argval);
+ if (um)
+ {
+ if (!parse_gateway_usage_option(settings, um))
+ goto fail;
+ validOption = TRUE;
+ allowHttpOpts = FALSE;
+ }
+
+ if (allowHttpOpts)
+ {
+ if (option_equals(argval, "no-websockets"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpUseWebsockets, FALSE))
+ goto fail;
+ validOption = TRUE;
+ }
+ else if (option_equals(argval, "extauth-sspi-ntlm"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpExtAuthSspiNtlm, TRUE))
+ goto fail;
+ validOption = TRUE;
+ }
+ }
+
+ if (!validOption)
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ free(args);
+ return rc;
+}
+
+static void fill_credential_string(COMMAND_LINE_ARGUMENT_A* args, const char* value)
+{
+ WINPR_ASSERT(args);
+ WINPR_ASSERT(value);
+
+ const COMMAND_LINE_ARGUMENT_A* arg = CommandLineFindArgumentA(args, value);
+ if (!arg)
+ return;
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ FillMemory(arg->Value, strlen(arg->Value), '*');
+}
+
+static void fill_credential_strings(COMMAND_LINE_ARGUMENT_A* args)
+{
+ const char* credentials[] = {
+ "p",
+ "smartcard-logon",
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ "gp",
+ "gat",
+#endif
+ "pth",
+ "reconnect-cookie",
+ "assistance"
+ };
+
+ for (size_t x = 0; x < ARRAYSIZE(credentials); x++)
+ {
+ const char* cred = credentials[x];
+ fill_credential_string(args, cred);
+ }
+
+ const COMMAND_LINE_ARGUMENT_A* arg = CommandLineFindArgumentA(args, "gateway");
+ if (arg && ((arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT) != 0))
+ {
+ const char* gwcreds[] = { "p:", "access-token:" };
+ char* tok = strtok(arg->Value, ",");
+ while (tok)
+ {
+ for (size_t x = 0; x < ARRAYSIZE(gwcreds); x++)
+ {
+ const char* opt = gwcreds[x];
+ if (option_starts_with(opt, tok))
+ {
+ char* val = &tok[strlen(opt)];
+ FillMemory(val, strlen(val), '*');
+ }
+ }
+ tok = strtok(NULL, ",");
+ }
+ }
+}
+
+static int freerdp_client_settings_parse_command_line_arguments_int(
+ rdpSettings* settings, int argc, char* argv[], BOOL allowUnknown,
+ COMMAND_LINE_ARGUMENT_A* largs, size_t count,
+ int (*handle_option)(const COMMAND_LINE_ARGUMENT* arg, void* custom), void* handle_userdata)
+{
+ char* user = NULL;
+ int status = 0;
+ BOOL ext = FALSE;
+ BOOL assist = FALSE;
+ DWORD flags = 0;
+ BOOL promptForPassword = FALSE;
+ BOOL compatibility = FALSE;
+ const COMMAND_LINE_ARGUMENT_A* arg = NULL;
+
+ /* Command line detection fails if only a .rdp or .msrcIncident file
+ * is supplied. Check this case first, only then try to detect
+ * legacy command line syntax. */
+ if (argc > 1)
+ {
+ ext = option_is_rdp_file(argv[1]);
+ assist = option_is_incident_file(argv[1]);
+ }
+
+ if (!ext && !assist)
+ compatibility = freerdp_client_detect_command_line(argc, argv, &flags);
+ else
+ compatibility = freerdp_client_detect_command_line(argc - 1, &argv[1], &flags);
+
+ freerdp_settings_set_string(settings, FreeRDP_ProxyHostname, NULL);
+ freerdp_settings_set_string(settings, FreeRDP_ProxyUsername, NULL);
+ freerdp_settings_set_string(settings, FreeRDP_ProxyPassword, NULL);
+
+ if (compatibility)
+ {
+ WLog_WARN(TAG, "Unsupported command line syntax!");
+ WLog_WARN(TAG, "FreeRDP 1.0 style syntax was dropped with version 3!");
+ return -1;
+ }
+ else
+ {
+ if (allowUnknown)
+ flags |= COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
+
+ if (ext)
+ {
+ if (freerdp_client_settings_parse_connection_file(settings, argv[1]))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+
+ if (assist)
+ {
+ if (freerdp_client_settings_parse_assistance_file(settings, argc, argv) < 0)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+
+ CommandLineClearArgumentsA(largs);
+ status = CommandLineParseArgumentsA(argc, argv, largs, flags, settings,
+ freerdp_client_command_line_pre_filter,
+ freerdp_client_command_line_post_filter);
+
+ if (status < 0)
+ return status;
+
+ prepare_default_settings(settings, largs, ext);
+ }
+
+ CommandLineFindArgumentA(largs, "v");
+ arg = largs;
+ errno = 0;
+
+ /* Disable unicode input unless requested. */
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UnicodeInput, FALSE))
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ do
+ {
+ BOOL enable = arg->Value ? TRUE : FALSE;
+
+ if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
+ continue;
+
+ CommandLineSwitchStart(arg)
+
+ CommandLineSwitchCase(arg, "v")
+ {
+ const int rc = parse_host_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "spn-class")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_AuthenticationServiceClass,
+ arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "sspi-module")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_SspiModule, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "winscard-module")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_WinSCardModule, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "redirect-prefer")
+ {
+ const int rc = parse_redirect_prefer_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "credentials-delegation")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableCredentialsDelegation, !enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "prevent-session-lock")
+ {
+ const int rc = parse_prevent_session_lock_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "vmconnect")
+ {
+ const int rc = parse_vmconnect_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "w")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, -1, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "h")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, -1, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "size")
+ {
+ const int rc = parse_size_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "f")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_Fullscreen, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "suppress-output")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SuppressOutput, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "multimon")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UseMultimon, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ if (option_equals(arg->Value, "force"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ForceMultimon, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ }
+ }
+ CommandLineSwitchCase(arg, "span")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SpanMonitors, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "workarea")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_Workarea, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "monitors")
+ {
+ const int rc = parse_monitors_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "t")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_WindowTitle, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "decorations")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_Decorations, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "dynamic-resolution")
+ {
+ const int rc = parse_dynamic_resolution_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "smart-sizing")
+ {
+ const int rc = parse_smart_sizing_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "bpp")
+ {
+ const int rc = parse_bpp_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "admin")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ConsoleSession, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "relax-order-checks")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AllowUnanouncedOrdersFromServer,
+ enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "restricted-admin")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ConsoleSession, enable))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RestrictedAdminModeRequired, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "pth")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ConsoleSession, TRUE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RestrictedAdminModeRequired, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_PasswordHash, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "client-hostname")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_ClientHostname, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "kbd")
+ {
+ int rc = parse_kbd_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ CommandLineSwitchCase(arg, "kbd-remap")
+ {
+ WLog_WARN(TAG, "/kbd-remap:<key>=<value>,<key2>=<value2> is deprecated, use "
+ "/kbd:remap:<key>=<value>,remap:<key2>=<value2>,... instead");
+ if (!freerdp_settings_set_string(settings, FreeRDP_KeyboardRemappingList, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "kbd-lang")
+ {
+ LONGLONG val = 0;
+
+ WLog_WARN(TAG, "/kbd-lang:<value> is deprecated, use /kbd:lang:<value> instead");
+ if (!value_to_int(arg->Value, &val, 1, UINT32_MAX))
+ {
+ WLog_ERR(TAG, "Could not identify keyboard active language %s", arg->Value);
+ WLog_ERR(TAG, "Use /list:kbd-lang to list available layouts");
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardCodePage, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "kbd-type")
+ {
+ LONGLONG val = 0;
+
+ WLog_WARN(TAG, "/kbd-type:<value> is deprecated, use /kbd:type:<value> instead");
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardType, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "kbd-unicode")
+ {
+ WLog_WARN(TAG, "/kbd-unicode is deprecated, use /kbd:unicode[:on|off] instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UnicodeInput, enable))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "kbd-subtype")
+ {
+ LONGLONG val = 0;
+
+ WLog_WARN(TAG, "/kbd-subtype:<value> is deprecated, use /kbd:subtype:<value> instead");
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardSubType, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "kbd-fn-key")
+ {
+ LONGLONG val = 0;
+
+ WLog_WARN(TAG, "/kbd-fn-key:<value> is deprecated, use /kbd:fn-key:<value> instead");
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardFunctionKey, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+#endif
+ CommandLineSwitchCase(arg, "u")
+ {
+ WINPR_ASSERT(arg->Value);
+ user = arg->Value;
+ }
+ CommandLineSwitchCase(arg, "d")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_Domain, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "p")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_Password, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "gateway")
+ {
+ if (!parse_gateway_options(settings, arg))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "proxy")
+ {
+ const int rc = parse_proxy_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ CommandLineSwitchCase(arg, "g")
+ {
+ if (!parse_gateway_host_option(settings, arg->Value))
+ return FALSE;
+ }
+ CommandLineSwitchCase(arg, "gu")
+ {
+ if (!parse_gateway_cred_option(settings, arg->Value, FreeRDP_GatewayUsername))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "gd")
+ {
+ if (!parse_gateway_cred_option(settings, arg->Value, FreeRDP_GatewayDomain))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "gp")
+ {
+ if (!parse_gateway_cred_option(settings, arg->Value, FreeRDP_GatewayPassword))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "gt")
+ {
+ if (!parse_gateway_type_option(settings, arg->Value))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "gat")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAccessToken, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "gateway-usage-method")
+ {
+ if (!parse_gateway_usage_option(settings, arg->Value))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+#endif
+ CommandLineSwitchCase(arg, "app")
+ {
+ int rc = parse_app_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "load-balance-info")
+ {
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_LoadBalanceInfo, arg->Value,
+ strlen(arg->Value)))
+ return COMMAND_LINE_ERROR;
+ }
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ CommandLineSwitchCase(arg, "app-workdir")
+ {
+ WLog_WARN(
+ TAG,
+ "/app-workdir:<directory> is deprecated, use /app:workdir:<directory> instead");
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationWorkingDir,
+ arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "app-name")
+ {
+ WLog_WARN(TAG, "/app-name:<directory> is deprecated, use /app:name:<name> instead");
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationName, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "app-icon")
+ {
+ WLog_WARN(TAG, "/app-icon:<filename> is deprecated, use /app:icon:<filename> instead");
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationIcon, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "app-cmd")
+ {
+ WLog_WARN(TAG, "/app-cmd:<command> is deprecated, use /app:cmd:<command> instead");
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationCmdLine,
+ arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "app-file")
+ {
+ WLog_WARN(TAG, "/app-file:<filename> is deprecated, use /app:file:<filename> instead");
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationFile, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "app-guid")
+ {
+ WLog_WARN(TAG, "/app-guid:<guid> is deprecated, use /app:guid:<guid> instead");
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationGuid, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+#endif
+ CommandLineSwitchCase(arg, "compression")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_CompressionEnabled, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "compression-level")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_CompressionLevel, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "drives")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectDrives, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "dump")
+ {
+ const int rc = parse_dump_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "disable-output")
+ {
+ freerdp_settings_set_bool(settings, FreeRDP_DeactivateClientDecoding, enable);
+ }
+ CommandLineSwitchCase(arg, "home-drive")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectHomeDrive, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "ipv6")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_PreferIPv6OverIPv4, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "clipboard")
+ {
+ const int rc = parse_clipboard_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "server-name")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_UserSpecifiedServerName, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "shell")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_AlternateShell, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "shell-dir")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_ShellWorkingDirectory, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "audio-mode")
+ {
+ const int rc = parse_audio_mode_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "network")
+ {
+ const int rc = parse_network_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "fonts")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AllowFontSmoothing, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "wallpaper")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableWallpaper, !enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "window-drag")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableFullWindowDrag, !enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "window-position")
+ {
+ unsigned long x = 0;
+ unsigned long y = 0;
+
+ if (!arg->Value)
+ return COMMAND_LINE_ERROR_MISSING_ARGUMENT;
+
+ if (!parseSizeValue(arg->Value, &x, &y) || x > UINT16_MAX || y > UINT16_MAX)
+ {
+ WLog_ERR(TAG, "invalid window-position argument");
+ return COMMAND_LINE_ERROR_MISSING_ARGUMENT;
+ }
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopPosX, (UINT32)x))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopPosY, (UINT32)y))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "menu-anims")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableMenuAnims, !enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "themes")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableThemes, !enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "timeout")
+ {
+ ULONGLONG val = 0;
+ if (!value_to_uint(arg->Value, &val, 1, 600000))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_TcpAckTimeout, (UINT32)val))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "aero")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AllowDesktopComposition, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "gdi")
+ {
+ if (option_equals(arg->Value, "sw"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SoftwareGdi, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (option_equals(arg->Value, "hw"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SoftwareGdi, FALSE))
+ return COMMAND_LINE_ERROR;
+ }
+ else
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "gfx")
+ {
+ int rc = parse_gfx_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ CommandLineSwitchCase(arg, "gfx-thin-client")
+ {
+ WLog_WARN(TAG, "/gfx-thin-client is deprecated, use /gfx:thin-client[:on|off] instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxThinClient, enable))
+ return COMMAND_LINE_ERROR;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_GfxThinClient))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxSmallCache, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGraphicsPipeline, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "gfx-small-cache")
+ {
+ WLog_WARN(TAG, "/gfx-small-cache is deprecated, use /gfx:small-cache[:on|off] instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxSmallCache, enable))
+ return COMMAND_LINE_ERROR;
+
+ if (enable)
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGraphicsPipeline, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "gfx-progressive")
+ {
+ WLog_WARN(TAG, "/gfx-progressive is deprecated, use /gfx:progressive[:on|off] instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxProgressive, enable))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GfxThinClient, !enable))
+ return COMMAND_LINE_ERROR;
+
+ if (enable)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGraphicsPipeline, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ }
+#ifdef WITH_GFX_H264
+ CommandLineSwitchCase(arg, "gfx-h264")
+ {
+ WLog_WARN(TAG, "/gfx-h264 is deprecated, use /gfx:avc420 instead");
+ int rc = parse_gfx_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+#endif
+#endif
+ CommandLineSwitchCase(arg, "rfx")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "rfx-mode")
+ {
+ if (!arg->Value)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (option_equals(arg->Value, "video"))
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_RemoteFxCodecMode, 0x00))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (option_equals(arg->Value, "image"))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxImageCodec, TRUE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_RemoteFxCodecMode, 0x02))
+ return COMMAND_LINE_ERROR;
+ }
+ }
+ CommandLineSwitchCase(arg, "frame-ack")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_FrameAcknowledge, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "nsc")
+ {
+ freerdp_settings_set_bool(settings, FreeRDP_NSCodec, enable);
+ }
+#if defined(WITH_JPEG)
+ CommandLineSwitchCase(arg, "jpeg")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_JpegCodec, enable))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_JpegQuality, 75))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "jpeg-quality")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, 100))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_JpegQuality, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+#endif
+ CommandLineSwitchCase(arg, "nego")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NegotiateSecurityLayer, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "pcb")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SendPreconnectionPdu, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_PreconnectionBlob, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "pcid")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SendPreconnectionPdu, TRUE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_PreconnectionId, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+#ifdef _WIN32
+ CommandLineSwitchCase(arg, "connect-child-session")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_AuthenticationServiceClass,
+ "vs-debug") ||
+ !freerdp_settings_set_string(settings, FreeRDP_ServerHostname, "localhost") ||
+ !freerdp_settings_set_string(settings, FreeRDP_AuthenticationPackageList, "ntlm") ||
+ !freerdp_settings_set_bool(settings, FreeRDP_NegotiateSecurityLayer, FALSE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_VmConnectMode, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_ConnectChildSession, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, TRUE) ||
+ !freerdp_settings_set_uint32(settings, FreeRDP_AuthenticationLevel, 0))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+#endif
+ CommandLineSwitchCase(arg, "sec")
+ {
+ const int rc = parse_sec_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "encryption-methods")
+ {
+ const int rc = parse_encryption_methods_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "args-from")
+ {
+ WLog_ERR(TAG, "/args-from:%s can not be used in combination with other arguments!",
+ arg->Value);
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "from-stdin")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_CredentialsFromStdin, TRUE))
+ return COMMAND_LINE_ERROR;
+
+ if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
+ {
+ if (!arg->Value)
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ promptForPassword = (option_equals(arg->Value, "force"));
+
+ if (!promptForPassword)
+ return COMMAND_LINE_ERROR;
+ }
+ }
+ CommandLineSwitchCase(arg, "log-level")
+ {
+ wLog* root = WLog_GetRoot();
+
+ if (!WLog_SetStringLogLevel(root, arg->Value))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "log-filters")
+ {
+ if (!WLog_AddStringLogFilters(arg->Value))
+ return COMMAND_LINE_ERROR;
+ }
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ CommandLineSwitchCase(arg, "sec-rdp")
+ {
+ WLog_WARN(TAG, "/sec-rdp is deprecated, use /sec:rdp[:on|off] instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "sec-tls")
+ {
+ WLog_WARN(TAG, "/sec-tls is deprecated, use /sec:tls[:on|off] instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "sec-nla")
+ {
+ WLog_WARN(TAG, "/sec-nla is deprecated, use /sec:nla[:on|off] instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "sec-ext")
+ {
+ WLog_WARN(TAG, "/sec-ext is deprecated, use /sec:ext[:on|off] instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ExtSecurity, enable))
+ return COMMAND_LINE_ERROR;
+ }
+#endif
+ CommandLineSwitchCase(arg, "tls")
+ {
+ int rc = parse_tls_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ CommandLineSwitchCase(arg, "tls-ciphers")
+ {
+ WLog_WARN(TAG, "/tls-ciphers:<cipher list> is deprecated, use "
+ "/tls:ciphers:<cipher list> instead");
+ int rc = parse_tls_cipher_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "tls-seclevel")
+ {
+ WLog_WARN(TAG,
+ "/tls-seclevel:<level> is deprecated, use /tls:sec-level:<level> instead");
+ int rc = parse_tls_cipher_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "tls-secrets-file")
+ {
+ WLog_WARN(TAG, "/tls-secrets-file:<filename> is deprecated, use "
+ "/tls:secrets-file:<filename> instead");
+ int rc = parse_tls_cipher_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "enforce-tlsv1_2")
+ {
+ WLog_WARN(TAG, "/enforce-tlsv1_2 is deprecated, use /tls:enforce:1.2 instead");
+ int rc = parse_tls_cipher_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+#endif
+ CommandLineSwitchCase(arg, "cert")
+ {
+ const int rc = parse_cert_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ CommandLineSwitchCase(arg, "cert-name")
+ {
+ WLog_WARN(TAG, "/cert-name is deprecated, use /cert:name instead");
+ if (!freerdp_settings_set_string(settings, FreeRDP_CertificateName, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "cert-ignore")
+ {
+ WLog_WARN(TAG, "/cert-ignore is deprecated, use /cert:ignore instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_IgnoreCertificate, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "cert-tofu")
+ {
+ WLog_WARN(TAG, "/cert-tofu is deprecated, use /cert:tofu instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AutoAcceptCertificate, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "cert-deny")
+ {
+ WLog_WARN(TAG, "/cert-deny is deprecated, use /cert:deny instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AutoDenyCertificate, enable))
+ return COMMAND_LINE_ERROR;
+ }
+#endif
+ CommandLineSwitchCase(arg, "authentication")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_Authentication, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "encryption")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, !enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "grab-keyboard")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GrabKeyboard, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "grab-mouse")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GrabMouse, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "mouse-relative")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_MouseUseRelativeMove, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "mouse")
+ {
+ const int rc = parse_mouse_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "unmap-buttons")
+ {
+ freerdp_settings_set_bool(settings, FreeRDP_UnmapButtons, enable);
+ }
+ CommandLineSwitchCase(arg, "toggle-fullscreen")
+ {
+ freerdp_settings_set_bool(settings, FreeRDP_ToggleFullscreen, enable);
+ }
+ CommandLineSwitchCase(arg, "force-console-callbacks")
+ {
+ freerdp_settings_set_bool(settings, FreeRDP_UseCommonStdioCallbacks, enable);
+ }
+ CommandLineSwitchCase(arg, "floatbar")
+ {
+ const int rc = parse_floatbar_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "mouse-motion")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_MouseMotion, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "parent-window")
+ {
+ ULONGLONG val = 0;
+
+ if (!value_to_uint(arg->Value, &val, 0, UINT64_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint64(settings, FreeRDP_ParentWindowId, (UINT64)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "client-build-number")
+ {
+ ULONGLONG val = 0;
+
+ if (!value_to_uint(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ClientBuild, (UINT32)val))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "cache")
+ {
+ int rc = parse_cache_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ CommandLineSwitchCase(arg, "bitmap-cache")
+ {
+ WLog_WARN(TAG, "/bitmap-cache is deprecated, use /cache:bitmap[:on|off] instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCacheEnabled, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "persist-cache")
+ {
+ WLog_WARN(TAG, "/persist-cache is deprecated, use /cache:persist[:on|off] instead");
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCachePersistEnabled, enable))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "persist-cache-file")
+ {
+ WLog_WARN(TAG, "/persist-cache-file:<filename> is deprecated, use "
+ "/cache:persist-file:<filename> instead");
+ if (!freerdp_settings_set_string(settings, FreeRDP_BitmapCachePersistFile, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCachePersistEnabled, TRUE))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+ }
+ CommandLineSwitchCase(arg, "offscreen-cache")
+ {
+ WLog_WARN(TAG, "/bitmap-cache is deprecated, use /cache:bitmap[:on|off] instead");
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_OffscreenSupportLevel,
+ (UINT32)enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "glyph-cache")
+ {
+ WLog_WARN(TAG, "/glyph-cache is deprecated, use /cache:glyph[:on|off] instead");
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_GlyphSupportLevel,
+ arg->Value ? GLYPH_SUPPORT_FULL : GLYPH_SUPPORT_NONE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "codec-cache")
+ {
+ WLog_WARN(TAG,
+ "/codec-cache:<option> is deprecated, use /cache:codec:<option> instead");
+ const int rc = parse_codec_cache_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+#endif
+ CommandLineSwitchCase(arg, "max-fast-path-size")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_MultifragMaxRequestSize,
+ (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "auto-request-control")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteAssistanceRequestControl,
+ enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "async-update")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AsyncUpdate, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "async-channels")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AsyncChannels, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "wm-class")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_WmClass, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "play-rfx")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_PlayRemoteFxFile, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_PlayRemoteFx, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "auth-only")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AuthenticationOnly, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "auth-pkg-list")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_AuthenticationPackageList,
+ arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "auto-reconnect")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AutoReconnectionEnabled, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "auto-reconnect-max-retries")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, 1000))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_AutoReconnectMaxRetries,
+ (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "reconnect-cookie")
+ {
+ const int rc = parse_reconnect_cookie_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "print-reconnect-cookie")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_PrintReconnectCookie, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "pwidth")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopPhysicalWidth, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "pheight")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, UINT32_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopPhysicalHeight, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "orientation")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 0, UINT16_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint16(settings, FreeRDP_DesktopOrientation, (UINT16)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "old-license")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_OldLicenseBehaviour, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "scale")
+ {
+ const int rc = parse_scale_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "scale-desktop")
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 100, 500))
+ return FALSE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopScaleFactor, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "scale-device")
+ {
+ const int rc = parse_scale_device_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "action-script")
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_ActionScript, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, RDP2TCP_DVC_CHANNEL_NAME)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_RDP2TCPArgs, arg->Value))
+ return COMMAND_LINE_ERROR_MEMORY;
+ }
+ CommandLineSwitchCase(arg, "fipsmode")
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_FIPSMode, enable))
+ return COMMAND_LINE_ERROR;
+ }
+ CommandLineSwitchCase(arg, "smartcard-logon")
+ {
+ const int rc = parse_smartcard_logon_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchCase(arg, "tune")
+ {
+ const int rc = parse_tune_options(settings, arg);
+ if (rc != 0)
+ return rc;
+ }
+ CommandLineSwitchDefault(arg)
+ {
+ if (handle_option)
+ {
+ const int rc = handle_option(arg, handle_userdata);
+ if (rc != 0)
+ return rc;
+ }
+ }
+ CommandLineSwitchEnd(arg)
+ } while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);
+
+ if (user)
+ {
+ if (!freerdp_settings_get_string(settings, FreeRDP_Domain) && user)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_Username, NULL))
+ return COMMAND_LINE_ERROR;
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_Domain, NULL))
+ return COMMAND_LINE_ERROR;
+
+ if (!freerdp_parse_username_settings(user, settings, FreeRDP_Username, FreeRDP_Domain))
+ return COMMAND_LINE_ERROR;
+ }
+ else
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_Username, user))
+ return COMMAND_LINE_ERROR;
+ }
+ }
+
+ if (promptForPassword)
+ {
+ freerdp* instance = freerdp_settings_get_pointer_writable(settings, FreeRDP_instance);
+ if (!freerdp_settings_get_string(settings, FreeRDP_Password))
+ {
+ char buffer[512 + 1] = { 0 };
+
+ if (!freerdp_passphrase_read(instance->context, "Password: ", buffer,
+ ARRAYSIZE(buffer) - 1, 1))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_string(settings, FreeRDP_Password, buffer))
+ return COMMAND_LINE_ERROR;
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_GatewayEnabled) &&
+ !freerdp_settings_get_bool(settings, FreeRDP_GatewayUseSameCredentials))
+ {
+ if (!freerdp_settings_get_string(settings, FreeRDP_GatewayPassword))
+ {
+ char buffer[512 + 1] = { 0 };
+
+ if (!freerdp_passphrase_read(instance->context, "Gateway Password: ", buffer,
+ ARRAYSIZE(buffer) - 1, 1))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayPassword, buffer))
+ return COMMAND_LINE_ERROR;
+ }
+ }
+ }
+
+ freerdp_performance_flags_make(settings);
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_RemoteFxCodec) ||
+ freerdp_settings_get_bool(settings, FreeRDP_NSCodec) ||
+ freerdp_settings_get_bool(settings, FreeRDP_SupportGraphicsPipeline))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_FastPathOutput, TRUE))
+ return COMMAND_LINE_ERROR;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_FrameMarkerCommandEnabled, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+
+ arg = CommandLineFindArgumentA(largs, "port");
+ if (arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT)
+ {
+ LONGLONG val = 0;
+
+ if (!value_to_int(arg->Value, &val, 1, UINT16_MAX))
+ return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, (UINT32)val))
+ return COMMAND_LINE_ERROR;
+ }
+
+ fill_credential_strings(largs);
+
+ return status;
+}
+
+static void argv_free(int* pargc, char** pargv[])
+{
+ WINPR_ASSERT(pargc);
+ WINPR_ASSERT(pargv);
+ const int argc = *pargc;
+ char** argv = *pargv;
+ *pargc = 0;
+ *pargv = NULL;
+
+ if (!argv)
+ return;
+ for (int x = 0; x < argc; x++)
+ free(argv[x]);
+ free(argv);
+}
+
+static BOOL argv_append(int* pargc, char** pargv[], const char* what)
+{
+ WINPR_ASSERT(pargc);
+ WINPR_ASSERT(pargv);
+
+ if (*pargc < 0)
+ return FALSE;
+
+ if (!what)
+ return FALSE;
+
+ int nargc = *pargc + 1;
+ char** tmp = realloc(*pargv, nargc * sizeof(char*));
+ if (!tmp)
+ return FALSE;
+
+ tmp[*pargc] = what;
+ *pargv = tmp;
+ *pargc = nargc;
+ return TRUE;
+}
+
+static BOOL argv_append_dup(int* pargc, char** pargv[], const char* what)
+{
+ char* copy = NULL;
+ if (what)
+ copy = _strdup(what);
+
+ const BOOL rc = argv_append(pargc, pargv, copy);
+ if (!rc)
+ free(copy);
+ return rc;
+}
+
+static BOOL args_from_fp(FILE* fp, int* aargc, char** aargv[], const char* file, const char* cmd)
+{
+ BOOL success = FALSE;
+
+ WINPR_ASSERT(aargc);
+ WINPR_ASSERT(aargv);
+ WINPR_ASSERT(cmd);
+
+ if (!fp)
+ {
+ WLog_ERR(TAG, "Failed to read command line options from file '%s'", file);
+ return FALSE;
+ }
+ if (!argv_append_dup(aargc, aargv, cmd))
+ goto fail;
+ while (!feof(fp))
+ {
+ char* line = NULL;
+ size_t size = 0;
+ INT64 rc = GetLine(&line, &size, fp);
+ if ((rc < 0) || !line)
+ {
+ /* abort if GetLine failed due to reaching EOF */
+ if (feof(fp))
+ break;
+ goto fail;
+ }
+
+ while (rc > 0)
+ {
+ const char cur = (line[rc - 1]);
+ if ((cur == '\n') || (cur == '\r'))
+ {
+ line[rc - 1] = '\0';
+ rc--;
+ }
+ else
+ break;
+ }
+ /* abort on empty lines */
+ if (rc == 0)
+ {
+ free(line);
+ break;
+ }
+ if (!argv_append(aargc, aargv, line))
+ {
+ free(line);
+ goto fail;
+ }
+ }
+
+ success = TRUE;
+fail:
+ fclose(fp);
+ if (!success)
+ argv_free(aargc, aargv);
+ return success;
+}
+
+static BOOL args_from_env(const char* name, int* aargc, char** aargv[], const char* arg,
+ const char* cmd)
+{
+ BOOL success = FALSE;
+ char* env = NULL;
+
+ WINPR_ASSERT(aargc);
+ WINPR_ASSERT(aargv);
+ WINPR_ASSERT(cmd);
+
+ if (!name)
+ {
+ WLog_ERR(TAG, "%s - environment variable name empty", arg);
+ goto cleanup;
+ }
+
+ const DWORD size = GetEnvironmentVariableX(name, env, 0);
+ if (size == 0)
+ {
+ WLog_ERR(TAG, "%s - no environment variable '%s'", arg, name);
+ goto cleanup;
+ }
+ env = calloc(size + 1, sizeof(char));
+ if (!env)
+ goto cleanup;
+ const DWORD rc = GetEnvironmentVariableX(name, env, size);
+ if (rc != size - 1)
+ goto cleanup;
+ if (rc == 0)
+ {
+ WLog_ERR(TAG, "%s - environment variable '%s' is empty", arg);
+ goto cleanup;
+ }
+
+ if (!argv_append_dup(aargc, aargv, cmd))
+ goto cleanup;
+
+ char* context = NULL;
+ char* tok = strtok_s(env, "\n", &context);
+ while (tok)
+ {
+ if (!argv_append_dup(aargc, aargv, tok))
+ goto cleanup;
+ tok = strtok_s(NULL, "\n", &context);
+ }
+
+ success = TRUE;
+cleanup:
+ free(env);
+ if (!success)
+ argv_free(aargc, aargv);
+ return success;
+}
+
+int freerdp_client_settings_parse_command_line_arguments(rdpSettings* settings, int oargc,
+ char* oargv[], BOOL allowUnknown)
+{
+ return freerdp_client_settings_parse_command_line_arguments_ex(
+ settings, oargc, oargv, allowUnknown, NULL, 0, NULL, NULL);
+}
+
+int freerdp_client_settings_parse_command_line_arguments_ex(
+ rdpSettings* settings, int oargc, char** oargv, BOOL allowUnknown,
+ COMMAND_LINE_ARGUMENT_A* args, size_t count,
+ int (*handle_option)(const COMMAND_LINE_ARGUMENT* arg, void* custom), void* handle_userdata)
+{
+ int argc = oargc;
+ char** argv = oargv;
+ int res = -1;
+ int aargc = 0;
+ char** aargv = NULL;
+ if ((argc == 2) && option_starts_with("/args-from:", argv[1]))
+ {
+ BOOL success = FALSE;
+ const char* file = strchr(argv[1], ':') + 1;
+ FILE* fp = stdin;
+
+ if (option_starts_with("fd:", file))
+ {
+ ULONGLONG result = 0;
+ const char* val = strchr(file, ':') + 1;
+ if (!value_to_uint(val, &result, 0, INT_MAX))
+ return -1;
+ fp = fdopen((int)result, "r");
+ success = args_from_fp(fp, &aargc, &aargv, file, oargv[0]);
+ }
+ else if (strncmp(file, "env:", 4) == 0)
+ {
+ const char* name = strchr(file, ':') + 1;
+ success = args_from_env(name, &aargc, &aargv, oargv[1], oargv[0]);
+ }
+ else if (strcmp(file, "stdin") != 0)
+ {
+ fp = winpr_fopen(file, "r");
+ success = args_from_fp(fp, &aargc, &aargv, file, oargv[0]);
+ }
+ else
+ success = args_from_fp(fp, &aargc, &aargv, file, oargv[0]);
+
+ if (!success)
+ return -1;
+ argc = aargc;
+ argv = aargv;
+ }
+
+ size_t lcount = 0;
+ COMMAND_LINE_ARGUMENT_A* largs = create_merged_args(args, count, &lcount);
+ if (!largs)
+ goto fail;
+
+ res = freerdp_client_settings_parse_command_line_arguments_int(
+ settings, argc, argv, allowUnknown, largs, lcount, handle_option, handle_userdata);
+fail:
+ free(largs);
+ argv_free(&aargc, &aargv);
+ return res;
+}
+
+static BOOL freerdp_client_load_static_channel_addin(rdpChannels* channels, rdpSettings* settings,
+ const char* name, void* data)
+{
+ PVIRTUALCHANNELENTRY entry = NULL;
+ PVIRTUALCHANNELENTRYEX entryEx = NULL;
+ entryEx = (PVIRTUALCHANNELENTRYEX)(void*)freerdp_load_channel_addin_entry(
+ name, NULL, NULL, FREERDP_ADDIN_CHANNEL_STATIC | FREERDP_ADDIN_CHANNEL_ENTRYEX);
+
+ if (!entryEx)
+ entry = freerdp_load_channel_addin_entry(name, NULL, NULL, FREERDP_ADDIN_CHANNEL_STATIC);
+
+ if (entryEx)
+ {
+ if (freerdp_channels_client_load_ex(channels, settings, entryEx, data) == 0)
+ {
+ WLog_DBG(TAG, "loading channelEx %s", name);
+ return TRUE;
+ }
+ }
+ else if (entry)
+ {
+ if (freerdp_channels_client_load(channels, settings, entry, data) == 0)
+ {
+ WLog_DBG(TAG, "loading channel %s", name);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+typedef struct
+{
+ FreeRDP_Settings_Keys_Bool settingId;
+ const char* channelName;
+ void* args;
+} ChannelToLoad;
+
+BOOL freerdp_client_load_addins(rdpChannels* channels, rdpSettings* settings)
+{
+ ChannelToLoad dynChannels[] = {
+#if defined(CHANNEL_AINPUT_CLIENT)
+ { FreeRDP_BOOL_UNUSED, AINPUT_CHANNEL_NAME, NULL }, /* always loaded */
+#endif
+ { FreeRDP_AudioCapture, AUDIN_CHANNEL_NAME, NULL },
+ { FreeRDP_AudioPlayback, RDPSND_CHANNEL_NAME, NULL },
+#ifdef CHANNEL_RDPEI_CLIENT
+ { FreeRDP_MultiTouchInput, RDPEI_CHANNEL_NAME, NULL },
+#endif
+ { FreeRDP_SupportGraphicsPipeline, RDPGFX_CHANNEL_NAME, NULL },
+ { FreeRDP_SupportEchoChannel, ECHO_CHANNEL_NAME, NULL },
+ { FreeRDP_SupportSSHAgentChannel, "sshagent", NULL },
+ { FreeRDP_SupportDisplayControl, DISP_CHANNEL_NAME, NULL },
+ { FreeRDP_SupportGeometryTracking, GEOMETRY_CHANNEL_NAME, NULL },
+ { FreeRDP_SupportVideoOptimized, VIDEO_CHANNEL_NAME, NULL },
+ };
+
+ ChannelToLoad staticChannels[] = {
+ { FreeRDP_AudioPlayback, RDPSND_CHANNEL_NAME, NULL },
+ { FreeRDP_RedirectClipboard, CLIPRDR_SVC_CHANNEL_NAME, NULL },
+#if defined(CHANNEL_ENCOMSP_CLIENT)
+ { FreeRDP_EncomspVirtualChannel, ENCOMSP_SVC_CHANNEL_NAME, settings },
+#endif
+ { FreeRDP_RemdeskVirtualChannel, REMDESK_SVC_CHANNEL_NAME, settings },
+ { FreeRDP_RemoteApplicationMode, RAIL_SVC_CHANNEL_NAME, settings }
+ };
+
+ /**
+ * Step 1: first load dynamic channels according to the settings
+ */
+ for (size_t i = 0; i < ARRAYSIZE(dynChannels); i++)
+ {
+ if ((dynChannels[i].settingId == FreeRDP_BOOL_UNUSED) ||
+ freerdp_settings_get_bool(settings, dynChannels[i].settingId))
+ {
+ const char* p[] = { dynChannels[i].channelName };
+
+ if (!freerdp_client_add_dynamic_channel(settings, ARRAYSIZE(p), p))
+ return FALSE;
+ }
+ }
+
+ /**
+ * step 2: do various adjustements in the settings, to handle channels and settings dependencies
+ */
+ if ((freerdp_static_channel_collection_find(settings, RDPSND_CHANNEL_NAME)) ||
+ (freerdp_dynamic_channel_collection_find(settings, RDPSND_CHANNEL_NAME))
+#if defined(CHANNEL_TSMF_CLIENT)
+ || (freerdp_dynamic_channel_collection_find(settings, "tsmf"))
+#endif
+ )
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return COMMAND_LINE_ERROR; /* rdpsnd requires rdpdr to be registered */
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AudioPlayback, TRUE))
+ return COMMAND_LINE_ERROR; /* Both rdpsnd and tsmf require this flag to be set */
+ }
+
+ if (freerdp_dynamic_channel_collection_find(settings, AUDIN_CHANNEL_NAME))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AudioCapture, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_NetworkAutoDetect) ||
+ freerdp_settings_get_bool(settings, FreeRDP_SupportHeartbeatPdu) ||
+ freerdp_settings_get_bool(settings, FreeRDP_SupportMultitransport))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return COMMAND_LINE_ERROR; /* these RDP8 features require rdpdr to be registered */
+ }
+
+ const char* DrivesToRedirect = freerdp_settings_get_string(settings, FreeRDP_DrivesToRedirect);
+
+ if (DrivesToRedirect && (strlen(DrivesToRedirect) != 0))
+ {
+ /*
+ * Drives to redirect:
+ *
+ * Very similar to DevicesToRedirect, but can contain a
+ * comma-separated list of drive letters to redirect.
+ */
+ char* value = NULL;
+ char* tok = NULL;
+ char* context = NULL;
+
+ value = _strdup(DrivesToRedirect);
+ if (!value)
+ return FALSE;
+
+ tok = strtok_s(value, ";", &context);
+ if (!tok)
+ {
+ WLog_ERR(TAG, "DrivesToRedirect contains invalid data: '%s'", DrivesToRedirect);
+ free(value);
+ return FALSE;
+ }
+
+ while (tok)
+ {
+ /* Syntax: Comma seperated list of the following entries:
+ * '*' ... Redirect all drives, including hotplug
+ * 'DynamicDrives' ... hotplug
+ * '%' ... user home directory
+ * <label>(<path>) ... One or more paths to redirect.
+ * <path>(<label>) ... One or more paths to redirect.
+ * <path> ... One or more paths to redirect.
+ */
+ /* TODO: Need to properly escape labels and paths */
+ BOOL success = 0;
+ const char* name = NULL;
+ const char* drive = tok;
+ char* subcontext = NULL;
+ char* start = strtok_s(tok, "(", &subcontext);
+ char* end = strtok_s(NULL, ")", &subcontext);
+ if (start && end)
+ name = end;
+
+ if (freerdp_path_valid(name, NULL) && freerdp_path_valid(drive, NULL))
+ {
+ success = freerdp_client_add_drive(settings, name, NULL);
+ if (success)
+ success = freerdp_client_add_drive(settings, drive, NULL);
+ }
+ else
+ success = freerdp_client_add_drive(settings, drive, name);
+
+ if (!success)
+ {
+ free(value);
+ return FALSE;
+ }
+
+ tok = strtok_s(NULL, ";", &context);
+ }
+ free(value);
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return FALSE;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return COMMAND_LINE_ERROR;
+ }
+ else if (freerdp_settings_get_bool(settings, FreeRDP_RedirectDrives))
+ {
+ if (!freerdp_device_collection_find(settings, "drive"))
+ {
+ const char* params[] = { "drive", "media", "*" };
+
+ if (!freerdp_client_add_device_channel(settings, ARRAYSIZE(params), params))
+ return FALSE;
+ }
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_RedirectDrives) ||
+ freerdp_settings_get_bool(settings, FreeRDP_RedirectHomeDrive) ||
+ freerdp_settings_get_bool(settings, FreeRDP_RedirectSerialPorts) ||
+ freerdp_settings_get_bool(settings, FreeRDP_RedirectSmartCards) ||
+ freerdp_settings_get_bool(settings, FreeRDP_RedirectPrinters))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return COMMAND_LINE_ERROR; /* All of these features require rdpdr */
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_RedirectHomeDrive))
+ {
+ if (!freerdp_device_collection_find(settings, "drive"))
+ {
+ const char* params[] = { "drive", "home", "%" };
+
+ if (!freerdp_client_add_device_channel(settings, ARRAYSIZE(params), params))
+ return FALSE;
+ }
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_DeviceRedirection))
+ {
+ if (!freerdp_client_load_static_channel_addin(channels, settings, RDPDR_SVC_CHANNEL_NAME,
+ settings))
+ return FALSE;
+
+ if (!freerdp_static_channel_collection_find(settings, RDPSND_CHANNEL_NAME) &&
+ !freerdp_dynamic_channel_collection_find(settings, RDPSND_CHANNEL_NAME))
+ {
+ const char* params[] = { RDPSND_CHANNEL_NAME, "sys:fake" };
+
+ if (!freerdp_client_add_static_channel(settings, ARRAYSIZE(params), params))
+ return FALSE;
+
+ if (!freerdp_client_add_dynamic_channel(settings, ARRAYSIZE(params), params))
+ return FALSE;
+ }
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_RedirectSmartCards))
+ {
+ if (!freerdp_device_collection_find_type(settings, RDPDR_DTYP_SMARTCARD))
+ {
+ RDPDR_DEVICE* smartcard = freerdp_device_new(RDPDR_DTYP_SMARTCARD, 0, NULL);
+
+ if (!smartcard)
+ return FALSE;
+
+ if (!freerdp_device_collection_add(settings, smartcard))
+ {
+ freerdp_device_free(smartcard);
+ return FALSE;
+ }
+ }
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_RedirectPrinters))
+ {
+ if (!freerdp_device_collection_find_type(settings, RDPDR_DTYP_PRINT))
+ {
+ RDPDR_DEVICE* printer = freerdp_device_new(RDPDR_DTYP_PRINT, 0, NULL);
+
+ if (!printer)
+ return FALSE;
+
+ if (!freerdp_device_collection_add(settings, printer))
+ {
+ freerdp_device_free(printer);
+ return FALSE;
+ }
+ }
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_LyncRdpMode))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_EncomspVirtualChannel, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemdeskVirtualChannel, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_CompressionEnabled, FALSE))
+ return FALSE;
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_RemoteAssistanceMode))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_EncomspVirtualChannel, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemdeskVirtualChannel, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE))
+ return FALSE;
+ }
+
+ /* step 3: schedule some static channels to load depending on the settings */
+ for (size_t i = 0; i < ARRAYSIZE(staticChannels); i++)
+ {
+ if ((staticChannels[i].settingId == 0) ||
+ freerdp_settings_get_bool(settings, staticChannels[i].settingId))
+ {
+ if (staticChannels[i].args)
+ {
+ if (!freerdp_client_load_static_channel_addin(
+ channels, settings, staticChannels[i].channelName, staticChannels[i].args))
+ return FALSE;
+ }
+ else
+ {
+ const char* p[] = { staticChannels[i].channelName };
+ if (!freerdp_client_add_static_channel(settings, ARRAYSIZE(p), p))
+ return FALSE;
+ }
+ }
+ }
+
+ char* RDP2TCPArgs = freerdp_settings_get_string_writable(settings, FreeRDP_RDP2TCPArgs);
+ if (RDP2TCPArgs)
+ {
+ if (!freerdp_client_load_static_channel_addin(channels, settings, RDP2TCP_DVC_CHANNEL_NAME,
+ RDP2TCPArgs))
+ return FALSE;
+ }
+
+ /* step 4: do the static channels loading and init */
+ for (UINT32 i = 0; i < freerdp_settings_get_uint32(settings, FreeRDP_StaticChannelCount); i++)
+ {
+ ADDIN_ARGV* _args =
+ freerdp_settings_get_pointer_array_writable(settings, FreeRDP_StaticChannelArray, i);
+
+ if (!freerdp_client_load_static_channel_addin(channels, settings, _args->argv[0], _args))
+ return FALSE;
+ }
+
+ if (freerdp_settings_get_uint32(settings, FreeRDP_DynamicChannelCount) > 0)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportDynamicChannels, TRUE))
+ return FALSE;
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_SupportDynamicChannels))
+ {
+ if (!freerdp_client_load_static_channel_addin(channels, settings, DRDYNVC_SVC_CHANNEL_NAME,
+ settings))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void freerdp_client_warn_unmaintained(int argc, char* argv[])
+{
+ const char* app = (argc > 0) ? argv[0] : "INVALID_ARGV";
+ const DWORD log_level = WLOG_WARN;
+ wLog* log = WLog_Get(TAG);
+ WINPR_ASSERT(log);
+
+ if (!WLog_IsLevelActive(log, log_level))
+ return;
+
+ WLog_Print_unchecked(log, log_level, "[unmaintained] %s client is currently unmaintained!",
+ app);
+ WLog_Print_unchecked(
+ log, log_level,
+ " If problems occur please check https://github.com/FreeRDP/FreeRDP/issues for "
+ "known issues!");
+ WLog_Print_unchecked(
+ log, log_level,
+ "Be prepared to fix issues yourself though as nobody is actively working on this.");
+ WLog_Print_unchecked(
+ log, log_level,
+ " Developers hang out in https://matrix.to/#/#FreeRDP:matrix.org?via=matrix.org "
+ "- dont hesitate to ask some questions. (replies might take some time depending "
+ "on your timezone) - if you intend using this component write us a message");
+}
+
+void freerdp_client_warn_experimental(int argc, char* argv[])
+{
+ const char* app = (argc > 0) ? argv[0] : "INVALID_ARGV";
+ const DWORD log_level = WLOG_WARN;
+ wLog* log = WLog_Get(TAG);
+ WINPR_ASSERT(log);
+
+ if (!WLog_IsLevelActive(log, log_level))
+ return;
+
+ WLog_Print_unchecked(log, log_level, "[experimental] %s client is currently experimental!",
+ app);
+ WLog_Print_unchecked(
+ log, log_level,
+ " If problems occur please check https://github.com/FreeRDP/FreeRDP/issues for "
+ "known issues or create a new one!");
+ WLog_Print_unchecked(
+ log, log_level,
+ " Developers hang out in https://matrix.to/#/#FreeRDP:matrix.org?via=matrix.org "
+ "- dont hesitate to ask some questions. (replies might take some time depending "
+ "on your timezone)");
+}
+
+void freerdp_client_warn_deprecated(int argc, char* argv[])
+{
+ const char* app = (argc > 0) ? argv[0] : "INVALID_ARGV";
+ const DWORD log_level = WLOG_WARN;
+ wLog* log = WLog_Get(TAG);
+ WINPR_ASSERT(log);
+
+ if (!WLog_IsLevelActive(log, log_level))
+ return;
+
+ WLog_Print_unchecked(log, log_level, "[deprecated] %s client has been deprecated", app);
+ WLog_Print_unchecked(log, log_level, "As replacement there is a SDL based client available.");
+ WLog_Print_unchecked(
+ log, log_level,
+ "If you are interested in keeping %s alive get in touch with the developers", app);
+ WLog_Print_unchecked(
+ log, log_level,
+ "The project is hosted at https://github.com/freerdp/freerdp and "
+ " developers hang out in https://matrix.to/#/#FreeRDP:matrix.org?via=matrix.org "
+ "- dont hesitate to ask some questions. (replies might take some time depending "
+ "on your timezone)");
+}
diff --git a/client/common/cmdline.h b/client/common/cmdline.h
new file mode 100644
index 0000000..8186cc6
--- /dev/null
+++ b/client/common/cmdline.h
@@ -0,0 +1,519 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Client Command-Line Interface
+ *
+ * Copyright 2018 Bernhard Miklautz <bernhard.miklautz@thincast.com>
+ * Copyright 2018 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef CLIENT_COMMON_CMDLINE_H
+#define CLIENT_COMMON_CMDLINE_H
+
+#include <freerdp/config.h>
+
+#include <winpr/cmdline.h>
+
+static const COMMAND_LINE_ARGUMENT_A global_cmd_args[] = {
+ { "a", COMMAND_LINE_VALUE_REQUIRED, "<addin>[,<options>]", NULL, NULL, -1, "addin", "Addin" },
+ { "action-script", COMMAND_LINE_VALUE_REQUIRED, "<file-name>", "~/.config/freerdp/action.sh",
+ NULL, -1, NULL, "Action script" },
+ { "admin", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, "console",
+ "Admin (or console) session" },
+ { "aero", COMMAND_LINE_VALUE_BOOL, NULL, NULL, BoolValueFalse, -1, NULL,
+ "desktop composition" },
+ { "app", COMMAND_LINE_VALUE_REQUIRED,
+ "program:[<path>|<||alias>],cmd:<command>,file:<filename>,guid:<guid>,icon:<filename>,name:<"
+ "name>,workdir:<directory>",
+ NULL, NULL, -1, NULL, "Remote application program" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "app-cmd", COMMAND_LINE_VALUE_REQUIRED, "<parameters>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /app:cmd:<command>] Remote application command-line parameters" },
+ { "app-file", COMMAND_LINE_VALUE_REQUIRED, "<file-name>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /app:file:<filename>] File to open with remote application" },
+ { "app-guid", COMMAND_LINE_VALUE_REQUIRED, "<app-guid>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /app:guid:<guid>] Remote application GUID" },
+ { "app-icon", COMMAND_LINE_VALUE_REQUIRED, "<icon-path>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /app:icon:<filename>] Remote application icon for user interface" },
+ { "app-name", COMMAND_LINE_VALUE_REQUIRED, "<app-name>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /app:name:<name>] Remote application name for user interface" },
+ { "app-workdir", COMMAND_LINE_VALUE_REQUIRED, "<workspace path>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /app:workdir:<directory>] Remote application workspace path" },
+#endif
+ { "assistance", COMMAND_LINE_VALUE_REQUIRED, "<password>", NULL, NULL, -1, NULL,
+ "Remote assistance password" },
+ { "auto-request-control", COMMAND_LINE_VALUE_FLAG, "", NULL, NULL, -1, NULL,
+ "Automatically request remote assistance input control" },
+ { "async-channels", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Asynchronous channels (experimental)" },
+ { "async-update", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Asynchronous update" },
+ { "audio-mode", COMMAND_LINE_VALUE_REQUIRED, "<mode>", NULL, NULL, -1, NULL,
+ "Audio output mode" },
+ { "auth-only", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Authenticate only" },
+ { "auth-pkg-list", COMMAND_LINE_VALUE_REQUIRED, "<!ntlm,kerberos>", NULL, NULL, -1, NULL,
+ "Authentication package filter (comma-separated list, use '!' to exclude)" },
+ { "authentication", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Authentication (experimental)" },
+ { "auto-reconnect", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Automatic reconnection" },
+ { "auto-reconnect-max-retries", COMMAND_LINE_VALUE_REQUIRED, "<retries>", NULL, NULL, -1, NULL,
+ "Automatic reconnection maximum retries, 0 for unlimited [0,1000]" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "bitmap-cache", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "[DEPRECATED, use /cache:bitmap[:on|off]] bitmap cache" },
+ { "persist-cache", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "[DEPRECATED, use /cache:persist[:on|off]] persistent bitmap cache" },
+ { "persist-cache-file", COMMAND_LINE_VALUE_REQUIRED, "<filename>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /cache:persist-file:<filename>] persistent bitmap cache file" },
+#endif
+ { "bpp", COMMAND_LINE_VALUE_REQUIRED, "<depth>", "16", NULL, -1, NULL,
+ "Session bpp (color depth)" },
+ { "buildconfig", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT_BUILDCONFIG, NULL, NULL, NULL, -1,
+ NULL, "Print the build configuration" },
+ { "cache", COMMAND_LINE_VALUE_REQUIRED,
+ "[bitmap[:on|off],codec[:rfx|nsc],glyph[:on|off],offscreen[:on|off],persist,persist-file:<"
+ "filename>]",
+ NULL, NULL, -1, NULL, "" },
+ { "cert", COMMAND_LINE_VALUE_REQUIRED,
+ "[deny,ignore,name:<name>,tofu,fingerprint:<hash>:<hash as hex>[,fingerprint:<hash>:<another "
+ "hash>]]",
+ NULL, NULL, -1, NULL,
+ "Certificate accept options. Use with care!\n"
+ " * deny ... Automatically abort connection if the certificate does not match, no "
+ "user interaction.\n"
+ " * ignore ... Ignore the certificate checks altogether (overrules all other options)\n"
+ " * name ... Use the alternate <name> instead of the certificate subject to match "
+ "locally stored certificates\n"
+ " * tofu ... Accept certificate unconditionally on first connect and deny on "
+ "subsequent connections if the certificate does not match\n"
+ " * fingerprints ... A list of certificate hashes that are accepted unconditionally for a "
+ "connection" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "cert-deny", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /cert:deny] Automatically abort connection for any certificate that can "
+ "not be validated." },
+ { "cert-ignore", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /cert:ignore] Ignore certificate" },
+ { "cert-name", COMMAND_LINE_VALUE_REQUIRED, "<name>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /cert:name:<name>] Certificate name" },
+ { "cert-tofu", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /cert:tofu] Automatically accept certificate on first connect" },
+#endif
+#ifdef _WIN32
+ { "connect-child-session", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, "",
+ "connect to child session (win32)" },
+#endif
+ { "client-build-number", COMMAND_LINE_VALUE_REQUIRED, "<number>", NULL, NULL, -1, NULL,
+ "Client Build Number sent to server (influences smartcard behaviour, see [MS-RDPESC])" },
+ { "client-hostname", COMMAND_LINE_VALUE_REQUIRED, "<name>", NULL, NULL, -1, NULL,
+ "Client Hostname to send to server" },
+ { "clipboard", COMMAND_LINE_VALUE_BOOL | COMMAND_LINE_VALUE_OPTIONAL,
+ "[[use-selection:<atom>],[direction-to:[all|local|remote|off]],[files-to[:all|local|remote|"
+ "off]]]",
+ BoolValueTrue, NULL, -1, NULL,
+ "Redirect clipboard:\n"
+ " * use-selection:<atom> ... (X11) Specify which X selection to access. Default is "
+ "CLIPBOARD. PRIMARY is the X-style middle-click selection.\n"
+ " * direction-to:[all|local|remote|off] control enabled clipboard direction\n"
+ " * files-to:[all|local|remote|off] control enabled file clipboard directiont" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "codec-cache", COMMAND_LINE_VALUE_REQUIRED, "[rfx|nsc|jpeg]", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /cache:codec:[rfx|nsc|jpeg]] Bitmap codec cache" },
+#endif
+ { "compression", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, "z", "compression" },
+ { "compression-level", COMMAND_LINE_VALUE_REQUIRED, "<level>", NULL, NULL, -1, NULL,
+ "Compression level (0,1,2)" },
+ { "credentials-delegation", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "credentials delegation" },
+ { "d", COMMAND_LINE_VALUE_REQUIRED, "<domain>", NULL, NULL, -1, NULL, "Domain" },
+ { "decorations", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Window decorations" },
+ { "disp", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL, "Display control" },
+ { "drive", COMMAND_LINE_VALUE_REQUIRED, "<name>,<path>", NULL, NULL, -1, NULL,
+ "Redirect directory <path> as named share <name>. Hotplug support is enabled with "
+ "/drive:hotplug,*. This argument provides the same function as \"Drives that I plug in "
+ "later\" option in MSTSC." },
+ { "drives", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Redirect all mount points as shares" },
+ { "dump", COMMAND_LINE_VALUE_REQUIRED, "<record|replay>,<file>", NULL, NULL, -1, NULL,
+ "record or replay dump" },
+ { "dvc", COMMAND_LINE_VALUE_REQUIRED, "<channel>[,<options>]", NULL, NULL, -1, NULL,
+ "Dynamic virtual channel" },
+ { "dynamic-resolution", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "Send resolution updates when the window is resized" },
+ { "echo", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, "echo", "Echo channel" },
+ { "encryption", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Encryption (experimental)" },
+ { "encryption-methods", COMMAND_LINE_VALUE_REQUIRED, "[40,][56,][128,][FIPS]", NULL, NULL, -1,
+ NULL, "RDP standard security encryption methods" },
+ { "f", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "Fullscreen mode (<Ctrl>+<Alt>+<Enter> toggles fullscreen)" },
+ { "fipsmode", COMMAND_LINE_VALUE_BOOL, NULL, NULL, NULL, -1, NULL, "FIPS mode" },
+ { "floatbar", COMMAND_LINE_VALUE_OPTIONAL,
+ "sticky:[on|off],default:[visible|hidden],show:[always|fullscreen|window]", NULL, NULL, -1,
+ NULL,
+ "floatbar is disabled by default (when enabled defaults to sticky in fullscreen mode)" },
+ { "fonts", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "smooth fonts (ClearType)" },
+ { "force-console-callbacks", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Use default callbacks (console) for certificate/credential/..." },
+ { "frame-ack", COMMAND_LINE_VALUE_REQUIRED, "<number>", NULL, NULL, -1, NULL,
+ "Number of frame acknowledgement" },
+ { "args-from", COMMAND_LINE_VALUE_REQUIRED, "<file>|stdin|fd:<number>|env:<name>", NULL, NULL,
+ -1, NULL,
+ "Read command line from a file, stdin or file descriptor. This argument can not be combined "
+ "with any other. "
+ "Provide one argument per line." },
+ { "from-stdin", COMMAND_LINE_VALUE_OPTIONAL, "force", NULL, NULL, -1, NULL,
+ "Read credentials from stdin. With <force> the prompt is done before connection, otherwise "
+ "on server request." },
+ { "gateway", COMMAND_LINE_VALUE_REQUIRED,
+ "g:<gateway>[:<port>],u:<user>,d:<domain>,p:<password>,usage-method:["
+ "direct|detect],access-token:<"
+ "token>,type:[rpc|http[,no-websockets][,extauth-sspi-ntlm]|auto[,no-websockets][,extauth-"
+ "sspi-ntlm]]|arm,url:<wss://url>,bearer:<oauth2-bearer-token>",
+ NULL, NULL, -1, "gw", "Gateway Hostname" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "g", COMMAND_LINE_VALUE_REQUIRED, "<gateway>[:<port>]", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /gateway:g:<url>] Gateway Hostname" },
+ { "gateway-usage-method", COMMAND_LINE_VALUE_REQUIRED, "[direct|detect]", NULL, NULL, -1, "gum",
+ "[DEPRECATED, use /gateway:usage-method:<method>] Gateway usage method" },
+ { "gd", COMMAND_LINE_VALUE_REQUIRED, "<domain>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /gateway:d:<domain>] Gateway domain" },
+#endif
+ { "gdi", COMMAND_LINE_VALUE_REQUIRED, "sw|hw", NULL, NULL, -1, NULL, "GDI rendering" },
+ { "geometry", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "Geometry tracking channel" },
+ { "gestures", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Consume multitouch input locally" },
+#ifdef WITH_GFX_H264
+ { "gfx", COMMAND_LINE_VALUE_OPTIONAL,
+ "[[progressive[:on|off]|RFX[:on|off]|AVC420[:on|off]AVC444[:on|off]],mask:<value>,small-"
+ "cache[:on|off],thin-client[:on|off],progressive[:on|"
+ "off]]",
+ NULL, NULL, -1, NULL, "RDP8 graphics pipeline" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "gfx-h264", COMMAND_LINE_VALUE_OPTIONAL, "[[AVC420|AVC444],mask:<value>]", NULL, NULL, -1,
+ NULL, "[DEPRECATED, use /gfx:avc420] RDP8.1 graphics pipeline using H264 codec" },
+#endif
+#else
+ { "gfx", COMMAND_LINE_VALUE_OPTIONAL,
+ "[progressive[:on|off]|RFX[:on|off]|AVC420[:on|off]AVC444[:on|off]],mask:<value>,small-cache["
+ ":on|off],thin-client[:on|off],progressive[:on|off]]",
+ NULL, NULL, -1, NULL, "RDP8 graphics pipeline" },
+#endif
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "gfx-progressive", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "[DEPRECATED, use /gfx:progressive] RDP8 graphics pipeline using progressive codec" },
+ { "gfx-small-cache", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "[DEPRECATED, use /gfx:small-cache] RDP8 graphics pipeline using small cache mode" },
+ { "gfx-thin-client", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "[DEPRECATED, use /gfx:thin-client] RDP8 graphics pipeline using thin client mode" },
+ { "glyph-cache", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "[DEPRECATED, use /cache:glyph[:on|off]] Glyph cache (experimental)" },
+#endif
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "gp", COMMAND_LINE_VALUE_REQUIRED, "<password>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /gateway:p:<password>] Gateway password" },
+#endif
+ { "grab-keyboard", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Grab keyboard" },
+ { "grab-mouse", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL, "Grab mouse" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "gt", COMMAND_LINE_VALUE_REQUIRED,
+ "[rpc|http[,no-websockets][,extauth-sspi-ntlm]|auto[,no-websockets][,extauth-sspi-ntlm]]",
+ NULL, NULL, -1, NULL, "[DEPRECATED, use /gateway:type:<type>] Gateway transport type" },
+ { "gu", COMMAND_LINE_VALUE_REQUIRED, "[[<domain>\\]<user>|<user>[@<domain>]]", NULL, NULL, -1,
+ NULL, "[DEPRECATED, use /gateway:u:<user>] Gateway username" },
+ { "gat", COMMAND_LINE_VALUE_REQUIRED, "<access token>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /gateway:access-token:<token>] Gateway Access Token" },
+#endif
+ { "h", COMMAND_LINE_VALUE_REQUIRED, "<height>", "768", NULL, -1, NULL, "Height" },
+ { "heartbeat", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Support heartbeat PDUs" },
+ { "help", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT_HELP, NULL, NULL, NULL, -1, "?",
+ "Print help" },
+ { "home-drive", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Redirect user home as share" },
+ { "ipv6", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, "6",
+ "Prefer IPv6 AAA record over IPv4 A record" },
+#if defined(WITH_JPEG)
+ { "jpeg", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL, "JPEG codec support" },
+ { "jpeg-quality", COMMAND_LINE_VALUE_REQUIRED, "<percentage>", NULL, NULL, -1, NULL,
+ "JPEG quality" },
+#endif
+ { "kbd", COMMAND_LINE_VALUE_REQUIRED,
+ "[layout:[0x<id>|<name>],lang:<0x<id>>,fn-key:<value>,type:<value>,subtype:<value>,unicode[:"
+ "on|off],remap:<key1>=<value1>,remap:<key2>=<value2>,pipe:<filename>]",
+ NULL, NULL, -1, NULL,
+ "Keyboard related options:\n"
+ " * layout: set the keybouard layout announced to the server\n"
+ " * lang: set the keyboard language identifier sent to the server\n"
+ " * fn-key: Function key value\n"
+ " * pipe: Name of a named pipe that can be used to type text into the RDP session\n" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "kbd-lang", COMMAND_LINE_VALUE_REQUIRED, "0x<id>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use / kbd:lang:<value>] Keyboard active language identifier" },
+ { "kbd-fn-key", COMMAND_LINE_VALUE_REQUIRED, "<value>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /kbd:fn-key:<value>] Function key value" },
+ { "kbd-list", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT, NULL, NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /list:kbd] List keyboard layouts" },
+ { "kbd-scancode-list", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT, NULL, NULL, NULL, -1, NULL,
+ "[DEPRECATED, use list:kbd-scancode] List keyboard RDP scancodes" },
+ { "kbd-lang-list", COMMAND_LINE_VALUE_OPTIONAL | COMMAND_LINE_PRINT, NULL, NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /list:kbd-lang] List keyboard languages" },
+ { "kbd-remap", COMMAND_LINE_VALUE_REQUIRED,
+ "[DEPRECATED, use /kbd:remap] List of <key>=<value>,... pairs to remap scancodes", NULL, NULL,
+ -1, NULL, "Keyboard scancode remapping" },
+ { "kbd-subtype", COMMAND_LINE_VALUE_REQUIRED, "<id>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /kbd:subtype]Keyboard subtype" },
+ { "kbd-type", COMMAND_LINE_VALUE_REQUIRED, "<id>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /kbd:type] Keyboard type" },
+ { "kbd-unicode", COMMAND_LINE_VALUE_FLAG, "", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /kbd:unicode[:on|off]] Send unicode symbols, e.g. use the local "
+ "keyboard map. ATTENTION: Does not work with every "
+ "RDP server!" },
+#endif
+ { "kerberos", COMMAND_LINE_VALUE_REQUIRED,
+ "[kdc-url:<url>,lifetime:<time>,start-time:<time>,renewable-lifetime:<time>,cache:<path>,"
+ "armor:<path>,pkinit-anchors:<path>,pkcs11-module:<name>]",
+ NULL, NULL, -1, NULL, "Kerberos options" },
+ { "load-balance-info", COMMAND_LINE_VALUE_REQUIRED, "<info-string>", NULL, NULL, -1, NULL,
+ "Load balance info" },
+ { "list", COMMAND_LINE_VALUE_REQUIRED | COMMAND_LINE_PRINT,
+ "[kbd|kbd-scancode|kbd-lang[:<value>]|smartcard[:[pkinit-anchors:<path>][,pkcs11-module:<"
+ "name>]]|"
+ "monitor|tune]",
+ "List available options for subcommand", NULL, -1, NULL,
+ "List available options for subcommand" },
+ { "log-filters", COMMAND_LINE_VALUE_REQUIRED, "<tag>:<level>[,<tag>:<level>[,...]]", NULL, NULL,
+ -1, NULL, "Set logger filters, see wLog(7) for details" },
+ { "log-level", COMMAND_LINE_VALUE_REQUIRED, "[OFF|FATAL|ERROR|WARN|INFO|DEBUG|TRACE]", NULL,
+ NULL, -1, NULL, "Set the default log level, see wLog(7) for details" },
+ { "max-fast-path-size", COMMAND_LINE_VALUE_REQUIRED, "<size>", NULL, NULL, -1, NULL,
+ "Specify maximum fast-path update size" },
+ { "max-loop-time", COMMAND_LINE_VALUE_REQUIRED, "<time>", NULL, NULL, -1, NULL,
+ "Specify maximum time in milliseconds spend treating packets" },
+ { "menu-anims", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "menu animations" },
+ { "microphone", COMMAND_LINE_VALUE_OPTIONAL,
+ "[sys:<sys>,][dev:<dev>,][format:<format>,][rate:<rate>,][channel:<channel>]", NULL, NULL, -1,
+ "mic", "Audio input (microphone)" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "smartcard-list", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT, NULL, NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /list:smartcard] List smartcard informations" },
+ { "monitor-list", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT, NULL, NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /list:monitor] List detected monitors" },
+#endif
+ { "monitors", COMMAND_LINE_VALUE_REQUIRED, "<id>[,<id>[,...]]", NULL, NULL, -1, NULL,
+ "Select monitors to use" },
+ { "mouse-motion", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Send mouse motion" },
+ { "mouse-relative", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Send mouse motion with relative addressing" },
+ { "mouse", COMMAND_LINE_VALUE_REQUIRED, "[relative:[on|off],grab:[on|off]]", NULL, NULL, -1,
+ NULL,
+ "Mouse related options:\n"
+ " * relative: send relative mouse movements if supported by server\n"
+ " * grab: grab the mouse if within the window" },
+#if defined(CHANNEL_TSMF_CLIENT)
+ { "multimedia", COMMAND_LINE_VALUE_OPTIONAL, "[sys:<sys>,][dev:<dev>,][decoder:<decoder>]",
+ NULL, NULL, -1, "mmr", "[DEPRECATED], use /video] Redirect multimedia (video)" },
+#endif
+ { "multimon", COMMAND_LINE_VALUE_OPTIONAL, "force", NULL, NULL, -1, NULL,
+ "Use multiple monitors" },
+ { "multitouch", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Redirect multitouch input" },
+ { "multitransport", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Support multitransport protocol" },
+ { "nego", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "protocol security negotiation" },
+ { "network", COMMAND_LINE_VALUE_REQUIRED,
+ "[modem|broadband|broadband-low|broadband-high|wan|lan|auto]", NULL, NULL, -1, NULL,
+ "Network connection type" },
+ { "nsc", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, "nscodec", "NSCodec support" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "offscreen-cache", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "[DEPRECATED, use /cache:offscreen[:on|off]] offscreen bitmap cache" },
+#endif
+ { "orientation", COMMAND_LINE_VALUE_REQUIRED, "[0|90|180|270]", NULL, NULL, -1, NULL,
+ "Orientation of display in degrees" },
+ { "old-license", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Use the old license workflow (no CAL and hwId set to 0)" },
+ { "p", COMMAND_LINE_VALUE_REQUIRED, "<password>", NULL, NULL, -1, NULL, "Password" },
+ { "parallel", COMMAND_LINE_VALUE_OPTIONAL, "<name>[,<path>]", NULL, NULL, -1, NULL,
+ "Redirect parallel device" },
+ { "parent-window", COMMAND_LINE_VALUE_REQUIRED, "<window-id>", NULL, NULL, -1, NULL,
+ "Parent window id" },
+ { "pcb", COMMAND_LINE_VALUE_REQUIRED, "<blob>", NULL, NULL, -1, NULL, "Preconnection Blob" },
+ { "pcid", COMMAND_LINE_VALUE_REQUIRED, "<id>", NULL, NULL, -1, NULL, "Preconnection Id" },
+ { "pheight", COMMAND_LINE_VALUE_REQUIRED, "<height>", NULL, NULL, -1, NULL,
+ "Physical height of display (in millimeters)" },
+ { "play-rfx", COMMAND_LINE_VALUE_REQUIRED, "<pcap-file>", NULL, NULL, -1, NULL,
+ "Replay rfx pcap file" },
+ { "port", COMMAND_LINE_VALUE_REQUIRED, "<number>", NULL, NULL, -1, NULL, "Server port" },
+ { "suppress-output", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "suppress output when minimized" },
+ { "print-reconnect-cookie", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Print base64 reconnect cookie after connecting" },
+ { "printer", COMMAND_LINE_VALUE_OPTIONAL, "<name>[,<driver>]", NULL, NULL, -1, NULL,
+ "Redirect printer device" },
+ { "proxy", COMMAND_LINE_VALUE_REQUIRED, "[<proto>://][<user>:<password>@]<host>[:<port>]", NULL,
+ NULL, -1, NULL,
+ "Proxy settings: override env. var (see also environment variable below). Protocol "
+ "\"socks5\" should be given explicitly where \"http\" is default." },
+ { "pth", COMMAND_LINE_VALUE_REQUIRED, "<password-hash>", NULL, NULL, -1, "pass-the-hash",
+ "Pass the hash (restricted admin mode)" },
+ { "pwidth", COMMAND_LINE_VALUE_REQUIRED, "<width>", NULL, NULL, -1, NULL,
+ "Physical width of display (in millimeters)" },
+ { "rdp2tcp", COMMAND_LINE_VALUE_REQUIRED, "<executable path[:arg...]>", NULL, NULL, -1, NULL,
+ "TCP redirection" },
+ { "reconnect-cookie", COMMAND_LINE_VALUE_REQUIRED, "<base64-cookie>", NULL, NULL, -1, NULL,
+ "Pass base64 reconnect cookie to the connection" },
+ { "redirect-prefer", COMMAND_LINE_VALUE_REQUIRED, "<FQDN|IP|NETBIOS>,[...]", NULL, NULL, -1,
+ NULL, "Override the preferred redirection order" },
+ { "relax-order-checks", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, "relax-order-checks",
+ "Do not check if a RDP order was announced during capability exchange, only use when "
+ "connecting to a buggy server" },
+ { "restricted-admin", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, "restrictedAdmin",
+ "Restricted admin mode" },
+ { "rfx", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL, "RemoteFX" },
+ { "rfx-mode", COMMAND_LINE_VALUE_REQUIRED, "[image|video]", NULL, NULL, -1, NULL,
+ "RemoteFX mode" },
+ { "scale", COMMAND_LINE_VALUE_REQUIRED, "[100|140|180]", "100", NULL, -1, NULL,
+ "Scaling factor of the display" },
+ { "scale-desktop", COMMAND_LINE_VALUE_REQUIRED, "<percentage>", "100", NULL, -1, NULL,
+ "Scaling factor for desktop applications (value between 100 and 500)" },
+ { "scale-device", COMMAND_LINE_VALUE_REQUIRED, "100|140|180", "100", NULL, -1, NULL,
+ "Scaling factor for app store applications" },
+ { "sec", COMMAND_LINE_VALUE_REQUIRED,
+ "[rdp[:[on|off]]|tls[:[on|off]]|nla[:[on|off]]|ext[:[on|off]]|aad[:[on|off]]]", NULL, NULL,
+ -1, NULL,
+ "Force specific protocol security. e.g. /sec:nla enables NLA and disables all others, while "
+ "/sec:nla:[on|off] just toggles NLA" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "sec-ext", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "[DEPRECATED, use /sec:ext] NLA extended protocol security" },
+ { "sec-nla", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "[DEPRECATED, use /sec:nla] NLA protocol security" },
+ { "sec-rdp", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "[DEPRECATED, use /sec:rdp] RDP protocol security" },
+ { "sec-tls", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "[DEPRECATED, use /sec:tls] TLS protocol security" },
+#endif
+ { "serial", COMMAND_LINE_VALUE_OPTIONAL, "<name>[,<path>[,<driver>[,permissive]]]", NULL, NULL,
+ -1, "tty", "Redirect serial device" },
+ { "server-name", COMMAND_LINE_VALUE_REQUIRED, "<name>", NULL, NULL, -1, NULL,
+ "User-specified server name to use for validation (TLS, Kerberos)" },
+ { "shell", COMMAND_LINE_VALUE_REQUIRED, "<shell>", NULL, NULL, -1, NULL, "Alternate shell" },
+ { "shell-dir", COMMAND_LINE_VALUE_REQUIRED, "<dir>", NULL, NULL, -1, NULL,
+ "Shell working directory" },
+ { "size", COMMAND_LINE_VALUE_REQUIRED, "<width>x<height> or <percent>%[wh]", "1024x768", NULL,
+ -1, NULL, "Screen size" },
+ { "smart-sizing", COMMAND_LINE_VALUE_OPTIONAL, "<width>x<height>", NULL, NULL, -1, NULL,
+ "Scale remote desktop to window size" },
+ { "smartcard", COMMAND_LINE_VALUE_OPTIONAL, "<str>[,<str>...]", NULL, NULL, -1, NULL,
+ "Redirect the smartcard devices containing any of the <str> in their names." },
+ { "smartcard-logon", COMMAND_LINE_VALUE_OPTIONAL,
+ "[cert:<path>,key:<key>,pin:<pin>,csp:<csp name>,reader:<reader>,card:<card>]", NULL, NULL,
+ -1, NULL, "Activates Smartcard (optional certificate) Logon authentication." },
+ { "sound", COMMAND_LINE_VALUE_OPTIONAL,
+ "[sys:<sys>,][dev:<dev>,][format:<format>,][rate:<rate>,][channel:<channel>,][latency:<"
+ "latency>,][quality:<quality>]",
+ NULL, NULL, -1, "audio", "Audio output (sound)" },
+ { "span", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "Span screen over multiple monitors" },
+ { "spn-class", COMMAND_LINE_VALUE_REQUIRED, "<service-class>", NULL, NULL, -1, NULL,
+ "SPN authentication service class" },
+ { "ssh-agent", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, "ssh-agent",
+ "SSH Agent forwarding channel" },
+ { "sspi-module", COMMAND_LINE_VALUE_REQUIRED, "<SSPI module path>", NULL, NULL, -1, NULL,
+ "SSPI shared library module file path" },
+ { "winscard-module", COMMAND_LINE_VALUE_REQUIRED, "<WinSCard module path>", NULL, NULL, -1,
+ NULL, "WinSCard shared library module file path" },
+ { "disable-output", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "Deactivate all graphics decoding in the client session. Useful for load tests with many "
+ "simultaneous connections" },
+ { "t", COMMAND_LINE_VALUE_REQUIRED, "<title>", NULL, NULL, -1, "title", "Window title" },
+ { "themes", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL, "themes" },
+ { "timeout", COMMAND_LINE_VALUE_REQUIRED, "<time in ms>", "9000", NULL, -1, "timeout",
+ "Advanced setting for high latency links: Adjust connection timeout, use if you encounter "
+ "timeout failures with your connection" },
+ { "tls", COMMAND_LINE_VALUE_REQUIRED, "[ciphers|seclevel|secrets-file|enforce]", NULL, NULL, -1,
+ NULL,
+ "TLS configuration options:"
+ " * ciphers:[netmon|ma|<cipher names>]\n"
+ " * seclevel:<level>, default: 1, range: [0-5] Override the default TLS security level, "
+ "might be required for older target servers\n"
+ " * secrets-file:<filename>\n"
+ " * enforce[:[ssl3|1.0|1.1|1.2|1.3]] Force use of SSL/TLS version for a connection. Some "
+ "servers have a buggy TLS "
+ "version negotiation and might fail without this. Defaults to TLS 1.2 if no argument is "
+ "supplied. Use 1.0 for windows 7" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "tls-ciphers", COMMAND_LINE_VALUE_REQUIRED, "[netmon|ma|ciphers]", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /tls:ciphers] Allowed TLS ciphers" },
+ { "tls-seclevel", COMMAND_LINE_VALUE_REQUIRED, "<level>", "1", NULL, -1, NULL,
+ "[DEPRECATED, use /tls:seclevel] TLS security level - defaults to 1" },
+ { "tls-secrets-file", COMMAND_LINE_VALUE_REQUIRED, "<filename>", NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /tls:secrets:file] File were TLS secrets will be stored in the "
+ "SSLKEYLOGFILE format" },
+ { "enforce-tlsv1_2", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "[DEPRECATED, use /tls:enforce:1.2] Force use of TLS1.2 for connection. Some "
+ "servers have a buggy TLS version negotiation and "
+ "might fail without this" },
+#endif
+ { "toggle-fullscreen", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
+ "Alt+Ctrl+Enter to toggle fullscreen" },
+ { "tune", COMMAND_LINE_VALUE_REQUIRED, "<setting:value>,<setting:value>", "", NULL, -1, NULL,
+ "[experimental] directly manipulate freerdp settings, use with extreme caution!" },
+#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE)
+ { "tune-list", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT, NULL, NULL, NULL, -1, NULL,
+ "[DEPRECATED, use /list:tune] Print options allowed for /tune" },
+#endif
+ { "u", COMMAND_LINE_VALUE_REQUIRED, "[[<domain>\\]<user>|<user>[@<domain>]]", NULL, NULL, -1,
+ NULL, "Username" },
+ { "unmap-buttons", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "Let server see real physical pointer button" },
+#ifdef CHANNEL_URBDRC_CLIENT
+ { "usb", COMMAND_LINE_VALUE_REQUIRED,
+ "[dbg,][id:<vid>:<pid>#...,][addr:<bus>:<addr>#...,][auto]", NULL, NULL, -1, NULL,
+ "Redirect USB device" },
+#endif
+ { "v", COMMAND_LINE_VALUE_REQUIRED, "<server>[:port]", NULL, NULL, -1, NULL,
+ "Server hostname" },
+ { "vc", COMMAND_LINE_VALUE_REQUIRED, "<channel>[,<options>]", NULL, NULL, -1, NULL,
+ "Static virtual channel" },
+ { "version", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT_VERSION, NULL, NULL, NULL, -1, NULL,
+ "Print version" },
+ { "video", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL,
+ "Video optimized remoting channel" },
+ { "prevent-session-lock", COMMAND_LINE_VALUE_OPTIONAL, "<time in sec>", NULL, NULL, -1, NULL,
+ "Prevent session locking by injecting fake mouse motion events to the server "
+ "when the connection is idle (default interval: 180 seconds)" },
+ { "vmconnect", COMMAND_LINE_VALUE_OPTIONAL, "<vmid>", NULL, NULL, -1, NULL,
+ "Hyper-V console (use port 2179, disable negotiation)" },
+ { "w", COMMAND_LINE_VALUE_REQUIRED, "<width>", "1024", NULL, -1, NULL, "Width" },
+ { "wallpaper", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL, "wallpaper" },
+ { "window-drag", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL,
+ "full window drag" },
+ { "window-position", COMMAND_LINE_VALUE_REQUIRED, "<xpos>x<ypos>", NULL, NULL, -1, NULL,
+ "window position" },
+ { "wm-class", COMMAND_LINE_VALUE_REQUIRED, "<class-name>", NULL, NULL, -1, NULL,
+ "Set the WM_CLASS hint for the window instance" },
+ { "workarea", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL, "Use available work area" },
+ { NULL, 0, NULL, NULL, NULL, -1, NULL, NULL }
+};
+#endif /* CLIENT_COMMON_CMDLINE_H */
diff --git a/client/common/file.c b/client/common/file.c
new file mode 100644
index 0000000..760c62e
--- /dev/null
+++ b/client/common/file.c
@@ -0,0 +1,2707 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * .rdp file
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <errno.h>
+#include <ctype.h>
+#include <stdlib.h>
+
+#include <winpr/string.h>
+#include <winpr/file.h>
+
+#include <freerdp/client.h>
+#include <freerdp/client/file.h>
+#include <freerdp/client/cmdline.h>
+
+#include <freerdp/channels/urbdrc.h>
+#include <freerdp/channels/rdpecam.h>
+#include <freerdp/channels/location.h>
+
+/**
+ * Remote Desktop Plus - Overview of .rdp file settings:
+ * http://www.donkz.nl/files/rdpsettings.html
+ *
+ * RDP Settings for Remote Desktop Services in Windows Server 2008 R2:
+ * http://technet.microsoft.com/en-us/library/ff393699/
+ *
+ * https://docs.microsoft.com/en-us/windows-server/remote/remote-desktop-services/clients/rdp-files
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include <winpr/wtypes.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <freerdp/log.h>
+#define TAG CLIENT_TAG("common")
+
+/*#define DEBUG_CLIENT_FILE 1*/
+
+static const BYTE BOM_UTF16_LE[2] = { 0xFF, 0xFE };
+
+#define INVALID_INTEGER_VALUE 0xFFFFFFFF
+
+#define RDP_FILE_LINE_FLAG_FORMATTED 0x00000001
+#define RDP_FILE_LINE_FLAG_STANDARD 0x00000002
+#define RDP_FILE_LINE_FLAG_TYPE_STRING 0x00000010
+#define RDP_FILE_LINE_FLAG_TYPE_INTEGER 0x00000020
+#define RDP_FILE_LINE_FLAG_TYPE_BINARY 0x00000040
+
+struct rdp_file_line
+{
+ char* name;
+ LPSTR sValue;
+ PBYTE bValue;
+
+ size_t index;
+
+ long iValue;
+ DWORD flags;
+ int valueLength;
+};
+typedef struct rdp_file_line rdpFileLine;
+
+struct rdp_file
+{
+ DWORD UseMultiMon; /* use multimon */
+ LPSTR SelectedMonitors; /* selectedmonitors */
+ DWORD MaximizeToCurrentDisplays; /* maximizetocurrentdisplays */
+ DWORD SingleMonInWindowedMode; /* singlemoninwindowedmode */
+ DWORD ScreenModeId; /* screen mode id */
+ DWORD SpanMonitors; /* span monitors */
+ DWORD SmartSizing; /* smartsizing */
+ DWORD DynamicResolution; /* dynamic resolution */
+ DWORD EnableSuperSpan; /* enablesuperpan */
+ DWORD SuperSpanAccelerationFactor; /* superpanaccelerationfactor */
+
+ DWORD DesktopWidth; /* desktopwidth */
+ DWORD DesktopHeight; /* desktopheight */
+ DWORD DesktopSizeId; /* desktop size id */
+ DWORD SessionBpp; /* session bpp */
+ DWORD DesktopScaleFactor; /* desktopscalefactor */
+
+ DWORD Compression; /* compression */
+ DWORD KeyboardHook; /* keyboardhook */
+ DWORD DisableCtrlAltDel; /* disable ctrl+alt+del */
+
+ DWORD AudioMode; /* audiomode */
+ DWORD AudioQualityMode; /* audioqualitymode */
+ DWORD AudioCaptureMode; /* audiocapturemode */
+ DWORD EncodeRedirectedVideoCapture; /* encode redirected video capture */
+ DWORD RedirectedVideoCaptureEncodingQuality; /* redirected video capture encoding quality */
+ DWORD VideoPlaybackMode; /* videoplaybackmode */
+
+ DWORD ConnectionType; /* connection type */
+
+ DWORD NetworkAutoDetect; /* networkautodetect */
+ DWORD BandwidthAutoDetect; /* bandwidthautodetect */
+
+ DWORD PinConnectionBar; /* pinconnectionbar */
+ DWORD DisplayConnectionBar; /* displayconnectionbar */
+
+ DWORD WorkspaceId; /* workspaceid */
+ DWORD EnableWorkspaceReconnect; /* enableworkspacereconnect */
+
+ DWORD DisableWallpaper; /* disable wallpaper */
+ DWORD AllowFontSmoothing; /* allow font smoothing */
+ DWORD AllowDesktopComposition; /* allow desktop composition */
+ DWORD DisableFullWindowDrag; /* disable full window drag */
+ DWORD DisableMenuAnims; /* disable menu anims */
+ DWORD DisableThemes; /* disable themes */
+ DWORD DisableCursorSetting; /* disable cursor setting */
+
+ DWORD BitmapCacheSize; /* bitmapcachesize */
+ DWORD BitmapCachePersistEnable; /* bitmapcachepersistenable */
+
+ DWORD ServerPort; /* server port */
+
+ LPSTR Username; /* username */
+ LPSTR Domain; /* domain */
+ LPSTR Password; /*password*/
+ PBYTE Password51; /* password 51 */
+
+ LPSTR FullAddress; /* full address */
+ LPSTR AlternateFullAddress; /* alternate full address */
+
+ LPSTR UsbDevicesToRedirect; /* usbdevicestoredirect */
+ DWORD RedirectDrives; /* redirectdrives */
+ DWORD RedirectPrinters; /* redirectprinters */
+ DWORD RedirectComPorts; /* redirectcomports */
+ DWORD RedirectLocation; /* redirectlocation */
+ DWORD RedirectSmartCards; /* redirectsmartcards */
+ DWORD RedirectWebauthN; /* redirectwebauthn */
+ LPSTR RedirectCameras; /* camerastoredirect */
+ DWORD RedirectClipboard; /* redirectclipboard */
+ DWORD RedirectPosDevices; /* redirectposdevices */
+ DWORD RedirectDirectX; /* redirectdirectx */
+ DWORD DisablePrinterRedirection; /* disableprinterredirection */
+ DWORD DisableClipboardRedirection; /* disableclipboardredirection */
+
+ DWORD ConnectToConsole; /* connect to console */
+ DWORD AdministrativeSession; /* administrative session */
+ DWORD AutoReconnectionEnabled; /* autoreconnection enabled */
+ DWORD AutoReconnectMaxRetries; /* autoreconnect max retries */
+
+ DWORD PublicMode; /* public mode */
+ DWORD AuthenticationLevel; /* authentication level */
+ DWORD PromptCredentialOnce; /* promptcredentialonce */
+ DWORD PromptForCredentials; /* prompt for credentials */
+ DWORD NegotiateSecurityLayer; /* negotiate security layer */
+ DWORD EnableCredSSPSupport; /* enablecredsspsupport */
+ DWORD EnableRdsAadAuth; /* enablerdsaadauth */
+
+ DWORD RemoteApplicationMode; /* remoteapplicationmode */
+ LPSTR LoadBalanceInfo; /* loadbalanceinfo */
+
+ LPSTR RemoteApplicationName; /* remoteapplicationname */
+ LPSTR RemoteApplicationIcon; /* remoteapplicationicon */
+ LPSTR RemoteApplicationProgram; /* remoteapplicationprogram */
+ LPSTR RemoteApplicationFile; /* remoteapplicationfile */
+ LPSTR RemoteApplicationGuid; /* remoteapplicationguid */
+ LPSTR RemoteApplicationCmdLine; /* remoteapplicationcmdline */
+ DWORD RemoteApplicationExpandCmdLine; /* remoteapplicationexpandcmdline */
+ DWORD RemoteApplicationExpandWorkingDir; /* remoteapplicationexpandworkingdir */
+ DWORD DisableConnectionSharing; /* disableconnectionsharing */
+ DWORD DisableRemoteAppCapsCheck; /* disableremoteappcapscheck */
+
+ LPSTR AlternateShell; /* alternate shell */
+ LPSTR ShellWorkingDirectory; /* shell working directory */
+
+ LPSTR GatewayHostname; /* gatewayhostname */
+ DWORD GatewayUsageMethod; /* gatewayusagemethod */
+ DWORD GatewayProfileUsageMethod; /* gatewayprofileusagemethod */
+ DWORD GatewayCredentialsSource; /* gatewaycredentialssource */
+
+ LPSTR ResourceProvider; /* resourceprovider */
+
+ LPSTR WvdEndpointPool; /* wvd endpoint pool */
+ LPSTR geo; /* geo */
+ LPSTR armpath; /* armpath */
+ LPSTR aadtenantid; /* aadtenantid" */
+ LPSTR diagnosticserviceurl; /* diagnosticserviceurl */
+ LPSTR hubdiscoverygeourl; /* hubdiscoverygeourl" */
+ LPSTR activityhint; /* activityhint */
+
+ DWORD UseRedirectionServerName; /* use redirection server name */
+
+ LPSTR GatewayAccessToken; /* gatewayaccesstoken */
+
+ LPSTR DrivesToRedirect; /* drivestoredirect */
+ LPSTR DevicesToRedirect; /* devicestoredirect */
+ LPSTR WinPosStr; /* winposstr */
+
+ LPSTR PreconnectionBlob; /* pcb */
+
+ LPSTR KdcProxyName; /* kdcproxyname */
+ DWORD RdgIsKdcProxy; /* rdgiskdcproxy */
+
+ DWORD align1;
+
+ size_t lineCount;
+ size_t lineSize;
+ rdpFileLine* lines;
+
+ ADDIN_ARGV* args;
+ void* context;
+
+ DWORD flags;
+};
+
+static const char key_str_username[] = "username";
+static const char key_str_domain[] = "domain";
+static const char key_str_password[] = "password";
+static const char key_str_full_address[] = "full address";
+static const char key_str_alternate_full_address[] = "alternate full address";
+static const char key_str_usbdevicestoredirect[] = "usbdevicestoredirect";
+static const char key_str_camerastoredirect[] = "camerastoredirect";
+static const char key_str_loadbalanceinfo[] = "loadbalanceinfo";
+static const char key_str_remoteapplicationname[] = "remoteapplicationname";
+static const char key_str_remoteapplicationicon[] = "remoteapplicationicon";
+static const char key_str_remoteapplicationprogram[] = "remoteapplicationprogram";
+static const char key_str_remoteapplicationfile[] = "remoteapplicationfile";
+static const char key_str_remoteapplicationguid[] = "remoteapplicationguid";
+static const char key_str_remoteapplicationcmdline[] = "remoteapplicationcmdline";
+static const char key_str_alternate_shell[] = "alternate shell";
+static const char key_str_shell_working_directory[] = "shell working directory";
+static const char key_str_gatewayhostname[] = "gatewayhostname";
+static const char key_str_gatewayaccesstoken[] = "gatewayaccesstoken";
+static const char key_str_resourceprovider[] = "resourceprovider";
+static const char str_resourceprovider_arm[] = "arm";
+static const char key_str_kdcproxyname[] = "kdcproxyname";
+static const char key_str_drivestoredirect[] = "drivestoredirect";
+static const char key_str_devicestoredirect[] = "devicestoredirect";
+static const char key_str_winposstr[] = "winposstr";
+static const char key_str_pcb[] = "pcb";
+static const char key_str_selectedmonitors[] = "selectedmonitors";
+
+static const char key_str_wvd[] = "wvd endpoint pool";
+static const char key_str_geo[] = "geo";
+static const char key_str_armpath[] = "armpath";
+static const char key_str_aadtenantid[] = "aadtenantid";
+
+static const char key_str_diagnosticserviceurl[] = "diagnosticserviceurl";
+static const char key_str_hubdiscoverygeourl[] = "hubdiscoverygeourl";
+
+static const char key_str_activityhint[] = "activityhint";
+
+static const char key_int_rdgiskdcproxy[] = "rdgiskdcproxy";
+static const char key_int_use_redirection_server_name[] = "use redirection server name";
+static const char key_int_gatewaycredentialssource[] = "gatewaycredentialssource";
+static const char key_int_gatewayprofileusagemethod[] = "gatewayprofileusagemethod";
+static const char key_int_gatewayusagemethod[] = "gatewayusagemethod";
+static const char key_int_disableremoteappcapscheck[] = "disableremoteappcapscheck";
+static const char key_int_disableconnectionsharing[] = "disableconnectionsharing";
+static const char key_int_remoteapplicationexpandworkingdir[] = "remoteapplicationexpandworkingdir";
+static const char key_int_remoteapplicationexpandcmdline[] = "remoteapplicationexpandcmdline";
+static const char key_int_remoteapplicationmode[] = "remoteapplicationmode";
+static const char key_int_enablecredsspsupport[] = "enablecredsspsupport";
+static const char key_int_enablerdsaadauth[] = "enablerdsaadauth";
+static const char key_int_negotiate_security_layer[] = "negotiate security layer";
+static const char key_int_prompt_for_credentials[] = "prompt for credentials";
+static const char key_int_promptcredentialonce[] = "promptcredentialonce";
+static const char key_int_authentication_level[] = "authentication level";
+static const char key_int_public_mode[] = "public mode";
+static const char key_int_autoreconnect_max_retries[] = "autoreconnect max retries";
+static const char key_int_autoreconnection_enabled[] = "autoreconnection enabled";
+static const char key_int_administrative_session[] = "administrative session";
+static const char key_int_connect_to_console[] = "connect to console";
+static const char key_int_disableclipboardredirection[] = "disableclipboardredirection";
+static const char key_int_disableprinterredirection[] = "disableprinterredirection";
+static const char key_int_redirectdirectx[] = "redirectdirectx";
+static const char key_int_redirectposdevices[] = "redirectposdevices";
+static const char key_int_redirectclipboard[] = "redirectclipboard";
+static const char key_int_redirectsmartcards[] = "redirectsmartcards";
+static const char key_int_redirectcomports[] = "redirectcomports";
+static const char key_int_redirectlocation[] = "redirectlocation";
+static const char key_int_redirectprinters[] = "redirectprinters";
+static const char key_int_redirectdrives[] = "redirectdrives";
+static const char key_int_server_port[] = "server port";
+static const char key_int_bitmapcachepersistenable[] = "bitmapcachepersistenable";
+static const char key_int_bitmapcachesize[] = "bitmapcachesize";
+static const char key_int_disable_cursor_setting[] = "disable cursor setting";
+static const char key_int_disable_themes[] = "disable themes";
+static const char key_int_disable_menu_anims[] = "disable menu anims";
+static const char key_int_disable_full_window_drag[] = "disable full window drag";
+static const char key_int_allow_desktop_composition[] = "allow desktop composition";
+static const char key_int_allow_font_smoothing[] = "allow font smoothing";
+static const char key_int_disable_wallpaper[] = "disable wallpaper";
+static const char key_int_enableworkspacereconnect[] = "enableworkspacereconnect";
+static const char key_int_workspaceid[] = "workspaceid";
+static const char key_int_displayconnectionbar[] = "displayconnectionbar";
+static const char key_int_pinconnectionbar[] = "pinconnectionbar";
+static const char key_int_bandwidthautodetect[] = "bandwidthautodetect";
+static const char key_int_networkautodetect[] = "networkautodetect";
+static const char key_int_connection_type[] = "connection type";
+static const char key_int_videoplaybackmode[] = "videoplaybackmode";
+static const char key_int_redirected_video_capture_encoding_quality[] =
+ "redirected video capture encoding quality";
+static const char key_int_encode_redirected_video_capture[] = "encode redirected video capture";
+static const char key_int_audiocapturemode[] = "audiocapturemode";
+static const char key_int_audioqualitymode[] = "audioqualitymode";
+static const char key_int_audiomode[] = "audiomode";
+static const char key_int_disable_ctrl_alt_del[] = "disable ctrl+alt+del";
+static const char key_int_keyboardhook[] = "keyboardhook";
+static const char key_int_compression[] = "compression";
+static const char key_int_desktopscalefactor[] = "desktopscalefactor";
+static const char key_int_session_bpp[] = "session bpp";
+static const char key_int_desktop_size_id[] = "desktop size id";
+static const char key_int_desktopheight[] = "desktopheight";
+static const char key_int_desktopwidth[] = "desktopwidth";
+static const char key_int_superpanaccelerationfactor[] = "superpanaccelerationfactor";
+static const char key_int_enablesuperpan[] = "enablesuperpan";
+static const char key_int_dynamic_resolution[] = "dynamic resolution";
+static const char key_int_smart_sizing[] = "smart sizing";
+static const char key_int_span_monitors[] = "span monitors";
+static const char key_int_screen_mode_id[] = "screen mode id";
+static const char key_int_singlemoninwindowedmode[] = "singlemoninwindowedmode";
+static const char key_int_maximizetocurrentdisplays[] = "maximizetocurrentdisplays";
+static const char key_int_use_multimon[] = "use multimon";
+static const char key_int_redirectwebauthn[] = "redirectwebauthn";
+
+static SSIZE_T freerdp_client_rdp_file_add_line(rdpFile* file);
+static rdpFileLine* freerdp_client_rdp_file_find_line_by_name(const rdpFile* file,
+ const char* name);
+static void freerdp_client_file_string_check_free(LPSTR str);
+
+static BOOL freerdp_client_rdp_file_find_integer_entry(rdpFile* file, const char* name,
+ DWORD** outValue, rdpFileLine** outLine)
+{
+ WINPR_ASSERT(file);
+ WINPR_ASSERT(name);
+ WINPR_ASSERT(outValue);
+ WINPR_ASSERT(outLine);
+
+ *outValue = NULL;
+ *outLine = NULL;
+
+ if (_stricmp(name, key_int_use_multimon) == 0)
+ *outValue = &file->UseMultiMon;
+ else if (_stricmp(name, key_int_maximizetocurrentdisplays) == 0)
+ *outValue = &file->MaximizeToCurrentDisplays;
+ else if (_stricmp(name, key_int_singlemoninwindowedmode) == 0)
+ *outValue = &file->SingleMonInWindowedMode;
+ else if (_stricmp(name, key_int_screen_mode_id) == 0)
+ *outValue = &file->ScreenModeId;
+ else if (_stricmp(name, key_int_span_monitors) == 0)
+ *outValue = &file->SpanMonitors;
+ else if (_stricmp(name, key_int_smart_sizing) == 0)
+ *outValue = &file->SmartSizing;
+ else if (_stricmp(name, key_int_dynamic_resolution) == 0)
+ *outValue = &file->DynamicResolution;
+ else if (_stricmp(name, key_int_enablesuperpan) == 0)
+ *outValue = &file->EnableSuperSpan;
+ else if (_stricmp(name, key_int_superpanaccelerationfactor) == 0)
+ *outValue = &file->SuperSpanAccelerationFactor;
+ else if (_stricmp(name, key_int_desktopwidth) == 0)
+ *outValue = &file->DesktopWidth;
+ else if (_stricmp(name, key_int_desktopheight) == 0)
+ *outValue = &file->DesktopHeight;
+ else if (_stricmp(name, key_int_desktop_size_id) == 0)
+ *outValue = &file->DesktopSizeId;
+ else if (_stricmp(name, key_int_session_bpp) == 0)
+ *outValue = &file->SessionBpp;
+ else if (_stricmp(name, key_int_desktopscalefactor) == 0)
+ *outValue = &file->DesktopScaleFactor;
+ else if (_stricmp(name, key_int_compression) == 0)
+ *outValue = &file->Compression;
+ else if (_stricmp(name, key_int_keyboardhook) == 0)
+ *outValue = &file->KeyboardHook;
+ else if (_stricmp(name, key_int_disable_ctrl_alt_del) == 0)
+ *outValue = &file->DisableCtrlAltDel;
+ else if (_stricmp(name, key_int_audiomode) == 0)
+ *outValue = &file->AudioMode;
+ else if (_stricmp(name, key_int_audioqualitymode) == 0)
+ *outValue = &file->AudioQualityMode;
+ else if (_stricmp(name, key_int_audiocapturemode) == 0)
+ *outValue = &file->AudioCaptureMode;
+ else if (_stricmp(name, key_int_encode_redirected_video_capture) == 0)
+ *outValue = &file->EncodeRedirectedVideoCapture;
+ else if (_stricmp(name, key_int_redirected_video_capture_encoding_quality) == 0)
+ *outValue = &file->RedirectedVideoCaptureEncodingQuality;
+ else if (_stricmp(name, key_int_videoplaybackmode) == 0)
+ *outValue = &file->VideoPlaybackMode;
+ else if (_stricmp(name, key_int_connection_type) == 0)
+ *outValue = &file->ConnectionType;
+ else if (_stricmp(name, key_int_networkautodetect) == 0)
+ *outValue = &file->NetworkAutoDetect;
+ else if (_stricmp(name, key_int_bandwidthautodetect) == 0)
+ *outValue = &file->BandwidthAutoDetect;
+ else if (_stricmp(name, key_int_pinconnectionbar) == 0)
+ *outValue = &file->PinConnectionBar;
+ else if (_stricmp(name, key_int_displayconnectionbar) == 0)
+ *outValue = &file->DisplayConnectionBar;
+ else if (_stricmp(name, key_int_workspaceid) == 0)
+ *outValue = &file->WorkspaceId;
+ else if (_stricmp(name, key_int_enableworkspacereconnect) == 0)
+ *outValue = &file->EnableWorkspaceReconnect;
+ else if (_stricmp(name, key_int_disable_wallpaper) == 0)
+ *outValue = &file->DisableWallpaper;
+ else if (_stricmp(name, key_int_allow_font_smoothing) == 0)
+ *outValue = &file->AllowFontSmoothing;
+ else if (_stricmp(name, key_int_allow_desktop_composition) == 0)
+ *outValue = &file->AllowDesktopComposition;
+ else if (_stricmp(name, key_int_disable_full_window_drag) == 0)
+ *outValue = &file->DisableFullWindowDrag;
+ else if (_stricmp(name, key_int_disable_menu_anims) == 0)
+ *outValue = &file->DisableMenuAnims;
+ else if (_stricmp(name, key_int_disable_themes) == 0)
+ *outValue = &file->DisableThemes;
+ else if (_stricmp(name, key_int_disable_cursor_setting) == 0)
+ *outValue = &file->DisableCursorSetting;
+ else if (_stricmp(name, key_int_bitmapcachesize) == 0)
+ *outValue = &file->BitmapCacheSize;
+ else if (_stricmp(name, key_int_bitmapcachepersistenable) == 0)
+ *outValue = &file->BitmapCachePersistEnable;
+ else if (_stricmp(name, key_int_server_port) == 0)
+ *outValue = &file->ServerPort;
+ else if (_stricmp(name, key_int_redirectdrives) == 0)
+ *outValue = &file->RedirectDrives;
+ else if (_stricmp(name, key_int_redirectprinters) == 0)
+ *outValue = &file->RedirectPrinters;
+ else if (_stricmp(name, key_int_redirectcomports) == 0)
+ *outValue = &file->RedirectComPorts;
+ else if (_stricmp(name, key_int_redirectlocation) == 0)
+ *outValue = &file->RedirectLocation;
+ else if (_stricmp(name, key_int_redirectsmartcards) == 0)
+ *outValue = &file->RedirectSmartCards;
+ else if (_stricmp(name, key_int_redirectclipboard) == 0)
+ *outValue = &file->RedirectClipboard;
+ else if (_stricmp(name, key_int_redirectposdevices) == 0)
+ *outValue = &file->RedirectPosDevices;
+ else if (_stricmp(name, key_int_redirectdirectx) == 0)
+ *outValue = &file->RedirectDirectX;
+ else if (_stricmp(name, key_int_disableprinterredirection) == 0)
+ *outValue = &file->DisablePrinterRedirection;
+ else if (_stricmp(name, key_int_disableclipboardredirection) == 0)
+ *outValue = &file->DisableClipboardRedirection;
+ else if (_stricmp(name, key_int_connect_to_console) == 0)
+ *outValue = &file->ConnectToConsole;
+ else if (_stricmp(name, key_int_administrative_session) == 0)
+ *outValue = &file->AdministrativeSession;
+ else if (_stricmp(name, key_int_autoreconnection_enabled) == 0)
+ *outValue = &file->AutoReconnectionEnabled;
+ else if (_stricmp(name, key_int_autoreconnect_max_retries) == 0)
+ *outValue = &file->AutoReconnectMaxRetries;
+ else if (_stricmp(name, key_int_public_mode) == 0)
+ *outValue = &file->PublicMode;
+ else if (_stricmp(name, key_int_authentication_level) == 0)
+ *outValue = &file->AuthenticationLevel;
+ else if (_stricmp(name, key_int_promptcredentialonce) == 0)
+ *outValue = &file->PromptCredentialOnce;
+ else if ((_stricmp(name, key_int_prompt_for_credentials) == 0))
+ *outValue = &file->PromptForCredentials;
+ else if (_stricmp(name, key_int_negotiate_security_layer) == 0)
+ *outValue = &file->NegotiateSecurityLayer;
+ else if (_stricmp(name, key_int_enablecredsspsupport) == 0)
+ *outValue = &file->EnableCredSSPSupport;
+ else if (_stricmp(name, key_int_enablerdsaadauth) == 0)
+ *outValue = &file->EnableRdsAadAuth;
+ else if (_stricmp(name, key_int_remoteapplicationmode) == 0)
+ *outValue = &file->RemoteApplicationMode;
+ else if (_stricmp(name, key_int_remoteapplicationexpandcmdline) == 0)
+ *outValue = &file->RemoteApplicationExpandCmdLine;
+ else if (_stricmp(name, key_int_remoteapplicationexpandworkingdir) == 0)
+ *outValue = &file->RemoteApplicationExpandWorkingDir;
+ else if (_stricmp(name, key_int_disableconnectionsharing) == 0)
+ *outValue = &file->DisableConnectionSharing;
+ else if (_stricmp(name, key_int_disableremoteappcapscheck) == 0)
+ *outValue = &file->DisableRemoteAppCapsCheck;
+ else if (_stricmp(name, key_int_gatewayusagemethod) == 0)
+ *outValue = &file->GatewayUsageMethod;
+ else if (_stricmp(name, key_int_gatewayprofileusagemethod) == 0)
+ *outValue = &file->GatewayProfileUsageMethod;
+ else if (_stricmp(name, key_int_gatewaycredentialssource) == 0)
+ *outValue = &file->GatewayCredentialsSource;
+ else if (_stricmp(name, key_int_use_redirection_server_name) == 0)
+ *outValue = &file->UseRedirectionServerName;
+ else if (_stricmp(name, key_int_rdgiskdcproxy) == 0)
+ *outValue = &file->RdgIsKdcProxy;
+ else if (_stricmp(name, key_int_redirectwebauthn) == 0)
+ *outValue = &file->RedirectWebauthN;
+ else
+ {
+ rdpFileLine* line = freerdp_client_rdp_file_find_line_by_name(file, name);
+ if (!line)
+ return FALSE;
+ if (!(line->flags & RDP_FILE_LINE_FLAG_TYPE_INTEGER))
+ return FALSE;
+
+ *outLine = line;
+ }
+
+ return TRUE;
+}
+
+static BOOL freerdp_client_rdp_file_find_string_entry(rdpFile* file, const char* name,
+ LPSTR** outValue, rdpFileLine** outLine)
+{
+ WINPR_ASSERT(file);
+ WINPR_ASSERT(name);
+ WINPR_ASSERT(outValue);
+ WINPR_ASSERT(outLine);
+
+ *outValue = NULL;
+ *outLine = NULL;
+
+ if (_stricmp(name, key_str_username) == 0)
+ *outValue = &file->Username;
+ else if (_stricmp(name, key_str_domain) == 0)
+ *outValue = &file->Domain;
+ else if (_stricmp(name, key_str_password) == 0)
+ *outValue = &file->Password;
+ else if (_stricmp(name, key_str_full_address) == 0)
+ *outValue = &file->FullAddress;
+ else if (_stricmp(name, key_str_alternate_full_address) == 0)
+ *outValue = &file->AlternateFullAddress;
+ else if (_stricmp(name, key_str_usbdevicestoredirect) == 0)
+ *outValue = &file->UsbDevicesToRedirect;
+ else if (_stricmp(name, key_str_camerastoredirect) == 0)
+ *outValue = &file->RedirectCameras;
+ else if (_stricmp(name, key_str_loadbalanceinfo) == 0)
+ *outValue = &file->LoadBalanceInfo;
+ else if (_stricmp(name, key_str_remoteapplicationname) == 0)
+ *outValue = &file->RemoteApplicationName;
+ else if (_stricmp(name, key_str_remoteapplicationicon) == 0)
+ *outValue = &file->RemoteApplicationIcon;
+ else if (_stricmp(name, key_str_remoteapplicationprogram) == 0)
+ *outValue = &file->RemoteApplicationProgram;
+ else if (_stricmp(name, key_str_remoteapplicationfile) == 0)
+ *outValue = &file->RemoteApplicationFile;
+ else if (_stricmp(name, key_str_remoteapplicationguid) == 0)
+ *outValue = &file->RemoteApplicationGuid;
+ else if (_stricmp(name, key_str_remoteapplicationcmdline) == 0)
+ *outValue = &file->RemoteApplicationCmdLine;
+ else if (_stricmp(name, key_str_alternate_shell) == 0)
+ *outValue = &file->AlternateShell;
+ else if (_stricmp(name, key_str_shell_working_directory) == 0)
+ *outValue = &file->ShellWorkingDirectory;
+ else if (_stricmp(name, key_str_gatewayhostname) == 0)
+ *outValue = &file->GatewayHostname;
+ else if (_stricmp(name, key_str_resourceprovider) == 0)
+ *outValue = &file->ResourceProvider;
+ else if (_stricmp(name, key_str_wvd) == 0)
+ *outValue = &file->WvdEndpointPool;
+ else if (_stricmp(name, key_str_geo) == 0)
+ *outValue = &file->geo;
+ else if (_stricmp(name, key_str_armpath) == 0)
+ *outValue = &file->armpath;
+ else if (_stricmp(name, key_str_aadtenantid) == 0)
+ *outValue = &file->aadtenantid;
+ else if (_stricmp(name, key_str_diagnosticserviceurl) == 0)
+ *outValue = &file->diagnosticserviceurl;
+ else if (_stricmp(name, key_str_hubdiscoverygeourl) == 0)
+ *outValue = &file->hubdiscoverygeourl;
+ else if (_stricmp(name, key_str_activityhint) == 0)
+ *outValue = &file->activityhint;
+ else if (_stricmp(name, key_str_gatewayaccesstoken) == 0)
+ *outValue = &file->GatewayAccessToken;
+ else if (_stricmp(name, key_str_kdcproxyname) == 0)
+ *outValue = &file->KdcProxyName;
+ else if (_stricmp(name, key_str_drivestoredirect) == 0)
+ *outValue = &file->DrivesToRedirect;
+ else if (_stricmp(name, key_str_devicestoredirect) == 0)
+ *outValue = &file->DevicesToRedirect;
+ else if (_stricmp(name, key_str_winposstr) == 0)
+ *outValue = &file->WinPosStr;
+ else if (_stricmp(name, key_str_pcb) == 0)
+ *outValue = &file->PreconnectionBlob;
+ else if (_stricmp(name, key_str_selectedmonitors) == 0)
+ *outValue = &file->SelectedMonitors;
+ else
+ {
+ rdpFileLine* line = freerdp_client_rdp_file_find_line_by_name(file, name);
+ if (!line)
+ return FALSE;
+ if (!(line->flags & RDP_FILE_LINE_FLAG_TYPE_STRING))
+ return FALSE;
+
+ *outLine = line;
+ }
+
+ return TRUE;
+}
+
+/*
+ * Set an integer in a rdpFile
+ *
+ * @return FALSE if a standard name was set, TRUE for a non-standard name, FALSE on error
+ *
+ */
+static BOOL freerdp_client_rdp_file_set_integer(rdpFile* file, const char* name, long value)
+{
+ DWORD* targetValue = NULL;
+ rdpFileLine* line = NULL;
+#ifdef DEBUG_CLIENT_FILE
+ WLog_DBG(TAG, "%s:i:%ld", name, value);
+#endif
+
+ if (value < 0)
+ return FALSE;
+
+ if (!freerdp_client_rdp_file_find_integer_entry(file, name, &targetValue, &line))
+ {
+ SSIZE_T index = freerdp_client_rdp_file_add_line(file);
+ if (index == -1)
+ return FALSE;
+ line = &file->lines[index];
+ }
+
+ if (targetValue)
+ {
+ *targetValue = (DWORD)value;
+ return TRUE;
+ }
+
+ if (line)
+ {
+ free(line->name);
+ line->name = _strdup(name);
+ if (!line->name)
+ {
+ free(line->name);
+ line->name = NULL;
+ return FALSE;
+ }
+
+ line->iValue = value;
+ line->flags = RDP_FILE_LINE_FLAG_FORMATTED;
+ line->flags |= RDP_FILE_LINE_FLAG_TYPE_INTEGER;
+ line->valueLength = 0;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static BOOL freerdp_client_parse_rdp_file_integer(rdpFile* file, const char* name,
+ const char* value)
+{
+ char* endptr = NULL;
+ long ivalue = 0;
+ errno = 0;
+ ivalue = strtol(value, &endptr, 0);
+
+ if ((endptr == NULL) || (errno != 0) || (endptr == value) || (ivalue > INT32_MAX) ||
+ (ivalue < INT32_MIN))
+ {
+ if (file->flags & RDP_FILE_FLAG_PARSE_INT_RELAXED)
+ {
+ WLog_WARN(TAG, "Integer option %s has invalid value %s, using default", name, value);
+ return TRUE;
+ }
+ else
+ {
+ WLog_ERR(TAG, "Failed to convert RDP file integer option %s [value=%s]", name, value);
+ return FALSE;
+ }
+ }
+
+ return freerdp_client_rdp_file_set_integer(file, name, ivalue);
+}
+
+/** set a string value in the provided rdp file context
+ *
+ * @param file rdpFile
+ * @param name name of the string
+ * @param value value of the string to set
+ * @return 0 on success, 1 if the key wasn't found (not a standard key), -1 on error
+ */
+
+static BOOL freerdp_client_rdp_file_set_string(rdpFile* file, const char* name, const char* value)
+{
+ LPSTR* targetValue = NULL;
+ rdpFileLine* line = NULL;
+#ifdef DEBUG_CLIENT_FILE
+ WLog_DBG(TAG, "%s:s:%s", name, value);
+#endif
+
+ if (!name || !value)
+ return FALSE;
+
+ if (!freerdp_client_rdp_file_find_string_entry(file, name, &targetValue, &line))
+ {
+ SSIZE_T index = freerdp_client_rdp_file_add_line(file);
+ if (index == -1)
+ return FALSE;
+ line = &file->lines[index];
+ }
+
+ if (targetValue)
+ {
+ *targetValue = _strdup(value);
+ if (!(*targetValue))
+ return FALSE;
+ return TRUE;
+ }
+
+ if (line)
+ {
+ free(line->name);
+ free(line->sValue);
+ line->name = _strdup(name);
+ line->sValue = _strdup(value);
+ if (!line->name || !line->sValue)
+ {
+ free(line->name);
+ free(line->sValue);
+ line->name = NULL;
+ line->sValue = NULL;
+ return FALSE;
+ }
+
+ line->flags = RDP_FILE_LINE_FLAG_FORMATTED;
+ line->flags |= RDP_FILE_LINE_FLAG_TYPE_STRING;
+ line->valueLength = 0;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static BOOL freerdp_client_add_option(rdpFile* file, const char* option)
+{
+ return freerdp_addin_argv_add_argument(file->args, option);
+}
+
+static SSIZE_T freerdp_client_rdp_file_add_line(rdpFile* file)
+{
+ SSIZE_T index = (SSIZE_T)file->lineCount;
+
+ while ((file->lineCount + 1) > file->lineSize)
+ {
+ size_t new_size = 0;
+ rdpFileLine* new_line = NULL;
+ new_size = file->lineSize * 2;
+ new_line = (rdpFileLine*)realloc(file->lines, new_size * sizeof(rdpFileLine));
+
+ if (!new_line)
+ return -1;
+
+ file->lines = new_line;
+ file->lineSize = new_size;
+ }
+
+ ZeroMemory(&(file->lines[file->lineCount]), sizeof(rdpFileLine));
+ file->lines[file->lineCount].index = (size_t)index;
+ (file->lineCount)++;
+ return index;
+}
+
+static BOOL freerdp_client_parse_rdp_file_string(rdpFile* file, char* name, char* value)
+{
+ return freerdp_client_rdp_file_set_string(file, name, value);
+}
+
+static BOOL freerdp_client_parse_rdp_file_option(rdpFile* file, const char* option)
+{
+ return freerdp_client_add_option(file, option);
+}
+
+BOOL freerdp_client_parse_rdp_file_buffer(rdpFile* file, const BYTE* buffer, size_t size)
+{
+ return freerdp_client_parse_rdp_file_buffer_ex(file, buffer, size, NULL);
+}
+
+static BOOL trim(char** strptr)
+{
+ char* start = NULL;
+ char* str = NULL;
+ char* end = NULL;
+
+ start = str = *strptr;
+ if (!str)
+ return TRUE;
+ if (!(~((size_t)str)))
+ return TRUE;
+ end = str + strlen(str) - 1;
+
+ while (isspace(*str))
+ str++;
+
+ while ((end > str) && isspace(*end))
+ end--;
+ end[1] = '\0';
+ if (start == str)
+ *strptr = str;
+ else
+ {
+ *strptr = _strdup(str);
+ free(start);
+ return *strptr != NULL;
+ }
+
+ return TRUE;
+}
+
+static BOOL trim_strings(rdpFile* file)
+{
+ if (!trim(&file->Username))
+ return FALSE;
+ if (!trim(&file->Domain))
+ return FALSE;
+ if (!trim(&file->AlternateFullAddress))
+ return FALSE;
+ if (!trim(&file->FullAddress))
+ return FALSE;
+ if (!trim(&file->UsbDevicesToRedirect))
+ return FALSE;
+ if (!trim(&file->RedirectCameras))
+ return FALSE;
+ if (!trim(&file->LoadBalanceInfo))
+ return FALSE;
+ if (!trim(&file->GatewayHostname))
+ return FALSE;
+ if (!trim(&file->GatewayAccessToken))
+ return FALSE;
+ if (!trim(&file->RemoteApplicationName))
+ return FALSE;
+ if (!trim(&file->RemoteApplicationIcon))
+ return FALSE;
+ if (!trim(&file->RemoteApplicationProgram))
+ return FALSE;
+ if (!trim(&file->RemoteApplicationFile))
+ return FALSE;
+ if (!trim(&file->RemoteApplicationGuid))
+ return FALSE;
+ if (!trim(&file->RemoteApplicationCmdLine))
+ return FALSE;
+ if (!trim(&file->AlternateShell))
+ return FALSE;
+ if (!trim(&file->ShellWorkingDirectory))
+ return FALSE;
+ if (!trim(&file->DrivesToRedirect))
+ return FALSE;
+ if (!trim(&file->DevicesToRedirect))
+ return FALSE;
+ if (!trim(&file->DevicesToRedirect))
+ return FALSE;
+ if (!trim(&file->WinPosStr))
+ return FALSE;
+ if (!trim(&file->PreconnectionBlob))
+ return FALSE;
+ if (!trim(&file->KdcProxyName))
+ return FALSE;
+ if (!trim(&file->SelectedMonitors))
+ return FALSE;
+
+ for (size_t i = 0; i < file->lineCount; ++i)
+ {
+ rdpFileLine* curLine = &file->lines[i];
+ if (curLine->flags & RDP_FILE_LINE_FLAG_TYPE_STRING)
+ {
+ if (!trim(&curLine->sValue))
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+BOOL freerdp_client_parse_rdp_file_buffer_ex(rdpFile* file, const BYTE* buffer, size_t size,
+ rdp_file_fkt_parse parse)
+{
+ BOOL rc = FALSE;
+ size_t length = 0;
+ char* line = NULL;
+ char* type = NULL;
+ char* context = NULL;
+ char* d1 = NULL;
+ char* d2 = NULL;
+ char* beg = NULL;
+ char* name = NULL;
+ char* value = NULL;
+ char* copy = NULL;
+
+ if (!file)
+ return FALSE;
+ if (size < 2)
+ return FALSE;
+
+ if ((buffer[0] == BOM_UTF16_LE[0]) && (buffer[1] == BOM_UTF16_LE[1]))
+ {
+ LPCWSTR uc = (LPCWSTR)(&buffer[2]);
+ size = size / sizeof(WCHAR) - 1;
+
+ copy = ConvertWCharNToUtf8Alloc(uc, size, NULL);
+ if (!copy)
+ {
+ WLog_ERR(TAG, "Failed to convert RDP file from UCS2 to UTF8");
+ return FALSE;
+ }
+ }
+ else
+ {
+ copy = calloc(1, size + sizeof(BYTE));
+
+ if (!copy)
+ return FALSE;
+
+ memcpy(copy, buffer, size);
+ }
+
+ line = strtok_s(copy, "\r\n", &context);
+
+ while (line)
+ {
+ length = strnlen(line, size);
+
+ if (length > 1)
+ {
+ beg = line;
+ if (beg[0] == '/')
+ {
+ if (!freerdp_client_parse_rdp_file_option(file, line))
+ goto fail;
+
+ goto next_line; /* FreeRDP option */
+ }
+
+ d1 = strchr(line, ':');
+
+ if (!d1)
+ goto next_line; /* not first delimiter */
+
+ type = &d1[1];
+ d2 = strchr(type, ':');
+
+ if (!d2)
+ goto next_line; /* no second delimiter */
+
+ if ((d2 - d1) != 2)
+ goto next_line; /* improper type length */
+
+ *d1 = 0;
+ *d2 = 0;
+ name = beg;
+ value = &d2[1];
+
+ if (parse && parse(file->context, name, *type, value))
+ {
+ }
+ else if (*type == 'i')
+ {
+ /* integer type */
+ if (!freerdp_client_parse_rdp_file_integer(file, name, value))
+ goto fail;
+ }
+ else if (*type == 's')
+ {
+ /* string type */
+ if (!freerdp_client_parse_rdp_file_string(file, name, value))
+ goto fail;
+ }
+ else if (*type == 'b')
+ {
+ /* binary type */
+ WLog_ERR(TAG, "Unsupported RDP file binary option %s [value=%s]", name, value);
+ }
+ }
+
+ next_line:
+ line = strtok_s(NULL, "\r\n", &context);
+ }
+
+ rc = trim_strings(file);
+fail:
+ free(copy);
+ return rc;
+}
+
+BOOL freerdp_client_parse_rdp_file(rdpFile* file, const char* name)
+{
+ return freerdp_client_parse_rdp_file_ex(file, name, NULL);
+}
+
+BOOL freerdp_client_parse_rdp_file_ex(rdpFile* file, const char* name, rdp_file_fkt_parse parse)
+{
+ BOOL status = 0;
+ BYTE* buffer = NULL;
+ FILE* fp = NULL;
+ size_t read_size = 0;
+ INT64 file_size = 0;
+ const char* fname = name;
+
+ if (!file || !name)
+ return FALSE;
+
+ if (_strnicmp(fname, "file://", 7) == 0)
+ fname = &name[7];
+
+ fp = winpr_fopen(fname, "r");
+ if (!fp)
+ {
+ WLog_ERR(TAG, "Failed to open RDP file %s", name);
+ return FALSE;
+ }
+
+ _fseeki64(fp, 0, SEEK_END);
+ file_size = _ftelli64(fp);
+ _fseeki64(fp, 0, SEEK_SET);
+
+ if (file_size < 1)
+ {
+ WLog_ERR(TAG, "RDP file %s is empty", name);
+ fclose(fp);
+ return FALSE;
+ }
+
+ buffer = (BYTE*)malloc((size_t)file_size + 2);
+
+ if (!buffer)
+ {
+ fclose(fp);
+ return FALSE;
+ }
+
+ read_size = fread(buffer, (size_t)file_size, 1, fp);
+
+ if (!read_size)
+ {
+ if (!ferror(fp))
+ read_size = (size_t)file_size;
+ }
+
+ fclose(fp);
+
+ if (read_size < 1)
+ {
+ WLog_ERR(TAG, "Could not read from RDP file %s", name);
+ free(buffer);
+ return FALSE;
+ }
+
+ buffer[file_size] = '\0';
+ buffer[file_size + 1] = '\0';
+ status = freerdp_client_parse_rdp_file_buffer_ex(file, buffer, (size_t)file_size, parse);
+ free(buffer);
+ return status;
+}
+
+static INLINE BOOL FILE_POPULATE_STRING(char** _target, const rdpSettings* _settings,
+ FreeRDP_Settings_Keys_String _option)
+{
+ WINPR_ASSERT(_target);
+ WINPR_ASSERT(_settings);
+
+ const char* str = freerdp_settings_get_string(_settings, _option);
+ freerdp_client_file_string_check_free(*_target);
+ *_target = (void*)~((size_t)NULL);
+ if (str)
+ {
+ *_target = _strdup(str);
+ if (!_target)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static char* freerdp_client_channel_args_to_string(const rdpSettings* settings, const char* channel,
+ const char* option)
+{
+ ADDIN_ARGV* args = freerdp_dynamic_channel_collection_find(settings, channel);
+ const char* filters[] = { option };
+ if (!args || (args->argc < 2))
+ return NULL;
+
+ return CommandLineToCommaSeparatedValuesEx(args->argc - 1, args->argv + 1, filters,
+ ARRAYSIZE(filters));
+}
+
+static BOOL rdp_opt_duplicate(const rdpSettings* _settings, FreeRDP_Settings_Keys_String _id,
+ char** _key)
+{
+ WINPR_ASSERT(_settings);
+ WINPR_ASSERT(_key);
+ const char* tmp = freerdp_settings_get_string(_settings, _id);
+
+ if (tmp)
+ {
+ *_key = _strdup(tmp);
+ if (!*_key)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL freerdp_client_populate_rdp_file_from_settings(rdpFile* file, const rdpSettings* settings)
+{
+ FreeRDP_Settings_Keys_String index = FreeRDP_STRING_UNUSED;
+ UINT32 LoadBalanceInfoLength = 0;
+ const char* GatewayHostname = NULL;
+ char* redirectCameras = NULL;
+ char* redirectUsb = NULL;
+
+ if (!file || !settings)
+ return FALSE;
+
+ if (!FILE_POPULATE_STRING(&file->Domain, settings, FreeRDP_Domain) ||
+ !FILE_POPULATE_STRING(&file->Username, settings, FreeRDP_Username) ||
+ !FILE_POPULATE_STRING(&file->Password, settings, FreeRDP_Password) ||
+ !FILE_POPULATE_STRING(&file->FullAddress, settings, FreeRDP_ServerHostname) ||
+ !FILE_POPULATE_STRING(&file->AlternateFullAddress, settings, FreeRDP_ServerHostname) ||
+ !FILE_POPULATE_STRING(&file->AlternateShell, settings, FreeRDP_AlternateShell) ||
+ !FILE_POPULATE_STRING(&file->DrivesToRedirect, settings, FreeRDP_DrivesToRedirect))
+
+ return FALSE;
+ file->ServerPort = freerdp_settings_get_uint32(settings, FreeRDP_ServerPort);
+
+ file->DesktopWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
+ file->DesktopHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
+ file->SessionBpp = freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth);
+ file->DesktopScaleFactor = freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor);
+ file->DynamicResolution = freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate);
+ file->VideoPlaybackMode = freerdp_settings_get_bool(settings, FreeRDP_SupportVideoOptimized);
+
+ // TODO file->MaximizeToCurrentDisplays;
+ // TODO file->SingleMonInWindowedMode;
+ // TODO file->EncodeRedirectedVideoCapture;
+ // TODO file->RedirectedVideoCaptureEncodingQuality;
+ file->ConnectToConsole = freerdp_settings_get_bool(settings, FreeRDP_ConsoleSession);
+ file->NegotiateSecurityLayer =
+ freerdp_settings_get_bool(settings, FreeRDP_NegotiateSecurityLayer);
+ file->EnableCredSSPSupport = freerdp_settings_get_bool(settings, FreeRDP_NlaSecurity);
+ file->EnableRdsAadAuth = freerdp_settings_get_bool(settings, FreeRDP_AadSecurity);
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_RemoteApplicationMode))
+ index = FreeRDP_RemoteApplicationWorkingDir;
+ else
+ index = FreeRDP_ShellWorkingDirectory;
+ if (!FILE_POPULATE_STRING(&file->ShellWorkingDirectory, settings, index))
+ return FALSE;
+ file->ConnectionType = freerdp_settings_get_uint32(settings, FreeRDP_ConnectionType);
+
+ file->ScreenModeId = freerdp_settings_get_bool(settings, FreeRDP_Fullscreen) ? 2 : 1;
+
+ LoadBalanceInfoLength = freerdp_settings_get_uint32(settings, FreeRDP_LoadBalanceInfoLength);
+ if (LoadBalanceInfoLength > 0)
+ {
+ const BYTE* LoadBalanceInfo =
+ freerdp_settings_get_pointer(settings, FreeRDP_LoadBalanceInfo);
+ file->LoadBalanceInfo = calloc(LoadBalanceInfoLength + 1, 1);
+ if (!file->LoadBalanceInfo)
+ return FALSE;
+ memcpy(file->LoadBalanceInfo, LoadBalanceInfo, LoadBalanceInfoLength);
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_AudioPlayback))
+ file->AudioMode = AUDIO_MODE_REDIRECT;
+ else if (freerdp_settings_get_bool(settings, FreeRDP_RemoteConsoleAudio))
+ file->AudioMode = AUDIO_MODE_PLAY_ON_SERVER;
+ else
+ file->AudioMode = AUDIO_MODE_NONE;
+
+ /* The gateway hostname should also contain a port specifier unless it is the default port 443
+ */
+ GatewayHostname = freerdp_settings_get_string(settings, FreeRDP_GatewayHostname);
+ if (GatewayHostname)
+ {
+ const UINT32 GatewayPort = freerdp_settings_get_uint32(settings, FreeRDP_GatewayPort);
+ freerdp_client_file_string_check_free(file->GatewayHostname);
+ if (GatewayPort == 443)
+ file->GatewayHostname = _strdup(GatewayHostname);
+ else
+ {
+ int length = _scprintf("%s:%" PRIu32, GatewayHostname, GatewayPort);
+ if (length < 0)
+ return FALSE;
+
+ file->GatewayHostname = (char*)malloc((size_t)length + 1);
+ if (!file->GatewayHostname)
+ return FALSE;
+
+ if (sprintf_s(file->GatewayHostname, (size_t)length + 1, "%s:%" PRIu32, GatewayHostname,
+ GatewayPort) < 0)
+ return FALSE;
+ }
+ if (!file->GatewayHostname)
+ return FALSE;
+ }
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_GatewayArmTransport))
+ file->ResourceProvider = _strdup(str_resourceprovider_arm);
+
+ if (!rdp_opt_duplicate(settings, FreeRDP_GatewayAvdWvdEndpointPool, &file->WvdEndpointPool))
+ return FALSE;
+ if (!rdp_opt_duplicate(settings, FreeRDP_GatewayAvdGeo, &file->geo))
+ return FALSE;
+ if (!rdp_opt_duplicate(settings, FreeRDP_GatewayAvdArmpath, &file->armpath))
+ return FALSE;
+ if (!rdp_opt_duplicate(settings, FreeRDP_GatewayAvdAadtenantid, &file->aadtenantid))
+ return FALSE;
+ if (!rdp_opt_duplicate(settings, FreeRDP_GatewayAvdDiagnosticserviceurl,
+ &file->diagnosticserviceurl))
+ return FALSE;
+ if (!rdp_opt_duplicate(settings, FreeRDP_GatewayAvdHubdiscoverygeourl,
+ &file->hubdiscoverygeourl))
+ return FALSE;
+ if (!rdp_opt_duplicate(settings, FreeRDP_GatewayAvdActivityhint, &file->activityhint))
+ return FALSE;
+
+ file->AudioCaptureMode = freerdp_settings_get_bool(settings, FreeRDP_AudioCapture);
+ file->BitmapCachePersistEnable =
+ freerdp_settings_get_bool(settings, FreeRDP_BitmapCachePersistEnabled);
+ file->Compression = freerdp_settings_get_bool(settings, FreeRDP_CompressionEnabled);
+ file->AuthenticationLevel = freerdp_settings_get_uint32(settings, FreeRDP_AuthenticationLevel);
+ file->GatewayUsageMethod = freerdp_settings_get_uint32(settings, FreeRDP_GatewayUsageMethod);
+ file->GatewayCredentialsSource =
+ freerdp_settings_get_uint32(settings, FreeRDP_GatewayCredentialsSource);
+ file->PromptCredentialOnce =
+ freerdp_settings_get_bool(settings, FreeRDP_GatewayUseSameCredentials);
+ file->PromptForCredentials = freerdp_settings_get_bool(settings, FreeRDP_PromptForCredentials);
+ file->RemoteApplicationMode =
+ freerdp_settings_get_bool(settings, FreeRDP_RemoteApplicationMode);
+ if (!FILE_POPULATE_STRING(&file->GatewayAccessToken, settings, FreeRDP_GatewayAccessToken) ||
+ !FILE_POPULATE_STRING(&file->RemoteApplicationProgram, settings,
+ FreeRDP_RemoteApplicationProgram) ||
+ !FILE_POPULATE_STRING(&file->RemoteApplicationName, settings,
+ FreeRDP_RemoteApplicationName) ||
+ !FILE_POPULATE_STRING(&file->RemoteApplicationIcon, settings,
+ FreeRDP_RemoteApplicationIcon) ||
+ !FILE_POPULATE_STRING(&file->RemoteApplicationFile, settings,
+ FreeRDP_RemoteApplicationFile) ||
+ !FILE_POPULATE_STRING(&file->RemoteApplicationGuid, settings,
+ FreeRDP_RemoteApplicationGuid) ||
+ !FILE_POPULATE_STRING(&file->RemoteApplicationCmdLine, settings,
+ FreeRDP_RemoteApplicationCmdLine))
+ return FALSE;
+ file->SpanMonitors = freerdp_settings_get_bool(settings, FreeRDP_SpanMonitors);
+ file->UseMultiMon = freerdp_settings_get_bool(settings, FreeRDP_UseMultimon);
+ file->AllowDesktopComposition =
+ freerdp_settings_get_bool(settings, FreeRDP_AllowDesktopComposition);
+ file->AllowFontSmoothing = freerdp_settings_get_bool(settings, FreeRDP_AllowFontSmoothing);
+ file->DisableWallpaper = freerdp_settings_get_bool(settings, FreeRDP_DisableWallpaper);
+ file->DisableFullWindowDrag =
+ freerdp_settings_get_bool(settings, FreeRDP_DisableFullWindowDrag);
+ file->DisableMenuAnims = freerdp_settings_get_bool(settings, FreeRDP_DisableMenuAnims);
+ file->DisableThemes = freerdp_settings_get_bool(settings, FreeRDP_DisableThemes);
+ file->BandwidthAutoDetect =
+ (freerdp_settings_get_uint32(settings, FreeRDP_ConnectionType) >= 7) ? TRUE : FALSE;
+ file->NetworkAutoDetect =
+ freerdp_settings_get_bool(settings, FreeRDP_NetworkAutoDetect) ? 1 : 0;
+ file->AutoReconnectionEnabled =
+ freerdp_settings_get_bool(settings, FreeRDP_AutoReconnectionEnabled);
+ file->RedirectSmartCards = freerdp_settings_get_bool(settings, FreeRDP_RedirectSmartCards);
+ file->RedirectWebauthN = freerdp_settings_get_bool(settings, FreeRDP_RedirectWebAuthN);
+
+ redirectCameras =
+ freerdp_client_channel_args_to_string(settings, RDPECAM_DVC_CHANNEL_NAME, "device:");
+ if (redirectCameras)
+ {
+ char* str =
+ freerdp_client_channel_args_to_string(settings, RDPECAM_DVC_CHANNEL_NAME, "encode:");
+ file->EncodeRedirectedVideoCapture = 0;
+ if (str)
+ {
+ unsigned long val = 0;
+ errno = 0;
+ val = strtoul(str, NULL, 0);
+ if ((val < UINT32_MAX) && (errno == 0))
+ file->EncodeRedirectedVideoCapture = val;
+ }
+ free(str);
+
+ str = freerdp_client_channel_args_to_string(settings, RDPECAM_DVC_CHANNEL_NAME, "quality:");
+ file->RedirectedVideoCaptureEncodingQuality = 0;
+ if (str)
+ {
+ unsigned long val = 0;
+ errno = 0;
+ val = strtoul(str, NULL, 0);
+ if ((val <= 2) && (errno == 0))
+ {
+ file->RedirectedVideoCaptureEncodingQuality = val;
+ }
+ }
+ free(str);
+
+ file->RedirectCameras = redirectCameras;
+ }
+#ifdef CHANNEL_URBDRC_CLIENT
+ redirectUsb = freerdp_client_channel_args_to_string(settings, URBDRC_CHANNEL_NAME, "device:");
+ if (redirectUsb)
+ file->UsbDevicesToRedirect = redirectUsb;
+
+#endif
+ file->RedirectClipboard =
+ freerdp_settings_get_bool(settings, FreeRDP_RedirectClipboard) ? 1 : 0;
+ file->RedirectPrinters = freerdp_settings_get_bool(settings, FreeRDP_RedirectPrinters) ? 1 : 0;
+ file->RedirectDrives = freerdp_settings_get_bool(settings, FreeRDP_RedirectDrives) ? 1 : 0;
+ file->RdgIsKdcProxy = freerdp_settings_get_bool(settings, FreeRDP_KerberosRdgIsProxy) ? 1 : 0;
+ file->RedirectComPorts = (freerdp_settings_get_bool(settings, FreeRDP_RedirectSerialPorts) ||
+ freerdp_settings_get_bool(settings, FreeRDP_RedirectParallelPorts));
+ file->RedirectLocation =
+ freerdp_dynamic_channel_collection_find(settings, LOCATION_DVC_CHANNEL_NAME) ? TRUE : FALSE;
+ if (!FILE_POPULATE_STRING(&file->DrivesToRedirect, settings, FreeRDP_DrivesToRedirect) ||
+ !FILE_POPULATE_STRING(&file->PreconnectionBlob, settings, FreeRDP_PreconnectionBlob) ||
+ !FILE_POPULATE_STRING(&file->KdcProxyName, settings, FreeRDP_KerberosKdcUrl))
+ return FALSE;
+
+ {
+ size_t offset = 0;
+ UINT32 count = freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds);
+ const UINT32* MonitorIds = freerdp_settings_get_pointer(settings, FreeRDP_MonitorIds);
+ /* String size: 10 char UINT32 max string length, 1 char separator, one element NULL */
+ size_t size = count * (10 + 1) + 1;
+
+ char* str = calloc(size, sizeof(char));
+ for (UINT32 x = 0; x < count; x++)
+ {
+ int rc = _snprintf(&str[offset], size - offset, "%" PRIu32 ",", MonitorIds[x]);
+ if (rc <= 0)
+ {
+ free(str);
+ return FALSE;
+ }
+ offset += (size_t)rc;
+ }
+ if (offset > 0)
+ str[offset - 1] = '\0';
+ freerdp_client_file_string_check_free(file->SelectedMonitors);
+ file->SelectedMonitors = str;
+ }
+
+ file->KeyboardHook = freerdp_settings_get_uint32(settings, FreeRDP_KeyboardHook);
+
+ return TRUE;
+}
+
+BOOL freerdp_client_write_rdp_file(const rdpFile* file, const char* name, BOOL unicode)
+{
+ FILE* fp = NULL;
+ size_t size = 0;
+ char* buffer = NULL;
+ int status = 0;
+ WCHAR* unicodestr = NULL;
+
+ if (!file || !name)
+ return FALSE;
+
+ size = freerdp_client_write_rdp_file_buffer(file, NULL, 0);
+ if (size == 0)
+ return FALSE;
+ buffer = (char*)calloc((size_t)(size + 1), sizeof(char));
+
+ if (freerdp_client_write_rdp_file_buffer(file, buffer, (size_t)size + 1) != size)
+ {
+ WLog_ERR(TAG, "freerdp_client_write_rdp_file: error writing to output buffer");
+ free(buffer);
+ return FALSE;
+ }
+
+ fp = winpr_fopen(name, "w+b");
+
+ if (fp)
+ {
+ if (unicode)
+ {
+ size_t len = 0;
+ unicodestr = ConvertUtf8NToWCharAlloc(buffer, size, &len);
+
+ if (!unicodestr)
+ {
+ free(buffer);
+ fclose(fp);
+ return FALSE;
+ }
+
+ /* Write multi-byte header */
+ if ((fwrite(BOM_UTF16_LE, sizeof(BYTE), 2, fp) != 2) ||
+ (fwrite(unicodestr, sizeof(WCHAR), len, fp) != len))
+ {
+ free(buffer);
+ free(unicodestr);
+ fclose(fp);
+ return FALSE;
+ }
+
+ free(unicodestr);
+ }
+ else
+ {
+ if (fwrite(buffer, 1, (size_t)size, fp) != (size_t)size)
+ {
+ free(buffer);
+ fclose(fp);
+ return FALSE;
+ }
+ }
+
+ fflush(fp);
+ status = fclose(fp);
+ }
+
+ free(buffer);
+ return (status == 0) ? TRUE : FALSE;
+}
+
+WINPR_ATTR_FORMAT_ARG(3, 4)
+static SSIZE_T freerdp_client_write_setting_to_buffer(char** buffer, size_t* bufferSize,
+ WINPR_FORMAT_ARG const char* fmt, ...)
+{
+ va_list ap;
+ SSIZE_T len = 0;
+ char* buf = NULL;
+ size_t bufSize = 0;
+
+ if (!buffer || !bufferSize || !fmt)
+ return -1;
+
+ buf = *buffer;
+ bufSize = *bufferSize;
+
+ va_start(ap, fmt);
+ len = vsnprintf(buf, bufSize, fmt, ap);
+ va_end(ap);
+ if (len < 0)
+ return -1;
+
+ /* _snprintf doesn't add the ending \0 to its return value */
+ ++len;
+
+ /* we just want to know the size - return it */
+ if (!buf && !bufSize)
+ return len;
+
+ if (!buf)
+ return -1;
+
+ /* update buffer size and buffer position and replace \0 with \n */
+ if (bufSize >= (size_t)len)
+ {
+ *bufferSize -= (size_t)len;
+ buf[len - 1] = '\n';
+ *buffer = buf + len;
+ }
+ else
+ return -1;
+
+ return len;
+}
+
+size_t freerdp_client_write_rdp_file_buffer(const rdpFile* file, char* buffer, size_t size)
+{
+ size_t totalSize = 0;
+
+ if (!file)
+ return 0;
+
+ /* either buffer and size are null or non-null */
+ if ((!buffer || !size) && (buffer || size))
+ return 0;
+
+#define WRITE_SETTING_(fmt_, ...) \
+ { \
+ SSIZE_T res = freerdp_client_write_setting_to_buffer(&buffer, &size, fmt_, __VA_ARGS__); \
+ if (res < 0) \
+ return 0; \
+ totalSize += (size_t)res; \
+ }
+
+#define WRITE_SETTING_INT(key_, param_) \
+ do \
+ { \
+ if (~(param_)) \
+ WRITE_SETTING_("%s:i:%" PRIu32, key_, param_) \
+ } while (0)
+
+#define WRITE_SETTING_STR(key_, param_) \
+ do \
+ { \
+ if (~(size_t)(param_)) \
+ WRITE_SETTING_("%s:s:%s", key_, param_) \
+ } while (0)
+
+ /* integer parameters */
+ WRITE_SETTING_INT(key_int_use_multimon, file->UseMultiMon);
+ WRITE_SETTING_INT(key_int_maximizetocurrentdisplays, file->MaximizeToCurrentDisplays);
+ WRITE_SETTING_INT(key_int_singlemoninwindowedmode, file->SingleMonInWindowedMode);
+ WRITE_SETTING_INT(key_int_screen_mode_id, file->ScreenModeId);
+ WRITE_SETTING_INT(key_int_span_monitors, file->SpanMonitors);
+ WRITE_SETTING_INT(key_int_smart_sizing, file->SmartSizing);
+ WRITE_SETTING_INT(key_int_dynamic_resolution, file->DynamicResolution);
+ WRITE_SETTING_INT(key_int_enablesuperpan, file->EnableSuperSpan);
+ WRITE_SETTING_INT(key_int_superpanaccelerationfactor, file->SuperSpanAccelerationFactor);
+ WRITE_SETTING_INT(key_int_desktopwidth, file->DesktopWidth);
+ WRITE_SETTING_INT(key_int_desktopheight, file->DesktopHeight);
+ WRITE_SETTING_INT(key_int_desktop_size_id, file->DesktopSizeId);
+ WRITE_SETTING_INT(key_int_session_bpp, file->SessionBpp);
+ WRITE_SETTING_INT(key_int_desktopscalefactor, file->DesktopScaleFactor);
+ WRITE_SETTING_INT(key_int_compression, file->Compression);
+ WRITE_SETTING_INT(key_int_keyboardhook, file->KeyboardHook);
+ WRITE_SETTING_INT(key_int_disable_ctrl_alt_del, file->DisableCtrlAltDel);
+ WRITE_SETTING_INT(key_int_audiomode, file->AudioMode);
+ WRITE_SETTING_INT(key_int_audioqualitymode, file->AudioQualityMode);
+ WRITE_SETTING_INT(key_int_audiocapturemode, file->AudioCaptureMode);
+ WRITE_SETTING_INT(key_int_encode_redirected_video_capture, file->EncodeRedirectedVideoCapture);
+ WRITE_SETTING_INT(key_int_redirected_video_capture_encoding_quality,
+ file->RedirectedVideoCaptureEncodingQuality);
+ WRITE_SETTING_INT(key_int_videoplaybackmode, file->VideoPlaybackMode);
+ WRITE_SETTING_INT(key_int_connection_type, file->ConnectionType);
+ WRITE_SETTING_INT(key_int_networkautodetect, file->NetworkAutoDetect);
+ WRITE_SETTING_INT(key_int_bandwidthautodetect, file->BandwidthAutoDetect);
+ WRITE_SETTING_INT(key_int_pinconnectionbar, file->PinConnectionBar);
+ WRITE_SETTING_INT(key_int_displayconnectionbar, file->DisplayConnectionBar);
+ WRITE_SETTING_INT(key_int_workspaceid, file->WorkspaceId);
+ WRITE_SETTING_INT(key_int_enableworkspacereconnect, file->EnableWorkspaceReconnect);
+ WRITE_SETTING_INT(key_int_disable_wallpaper, file->DisableWallpaper);
+ WRITE_SETTING_INT(key_int_allow_font_smoothing, file->AllowFontSmoothing);
+ WRITE_SETTING_INT(key_int_allow_desktop_composition, file->AllowDesktopComposition);
+ WRITE_SETTING_INT(key_int_disable_full_window_drag, file->DisableFullWindowDrag);
+ WRITE_SETTING_INT(key_int_disable_menu_anims, file->DisableMenuAnims);
+ WRITE_SETTING_INT(key_int_disable_themes, file->DisableThemes);
+ WRITE_SETTING_INT(key_int_disable_cursor_setting, file->DisableCursorSetting);
+ WRITE_SETTING_INT(key_int_bitmapcachesize, file->BitmapCacheSize);
+ WRITE_SETTING_INT(key_int_bitmapcachepersistenable, file->BitmapCachePersistEnable);
+ WRITE_SETTING_INT(key_int_server_port, file->ServerPort);
+ WRITE_SETTING_INT(key_int_redirectdrives, file->RedirectDrives);
+ WRITE_SETTING_INT(key_int_redirectprinters, file->RedirectPrinters);
+ WRITE_SETTING_INT(key_int_redirectcomports, file->RedirectComPorts);
+ WRITE_SETTING_INT(key_int_redirectlocation, file->RedirectLocation);
+ WRITE_SETTING_INT(key_int_redirectsmartcards, file->RedirectSmartCards);
+ WRITE_SETTING_INT(key_int_redirectclipboard, file->RedirectClipboard);
+ WRITE_SETTING_INT(key_int_redirectposdevices, file->RedirectPosDevices);
+ WRITE_SETTING_INT(key_int_redirectdirectx, file->RedirectDirectX);
+ WRITE_SETTING_INT(key_int_disableprinterredirection, file->DisablePrinterRedirection);
+ WRITE_SETTING_INT(key_int_disableclipboardredirection, file->DisableClipboardRedirection);
+ WRITE_SETTING_INT(key_int_connect_to_console, file->ConnectToConsole);
+ WRITE_SETTING_INT(key_int_administrative_session, file->AdministrativeSession);
+ WRITE_SETTING_INT(key_int_autoreconnection_enabled, file->AutoReconnectionEnabled);
+ WRITE_SETTING_INT(key_int_autoreconnect_max_retries, file->AutoReconnectMaxRetries);
+ WRITE_SETTING_INT(key_int_public_mode, file->PublicMode);
+ WRITE_SETTING_INT(key_int_authentication_level, file->AuthenticationLevel);
+ WRITE_SETTING_INT(key_int_promptcredentialonce, file->PromptCredentialOnce);
+ WRITE_SETTING_INT(key_int_prompt_for_credentials, file->PromptForCredentials);
+ WRITE_SETTING_INT(key_int_negotiate_security_layer, file->NegotiateSecurityLayer);
+ WRITE_SETTING_INT(key_int_enablecredsspsupport, file->EnableCredSSPSupport);
+ WRITE_SETTING_INT(key_int_enablerdsaadauth, file->EnableRdsAadAuth);
+ WRITE_SETTING_INT(key_int_remoteapplicationmode, file->RemoteApplicationMode);
+ WRITE_SETTING_INT(key_int_remoteapplicationexpandcmdline, file->RemoteApplicationExpandCmdLine);
+ WRITE_SETTING_INT(key_int_remoteapplicationexpandworkingdir,
+ file->RemoteApplicationExpandWorkingDir);
+ WRITE_SETTING_INT(key_int_disableconnectionsharing, file->DisableConnectionSharing);
+ WRITE_SETTING_INT(key_int_disableremoteappcapscheck, file->DisableRemoteAppCapsCheck);
+ WRITE_SETTING_INT(key_int_gatewayusagemethod, file->GatewayUsageMethod);
+ WRITE_SETTING_INT(key_int_gatewayprofileusagemethod, file->GatewayProfileUsageMethod);
+ WRITE_SETTING_INT(key_int_gatewaycredentialssource, file->GatewayCredentialsSource);
+ WRITE_SETTING_INT(key_int_use_redirection_server_name, file->UseRedirectionServerName);
+ WRITE_SETTING_INT(key_int_rdgiskdcproxy, file->RdgIsKdcProxy);
+ WRITE_SETTING_INT(key_int_redirectwebauthn, file->RedirectWebauthN);
+
+ /* string parameters */
+ WRITE_SETTING_STR(key_str_username, file->Username);
+ WRITE_SETTING_STR(key_str_domain, file->Domain);
+ WRITE_SETTING_STR(key_str_password, file->Password);
+ WRITE_SETTING_STR(key_str_full_address, file->FullAddress);
+ WRITE_SETTING_STR(key_str_alternate_full_address, file->AlternateFullAddress);
+ WRITE_SETTING_STR(key_str_usbdevicestoredirect, file->UsbDevicesToRedirect);
+ WRITE_SETTING_STR(key_str_camerastoredirect, file->RedirectCameras);
+ WRITE_SETTING_STR(key_str_loadbalanceinfo, file->LoadBalanceInfo);
+ WRITE_SETTING_STR(key_str_remoteapplicationname, file->RemoteApplicationName);
+ WRITE_SETTING_STR(key_str_remoteapplicationicon, file->RemoteApplicationIcon);
+ WRITE_SETTING_STR(key_str_remoteapplicationprogram, file->RemoteApplicationProgram);
+ WRITE_SETTING_STR(key_str_remoteapplicationfile, file->RemoteApplicationFile);
+ WRITE_SETTING_STR(key_str_remoteapplicationguid, file->RemoteApplicationGuid);
+ WRITE_SETTING_STR(key_str_remoteapplicationcmdline, file->RemoteApplicationCmdLine);
+ WRITE_SETTING_STR(key_str_alternate_shell, file->AlternateShell);
+ WRITE_SETTING_STR(key_str_shell_working_directory, file->ShellWorkingDirectory);
+ WRITE_SETTING_STR(key_str_gatewayhostname, file->GatewayHostname);
+ WRITE_SETTING_STR(key_str_resourceprovider, file->ResourceProvider);
+ WRITE_SETTING_STR(key_str_wvd, file->WvdEndpointPool);
+ WRITE_SETTING_STR(key_str_geo, file->geo);
+ WRITE_SETTING_STR(key_str_armpath, file->armpath);
+ WRITE_SETTING_STR(key_str_aadtenantid, file->aadtenantid);
+ WRITE_SETTING_STR(key_str_diagnosticserviceurl, file->diagnosticserviceurl);
+ WRITE_SETTING_STR(key_str_hubdiscoverygeourl, file->hubdiscoverygeourl);
+ WRITE_SETTING_STR(key_str_activityhint, file->activityhint);
+ WRITE_SETTING_STR(key_str_gatewayaccesstoken, file->GatewayAccessToken);
+ WRITE_SETTING_STR(key_str_kdcproxyname, file->KdcProxyName);
+ WRITE_SETTING_STR(key_str_drivestoredirect, file->DrivesToRedirect);
+ WRITE_SETTING_STR(key_str_devicestoredirect, file->DevicesToRedirect);
+ WRITE_SETTING_STR(key_str_winposstr, file->WinPosStr);
+ WRITE_SETTING_STR(key_str_pcb, file->PreconnectionBlob);
+ WRITE_SETTING_STR(key_str_selectedmonitors, file->SelectedMonitors);
+
+ /* custom parameters */
+ for (size_t i = 0; i < file->lineCount; ++i)
+ {
+ SSIZE_T res = -1;
+ const rdpFileLine* curLine = &file->lines[i];
+
+ if (curLine->flags & RDP_FILE_LINE_FLAG_TYPE_INTEGER)
+ res = freerdp_client_write_setting_to_buffer(&buffer, &size, "%s:i:%" PRIu32,
+ curLine->name, (UINT32)curLine->iValue);
+ else if (curLine->flags & RDP_FILE_LINE_FLAG_TYPE_STRING)
+ res = freerdp_client_write_setting_to_buffer(&buffer, &size, "%s:s:%s", curLine->name,
+ curLine->sValue);
+ if (res < 0)
+ return 0;
+
+ totalSize += (size_t)res;
+ }
+
+ return totalSize;
+}
+
+static ADDIN_ARGV* rdp_file_to_args(const char* channel, const char* values)
+{
+ size_t count = 0;
+ char** p = NULL;
+ ADDIN_ARGV* args = freerdp_addin_argv_new(0, NULL);
+ if (!args)
+ return NULL;
+ if (!freerdp_addin_argv_add_argument(args, channel))
+ goto fail;
+
+ p = CommandLineParseCommaSeparatedValues(values, &count);
+ for (size_t x = 0; x < count; x++)
+ {
+ BOOL rc = 0;
+ const char* val = p[x];
+ const size_t len = strlen(val) + 8;
+ char* str = calloc(len, sizeof(char));
+ if (!str)
+ goto fail;
+
+ _snprintf(str, len, "device:%s", val);
+ rc = freerdp_addin_argv_add_argument(args, str);
+ free(str);
+ if (!rc)
+ goto fail;
+ }
+ free(p);
+ return args;
+
+fail:
+ free(p);
+ freerdp_addin_argv_free(args);
+ return NULL;
+}
+
+BOOL freerdp_client_populate_settings_from_rdp_file(const rdpFile* file, rdpSettings* settings)
+{
+ BOOL setDefaultConnectionType = TRUE;
+
+ if (!file || !settings)
+ return FALSE;
+
+ if (~((size_t)file->Domain))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_Domain, file->Domain))
+ return FALSE;
+ }
+
+ if (~((size_t)file->Username))
+ {
+ char* user = NULL;
+ char* domain = NULL;
+
+ if (!freerdp_parse_username(file->Username, &user, &domain))
+ return FALSE;
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_Username, user))
+ return FALSE;
+
+ if (!(~((size_t)file->Domain)) && domain)
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_Domain, domain))
+ return FALSE;
+ }
+
+ free(user);
+ free(domain);
+ }
+
+ if (~((size_t)file->Password))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_Password, file->Password))
+ return FALSE;
+ }
+
+ {
+ const char* address = NULL;
+
+ /* With MSTSC alternate full address always wins,
+ * so mimic this. */
+ if (~((size_t)file->AlternateFullAddress))
+ address = file->AlternateFullAddress;
+ else if (~((size_t)file->FullAddress))
+ address = file->FullAddress;
+
+ if (address)
+ {
+ int port = -1;
+ char* host = NULL;
+
+ if (!freerdp_parse_hostname(address, &host, &port))
+ return FALSE;
+
+ const BOOL rc = freerdp_settings_set_string(settings, FreeRDP_ServerHostname, host);
+ free(host);
+ if (!rc)
+ return FALSE;
+
+ if (port > 0)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, (UINT32)port))
+ return FALSE;
+ }
+ }
+ }
+
+ if (~file->ServerPort)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, file->ServerPort))
+ return FALSE;
+ }
+
+ if (~file->DesktopSizeId)
+ {
+ switch (file->DesktopSizeId)
+ {
+ case 0:
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, 640))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, 480))
+ return FALSE;
+ break;
+ case 1:
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, 800))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, 600))
+ return FALSE;
+ break;
+ case 2:
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, 1024))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, 768))
+ return FALSE;
+ break;
+ case 3:
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, 1280))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, 1024))
+ return FALSE;
+ break;
+ case 4:
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, 1600))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, 1200))
+ return FALSE;
+ break;
+ default:
+ WLog_WARN(TAG, "Unsupported 'desktop size id' value %" PRIu32, file->DesktopSizeId);
+ break;
+ }
+ }
+ if (~file->DesktopWidth)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, file->DesktopWidth))
+ return FALSE;
+ }
+
+ if (~file->DesktopHeight)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, file->DesktopHeight))
+ return FALSE;
+ }
+
+ if (~file->SessionBpp)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, file->SessionBpp))
+ return FALSE;
+ }
+
+ if (~file->ConnectToConsole)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ConsoleSession,
+ file->ConnectToConsole != 0))
+ return FALSE;
+ }
+
+ if (~file->AdministrativeSession)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_ConsoleSession,
+ file->AdministrativeSession != 0))
+ return FALSE;
+ }
+
+ if (~file->NegotiateSecurityLayer)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NegotiateSecurityLayer,
+ file->NegotiateSecurityLayer != 0))
+ return FALSE;
+ }
+
+ if (~file->EnableCredSSPSupport)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity,
+ file->EnableCredSSPSupport != 0))
+ return FALSE;
+ }
+
+ if (~file->EnableRdsAadAuth)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AadSecurity, file->EnableRdsAadAuth != 0))
+ return FALSE;
+ }
+
+ if (~((size_t)file->AlternateShell))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_AlternateShell, file->AlternateShell))
+ return FALSE;
+ }
+
+ if (~((size_t)file->ShellWorkingDirectory))
+ {
+ /* ShellWorkingDir is used for either, shell working dir or remote app working dir */
+ FreeRDP_Settings_Keys_String targetId =
+ (~file->RemoteApplicationMode && file->RemoteApplicationMode != 0)
+ ? FreeRDP_RemoteApplicationWorkingDir
+ : FreeRDP_ShellWorkingDirectory;
+
+ if (!freerdp_settings_set_string(settings, targetId, file->ShellWorkingDirectory))
+ return FALSE;
+ }
+
+ if (~file->ScreenModeId)
+ {
+ /**
+ * Screen Mode Id:
+ * http://technet.microsoft.com/en-us/library/ff393692/
+ *
+ * This setting corresponds to the selection in the Display
+ * configuration slider on the Display tab under Options in RDC.
+ *
+ * Values:
+ *
+ * 1: The remote session will appear in a window.
+ * 2: The remote session will appear full screen.
+ */
+ if (!freerdp_settings_set_bool(settings, FreeRDP_Fullscreen,
+ (file->ScreenModeId == 2) ? TRUE : FALSE))
+ return FALSE;
+ }
+
+ if (~(file->SmartSizing))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SmartSizing,
+ (file->SmartSizing == 1) ? TRUE : FALSE))
+ return FALSE;
+ /**
+ * SmartSizingWidth and SmartSizingHeight:
+ *
+ * Adding this option to use the DesktopHeight and DesktopWidth as
+ * parameters for the SmartSizingWidth and SmartSizingHeight, as there
+ * are no options for that in standard RDP files.
+ *
+ * Equivalent of doing /smart-sizing:WxH
+ */
+ if (((~(file->DesktopWidth) && ~(file->DesktopHeight)) || ~(file->DesktopSizeId)) &&
+ (file->SmartSizing == 1))
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_SmartSizingWidth,
+ file->DesktopWidth))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_SmartSizingHeight,
+ file->DesktopHeight))
+ return FALSE;
+ }
+ }
+
+ if (~((size_t)file->LoadBalanceInfo))
+ {
+ const size_t len = strlen(file->LoadBalanceInfo);
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_LoadBalanceInfo,
+ file->LoadBalanceInfo, len))
+ return FALSE;
+ }
+
+ if (~file->AuthenticationLevel)
+ {
+ /**
+ * Authentication Level:
+ * http://technet.microsoft.com/en-us/library/ff393709/
+ *
+ * This setting corresponds to the selection in the If server authentication
+ * fails drop-down list on the Advanced tab under Options in RDC.
+ *
+ * Values:
+ *
+ * 0: If server authentication fails, connect to the computer without warning (Connect and
+ * don’t warn me). 1: If server authentication fails, do not establish a connection (Do not
+ * connect). 2: If server authentication fails, show a warning and allow me to connect or
+ * refuse the connection (Warn me). 3: No authentication requirement is specified.
+ */
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_AuthenticationLevel,
+ file->AuthenticationLevel))
+ return FALSE;
+ }
+
+ if (~file->ConnectionType)
+ {
+ if (!freerdp_set_connection_type(settings, file->ConnectionType))
+ return FALSE;
+ setDefaultConnectionType = FALSE;
+ }
+
+ if (~file->AudioMode)
+ {
+ switch (file->AudioMode)
+ {
+ case AUDIO_MODE_REDIRECT:
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteConsoleAudio, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AudioPlayback, TRUE))
+ return FALSE;
+ break;
+ case AUDIO_MODE_PLAY_ON_SERVER:
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteConsoleAudio, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AudioPlayback, FALSE))
+ return FALSE;
+ break;
+ case AUDIO_MODE_NONE:
+ default:
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AudioPlayback, FALSE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteConsoleAudio, FALSE))
+ return FALSE;
+ break;
+ }
+ }
+
+ if (~file->AudioCaptureMode)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AudioCapture, file->AudioCaptureMode != 0))
+ return FALSE;
+ }
+
+ if (~file->Compression)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_CompressionEnabled,
+ file->Compression != 0))
+ return FALSE;
+ }
+
+ if (~((size_t)file->GatewayHostname))
+ {
+ int port = -1;
+ char* host = NULL;
+
+ if (!freerdp_parse_hostname(file->GatewayHostname, &host, &port))
+ return FALSE;
+
+ const BOOL rc = freerdp_settings_set_string(settings, FreeRDP_GatewayHostname, host);
+ free(host);
+ if (!rc)
+ return FALSE;
+
+ if (port > 0)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_GatewayPort, (UINT32)port))
+ return FALSE;
+ }
+ }
+
+ if (~((size_t)file->ResourceProvider))
+ {
+ if (_stricmp(file->ResourceProvider, str_resourceprovider_arm) == 0)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayArmTransport, TRUE))
+ return FALSE;
+ }
+ }
+
+ if (~((size_t)file->WvdEndpointPool))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAvdWvdEndpointPool,
+ file->WvdEndpointPool))
+ return FALSE;
+ }
+
+ if (~((size_t)file->geo))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAvdGeo, file->geo))
+ return FALSE;
+ }
+
+ if (~((size_t)file->armpath))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAvdArmpath, file->armpath))
+ return FALSE;
+ }
+
+ if (~((size_t)file->aadtenantid))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAvdAadtenantid,
+ file->aadtenantid))
+ return FALSE;
+ }
+
+ if (~((size_t)file->diagnosticserviceurl))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAvdDiagnosticserviceurl,
+ file->diagnosticserviceurl))
+ return FALSE;
+ }
+
+ if (~((size_t)file->hubdiscoverygeourl))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAvdHubdiscoverygeourl,
+ file->hubdiscoverygeourl))
+ return FALSE;
+ }
+
+ if (~((size_t)file->activityhint))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAvdActivityhint,
+ file->activityhint))
+ return FALSE;
+ }
+
+ if (~((size_t)file->GatewayAccessToken))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAccessToken,
+ file->GatewayAccessToken))
+ return FALSE;
+ }
+
+ if (~file->GatewayUsageMethod)
+ {
+ if (!freerdp_set_gateway_usage_method(settings, file->GatewayUsageMethod))
+ return FALSE;
+ }
+
+ if (~file->PromptCredentialOnce)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayUseSameCredentials,
+ file->PromptCredentialOnce != 0))
+ return FALSE;
+ }
+
+ if (~file->PromptForCredentials)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_PromptForCredentials,
+ file->PromptForCredentials != 0))
+ return FALSE;
+ }
+
+ if (~file->RemoteApplicationMode)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteApplicationMode,
+ file->RemoteApplicationMode != 0))
+ return FALSE;
+ }
+
+ if (~((size_t)file->RemoteApplicationProgram))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationProgram,
+ file->RemoteApplicationProgram))
+ return FALSE;
+ }
+
+ if (~((size_t)file->RemoteApplicationName))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationName,
+ file->RemoteApplicationName))
+ return FALSE;
+ }
+
+ if (~((size_t)file->RemoteApplicationIcon))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationIcon,
+ file->RemoteApplicationIcon))
+ return FALSE;
+ }
+
+ if (~((size_t)file->RemoteApplicationFile))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationFile,
+ file->RemoteApplicationFile))
+ return FALSE;
+ }
+
+ if (~((size_t)file->RemoteApplicationGuid))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationGuid,
+ file->RemoteApplicationGuid))
+ return FALSE;
+ }
+
+ if (~((size_t)file->RemoteApplicationCmdLine))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationCmdLine,
+ file->RemoteApplicationCmdLine))
+ return FALSE;
+ }
+
+ if (~file->SpanMonitors)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SpanMonitors, file->SpanMonitors != 0))
+ return FALSE;
+ }
+
+ if (~file->UseMultiMon)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UseMultimon, file->UseMultiMon != 0))
+ return FALSE;
+ }
+
+ if (~file->AllowFontSmoothing)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AllowFontSmoothing,
+ file->AllowFontSmoothing != 0))
+ return FALSE;
+ }
+
+ if (~file->DisableWallpaper)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableWallpaper,
+ file->DisableWallpaper != 0))
+ return FALSE;
+ }
+
+ if (~file->DisableFullWindowDrag)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableFullWindowDrag,
+ file->DisableFullWindowDrag != 0))
+ return FALSE;
+ }
+
+ if (~file->DisableMenuAnims)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableMenuAnims,
+ file->DisableMenuAnims != 0))
+ return FALSE;
+ }
+
+ if (~file->DisableThemes)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableThemes, file->DisableThemes != 0))
+ return FALSE;
+ }
+
+ if (~file->AllowDesktopComposition)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AllowDesktopComposition,
+ file->AllowDesktopComposition != 0))
+ return FALSE;
+ }
+
+ if (~file->BitmapCachePersistEnable)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCachePersistEnabled,
+ file->BitmapCachePersistEnable != 0))
+ return FALSE;
+ }
+
+ if (~file->DisableRemoteAppCapsCheck)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DisableRemoteAppCapsCheck,
+ file->DisableRemoteAppCapsCheck != 0))
+ return FALSE;
+ }
+
+ if (~file->BandwidthAutoDetect)
+ {
+ if (file->BandwidthAutoDetect != 0)
+ {
+ if ((~file->NetworkAutoDetect) && (file->NetworkAutoDetect == 0))
+ {
+ WLog_WARN(TAG,
+ "Got networkautodetect:i:%" PRIu32 " and bandwidthautodetect:i:%" PRIu32
+ ". Correcting to networkautodetect:i:1",
+ file->NetworkAutoDetect, file->BandwidthAutoDetect);
+ WLog_WARN(TAG,
+ "Add networkautodetect:i:1 to your RDP file to eliminate this warning.");
+ }
+
+ if (!freerdp_set_connection_type(settings, CONNECTION_TYPE_AUTODETECT))
+ return FALSE;
+ setDefaultConnectionType = FALSE;
+ }
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NetworkAutoDetect,
+ (file->BandwidthAutoDetect != 0) ||
+ (file->NetworkAutoDetect != 0)))
+ return FALSE;
+ }
+
+ if (~file->NetworkAutoDetect)
+ {
+ if (file->NetworkAutoDetect != 0)
+ {
+ if ((~file->BandwidthAutoDetect) && (file->BandwidthAutoDetect == 0))
+ {
+ WLog_WARN(TAG,
+ "Got networkautodetect:i:%" PRIu32 " and bandwidthautodetect:i:%" PRIu32
+ ". Correcting to bandwidthautodetect:i:1",
+ file->NetworkAutoDetect, file->BandwidthAutoDetect);
+ WLog_WARN(
+ TAG, "Add bandwidthautodetect:i:1 to your RDP file to eliminate this warning.");
+ }
+
+ if (!freerdp_set_connection_type(settings, CONNECTION_TYPE_AUTODETECT))
+ return FALSE;
+
+ setDefaultConnectionType = FALSE;
+ }
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NetworkAutoDetect,
+ (file->BandwidthAutoDetect != 0) ||
+ (file->NetworkAutoDetect != 0)))
+ return FALSE;
+ }
+
+ if (~file->AutoReconnectionEnabled)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AutoReconnectionEnabled,
+ file->AutoReconnectionEnabled != 0))
+ return FALSE;
+ }
+
+ if (~file->AutoReconnectMaxRetries)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_AutoReconnectMaxRetries,
+ file->AutoReconnectMaxRetries))
+ return FALSE;
+ }
+
+ if (~file->RedirectSmartCards)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectSmartCards,
+ file->RedirectSmartCards != 0))
+ return FALSE;
+ }
+
+ if (~file->RedirectWebauthN)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectWebAuthN,
+ file->RedirectWebauthN != 0))
+ return FALSE;
+ }
+
+ if (~file->RedirectClipboard)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectClipboard,
+ file->RedirectClipboard != 0))
+ return FALSE;
+ }
+
+ if (~file->RedirectPrinters)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectPrinters,
+ file->RedirectPrinters != 0))
+ return FALSE;
+ }
+
+ if (~file->RedirectDrives)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectDrives, file->RedirectDrives != 0))
+ return FALSE;
+ }
+
+ if (~file->RedirectPosDevices)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectSerialPorts,
+ file->RedirectComPorts != 0) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_RedirectParallelPorts,
+ file->RedirectComPorts != 0))
+ return FALSE;
+ }
+
+ if (~file->RedirectComPorts)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectSerialPorts,
+ file->RedirectComPorts != 0) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_RedirectParallelPorts,
+ file->RedirectComPorts != 0))
+ return FALSE;
+ }
+
+ if (~file->RedirectLocation)
+ {
+ size_t count = 0;
+ char** str =
+ CommandLineParseCommaSeparatedValuesEx(LOCATION_DVC_CHANNEL_NAME, NULL, &count);
+ const BOOL rc = freerdp_client_add_dynamic_channel(settings, count, str);
+ free(str);
+ if (!rc)
+ return FALSE;
+ }
+
+ if (~file->RedirectDirectX)
+ {
+ /* What is this?! */
+ }
+
+ if (~((size_t)file->DevicesToRedirect))
+ {
+ /**
+ * Devices to redirect:
+ * http://technet.microsoft.com/en-us/library/ff393728/
+ *
+ * This setting corresponds to the selections for Other supported Plug and Play
+ * (PnP) devices under More on the Local Resources tab under Options in RDC.
+ *
+ * Values:
+ *
+ * '*':
+ * Redirect all supported Plug and Play devices.
+ *
+ * 'DynamicDevices':
+ * Redirect any supported Plug and Play devices that are connected later.
+ *
+ * The hardware ID for the supported Plug and Play device:
+ * Redirect the specified supported Plug and Play device.
+ *
+ * Examples:
+ * devicestoredirect:s:*
+ * devicestoredirect:s:DynamicDevices
+ * devicestoredirect:s:USB\VID_04A9&PID_30C1\6&4BD985D&0&2;,DynamicDevices
+ *
+ */
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return FALSE;
+ }
+
+ if (~((size_t)file->DrivesToRedirect))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_DrivesToRedirect,
+ file->DrivesToRedirect))
+ return FALSE;
+ }
+
+ if (~((size_t)file->RedirectCameras))
+ {
+#if defined(CHANNEL_RDPECAM_CLIENT)
+ union
+ {
+ char** c;
+ const char** cc;
+ } cnv;
+ ADDIN_ARGV* args = rdp_file_to_args(RDPECAM_DVC_CHANNEL_NAME, file->RedirectCameras);
+ if (!args)
+ return FALSE;
+
+ if (~file->EncodeRedirectedVideoCapture)
+ {
+ char encode[64];
+ _snprintf(encode, sizeof(encode), "encode:%" PRIu32,
+ file->EncodeRedirectedVideoCapture);
+ freerdp_addin_argv_add_argument(args, encode);
+ }
+ if (~file->RedirectedVideoCaptureEncodingQuality)
+ {
+ char quality[64];
+ _snprintf(quality, sizeof(quality), "quality:%" PRIu32,
+ file->RedirectedVideoCaptureEncodingQuality);
+ freerdp_addin_argv_add_argument(args, quality);
+ }
+
+ cnv.c = args->argv;
+ const BOOL status = freerdp_client_add_dynamic_channel(settings, args->argc, cnv.cc);
+ freerdp_addin_argv_free(args);
+ if (!status)
+ return FALSE;
+#else
+ WLog_WARN(
+ TAG,
+ "This build does not support [MS-RDPECAM] camera redirection channel. Ignoring '%s'",
+ key_str_camerastoredirect);
+#endif
+ }
+
+ if (~((size_t)file->UsbDevicesToRedirect))
+ {
+#ifdef CHANNEL_URBDRC_CLIENT
+ union
+ {
+ char** c;
+ const char** cc;
+ } cnv;
+ ADDIN_ARGV* args = rdp_file_to_args(URBDRC_CHANNEL_NAME, file->UsbDevicesToRedirect);
+ if (!args)
+ return FALSE;
+ cnv.c = args->argv;
+ const BOOL status = freerdp_client_add_dynamic_channel(settings, args->argc, cnv.cc);
+ freerdp_addin_argv_free(args);
+ if (!status)
+ return FALSE;
+#else
+ WLog_WARN(TAG,
+ "This build does not support [MS-RDPEUSB] usb redirection channel. Ignoring '%s'",
+ key_str_usbdevicestoredirect);
+#endif
+ }
+
+ if (~file->KeyboardHook)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardHook, file->KeyboardHook))
+ return FALSE;
+ }
+
+ if (~(size_t)file->SelectedMonitors)
+ {
+ size_t count = 0;
+ char** args = CommandLineParseCommaSeparatedValues(file->SelectedMonitors, &count);
+ UINT32* list = NULL;
+
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_MonitorIds, NULL, count))
+ {
+ free(args);
+ return FALSE;
+ }
+ list = freerdp_settings_get_pointer_writable(settings, FreeRDP_MonitorIds);
+ if (!list && (count > 0))
+ {
+ free(args);
+ return FALSE;
+ }
+ for (size_t x = 0; x < count; x++)
+ {
+ unsigned long val = 0;
+ errno = 0;
+ val = strtoul(args[x], NULL, 0);
+ if ((val >= UINT32_MAX) && (errno != 0))
+ {
+ free(args);
+ free(list);
+ return FALSE;
+ }
+ list[x] = val;
+ }
+ free(args);
+ }
+
+ if (~file->DynamicResolution)
+ {
+ const BOOL val = file->DynamicResolution != 0;
+ if (val)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportDisplayControl, TRUE))
+ return FALSE;
+ }
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DynamicResolutionUpdate, val))
+ return FALSE;
+ }
+
+ if (~file->DesktopScaleFactor)
+ {
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopScaleFactor,
+ file->DesktopScaleFactor))
+ return FALSE;
+ }
+
+ if (~file->VideoPlaybackMode)
+ {
+ if (file->VideoPlaybackMode != 0)
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGeometryTracking, TRUE) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_SupportVideoOptimized, TRUE))
+ return FALSE;
+ }
+ else
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportVideoOptimized, FALSE))
+ return FALSE;
+ }
+ }
+ // TODO file->MaximizeToCurrentDisplays;
+ // TODO file->SingleMonInWindowedMode;
+ // TODO file->EncodeRedirectedVideoCapture;
+ // TODO file->RedirectedVideoCaptureEncodingQuality;
+
+ if (~((size_t)file->PreconnectionBlob))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_PreconnectionBlob,
+ file->PreconnectionBlob) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_SendPreconnectionPdu, TRUE))
+ return FALSE;
+ }
+
+ if (~((size_t)file->KdcProxyName))
+ {
+ if (!freerdp_settings_set_string(settings, FreeRDP_KerberosKdcUrl, file->KdcProxyName))
+ return FALSE;
+ }
+
+ if (~((size_t)file->RdgIsKdcProxy))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_KerberosRdgIsProxy,
+ file->RdgIsKdcProxy != 0))
+ return FALSE;
+ }
+
+ if (file->args->argc > 1)
+ {
+ WCHAR* ConnectionFile =
+ freerdp_settings_get_string_as_utf16(settings, FreeRDP_ConnectionFile, NULL);
+
+ if (freerdp_client_settings_parse_command_line(settings, file->args->argc, file->args->argv,
+ FALSE) < 0)
+ {
+ free(ConnectionFile);
+ return FALSE;
+ }
+
+ BOOL rc = freerdp_settings_set_string_from_utf16(settings, FreeRDP_ConnectionFile,
+ ConnectionFile);
+ free(ConnectionFile);
+ if (!rc)
+ return FALSE;
+ }
+
+ if (setDefaultConnectionType)
+ {
+ if (!freerdp_set_connection_type(settings, CONNECTION_TYPE_AUTODETECT))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static rdpFileLine* freerdp_client_rdp_file_find_line_by_name(const rdpFile* file, const char* name)
+{
+ BOOL bFound = FALSE;
+ rdpFileLine* line = NULL;
+
+ for (size_t index = 0; index < file->lineCount; index++)
+ {
+ line = &(file->lines[index]);
+
+ if (line->flags & RDP_FILE_LINE_FLAG_FORMATTED)
+ {
+ if (_stricmp(name, line->name) == 0)
+ {
+ bFound = TRUE;
+ break;
+ }
+ }
+ }
+
+ return (bFound) ? line : NULL;
+}
+/**
+ * Set a string option to a rdpFile
+ * @param file rdpFile
+ * @param name name of the option
+ * @param value value of the option
+ * @return 0 on success
+ */
+int freerdp_client_rdp_file_set_string_option(rdpFile* file, const char* name, const char* value)
+{
+ return freerdp_client_rdp_file_set_string(file, name, value);
+}
+
+const char* freerdp_client_rdp_file_get_string_option(const rdpFile* file, const char* name)
+{
+ LPSTR* value = NULL;
+ rdpFileLine* line = NULL;
+
+ if (freerdp_client_rdp_file_find_string_entry((rdpFile*)file, name, &value, &line))
+ {
+ if (value && ~(size_t)(*value))
+ return *value;
+ if (line)
+ return line->sValue;
+ }
+
+ return NULL;
+}
+
+int freerdp_client_rdp_file_set_integer_option(rdpFile* file, const char* name, int value)
+{
+ return freerdp_client_rdp_file_set_integer(file, name, value);
+}
+
+int freerdp_client_rdp_file_get_integer_option(const rdpFile* file, const char* name)
+{
+ DWORD* value = NULL;
+ rdpFileLine* line = NULL;
+
+ if (freerdp_client_rdp_file_find_integer_entry((rdpFile*)file, name, &value, &line))
+ {
+ if (value && ~(*value))
+ return *value;
+ if (line)
+ return (int)line->iValue;
+ }
+
+ return -1;
+}
+
+static void freerdp_client_file_string_check_free(LPSTR str)
+{
+ if (~((size_t)str))
+ free(str);
+}
+
+rdpFile* freerdp_client_rdp_file_new(void)
+{
+ return freerdp_client_rdp_file_new_ex(0);
+}
+
+rdpFile* freerdp_client_rdp_file_new_ex(DWORD flags)
+{
+ rdpFile* file = (rdpFile*)calloc(1, sizeof(rdpFile));
+
+ if (!file)
+ return NULL;
+
+ file->flags = flags;
+
+ FillMemory(file, sizeof(rdpFile), 0xFF);
+ file->lines = NULL;
+ file->lineCount = 0;
+ file->lineSize = 32;
+ file->GatewayProfileUsageMethod = 1;
+ file->lines = (rdpFileLine*)calloc(file->lineSize, sizeof(rdpFileLine));
+
+ file->args = freerdp_addin_argv_new(0, NULL);
+ if (!file->lines || !file->args)
+ goto fail;
+
+ if (!freerdp_client_add_option(file, "freerdp"))
+ goto fail;
+
+ return file;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ freerdp_client_rdp_file_free(file);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+void freerdp_client_rdp_file_free(rdpFile* file)
+{
+ if (file)
+ {
+ if (file->lineCount)
+ {
+ for (size_t i = 0; i < file->lineCount; i++)
+ {
+ free(file->lines[i].name);
+ free(file->lines[i].sValue);
+ }
+ }
+ free(file->lines);
+
+ freerdp_addin_argv_free(file->args);
+
+ freerdp_client_file_string_check_free(file->Username);
+ freerdp_client_file_string_check_free(file->Domain);
+ freerdp_client_file_string_check_free(file->Password);
+ freerdp_client_file_string_check_free(file->FullAddress);
+ freerdp_client_file_string_check_free(file->AlternateFullAddress);
+ freerdp_client_file_string_check_free(file->UsbDevicesToRedirect);
+ freerdp_client_file_string_check_free(file->RedirectCameras);
+ freerdp_client_file_string_check_free(file->SelectedMonitors);
+ freerdp_client_file_string_check_free(file->LoadBalanceInfo);
+ freerdp_client_file_string_check_free(file->RemoteApplicationName);
+ freerdp_client_file_string_check_free(file->RemoteApplicationIcon);
+ freerdp_client_file_string_check_free(file->RemoteApplicationProgram);
+ freerdp_client_file_string_check_free(file->RemoteApplicationFile);
+ freerdp_client_file_string_check_free(file->RemoteApplicationGuid);
+ freerdp_client_file_string_check_free(file->RemoteApplicationCmdLine);
+ freerdp_client_file_string_check_free(file->AlternateShell);
+ freerdp_client_file_string_check_free(file->ShellWorkingDirectory);
+ freerdp_client_file_string_check_free(file->GatewayHostname);
+ freerdp_client_file_string_check_free(file->GatewayAccessToken);
+ freerdp_client_file_string_check_free(file->KdcProxyName);
+ freerdp_client_file_string_check_free(file->DrivesToRedirect);
+ freerdp_client_file_string_check_free(file->DevicesToRedirect);
+ freerdp_client_file_string_check_free(file->WinPosStr);
+ freerdp_client_file_string_check_free(file->ResourceProvider);
+ freerdp_client_file_string_check_free(file->WvdEndpointPool);
+ freerdp_client_file_string_check_free(file->geo);
+ freerdp_client_file_string_check_free(file->armpath);
+ freerdp_client_file_string_check_free(file->aadtenantid);
+ freerdp_client_file_string_check_free(file->diagnosticserviceurl);
+ freerdp_client_file_string_check_free(file->hubdiscoverygeourl);
+ freerdp_client_file_string_check_free(file->activityhint);
+ free(file);
+ }
+}
+
+void freerdp_client_rdp_file_set_callback_context(rdpFile* file, void* context)
+{
+ file->context = context;
+}
diff --git a/client/common/geometry.c b/client/common/geometry.c
new file mode 100644
index 0000000..83347ea
--- /dev/null
+++ b/client/common/geometry.c
@@ -0,0 +1,43 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Geometry tracking Virtual Channel Extension
+ *
+ * Copyright 2017 David Fort <contact@hardening-consulting.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <freerdp/client/geometry.h>
+#include <winpr/interlocked.h>
+
+void mappedGeometryRef(MAPPED_GEOMETRY* g)
+{
+ InterlockedIncrement(&g->refCounter);
+}
+
+void mappedGeometryUnref(MAPPED_GEOMETRY* g)
+{
+ if (!g)
+ return;
+
+ if (InterlockedDecrement(&g->refCounter))
+ return;
+
+ g->MappedGeometryUpdate = NULL;
+ g->MappedGeometryClear = NULL;
+ g->custom = NULL;
+ free(g->geometry.rects);
+ free(g);
+}
diff --git a/client/common/man/CMakeLists.txt b/client/common/man/CMakeLists.txt
new file mode 100644
index 0000000..b601f1d
--- /dev/null
+++ b/client/common/man/CMakeLists.txt
@@ -0,0 +1,3 @@
+add_executable(generate_argument_docbook
+ generate_argument_docbook.c
+)
diff --git a/client/common/man/generate_argument_docbook.c b/client/common/man/generate_argument_docbook.c
new file mode 100644
index 0000000..156d809
--- /dev/null
+++ b/client/common/man/generate_argument_docbook.c
@@ -0,0 +1,210 @@
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+
+#include "../cmdline.h"
+
+static char* resize(char** buffer, size_t* size, size_t increment)
+{
+ const size_t nsize = *size + increment;
+ char* tmp = realloc(*buffer, nsize);
+ if (!tmp)
+ {
+ fprintf(stderr, "Could not reallocate string buffer from %" PRIuz " to %" PRIuz " bytes.\n",
+ *size, nsize);
+ free(*buffer);
+ }
+ memset(&tmp[*size], '\0', increment);
+ *size = nsize;
+ *buffer = tmp;
+ return tmp;
+}
+
+static char* append(char** buffer, size_t* size, const char* str)
+{
+ const size_t len = strnlen(*buffer, *size);
+ const size_t add = strlen(str);
+ const size_t required = len + add + 1;
+
+ if (required > *size)
+ {
+ if (!resize(buffer, size, required - *size))
+ return NULL;
+ }
+ strcat(*buffer, str);
+ return *buffer;
+}
+
+static LPSTR tr_esc_str(LPCSTR arg, bool format)
+{
+ const char* str = NULL;
+ LPSTR tmp = NULL;
+ size_t ds = 0;
+
+ if (NULL == arg)
+ return NULL;
+
+ const size_t s = strlen(arg) + 1;
+ if (!resize(&tmp, &ds, s))
+ exit(-2);
+
+ for (size_t x = 0; x < s; x++)
+ {
+ char data[2] = { 0 };
+ switch (arg[x])
+ {
+ case '<':
+ if (format)
+ str = "<replaceable>";
+ else
+ str = "&lt;";
+
+ if (!append(&tmp, &ds, str))
+ exit(-3);
+ break;
+
+ case '>':
+ if (format)
+ str = "</replaceable>";
+ else
+ str = "&gt;";
+
+ if (!append(&tmp, &ds, str))
+ exit(-4);
+ break;
+
+ case '\'':
+ if (!append(&tmp, &ds, "&apos;"))
+ exit(-5);
+ break;
+
+ case '"':
+ if (!append(&tmp, &ds, "&quot;"))
+ exit(-6);
+ break;
+
+ case '&':
+ if (!append(&tmp, &ds, "&amp;"))
+ exit(-6);
+ break;
+
+ case '\r':
+ case '\n':
+ if (!append(&tmp, &ds, "<sbr/>"))
+ exit(-7);
+ break;
+
+ default:
+ data[0] = arg[x];
+ if (!append(&tmp, &ds, data))
+ exit(-8);
+ break;
+ }
+ }
+
+ return tmp;
+}
+
+int main(int argc, char* argv[])
+{
+ size_t elements = sizeof(global_cmd_args) / sizeof(global_cmd_args[0]);
+ const char* fname = "freerdp-argument.1.xml";
+
+ fprintf(stdout, "Generating docbook file '%s'\n", fname);
+ FILE* fp = fopen(fname, "w");
+ if (NULL == fp)
+ {
+ fprintf(stderr, "Could not open '%s' for writing.\n", fname);
+ return -1;
+ }
+
+ /* The tag used as header in the manpage */
+ fprintf(fp, "<refsect1>\n");
+ fprintf(fp, "\t<title>Options</title>\n");
+ fprintf(fp, "\t\t<variablelist>\n");
+
+ /* Iterate over argument struct and write data to docbook 4.5
+ * compatible XML */
+ if (elements < 2)
+ {
+ fprintf(stderr, "The argument array 'args' is empty, writing an empty file.\n");
+ elements = 1;
+ }
+
+ for (size_t x = 0; x < elements - 1; x++)
+ {
+ const COMMAND_LINE_ARGUMENT_A* arg = &global_cmd_args[x];
+ char* name = tr_esc_str(arg->Name, FALSE);
+ char* alias = tr_esc_str(arg->Alias, FALSE);
+ char* format = tr_esc_str(arg->Format, TRUE);
+ char* text = tr_esc_str(arg->Text, FALSE);
+ fprintf(fp, "\t\t\t<varlistentry>\n");
+
+ do
+ {
+ fprintf(fp, "\t\t\t\t<term><option>");
+
+ if (arg->Flags == COMMAND_LINE_VALUE_BOOL)
+ fprintf(fp, "%s", arg->Default ? "-" : "+");
+ else
+ fprintf(fp, "/");
+
+ fprintf(fp, "%s</option>", name);
+
+ if (format)
+ {
+ if (arg->Flags == COMMAND_LINE_VALUE_OPTIONAL)
+ fprintf(fp, "[");
+
+ fprintf(fp, ":%s", format);
+
+ if (arg->Flags == COMMAND_LINE_VALUE_OPTIONAL)
+ fprintf(fp, "]");
+ }
+
+ fprintf(fp, "</term>\n");
+
+ if (alias == name)
+ break;
+
+ free(name);
+ name = alias;
+ } while (alias);
+
+ if (text)
+ {
+ fprintf(fp, "\t\t\t\t<listitem>\n");
+ fprintf(fp, "\t\t\t\t\t<para>");
+
+ if (text)
+ fprintf(fp, "%s", text);
+
+ if (arg->Flags & COMMAND_LINE_VALUE_BOOL &&
+ (!arg->Default || arg->Default == BoolValueTrue))
+ fprintf(fp, " (default:%s)", arg->Default ? "on" : "off");
+ else if (arg->Default)
+ {
+ char* value = tr_esc_str(arg->Default, FALSE);
+ fprintf(fp, " (default:%s)", value);
+ free(value);
+ }
+
+ fprintf(fp, "</para>\n");
+ fprintf(fp, "\t\t\t\t</listitem>\n");
+ }
+
+ fprintf(fp, "\t\t\t</varlistentry>\n");
+ free(name);
+ free(format);
+ free(text);
+ }
+
+ fprintf(fp, "\t\t</variablelist>\n");
+ fprintf(fp, "\t</refsect1>\n");
+ fclose(fp);
+
+ fprintf(stdout, "successfully generated '%s'\n", fname);
+ return 0;
+}
diff --git a/client/common/smartcard_cli.c b/client/common/smartcard_cli.c
new file mode 100644
index 0000000..2832e92
--- /dev/null
+++ b/client/common/smartcard_cli.c
@@ -0,0 +1,60 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Smartcard client functions
+ *
+ * Copyright 2021 David Fort <contact@hardening-consulting.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <winpr/assert.h>
+
+#include <freerdp/client/utils/smartcard_cli.h>
+#include <freerdp/utils/smartcardlogon.h>
+
+BOOL freerdp_smartcard_list(const rdpSettings* settings)
+{
+ SmartcardCertInfo** certs = NULL;
+ size_t count = 0;
+
+ if (!smartcard_enumerateCerts(settings, &certs, &count, FALSE))
+ return FALSE;
+
+ printf("smartcard reader detected, listing %" PRIuz " certificates:\n", count);
+ for (size_t i = 0; i < count; i++)
+ {
+ const SmartcardCertInfo* info = certs[i];
+ char asciiStr[256] = { 0 };
+
+ WINPR_ASSERT(info);
+
+ printf("%" PRIuz ": %s\n", i, info->subject);
+
+ if (ConvertWCharToUtf8(info->csp, asciiStr, ARRAYSIZE(asciiStr)))
+ printf("\t* CSP: %s\n", asciiStr);
+
+ if (ConvertWCharToUtf8(info->reader, asciiStr, ARRAYSIZE(asciiStr)))
+ printf("\t* reader: %s\n", asciiStr);
+#ifndef _WIN32
+ printf("\t* slotId: %" PRIu32 "\n", info->slotId);
+ printf("\t* pkinitArgs: %s\n", info->pkinitArgs);
+#endif
+ if (ConvertWCharToUtf8(info->containerName, asciiStr, ARRAYSIZE(asciiStr)))
+ printf("\t* containerName: %s\n", asciiStr);
+ if (info->upn)
+ printf("\t* UPN: %s\n", info->upn);
+ }
+ smartcardCertList_Free(certs, count);
+
+ return TRUE;
+}
diff --git a/client/common/test/CMakeLists.txt b/client/common/test/CMakeLists.txt
new file mode 100644
index 0000000..1e31f7c
--- /dev/null
+++ b/client/common/test/CMakeLists.txt
@@ -0,0 +1,30 @@
+
+set(MODULE_NAME "TestClient")
+set(MODULE_PREFIX "TEST_CLIENT")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestClientRdpFile.c
+ TestClientChannels.c
+ TestClientCmdLine.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} freerdp-client freerdp)
+
+target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS})
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "FreeRDP/Client/Test")
+
diff --git a/client/common/test/TestClientChannels.c b/client/common/test/TestClientChannels.c
new file mode 100644
index 0000000..b15a734
--- /dev/null
+++ b/client/common/test/TestClientChannels.c
@@ -0,0 +1,87 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/windows.h>
+
+#include <freerdp/client/channels.h>
+#include <freerdp/channels/rdpsnd.h>
+
+int TestClientChannels(int argc, char* argv[])
+{
+ DWORD dwFlags = 0;
+ FREERDP_ADDIN** ppAddins = NULL;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ dwFlags = FREERDP_ADDIN_DYNAMIC;
+
+ printf("Enumerate all\n");
+ ppAddins = freerdp_channels_list_addins(NULL, NULL, NULL, dwFlags);
+
+ for (size_t index = 0; ppAddins[index] != NULL; index++)
+ {
+ FREERDP_ADDIN* pAddin = ppAddins[index];
+
+ printf("Addin: Name: %s Subsystem: %s Type: %s\n", pAddin->cName, pAddin->cSubsystem,
+ pAddin->cType);
+ }
+
+ freerdp_channels_addin_list_free(ppAddins);
+
+ printf("Enumerate rdpsnd\n");
+ ppAddins = freerdp_channels_list_addins(RDPSND_CHANNEL_NAME, NULL, NULL, dwFlags);
+
+ for (size_t index = 0; ppAddins[index] != NULL; index++)
+ {
+ FREERDP_ADDIN* pAddin = ppAddins[index];
+
+ printf("Addin: Name: %s Subsystem: %s Type: %s\n", pAddin->cName, pAddin->cSubsystem,
+ pAddin->cType);
+ }
+
+ freerdp_channels_addin_list_free(ppAddins);
+
+#if defined(CHANNEL_TSMF_CLIENT)
+ printf("Enumerate tsmf video\n");
+ ppAddins = freerdp_channels_list_addins("tsmf", NULL, "video", dwFlags);
+
+ for (size_t index = 0; ppAddins[index] != NULL; index++)
+ {
+ FREERDP_ADDIN* pAddin = ppAddins[index];
+
+ printf("Addin: Name: %s Subsystem: %s Type: %s\n", pAddin->cName, pAddin->cSubsystem,
+ pAddin->cType);
+ }
+
+ freerdp_channels_addin_list_free(ppAddins);
+#endif
+
+ ppAddins = freerdp_channels_list_addins("unknown", NULL, NULL, dwFlags);
+
+ for (size_t index = 0; ppAddins[index] != NULL; index++)
+ {
+ FREERDP_ADDIN* pAddin = ppAddins[index];
+
+ printf("Addin: Name: %s Subsystem: %s Type: %s\n", pAddin->cName, pAddin->cSubsystem,
+ pAddin->cType);
+ }
+
+ freerdp_channels_addin_list_free(ppAddins);
+
+ printf("Enumerate static addins\n");
+
+ dwFlags = FREERDP_ADDIN_STATIC;
+ ppAddins = freerdp_channels_list_addins(NULL, NULL, NULL, dwFlags);
+
+ for (size_t index = 0; ppAddins[index] != NULL; index++)
+ {
+ FREERDP_ADDIN* pAddin = ppAddins[index];
+
+ printf("Addin: Name: %s Subsystem: %s Type: %s\n", pAddin->cName, pAddin->cSubsystem,
+ pAddin->cType);
+ }
+
+ freerdp_channels_addin_list_free(ppAddins);
+
+ return 0;
+}
diff --git a/client/common/test/TestClientCmdLine.c b/client/common/test/TestClientCmdLine.c
new file mode 100644
index 0000000..2ce0c47
--- /dev/null
+++ b/client/common/test/TestClientCmdLine.c
@@ -0,0 +1,263 @@
+#include <freerdp/client.h>
+#include <freerdp/client/cmdline.h>
+#include <freerdp/settings.h>
+#include <winpr/cmdline.h>
+#include <winpr/spec.h>
+#include <winpr/strlst.h>
+#include <winpr/collections.h>
+
+typedef BOOL (*validate_settings_pr)(rdpSettings* settings);
+
+#define printref() printf("%s:%d: in function %-40s:", __FILE__, __LINE__, __func__)
+
+#define TEST_ERROR(format, ...) \
+ do \
+ { \
+ fprintf(stderr, format, ##__VA_ARGS__); \
+ printref(); \
+ printf(format, ##__VA_ARGS__); \
+ fflush(stdout); \
+ } while (0)
+
+#define TEST_FAILURE(format, ...) \
+ do \
+ { \
+ printref(); \
+ printf(" FAILURE "); \
+ printf(format, ##__VA_ARGS__); \
+ fflush(stdout); \
+ } while (0)
+
+static void print_test_title(int argc, char** argv)
+{
+ printf("Running test:");
+
+ for (int i = 0; i < argc; i++)
+ {
+ printf(" %s", argv[i]);
+ }
+
+ printf("\n");
+}
+
+static INLINE BOOL testcase(const char* name, char** argv, size_t argc, int expected_return,
+ validate_settings_pr validate_settings)
+{
+ int status = 0;
+ BOOL valid_settings = TRUE;
+ rdpSettings* settings = freerdp_settings_new(0);
+ print_test_title(argc, argv);
+
+ if (!settings)
+ {
+ TEST_ERROR("Test %s could not allocate settings!\n", name);
+ return FALSE;
+ }
+
+ status = freerdp_client_settings_parse_command_line(settings, argc, argv, FALSE);
+
+ if (validate_settings)
+ {
+ valid_settings = validate_settings(settings);
+ }
+
+ freerdp_settings_free(settings);
+
+ if (status == expected_return)
+ {
+ if (!valid_settings)
+ {
+ return FALSE;
+ }
+ }
+ else
+ {
+ TEST_FAILURE("Expected status %d, got status %d\n", expected_return, status);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+#if defined(_WIN32)
+#define DRIVE_REDIRECT_PATH "c:\\Windows"
+#else
+#define DRIVE_REDIRECT_PATH "/tmp"
+#endif
+
+static BOOL check_settings_smartcard_no_redirection(rdpSettings* settings)
+{
+ BOOL result = TRUE;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_RedirectSmartCards))
+ {
+ TEST_FAILURE("Expected RedirectSmartCards = FALSE, but RedirectSmartCards = TRUE!\n");
+ result = FALSE;
+ }
+
+ if (freerdp_device_collection_find_type(settings, RDPDR_DTYP_SMARTCARD))
+ {
+ TEST_FAILURE("Expected no SMARTCARD device, but found at least one!\n");
+ result = FALSE;
+ }
+
+ return result;
+}
+
+typedef struct
+{
+ int expected_status;
+ validate_settings_pr validate_settings;
+ const char* command_line[128];
+ struct
+ {
+ int index;
+ const char* expected_value;
+ } modified_arguments[8];
+} test;
+
+static const test tests[] = {
+ { COMMAND_LINE_STATUS_PRINT_HELP,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "--help", 0 },
+ { { 0 } } },
+ { COMMAND_LINE_STATUS_PRINT_HELP,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/help", 0 },
+ { { 0 } } },
+ { COMMAND_LINE_STATUS_PRINT_HELP,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "-help", 0 },
+ { { 0 } } },
+ { COMMAND_LINE_STATUS_PRINT_VERSION,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "--version", 0 },
+ { { 0 } } },
+ { COMMAND_LINE_STATUS_PRINT_VERSION,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/version", 0 },
+ { { 0 } } },
+ { COMMAND_LINE_STATUS_PRINT_VERSION,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "-version", 0 },
+ { { 0 } } },
+ { 0,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "-v", "test.freerdp.com", 0 },
+ { { 0 } } },
+ { 0,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "--v", "test.freerdp.com", 0 },
+ { { 0 } } },
+ { 0,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/v:test.freerdp.com", 0 },
+ { { 0 } } },
+ { 0,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/sound", "/drive:media," DRIVE_REDIRECT_PATH, "/v:test.freerdp.com", 0 },
+ { { 0 } } },
+ { 0,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "-u", "test", "-p", "test", "-v", "test.freerdp.com", 0 },
+ { { 4, "****" }, { 0 } } },
+ { 0,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/u:test", "/p:test", "/v:test.freerdp.com", 0 },
+ { { 2, "/p:****" }, { 0 } } },
+ { COMMAND_LINE_ERROR_NO_KEYWORD,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "-invalid", 0 },
+ { { 0 } } },
+ { COMMAND_LINE_ERROR_NO_KEYWORD,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "--invalid", 0 },
+ { { 0 } } },
+#if defined(WITH_FREERDP_DEPRECATED_CMDLINE)
+ { COMMAND_LINE_STATUS_PRINT,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/kbd-list", 0 },
+ { { 0 } } },
+ { COMMAND_LINE_STATUS_PRINT,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/monitor-list", 0 },
+ { { 0 } } },
+#endif
+ { COMMAND_LINE_STATUS_PRINT,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/list:kbd", 0 },
+ { { 0 } } },
+ { COMMAND_LINE_STATUS_PRINT,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/list:monitor", 0 },
+ { { 0 } } },
+ { COMMAND_LINE_ERROR,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/sound", "/drive:media:" DRIVE_REDIRECT_PATH, "/v:test.freerdp.com", 0 },
+ { { 0 } } },
+ { COMMAND_LINE_ERROR,
+ check_settings_smartcard_no_redirection,
+ { "testfreerdp", "/sound", "/drive:media,/foo/bar/blabla", "/v:test.freerdp.com", 0 },
+ { { 0 } } },
+
+#if 0
+ {
+ COMMAND_LINE_STATUS_PRINT, check_settings_smartcard_no_redirection,
+ {"testfreerdp", "-z", "--plugin", "cliprdr", "--plugin", "rdpsnd", "--data", "alsa", "latency:100", "--", "--plugin", "rdpdr", "--data", "disk:w7share:/home/w7share", "--", "--plugin", "drdynvc", "--data", "tsmf:decoder:gstreamer", "--", "-u", "test", "host.example.com", 0},
+ {{0}}
+ },
+#endif
+};
+
+static void check_modified_arguments(const test* test, char** command_line, int* rc)
+{
+ const char* expected_argument = NULL;
+
+ for (int k = 0; (expected_argument = test->modified_arguments[k].expected_value); k++)
+ {
+ int index = test->modified_arguments[k].index;
+ char* actual_argument = command_line[index];
+
+ if (0 != strcmp(actual_argument, expected_argument))
+ {
+ printref();
+ printf("Failure: overridden argument %d is %s but it should be %s\n", index,
+ actual_argument, expected_argument);
+ fflush(stdout);
+ *rc = -1;
+ }
+ }
+}
+
+int TestClientCmdLine(int argc, char* argv[])
+{
+ int rc = 0;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++)
+ {
+ const test* current = &tests[i];
+ int failure = 0;
+ char** command_line = string_list_copy(current->command_line);
+
+ if (!testcase(__func__, command_line, string_list_length((const char* const*)command_line),
+ current->expected_status, current->validate_settings))
+ {
+ TEST_FAILURE("parsing arguments.\n");
+ failure = 1;
+ }
+
+ check_modified_arguments(current, command_line, &failure);
+
+ if (failure)
+ {
+ string_list_print(stdout, (const char* const*)command_line);
+ rc = -1;
+ }
+
+ string_list_free(command_line);
+ }
+
+ return rc;
+}
diff --git a/client/common/test/TestClientRdpFile.c b/client/common/test/TestClientRdpFile.c
new file mode 100644
index 0000000..e631bc8
--- /dev/null
+++ b/client/common/test/TestClientRdpFile.c
@@ -0,0 +1,600 @@
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/windows.h>
+#include <winpr/path.h>
+#include <winpr/crypto.h>
+
+#include <freerdp/client/file.h>
+#include <freerdp/channels/rdpecam.h>
+
+static const BYTE testRdpFileUTF16[] = {
+ 0xff, 0xfe, 0x73, 0x00, 0x63, 0x00, 0x72, 0x00, 0x65, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x20, 0x00,
+ 0x6d, 0x00, 0x6f, 0x00, 0x64, 0x00, 0x65, 0x00, 0x20, 0x00, 0x69, 0x00, 0x64, 0x00, 0x3a, 0x00,
+ 0x69, 0x00, 0x3a, 0x00, 0x32, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x75, 0x00, 0x73, 0x00, 0x65, 0x00,
+ 0x20, 0x00, 0x6d, 0x00, 0x75, 0x00, 0x6c, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6d, 0x00, 0x6f, 0x00,
+ 0x6e, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x64, 0x00,
+ 0x65, 0x00, 0x73, 0x00, 0x6b, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x70, 0x00, 0x77, 0x00, 0x69, 0x00,
+ 0x64, 0x00, 0x74, 0x00, 0x68, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x39, 0x00,
+ 0x32, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x64, 0x00, 0x65, 0x00, 0x73, 0x00, 0x6b, 0x00,
+ 0x74, 0x00, 0x6f, 0x00, 0x70, 0x00, 0x68, 0x00, 0x65, 0x00, 0x69, 0x00, 0x67, 0x00, 0x68, 0x00,
+ 0x74, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x30, 0x00, 0x38, 0x00, 0x30, 0x00,
+ 0x0d, 0x00, 0x0a, 0x00, 0x73, 0x00, 0x65, 0x00, 0x73, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00,
+ 0x6e, 0x00, 0x20, 0x00, 0x62, 0x00, 0x70, 0x00, 0x70, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00,
+ 0x33, 0x00, 0x32, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x77, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x70, 0x00,
+ 0x6f, 0x00, 0x73, 0x00, 0x73, 0x00, 0x74, 0x00, 0x72, 0x00, 0x3a, 0x00, 0x73, 0x00, 0x3a, 0x00,
+ 0x30, 0x00, 0x2c, 0x00, 0x31, 0x00, 0x2c, 0x00, 0x35, 0x00, 0x35, 0x00, 0x33, 0x00, 0x2c, 0x00,
+ 0x32, 0x00, 0x31, 0x00, 0x31, 0x00, 0x2c, 0x00, 0x31, 0x00, 0x33, 0x00, 0x35, 0x00, 0x33, 0x00,
+ 0x2c, 0x00, 0x38, 0x00, 0x31, 0x00, 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x63, 0x00, 0x6f, 0x00,
+ 0x6d, 0x00, 0x70, 0x00, 0x72, 0x00, 0x65, 0x00, 0x73, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00,
+ 0x6e, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x6b, 0x00,
+ 0x65, 0x00, 0x79, 0x00, 0x62, 0x00, 0x6f, 0x00, 0x61, 0x00, 0x72, 0x00, 0x64, 0x00, 0x68, 0x00,
+ 0x6f, 0x00, 0x6f, 0x00, 0x6b, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x32, 0x00, 0x0d, 0x00,
+ 0x0a, 0x00, 0x61, 0x00, 0x75, 0x00, 0x64, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x63, 0x00, 0x61, 0x00,
+ 0x70, 0x00, 0x74, 0x00, 0x75, 0x00, 0x72, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x6f, 0x00, 0x64, 0x00,
+ 0x65, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x76, 0x00,
+ 0x69, 0x00, 0x64, 0x00, 0x65, 0x00, 0x6f, 0x00, 0x70, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x79, 0x00,
+ 0x62, 0x00, 0x61, 0x00, 0x63, 0x00, 0x6b, 0x00, 0x6d, 0x00, 0x6f, 0x00, 0x64, 0x00, 0x65, 0x00,
+ 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x63, 0x00, 0x6f, 0x00,
+ 0x6e, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00,
+ 0x20, 0x00, 0x74, 0x00, 0x79, 0x00, 0x70, 0x00, 0x65, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00,
+ 0x37, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x74, 0x00, 0x77, 0x00, 0x6f, 0x00,
+ 0x72, 0x00, 0x6b, 0x00, 0x61, 0x00, 0x75, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x64, 0x00, 0x65, 0x00,
+ 0x74, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00,
+ 0x0d, 0x00, 0x0a, 0x00, 0x62, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x77, 0x00, 0x69, 0x00,
+ 0x64, 0x00, 0x74, 0x00, 0x68, 0x00, 0x61, 0x00, 0x75, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x64, 0x00,
+ 0x65, 0x00, 0x74, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00,
+ 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x64, 0x00, 0x69, 0x00, 0x73, 0x00, 0x70, 0x00, 0x6c, 0x00,
+ 0x61, 0x00, 0x79, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x63, 0x00,
+ 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x72, 0x00, 0x3a, 0x00,
+ 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x61, 0x00,
+ 0x62, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x6b, 0x00, 0x73, 0x00,
+ 0x70, 0x00, 0x61, 0x00, 0x63, 0x00, 0x65, 0x00, 0x72, 0x00, 0x65, 0x00, 0x63, 0x00, 0x6f, 0x00,
+ 0x6e, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00,
+ 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x64, 0x00, 0x69, 0x00, 0x73, 0x00, 0x61, 0x00, 0x62, 0x00,
+ 0x6c, 0x00, 0x65, 0x00, 0x20, 0x00, 0x77, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x70, 0x00,
+ 0x61, 0x00, 0x70, 0x00, 0x65, 0x00, 0x72, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00,
+ 0x0d, 0x00, 0x0a, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x77, 0x00, 0x20, 0x00,
+ 0x66, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x20, 0x00, 0x73, 0x00, 0x6d, 0x00, 0x6f, 0x00,
+ 0x6f, 0x00, 0x74, 0x00, 0x68, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x3a, 0x00, 0x69, 0x00,
+ 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00,
+ 0x77, 0x00, 0x20, 0x00, 0x64, 0x00, 0x65, 0x00, 0x73, 0x00, 0x6b, 0x00, 0x74, 0x00, 0x6f, 0x00,
+ 0x70, 0x00, 0x20, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x70, 0x00, 0x6f, 0x00, 0x73, 0x00,
+ 0x69, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00,
+ 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x64, 0x00, 0x69, 0x00, 0x73, 0x00, 0x61, 0x00, 0x62, 0x00,
+ 0x6c, 0x00, 0x65, 0x00, 0x20, 0x00, 0x66, 0x00, 0x75, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x20, 0x00,
+ 0x77, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x77, 0x00, 0x20, 0x00, 0x64, 0x00,
+ 0x72, 0x00, 0x61, 0x00, 0x67, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00,
+ 0x0a, 0x00, 0x64, 0x00, 0x69, 0x00, 0x73, 0x00, 0x61, 0x00, 0x62, 0x00, 0x6c, 0x00, 0x65, 0x00,
+ 0x20, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x75, 0x00, 0x20, 0x00, 0x61, 0x00, 0x6e, 0x00,
+ 0x69, 0x00, 0x6d, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00,
+ 0x0a, 0x00, 0x64, 0x00, 0x69, 0x00, 0x73, 0x00, 0x61, 0x00, 0x62, 0x00, 0x6c, 0x00, 0x65, 0x00,
+ 0x20, 0x00, 0x74, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x73, 0x00, 0x3a, 0x00,
+ 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x64, 0x00, 0x69, 0x00, 0x73, 0x00,
+ 0x61, 0x00, 0x62, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x20, 0x00, 0x63, 0x00, 0x75, 0x00, 0x72, 0x00,
+ 0x73, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x20, 0x00, 0x73, 0x00, 0x65, 0x00, 0x74, 0x00, 0x74, 0x00,
+ 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00,
+ 0x0a, 0x00, 0x62, 0x00, 0x69, 0x00, 0x74, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x70, 0x00, 0x63, 0x00,
+ 0x61, 0x00, 0x63, 0x00, 0x68, 0x00, 0x65, 0x00, 0x70, 0x00, 0x65, 0x00, 0x72, 0x00, 0x73, 0x00,
+ 0x69, 0x00, 0x73, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x62, 0x00, 0x6c, 0x00,
+ 0x65, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x66, 0x00,
+ 0x75, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x20, 0x00, 0x61, 0x00, 0x64, 0x00, 0x64, 0x00, 0x72, 0x00,
+ 0x65, 0x00, 0x73, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x4c, 0x00, 0x41, 0x00,
+ 0x42, 0x00, 0x31, 0x00, 0x2d, 0x00, 0x57, 0x00, 0x37, 0x00, 0x2d, 0x00, 0x44, 0x00, 0x4d, 0x00,
+ 0x2d, 0x00, 0x30, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x62, 0x00, 0x31, 0x00,
+ 0x2e, 0x00, 0x61, 0x00, 0x77, 0x00, 0x61, 0x00, 0x6b, 0x00, 0x65, 0x00, 0x2e, 0x00, 0x6c, 0x00,
+ 0x6f, 0x00, 0x63, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x61, 0x00, 0x75, 0x00,
+ 0x64, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x6f, 0x00, 0x64, 0x00, 0x65, 0x00, 0x3a, 0x00,
+ 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x72, 0x00, 0x65, 0x00, 0x64, 0x00,
+ 0x69, 0x00, 0x72, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x70, 0x00, 0x72, 0x00, 0x69, 0x00,
+ 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x72, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00,
+ 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x72, 0x00, 0x65, 0x00, 0x64, 0x00, 0x69, 0x00, 0x72, 0x00,
+ 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x70, 0x00, 0x6f, 0x00,
+ 0x72, 0x00, 0x74, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00,
+ 0x0a, 0x00, 0x72, 0x00, 0x65, 0x00, 0x64, 0x00, 0x69, 0x00, 0x72, 0x00, 0x65, 0x00, 0x63, 0x00,
+ 0x74, 0x00, 0x73, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x72, 0x00, 0x74, 0x00, 0x63, 0x00, 0x61, 0x00,
+ 0x72, 0x00, 0x64, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00,
+ 0x0a, 0x00, 0x72, 0x00, 0x65, 0x00, 0x64, 0x00, 0x69, 0x00, 0x72, 0x00, 0x65, 0x00, 0x63, 0x00,
+ 0x74, 0x00, 0x63, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x70, 0x00, 0x62, 0x00, 0x6f, 0x00, 0x61, 0x00,
+ 0x72, 0x00, 0x64, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00,
+ 0x72, 0x00, 0x65, 0x00, 0x64, 0x00, 0x69, 0x00, 0x72, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00,
+ 0x70, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x64, 0x00, 0x65, 0x00, 0x76, 0x00, 0x69, 0x00, 0x63, 0x00,
+ 0x65, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00,
+ 0x61, 0x00, 0x75, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x65, 0x00, 0x63, 0x00, 0x6f, 0x00,
+ 0x6e, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00,
+ 0x20, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x62, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x64, 0x00,
+ 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x61, 0x00, 0x75, 0x00,
+ 0x74, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x69, 0x00, 0x63, 0x00, 0x61, 0x00,
+ 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x20, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x76, 0x00,
+ 0x65, 0x00, 0x6c, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x32, 0x00, 0x0d, 0x00, 0x0a, 0x00,
+ 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x70, 0x00, 0x74, 0x00, 0x20, 0x00, 0x66, 0x00,
+ 0x6f, 0x00, 0x72, 0x00, 0x20, 0x00, 0x63, 0x00, 0x72, 0x00, 0x65, 0x00, 0x64, 0x00, 0x65, 0x00,
+ 0x6e, 0x00, 0x74, 0x00, 0x69, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x69, 0x00,
+ 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x67, 0x00, 0x6f, 0x00,
+ 0x74, 0x00, 0x69, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x20, 0x00, 0x73, 0x00, 0x65, 0x00,
+ 0x63, 0x00, 0x75, 0x00, 0x72, 0x00, 0x69, 0x00, 0x74, 0x00, 0x79, 0x00, 0x20, 0x00, 0x6c, 0x00,
+ 0x61, 0x00, 0x79, 0x00, 0x65, 0x00, 0x72, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00,
+ 0x0d, 0x00, 0x0a, 0x00, 0x72, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x6f, 0x00, 0x74, 0x00, 0x65, 0x00,
+ 0x61, 0x00, 0x70, 0x00, 0x70, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x63, 0x00, 0x61, 0x00, 0x74, 0x00,
+ 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x6d, 0x00, 0x6f, 0x00, 0x64, 0x00, 0x65, 0x00, 0x3a, 0x00,
+ 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x74, 0x00,
+ 0x65, 0x00, 0x72, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x20, 0x00, 0x73, 0x00,
+ 0x68, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x3a, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x0d, 0x00,
+ 0x0a, 0x00, 0x73, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x20, 0x00, 0x77, 0x00,
+ 0x6f, 0x00, 0x72, 0x00, 0x6b, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x20, 0x00, 0x64, 0x00,
+ 0x69, 0x00, 0x72, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x79, 0x00,
+ 0x3a, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x67, 0x00, 0x61, 0x00, 0x74, 0x00,
+ 0x65, 0x00, 0x77, 0x00, 0x61, 0x00, 0x79, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x74, 0x00,
+ 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x3a, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x4c, 0x00,
+ 0x41, 0x00, 0x42, 0x00, 0x31, 0x00, 0x2d, 0x00, 0x57, 0x00, 0x32, 0x00, 0x4b, 0x00, 0x38, 0x00,
+ 0x52, 0x00, 0x32, 0x00, 0x2d, 0x00, 0x47, 0x00, 0x57, 0x00, 0x2e, 0x00, 0x6c, 0x00, 0x61, 0x00,
+ 0x62, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x61, 0x00, 0x77, 0x00, 0x61, 0x00, 0x6b, 0x00, 0x65, 0x00,
+ 0x2e, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x63, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x0d, 0x00, 0x0a, 0x00,
+ 0x67, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x77, 0x00, 0x61, 0x00, 0x79, 0x00, 0x75, 0x00,
+ 0x73, 0x00, 0x61, 0x00, 0x67, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x74, 0x00, 0x68, 0x00,
+ 0x6f, 0x00, 0x64, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00,
+ 0x67, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x77, 0x00, 0x61, 0x00, 0x79, 0x00, 0x63, 0x00,
+ 0x72, 0x00, 0x65, 0x00, 0x64, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x69, 0x00, 0x61, 0x00,
+ 0x6c, 0x00, 0x73, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x72, 0x00, 0x63, 0x00, 0x65, 0x00,
+ 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x67, 0x00, 0x61, 0x00,
+ 0x74, 0x00, 0x65, 0x00, 0x77, 0x00, 0x61, 0x00, 0x79, 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00,
+ 0x66, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x75, 0x00, 0x73, 0x00, 0x61, 0x00, 0x67, 0x00,
+ 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x74, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x64, 0x00, 0x3a, 0x00,
+ 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00,
+ 0x6d, 0x00, 0x70, 0x00, 0x74, 0x00, 0x63, 0x00, 0x72, 0x00, 0x65, 0x00, 0x64, 0x00, 0x65, 0x00,
+ 0x6e, 0x00, 0x74, 0x00, 0x69, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x63, 0x00,
+ 0x65, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x31, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x75, 0x00,
+ 0x73, 0x00, 0x65, 0x00, 0x20, 0x00, 0x72, 0x00, 0x65, 0x00, 0x64, 0x00, 0x69, 0x00, 0x72, 0x00,
+ 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x20, 0x00, 0x73, 0x00,
+ 0x65, 0x00, 0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00,
+ 0x6d, 0x00, 0x65, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00, 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00,
+ 0x72, 0x00, 0x64, 0x00, 0x67, 0x00, 0x69, 0x00, 0x73, 0x00, 0x6b, 0x00, 0x64, 0x00, 0x63, 0x00,
+ 0x70, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x78, 0x00, 0x79, 0x00, 0x3a, 0x00, 0x69, 0x00, 0x3a, 0x00,
+ 0x30, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x6b, 0x00, 0x64, 0x00, 0x63, 0x00, 0x70, 0x00, 0x72, 0x00,
+ 0x6f, 0x00, 0x78, 0x00, 0x79, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x3a, 0x00,
+ 0x73, 0x00, 0x3a, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x64, 0x00, 0x72, 0x00, 0x69, 0x00, 0x76, 0x00,
+ 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x65, 0x00, 0x64, 0x00, 0x69, 0x00,
+ 0x72, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x3a, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x2a, 0x00,
+ 0x0d, 0x00, 0x0a, 0x00, 0x75, 0x00, 0x73, 0x00, 0x65, 0x00, 0x72, 0x00, 0x6e, 0x00, 0x61, 0x00,
+ 0x6d, 0x00, 0x65, 0x00, 0x3a, 0x00, 0x73, 0x00, 0x3a, 0x00, 0x4c, 0x00, 0x41, 0x00, 0x42, 0x00,
+ 0x31, 0x00, 0x5c, 0x00, 0x4a, 0x00, 0x6f, 0x00, 0x68, 0x00, 0x6e, 0x00, 0x44, 0x00, 0x6f, 0x00,
+ 0x65, 0x00, 0x0d, 0x00, 0x0a, 0x00
+};
+
+#if defined(CHANNEL_RDPECAM_CLIENT)
+static const char* camera_args[] = { RDPECAM_DVC_CHANNEL_NAME,
+ "device:*",
+ "device:\\?\\usb#vid_0bda&pid_58b0&mi",
+ "device:-\\?\\usb#vid_0bdc&pid_58b1&mi",
+ "encode:1",
+ "quality:2" };
+#endif
+
+#if defined(CHANNEL_URBDRC_CLIENT)
+static const char* urbdrc_args[] = { "urbdrc", "device:*", "device:USBInstanceID:someid",
+ "device:{72631e54-78a4-11d0-bcf7-00aa00b7b32a}" };
+#endif
+
+static char testRdpFileUTF8[] =
+ "screen mode id:i:2\n"
+ "use multimon:i:0\n"
+ "desktopwidth:i:1920\n"
+ "desktopheight:i:1080\n"
+ "dynamic resolution:i:1080\n"
+ "desktopscalefactor:i:1080\n"
+ "redirected video capture encoding quality:i:2\n"
+ "encode redirected video capture:i:1\n"
+ "camerastoredirect:s:*,\\?\\usb#vid_0bda&pid_58b0&mi,-\\?\\usb#vid_0bdc&pid_58b1&mi\n"
+ "usbdevicestoredirect:s:*,USBInstanceID:someid,{72631e54-78a4-11d0-bcf7-00aa00b7b32a}\n"
+ "selectedmonitors:s:3,2,42,23"
+ "session bpp:i:32\n"
+ "winposstr:s:0,1,553,211,1353,811\n"
+ "compression:i:1\n"
+ "keyboardhook:i:2\n"
+ "audiocapturemode:i:0\n"
+ "videoplaybackmode:i:2\n"
+ "connection type:i:7\n"
+ "networkautodetect:i:1\n"
+ "bandwidthautodetect:i:1\n"
+ "displayconnectionbar:i:1\n"
+ "enableworkspacereconnect:i:0\n"
+ "disable wallpaper:i:0\n"
+ "allow font smoothing:i:0\n"
+ "allow desktop composition:i:0\n"
+ "disable full window drag:i:1\n"
+ "disable menu anims:i:1\n"
+ "disable themes:i:0\n"
+ "disable cursor setting:i:0\n"
+ "bitmapcachepersistenable:i:1\n"
+ "full address:s:LAB1-W7-DM-01.lab1.awake.local\n"
+ "alternate full address:s:LAB1-W7-DM-01.lab1.awake.global\n"
+ "audiomode:i:0\n"
+ "redirectprinters:i:1\n"
+ "redirectcomports:i:0\n"
+ "redirectsmartcards:i:1\n"
+ "redirectclipboard:i:1\n"
+ "redirectposdevices:i:0\n"
+ "autoreconnection enabled:i:1\n"
+ "authentication level:i:2\n"
+ "prompt for credentials:i:0\n"
+ "negotiate security layer:i:1\n"
+ "remoteapplicationmode:i:0\n"
+ "alternate shell:s:\n"
+ "shell working directory:s:\n"
+ "gatewayhostname:s:LAB1-W2K8R2-GW.lab1.awake.local\n"
+ "gatewayusagemethod:i:1\n"
+ "gatewaycredentialssource:i:0\n"
+ "gatewayprofileusagemethod:i:1\n"
+ "promptcredentialonce:i:1\n"
+ "use redirection server name:i:0\n"
+ "rdgiskdcproxy:i:0\n"
+ "kdcproxyname:s:\n"
+ "drivestoredirect:s:*\n"
+ "username:s:LAB1\\JohnDoe\n"
+ "vendor integer:i:123\n"
+ "vendor string:s:microsoft\n";
+
+static char* append(const char* fmt, ...)
+{
+ int rc = 0;
+ char* dst = NULL;
+ va_list ap;
+
+ va_start(ap, fmt);
+ rc = vsnprintf(NULL, 0, fmt, ap);
+ va_end(ap);
+ if (rc < 0)
+ return NULL;
+ dst = malloc((size_t)rc + 1);
+ if (!dst)
+ return NULL;
+
+ va_start(ap, fmt);
+ rc = vsnprintf(dst, (size_t)rc + 1, fmt, ap);
+ va_end(ap);
+ if (rc < 0)
+ {
+ free(dst);
+ return NULL;
+ }
+ return dst;
+}
+
+int TestClientRdpFile(int argc, char* argv[])
+{
+ int rc = -1;
+ int iValue = 0;
+ UINT32 uValue = 0;
+ const UINT32* puValue = NULL;
+ const char* sValue = NULL;
+ char* utfname = NULL;
+ char* uniname = NULL;
+ char* base = NULL;
+ char* tmp = NULL;
+ UINT64 id = 0;
+ rdpFile* file = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ winpr_RAND(&id, sizeof(id));
+
+ /* Unicode */
+ file = freerdp_client_rdp_file_new();
+ settings = freerdp_settings_new(0);
+
+ if (!file || !settings)
+ {
+ printf("rdp_file_new failed\n");
+ goto fail;
+ }
+
+ if (!freerdp_client_parse_rdp_file_buffer(file, testRdpFileUTF16, sizeof(testRdpFileUTF16)))
+ goto fail;
+
+ if (!freerdp_client_populate_settings_from_rdp_file(file, settings))
+ goto fail;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
+ {
+ printf("UseMultiMon mismatch: Actual: %" PRIu32 ", Expected: 0\n",
+ freerdp_settings_get_bool(settings, FreeRDP_UseMultimon));
+ goto fail;
+ }
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ {
+ printf("ScreenModeId mismatch: Actual: %" PRIu32 ", Expected: TRUE\n",
+ freerdp_settings_get_bool(settings, FreeRDP_Fullscreen));
+ goto fail;
+ }
+
+#if 0 /* TODO: Currently unused */
+ if (freerdp_settings_get_uint32(settings, FreeRDP_GatewayProfileUsageMethod) != 1)
+ {
+ printf("GatewayProfileUsageMethod mismatch: Actual: %"PRIu32", Expected: 1\n",
+ freerdp_settings_get_uint32(settings, FreeRDP_GatewayProfileUsageMethod));
+ goto fail;
+ }
+#endif
+
+ if (strcmp(freerdp_settings_get_string(settings, FreeRDP_GatewayHostname),
+ "LAB1-W2K8R2-GW.lab1.awake.local") != 0)
+ {
+ printf("GatewayHostname mismatch: Actual: %s, Expected: %s\n",
+ freerdp_settings_get_string(settings, FreeRDP_GatewayHostname),
+ "LAB1-W2K8R2-GW.lab1.awake.local");
+ goto fail;
+ }
+
+ if (strcmp(freerdp_settings_get_string(settings, FreeRDP_ServerHostname),
+ "LAB1-W7-DM-01.lab1.awake.local") != 0)
+ {
+ printf("ServerHostname mismatch: Actual: %s, Expected: %s\n",
+ freerdp_settings_get_string(settings, FreeRDP_ServerHostname),
+ "LAB1-W7-DM-01.lab1.awake.local");
+ goto fail;
+ }
+
+ freerdp_client_rdp_file_free(file);
+ freerdp_settings_free(settings);
+ /* Ascii */
+ file = freerdp_client_rdp_file_new();
+ settings = freerdp_settings_new(0);
+ if (!file || !settings)
+ {
+ printf("rdp_file_new failed\n");
+ goto fail;
+ }
+
+ if (!freerdp_client_parse_rdp_file_buffer(file, (BYTE*)testRdpFileUTF8,
+ sizeof(testRdpFileUTF8)))
+ goto fail;
+
+ if (!freerdp_client_populate_settings_from_rdp_file(file, settings))
+ goto fail;
+
+ if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
+ {
+ printf("UseMultiMon mismatch: Actual: %" PRIu32 ", Expected: 0\n",
+ freerdp_settings_get_bool(settings, FreeRDP_UseMultimon));
+ return -1;
+ }
+
+ if (!freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
+ {
+ printf("ScreenModeId mismatch: Actual: %" PRIu32 ", Expected: TRUE\n",
+ freerdp_settings_get_bool(settings, FreeRDP_Fullscreen));
+ return -1;
+ }
+
+#if 0 /* TODO: Currently unused */
+ if (freerdp_settings_get_uint32(settings, FreeRDP_GatewayProfileUsageMethod) != 1)
+ {
+ printf("GatewayProfileUsageMethod mismatch: Actual: %"PRIu32", Expected: 1\n",
+ freerdp_settings_get_uint32(settings, FreeRDP_GatewayProfileUsageMethod));
+ goto fail;
+ }
+#endif
+
+ if (strcmp(freerdp_settings_get_string(settings, FreeRDP_ServerHostname),
+ "LAB1-W7-DM-01.lab1.awake.global") != 0)
+ {
+ printf("ServerHostname mismatch: Actual: %s, Expected: %s\n",
+ freerdp_settings_get_string(settings, FreeRDP_ServerHostname),
+ "LAB1-W7-DM-01.lab1.awake.global");
+ goto fail;
+ }
+
+ if (strcmp(freerdp_settings_get_string(settings, FreeRDP_GatewayHostname),
+ "LAB1-W2K8R2-GW.lab1.awake.local") != 0)
+ {
+ printf("GatewayHostname mismatch: Actual: %s, Expected: %s\n",
+ freerdp_settings_get_string(settings, FreeRDP_GatewayHostname),
+ "LAB1-W2K8R2-GW.lab1.awake.local");
+ goto fail;
+ }
+
+ iValue = freerdp_client_rdp_file_get_integer_option(file, "dynamic resolution");
+ if (iValue != 1080)
+ {
+ printf("dynamic resolution uses invalid default value %d", iValue);
+ goto fail;
+ }
+ if (!freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate))
+ {
+ printf("FreeRDP_DynamicResolutionUpdate has invalid value");
+ goto fail;
+ }
+ iValue = freerdp_client_rdp_file_get_integer_option(file, "desktopscalefactor");
+ if (iValue != 1080)
+ {
+ printf("desktopscalefactor uses invalid default value %d", iValue);
+ goto fail;
+ }
+ if ((INT64)freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor) != iValue)
+ {
+ printf("FreeRDP_DesktopScaleFactor has invalid value");
+ goto fail;
+ }
+
+ /* Check [MS-RDPECAM] related options */
+#if defined(CHANNEL_RDPECAM_CLIENT)
+ {
+ ADDIN_ARGV* args = NULL;
+ iValue =
+ freerdp_client_rdp_file_get_integer_option(file, "encode redirected video capture");
+ if (iValue != 1)
+ {
+ printf("encode redirected video capture uses invalid default value %d", iValue);
+ goto fail;
+ }
+ iValue = freerdp_client_rdp_file_get_integer_option(
+ file, "redirected video capture encoding quality");
+ if (iValue != 2)
+ {
+ printf("redirected video capture encoding quality uses invalid default value %d",
+ iValue);
+ goto fail;
+ }
+ args = freerdp_dynamic_channel_collection_find(settings, RDPECAM_DVC_CHANNEL_NAME);
+ if (!args)
+ {
+ printf("rdpecam channel was not loaded");
+ goto fail;
+ }
+ if (args->argc != 6)
+ {
+ printf("rdpecam channel was not loaded");
+ goto fail;
+ }
+
+ for (int x = 0; x < args->argc; x++)
+ {
+ if (strcmp(args->argv[x], camera_args[x]) != 0)
+ {
+ printf("rdpecam invalid argument argv[%d]: %s", x, args->argv[x]);
+ goto fail;
+ }
+ }
+ }
+#endif
+
+ /* Check [URBDRC] related options */
+#if defined(CHANNEL_URBDRC_CLIENT)
+ {
+ ADDIN_ARGV* args = freerdp_dynamic_channel_collection_find(settings, "urbdrc");
+ if (!args)
+ {
+ printf("urbdrc channel was not loaded");
+ goto fail;
+ }
+ if (args->argc != 4)
+ {
+ printf("urbdrc channel was not loaded");
+ goto fail;
+ }
+
+ for (int x = 0; x < args->argc; x++)
+ {
+ if (strcmp(args->argv[x], urbdrc_args[x]) != 0)
+ {
+ printf("urbdrc invalid argument argv[%d]: %s", x, args->argv[x]);
+ goto fail;
+ }
+ }
+ }
+#endif
+
+ /* Validate selectedmonitors:s:3,2,42,23 */
+ uValue = freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds);
+ if (uValue != 4)
+ {
+ printf("FreeRDP_NumMonitorIds has invalid value %" PRIu32, uValue);
+ goto fail;
+ }
+ puValue = (const UINT32*)freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorIds, 0);
+ if (!puValue)
+ {
+ printf("FreeRDP_MonitorIds has invalid value %p", (const void*)puValue);
+ goto fail;
+ }
+ if ((puValue[0] != 3) || (puValue[1] != 2) || (puValue[2] != 42) || (puValue[3] != 23))
+ {
+ printf("FreeRDP_MonitorIds has invalid values: [%" PRIu32 ",%" PRIu32 ",%" PRIu32
+ ",%" PRIu32 "]",
+ puValue[0], puValue[1], puValue[2], puValue[3]);
+ goto fail;
+ }
+
+ iValue = freerdp_client_rdp_file_get_integer_option(file, "videoplaybackmode");
+ if (iValue != 2)
+ {
+ printf("videoplaybackmode uses invalid default value %d", iValue);
+ goto fail;
+ }
+ if (!freerdp_settings_get_bool(settings, FreeRDP_SupportVideoOptimized))
+ {
+ printf("FreeRDP_SupportVideoOptimized has invalid value");
+ goto fail;
+ }
+ if (!freerdp_settings_get_bool(settings, FreeRDP_SupportGeometryTracking))
+ {
+ printf("FreeRDP_SupportGeometryTracking has invalid value");
+ goto fail;
+ }
+
+ iValue = freerdp_client_rdp_file_get_integer_option(file, "vendor integer");
+ if (iValue != 123)
+ goto fail;
+
+ if (freerdp_client_rdp_file_set_integer_option(file, "vendor integer", 456) == -1)
+ {
+ printf("failed to set integer: vendor integer");
+ goto fail;
+ }
+
+ iValue = freerdp_client_rdp_file_get_integer_option(file, "vendor integer");
+ if (iValue != 456)
+ return -1;
+
+ sValue = freerdp_client_rdp_file_get_string_option(file, "vendor string");
+ if (strncmp(sValue, "microsoft", 10) != 0)
+ goto fail;
+
+ freerdp_client_rdp_file_set_string_option(file, "vendor string", "apple");
+ sValue = freerdp_client_rdp_file_get_string_option(file, "vendor string");
+ if (strncmp(sValue, "apple", 6) != 0)
+ goto fail;
+
+ freerdp_client_rdp_file_set_string_option(file, "fruits", "banana,oranges");
+
+ if (freerdp_client_rdp_file_set_integer_option(file, "numbers", 123456789) == -1)
+ {
+ printf("failed to set integer: numbers");
+ return -1;
+ }
+
+ freerdp_client_rdp_file_free(file);
+
+ tmp = GetKnownPath(KNOWN_PATH_TEMP);
+ if (!tmp)
+ goto fail;
+
+ base = append("%s/rdp-file-test-%" PRIx64, tmp, id);
+ if (!base)
+ goto fail;
+ if (!CreateDirectoryA(base, NULL))
+ goto fail;
+ utfname = append("%s/utfname", base);
+ uniname = append("%s/uniname", base);
+ file = freerdp_client_rdp_file_new();
+ if (!file || !utfname || !uniname)
+ goto fail;
+
+ if (!freerdp_client_populate_rdp_file_from_settings(file, settings))
+ goto fail;
+
+ if (!freerdp_client_write_rdp_file(file, utfname, FALSE))
+ goto fail;
+
+ if (!freerdp_client_write_rdp_file(file, uniname, TRUE))
+ goto fail;
+
+ rc = 0;
+fail:
+ if (utfname)
+ winpr_DeleteFile(utfname);
+ if (uniname)
+ winpr_DeleteFile(uniname);
+ if (base)
+ winpr_RemoveDirectory(base);
+ free(utfname);
+ free(uniname);
+ free(base);
+ free(tmp);
+ freerdp_client_rdp_file_free(file);
+ freerdp_settings_free(settings);
+ return rc;
+}