diff options
Diffstat (limited to 'client/common')
-rw-r--r-- | client/common/CMakeLists.txt | 110 | ||||
-rw-r--r-- | client/common/client.c | 2156 | ||||
-rw-r--r-- | client/common/client_cliprdr_file.c | 2556 | ||||
-rw-r--r-- | client/common/cmdline.c | 5922 | ||||
-rw-r--r-- | client/common/cmdline.h | 519 | ||||
-rw-r--r-- | client/common/file.c | 2707 | ||||
-rw-r--r-- | client/common/geometry.c | 43 | ||||
-rw-r--r-- | client/common/man/CMakeLists.txt | 3 | ||||
-rw-r--r-- | client/common/man/generate_argument_docbook.c | 210 | ||||
-rw-r--r-- | client/common/smartcard_cli.c | 60 | ||||
-rw-r--r-- | client/common/test/CMakeLists.txt | 30 | ||||
-rw-r--r-- | client/common/test/TestClientChannels.c | 87 | ||||
-rw-r--r-- | client/common/test/TestClientCmdLine.c | 263 | ||||
-rw-r--r-- | client/common/test/TestClientRdpFile.c | 600 |
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, ¤t, 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, ¤t, 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, ¶ms[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, ¶ms[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, ¶ms[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, ¶ms[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 = "<"; + + if (!append(&tmp, &ds, str)) + exit(-3); + break; + + case '>': + if (format) + str = "</replaceable>"; + else + str = ">"; + + if (!append(&tmp, &ds, str)) + exit(-4); + break; + + case '\'': + if (!append(&tmp, &ds, "'")) + exit(-5); + break; + + case '"': + if (!append(&tmp, &ds, """)) + exit(-6); + break; + + case '&': + if (!append(&tmp, &ds, "&")) + 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; +} |