summaryrefslogtreecommitdiffstats
path: root/server/proxy
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:24:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:24:41 +0000
commita9bcc81f821d7c66f623779fa5147e728eb3c388 (patch)
tree98676963bcdd537ae5908a067a8eb110b93486a6 /server/proxy
parentInitial commit. (diff)
downloadfreerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.tar.xz
freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.zip
Adding upstream version 3.3.0+dfsg1.upstream/3.3.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'server/proxy')
-rw-r--r--server/proxy/CMakeLists.txt131
-rw-r--r--server/proxy/FreeRDP-ProxyConfig.cmake.in10
-rw-r--r--server/proxy/channels/CMakeLists.txt17
-rw-r--r--server/proxy/channels/pf_channel_drdynvc.c711
-rw-r--r--server/proxy/channels/pf_channel_drdynvc.h26
-rw-r--r--server/proxy/channels/pf_channel_rdpdr.c2017
-rw-r--r--server/proxy/channels/pf_channel_rdpdr.h47
-rw-r--r--server/proxy/channels/pf_channel_smartcard.c397
-rw-r--r--server/proxy/channels/pf_channel_smartcard.h39
-rw-r--r--server/proxy/cli/CMakeLists.txt60
-rw-r--r--server/proxy/cli/freerdp-proxy.1.in85
-rw-r--r--server/proxy/cli/freerdp_proxy.c161
-rw-r--r--server/proxy/config.ini53
-rw-r--r--server/proxy/freerdp-proxy.pc.in16
-rw-r--r--server/proxy/modules/CMakeLists.txt33
-rw-r--r--server/proxy/modules/README.md66
-rw-r--r--server/proxy/modules/bitmap-filter/CMakeLists.txt60
-rw-r--r--server/proxy/modules/bitmap-filter/bitmap-filter.cpp453
-rw-r--r--server/proxy/modules/demo/CMakeLists.txt53
-rw-r--r--server/proxy/modules/demo/demo.cpp422
-rw-r--r--server/proxy/modules/dyn-channel-dump/CMakeLists.txt58
-rw-r--r--server/proxy/modules/dyn-channel-dump/dyn-channel-dump.cpp436
-rw-r--r--server/proxy/pf_channel.c355
-rw-r--r--server/proxy/pf_channel.h64
-rw-r--r--server/proxy/pf_client.c1102
-rw-r--r--server/proxy/pf_client.h31
-rw-r--r--server/proxy/pf_config.c1348
-rw-r--r--server/proxy/pf_context.c394
-rw-r--r--server/proxy/pf_input.c206
-rw-r--r--server/proxy/pf_input.h29
-rw-r--r--server/proxy/pf_modules.c633
-rw-r--r--server/proxy/pf_server.c1073
-rw-r--r--server/proxy/pf_server.h43
-rw-r--r--server/proxy/pf_update.c629
-rw-r--r--server/proxy/pf_update.h34
-rw-r--r--server/proxy/pf_utils.c95
-rw-r--r--server/proxy/pf_utils.h43
-rw-r--r--server/proxy/proxy_modules.h100
38 files changed, 11530 insertions, 0 deletions
diff --git a/server/proxy/CMakeLists.txt b/server/proxy/CMakeLists.txt
new file mode 100644
index 0000000..4693b17
--- /dev/null
+++ b/server/proxy/CMakeLists.txt
@@ -0,0 +1,131 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Proxy Server
+#
+# Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
+# Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+# Copyright 2019 Idan Freiberg <speidy@gmail.com>
+# Copyright 2021 Armin Novak <anovak@thincast.com>
+# Copyright 2021 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.
+
+include(CMakeDependentOption)
+set(MODULE_NAME "freerdp-server-proxy")
+set(MODULE_PREFIX "FREERDP_SERVER_PROXY")
+
+set(${MODULE_PREFIX}_SRCS
+ pf_context.c
+ pf_channel.c
+ pf_channel.h
+ pf_client.c
+ pf_client.h
+ pf_input.c
+ pf_input.h
+ pf_update.c
+ pf_update.h
+ pf_server.c
+ pf_server.h
+ pf_config.c
+ pf_modules.c
+ pf_utils.h
+ pf_utils.c
+ $<TARGET_OBJECTS:pf_channels>
+ )
+
+set(PROXY_APP_SRCS freerdp_proxy.c)
+
+option(WITH_PROXY_EMULATE_SMARTCARD "Compile proxy smartcard emulation" OFF)
+add_subdirectory("channels")
+
+# On windows create dll version information.
+# Vendor, product and year are already set in top level CMakeLists.txt
+if (WIN32)
+ 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 "${MODULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}" )
+
+ configure_file(
+ ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+ set ( ${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+ list(APPEND PROXY_APP_SRCS ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+endif()
+
+add_library(${MODULE_NAME} ${${MODULE_PREFIX}_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()
+
+set(PRIVATE_LIBS
+ freerdp-client
+ freerdp-server
+)
+
+set(PUBLIC_LIBS
+ winpr
+ freerdp
+)
+
+target_include_directories(${MODULE_NAME} INTERFACE $<INSTALL_INTERFACE:include>)
+target_link_libraries(${MODULE_NAME} PRIVATE ${PRIVATE_LIBS} PUBLIC ${PUBLIC_LIBS})
+
+install(TARGETS ${MODULE_NAME} COMPONENT server EXPORT FreeRDP-ProxyTargets
+ 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 "Server/Proxy")
+
+# pkg-config
+include(pkg-config-install-prefix)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/freerdp-proxy.pc.in ${CMAKE_CURRENT_BINARY_DIR}/${MODULE_NAME}${FREERDP_VERSION_MAJOR}.pc @ONLY)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${MODULE_NAME}${FREERDP_VERSION_MAJOR}.pc DESTINATION ${PKG_CONFIG_PC_INSTALL_DIR})
+
+export(PACKAGE freerdp-proxy)
+
+SetFreeRDPCMakeInstallDir(FREERDP_PROXY_CMAKE_INSTALL_DIR "FreeRDP-Proxy${FREERDP_VERSION_MAJOR}")
+
+configure_package_config_file(FreeRDP-ProxyConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ProxyConfig.cmake
+ INSTALL_DESTINATION ${FREERDP_PROXY_CMAKE_INSTALL_DIR}
+ PATH_VARS FREERDP_INCLUDE_DIR)
+
+write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ProxyConfigVersion.cmake
+ VERSION ${FREERDP_VERSION} COMPATIBILITY SameMajorVersion)
+
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ProxyConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/FreeRDP-ProxyConfigVersion.cmake
+ DESTINATION ${FREERDP_PROXY_CMAKE_INSTALL_DIR})
+install(EXPORT FreeRDP-ProxyTargets DESTINATION ${FREERDP_PROXY_CMAKE_INSTALL_DIR})
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/proxy")
+
+option(WITH_PROXY_APP "Compile proxy application" ON)
+
+if (WITH_PROXY_APP)
+ add_subdirectory("cli")
+endif()
+
+option(WITH_PROXY_MODULES "Compile proxy modules" ON)
+if (WITH_PROXY_MODULES)
+ add_subdirectory("modules")
+endif()
+
diff --git a/server/proxy/FreeRDP-ProxyConfig.cmake.in b/server/proxy/FreeRDP-ProxyConfig.cmake.in
new file mode 100644
index 0000000..406da3a
--- /dev/null
+++ b/server/proxy/FreeRDP-ProxyConfig.cmake.in
@@ -0,0 +1,10 @@
+
+@PACKAGE_INIT@
+
+set(FreeRDP-Proxy_VERSION_MAJOR "@FREERDP_VERSION_MAJOR@")
+set(FreeRDP-Proxy_VERSION_MINOR "@FREERDP_VERSION_MINOR@")
+set(FreeRDP-Proxy_VERSION_REVISION "@FREERDP_VERSION_REVISION@")
+
+set_and_check(FreeRDP-Proxy_INCLUDE_DIR "@PACKAGE_FREERDP_INCLUDE_DIR@")
+
+include("${CMAKE_CURRENT_LIST_DIR}/FreeRDP-ProxyTargets.cmake")
diff --git a/server/proxy/channels/CMakeLists.txt b/server/proxy/channels/CMakeLists.txt
new file mode 100644
index 0000000..1915d83
--- /dev/null
+++ b/server/proxy/channels/CMakeLists.txt
@@ -0,0 +1,17 @@
+
+set(MODULE_NAME pf_channels)
+set(SOURCES
+ pf_channel_rdpdr.c
+ pf_channel_rdpdr.h
+ pf_channel_drdynvc.c
+ pf_channel_drdynvc.h
+)
+
+if (WITH_PROXY_EMULATE_SMARTCARD)
+ list(APPEND SOURCES
+ pf_channel_smartcard.c
+ pf_channel_smartcard.h
+ )
+endif()
+
+add_library(${MODULE_NAME} OBJECT ${SOURCES})
diff --git a/server/proxy/channels/pf_channel_drdynvc.c b/server/proxy/channels/pf_channel_drdynvc.c
new file mode 100644
index 0000000..9d8cab9
--- /dev/null
+++ b/server/proxy/channels/pf_channel_drdynvc.c
@@ -0,0 +1,711 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * pf_channel_drdynvc
+ *
+ * Copyright 2022 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/channels/drdynvc.h>
+#include <freerdp/utils/drdynvc.h>
+#include <freerdp/server/proxy/proxy_log.h>
+
+#include "pf_channel_drdynvc.h"
+#include "../pf_channel.h"
+#include "../proxy_modules.h"
+#include "../pf_utils.h"
+
+#define DTAG PROXY_TAG("drdynvc")
+
+/** @brief channel opened status */
+typedef enum
+{
+ CHANNEL_OPENSTATE_WAITING_OPEN_STATUS, /*!< dynamic channel waiting for create response */
+ CHANNEL_OPENSTATE_OPENED, /*!< opened */
+ CHANNEL_OPENSTATE_CLOSED /*!< dynamic channel has been opened then closed */
+} PfDynChannelOpenStatus;
+
+typedef struct p_server_dynamic_channel_context pServerDynamicChannelContext;
+typedef struct DynChannelTrackerState DynChannelTrackerState;
+
+typedef PfChannelResult (*dynamic_channel_on_data_fn)(pServerContext* ps,
+ pServerDynamicChannelContext* channel,
+ BOOL isBackData, ChannelStateTracker* tracker,
+ BOOL firstPacket, BOOL lastPacket);
+
+/** @brief tracker state for a drdynvc stream */
+struct DynChannelTrackerState
+{
+ UINT32 currentDataLength;
+ UINT32 CurrentDataReceived;
+ UINT32 CurrentDataFragments;
+ wStream* currentPacket;
+ dynamic_channel_on_data_fn dataCallback;
+};
+
+typedef void (*channel_data_dtor_fn)(void** user_data);
+
+struct p_server_dynamic_channel_context
+{
+ char* channelName;
+ UINT32 channelId;
+ PfDynChannelOpenStatus openStatus;
+ pf_utils_channel_mode channelMode;
+ BOOL packetReassembly;
+ DynChannelTrackerState backTracker;
+ DynChannelTrackerState frontTracker;
+
+ void* channelData;
+ channel_data_dtor_fn channelDataDtor;
+};
+
+/** @brief context for the dynamic channel */
+typedef struct
+{
+ wHashTable* channels;
+ ChannelStateTracker* backTracker;
+ ChannelStateTracker* frontTracker;
+ wLog* log;
+} DynChannelContext;
+
+/** @brief result of dynamic channel packet treatment */
+typedef enum
+{
+ DYNCVC_READ_OK, /*!< read was OK */
+ DYNCVC_READ_ERROR, /*!< an error happened during read */
+ DYNCVC_READ_INCOMPLETE /*!< missing bytes to read the complete packet */
+} DynvcReadResult;
+
+static const char* openstatus2str(PfDynChannelOpenStatus status)
+{
+ switch (status)
+ {
+ case CHANNEL_OPENSTATE_WAITING_OPEN_STATUS:
+ return "CHANNEL_OPENSTATE_WAITING_OPEN_STATUS";
+ case CHANNEL_OPENSTATE_CLOSED:
+ return "CHANNEL_OPENSTATE_CLOSED";
+ case CHANNEL_OPENSTATE_OPENED:
+ return "CHANNEL_OPENSTATE_OPENED";
+ default:
+ return "CHANNEL_OPENSTATE_UNKNOWN";
+ }
+}
+
+static PfChannelResult data_cb(pServerContext* ps, pServerDynamicChannelContext* channel,
+ BOOL isBackData, ChannelStateTracker* tracker, BOOL firstPacket,
+ BOOL lastPacket)
+{
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(channel);
+ WINPR_ASSERT(tracker);
+ WINPR_ASSERT(ps->pdata);
+
+ wStream* currentPacket = channelTracker_getCurrentPacket(tracker);
+ proxyDynChannelInterceptData dyn = { .name = channel->channelName,
+ .channelId = channel->channelId,
+ .data = currentPacket,
+ .isBackData = isBackData,
+ .first = firstPacket,
+ .last = lastPacket,
+ .rewritten = FALSE,
+ .packetSize = channelTracker_getCurrentPacketSize(tracker),
+ .result = PF_CHANNEL_RESULT_ERROR };
+ Stream_SealLength(dyn.data);
+ if (!pf_modules_run_filter(ps->pdata->module, FILTER_TYPE_INTERCEPT_CHANNEL, ps->pdata, &dyn))
+ return PF_CHANNEL_RESULT_ERROR;
+
+ channelTracker_setCurrentPacketSize(tracker, dyn.packetSize);
+ if (dyn.rewritten)
+ return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, !isBackData);
+ return dyn.result;
+}
+
+static pServerDynamicChannelContext* DynamicChannelContext_new(wLog* log, pServerContext* ps,
+ const char* name, UINT32 id)
+{
+ WINPR_ASSERT(log);
+
+ pServerDynamicChannelContext* ret = calloc(1, sizeof(*ret));
+ if (!ret)
+ {
+ WLog_Print(log, WLOG_ERROR, "error allocating dynamic channel context '%s'", name);
+ return NULL;
+ }
+
+ ret->channelId = id;
+ ret->channelName = _strdup(name);
+ if (!ret->channelName)
+ {
+ WLog_Print(log, WLOG_ERROR, "error allocating name in dynamic channel context '%s'", name);
+ free(ret);
+ return NULL;
+ }
+
+ ret->frontTracker.dataCallback = data_cb;
+ ret->backTracker.dataCallback = data_cb;
+
+ proxyChannelToInterceptData dyn = { .name = name, .channelId = id, .intercept = FALSE };
+ if (pf_modules_run_filter(ps->pdata->module, FILTER_TYPE_DYN_INTERCEPT_LIST, ps->pdata, &dyn) &&
+ dyn.intercept)
+ ret->channelMode = PF_UTILS_CHANNEL_INTERCEPT;
+ else
+ ret->channelMode = pf_utils_get_channel_mode(ps->pdata->config, name);
+ ret->openStatus = CHANNEL_OPENSTATE_OPENED;
+ ret->packetReassembly = (ret->channelMode == PF_UTILS_CHANNEL_INTERCEPT);
+
+ return ret;
+}
+
+static void DynamicChannelContext_free(void* ptr)
+{
+ pServerDynamicChannelContext* c = (pServerDynamicChannelContext*)ptr;
+ if (!c)
+ return;
+
+ if (c->backTracker.currentPacket)
+ Stream_Free(c->backTracker.currentPacket, TRUE);
+
+ if (c->frontTracker.currentPacket)
+ Stream_Free(c->frontTracker.currentPacket, TRUE);
+
+ if (c->channelDataDtor)
+ c->channelDataDtor(&c->channelData);
+
+ free(c->channelName);
+ free(c);
+}
+
+static UINT32 ChannelId_Hash(const void* key)
+{
+ const UINT32* v = (const UINT32*)key;
+ return *v;
+}
+
+static BOOL ChannelId_Compare(const void* objA, const void* objB)
+{
+ const UINT32* v1 = objA;
+ const UINT32* v2 = objB;
+ return (*v1 == *v2);
+}
+
+static DynvcReadResult dynvc_read_varInt(wLog* log, wStream* s, size_t len, UINT64* varInt,
+ BOOL last)
+{
+ WINPR_ASSERT(varInt);
+ switch (len)
+ {
+ case 0x00:
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 1))
+ return last ? DYNCVC_READ_ERROR : DYNCVC_READ_INCOMPLETE;
+ Stream_Read_UINT8(s, *varInt);
+ break;
+ case 0x01:
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 2))
+ return last ? DYNCVC_READ_ERROR : DYNCVC_READ_INCOMPLETE;
+ Stream_Read_UINT16(s, *varInt);
+ break;
+ case 0x02:
+ if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 4))
+ return last ? DYNCVC_READ_ERROR : DYNCVC_READ_INCOMPLETE;
+ Stream_Read_UINT32(s, *varInt);
+ break;
+ case 0x03:
+ default:
+ WLog_Print(log, WLOG_ERROR, "Unknown int len %" PRIuz, len);
+ return DYNCVC_READ_ERROR;
+ }
+ return DYNCVC_READ_OK;
+}
+
+static PfChannelResult DynvcTrackerPeekFn(ChannelStateTracker* tracker, BOOL firstPacket,
+ BOOL lastPacket)
+{
+ BYTE cmd = 0;
+ BYTE byte0 = 0;
+ wStream* s = NULL;
+ wStream sbuffer;
+ BOOL haveChannelId = 0;
+ BOOL haveLength = 0;
+ UINT64 dynChannelId = 0;
+ UINT64 Length = 0;
+ pServerDynamicChannelContext* dynChannel = NULL;
+
+ WINPR_ASSERT(tracker);
+
+ DynChannelContext* dynChannelContext =
+ (DynChannelContext*)channelTracker_getCustomData(tracker);
+ WINPR_ASSERT(dynChannelContext);
+
+ BOOL isBackData = (tracker == dynChannelContext->backTracker);
+ DynChannelTrackerState* trackerState = NULL;
+
+ UINT32 flags = lastPacket ? CHANNEL_FLAG_LAST : 0;
+ proxyData* pdata = channelTracker_getPData(tracker);
+ WINPR_ASSERT(pdata);
+
+ const char* direction = isBackData ? "B->F" : "F->B";
+
+ {
+ wStream* currentPacket = channelTracker_getCurrentPacket(tracker);
+ s = Stream_StaticConstInit(&sbuffer, Stream_Buffer(currentPacket),
+ Stream_GetPosition(currentPacket));
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(dynChannelContext->log, s, 1))
+ return PF_CHANNEL_RESULT_ERROR;
+
+ Stream_Read_UINT8(s, byte0);
+ cmd = byte0 >> 4;
+
+ switch (cmd)
+ {
+ case CREATE_REQUEST_PDU:
+ case CLOSE_REQUEST_PDU:
+ case DATA_PDU:
+ case DATA_COMPRESSED_PDU:
+ haveChannelId = TRUE;
+ haveLength = FALSE;
+ break;
+ case DATA_FIRST_PDU:
+ case DATA_FIRST_COMPRESSED_PDU:
+ haveLength = TRUE;
+ haveChannelId = TRUE;
+ break;
+ default:
+ haveChannelId = FALSE;
+ haveLength = FALSE;
+ break;
+ }
+
+ if (haveChannelId)
+ {
+ BYTE cbId = byte0 & 0x03;
+
+ switch (dynvc_read_varInt(dynChannelContext->log, s, cbId, &dynChannelId, lastPacket))
+ {
+ case DYNCVC_READ_OK:
+ break;
+ case DYNCVC_READ_INCOMPLETE:
+ return PF_CHANNEL_RESULT_DROP;
+ case DYNCVC_READ_ERROR:
+ default:
+ WLog_Print(dynChannelContext->log, WLOG_ERROR,
+ "DynvcTrackerPeekFn: invalid channelId field");
+ return PF_CHANNEL_RESULT_ERROR;
+ }
+
+ /* we always try to retrieve the dynamic channel in case it would have been opened
+ * and closed
+ */
+ dynChannel = (pServerDynamicChannelContext*)HashTable_GetItemValue(
+ dynChannelContext->channels, &dynChannelId);
+ if (cmd != CREATE_REQUEST_PDU || !isBackData)
+ {
+ if (!dynChannel)
+ {
+ /* we've not found the target channel, so we drop this chunk, plus all the rest of
+ * the packet */
+ channelTracker_setMode(tracker, CHANNEL_TRACKER_DROP);
+ return PF_CHANNEL_RESULT_DROP;
+ }
+ }
+ }
+
+ if (haveLength)
+ {
+ BYTE lenLen = (byte0 >> 2) & 0x03;
+ switch (dynvc_read_varInt(dynChannelContext->log, s, lenLen, &Length, lastPacket))
+ {
+ case DYNCVC_READ_OK:
+ break;
+ case DYNCVC_READ_INCOMPLETE:
+ return PF_CHANNEL_RESULT_DROP;
+ case DYNCVC_READ_ERROR:
+ default:
+ WLog_Print(dynChannelContext->log, WLOG_ERROR,
+ "DynvcTrackerPeekFn: invalid length field");
+ return PF_CHANNEL_RESULT_ERROR;
+ }
+ }
+
+ switch (cmd)
+ {
+ case CAPABILITY_REQUEST_PDU:
+ WLog_Print(dynChannelContext->log, WLOG_DEBUG, "DynvcTracker: %s CAPABILITY_%s",
+ direction, isBackData ? "REQUEST" : "RESPONSE");
+ channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS);
+ return PF_CHANNEL_RESULT_PASS;
+
+ case CREATE_REQUEST_PDU:
+ {
+ UINT32 creationStatus = 0;
+
+ /* we only want the full packet */
+ if (!lastPacket)
+ return PF_CHANNEL_RESULT_DROP;
+
+ if (isBackData)
+ {
+ proxyChannelDataEventInfo dev = { 0 };
+ const char* name = Stream_ConstPointer(s);
+ const size_t nameLen = Stream_GetRemainingLength(s);
+
+ const size_t len = strnlen(name, nameLen);
+ if ((len == 0) || (len == nameLen))
+ return PF_CHANNEL_RESULT_ERROR;
+
+ wStream* currentPacket = channelTracker_getCurrentPacket(tracker);
+ dev.channel_id = dynChannelId;
+ dev.channel_name = name;
+ dev.data = Stream_Buffer(s);
+ dev.data_len = Stream_GetPosition(currentPacket);
+ dev.flags = flags;
+ dev.total_size = Stream_GetPosition(currentPacket);
+
+ if (dynChannel)
+ {
+ WLog_Print(
+ dynChannelContext->log, WLOG_WARN,
+ "Reusing channel id %" PRIu32 ", previously %s [state %s, mode %s], now %s",
+ dynChannel->channelId, dynChannel->channelName,
+ openstatus2str(dynChannel->openStatus),
+ pf_utils_channel_mode_string(dynChannel->channelMode), dev.channel_name);
+
+ HashTable_Remove(dynChannelContext->channels, &dynChannel->channelId);
+ }
+
+ if (!pf_modules_run_filter(pdata->module,
+ FILTER_TYPE_CLIENT_PASSTHROUGH_DYN_CHANNEL_CREATE, pdata,
+ &dev))
+ return PF_CHANNEL_RESULT_DROP; /* Silently drop */
+
+ dynChannel = DynamicChannelContext_new(dynChannelContext->log, pdata->ps, name,
+ dynChannelId);
+ if (!dynChannel)
+ {
+ WLog_Print(dynChannelContext->log, WLOG_ERROR,
+ "unable to create dynamic channel context data");
+ return PF_CHANNEL_RESULT_ERROR;
+ }
+
+ WLog_Print(dynChannelContext->log, WLOG_DEBUG, "Adding channel '%s'[%d]",
+ dynChannel->channelName, dynChannel->channelId);
+ if (!HashTable_Insert(dynChannelContext->channels, &dynChannel->channelId,
+ dynChannel))
+ {
+ WLog_Print(dynChannelContext->log, WLOG_ERROR,
+ "unable register dynamic channel context data");
+ DynamicChannelContext_free(dynChannel);
+ return PF_CHANNEL_RESULT_ERROR;
+ }
+
+ dynChannel->openStatus = CHANNEL_OPENSTATE_WAITING_OPEN_STATUS;
+
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert owns dynChannel
+ return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, FALSE);
+ }
+
+ /* CREATE_REQUEST_PDU response */
+ if (!Stream_CheckAndLogRequiredLengthWLog(dynChannelContext->log, s, 4))
+ return PF_CHANNEL_RESULT_ERROR;
+
+ Stream_Read_UINT32(s, creationStatus);
+ WLog_Print(dynChannelContext->log, WLOG_DEBUG,
+ "DynvcTracker(%" PRIu64 ",%s): %s CREATE_RESPONSE openStatus=%" PRIu32,
+ dynChannelId, dynChannel->channelName, direction, creationStatus);
+
+ if (creationStatus == 0)
+ dynChannel->openStatus = CHANNEL_OPENSTATE_OPENED;
+
+ return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, TRUE);
+ }
+
+ case CLOSE_REQUEST_PDU:
+ if (!lastPacket)
+ return PF_CHANNEL_RESULT_DROP;
+
+ WLog_Print(dynChannelContext->log, WLOG_DEBUG,
+ "DynvcTracker(%s): %s Close request on channel", dynChannel->channelName,
+ direction);
+ channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS);
+ if (dynChannel->openStatus != CHANNEL_OPENSTATE_OPENED)
+ {
+ WLog_Print(dynChannelContext->log, WLOG_WARN,
+ "DynvcTracker(%s): is in state %s, expected %s", dynChannel->channelName,
+ openstatus2str(dynChannel->openStatus),
+ openstatus2str(CHANNEL_OPENSTATE_OPENED));
+ }
+ dynChannel->openStatus = CHANNEL_OPENSTATE_CLOSED;
+ return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, !isBackData);
+
+ case SOFT_SYNC_REQUEST_PDU:
+ /* just pass then as is for now */
+ WLog_Print(dynChannelContext->log, WLOG_DEBUG, "SOFT_SYNC_REQUEST_PDU");
+ channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS);
+ /*TODO: return pf_treat_softsync_req(pdata, s);*/
+ return PF_CHANNEL_RESULT_PASS;
+
+ case SOFT_SYNC_RESPONSE_PDU:
+ /* just pass then as is for now */
+ WLog_Print(dynChannelContext->log, WLOG_DEBUG, "SOFT_SYNC_RESPONSE_PDU");
+ channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS);
+ return PF_CHANNEL_RESULT_PASS;
+
+ case DATA_FIRST_PDU:
+ case DATA_PDU:
+ /* treat these below */
+ trackerState = isBackData ? &dynChannel->backTracker : &dynChannel->frontTracker;
+ break;
+
+ case DATA_FIRST_COMPRESSED_PDU:
+ case DATA_COMPRESSED_PDU:
+ WLog_Print(dynChannelContext->log, WLOG_DEBUG,
+ "TODO: compressed data packets, pass them as is for now");
+ channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS);
+ return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, !isBackData);
+
+ default:
+ return PF_CHANNEL_RESULT_ERROR;
+ }
+
+ if (dynChannel->openStatus != CHANNEL_OPENSTATE_OPENED)
+ {
+ WLog_Print(dynChannelContext->log, WLOG_ERROR,
+ "DynvcTracker(%s [%s]): channel is not opened", dynChannel->channelName,
+ drdynvc_get_packet_type(cmd));
+ return PF_CHANNEL_RESULT_ERROR;
+ }
+
+ if ((cmd == DATA_FIRST_PDU) || (cmd == DATA_FIRST_COMPRESSED_PDU))
+ {
+ WLog_Print(dynChannelContext->log, WLOG_DEBUG,
+ "DynvcTracker(%s [%s]): %s DATA_FIRST currentPacketLength=%" PRIu64 "",
+ dynChannel->channelName, drdynvc_get_packet_type(cmd), direction, Length);
+ trackerState->currentDataLength = Length;
+ trackerState->CurrentDataReceived = 0;
+ trackerState->CurrentDataFragments = 0;
+
+ if (dynChannel->packetReassembly)
+ {
+ if (trackerState->currentPacket)
+ Stream_SetPosition(trackerState->currentPacket, 0);
+ }
+ }
+
+ if (cmd == DATA_PDU || cmd == DATA_FIRST_PDU)
+ {
+ size_t extraSize = Stream_GetRemainingLength(s);
+
+ trackerState->CurrentDataFragments++;
+ trackerState->CurrentDataReceived += extraSize;
+
+ if (dynChannel->packetReassembly)
+ {
+ if (!trackerState->currentPacket)
+ {
+ trackerState->currentPacket = Stream_New(NULL, 1024);
+ if (!trackerState->currentPacket)
+ {
+ WLog_Print(dynChannelContext->log, WLOG_ERROR,
+ "unable to create current packet");
+ return PF_CHANNEL_RESULT_ERROR;
+ }
+ }
+
+ if (!Stream_EnsureRemainingCapacity(trackerState->currentPacket, extraSize))
+ {
+ WLog_Print(dynChannelContext->log, WLOG_ERROR, "unable to grow current packet");
+ return PF_CHANNEL_RESULT_ERROR;
+ }
+
+ Stream_Write(trackerState->currentPacket, Stream_ConstPointer(s), extraSize);
+ }
+ WLog_Print(dynChannelContext->log, WLOG_DEBUG,
+ "DynvcTracker(%s [%s]): %s frags=%" PRIu32 " received=%" PRIu32 "(%" PRIu32 ")",
+ dynChannel->channelName, drdynvc_get_packet_type(cmd), direction,
+ trackerState->CurrentDataFragments, trackerState->CurrentDataReceived,
+ trackerState->currentDataLength);
+ }
+
+ if (cmd == DATA_PDU)
+ {
+ if (trackerState->currentDataLength)
+ {
+ if (trackerState->CurrentDataReceived > trackerState->currentDataLength)
+ {
+ WLog_Print(dynChannelContext->log, WLOG_ERROR,
+ "DynvcTracker (%s [%s]): reassembled packet (%" PRIu32
+ ") is bigger than announced length (%" PRIu32 ")",
+ dynChannel->channelName, drdynvc_get_packet_type(cmd),
+ trackerState->CurrentDataReceived, trackerState->currentDataLength);
+ return PF_CHANNEL_RESULT_ERROR;
+ }
+ }
+ else
+ {
+ trackerState->CurrentDataFragments = 0;
+ trackerState->CurrentDataReceived = 0;
+ }
+ }
+
+ PfChannelResult result = PF_CHANNEL_RESULT_ERROR;
+ switch (dynChannel->channelMode)
+ {
+ case PF_UTILS_CHANNEL_PASSTHROUGH:
+ result = channelTracker_flushCurrent(tracker, firstPacket, lastPacket, !isBackData);
+ break;
+ case PF_UTILS_CHANNEL_BLOCK:
+ channelTracker_setMode(tracker, CHANNEL_TRACKER_DROP);
+ result = PF_CHANNEL_RESULT_DROP;
+ break;
+ case PF_UTILS_CHANNEL_INTERCEPT:
+ if (trackerState->dataCallback)
+ {
+ result = trackerState->dataCallback(pdata->ps, dynChannel, isBackData, tracker,
+ firstPacket, lastPacket);
+ }
+ else
+ {
+ WLog_Print(dynChannelContext->log, WLOG_ERROR,
+ "no intercept callback for channel %s(fromBack=%d), dropping packet",
+ dynChannel->channelName, isBackData);
+ result = PF_CHANNEL_RESULT_DROP;
+ }
+ break;
+ default:
+ WLog_Print(dynChannelContext->log, WLOG_ERROR, "unknown channel mode %d",
+ dynChannel->channelMode);
+ result = PF_CHANNEL_RESULT_ERROR;
+ break;
+ }
+
+ if (!trackerState->currentDataLength ||
+ (trackerState->CurrentDataReceived == trackerState->currentDataLength))
+ {
+ trackerState->currentDataLength = 0;
+ trackerState->CurrentDataFragments = 0;
+ trackerState->CurrentDataReceived = 0;
+
+ if (dynChannel->packetReassembly && trackerState->currentPacket)
+ Stream_SetPosition(trackerState->currentPacket, 0);
+ }
+
+ return result;
+}
+
+static void DynChannelContext_free(void* context)
+{
+ DynChannelContext* c = context;
+ if (!c)
+ return;
+ channelTracker_free(c->backTracker);
+ channelTracker_free(c->frontTracker);
+ HashTable_Free(c->channels);
+ free(c);
+}
+
+static const char* dynamic_context(void* arg)
+{
+ proxyData* pdata = arg;
+ if (!pdata)
+ return "pdata=null";
+ return pdata->session_id;
+}
+
+static DynChannelContext* DynChannelContext_new(proxyData* pdata,
+ pServerStaticChannelContext* channel)
+{
+ DynChannelContext* dyn = calloc(1, sizeof(DynChannelContext));
+ if (!dyn)
+ return FALSE;
+
+ dyn->log = WLog_Get(DTAG);
+ WINPR_ASSERT(dyn->log);
+ WLog_SetContext(dyn->log, dynamic_context, pdata);
+
+ dyn->backTracker = channelTracker_new(channel, DynvcTrackerPeekFn, dyn);
+ if (!dyn->backTracker)
+ goto fail;
+ if (!channelTracker_setPData(dyn->backTracker, pdata))
+ goto fail;
+
+ dyn->frontTracker = channelTracker_new(channel, DynvcTrackerPeekFn, dyn);
+ if (!dyn->frontTracker)
+ goto fail;
+ if (!channelTracker_setPData(dyn->frontTracker, pdata))
+ goto fail;
+
+ dyn->channels = HashTable_New(FALSE);
+ if (!dyn->channels)
+ goto fail;
+
+ if (!HashTable_SetHashFunction(dyn->channels, ChannelId_Hash))
+ goto fail;
+
+ wObject* kobj = HashTable_KeyObject(dyn->channels);
+ WINPR_ASSERT(kobj);
+ kobj->fnObjectEquals = ChannelId_Compare;
+
+ wObject* vobj = HashTable_ValueObject(dyn->channels);
+ WINPR_ASSERT(vobj);
+ vobj->fnObjectFree = DynamicChannelContext_free;
+
+ return dyn;
+
+fail:
+ DynChannelContext_free(dyn);
+ return NULL;
+}
+
+static PfChannelResult pf_dynvc_back_data(proxyData* pdata,
+ const pServerStaticChannelContext* channel,
+ const BYTE* xdata, size_t xsize, UINT32 flags,
+ size_t totalSize)
+{
+ WINPR_ASSERT(channel);
+
+ DynChannelContext* dyn = (DynChannelContext*)channel->context;
+ WINPR_UNUSED(pdata);
+ WINPR_ASSERT(dyn);
+
+ return channelTracker_update(dyn->backTracker, xdata, xsize, flags, totalSize);
+}
+
+static PfChannelResult pf_dynvc_front_data(proxyData* pdata,
+ const pServerStaticChannelContext* channel,
+ const BYTE* xdata, size_t xsize, UINT32 flags,
+ size_t totalSize)
+{
+ WINPR_ASSERT(channel);
+
+ DynChannelContext* dyn = (DynChannelContext*)channel->context;
+ WINPR_UNUSED(pdata);
+ WINPR_ASSERT(dyn);
+
+ return channelTracker_update(dyn->frontTracker, xdata, xsize, flags, totalSize);
+}
+
+BOOL pf_channel_setup_drdynvc(proxyData* pdata, pServerStaticChannelContext* channel)
+{
+ DynChannelContext* ret = DynChannelContext_new(pdata, channel);
+ if (!ret)
+ return FALSE;
+
+ channel->onBackData = pf_dynvc_back_data;
+ channel->onFrontData = pf_dynvc_front_data;
+ channel->contextDtor = DynChannelContext_free;
+ channel->context = ret;
+ return TRUE;
+}
diff --git a/server/proxy/channels/pf_channel_drdynvc.h b/server/proxy/channels/pf_channel_drdynvc.h
new file mode 100644
index 0000000..b084143
--- /dev/null
+++ b/server/proxy/channels/pf_channel_drdynvc.h
@@ -0,0 +1,26 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * pf_channel_drdynvc
+ *
+ * Copyright 2022 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.
+ */
+#ifndef SERVER_PROXY_CHANNELS_PF_CHANNEL_DRDYNVC_H_
+#define SERVER_PROXY_CHANNELS_PF_CHANNEL_DRDYNVC_H_
+
+#include <freerdp/server/proxy/proxy_context.h>
+
+BOOL pf_channel_setup_drdynvc(proxyData* pdata, pServerStaticChannelContext* channel);
+
+#endif /* SERVER_PROXY_CHANNELS_PF_CHANNEL_DRDYNVC_H_ */
diff --git a/server/proxy/channels/pf_channel_rdpdr.c b/server/proxy/channels/pf_channel_rdpdr.c
new file mode 100644
index 0000000..cb79266
--- /dev/null
+++ b/server/proxy/channels/pf_channel_rdpdr.c
@@ -0,0 +1,2017 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2021 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2021 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/assert.h>
+#include <winpr/string.h>
+#include <winpr/print.h>
+
+#include "pf_channel_rdpdr.h"
+#include "pf_channel_smartcard.h"
+
+#include <freerdp/server/proxy/proxy_log.h>
+#include <freerdp/channels/rdpdr.h>
+#include <freerdp/channels/channels.h>
+#include <freerdp/utils/rdpdr_utils.h>
+
+#define RTAG PROXY_TAG("channel.rdpdr")
+
+#define SCARD_DEVICE_ID UINT32_MAX
+
+typedef struct
+{
+ InterceptContextMapEntry base;
+ wStream* s;
+ wStream* buffer;
+ UINT16 versionMajor;
+ UINT16 versionMinor;
+ UINT32 clientID;
+ UINT32 computerNameLen;
+ BOOL computerNameUnicode;
+ union
+ {
+ WCHAR* wc;
+ char* c;
+ void* v;
+ } computerName;
+ UINT32 SpecialDeviceCount;
+ UINT32 capabilityVersions[6];
+} pf_channel_common_context;
+
+typedef enum
+{
+ STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST = 0x01,
+ STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST = 0x02,
+ STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM = 0x04,
+ STATE_CLIENT_CHANNEL_RUNNING = 0x10
+} pf_channel_client_state;
+
+typedef struct
+{
+ pf_channel_common_context common;
+ pf_channel_client_state state;
+ UINT32 flags;
+ UINT16 maxMajorVersion;
+ UINT16 maxMinorVersion;
+ wQueue* queue;
+ wLog* log;
+} pf_channel_client_context;
+
+typedef enum
+{
+ STATE_SERVER_INITIAL,
+ STATE_SERVER_EXPECT_CLIENT_ANNOUNCE_REPLY,
+ STATE_SERVER_EXPECT_CLIENT_NAME_REQUEST,
+ STATE_SERVER_EXPECT_EXPECT_CLIENT_CAPABILITY_RESPONE,
+ STATE_SERVER_CHANNEL_RUNNING
+} pf_channel_server_state;
+
+typedef struct
+{
+ pf_channel_common_context common;
+ pf_channel_server_state state;
+ DWORD SessionId;
+ HANDLE handle;
+ wArrayList* blockedDevices;
+ wLog* log;
+} pf_channel_server_context;
+
+#define proxy_client "[proxy<-->client]"
+#define proxy_server "[proxy<-->server]"
+
+#define proxy_client_rx proxy_client " receive"
+#define proxy_client_tx proxy_client " send"
+#define proxy_server_rx proxy_server " receive"
+#define proxy_server_tx proxy_server " send"
+
+#define SERVER_RX_LOG(log, lvl, fmt, ...) WLog_Print(log, lvl, proxy_client_rx fmt, ##__VA_ARGS__)
+#define CLIENT_RX_LOG(log, lvl, fmt, ...) WLog_Print(log, lvl, proxy_server_rx fmt, ##__VA_ARGS__)
+#define SERVER_TX_LOG(log, lvl, fmt, ...) WLog_Print(log, lvl, proxy_client_tx fmt, ##__VA_ARGS__)
+#define CLIENT_TX_LOG(log, lvl, fmt, ...) WLog_Print(log, lvl, proxy_server_tx fmt, ##__VA_ARGS__)
+#define RX_LOG(srv, lvl, fmt, ...) \
+ do \
+ { \
+ if (srv) \
+ { \
+ SERVER_RX_LOG(lvl, fmt, ##__VA_ARGS__); \
+ } \
+ else \
+ { \
+ CLIENT_RX_LOG(lvl, fmt, ##__VA_ARGS__); \
+ } \
+ } while (0)
+
+#define SERVER_RXTX_LOG(send, log, lvl, fmt, ...) \
+ do \
+ { \
+ if (send) \
+ { \
+ SERVER_TX_LOG(log, lvl, fmt, ##__VA_ARGS__); \
+ } \
+ else \
+ { \
+ SERVER_RX_LOG(log, lvl, fmt, ##__VA_ARGS__); \
+ } \
+ } while (0)
+
+#define Stream_CheckAndLogRequiredLengthSrv(log, s, len) \
+ Stream_CheckAndLogRequiredLengthWLogEx(log, WLOG_WARN, s, len, 1, \
+ proxy_client_rx " %s(%s:%" PRIuz ")", __func__, \
+ __FILE__, (size_t)__LINE__)
+#define Stream_CheckAndLogRequiredLengthClient(log, s, len) \
+ Stream_CheckAndLogRequiredLengthWLogEx(log, WLOG_WARN, s, len, 1, \
+ proxy_server_rx " %s(%s:%" PRIuz ")", __func__, \
+ __FILE__, (size_t)__LINE__)
+#define Stream_CheckAndLogRequiredLengthRx(srv, log, s, len) \
+ Stream_CheckAndLogRequiredLengthRx_(srv, log, s, len, 1, __func__, __FILE__, __LINE__)
+static BOOL Stream_CheckAndLogRequiredLengthRx_(BOOL srv, wLog* log, wStream* s, size_t nmemb,
+ size_t size, const char* fkt, const char* file,
+ size_t line)
+{
+ const char* fmt =
+ srv ? proxy_server_rx " %s(%s:%" PRIuz ")" : proxy_client_rx " %s(%s:%" PRIuz ")";
+
+ return Stream_CheckAndLogRequiredLengthWLogEx(log, WLOG_WARN, s, nmemb, size, fmt, fkt, file,
+ line);
+}
+
+static const char* rdpdr_server_state_to_string(pf_channel_server_state state)
+{
+ switch (state)
+ {
+ case STATE_SERVER_INITIAL:
+ return "STATE_SERVER_INITIAL";
+ case STATE_SERVER_EXPECT_CLIENT_ANNOUNCE_REPLY:
+ return "STATE_SERVER_EXPECT_CLIENT_ANNOUNCE_REPLY";
+ case STATE_SERVER_EXPECT_CLIENT_NAME_REQUEST:
+ return "STATE_SERVER_EXPECT_CLIENT_NAME_REQUEST";
+ case STATE_SERVER_EXPECT_EXPECT_CLIENT_CAPABILITY_RESPONE:
+ return "STATE_SERVER_EXPECT_EXPECT_CLIENT_CAPABILITY_RESPONE";
+ case STATE_SERVER_CHANNEL_RUNNING:
+ return "STATE_SERVER_CHANNEL_RUNNING";
+ default:
+ return "STATE_SERVER_UNKNOWN";
+ }
+}
+
+static const char* rdpdr_client_state_to_string(pf_channel_client_state state)
+{
+ switch (state)
+ {
+ case STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST:
+ return "STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST";
+ case STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST:
+ return "STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST";
+ case STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM:
+ return "STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM";
+ case STATE_CLIENT_CHANNEL_RUNNING:
+ return "STATE_CLIENT_CHANNEL_RUNNING";
+ default:
+ return "STATE_CLIENT_UNKNOWN";
+ }
+}
+
+static wStream* rdpdr_get_send_buffer(pf_channel_common_context* rdpdr, UINT16 component,
+ UINT16 PacketID, size_t capacity)
+{
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(rdpdr->s);
+ if (!Stream_SetPosition(rdpdr->s, 0))
+ return NULL;
+ if (!Stream_EnsureCapacity(rdpdr->s, capacity + 4))
+ return NULL;
+ Stream_Write_UINT16(rdpdr->s, component);
+ Stream_Write_UINT16(rdpdr->s, PacketID);
+ return rdpdr->s;
+}
+
+static wStream* rdpdr_client_get_send_buffer(pf_channel_client_context* rdpdr, UINT16 component,
+ UINT16 PacketID, size_t capacity)
+{
+ WINPR_ASSERT(rdpdr);
+ return rdpdr_get_send_buffer(&rdpdr->common, component, PacketID, capacity);
+}
+
+static wStream* rdpdr_server_get_send_buffer(pf_channel_server_context* rdpdr, UINT16 component,
+ UINT16 PacketID, size_t capacity)
+{
+ WINPR_ASSERT(rdpdr);
+ return rdpdr_get_send_buffer(&rdpdr->common, component, PacketID, capacity);
+}
+
+static UINT rdpdr_client_send(wLog* log, pClientContext* pc, wStream* s)
+{
+ UINT16 channelId = 0;
+
+ WINPR_ASSERT(log);
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(pc->context.instance);
+
+ if (!pc->connected)
+ {
+ CLIENT_TX_LOG(log, WLOG_WARN, "Ignoring channel %s message, not connected!",
+ RDPDR_SVC_CHANNEL_NAME);
+ return CHANNEL_RC_OK;
+ }
+
+ channelId = freerdp_channels_get_id_by_name(pc->context.instance, RDPDR_SVC_CHANNEL_NAME);
+ /* Ignore unmappable channels. Might happen when the channel was already down and
+ * some delayed message is tried to be sent. */
+ if ((channelId == 0) || (channelId == UINT16_MAX))
+ return ERROR_INTERNAL_ERROR;
+
+ Stream_SealLength(s);
+ rdpdr_dump_send_packet(log, WLOG_TRACE, s, proxy_server_tx);
+ WINPR_ASSERT(pc->context.instance->SendChannelData);
+ if (!pc->context.instance->SendChannelData(pc->context.instance, channelId, Stream_Buffer(s),
+ Stream_Length(s)))
+ return ERROR_EVT_CHANNEL_NOT_FOUND;
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_seal_send_free_request(pf_channel_server_context* context, wStream* s)
+{
+ BOOL status = 0;
+ size_t len = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(context->handle);
+ WINPR_ASSERT(s);
+
+ Stream_SealLength(s);
+ len = Stream_Length(s);
+ WINPR_ASSERT(len <= ULONG_MAX);
+
+ rdpdr_dump_send_packet(context->log, WLOG_TRACE, s, proxy_client_tx);
+ status = WTSVirtualChannelWrite(context->handle, (char*)Stream_Buffer(s), (ULONG)len, NULL);
+ return (status) ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR;
+}
+
+static BOOL rdpdr_process_server_header(BOOL server, wLog* log, wStream* s, UINT16 component,
+ UINT16 PacketId, size_t expect)
+{
+ UINT16 rpacketid = 0;
+ UINT16 rcomponent = 0;
+
+ WINPR_ASSERT(s);
+ if (!Stream_CheckAndLogRequiredLengthRx(server, log, s, 4))
+ {
+ RX_LOG(server, log, WLOG_WARN, "RDPDR_HEADER[%s | %s]: expected length 4, got %" PRIuz,
+ rdpdr_component_string(component), rdpdr_packetid_string(PacketId),
+ Stream_GetRemainingLength(s));
+ return FALSE;
+ }
+
+ Stream_Read_UINT16(s, rcomponent);
+ Stream_Read_UINT16(s, rpacketid);
+
+ if (rcomponent != component)
+ {
+ RX_LOG(server, log, WLOG_WARN, "RDPDR_HEADER[%s | %s]: got component %s",
+ rdpdr_component_string(component), rdpdr_packetid_string(PacketId),
+ rdpdr_component_string(rcomponent));
+ return FALSE;
+ }
+
+ if (rpacketid != PacketId)
+ {
+ RX_LOG(server, log, WLOG_WARN, "RDPDR_HEADER[%s | %s]: got PacketID %s",
+ rdpdr_component_string(component), rdpdr_packetid_string(PacketId),
+ rdpdr_packetid_string(rpacketid));
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthRx(server, log, s, expect))
+ {
+ RX_LOG(server, log, WLOG_WARN,
+ "RDPDR_HEADER[%s | %s] not enought data, expected %" PRIuz ", "
+ "got %" PRIuz,
+ rdpdr_component_string(component), rdpdr_packetid_string(PacketId), expect,
+ Stream_GetRemainingLength(s));
+ return ERROR_INVALID_DATA;
+ }
+
+ return TRUE;
+}
+
+static BOOL rdpdr_check_version(BOOL server, wLog* log, UINT16 versionMajor, UINT16 versionMinor,
+ UINT16 component, UINT16 PacketId)
+{
+ if (versionMajor != RDPDR_VERSION_MAJOR)
+ {
+ RX_LOG(server, log, WLOG_WARN, "[%s | %s] expected MajorVersion %" PRIu16 ", got %" PRIu16,
+ rdpdr_component_string(component), rdpdr_packetid_string(PacketId),
+ RDPDR_VERSION_MAJOR, versionMajor);
+ return FALSE;
+ }
+ switch (versionMinor)
+ {
+ case RDPDR_VERSION_MINOR_RDP50:
+ case RDPDR_VERSION_MINOR_RDP51:
+ case RDPDR_VERSION_MINOR_RDP52:
+ case RDPDR_VERSION_MINOR_RDP6X:
+ case RDPDR_VERSION_MINOR_RDP10X:
+ break;
+ default:
+ {
+ RX_LOG(server, log, WLOG_WARN, "[%s | %s] unsupported MinorVersion %" PRIu16,
+ rdpdr_component_string(component), rdpdr_packetid_string(PacketId),
+ versionMinor);
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static UINT rdpdr_process_server_announce_request(pf_channel_client_context* rdpdr, wStream* s)
+{
+ const UINT16 component = RDPDR_CTYP_CORE;
+ const UINT16 packetid = PAKID_CORE_SERVER_ANNOUNCE;
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ if (!rdpdr_process_server_header(FALSE, rdpdr->log, s, component, packetid, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, rdpdr->common.versionMajor);
+ Stream_Read_UINT16(s, rdpdr->common.versionMinor);
+
+ if (!rdpdr_check_version(FALSE, rdpdr->log, rdpdr->common.versionMajor,
+ rdpdr->common.versionMinor, component, packetid))
+ return ERROR_INVALID_DATA;
+
+ /* Limit maximum channel protocol version to the one set by proxy server */
+ if (rdpdr->common.versionMajor > rdpdr->maxMajorVersion)
+ {
+ rdpdr->common.versionMajor = rdpdr->maxMajorVersion;
+ rdpdr->common.versionMinor = rdpdr->maxMinorVersion;
+ }
+ else if (rdpdr->common.versionMinor > rdpdr->maxMinorVersion)
+ rdpdr->common.versionMinor = rdpdr->maxMinorVersion;
+
+ Stream_Read_UINT32(s, rdpdr->common.clientID);
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_server_send_announce_request(pf_channel_server_context* context)
+{
+ wStream* s =
+ rdpdr_server_get_send_buffer(context, RDPDR_CTYP_CORE, PAKID_CORE_SERVER_ANNOUNCE, 8);
+ if (!s)
+ return CHANNEL_RC_NO_MEMORY;
+
+ Stream_Write_UINT16(s, context->common.versionMajor); /* VersionMajor (2 bytes) */
+ Stream_Write_UINT16(s, context->common.versionMinor); /* VersionMinor (2 bytes) */
+ Stream_Write_UINT32(s, context->common.clientID); /* ClientId (4 bytes) */
+ return rdpdr_seal_send_free_request(context, s);
+}
+
+static UINT rdpdr_process_client_announce_reply(pf_channel_server_context* rdpdr, wStream* s)
+{
+ const UINT16 component = RDPDR_CTYP_CORE;
+ const UINT16 packetid = PAKID_CORE_CLIENTID_CONFIRM;
+ UINT16 versionMajor = 0;
+ UINT16 versionMinor = 0;
+ UINT32 clientID = 0;
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ if (!rdpdr_process_server_header(TRUE, rdpdr->log, s, component, packetid, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, versionMajor);
+ Stream_Read_UINT16(s, versionMinor);
+
+ if (!rdpdr_check_version(TRUE, rdpdr->log, versionMajor, versionMinor, component, packetid))
+ return ERROR_INVALID_DATA;
+
+ if ((rdpdr->common.versionMajor != versionMajor) ||
+ (rdpdr->common.versionMinor != versionMinor))
+ {
+ SERVER_RX_LOG(
+ rdpdr->log, WLOG_WARN,
+ "[%s | %s] downgrading version from %" PRIu16 ".%" PRIu16 " to %" PRIu16 ".%" PRIu16,
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid),
+ rdpdr->common.versionMajor, rdpdr->common.versionMinor, versionMajor, versionMinor);
+ rdpdr->common.versionMajor = versionMajor;
+ rdpdr->common.versionMinor = versionMinor;
+ }
+ Stream_Read_UINT32(s, clientID);
+ if (rdpdr->common.clientID != clientID)
+ {
+ SERVER_RX_LOG(rdpdr->log, WLOG_WARN,
+ "[%s | %s] changing clientID 0x%08" PRIu32 " to 0x%08" PRIu32,
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid),
+ rdpdr->common.clientID, clientID);
+ rdpdr->common.clientID = clientID;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_send_client_announce_reply(pClientContext* pc, pf_channel_client_context* rdpdr)
+{
+ wStream* s =
+ rdpdr_client_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_CLIENTID_CONFIRM, 8);
+ if (!s)
+ return CHANNEL_RC_NO_MEMORY;
+
+ Stream_Write_UINT16(s, rdpdr->common.versionMajor);
+ Stream_Write_UINT16(s, rdpdr->common.versionMinor);
+ Stream_Write_UINT32(s, rdpdr->common.clientID);
+ return rdpdr_client_send(rdpdr->log, pc, s);
+}
+
+static UINT rdpdr_process_client_name_request(pf_channel_server_context* rdpdr, wStream* s,
+ pClientContext* pc)
+{
+ UINT32 unicodeFlag = 0;
+ UINT32 codePage = 0;
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(pc);
+
+ if (!rdpdr_process_server_header(TRUE, rdpdr->log, s, RDPDR_CTYP_CORE, PAKID_CORE_CLIENT_NAME,
+ 12))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, unicodeFlag);
+ rdpdr->common.computerNameUnicode = (unicodeFlag & 1);
+
+ Stream_Read_UINT32(s, codePage);
+ WINPR_UNUSED(codePage); /* Field is ignored */
+ Stream_Read_UINT32(s, rdpdr->common.computerNameLen);
+ if (!Stream_CheckAndLogRequiredLengthSrv(rdpdr->log, s, rdpdr->common.computerNameLen))
+ {
+ SERVER_RX_LOG(
+ rdpdr->log, WLOG_WARN, "[%s | %s]: missing data, got %" PRIu32 ", expected %" PRIu32,
+ rdpdr_component_string(RDPDR_CTYP_CORE), rdpdr_packetid_string(PAKID_CORE_CLIENT_NAME),
+ Stream_GetRemainingLength(s), rdpdr->common.computerNameLen);
+ return ERROR_INVALID_DATA;
+ }
+ void* tmp = realloc(rdpdr->common.computerName.v, rdpdr->common.computerNameLen);
+ if (!tmp)
+ return CHANNEL_RC_NO_MEMORY;
+ rdpdr->common.computerName.v = tmp;
+
+ Stream_Read(s, rdpdr->common.computerName.v, rdpdr->common.computerNameLen);
+
+ pc->computerNameLen = rdpdr->common.computerNameLen;
+ pc->computerNameUnicode = rdpdr->common.computerNameUnicode;
+ tmp = realloc(pc->computerName.v, pc->computerNameLen);
+ if (!tmp)
+ return CHANNEL_RC_NO_MEMORY;
+ pc->computerName.v = tmp;
+ memcpy(pc->computerName.v, rdpdr->common.computerName.v, pc->computerNameLen);
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_send_client_name_request(pClientContext* pc, pf_channel_client_context* rdpdr)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(pc);
+
+ {
+ void* tmp = realloc(rdpdr->common.computerName.v, pc->computerNameLen);
+ if (!tmp)
+ return CHANNEL_RC_NO_MEMORY;
+ rdpdr->common.computerName.v = tmp;
+ rdpdr->common.computerNameLen = pc->computerNameLen;
+ rdpdr->common.computerNameUnicode = pc->computerNameUnicode;
+ memcpy(rdpdr->common.computerName.v, pc->computerName.v, pc->computerNameLen);
+ }
+ s = rdpdr_client_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_CLIENT_NAME,
+ 12U + rdpdr->common.computerNameLen);
+ if (!s)
+ return CHANNEL_RC_NO_MEMORY;
+
+ Stream_Write_UINT32(s, rdpdr->common.computerNameUnicode
+ ? 1
+ : 0); /* unicodeFlag, 0 for ASCII and 1 for Unicode */
+ Stream_Write_UINT32(s, 0); /* codePage, must be set to zero */
+ Stream_Write_UINT32(s, rdpdr->common.computerNameLen);
+ Stream_Write(s, rdpdr->common.computerName.v, rdpdr->common.computerNameLen);
+ return rdpdr_client_send(rdpdr->log, pc, s);
+}
+
+#define rdpdr_ignore_capset(srv, log, s, header) \
+ rdpdr_ignore_capset_((srv), (log), (s), header, __func__)
+static UINT rdpdr_ignore_capset_(BOOL srv, wLog* log, wStream* s,
+ const RDPDR_CAPABILITY_HEADER* header, const char* fkt)
+{
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(header);
+
+ Stream_Seek(s, header->CapabilityLength);
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpdr_client_process_general_capset(pf_channel_client_context* rdpdr, wStream* s,
+ const RDPDR_CAPABILITY_HEADER* header)
+{
+ WINPR_UNUSED(rdpdr);
+ return rdpdr_ignore_capset(FALSE, rdpdr->log, s, header);
+}
+
+static UINT rdpdr_process_printer_capset(pf_channel_client_context* rdpdr, wStream* s,
+ const RDPDR_CAPABILITY_HEADER* header)
+{
+ WINPR_UNUSED(rdpdr);
+ return rdpdr_ignore_capset(FALSE, rdpdr->log, s, header);
+}
+
+static UINT rdpdr_process_port_capset(pf_channel_client_context* rdpdr, wStream* s,
+ const RDPDR_CAPABILITY_HEADER* header)
+{
+ WINPR_UNUSED(rdpdr);
+ return rdpdr_ignore_capset(FALSE, rdpdr->log, s, header);
+}
+
+static UINT rdpdr_process_drive_capset(pf_channel_client_context* rdpdr, wStream* s,
+ const RDPDR_CAPABILITY_HEADER* header)
+{
+ WINPR_UNUSED(rdpdr);
+ return rdpdr_ignore_capset(FALSE, rdpdr->log, s, header);
+}
+
+static UINT rdpdr_process_smartcard_capset(pf_channel_client_context* rdpdr, wStream* s,
+ const RDPDR_CAPABILITY_HEADER* header)
+{
+ WINPR_UNUSED(rdpdr);
+ return rdpdr_ignore_capset(FALSE, rdpdr->log, s, header);
+}
+
+static UINT rdpdr_process_server_core_capability_request(pf_channel_client_context* rdpdr,
+ wStream* s)
+{
+ UINT status = CHANNEL_RC_OK;
+ UINT16 numCapabilities = 0;
+
+ WINPR_ASSERT(rdpdr);
+
+ if (!rdpdr_process_server_header(FALSE, rdpdr->log, s, RDPDR_CTYP_CORE,
+ PAKID_CORE_SERVER_CAPABILITY, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, numCapabilities);
+ Stream_Seek(s, 2); /* pad (2 bytes) */
+
+ for (UINT16 i = 0; i < numCapabilities; i++)
+ {
+ RDPDR_CAPABILITY_HEADER header = { 0 };
+ UINT error = rdpdr_read_capset_header(rdpdr->log, s, &header);
+ if (error != CHANNEL_RC_OK)
+ return error;
+
+ if (header.CapabilityType < ARRAYSIZE(rdpdr->common.capabilityVersions))
+ {
+ if (rdpdr->common.capabilityVersions[header.CapabilityType] > header.Version)
+ rdpdr->common.capabilityVersions[header.CapabilityType] = header.Version;
+
+ WLog_Print(rdpdr->log, WLOG_TRACE,
+ "capability %s got version %" PRIu32 ", will use version %" PRIu32,
+ rdpdr_cap_type_string(header.CapabilityType), header.Version,
+ rdpdr->common.capabilityVersions[header.CapabilityType]);
+ }
+
+ switch (header.CapabilityType)
+ {
+ case CAP_GENERAL_TYPE:
+ status = rdpdr_client_process_general_capset(rdpdr, s, &header);
+ break;
+
+ case CAP_PRINTER_TYPE:
+ status = rdpdr_process_printer_capset(rdpdr, s, &header);
+ break;
+
+ case CAP_PORT_TYPE:
+ status = rdpdr_process_port_capset(rdpdr, s, &header);
+ break;
+
+ case CAP_DRIVE_TYPE:
+ status = rdpdr_process_drive_capset(rdpdr, s, &header);
+ break;
+
+ case CAP_SMARTCARD_TYPE:
+ status = rdpdr_process_smartcard_capset(rdpdr, s, &header);
+ break;
+
+ default:
+ WLog_Print(rdpdr->log, WLOG_WARN,
+ "unknown capability 0x%04" PRIx16 ", length %" PRIu16
+ ", version %" PRIu32,
+ header.CapabilityType, header.CapabilityLength, header.Version);
+ Stream_Seek(s, header.CapabilityLength);
+ break;
+ }
+
+ if (status != CHANNEL_RC_OK)
+ return status;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static BOOL rdpdr_write_general_capset(wLog* log, pf_channel_common_context* rdpdr, wStream* s)
+{
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ const RDPDR_CAPABILITY_HEADER header = { CAP_GENERAL_TYPE, 44,
+ rdpdr->capabilityVersions[CAP_GENERAL_TYPE] };
+ if (rdpdr_write_capset_header(log, s, &header) != CHANNEL_RC_OK)
+ return FALSE;
+ Stream_Write_UINT32(s, 0); /* osType, ignored on receipt */
+ Stream_Write_UINT32(s, 0); /* osVersion, should be ignored */
+ Stream_Write_UINT16(s, rdpdr->versionMajor); /* protocolMajorVersion, must be set to 1 */
+ Stream_Write_UINT16(s, rdpdr->versionMinor); /* protocolMinorVersion */
+ Stream_Write_UINT32(s, 0x0000FFFF); /* ioCode1 */
+ Stream_Write_UINT32(s, 0); /* ioCode2, must be set to zero, reserved for future use */
+ Stream_Write_UINT32(s, RDPDR_DEVICE_REMOVE_PDUS | RDPDR_CLIENT_DISPLAY_NAME_PDU |
+ RDPDR_USER_LOGGEDON_PDU); /* extendedPDU */
+ Stream_Write_UINT32(s, ENABLE_ASYNCIO); /* extraFlags1 */
+ Stream_Write_UINT32(s, 0); /* extraFlags2, must be set to zero, reserved for future use */
+ Stream_Write_UINT32(s, rdpdr->SpecialDeviceCount); /* SpecialTypeDeviceCap, number of special
+ devices to be redirected before logon */
+ return TRUE;
+}
+
+static BOOL rdpdr_write_printer_capset(wLog* log, pf_channel_common_context* rdpdr, wStream* s)
+{
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ const RDPDR_CAPABILITY_HEADER header = { CAP_PRINTER_TYPE, 8,
+ rdpdr->capabilityVersions[CAP_PRINTER_TYPE] };
+ if (rdpdr_write_capset_header(log, s, &header) != CHANNEL_RC_OK)
+ return FALSE;
+ return TRUE;
+}
+
+static BOOL rdpdr_write_port_capset(wLog* log, pf_channel_common_context* rdpdr, wStream* s)
+{
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ const RDPDR_CAPABILITY_HEADER header = { CAP_PORT_TYPE, 8,
+ rdpdr->capabilityVersions[CAP_PORT_TYPE] };
+ if (rdpdr_write_capset_header(log, s, &header) != CHANNEL_RC_OK)
+ return FALSE;
+ return TRUE;
+}
+
+static BOOL rdpdr_write_drive_capset(wLog* log, pf_channel_common_context* rdpdr, wStream* s)
+{
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ const RDPDR_CAPABILITY_HEADER header = { CAP_DRIVE_TYPE, 8,
+ rdpdr->capabilityVersions[CAP_DRIVE_TYPE] };
+ if (rdpdr_write_capset_header(log, s, &header) != CHANNEL_RC_OK)
+ return FALSE;
+ return TRUE;
+}
+
+static BOOL rdpdr_write_smartcard_capset(wLog* log, pf_channel_common_context* rdpdr, wStream* s)
+{
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ const RDPDR_CAPABILITY_HEADER header = { CAP_SMARTCARD_TYPE, 8,
+ rdpdr->capabilityVersions[CAP_SMARTCARD_TYPE] };
+ if (rdpdr_write_capset_header(log, s, &header) != CHANNEL_RC_OK)
+ return FALSE;
+ return TRUE;
+}
+
+static UINT rdpdr_send_server_capability_request(pf_channel_server_context* rdpdr)
+{
+ wStream* s =
+ rdpdr_server_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_SERVER_CAPABILITY, 8);
+ if (!s)
+ return CHANNEL_RC_NO_MEMORY;
+ Stream_Write_UINT16(s, 5); /* numCapabilities */
+ Stream_Write_UINT16(s, 0); /* pad */
+ if (!rdpdr_write_general_capset(rdpdr->log, &rdpdr->common, s))
+ return CHANNEL_RC_NO_MEMORY;
+ if (!rdpdr_write_printer_capset(rdpdr->log, &rdpdr->common, s))
+ return CHANNEL_RC_NO_MEMORY;
+ if (!rdpdr_write_port_capset(rdpdr->log, &rdpdr->common, s))
+ return CHANNEL_RC_NO_MEMORY;
+ if (!rdpdr_write_drive_capset(rdpdr->log, &rdpdr->common, s))
+ return CHANNEL_RC_NO_MEMORY;
+ if (!rdpdr_write_smartcard_capset(rdpdr->log, &rdpdr->common, s))
+ return CHANNEL_RC_NO_MEMORY;
+ return rdpdr_seal_send_free_request(rdpdr, s);
+}
+
+static UINT rdpdr_process_client_capability_response(pf_channel_server_context* rdpdr, wStream* s)
+{
+ const UINT16 component = RDPDR_CTYP_CORE;
+ const UINT16 packetid = PAKID_CORE_CLIENT_CAPABILITY;
+ UINT status = CHANNEL_RC_OK;
+ UINT16 numCapabilities = 0;
+ WINPR_ASSERT(rdpdr);
+
+ if (!rdpdr_process_server_header(TRUE, rdpdr->log, s, component, packetid, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, numCapabilities);
+ Stream_Seek_UINT16(s); /* padding */
+
+ for (UINT16 x = 0; x < numCapabilities; x++)
+ {
+ RDPDR_CAPABILITY_HEADER header = { 0 };
+ UINT error = rdpdr_read_capset_header(rdpdr->log, s, &header);
+ if (error != CHANNEL_RC_OK)
+ return error;
+ if (header.CapabilityType < ARRAYSIZE(rdpdr->common.capabilityVersions))
+ {
+ if (rdpdr->common.capabilityVersions[header.CapabilityType] > header.Version)
+ rdpdr->common.capabilityVersions[header.CapabilityType] = header.Version;
+
+ WLog_Print(rdpdr->log, WLOG_TRACE,
+ "capability %s got version %" PRIu32 ", will use version %" PRIu32,
+ rdpdr_cap_type_string(header.CapabilityType), header.Version,
+ rdpdr->common.capabilityVersions[header.CapabilityType]);
+ }
+
+ switch (header.CapabilityType)
+ {
+ case CAP_GENERAL_TYPE:
+ status = rdpdr_ignore_capset(TRUE, rdpdr->log, s, &header);
+ break;
+
+ case CAP_PRINTER_TYPE:
+ status = rdpdr_ignore_capset(TRUE, rdpdr->log, s, &header);
+ break;
+
+ case CAP_PORT_TYPE:
+ status = rdpdr_ignore_capset(TRUE, rdpdr->log, s, &header);
+ break;
+
+ case CAP_DRIVE_TYPE:
+ status = rdpdr_ignore_capset(TRUE, rdpdr->log, s, &header);
+ break;
+
+ case CAP_SMARTCARD_TYPE:
+ status = rdpdr_ignore_capset(TRUE, rdpdr->log, s, &header);
+ break;
+
+ default:
+ SERVER_RX_LOG(rdpdr->log, WLOG_WARN,
+ "[%s | %s] invalid capability type 0x%04" PRIx16,
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid),
+ header.CapabilityType);
+ status = ERROR_INVALID_DATA;
+ break;
+ }
+
+ if (status != CHANNEL_RC_OK)
+ break;
+ }
+
+ return status;
+}
+
+static UINT rdpdr_send_client_capability_response(pClientContext* pc,
+ pf_channel_client_context* rdpdr)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(rdpdr);
+ s = rdpdr_client_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_CLIENT_CAPABILITY, 4);
+ if (!s)
+ return CHANNEL_RC_NO_MEMORY;
+
+ Stream_Write_UINT16(s, 5); /* numCapabilities */
+ Stream_Write_UINT16(s, 0); /* pad */
+ if (!rdpdr_write_general_capset(rdpdr->log, &rdpdr->common, s))
+ return CHANNEL_RC_NO_MEMORY;
+ if (!rdpdr_write_printer_capset(rdpdr->log, &rdpdr->common, s))
+ return CHANNEL_RC_NO_MEMORY;
+ if (!rdpdr_write_port_capset(rdpdr->log, &rdpdr->common, s))
+ return CHANNEL_RC_NO_MEMORY;
+ if (!rdpdr_write_drive_capset(rdpdr->log, &rdpdr->common, s))
+ return CHANNEL_RC_NO_MEMORY;
+ if (!rdpdr_write_smartcard_capset(rdpdr->log, &rdpdr->common, s))
+ return CHANNEL_RC_NO_MEMORY;
+ return rdpdr_client_send(rdpdr->log, pc, s);
+}
+
+static UINT rdpdr_send_server_clientid_confirm(pf_channel_server_context* rdpdr)
+{
+ wStream* s = NULL;
+
+ s = rdpdr_server_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_CLIENTID_CONFIRM, 8);
+ if (!s)
+ return CHANNEL_RC_NO_MEMORY;
+ Stream_Write_UINT16(s, rdpdr->common.versionMajor);
+ Stream_Write_UINT16(s, rdpdr->common.versionMinor);
+ Stream_Write_UINT32(s, rdpdr->common.clientID);
+ return rdpdr_seal_send_free_request(rdpdr, s);
+}
+
+static UINT rdpdr_process_server_clientid_confirm(pf_channel_client_context* rdpdr, wStream* s)
+{
+ UINT16 versionMajor = 0;
+ UINT16 versionMinor = 0;
+ UINT32 clientID = 0;
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ if (!rdpdr_process_server_header(FALSE, rdpdr->log, s, RDPDR_CTYP_CORE,
+ PAKID_CORE_CLIENTID_CONFIRM, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, versionMajor);
+ Stream_Read_UINT16(s, versionMinor);
+ if (!rdpdr_check_version(FALSE, rdpdr->log, versionMajor, versionMinor, RDPDR_CTYP_CORE,
+ PAKID_CORE_CLIENTID_CONFIRM))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, clientID);
+
+ if ((versionMajor != rdpdr->common.versionMajor) ||
+ (versionMinor != rdpdr->common.versionMinor))
+ {
+ CLIENT_RX_LOG(rdpdr->log, WLOG_WARN,
+ "[%s | %s] Version mismatch, sent %" PRIu16 ".%" PRIu16
+ ", downgraded to %" PRIu16 ".%" PRIu16,
+ rdpdr_component_string(RDPDR_CTYP_CORE),
+ rdpdr_packetid_string(PAKID_CORE_CLIENTID_CONFIRM),
+ rdpdr->common.versionMajor, rdpdr->common.versionMinor, versionMajor,
+ versionMinor);
+ rdpdr->common.versionMajor = versionMajor;
+ rdpdr->common.versionMinor = versionMinor;
+ }
+
+ if (clientID != rdpdr->common.clientID)
+ {
+ CLIENT_RX_LOG(rdpdr->log, WLOG_WARN,
+ "[%s | %s] clientID mismatch, sent 0x%08" PRIx32 ", changed to 0x%08" PRIx32,
+ rdpdr_component_string(RDPDR_CTYP_CORE),
+ rdpdr_packetid_string(PAKID_CORE_CLIENTID_CONFIRM), rdpdr->common.clientID,
+ clientID);
+ rdpdr->common.clientID = clientID;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static BOOL
+rdpdr_process_server_capability_request_or_clientid_confirm(pf_channel_client_context* rdpdr,
+ wStream* s)
+{
+ const UINT32 mask = STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM |
+ STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST;
+ const UINT16 rcomponent = RDPDR_CTYP_CORE;
+ UINT16 component = 0;
+ UINT16 packetid = 0;
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ if ((rdpdr->flags & mask) == mask)
+ {
+ CLIENT_RX_LOG(rdpdr->log, WLOG_WARN, "already past this state, abort!");
+ return FALSE;
+ }
+
+ if (!Stream_CheckAndLogRequiredLengthClient(rdpdr->log, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT16(s, component);
+ if (rcomponent != component)
+ {
+ CLIENT_RX_LOG(rdpdr->log, WLOG_WARN, "got component %s, expected %s",
+ rdpdr_component_string(component), rdpdr_component_string(rcomponent));
+ return FALSE;
+ }
+ Stream_Read_UINT16(s, packetid);
+ Stream_Rewind(s, 4);
+
+ switch (packetid)
+ {
+ case PAKID_CORE_SERVER_CAPABILITY:
+ if (rdpdr->flags & STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST)
+ {
+ CLIENT_RX_LOG(rdpdr->log, WLOG_WARN, "got duplicate packetid %s",
+ rdpdr_packetid_string(packetid));
+ return FALSE;
+ }
+ rdpdr->flags |= STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST;
+ return rdpdr_process_server_core_capability_request(rdpdr, s) == CHANNEL_RC_OK;
+ case PAKID_CORE_CLIENTID_CONFIRM:
+ default:
+ if (rdpdr->flags & STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM)
+ {
+ CLIENT_RX_LOG(rdpdr->log, WLOG_WARN, "got duplicate packetid %s",
+ rdpdr_packetid_string(packetid));
+ return FALSE;
+ }
+ rdpdr->flags |= STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM;
+ return rdpdr_process_server_clientid_confirm(rdpdr, s) == CHANNEL_RC_OK;
+ }
+}
+
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+static UINT rdpdr_send_emulated_scard_device_list_announce_request(pClientContext* pc,
+ pf_channel_client_context* rdpdr)
+{
+ wStream* s = NULL;
+
+ s = rdpdr_client_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_DEVICELIST_ANNOUNCE, 24);
+ if (!s)
+ return CHANNEL_RC_NO_MEMORY;
+
+ Stream_Write_UINT32(s, 1); /* deviceCount -> our emulated smartcard only */
+ Stream_Write_UINT32(s, RDPDR_DTYP_SMARTCARD); /* deviceType */
+ Stream_Write_UINT32(
+ s, SCARD_DEVICE_ID); /* deviceID -> reserve highest value for the emulated smartcard */
+ Stream_Write(s, "SCARD\0\0\0", 8);
+ Stream_Write_UINT32(s, 6);
+ Stream_Write(s, "SCARD\0", 6);
+
+ return rdpdr_client_send(rdpdr->log, pc, s);
+}
+
+static UINT rdpdr_send_emulated_scard_device_remove(pClientContext* pc,
+ pf_channel_client_context* rdpdr)
+{
+ wStream* s = NULL;
+
+ s = rdpdr_client_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_DEVICELIST_REMOVE, 24);
+ if (!s)
+ return CHANNEL_RC_NO_MEMORY;
+
+ Stream_Write_UINT32(s, 1); /* deviceCount -> our emulated smartcard only */
+ Stream_Write_UINT32(
+ s, SCARD_DEVICE_ID); /* deviceID -> reserve highest value for the emulated smartcard */
+
+ return rdpdr_client_send(rdpdr->log, pc, s);
+}
+
+static UINT rdpdr_process_server_device_announce_response(pf_channel_client_context* rdpdr,
+ wStream* s)
+{
+ const UINT16 component = RDPDR_CTYP_CORE;
+ const UINT16 packetid = PAKID_CORE_DEVICE_REPLY;
+ UINT32 deviceID = 0;
+ UINT32 resultCode = 0;
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ if (!rdpdr_process_server_header(TRUE, rdpdr->log, s, component, packetid, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, deviceID);
+ Stream_Read_UINT32(s, resultCode);
+
+ if (deviceID != SCARD_DEVICE_ID)
+ {
+ CLIENT_RX_LOG(rdpdr->log, WLOG_WARN,
+ "[%s | %s] deviceID mismatch, sent 0x%08" PRIx32 ", changed to 0x%08" PRIx32,
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid),
+ SCARD_DEVICE_ID, deviceID);
+ }
+ else if (resultCode != 0)
+ {
+ CLIENT_RX_LOG(rdpdr->log, WLOG_WARN,
+ "[%s | %s] deviceID 0x%08" PRIx32 " resultCode=0x%08" PRIx32,
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid), deviceID,
+ resultCode);
+ }
+ else
+ CLIENT_RX_LOG(rdpdr->log, WLOG_DEBUG,
+ "[%s | %s] deviceID 0x%08" PRIx32 " resultCode=0x%08" PRIx32
+ " -> emulated smartcard redirected!",
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid), deviceID,
+ resultCode);
+
+ return CHANNEL_RC_OK;
+}
+#endif
+
+static BOOL pf_channel_rdpdr_rewrite_device_list_to(wStream* s, UINT32 fromVersion,
+ UINT32 toVersion)
+{
+ BOOL rc = FALSE;
+ if (fromVersion == toVersion)
+ return TRUE;
+
+ const size_t cap = Stream_GetRemainingLength(s);
+ wStream* clone = Stream_New(NULL, cap);
+ if (!clone)
+ goto fail;
+ const size_t pos = Stream_GetPosition(s);
+ Stream_Copy(s, clone, cap);
+ Stream_SealLength(clone);
+
+ Stream_SetPosition(clone, 0);
+ Stream_SetPosition(s, pos);
+
+ /* Skip device count */
+ if (!Stream_SafeSeek(s, 4))
+ goto fail;
+
+ UINT32 count = 0;
+ if (Stream_GetRemainingLength(clone) < 4)
+ goto fail;
+ Stream_Read_UINT32(clone, count);
+
+ for (UINT32 x = 0; x < count; x++)
+ {
+ RdpdrDevice device = { 0 };
+ const size_t charCount = ARRAYSIZE(device.PreferredDosName);
+ if (Stream_GetRemainingLength(clone) < 20)
+ goto fail;
+
+ Stream_Read_UINT32(clone, device.DeviceType); /* DeviceType (4 bytes) */
+ Stream_Read_UINT32(clone, device.DeviceId); /* DeviceId (4 bytes) */
+ Stream_Read(clone, device.PreferredDosName, charCount); /* PreferredDosName (8 bytes) */
+ Stream_Read_UINT32(clone, device.DeviceDataLength); /* DeviceDataLength (4 bytes) */
+ device.DeviceData = Stream_Pointer(clone);
+ if (!Stream_SafeSeek(clone, device.DeviceDataLength))
+ goto fail;
+
+ if (!Stream_EnsureRemainingCapacity(s, 20))
+ goto fail;
+ Stream_Write_UINT32(s, device.DeviceType);
+ Stream_Write_UINT32(s, device.DeviceId);
+ Stream_Write(s, device.PreferredDosName, charCount);
+
+ if (device.DeviceType == RDPDR_DTYP_FILESYSTEM)
+ {
+ if (toVersion == DRIVE_CAPABILITY_VERSION_01)
+ Stream_Write_UINT32(s, 0); /* No unicode name */
+ else
+ {
+ const size_t datalen = charCount * sizeof(WCHAR);
+ if (!Stream_EnsureRemainingCapacity(s, datalen + sizeof(UINT32)))
+ goto fail;
+ Stream_Write_UINT32(s, datalen);
+
+ const SSIZE_T rcw = Stream_Write_UTF16_String_From_UTF8(
+ s, charCount, device.PreferredDosName, charCount - 1, TRUE);
+ if (rcw < 0)
+ goto fail;
+ }
+ }
+ else
+ {
+ Stream_Write_UINT32(s, device.DeviceDataLength);
+ if (!Stream_EnsureRemainingCapacity(s, device.DeviceDataLength))
+ goto fail;
+ Stream_Write(s, device.DeviceData, device.DeviceDataLength);
+ }
+ }
+
+ Stream_SealLength(s);
+ rc = TRUE;
+
+fail:
+ Stream_Free(clone, TRUE);
+ return rc;
+}
+
+static BOOL pf_channel_rdpdr_rewrite_device_list(pf_channel_client_context* rdpdr,
+ pServerContext* ps, wStream* s, BOOL toServer)
+{
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(ps);
+
+ const size_t pos = Stream_GetPosition(s);
+ UINT16 component = 0;
+ UINT16 packetid = 0;
+ Stream_SetPosition(s, 0);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, 4))
+ return FALSE;
+
+ Stream_Read_UINT16(s, component);
+ Stream_Read_UINT16(s, packetid);
+ if ((component != RDPDR_CTYP_CORE) || (packetid != PAKID_CORE_DEVICELIST_ANNOUNCE))
+ {
+ Stream_SetPosition(s, pos);
+ return TRUE;
+ }
+
+ const pf_channel_server_context* srv =
+ HashTable_GetItemValue(ps->interceptContextMap, RDPDR_SVC_CHANNEL_NAME);
+ if (!srv)
+ {
+ WLog_Print(rdpdr->log, WLOG_ERROR, "No channel %s in intercep map", RDPDR_SVC_CHANNEL_NAME);
+ return FALSE;
+ }
+
+ UINT32 from = srv->common.capabilityVersions[CAP_DRIVE_TYPE];
+ UINT32 to = rdpdr->common.capabilityVersions[CAP_DRIVE_TYPE];
+ if (toServer)
+ {
+ from = rdpdr->common.capabilityVersions[CAP_DRIVE_TYPE];
+ to = srv->common.capabilityVersions[CAP_DRIVE_TYPE];
+ }
+ if (!pf_channel_rdpdr_rewrite_device_list_to(s, from, to))
+ return FALSE;
+
+ Stream_SetPosition(s, pos);
+ return TRUE;
+}
+
+static BOOL pf_channel_rdpdr_client_send_to_server(pf_channel_client_context* rdpdr,
+ pServerContext* ps, wStream* s)
+{
+ WINPR_ASSERT(rdpdr);
+ if (ps)
+ {
+ UINT16 server_channel_id = WTSChannelGetId(ps->context.peer, RDPDR_SVC_CHANNEL_NAME);
+
+ /* Ignore messages for channels that can not be mapped.
+ * The client might not have enabled support for this specific channel,
+ * so just drop the message. */
+ if (server_channel_id == 0)
+ return TRUE;
+
+ if (!pf_channel_rdpdr_rewrite_device_list(rdpdr, ps, s, TRUE))
+ return FALSE;
+ size_t len = Stream_Length(s);
+ Stream_SetPosition(s, len);
+ rdpdr_dump_send_packet(rdpdr->log, WLOG_TRACE, s, proxy_client_tx);
+ WINPR_ASSERT(ps->context.peer);
+ WINPR_ASSERT(ps->context.peer->SendChannelData);
+ return ps->context.peer->SendChannelData(ps->context.peer, server_channel_id,
+ Stream_Buffer(s), len);
+ }
+ return TRUE;
+}
+
+static BOOL pf_channel_send_client_queue(pClientContext* pc, pf_channel_client_context* rdpdr);
+
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+static BOOL rdpdr_process_server_loggedon_request(pServerContext* ps, pClientContext* pc,
+ pf_channel_client_context* rdpdr, wStream* s,
+ UINT16 component, UINT16 packetid)
+{
+ WINPR_ASSERT(rdpdr);
+ WLog_Print(rdpdr->log, WLOG_DEBUG, "[%s | %s]", rdpdr_component_string(component),
+ rdpdr_packetid_string(packetid));
+ if (rdpdr_send_emulated_scard_device_remove(pc, rdpdr) != CHANNEL_RC_OK)
+ return FALSE;
+ if (rdpdr_send_emulated_scard_device_list_announce_request(pc, rdpdr) != CHANNEL_RC_OK)
+ return FALSE;
+ return pf_channel_rdpdr_client_send_to_server(rdpdr, ps, s);
+}
+
+static BOOL filter_smartcard_io_requests(pf_channel_client_context* rdpdr, wStream* s,
+ UINT16* pPacketid)
+{
+ BOOL rc = FALSE;
+ UINT16 component = 0;
+ UINT16 packetid = 0;
+ UINT32 deviceID = 0;
+ size_t pos = 0;
+
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(pPacketid);
+
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, 4))
+ return FALSE;
+
+ pos = Stream_GetPosition(s);
+ Stream_Read_UINT16(s, component);
+ Stream_Read_UINT16(s, packetid);
+
+ if (Stream_GetRemainingLength(s) >= 4)
+ Stream_Read_UINT32(s, deviceID);
+
+ WLog_Print(rdpdr->log, WLOG_DEBUG, "got: [%s | %s]: [0x%08" PRIx32 "]",
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid), deviceID);
+
+ if (component != RDPDR_CTYP_CORE)
+ goto fail;
+
+ switch (packetid)
+ {
+ case PAKID_CORE_SERVER_ANNOUNCE:
+ case PAKID_CORE_CLIENTID_CONFIRM:
+ case PAKID_CORE_CLIENT_NAME:
+ case PAKID_CORE_DEVICELIST_ANNOUNCE:
+ case PAKID_CORE_DEVICELIST_REMOVE:
+ case PAKID_CORE_SERVER_CAPABILITY:
+ case PAKID_CORE_CLIENT_CAPABILITY:
+ WLog_Print(rdpdr->log, WLOG_WARN, "Filtering client -> server message [%s | %s]",
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid));
+ *pPacketid = packetid;
+ break;
+ case PAKID_CORE_USER_LOGGEDON:
+ *pPacketid = packetid;
+ break;
+ case PAKID_CORE_DEVICE_REPLY:
+ case PAKID_CORE_DEVICE_IOREQUEST:
+ if (deviceID != SCARD_DEVICE_ID)
+ goto fail;
+ *pPacketid = packetid;
+ break;
+ default:
+ if (deviceID != SCARD_DEVICE_ID)
+ goto fail;
+ WLog_Print(rdpdr->log, WLOG_WARN,
+ "Got [%s | %s] for deviceID 0x%08" PRIx32 ", TODO: Not handled!",
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid),
+ deviceID);
+ goto fail;
+ }
+
+ rc = TRUE;
+
+fail:
+ Stream_SetPosition(s, pos);
+ return rc;
+}
+#endif
+
+BOOL pf_channel_send_client_queue(pClientContext* pc, pf_channel_client_context* rdpdr)
+{
+ UINT16 channelId = 0;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(rdpdr);
+
+ if (rdpdr->state != STATE_CLIENT_CHANNEL_RUNNING)
+ return FALSE;
+ channelId = freerdp_channels_get_id_by_name(pc->context.instance, RDPDR_SVC_CHANNEL_NAME);
+ if ((channelId == 0) || (channelId == UINT16_MAX))
+ return TRUE;
+
+ Queue_Lock(rdpdr->queue);
+ while (Queue_Count(rdpdr->queue) > 0)
+ {
+ wStream* s = Queue_Dequeue(rdpdr->queue);
+ if (!s)
+ continue;
+
+ size_t len = Stream_Length(s);
+ Stream_SetPosition(s, len);
+
+ rdpdr_dump_send_packet(rdpdr->log, WLOG_TRACE, s, proxy_server_tx " (queue) ");
+ WINPR_ASSERT(pc->context.instance->SendChannelData);
+ if (!pc->context.instance->SendChannelData(pc->context.instance, channelId,
+ Stream_Buffer(s), len))
+ {
+ CLIENT_TX_LOG(rdpdr->log, WLOG_ERROR, "xxxxxx TODO: Failed to send data!");
+ }
+ Stream_Free(s, TRUE);
+ }
+ Queue_Unlock(rdpdr->queue);
+ return TRUE;
+}
+
+static BOOL rdpdr_handle_server_announce_request(pClientContext* pc,
+ pf_channel_client_context* rdpdr, wStream* s)
+{
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+
+ if (rdpdr_process_server_announce_request(rdpdr, s) != CHANNEL_RC_OK)
+ return FALSE;
+ if (rdpdr_send_client_announce_reply(pc, rdpdr) != CHANNEL_RC_OK)
+ return FALSE;
+ if (rdpdr_send_client_name_request(pc, rdpdr) != CHANNEL_RC_OK)
+ return FALSE;
+ rdpdr->state = STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST;
+ return TRUE;
+}
+
+BOOL pf_channel_rdpdr_client_handle(pClientContext* pc, UINT16 channelId, const char* channel_name,
+ const BYTE* xdata, size_t xsize, UINT32 flags, size_t totalSize)
+{
+ pf_channel_client_context* rdpdr = NULL;
+ pServerContext* ps = NULL;
+ wStream* s = NULL;
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+ UINT16 packetid = 0;
+#endif
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ WINPR_ASSERT(pc->interceptContextMap);
+ WINPR_ASSERT(channel_name);
+ WINPR_ASSERT(xdata);
+
+ ps = pc->pdata->ps;
+
+ rdpdr = HashTable_GetItemValue(pc->interceptContextMap, channel_name);
+ if (!rdpdr)
+ {
+ CLIENT_RX_LOG(WLog_Get(RTAG), WLOG_ERROR,
+ "Channel %s [0x%04" PRIx16 "] missing context in interceptContextMap",
+ channel_name, channelId);
+ return FALSE;
+ }
+ s = rdpdr->common.buffer;
+ if (flags & CHANNEL_FLAG_FIRST)
+ Stream_SetPosition(s, 0);
+ if (!Stream_EnsureRemainingCapacity(s, xsize))
+ {
+ CLIENT_RX_LOG(rdpdr->log, WLOG_ERROR,
+ "Channel %s [0x%04" PRIx16 "] not enough memory [need %" PRIuz "]",
+ channel_name, channelId, xsize);
+ return FALSE;
+ }
+ Stream_Write(s, xdata, xsize);
+ if ((flags & CHANNEL_FLAG_LAST) == 0)
+ return TRUE;
+
+ Stream_SealLength(s);
+ Stream_SetPosition(s, 0);
+ if (Stream_Length(s) != totalSize)
+ {
+ CLIENT_RX_LOG(rdpdr->log, WLOG_WARN,
+ "Received invalid %s channel data (server -> proxy), expected %" PRIuz
+ "bytes, got %" PRIuz,
+ channel_name, totalSize, Stream_Length(s));
+ return FALSE;
+ }
+
+ rdpdr_dump_received_packet(rdpdr->log, WLOG_TRACE, s, proxy_server_rx);
+ switch (rdpdr->state)
+ {
+ case STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST:
+ if (!rdpdr_handle_server_announce_request(pc, rdpdr, s))
+ return FALSE;
+ break;
+ case STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST:
+ if (!rdpdr_process_server_capability_request_or_clientid_confirm(rdpdr, s))
+ return FALSE;
+ rdpdr->state = STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM;
+ break;
+ case STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM:
+ if (!rdpdr_process_server_capability_request_or_clientid_confirm(rdpdr, s))
+ return FALSE;
+ if (rdpdr_send_client_capability_response(pc, rdpdr) != CHANNEL_RC_OK)
+ return FALSE;
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+ if (pf_channel_smartcard_client_emulate(pc))
+ {
+ if (rdpdr_send_emulated_scard_device_list_announce_request(pc, rdpdr) !=
+ CHANNEL_RC_OK)
+ return FALSE;
+ rdpdr->state = STATE_CLIENT_CHANNEL_RUNNING;
+ }
+ else
+#endif
+ {
+ rdpdr->state = STATE_CLIENT_CHANNEL_RUNNING;
+ pf_channel_send_client_queue(pc, rdpdr);
+ }
+
+ break;
+ case STATE_CLIENT_CHANNEL_RUNNING:
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+ if (!pf_channel_smartcard_client_emulate(pc) ||
+ !filter_smartcard_io_requests(rdpdr, s, &packetid))
+ return pf_channel_rdpdr_client_send_to_server(rdpdr, ps, s);
+ else
+ {
+ switch (packetid)
+ {
+ case PAKID_CORE_USER_LOGGEDON:
+ return rdpdr_process_server_loggedon_request(ps, pc, rdpdr, s,
+ RDPDR_CTYP_CORE, packetid);
+ case PAKID_CORE_DEVICE_IOREQUEST:
+ {
+ wStream* out = rdpdr_client_get_send_buffer(
+ rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_DEVICE_IOCOMPLETION, 0);
+ WINPR_ASSERT(out);
+
+ if (!rdpdr_process_server_header(FALSE, rdpdr->log, s, RDPDR_CTYP_CORE,
+ PAKID_CORE_DEVICE_IOREQUEST, 20))
+ return FALSE;
+
+ if (!pf_channel_smartcard_client_handle(rdpdr->log, pc, s, out,
+ rdpdr_client_send))
+ return FALSE;
+ }
+ break;
+ case PAKID_CORE_SERVER_ANNOUNCE:
+ pf_channel_rdpdr_client_reset(pc);
+ if (!rdpdr_handle_server_announce_request(pc, rdpdr, s))
+ return FALSE;
+ break;
+ case PAKID_CORE_SERVER_CAPABILITY:
+ rdpdr->state = STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST;
+ rdpdr->flags = 0;
+ return pf_channel_rdpdr_client_handle(pc, channelId, channel_name, xdata,
+ xsize, flags, totalSize);
+ case PAKID_CORE_DEVICE_REPLY:
+ break;
+ default:
+ CLIENT_RX_LOG(
+ rdpdr->log, WLOG_ERROR,
+ "Channel %s [0x%04" PRIx16
+ "] we´ve reached an impossible state %s! [%s] aliens invaded!",
+ channel_name, channelId, rdpdr_client_state_to_string(rdpdr->state),
+ rdpdr_packetid_string(packetid));
+ return FALSE;
+ }
+ }
+ break;
+#else
+ return pf_channel_rdpdr_client_send_to_server(rdpdr, ps, s);
+#endif
+ default:
+ CLIENT_RX_LOG(rdpdr->log, WLOG_ERROR,
+ "Channel %s [0x%04" PRIx16
+ "] we´ve reached an impossible state %s! aliens invaded!",
+ channel_name, channelId, rdpdr_client_state_to_string(rdpdr->state));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void pf_channel_rdpdr_common_context_free(pf_channel_common_context* common)
+{
+ if (!common)
+ return;
+ free(common->computerName.v);
+ Stream_Free(common->s, TRUE);
+ Stream_Free(common->buffer, TRUE);
+}
+
+static void pf_channel_rdpdr_client_context_free(InterceptContextMapEntry* base)
+{
+ pf_channel_client_context* entry = (pf_channel_client_context*)base;
+ if (!entry)
+ return;
+
+ pf_channel_rdpdr_common_context_free(&entry->common);
+ Queue_Free(entry->queue);
+ free(entry);
+}
+
+static BOOL pf_channel_rdpdr_common_context_new(pf_channel_common_context* common,
+ void (*fkt)(InterceptContextMapEntry*))
+{
+ if (!common)
+ return FALSE;
+ common->base.free = fkt;
+ common->s = Stream_New(NULL, 1024);
+ if (!common->s)
+ return FALSE;
+ common->buffer = Stream_New(NULL, 1024);
+ if (!common->buffer)
+ return FALSE;
+ common->computerNameUnicode = 1;
+ common->computerName.v = NULL;
+ common->versionMajor = RDPDR_VERSION_MAJOR;
+ common->versionMinor = RDPDR_VERSION_MINOR_RDP10X;
+ common->clientID = SCARD_DEVICE_ID;
+
+ const UINT32 versions[] = { 0,
+ GENERAL_CAPABILITY_VERSION_02,
+ PRINT_CAPABILITY_VERSION_01,
+ PORT_CAPABILITY_VERSION_01,
+ DRIVE_CAPABILITY_VERSION_02,
+ SMARTCARD_CAPABILITY_VERSION_01 };
+
+ memcpy(common->capabilityVersions, versions, sizeof(common->capabilityVersions));
+ return TRUE;
+}
+
+static BOOL pf_channel_rdpdr_client_pass_message(pServerContext* ps, pClientContext* pc,
+ UINT16 channelId, const char* channel_name,
+ wStream* s)
+{
+ pf_channel_client_context* rdpdr = NULL;
+
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(pc);
+
+ rdpdr = HashTable_GetItemValue(pc->interceptContextMap, channel_name);
+ if (!rdpdr)
+ return TRUE; /* Ignore data for channels not available on proxy -> server connection */
+ WINPR_ASSERT(rdpdr->queue);
+
+ if (!pf_channel_rdpdr_rewrite_device_list(rdpdr, ps, s, FALSE))
+ return FALSE;
+ if (!Queue_Enqueue(rdpdr->queue, s))
+ return FALSE;
+ pf_channel_send_client_queue(pc, rdpdr);
+ return TRUE;
+}
+
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+static BOOL filter_smartcard_device_list_remove(pf_channel_server_context* rdpdr, wStream* s)
+{
+ size_t pos = 0;
+ UINT32 count = 0;
+
+ WINPR_ASSERT(rdpdr);
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, sizeof(UINT32)))
+ return TRUE;
+ pos = Stream_GetPosition(s);
+ Stream_Read_UINT32(s, count);
+
+ if (count == 0)
+ return TRUE;
+
+ if (!Stream_CheckAndLogRequiredLengthOfSizeWLog(rdpdr->log, s, count, sizeof(UINT32)))
+ return TRUE;
+
+ for (UINT32 x = 0; x < count; x++)
+ {
+ UINT32 deviceID = 0;
+ BYTE* dst = Stream_Pointer(s);
+ Stream_Read_UINT32(s, deviceID);
+ if (deviceID == SCARD_DEVICE_ID)
+ {
+ ArrayList_Remove(rdpdr->blockedDevices, (void*)(size_t)deviceID);
+
+ /* This is the only device, filter it! */
+ if (count == 1)
+ return TRUE;
+
+ /* Remove this device from the list */
+ memmove(dst, Stream_ConstPointer(s), (count - x - 1) * sizeof(UINT32));
+
+ count--;
+ Stream_SetPosition(s, pos);
+ Stream_Write_UINT32(s, count);
+ return FALSE;
+ }
+ }
+
+ return FALSE;
+}
+
+static BOOL filter_smartcard_device_io_request(pf_channel_server_context* rdpdr, wStream* s)
+{
+ UINT32 DeviceID = 0;
+ WINPR_ASSERT(rdpdr);
+ WINPR_ASSERT(s);
+ Stream_Read_UINT32(s, DeviceID);
+ return ArrayList_Contains(rdpdr->blockedDevices, (void*)(size_t)DeviceID);
+}
+
+static BOOL filter_smartcard_device_list_announce(pf_channel_server_context* rdpdr, wStream* s)
+{
+ UINT32 count = 0;
+
+ WINPR_ASSERT(rdpdr);
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, sizeof(UINT32)))
+ return TRUE;
+ const size_t pos = Stream_GetPosition(s);
+ Stream_Read_UINT32(s, count);
+
+ if (count == 0)
+ return TRUE;
+
+ for (UINT32 x = 0; x < count; x++)
+ {
+ UINT32 DeviceType = 0;
+ UINT32 DeviceId = 0;
+ char PreferredDosName[8];
+ UINT32 DeviceDataLength = 0;
+ BYTE* dst = Stream_Pointer(s);
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, 20))
+ return TRUE;
+ Stream_Read_UINT32(s, DeviceType);
+ Stream_Read_UINT32(s, DeviceId);
+ Stream_Read(s, PreferredDosName, ARRAYSIZE(PreferredDosName));
+ Stream_Read_UINT32(s, DeviceDataLength);
+ if (!Stream_SafeSeek(s, DeviceDataLength))
+ return TRUE;
+ if (DeviceType == RDPDR_DTYP_SMARTCARD)
+ {
+ ArrayList_Append(rdpdr->blockedDevices, (void*)(size_t)DeviceId);
+ if (count == 1)
+ return TRUE;
+
+ WLog_Print(rdpdr->log, WLOG_INFO, "Filtering smartcard device 0x%08" PRIx32 "",
+ DeviceId);
+
+ memmove(dst, Stream_ConstPointer(s), Stream_GetRemainingLength(s));
+ Stream_SetPosition(s, pos);
+ Stream_Write_UINT32(s, count - 1);
+ return FALSE;
+ }
+ }
+
+ return FALSE;
+}
+
+static BOOL filter_smartcard_device_list_announce_request(pf_channel_server_context* rdpdr,
+ wStream* s)
+{
+ BOOL rc = TRUE;
+ size_t pos = 0;
+ UINT16 component = 0;
+ UINT16 packetid = 0;
+
+ WINPR_ASSERT(rdpdr);
+ if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, 8))
+ return FALSE;
+
+ pos = Stream_GetPosition(s);
+
+ Stream_Read_UINT16(s, component);
+ Stream_Read_UINT16(s, packetid);
+
+ if (component != RDPDR_CTYP_CORE)
+ goto fail;
+
+ switch (packetid)
+ {
+ case PAKID_CORE_DEVICELIST_ANNOUNCE:
+ if (filter_smartcard_device_list_announce(rdpdr, s))
+ goto fail;
+ break;
+ case PAKID_CORE_DEVICELIST_REMOVE:
+ if (filter_smartcard_device_list_remove(rdpdr, s))
+ goto fail;
+ break;
+ case PAKID_CORE_DEVICE_IOREQUEST:
+ if (filter_smartcard_device_io_request(rdpdr, s))
+ goto fail;
+ break;
+
+ case PAKID_CORE_SERVER_ANNOUNCE:
+ case PAKID_CORE_CLIENTID_CONFIRM:
+ case PAKID_CORE_CLIENT_NAME:
+ case PAKID_CORE_DEVICE_REPLY:
+ case PAKID_CORE_SERVER_CAPABILITY:
+ case PAKID_CORE_CLIENT_CAPABILITY:
+ case PAKID_CORE_USER_LOGGEDON:
+ WLog_Print(rdpdr->log, WLOG_WARN, "Filtering client -> server message [%s | %s]",
+ rdpdr_component_string(component), rdpdr_packetid_string(packetid));
+ goto fail;
+ default:
+ break;
+ }
+
+ rc = FALSE;
+fail:
+ Stream_SetPosition(s, pos);
+ return rc;
+}
+#endif
+
+static void* stream_copy(const void* obj)
+{
+ const wStream* src = obj;
+ wStream* dst = Stream_New(NULL, Stream_Capacity(src));
+ if (!dst)
+ return NULL;
+ memcpy(Stream_Buffer(dst), Stream_ConstBuffer(src), Stream_Capacity(dst));
+ Stream_SetLength(dst, Stream_Length(src));
+ Stream_SetPosition(dst, Stream_GetPosition(src));
+ return dst;
+}
+
+static void stream_free(void* obj)
+{
+ wStream* s = obj;
+ Stream_Free(s, TRUE);
+}
+
+static const char* pf_channel_rdpdr_client_context(void* arg)
+{
+ pClientContext* pc = arg;
+ if (!pc)
+ return "pc=null";
+ if (!pc->pdata)
+ return "pc->pdata=null";
+ return pc->pdata->session_id;
+}
+
+BOOL pf_channel_rdpdr_client_new(pClientContext* pc)
+{
+ wObject* obj = NULL;
+ pf_channel_client_context* rdpdr = NULL;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->interceptContextMap);
+
+ rdpdr = calloc(1, sizeof(pf_channel_client_context));
+ if (!rdpdr)
+ return FALSE;
+ rdpdr->log = WLog_Get(RTAG);
+ WINPR_ASSERT(rdpdr->log);
+
+ WLog_SetContext(rdpdr->log, pf_channel_rdpdr_client_context, pc);
+ if (!pf_channel_rdpdr_common_context_new(&rdpdr->common, pf_channel_rdpdr_client_context_free))
+ goto fail;
+
+ rdpdr->maxMajorVersion = RDPDR_VERSION_MAJOR;
+ rdpdr->maxMinorVersion = RDPDR_VERSION_MINOR_RDP10X;
+ rdpdr->state = STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST;
+
+ rdpdr->queue = Queue_New(TRUE, 0, 0);
+ if (!rdpdr->queue)
+ goto fail;
+ obj = Queue_Object(rdpdr->queue);
+ WINPR_ASSERT(obj);
+ obj->fnObjectNew = stream_copy;
+ obj->fnObjectFree = stream_free;
+ if (!HashTable_Insert(pc->interceptContextMap, RDPDR_SVC_CHANNEL_NAME, rdpdr))
+ goto fail;
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert takes ownership of rdpdr
+ return TRUE;
+fail:
+ pf_channel_rdpdr_client_context_free(&rdpdr->common.base);
+ return FALSE;
+}
+
+void pf_channel_rdpdr_client_free(pClientContext* pc)
+{
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->interceptContextMap);
+ HashTable_Remove(pc->interceptContextMap, RDPDR_SVC_CHANNEL_NAME);
+}
+
+static void pf_channel_rdpdr_server_context_free(InterceptContextMapEntry* base)
+{
+ pf_channel_server_context* entry = (pf_channel_server_context*)base;
+ if (!entry)
+ return;
+
+ WTSVirtualChannelClose(entry->handle);
+ pf_channel_rdpdr_common_context_free(&entry->common);
+ ArrayList_Free(entry->blockedDevices);
+ free(entry);
+}
+
+static const char* pf_channel_rdpdr_server_context(void* arg)
+{
+ pServerContext* ps = arg;
+ if (!ps)
+ return "ps=null";
+ if (!ps->pdata)
+ return "ps->pdata=null";
+ return ps->pdata->session_id;
+}
+
+BOOL pf_channel_rdpdr_server_new(pServerContext* ps)
+{
+ pf_channel_server_context* rdpdr = NULL;
+ PULONG pSessionId = NULL;
+ DWORD BytesReturned = 0;
+
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->interceptContextMap);
+
+ rdpdr = calloc(1, sizeof(pf_channel_server_context));
+ if (!rdpdr)
+ return FALSE;
+ rdpdr->log = WLog_Get(RTAG);
+ WINPR_ASSERT(rdpdr->log);
+ WLog_SetContext(rdpdr->log, pf_channel_rdpdr_server_context, ps);
+
+ if (!pf_channel_rdpdr_common_context_new(&rdpdr->common, pf_channel_rdpdr_server_context_free))
+ goto fail;
+ rdpdr->state = STATE_SERVER_INITIAL;
+
+ rdpdr->blockedDevices = ArrayList_New(FALSE);
+ if (!rdpdr->blockedDevices)
+ goto fail;
+
+ rdpdr->SessionId = WTS_CURRENT_SESSION;
+ if (WTSQuerySessionInformationA(ps->vcm, WTS_CURRENT_SESSION, WTSSessionId, (LPSTR*)&pSessionId,
+ &BytesReturned))
+ {
+ rdpdr->SessionId = (DWORD)*pSessionId;
+ WTSFreeMemory(pSessionId);
+ }
+
+ rdpdr->handle = WTSVirtualChannelOpenEx(rdpdr->SessionId, RDPDR_SVC_CHANNEL_NAME, 0);
+ if (rdpdr->handle == 0)
+ goto fail;
+ if (!HashTable_Insert(ps->interceptContextMap, RDPDR_SVC_CHANNEL_NAME, rdpdr))
+ goto fail;
+
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert takes ownership of rdpdr
+ return TRUE;
+fail:
+ pf_channel_rdpdr_server_context_free(&rdpdr->common.base);
+ return FALSE;
+}
+
+void pf_channel_rdpdr_server_free(pServerContext* ps)
+{
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->interceptContextMap);
+ HashTable_Remove(ps->interceptContextMap, RDPDR_SVC_CHANNEL_NAME);
+}
+
+static pf_channel_server_context* get_channel(pServerContext* ps, BOOL send)
+{
+ pf_channel_server_context* rdpdr = NULL;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->interceptContextMap);
+
+ rdpdr = HashTable_GetItemValue(ps->interceptContextMap, RDPDR_SVC_CHANNEL_NAME);
+ if (!rdpdr)
+ {
+ SERVER_RXTX_LOG(send, WLog_Get(RTAG), WLOG_ERROR,
+ "Channel %s missing context in interceptContextMap",
+ RDPDR_SVC_CHANNEL_NAME);
+ return NULL;
+ }
+
+ return rdpdr;
+}
+
+BOOL pf_channel_rdpdr_server_handle(pServerContext* ps, UINT16 channelId, const char* channel_name,
+ const BYTE* xdata, size_t xsize, UINT32 flags, size_t totalSize)
+{
+ wStream* s = NULL;
+ pClientContext* pc = NULL;
+ pf_channel_server_context* rdpdr = get_channel(ps, FALSE);
+ if (!rdpdr)
+ return FALSE;
+
+ WINPR_ASSERT(ps->pdata);
+ pc = ps->pdata->pc;
+
+ s = rdpdr->common.buffer;
+
+ if (flags & CHANNEL_FLAG_FIRST)
+ Stream_SetPosition(s, 0);
+
+ if (!Stream_EnsureRemainingCapacity(s, xsize))
+ return FALSE;
+ Stream_Write(s, xdata, xsize);
+
+ if ((flags & CHANNEL_FLAG_LAST) == 0)
+ return TRUE;
+
+ Stream_SealLength(s);
+ Stream_SetPosition(s, 0);
+
+ if (Stream_Length(s) != totalSize)
+ {
+ SERVER_RX_LOG(rdpdr->log, WLOG_WARN,
+ "Received invalid %s channel data (client -> proxy), expected %" PRIuz
+ "bytes, got %" PRIuz,
+ channel_name, totalSize, Stream_Length(s));
+ return FALSE;
+ }
+
+ rdpdr_dump_received_packet(rdpdr->log, WLOG_TRACE, s, proxy_client_rx);
+ switch (rdpdr->state)
+ {
+ case STATE_SERVER_EXPECT_CLIENT_ANNOUNCE_REPLY:
+ if (rdpdr_process_client_announce_reply(rdpdr, s) != CHANNEL_RC_OK)
+ return FALSE;
+ rdpdr->state = STATE_SERVER_EXPECT_CLIENT_NAME_REQUEST;
+ break;
+ case STATE_SERVER_EXPECT_CLIENT_NAME_REQUEST:
+ if (rdpdr_process_client_name_request(rdpdr, s, pc) != CHANNEL_RC_OK)
+ return FALSE;
+ if (rdpdr_send_server_capability_request(rdpdr) != CHANNEL_RC_OK)
+ return FALSE;
+ if (rdpdr_send_server_clientid_confirm(rdpdr) != CHANNEL_RC_OK)
+ return FALSE;
+ rdpdr->state = STATE_SERVER_EXPECT_EXPECT_CLIENT_CAPABILITY_RESPONE;
+ break;
+ case STATE_SERVER_EXPECT_EXPECT_CLIENT_CAPABILITY_RESPONE:
+ if (rdpdr_process_client_capability_response(rdpdr, s) != CHANNEL_RC_OK)
+ return FALSE;
+ rdpdr->state = STATE_SERVER_CHANNEL_RUNNING;
+ break;
+ case STATE_SERVER_CHANNEL_RUNNING:
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+ if (!pf_channel_smartcard_client_emulate(pc) ||
+ !filter_smartcard_device_list_announce_request(rdpdr, s))
+ {
+ if (!pf_channel_rdpdr_client_pass_message(ps, pc, channelId, channel_name, s))
+ return FALSE;
+ }
+ else
+ return pf_channel_smartcard_server_handle(ps, s);
+#else
+ if (!pf_channel_rdpdr_client_pass_message(ps, pc, channelId, channel_name, s))
+ return FALSE;
+#endif
+ break;
+ default:
+ case STATE_SERVER_INITIAL:
+ SERVER_RX_LOG(rdpdr->log, WLOG_WARN, "Invalid state %s",
+ rdpdr_server_state_to_string(rdpdr->state));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL pf_channel_rdpdr_server_announce(pServerContext* ps)
+{
+ pf_channel_server_context* rdpdr = get_channel(ps, TRUE);
+ if (!rdpdr)
+ return FALSE;
+
+ WINPR_ASSERT(rdpdr->state == STATE_SERVER_INITIAL);
+ if (rdpdr_server_send_announce_request(rdpdr) != CHANNEL_RC_OK)
+ return FALSE;
+ rdpdr->state = STATE_SERVER_EXPECT_CLIENT_ANNOUNCE_REPLY;
+ return TRUE;
+}
+
+BOOL pf_channel_rdpdr_client_reset(pClientContext* pc)
+{
+ pf_channel_client_context* rdpdr = NULL;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ WINPR_ASSERT(pc->interceptContextMap);
+
+ rdpdr = HashTable_GetItemValue(pc->interceptContextMap, RDPDR_SVC_CHANNEL_NAME);
+ if (!rdpdr)
+ return TRUE;
+
+ Queue_Clear(rdpdr->queue);
+ rdpdr->flags = 0;
+ rdpdr->state = STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST;
+
+ return TRUE;
+}
+
+static PfChannelResult pf_rdpdr_back_data(proxyData* pdata,
+ const pServerStaticChannelContext* channel,
+ const BYTE* xdata, size_t xsize, UINT32 flags,
+ size_t totalSize)
+{
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ if (!pf_channel_rdpdr_client_handle(pdata->pc, channel->back_channel_id, channel->channel_name,
+ xdata, xsize, flags, totalSize))
+ return PF_CHANNEL_RESULT_ERROR;
+
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+ if (pf_channel_smartcard_client_emulate(pdata->pc))
+ return PF_CHANNEL_RESULT_DROP;
+#endif
+ return PF_CHANNEL_RESULT_DROP;
+}
+
+static PfChannelResult pf_rdpdr_front_data(proxyData* pdata,
+ const pServerStaticChannelContext* channel,
+ const BYTE* xdata, size_t xsize, UINT32 flags,
+ size_t totalSize)
+{
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ if (!pf_channel_rdpdr_server_handle(pdata->ps, channel->front_channel_id, channel->channel_name,
+ xdata, xsize, flags, totalSize))
+ return PF_CHANNEL_RESULT_ERROR;
+
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+ if (pf_channel_smartcard_client_emulate(pdata->pc))
+ return PF_CHANNEL_RESULT_DROP;
+#endif
+ return PF_CHANNEL_RESULT_DROP;
+}
+
+BOOL pf_channel_setup_rdpdr(pServerContext* ps, pServerStaticChannelContext* channel)
+{
+ channel->onBackData = pf_rdpdr_back_data;
+ channel->onFrontData = pf_rdpdr_front_data;
+
+ if (!pf_channel_rdpdr_server_new(ps))
+ return FALSE;
+ if (!pf_channel_rdpdr_server_announce(ps))
+ return FALSE;
+
+ return TRUE;
+}
diff --git a/server/proxy/channels/pf_channel_rdpdr.h b/server/proxy/channels/pf_channel_rdpdr.h
new file mode 100644
index 0000000..dbc2e75
--- /dev/null
+++ b/server/proxy/channels/pf_channel_rdpdr.h
@@ -0,0 +1,47 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2021 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2021 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 FREERDP_SERVER_PROXY_RDPDR_H
+#define FREERDP_SERVER_PROXY_RDPDR_H
+
+#include <freerdp/server/proxy/proxy_context.h>
+
+BOOL pf_channel_setup_rdpdr(pServerContext* ps, pServerStaticChannelContext* channel);
+
+void pf_channel_rdpdr_client_free(pClientContext* pc);
+
+BOOL pf_channel_rdpdr_client_new(pClientContext* pc);
+
+BOOL pf_channel_rdpdr_client_reset(pClientContext* pc);
+
+BOOL pf_channel_rdpdr_client_handle(pClientContext* pc, UINT16 channelId, const char* channel_name,
+ const BYTE* xdata, size_t xsize, UINT32 flags,
+ size_t totalSize);
+
+void pf_channel_rdpdr_server_free(pServerContext* ps);
+
+BOOL pf_channel_rdpdr_server_new(pServerContext* ps);
+
+BOOL pf_channel_rdpdr_server_announce(pServerContext* ps);
+BOOL pf_channel_rdpdr_server_handle(pServerContext* ps, UINT16 channelId, const char* channel_name,
+ const BYTE* xdata, size_t xsize, UINT32 flags,
+ size_t totalSize);
+
+#endif /* FREERDP_SERVER_PROXY_RDPDR_H */
diff --git a/server/proxy/channels/pf_channel_smartcard.c b/server/proxy/channels/pf_channel_smartcard.c
new file mode 100644
index 0000000..1d2bdfe
--- /dev/null
+++ b/server/proxy/channels/pf_channel_smartcard.c
@@ -0,0 +1,397 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2021 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2021 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.
+ */
+
+#include <winpr/assert.h>
+#include <winpr/string.h>
+
+#include <winpr/smartcard.h>
+#include <winpr/pool.h>
+
+#include <freerdp/server/proxy/proxy_log.h>
+#include <freerdp/emulate/scard/smartcard_emulate.h>
+#include <freerdp/channels/scard.h>
+#include <freerdp/channels/rdpdr.h>
+#include <freerdp/utils/rdpdr_utils.h>
+
+#include <freerdp/utils/smartcard_operations.h>
+#include <freerdp/utils/smartcard_call.h>
+
+#include "pf_channel_smartcard.h"
+#include "pf_channel_rdpdr.h"
+
+#define TAG PROXY_TAG("channel.scard")
+
+#define SCARD_SVC_CHANNEL_NAME "SCARD"
+
+typedef struct
+{
+ InterceptContextMapEntry base;
+ scard_call_context* callctx;
+ PTP_POOL ThreadPool;
+ TP_CALLBACK_ENVIRON ThreadPoolEnv;
+ wArrayList* workObjects;
+} pf_channel_client_context;
+
+typedef struct
+{
+ SMARTCARD_OPERATION op;
+ wStream* out;
+ pClientContext* pc;
+ wLog* log;
+ pf_scard_send_fkt_t send_fkt;
+} pf_channel_client_queue_element;
+
+static pf_channel_client_context* scard_get_client_context(pClientContext* pc)
+{
+ pf_channel_client_context* scard = NULL;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->interceptContextMap);
+
+ scard = HashTable_GetItemValue(pc->interceptContextMap, SCARD_SVC_CHANNEL_NAME);
+ if (!scard)
+ WLog_WARN(TAG, "[%s] missing in pc->interceptContextMap", SCARD_SVC_CHANNEL_NAME);
+ return scard;
+}
+
+static BOOL pf_channel_client_write_iostatus(wStream* out, const SMARTCARD_OPERATION* op,
+ UINT32 ioStatus)
+{
+ UINT16 component = 0;
+ UINT16 packetid = 0;
+ UINT32 dID = 0;
+ UINT32 cID = 0;
+ size_t pos = 0;
+
+ WINPR_ASSERT(op);
+ WINPR_ASSERT(out);
+
+ pos = Stream_GetPosition(out);
+ Stream_SetPosition(out, 0);
+ if (!Stream_CheckAndLogRequiredLength(TAG, out, 16))
+ return FALSE;
+
+ Stream_Read_UINT16(out, component);
+ Stream_Read_UINT16(out, packetid);
+
+ Stream_Read_UINT32(out, dID);
+ Stream_Read_UINT32(out, cID);
+
+ WINPR_ASSERT(component == RDPDR_CTYP_CORE);
+ WINPR_ASSERT(packetid == PAKID_CORE_DEVICE_IOCOMPLETION);
+ WINPR_ASSERT(dID == op->deviceID);
+ WINPR_ASSERT(cID == op->completionID);
+
+ Stream_Write_UINT32(out, ioStatus);
+ Stream_SetPosition(out, pos);
+ return TRUE;
+}
+
+struct thread_arg
+{
+ pf_channel_client_context* scard;
+ pf_channel_client_queue_element* e;
+};
+
+static void queue_free(void* obj);
+static void* queue_copy(const void* obj);
+
+static VOID irp_thread(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WORK Work)
+{
+ struct thread_arg* arg = Context;
+ pf_channel_client_context* scard = arg->scard;
+ {
+ UINT32 ioStatus = 0;
+ LONG rc = smartcard_irp_device_control_call(arg->scard->callctx, arg->e->out, &ioStatus,
+ &arg->e->op);
+ if (rc == CHANNEL_RC_OK)
+ {
+ if (pf_channel_client_write_iostatus(arg->e->out, &arg->e->op, ioStatus))
+ arg->e->send_fkt(arg->e->log, arg->e->pc, arg->e->out);
+ }
+ }
+ queue_free(arg->e);
+ free(arg);
+ ArrayList_Remove(scard->workObjects, Work);
+}
+
+static BOOL start_irp_thread(pf_channel_client_context* scard,
+ const pf_channel_client_queue_element* e)
+{
+ PTP_WORK work = NULL;
+ struct thread_arg* arg = calloc(1, sizeof(struct thread_arg));
+ if (!arg)
+ return FALSE;
+ arg->scard = scard;
+ arg->e = queue_copy(e);
+ if (!arg->e)
+ goto fail;
+
+ work = CreateThreadpoolWork(irp_thread, arg, &scard->ThreadPoolEnv);
+ if (!work)
+ goto fail;
+ ArrayList_Append(scard->workObjects, work);
+ SubmitThreadpoolWork(work);
+
+ return TRUE;
+
+fail:
+ if (arg)
+ queue_free(arg->e);
+ free(arg);
+ return FALSE;
+}
+
+BOOL pf_channel_smartcard_client_handle(wLog* log, pClientContext* pc, wStream* s, wStream* out,
+ pf_scard_send_fkt_t send_fkt)
+{
+ BOOL rc = FALSE;
+ LONG status = 0;
+ UINT32 FileId = 0;
+ UINT32 CompletionId = 0;
+ UINT32 ioStatus = 0;
+ pf_channel_client_queue_element e = { 0 };
+ pf_channel_client_context* scard = scard_get_client_context(pc);
+
+ WINPR_ASSERT(log);
+ WINPR_ASSERT(send_fkt);
+ WINPR_ASSERT(s);
+
+ if (!scard)
+ return FALSE;
+
+ e.log = log;
+ e.pc = pc;
+ e.out = out;
+ e.send_fkt = send_fkt;
+
+ /* Skip IRP header */
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 20))
+ return FALSE;
+ else
+ {
+ UINT32 DeviceId = 0;
+ UINT32 MajorFunction = 0;
+ UINT32 MinorFunction = 0;
+
+ Stream_Read_UINT32(s, DeviceId); /* DeviceId (4 bytes) */
+ Stream_Read_UINT32(s, FileId); /* FileId (4 bytes) */
+ Stream_Read_UINT32(s, CompletionId); /* CompletionId (4 bytes) */
+ Stream_Read_UINT32(s, MajorFunction); /* MajorFunction (4 bytes) */
+ Stream_Read_UINT32(s, MinorFunction); /* MinorFunction (4 bytes) */
+
+ if (MajorFunction != IRP_MJ_DEVICE_CONTROL)
+ {
+ WLog_WARN(TAG, "[%s] Invalid IRP received, expected %s, got %2", SCARD_SVC_CHANNEL_NAME,
+ rdpdr_irp_string(IRP_MJ_DEVICE_CONTROL), rdpdr_irp_string(MajorFunction));
+ return FALSE;
+ }
+ e.op.completionID = CompletionId;
+ e.op.deviceID = DeviceId;
+
+ if (!rdpdr_write_iocompletion_header(out, DeviceId, CompletionId, 0))
+ return FALSE;
+ }
+
+ status = smartcard_irp_device_control_decode(s, CompletionId, FileId, &e.op);
+ if (status != 0)
+ goto fail;
+
+ switch (e.op.ioControlCode)
+ {
+ case SCARD_IOCTL_LISTREADERGROUPSA:
+ case SCARD_IOCTL_LISTREADERGROUPSW:
+ case SCARD_IOCTL_LISTREADERSA:
+ case SCARD_IOCTL_LISTREADERSW:
+ case SCARD_IOCTL_LOCATECARDSA:
+ case SCARD_IOCTL_LOCATECARDSW:
+ case SCARD_IOCTL_LOCATECARDSBYATRA:
+ case SCARD_IOCTL_LOCATECARDSBYATRW:
+ case SCARD_IOCTL_GETSTATUSCHANGEA:
+ case SCARD_IOCTL_GETSTATUSCHANGEW:
+ case SCARD_IOCTL_CONNECTA:
+ case SCARD_IOCTL_CONNECTW:
+ case SCARD_IOCTL_RECONNECT:
+ case SCARD_IOCTL_DISCONNECT:
+ case SCARD_IOCTL_BEGINTRANSACTION:
+ case SCARD_IOCTL_ENDTRANSACTION:
+ case SCARD_IOCTL_STATE:
+ case SCARD_IOCTL_STATUSA:
+ case SCARD_IOCTL_STATUSW:
+ case SCARD_IOCTL_TRANSMIT:
+ case SCARD_IOCTL_CONTROL:
+ case SCARD_IOCTL_GETATTRIB:
+ case SCARD_IOCTL_SETATTRIB:
+ if (!start_irp_thread(scard, &e))
+ goto fail;
+ return TRUE;
+
+ default:
+ status = smartcard_irp_device_control_call(scard->callctx, out, &ioStatus, &e.op);
+ if (status != 0)
+ goto fail;
+ if (!pf_channel_client_write_iostatus(out, &e.op, ioStatus))
+ goto fail;
+ break;
+ }
+
+ rc = send_fkt(log, pc, out) == CHANNEL_RC_OK;
+
+fail:
+ smartcard_operation_free(&e.op, FALSE);
+ return rc;
+}
+
+BOOL pf_channel_smartcard_server_handle(pServerContext* ps, wStream* s)
+{
+ WLog_ERR(TAG, "TODO: unimplemented");
+ return TRUE;
+}
+
+static void channel_stop_and_wait(pf_channel_client_context* scard, BOOL reset)
+{
+ WINPR_ASSERT(scard);
+ smartcard_call_context_signal_stop(scard->callctx, FALSE);
+
+ while (ArrayList_Count(scard->workObjects) > 0)
+ {
+ PTP_WORK work = ArrayList_GetItem(scard->workObjects, 0);
+ if (!work)
+ continue;
+ WaitForThreadpoolWorkCallbacks(work, TRUE);
+ }
+
+ smartcard_call_context_signal_stop(scard->callctx, reset);
+}
+
+static void pf_channel_scard_client_context_free(InterceptContextMapEntry* base)
+{
+ pf_channel_client_context* entry = (pf_channel_client_context*)base;
+ if (!entry)
+ return;
+
+ /* Set the stop event.
+ * All threads waiting in blocking operations will abort at the next
+ * available polling slot */
+ channel_stop_and_wait(entry, FALSE);
+ ArrayList_Free(entry->workObjects);
+ CloseThreadpool(entry->ThreadPool);
+ DestroyThreadpoolEnvironment(&entry->ThreadPoolEnv);
+
+ smartcard_call_context_free(entry->callctx);
+ free(entry);
+}
+
+static void queue_free(void* obj)
+{
+ pf_channel_client_queue_element* element = obj;
+ if (!element)
+ return;
+ smartcard_operation_free(&element->op, FALSE);
+ Stream_Free(element->out, TRUE);
+ free(element);
+}
+
+static void* queue_copy(const void* obj)
+{
+ const pf_channel_client_queue_element* other = obj;
+ pf_channel_client_queue_element* copy = NULL;
+ if (!other)
+ return NULL;
+ copy = calloc(1, sizeof(pf_channel_client_queue_element));
+ if (!copy)
+ return NULL;
+
+ *copy = *other;
+ copy->out = Stream_New(NULL, Stream_Capacity(other->out));
+ if (!copy->out)
+ goto fail;
+ Stream_Write(copy->out, Stream_Buffer(other->out), Stream_GetPosition(other->out));
+ return copy;
+fail:
+ queue_free(copy);
+ return NULL;
+}
+
+static void work_object_free(void* arg)
+{
+ PTP_WORK work = arg;
+ CloseThreadpoolWork(work);
+}
+
+BOOL pf_channel_smartcard_client_new(pClientContext* pc)
+{
+ pf_channel_client_context* scard = NULL;
+ wObject* obj = NULL;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->interceptContextMap);
+
+ scard = calloc(1, sizeof(pf_channel_client_context));
+ if (!scard)
+ return FALSE;
+ scard->base.free = pf_channel_scard_client_context_free;
+ scard->callctx = smartcard_call_context_new(pc->context.settings);
+ if (!scard->callctx)
+ goto fail;
+
+ scard->workObjects = ArrayList_New(TRUE);
+ if (!scard->workObjects)
+ goto fail;
+ obj = ArrayList_Object(scard->workObjects);
+ WINPR_ASSERT(obj);
+ obj->fnObjectFree = work_object_free;
+
+ scard->ThreadPool = CreateThreadpool(NULL);
+ if (!scard->ThreadPool)
+ goto fail;
+ InitializeThreadpoolEnvironment(&scard->ThreadPoolEnv);
+ SetThreadpoolCallbackPool(&scard->ThreadPoolEnv, scard->ThreadPool);
+
+ return HashTable_Insert(pc->interceptContextMap, SCARD_SVC_CHANNEL_NAME, scard);
+fail:
+ pf_channel_scard_client_context_free(&scard->base);
+ return FALSE;
+}
+
+void pf_channel_smartcard_client_free(pClientContext* pc)
+{
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->interceptContextMap);
+ HashTable_Remove(pc->interceptContextMap, SCARD_SVC_CHANNEL_NAME);
+}
+
+BOOL pf_channel_smartcard_client_emulate(pClientContext* pc)
+{
+ pf_channel_client_context* scard = scard_get_client_context(pc);
+ if (!scard)
+ return FALSE;
+ return smartcard_call_is_configured(scard->callctx);
+}
+
+BOOL pf_channel_smartcard_client_reset(pClientContext* pc)
+{
+ pf_channel_client_context* scard = scard_get_client_context(pc);
+ if (!scard)
+ return TRUE;
+
+ channel_stop_and_wait(scard, TRUE);
+ return TRUE;
+}
diff --git a/server/proxy/channels/pf_channel_smartcard.h b/server/proxy/channels/pf_channel_smartcard.h
new file mode 100644
index 0000000..975636d
--- /dev/null
+++ b/server/proxy/channels/pf_channel_smartcard.h
@@ -0,0 +1,39 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2021 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2021 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 FREERDP_SERVER_PROXY_SCARD_H
+#define FREERDP_SERVER_PROXY_SCARD_H
+
+#include <winpr/wlog.h>
+#include <freerdp/server/proxy/proxy_context.h>
+
+typedef UINT (*pf_scard_send_fkt_t)(wLog* log, pClientContext*, wStream*);
+
+BOOL pf_channel_smartcard_client_new(pClientContext* pc);
+void pf_channel_smartcard_client_free(pClientContext* pc);
+
+BOOL pf_channel_smartcard_client_reset(pClientContext* pc);
+BOOL pf_channel_smartcard_client_emulate(pClientContext* pc);
+
+BOOL pf_channel_smartcard_client_handle(wLog* log, pClientContext* pc, wStream* s, wStream* out,
+ pf_scard_send_fkt_t fkt);
+BOOL pf_channel_smartcard_server_handle(pServerContext* ps, wStream* s);
+
+#endif /* FREERDP_SERVER_PROXY_SCARD_H */
diff --git a/server/proxy/cli/CMakeLists.txt b/server/proxy/cli/CMakeLists.txt
new file mode 100644
index 0000000..1416b4a
--- /dev/null
+++ b/server/proxy/cli/CMakeLists.txt
@@ -0,0 +1,60 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Proxy Server
+#
+# Copyright 2021 Armin Novak <armin.novak@thincast.com>
+# Copyright 2021 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.
+
+set(PROXY_APP_SRCS freerdp_proxy.c)
+
+# On windows create dll version information.
+# Vendor, product and year are already set in top level CMakeLists.txt
+if (WIN32)
+ 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 "${MODULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}" )
+
+ configure_file(
+ ${PROJECT_SOURCE_DIR}/cmake/WindowsDLLVersion.rc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/version.rc
+ @ONLY)
+
+ list(APPEND PROXY_APP_SRCS ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
+endif()
+
+set(APP_NAME "freerdp-proxy")
+add_executable(${APP_NAME}
+ ${PROXY_APP_SRCS}
+)
+
+set(MANPAGE_NAME ${APP_NAME}.1)
+if (WITH_BINARY_VERSIONING)
+ set_target_properties(${APP_NAME}
+ PROPERTIES
+ OUTPUT_NAME "${APP_NAME}${FREERDP_API_VERSION}"
+ )
+ set(MANPAGE_NAME ${APP_NAME}${FREERDP_API_VERSION}.1)
+endif()
+
+target_link_libraries(${APP_NAME} ${MODULE_NAME})
+install(TARGETS ${APP_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT server)
+if (WITH_DEBUG_SYMBOLS AND MSVC)
+ install(FILES ${CMAKE_PDB_BINARY_DIR}/${APP_NAME}.pdb DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT symbols)
+endif()
+
+set_property(TARGET ${APP_NAME} PROPERTY FOLDER "Server/proxy")
+
+configure_file(${APP_NAME}.1.in ${CMAKE_CURRENT_BINARY_DIR}/${MANPAGE_NAME})
+install_freerdp_man(${CMAKE_CURRENT_BINARY_DIR}/${MANPAGE_NAME} 1)
diff --git a/server/proxy/cli/freerdp-proxy.1.in b/server/proxy/cli/freerdp-proxy.1.in
new file mode 100644
index 0000000..c41f97c
--- /dev/null
+++ b/server/proxy/cli/freerdp-proxy.1.in
@@ -0,0 +1,85 @@
+.de URL
+\\$2 \(laURL: \\$1 \(ra\\$3
+..
+.if \n[.g] .mso www.tmac
+.TH @MANPAGE_NAME@ 1 2023-12-14 "@FREERDP_VERSION_FULL@" "FreeRDP"
+.SH NAME
+@MANPAGE_NAME@ \- A server binary allowing MITM proxying of RDP connections
+.SH SYNOPSIS
+.B @MANPAGE_NAME@
+[\fB-h\fP]
+[\fB--help\fP]
+[\fB--buildconfig\fP]
+[\fB--dump-config\fP \fB<config file>\fP]
+[\fB-v\fP]
+[\fB--version\fP]
+[\fB<config file>\fP]
+.SH DESCRIPTION
+.B @MANPAGE_NAME@
+can be used to proxy a RDP connection between a target server and connecting clients.
+Possible usage scenarios are:
+.IP Proxying
+Connect outdated/insecure RDP servers from behind a (more secure) proxy
+.IP Analysis
+Allow detailed protocol analysis of (many) unknown protocol features (channels)
+.IP Inspection
+MITM proxy for session inspection and recording
+
+.SH OPTIONS
+.IP -h,--help
+Display a help text explaining usage.
+.IP --buildconfig
+Print the build configuration of the proxy and exit.
+.IP -v,--version
+Print the version of the proxy and exit.
+.IP --dump-config \fB<config-ini-file>\fP
+Dump a template configuration to \fB<config-ini-file>\fP
+.IP \fB<config-ini-file>\fP
+Start the proxy with settings read from \fB<config-ini-file>\fP
+
+.SH WARNING
+The proxy does not support authentication out of the box but acts simply as intermediary.
+Only \fBRDP\fP and \fBTLS\fP security modes are supported, \fBNLA\fP will fail for connections to the proxy.
+To implement authentication a \fBproxy-module\fP can be implemented that can authenticate against some backend
+and map connecting users and credentials to target server users and credentials.
+
+.SH EXAMPLES
+@MANPAGE_NAME@ /some/config/file
+
+@MANPAGE_NAME@ --dump-config /some/config/file
+
+.SH PREPARATIONS
+
+1. generate certificates for proxy
+
+\fBwinpr-makecert -rdp -path . proxy\fP
+
+2. generate proxy configuration
+
+\fB@MANPAGE_NAME@ --dump-config proxy.ini\fP
+
+3. edit configurartion and:
+
+ * provide (preferrably absolute) paths for \fBCertificateFile\fP and \fBPrivateKeyFile\fP generated previously
+ * remove the \fBCertificateContents\fP and \fBPrivateKeyContents\fP
+ * Adjust the \fB[Server]\fP settings \fBHost\fP and \fBPort\fP to bind a specific port on a network interface
+ * Adjust the \fB[Target]\fP \fBHost\fP and \fBPort\fP settings to the \fBRDP\fP target server
+ * Adjust (or remove if unuse) the \fBPlugins\fP settings
+
+3. start proxy server
+
+ \fB@MANPAGE_NAME@ proxy.ini\fP
+
+.SH EXIT STATUS
+.TP
+.B 0
+Successful program execution.
+.TP
+.B 1
+Otherwise.
+
+.SH SEE ALSO
+wlog(7)
+
+.SH AUTHOR
+FreeRDP <team@freerdp.com>
diff --git a/server/proxy/cli/freerdp_proxy.c b/server/proxy/cli/freerdp_proxy.c
new file mode 100644
index 0000000..bc53ae2
--- /dev/null
+++ b/server/proxy/cli/freerdp_proxy.c
@@ -0,0 +1,161 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@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 <winpr/collections.h>
+
+#include <freerdp/version.h>
+#include <freerdp/freerdp.h>
+
+#include <freerdp/server/proxy/proxy_server.h>
+#include <freerdp/server/proxy/proxy_log.h>
+
+#include <stdlib.h>
+#include <signal.h>
+
+#define TAG PROXY_TAG("server")
+
+static proxyServer* server = NULL;
+
+#if defined(_WIN32)
+static const char* strsignal(int signum)
+{
+ switch (signum)
+ {
+ case SIGINT:
+ return "SIGINT";
+ case SIGTERM:
+ return "SIGTERM";
+ default:
+ return "UNKNOWN";
+ }
+}
+#endif
+
+static void cleanup_handler(int signum)
+{
+ printf("\n");
+ WLog_INFO(TAG, "caught signal %s [%d], starting cleanup...", strsignal(signum), signum);
+
+ WLog_INFO(TAG, "stopping all connections.");
+ pf_server_stop(server);
+}
+
+static void pf_server_register_signal_handlers(void)
+{
+ signal(SIGINT, cleanup_handler);
+ signal(SIGTERM, cleanup_handler);
+#ifndef _WIN32
+ signal(SIGQUIT, cleanup_handler);
+ signal(SIGKILL, cleanup_handler);
+#endif
+}
+
+static WINPR_NORETURN(void usage(const char* app))
+{
+ printf("Usage:\n");
+ printf("%s -h Display this help text.\n", app);
+ printf("%s --help Display this help text.\n", app);
+ printf("%s --buildconfig Print the build configuration.\n", app);
+ printf("%s <config ini file> Start the proxy with <config.ini>\n", app);
+ printf("%s --dump-config <config ini file> Create a template <config.ini>\n", app);
+ printf("%s -v Print out binary version.\n", app);
+ printf("%s --version Print out binary version.\n", app);
+ exit(0);
+}
+
+static void version(const char* app)
+{
+ printf("%s version %s", app, freerdp_get_version_string());
+ exit(0);
+}
+
+static WINPR_NORETURN(void buildconfig(const char* app))
+{
+ printf("This is FreeRDP version %s (%s)\n", FREERDP_VERSION_FULL, FREERDP_GIT_REVISION);
+ printf("%s", freerdp_get_build_config());
+ exit(0);
+}
+
+int main(int argc, char* argv[])
+{
+ proxyConfig* config = NULL;
+ char* config_path = "config.ini";
+ int status = -1;
+
+ pf_server_register_signal_handlers();
+
+ WLog_INFO(TAG, "freerdp-proxy version info:");
+ WLog_INFO(TAG, "\tFreeRDP version: %s", FREERDP_VERSION_FULL);
+ WLog_INFO(TAG, "\tGit commit: %s", FREERDP_GIT_REVISION);
+ WLog_DBG(TAG, "\tBuild config: %s", freerdp_get_build_config());
+
+ if (argc < 2)
+ usage(argv[0]);
+
+ {
+ const char* arg = argv[1];
+
+ if (_stricmp(arg, "-h") == 0)
+ usage(argv[0]);
+ else if (_stricmp(arg, "--help") == 0)
+ usage(argv[0]);
+ else if (_stricmp(arg, "--buildconfig") == 0)
+ buildconfig(argv[0]);
+ else if (_stricmp(arg, "--dump-config") == 0)
+ {
+ if (argc <= 2)
+ usage(argv[0]);
+ pf_server_config_dump(argv[2]);
+ status = 0;
+ goto fail;
+ }
+ else if (_stricmp(arg, "-v") == 0)
+ version(argv[0]);
+ else if (_stricmp(arg, "--version") == 0)
+ version(argv[0]);
+ config_path = argv[1];
+ }
+
+ config = pf_server_config_load_file(config_path);
+ if (!config)
+ goto fail;
+
+ pf_server_config_print(config);
+
+ server = pf_server_new(config);
+ pf_server_config_free(config);
+
+ if (!server)
+ goto fail;
+
+ if (!pf_server_start(server))
+ goto fail;
+
+ if (!pf_server_run(server))
+ goto fail;
+
+ status = 0;
+
+fail:
+ pf_server_free(server);
+
+ return status;
+}
diff --git a/server/proxy/config.ini b/server/proxy/config.ini
new file mode 100644
index 0000000..a8ac44b
--- /dev/null
+++ b/server/proxy/config.ini
@@ -0,0 +1,53 @@
+[Server]
+Host = 0.0.0.0
+Port = 3389
+
+[Target]
+; If this value is set to TRUE, the target server info will be parsed using the
+; load balance info setting at runtime. The format is
+; "Cookie: msts=<target server>", and can be set in an rdp file for windows/mac,
+; and the /load-balance-info: CLI option for xfreerdp. Otherwise, the server
+; will always connect to the same target, using the configured values of `Host`
+; and `Port`.
+FixedTarget = TRUE
+Host = CustomHost
+Port = 3389
+
+[Input]
+Mouse = TRUE
+Keyboard = TRUE
+
+[Security]
+ServerTlsSecurity = TRUE
+ServerRdpSecurity = FALSE
+ClientTlsSecurity = TRUE
+ClientRdpSecurity = FALSE
+ClientNlaSecurity = TRUE
+ClientAllowFallbackToTls = TRUE
+
+[Channels]
+GFX = TRUE
+DisplayControl = TRUE
+Clipboard = TRUE
+AudioOutput = TRUE
+RemoteApp = TRUE
+; a list of comma seperated static channels that will be proxied. This feature is useful,
+; for example when there's a custom static channel that isn't implemented in freerdp/proxy, and is needed to be proxied when connecting through the proxy.
+; Passthrough = ""
+
+[Clipboard]
+TextOnly = FALSE
+MaxTextLength = 10 # 0 for no limit.
+
+[GFXSettings]
+DecodeGFX = TRUE
+
+[Plugins]
+; An optional, comma separated list of paths to modules that the proxy should load at startup.
+;
+; Modules = "proxy-demo-plugin.so"
+
+; An optional, comma separated list of required plugins (names),
+; that the proxy won't start without having them loaded.
+;
+; Required = "demo"
diff --git a/server/proxy/freerdp-proxy.pc.in b/server/proxy/freerdp-proxy.pc.in
new file mode 100644
index 0000000..4655075
--- /dev/null
+++ b/server/proxy/freerdp-proxy.pc.in
@@ -0,0 +1,16 @@
+prefix=@PKG_CONFIG_INSTALL_PREFIX@
+exec_prefix=${prefix}
+libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
+includedir=${prefix}/@FREERDP_INCLUDE_DIR@
+libs=-lfreerdp-server-proxy@FREERDP_API_VERSION@
+
+Name: FreeRDP proxy
+Description: FreeRDP: A Remote Desktop Protocol Implementation
+URL: http://www.freerdp.com/
+Version: @FREERDP_VERSION@
+Requires:
+Requires.private: @WINPR_PKG_CONFIG_FILENAME@ freerdp@FREERDP_VERSION_MAJOR@ freerdp-server@FREERDP_VERSION_MAJOR@ freerdp-client@FREERDP_VERSION_MAJOR@
+
+Libs: -L${libdir} ${libs}
+Libs.private: -ldl -lpthread
+Cflags: -I${includedir}
diff --git a/server/proxy/modules/CMakeLists.txt b/server/proxy/modules/CMakeLists.txt
new file mode 100644
index 0000000..500a3e7
--- /dev/null
+++ b/server/proxy/modules/CMakeLists.txt
@@ -0,0 +1,33 @@
+# Copyright 2019 Kobi Mizrachi <kmizrachi18@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.
+
+# The third-party directory is meant for third-party components to be built
+# as part of the main FreeRDP build system, making separate maintenance easier.
+# Subdirectories of the third-party directory are ignored by git, but are
+# automatically included by CMake when the -DWITH_THIRD_PARTY=on option is used.
+
+# include proxy header files for proxy modules
+include_directories("${PROJECT_SOURCE_DIR}/server/proxy")
+include_directories("${PROJECT_SOURCE_DIR}/server/proxy/modules")
+
+# taken from FreeRDP/third-party/CMakeLists.txt
+file(GLOB all_valid_subdirs RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*/CMakeLists.txt")
+
+foreach(dir ${all_valid_subdirs})
+ if(${dir} MATCHES "^([^/]*)/+CMakeLists.txt")
+ string(REGEX REPLACE "^([^/]*)/+CMakeLists.txt" "\\1" dir_trimmed ${dir})
+ message(STATUS "Adding proxy module ${dir_trimmed}")
+ add_subdirectory(${dir_trimmed})
+ endif()
+endforeach(dir)
diff --git a/server/proxy/modules/README.md b/server/proxy/modules/README.md
new file mode 100644
index 0000000..f1d2206
--- /dev/null
+++ b/server/proxy/modules/README.md
@@ -0,0 +1,66 @@
+# Proxy module API
+
+`freerdp-proxy` has an API for hooking/filtering certain events/messages.
+A module can register callbacks to events, allowing to record the data and control whether to pass/ignore, or right out drop the connection.
+
+During startup, the proxy reads its modules from the configuration:
+
+```ini
+[Plugins]
+Modules = demo,cap
+```
+
+These modules are loaded in a best effort manner. Additionally there is a configuration field for modules that must be loaded,
+so the proxy refuses to start if they are not found:
+
+```ini
+[Plugins]
+Required = demo,cap
+```
+
+Modules must be installed as shared libraris in the `<base install>/lib/freerdp3/proxy` folder and match the pattern
+`proxy-<name>-plugin.<ext>` (e.g. `proxy-demo-plugin.so`) to be found.
+For security reasons loading by full path is not supported and only the installation path is used for lookup.
+
+## Currently supported hook events
+
+### Client
+
+* ClientInitConnect: Called before the client tries to open a connection
+* ClientUninitConnect: Called after the client has disconnected
+* ClientPreConnect: Called in client PreConnect callback
+* ClientPostConnect: Called in client PostConnect callback
+* ClientPostDisconnect: Called in client PostDisconnect callback
+* ClientX509Certificate: Called in client X509 certificate verification callback
+* ClientLoginFailure: Called in client login failure callback
+* ClientEndPaint: Called in client EndPaint callback
+
+### Server
+
+* ServerPostConnect: Called after a client has connected
+* ServerPeerActivate: Called after a client has activated
+* ServerChannelsInit: Called after channels are initialized
+* ServerChannelsFree: Called after channels are cleaned up
+* ServerSessionEnd: Called after the client connection disconnected
+
+## Currently supported filter events
+
+* KeyboardEvent: Keyboard event, e.g. all key press and release events
+* MouseEvent: Mouse event, e.g. mouse movement and button press/release events
+* ClientChannelData: Client static channel data
+* ServerChannelData: Server static channel data
+* DynamicChannelCreate: Dynamic channel create
+* ServerFetchTargetAddr: Fetch target address (e.g. RDP TargetInfo)
+* ServerPeerLogon: A peer is logging on
+
+## Developing a new module
+* Create a new file that includes `freerdp/server/proxy/proxy_modules_api.h`.
+* Implement the `proxy_module_entry_point` function and register the callbacks you are interested in.
+* Each callback receives two parameters:
+ * `connectionInfo* info` holds connection info of the raised event.
+ * `void* param` holds the actual event data. It should be casted by the filter to the suitable struct from `filters_api.h`.
+* Each callback must return a `BOOL`:
+ * `FALSE`: The event will not be proxied.
+ * `TRUE`: The event will be proxied.
+
+A demo can be found in `filter_demo.c`.
diff --git a/server/proxy/modules/bitmap-filter/CMakeLists.txt b/server/proxy/modules/bitmap-filter/CMakeLists.txt
new file mode 100644
index 0000000..d2cc03b
--- /dev/null
+++ b/server/proxy/modules/bitmap-filter/CMakeLists.txt
@@ -0,0 +1,60 @@
+#
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Proxy Server Demo C++ Module
+#
+# Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+# Copyright 2021 Armin Novak <anovak@thincast.com>
+# Copyright 2021 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.
+#
+
+cmake_minimum_required(VERSION 3.13)
+
+if(POLICY CMP0091)
+ cmake_policy(SET CMP0091 NEW)
+endif()
+if (NOT FREERDP_DEFAULT_PROJECT_VERSION)
+ set(FREERDP_DEFAULT_PROJECT_VERSION "1.0.0.0")
+endif()
+
+project(proxy-bitmap-filter-plugin
+ VERSION ${FREERDP_DEFAULT_PROJECT_VERSION}
+ LANGUAGES CXX
+)
+
+message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}")
+
+project(proxy-bitmap-filter-plugin
+ VERSION ${FREERDP_DEFAULT_PROJECT_VERSION}
+ LANGUAGES CXX
+)
+
+message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}")
+
+set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../../../cmake/)
+include(CommonConfigOptions)
+
+set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+add_library(${PROJECT_NAME} SHARED
+ bitmap-filter.cpp
+)
+
+target_link_libraries(${PROJECT_NAME} winpr freerdp)
+
+set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
+set_target_properties(${PROJECT_NAME} PROPERTIES NO_SONAME 1)
+
+install(TARGETS ${PROJECT_NAME} DESTINATION ${FREERDP_PROXY_PLUGINDIR})
diff --git a/server/proxy/modules/bitmap-filter/bitmap-filter.cpp b/server/proxy/modules/bitmap-filter/bitmap-filter.cpp
new file mode 100644
index 0000000..d1b2e78
--- /dev/null
+++ b/server/proxy/modules/bitmap-filter/bitmap-filter.cpp
@@ -0,0 +1,453 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server persist-bitmap-filter Module
+ *
+ * this module is designed to deactivate all persistent bitmap cache settings a
+ * client might send.
+ *
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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.
+ */
+
+#include <iostream>
+#include <vector>
+#include <string>
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <mutex>
+
+#include <freerdp/server/proxy/proxy_modules_api.h>
+#include <freerdp/server/proxy/proxy_context.h>
+
+#include <freerdp/channels/drdynvc.h>
+#include <freerdp/channels/rdpgfx.h>
+#include <freerdp/utils/gfx.h>
+
+#define TAG MODULE_TAG("persist-bitmap-filter")
+
+static constexpr char plugin_name[] = "bitmap-filter";
+static constexpr char plugin_desc[] =
+ "this plugin deactivates and filters persistent bitmap cache.";
+
+static const std::vector<std::string> plugin_static_intercept = { DRDYNVC_SVC_CHANNEL_NAME };
+static const std::vector<std::string> plugin_dyn_intercept = { RDPGFX_DVC_CHANNEL_NAME };
+
+class DynChannelState
+{
+
+ public:
+ bool skip() const
+ {
+ return _toSkip != 0;
+ }
+
+ bool skip(size_t s)
+ {
+ if (s > _toSkip)
+ _toSkip = 0;
+ else
+ _toSkip -= s;
+ return skip();
+ }
+
+ size_t remaining() const
+ {
+ return _toSkip;
+ }
+
+ size_t total() const
+ {
+ return _totalSkipSize;
+ }
+
+ void setSkipSize(size_t len)
+ {
+ _toSkip = _totalSkipSize = len;
+ }
+
+ bool drop() const
+ {
+ return _drop;
+ }
+
+ void setDrop(bool d)
+ {
+ _drop = d;
+ }
+
+ uint32_t channelId() const
+ {
+ return _channelId;
+ }
+
+ void setChannelId(uint32_t id)
+ {
+ _channelId = id;
+ }
+
+ private:
+ size_t _toSkip = 0;
+ size_t _totalSkipSize = 0;
+ bool _drop = false;
+ uint32_t _channelId = 0;
+};
+
+static BOOL filter_client_pre_connect(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(pdata->pc);
+ WINPR_ASSERT(custom);
+
+ auto settings = pdata->pc->context.settings;
+
+ /* We do not want persistent bitmap cache to be used with proxy */
+ return freerdp_settings_set_bool(settings, FreeRDP_BitmapCachePersistEnabled, FALSE);
+}
+
+static BOOL filter_dyn_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata, void* arg)
+{
+ auto data = static_cast<proxyChannelToInterceptData*>(arg);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(data);
+
+ auto intercept = std::find(plugin_dyn_intercept.begin(), plugin_dyn_intercept.end(),
+ data->name) != plugin_dyn_intercept.end();
+ if (intercept)
+ data->intercept = TRUE;
+ return TRUE;
+}
+
+static BOOL filter_static_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata, void* arg)
+{
+ auto data = static_cast<proxyChannelToInterceptData*>(arg);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(data);
+
+ auto intercept = std::find(plugin_static_intercept.begin(), plugin_static_intercept.end(),
+ data->name) != plugin_static_intercept.end();
+ if (intercept)
+ data->intercept = TRUE;
+ return TRUE;
+}
+
+static size_t drdynvc_cblen_to_bytes(UINT8 cbLen)
+{
+ switch (cbLen)
+ {
+ case 0:
+ return 1;
+
+ case 1:
+ return 2;
+
+ default:
+ return 4;
+ }
+}
+
+static UINT32 drdynvc_read_variable_uint(wStream* s, UINT8 cbLen)
+{
+ UINT32 val = 0;
+
+ switch (cbLen)
+ {
+ case 0:
+ Stream_Read_UINT8(s, val);
+ break;
+
+ case 1:
+ Stream_Read_UINT16(s, val);
+ break;
+
+ default:
+ Stream_Read_UINT32(s, val);
+ break;
+ }
+
+ return val;
+}
+
+static BOOL drdynvc_try_read_header(wStream* s, size_t& channelId, size_t& length)
+{
+ UINT8 value = 0;
+ Stream_SetPosition(s, 0);
+ if (Stream_GetRemainingLength(s) < 1)
+ return FALSE;
+ Stream_Read_UINT8(s, value);
+
+ const UINT8 cmd = (value & 0xf0) >> 4;
+ const UINT8 Sp = (value & 0x0c) >> 2;
+ const UINT8 cbChId = (value & 0x03);
+
+ switch (cmd)
+ {
+ case DATA_PDU:
+ case DATA_FIRST_PDU:
+ break;
+ default:
+ return FALSE;
+ }
+
+ const size_t channelIdLen = drdynvc_cblen_to_bytes(cbChId);
+ if (Stream_GetRemainingLength(s) < channelIdLen)
+ return FALSE;
+
+ channelId = drdynvc_read_variable_uint(s, cbChId);
+ length = Stream_Length(s);
+ if (cmd == DATA_FIRST_PDU)
+ {
+ const size_t dataLen = drdynvc_cblen_to_bytes(Sp);
+ if (Stream_GetRemainingLength(s) < dataLen)
+ return FALSE;
+
+ length = drdynvc_read_variable_uint(s, Sp);
+ }
+
+ return TRUE;
+}
+
+static DynChannelState* filter_get_plugin_data(proxyPlugin* plugin, proxyData* pdata)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+
+ auto mgr = static_cast<proxyPluginsManager*>(plugin->custom);
+ WINPR_ASSERT(mgr);
+
+ WINPR_ASSERT(mgr->GetPluginData);
+ return static_cast<DynChannelState*>(mgr->GetPluginData(mgr, plugin_name, pdata));
+}
+
+static BOOL filter_set_plugin_data(proxyPlugin* plugin, proxyData* pdata, DynChannelState* data)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+
+ auto mgr = static_cast<proxyPluginsManager*>(plugin->custom);
+ WINPR_ASSERT(mgr);
+
+ WINPR_ASSERT(mgr->SetPluginData);
+ return mgr->SetPluginData(mgr, plugin_name, pdata, data);
+}
+
+static UINT8 drdynvc_value_to_cblen(UINT32 value)
+{
+ if (value <= 0xFF)
+ return 0;
+ if (value <= 0xFFFF)
+ return 1;
+ return 2;
+}
+
+static BOOL drdynvc_write_variable_uint(wStream* s, UINT32 value, UINT8 cbLen)
+{
+ switch (cbLen)
+ {
+ case 0:
+ Stream_Write_UINT8(s, static_cast<UINT8>(value));
+ break;
+
+ case 1:
+ Stream_Write_UINT16(s, static_cast<UINT16>(value));
+ break;
+
+ default:
+ Stream_Write_UINT32(s, value);
+ break;
+ }
+
+ return TRUE;
+}
+
+static BOOL drdynvc_write_header(wStream* s, UINT32 channelId)
+{
+ const UINT8 cbChId = drdynvc_value_to_cblen(channelId);
+ const UINT8 value = (DATA_PDU << 4) | cbChId;
+ const size_t len = drdynvc_cblen_to_bytes(cbChId) + 1;
+
+ if (!Stream_EnsureRemainingCapacity(s, len))
+ return FALSE;
+
+ Stream_Write_UINT8(s, value);
+ return drdynvc_write_variable_uint(s, value, cbChId);
+}
+
+static BOOL filter_forward_empty_offer(const char* sessionID, proxyDynChannelInterceptData* data,
+ size_t startPosition, UINT32 channelId)
+{
+ WINPR_ASSERT(data);
+
+ Stream_SetPosition(data->data, startPosition);
+ if (!drdynvc_write_header(data->data, channelId))
+ return FALSE;
+
+ if (!Stream_EnsureRemainingCapacity(data->data, sizeof(UINT16)))
+ return FALSE;
+ Stream_Write_UINT16(data->data, 0);
+ Stream_SealLength(data->data);
+
+ WLog_INFO(TAG, "[SessionID=%s][%s] forwarding empty %s", sessionID, plugin_name,
+ rdpgfx_get_cmd_id_string(RDPGFX_CMDID_CACHEIMPORTOFFER));
+ data->rewritten = TRUE;
+ return TRUE;
+}
+
+static BOOL filter_dyn_channel_intercept(proxyPlugin* plugin, proxyData* pdata, void* arg)
+{
+ auto data = static_cast<proxyDynChannelInterceptData*>(arg);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(data);
+
+ data->result = PF_CHANNEL_RESULT_PASS;
+ if (!data->isBackData &&
+ (strncmp(data->name, RDPGFX_DVC_CHANNEL_NAME, ARRAYSIZE(RDPGFX_DVC_CHANNEL_NAME)) == 0))
+ {
+ auto state = filter_get_plugin_data(plugin, pdata);
+ if (!state)
+ {
+ WLog_ERR(TAG, "[SessionID=%s][%s] missing custom data, aborting!", pdata->session_id,
+ plugin_name);
+ return FALSE;
+ }
+ const size_t inputDataLength = Stream_Length(data->data);
+ UINT16 cmdId = RDPGFX_CMDID_UNUSED_0000;
+
+ const auto pos = Stream_GetPosition(data->data);
+ if (!state->skip())
+ {
+ if (data->first)
+ {
+ size_t channelId = 0;
+ size_t length = 0;
+ if (drdynvc_try_read_header(data->data, channelId, length))
+ {
+ if (Stream_GetRemainingLength(data->data) >= 2)
+ {
+ Stream_Read_UINT16(data->data, cmdId);
+ state->setSkipSize(length);
+ state->setDrop(false);
+ }
+ }
+
+ switch (cmdId)
+ {
+ case RDPGFX_CMDID_CACHEIMPORTOFFER:
+ state->setDrop(true);
+ state->setChannelId(channelId);
+ break;
+ default:
+ break;
+ }
+ Stream_SetPosition(data->data, pos);
+ }
+ }
+
+ if (state->skip())
+ {
+ state->skip(inputDataLength);
+ if (state->drop())
+ {
+ WLog_WARN(TAG,
+ "[SessionID=%s][%s] dropping %s packet [total:%" PRIuz ", current:%" PRIuz
+ ", remaining: %" PRIuz "]",
+ pdata->session_id, plugin_name,
+ rdpgfx_get_cmd_id_string(RDPGFX_CMDID_CACHEIMPORTOFFER), state->total(),
+ inputDataLength, state->remaining());
+ data->result = PF_CHANNEL_RESULT_DROP;
+
+#if 0 // TODO: Sending this does screw up some windows RDP server versions :/
+ if (state->remaining() == 0)
+ {
+ if (!filter_forward_empty_offer(pdata->session_id, data, pos,
+ state->channelId()))
+ return FALSE;
+ }
+#endif
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static BOOL filter_server_session_started(proxyPlugin* plugin, proxyData* pdata, void*)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+
+ auto state = filter_get_plugin_data(plugin, pdata);
+ delete state;
+
+ auto newstate = new DynChannelState();
+ if (!filter_set_plugin_data(plugin, pdata, newstate))
+ {
+ delete newstate;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL filter_server_session_end(proxyPlugin* plugin, proxyData* pdata, void*)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+
+ auto state = filter_get_plugin_data(plugin, pdata);
+ delete state;
+ filter_set_plugin_data(plugin, pdata, nullptr);
+ return TRUE;
+}
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+ FREERDP_API BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata);
+#ifdef __cplusplus
+}
+#endif
+
+BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata)
+{
+ proxyPlugin plugin = {};
+
+ plugin.name = plugin_name;
+ plugin.description = plugin_desc;
+
+ plugin.ServerSessionStarted = filter_server_session_started;
+ plugin.ServerSessionEnd = filter_server_session_end;
+
+ plugin.ClientPreConnect = filter_client_pre_connect;
+
+ plugin.StaticChannelToIntercept = filter_static_channel_intercept_list;
+ plugin.DynChannelToIntercept = filter_dyn_channel_intercept_list;
+ plugin.DynChannelIntercept = filter_dyn_channel_intercept;
+
+ plugin.custom = plugins_manager;
+ if (!plugin.custom)
+ return FALSE;
+ plugin.userdata = userdata;
+
+ return plugins_manager->RegisterPlugin(plugins_manager, &plugin);
+}
diff --git a/server/proxy/modules/demo/CMakeLists.txt b/server/proxy/modules/demo/CMakeLists.txt
new file mode 100644
index 0000000..bdd85a3
--- /dev/null
+++ b/server/proxy/modules/demo/CMakeLists.txt
@@ -0,0 +1,53 @@
+#
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Proxy Server Demo C++ Module
+#
+# Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+# Copyright 2021 Armin Novak <anovak@thincast.com>
+# Copyright 2021 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.
+#
+
+cmake_minimum_required(VERSION 3.13)
+
+if(POLICY CMP0091)
+ cmake_policy(SET CMP0091 NEW)
+endif()
+if (NOT FREERDP_DEFAULT_PROJECT_VERSION)
+ set(FREERDP_DEFAULT_PROJECT_VERSION "1.0.0.0")
+endif()
+
+project(proxy-demo-plugin
+ VERSION ${FREERDP_DEFAULT_PROJECT_VERSION}
+ LANGUAGES CXX
+)
+
+message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}")
+
+set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../../../cmake/)
+include(CommonConfigOptions)
+
+set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+add_library(${PROJECT_NAME} SHARED
+ demo.cpp
+)
+
+target_link_libraries(${PROJECT_NAME} winpr)
+
+set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
+set_target_properties(${PROJECT_NAME} PROPERTIES NO_SONAME 1)
+
+install(TARGETS ${PROJECT_NAME} DESTINATION ${FREERDP_PROXY_PLUGINDIR})
diff --git a/server/proxy/modules/demo/demo.cpp b/server/proxy/modules/demo/demo.cpp
new file mode 100644
index 0000000..75526ef
--- /dev/null
+++ b/server/proxy/modules/demo/demo.cpp
@@ -0,0 +1,422 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server Demo C++ Module
+ *
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2021 Armin Novak <anovak@thincast.com>
+ * Copyright 2021 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.
+ */
+
+#include <iostream>
+
+#include <freerdp/api.h>
+#include <freerdp/scancode.h>
+#include <freerdp/server/proxy/proxy_modules_api.h>
+
+#define TAG MODULE_TAG("demo")
+
+struct demo_custom_data
+{
+ proxyPluginsManager* mgr;
+ int somesetting;
+};
+
+static constexpr char plugin_name[] = "demo";
+static constexpr char plugin_desc[] = "this is a test plugin";
+
+static BOOL demo_plugin_unload(proxyPlugin* plugin)
+{
+ WINPR_ASSERT(plugin);
+
+ std::cout << "C++ demo plugin: unloading..." << std::endl;
+
+ /* Here we have to free up our custom data storage. */
+ if (plugin)
+ delete static_cast<struct demo_custom_data*>(plugin->custom);
+
+ return TRUE;
+}
+
+static BOOL demo_client_init_connect(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_client_uninit_connect(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_client_pre_connect(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_client_post_connect(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_client_post_disconnect(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_client_x509_certificate(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_client_login_failure(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_client_end_paint(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_client_redirect(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_server_post_connect(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_server_peer_activate(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_server_channels_init(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_server_channels_free(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_server_session_end(proxyPlugin* plugin, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(custom);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_filter_keyboard_event(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ proxyPluginsManager* mgr = nullptr;
+ auto event_data = static_cast<const proxyKeyboardEventInfo*>(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(event_data);
+
+ mgr = plugin->mgr;
+ WINPR_ASSERT(mgr);
+
+ if (event_data == nullptr)
+ return FALSE;
+
+ if (event_data->rdp_scan_code == RDP_SCANCODE_KEY_B)
+ {
+ /* user typed 'B', that means bye :) */
+ std::cout << "C++ demo plugin: aborting connection" << std::endl;
+ mgr->AbortConnect(mgr, pdata);
+ }
+
+ return TRUE;
+}
+
+static BOOL demo_filter_unicode_event(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ proxyPluginsManager* mgr = nullptr;
+ auto event_data = static_cast<const proxyUnicodeEventInfo*>(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(event_data);
+
+ mgr = plugin->mgr;
+ WINPR_ASSERT(mgr);
+
+ if (event_data == nullptr)
+ return FALSE;
+
+ if (event_data->code == 'b')
+ {
+ /* user typed 'B', that means bye :) */
+ std::cout << "C++ demo plugin: aborting connection" << std::endl;
+ mgr->AbortConnect(mgr, pdata);
+ }
+
+ return TRUE;
+}
+
+static BOOL demo_mouse_event(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ auto event_data = static_cast<const proxyMouseEventInfo*>(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(event_data);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_mouse_ex_event(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ auto event_data = static_cast<const proxyMouseExEventInfo*>(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(event_data);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_client_channel_data(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ const proxyChannelDataEventInfo* channel = static_cast<const proxyChannelDataEventInfo*>(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ WLog_INFO(TAG, "%s [0x%04" PRIx16 "] got %" PRIuz, channel->channel_name, channel->channel_id,
+ channel->data_len);
+ return TRUE;
+}
+
+static BOOL demo_server_channel_data(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ const proxyChannelDataEventInfo* channel = static_cast<const proxyChannelDataEventInfo*>(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ WLog_WARN(TAG, "%s [0x%04" PRIx16 "] got %" PRIuz, channel->channel_name, channel->channel_id,
+ channel->data_len);
+ return TRUE;
+}
+
+static BOOL demo_dynamic_channel_create(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ const proxyChannelDataEventInfo* channel = static_cast<const proxyChannelDataEventInfo*>(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ WLog_WARN(TAG, "%s [0x%04" PRIx16 "]", channel->channel_name, channel->channel_id);
+ return TRUE;
+}
+
+static BOOL demo_server_fetch_target_addr(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ auto event_data = static_cast<const proxyFetchTargetEventInfo*>(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(event_data);
+
+ WLog_INFO(TAG, "called");
+ return TRUE;
+}
+
+static BOOL demo_server_peer_logon(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ auto info = static_cast<const proxyServerPeerLogon*>(param);
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(info);
+ WINPR_ASSERT(info->identity);
+
+ WLog_INFO(TAG, "%d", info->automatic);
+ return TRUE;
+}
+
+static BOOL demo_dyn_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata, void* arg)
+{
+ auto data = static_cast<proxyChannelToInterceptData*>(arg);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(data);
+
+ WLog_INFO(TAG, "%s", __func__);
+ return TRUE;
+}
+
+static BOOL demo_static_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata, void* arg)
+{
+ auto data = static_cast<proxyChannelToInterceptData*>(arg);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(data);
+
+ WLog_INFO(TAG, "%s", __func__);
+ return TRUE;
+}
+
+static BOOL demo_dyn_channel_intercept(proxyPlugin* plugin, proxyData* pdata, void* arg)
+{
+ auto data = static_cast<proxyDynChannelInterceptData*>(arg);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(data);
+
+ WLog_INFO(TAG, "%s", __func__);
+ return TRUE;
+}
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+ FREERDP_API BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata);
+#ifdef __cplusplus
+}
+#endif
+
+BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata)
+{
+ struct demo_custom_data* custom = nullptr;
+ proxyPlugin plugin = {};
+
+ plugin.name = plugin_name;
+ plugin.description = plugin_desc;
+ plugin.PluginUnload = demo_plugin_unload;
+ plugin.ClientInitConnect = demo_client_init_connect;
+ plugin.ClientUninitConnect = demo_client_uninit_connect;
+ plugin.ClientPreConnect = demo_client_pre_connect;
+ plugin.ClientPostConnect = demo_client_post_connect;
+ plugin.ClientPostDisconnect = demo_client_post_disconnect;
+ plugin.ClientX509Certificate = demo_client_x509_certificate;
+ plugin.ClientLoginFailure = demo_client_login_failure;
+ plugin.ClientEndPaint = demo_client_end_paint;
+ plugin.ClientRedirect = demo_client_redirect;
+ plugin.ServerPostConnect = demo_server_post_connect;
+ plugin.ServerPeerActivate = demo_server_peer_activate;
+ plugin.ServerChannelsInit = demo_server_channels_init;
+ plugin.ServerChannelsFree = demo_server_channels_free;
+ plugin.ServerSessionEnd = demo_server_session_end;
+ plugin.KeyboardEvent = demo_filter_keyboard_event;
+ plugin.UnicodeEvent = demo_filter_unicode_event;
+ plugin.MouseEvent = demo_mouse_event;
+ plugin.MouseExEvent = demo_mouse_ex_event;
+ plugin.ClientChannelData = demo_client_channel_data;
+ plugin.ServerChannelData = demo_server_channel_data;
+ plugin.DynamicChannelCreate = demo_dynamic_channel_create;
+ plugin.ServerFetchTargetAddr = demo_server_fetch_target_addr;
+ plugin.ServerPeerLogon = demo_server_peer_logon;
+
+ plugin.StaticChannelToIntercept = demo_static_channel_intercept_list;
+ plugin.DynChannelToIntercept = demo_dyn_channel_intercept_list;
+ plugin.DynChannelIntercept = demo_dyn_channel_intercept;
+
+ plugin.userdata = userdata;
+
+ custom = new (struct demo_custom_data);
+ if (!custom)
+ return FALSE;
+
+ custom->mgr = plugins_manager;
+ custom->somesetting = 42;
+
+ plugin.custom = custom;
+ plugin.userdata = userdata;
+
+ return plugins_manager->RegisterPlugin(plugins_manager, &plugin);
+}
diff --git a/server/proxy/modules/dyn-channel-dump/CMakeLists.txt b/server/proxy/modules/dyn-channel-dump/CMakeLists.txt
new file mode 100644
index 0000000..dc0fc53
--- /dev/null
+++ b/server/proxy/modules/dyn-channel-dump/CMakeLists.txt
@@ -0,0 +1,58 @@
+#
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP Proxy Server Demo C++ Module
+#
+# Copyright 2023 Armin Novak <anovak@thincast.com>
+# Copyright 2023 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.
+#
+
+cmake_minimum_required(VERSION 3.13)
+
+if(POLICY CMP0091)
+ cmake_policy(SET CMP0091 NEW)
+endif()
+if (NOT FREERDP_DEFAULT_PROJECT_VERSION)
+ set(FREERDP_DEFAULT_PROJECT_VERSION "1.0.0.0")
+endif()
+
+project(proxy-dyn-channel-dump-plugin
+ VERSION ${FREERDP_DEFAULT_PROJECT_VERSION}
+ LANGUAGES CXX
+)
+
+message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}")
+
+set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../../../cmake/)
+include(CommonConfigOptions)
+
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+add_library(${PROJECT_NAME} SHARED
+ dyn-channel-dump.cpp
+)
+
+target_link_libraries(${PROJECT_NAME} PRIVATE
+ winpr
+ freerdp
+ freerdp-client
+ freerdp-server
+ freerdp-server-proxy
+)
+
+set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
+set_target_properties(${PROJECT_NAME} PROPERTIES NO_SONAME 1)
+
+install(TARGETS ${PROJECT_NAME} DESTINATION ${FREERDP_PROXY_PLUGINDIR})
diff --git a/server/proxy/modules/dyn-channel-dump/dyn-channel-dump.cpp b/server/proxy/modules/dyn-channel-dump/dyn-channel-dump.cpp
new file mode 100644
index 0000000..d80902f
--- /dev/null
+++ b/server/proxy/modules/dyn-channel-dump/dyn-channel-dump.cpp
@@ -0,0 +1,436 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server persist-bitmap-filter Module
+ *
+ * this module is designed to deactivate all persistent bitmap cache settings a
+ * client might send.
+ *
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2023 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.
+ */
+
+#include <fstream>
+#include <iostream>
+#include <regex>
+#include <vector>
+#include <sstream>
+#include <string>
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <atomic>
+#if __has_include(<filesystem>)
+#include <filesystem>
+namespace fs = std::filesystem;
+#elif __has_include(<experimental/filesystem>)
+#include <experimental/filesystem>
+namespace fs = std::experimental::filesystem;
+#else
+#error Could not find system header "<filesystem>" or "<experimental/filesystem>"
+#endif
+
+#include <freerdp/server/proxy/proxy_modules_api.h>
+#include <freerdp/server/proxy/proxy_context.h>
+
+#include <freerdp/channels/drdynvc.h>
+#include <freerdp/channels/rdpgfx.h>
+#include <freerdp/utils/gfx.h>
+
+#define TAG MODULE_TAG("dyn-channel-dump")
+
+static constexpr char plugin_name[] = "dyn-channel-dump";
+static constexpr char plugin_desc[] =
+ "This plugin dumps configurable dynamic channel data to a file.";
+
+static const std::vector<std::string> plugin_static_intercept = { DRDYNVC_SVC_CHANNEL_NAME };
+
+static constexpr char key_path[] = "path";
+static constexpr char key_channels[] = "channels";
+
+class PluginData
+{
+ public:
+ PluginData(proxyPluginsManager* mgr) : _mgr(mgr), _sessionid(0)
+ {
+ }
+
+ proxyPluginsManager* mgr() const
+ {
+ return _mgr;
+ }
+
+ uint64_t session()
+ {
+ return _sessionid++;
+ }
+
+ private:
+ proxyPluginsManager* _mgr;
+ uint64_t _sessionid;
+};
+
+class ChannelData
+{
+ public:
+ ChannelData(const std::string& base, const std::vector<std::string>& list, uint64_t sessionid)
+ : _base(base), _channels_to_dump(list), _session_id(sessionid)
+ {
+ char str[64] = {};
+ _snprintf(str, sizeof(str), "session-%016" PRIx64, _session_id);
+ _base /= str;
+ }
+
+ bool add(const std::string& name, bool back)
+ {
+ std::lock_guard<std::mutex> guard(_mux);
+ if (_map.find(name) == _map.end())
+ {
+ WLog_INFO(TAG, "adding '%s' to dump list", name.c_str());
+ _map.insert({ name, 0 });
+ }
+ return true;
+ }
+
+ std::ofstream stream(const std::string& name, bool back)
+ {
+ std::lock_guard<std::mutex> guard(_mux);
+ auto& atom = _map[name];
+ auto count = atom++;
+ auto path = filepath(name, back, count);
+ WLog_DBG(TAG, "[%s] writing file '%s'", name.c_str(), path.c_str());
+ return std::ofstream(path);
+ }
+
+ bool dump_enabled(const std::string& name) const
+ {
+ if (name.empty())
+ {
+ WLog_WARN(TAG, "empty dynamic channel name, skipping");
+ return false;
+ }
+
+ auto enabled = std::find(_channels_to_dump.begin(), _channels_to_dump.end(), name) !=
+ _channels_to_dump.end();
+ WLog_DBG(TAG, "channel '%s' dumping %s", name.c_str(), enabled ? "enabled" : "disabled");
+ return enabled;
+ }
+
+ bool ensure_path_exists()
+ {
+ if (!fs::exists(_base))
+ {
+ if (!fs::create_directories(_base))
+ {
+ WLog_ERR(TAG, "Failed to create dump directory %s", _base.c_str());
+ return false;
+ }
+ }
+ else if (!fs::is_directory(_base))
+ {
+ WLog_ERR(TAG, "dump path %s is not a directory", _base.c_str());
+ return false;
+ }
+ return true;
+ }
+
+ bool create()
+ {
+ if (!ensure_path_exists())
+ return false;
+
+ if (_channels_to_dump.empty())
+ {
+ WLog_ERR(TAG, "Empty configuration entry [%s/%s], can not continue", plugin_name,
+ key_channels);
+ return false;
+ }
+ return true;
+ }
+
+ uint64_t session() const
+ {
+ return _session_id;
+ }
+
+ private:
+ fs::path filepath(const std::string& channel, bool back, uint64_t count) const
+ {
+ auto name = idstr(channel, back);
+ char cstr[32] = {};
+ _snprintf(cstr, sizeof(cstr), "%016" PRIx64 "-", count);
+ auto path = _base / cstr;
+ path += name;
+ path += ".dump";
+ return path;
+ }
+
+ std::string idstr(const std::string& name, bool back) const
+ {
+ std::stringstream ss;
+ ss << name << ".";
+ if (back)
+ ss << "back";
+ else
+ ss << "front";
+ return ss.str();
+ }
+
+ private:
+ fs::path _base;
+ std::vector<std::string> _channels_to_dump;
+
+ std::mutex _mux;
+ std::map<std::string, uint64_t> _map;
+ uint64_t _session_id;
+};
+
+static PluginData* dump_get_plugin_data(proxyPlugin* plugin)
+{
+ WINPR_ASSERT(plugin);
+
+ auto plugindata = static_cast<PluginData*>(plugin->custom);
+ WINPR_ASSERT(plugindata);
+ return plugindata;
+}
+
+static ChannelData* dump_get_plugin_data(proxyPlugin* plugin, proxyData* pdata)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+
+ auto plugindata = dump_get_plugin_data(plugin);
+ WINPR_ASSERT(plugindata);
+
+ auto mgr = plugindata->mgr();
+ WINPR_ASSERT(mgr);
+
+ WINPR_ASSERT(mgr->GetPluginData);
+ return static_cast<ChannelData*>(mgr->GetPluginData(mgr, plugin_name, pdata));
+}
+
+static BOOL dump_set_plugin_data(proxyPlugin* plugin, proxyData* pdata, ChannelData* data)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+
+ auto plugindata = dump_get_plugin_data(plugin);
+ WINPR_ASSERT(plugindata);
+
+ auto mgr = plugindata->mgr();
+ WINPR_ASSERT(mgr);
+
+ auto cdata = dump_get_plugin_data(plugin, pdata);
+ delete cdata;
+
+ WINPR_ASSERT(mgr->SetPluginData);
+ return mgr->SetPluginData(mgr, plugin_name, pdata, data);
+}
+
+static bool dump_channel_enabled(proxyPlugin* plugin, proxyData* pdata, const std::string& name)
+{
+ auto config = dump_get_plugin_data(plugin, pdata);
+ if (!config)
+ {
+ WLog_ERR(TAG, "Missing channel data");
+ return false;
+ }
+ return config->dump_enabled(name);
+}
+
+static BOOL dump_dyn_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata, void* arg)
+{
+ auto data = static_cast<proxyChannelToInterceptData*>(arg);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(data);
+
+ data->intercept = dump_channel_enabled(plugin, pdata, data->name);
+ if (data->intercept)
+ {
+ auto cdata = dump_get_plugin_data(plugin, pdata);
+ if (!cdata)
+ return FALSE;
+
+ if (!cdata->add(data->name, false))
+ {
+ WLog_ERR(TAG, "failed to create files for '%s'", data->name);
+ }
+ if (!cdata->add(data->name, true))
+ {
+ WLog_ERR(TAG, "failed to create files for '%s'", data->name);
+ }
+ WLog_INFO(TAG, "Dumping channel '%s'", data->name);
+ }
+ return TRUE;
+}
+
+static BOOL dump_static_channel_intercept_list(proxyPlugin* plugin, proxyData* pdata, void* arg)
+{
+ auto data = static_cast<proxyChannelToInterceptData*>(arg);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(data);
+
+ auto intercept = std::find(plugin_static_intercept.begin(), plugin_static_intercept.end(),
+ data->name) != plugin_static_intercept.end();
+ if (intercept)
+ {
+ WLog_INFO(TAG, "intercepting channel '%s'", data->name);
+ data->intercept = TRUE;
+ }
+
+ return TRUE;
+}
+
+static BOOL dump_dyn_channel_intercept(proxyPlugin* plugin, proxyData* pdata, void* arg)
+{
+ auto data = static_cast<proxyDynChannelInterceptData*>(arg);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(data);
+
+ data->result = PF_CHANNEL_RESULT_PASS;
+ if (dump_channel_enabled(plugin, pdata, data->name))
+ {
+ WLog_DBG(TAG, "intercepting channel '%s'", data->name);
+ auto cdata = dump_get_plugin_data(plugin, pdata);
+ if (!cdata)
+ {
+ WLog_ERR(TAG, "Missing channel data");
+ return FALSE;
+ }
+
+ if (!cdata->ensure_path_exists())
+ return FALSE;
+
+ auto stream = cdata->stream(data->name, data->isBackData);
+ auto buffer = reinterpret_cast<const char*>(Stream_ConstBuffer(data->data));
+ if (!stream.is_open() || !stream.good())
+ {
+ WLog_ERR(TAG, "Could not write to stream");
+ return FALSE;
+ }
+ stream.write(buffer, Stream_Length(data->data));
+ if (stream.fail())
+ {
+ WLog_ERR(TAG, "Could not write to stream");
+ return FALSE;
+ }
+ stream.flush();
+ }
+
+ return TRUE;
+}
+
+static std::vector<std::string> split(const std::string& input, const std::string& regex)
+{
+ // passing -1 as the submatch index parameter performs splitting
+ std::regex re(regex);
+ std::sregex_token_iterator first{ input.begin(), input.end(), re, -1 };
+ std::sregex_token_iterator last;
+ return { first, last };
+}
+
+static BOOL dump_session_started(proxyPlugin* plugin, proxyData* pdata, void*)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+
+ auto custom = dump_get_plugin_data(plugin);
+ WINPR_ASSERT(custom);
+
+ auto config = pdata->config;
+ WINPR_ASSERT(config);
+
+ auto cpath = pf_config_get(config, plugin_name, key_path);
+ if (!cpath)
+ {
+ WLog_ERR(TAG, "Missing configuration entry [%s/%s], can not continue", plugin_name,
+ key_path);
+ return FALSE;
+ }
+ auto cchannels = pf_config_get(config, plugin_name, key_channels);
+ if (!cchannels)
+ {
+ WLog_ERR(TAG, "Missing configuration entry [%s/%s], can not continue", plugin_name,
+ key_channels);
+ return FALSE;
+ }
+
+ std::string path(cpath);
+ std::string channels(cchannels);
+ std::vector<std::string> list = split(channels, "[;,]");
+ auto cfg = new ChannelData(path, list, custom->session());
+ if (!cfg || !cfg->create())
+ {
+ delete cfg;
+ return FALSE;
+ }
+
+ dump_set_plugin_data(plugin, pdata, cfg);
+
+ WLog_DBG(TAG, "starting session dump %" PRIu64, cfg->session());
+ return TRUE;
+}
+
+static BOOL dump_session_end(proxyPlugin* plugin, proxyData* pdata, void*)
+{
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+
+ auto cfg = dump_get_plugin_data(plugin, pdata);
+ if (cfg)
+ WLog_DBG(TAG, "ending session dump %" PRIu64, cfg->session());
+ dump_set_plugin_data(plugin, pdata, nullptr);
+ return TRUE;
+}
+
+static BOOL dump_unload(proxyPlugin* plugin)
+{
+ if (!plugin)
+ return TRUE;
+ delete static_cast<PluginData*>(plugin->custom);
+ return TRUE;
+}
+
+extern "C" FREERDP_API BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager,
+ void* userdata);
+
+BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata)
+{
+ proxyPlugin plugin = {};
+
+ plugin.name = plugin_name;
+ plugin.description = plugin_desc;
+
+ plugin.PluginUnload = dump_unload;
+ plugin.ServerSessionStarted = dump_session_started;
+ plugin.ServerSessionEnd = dump_session_end;
+
+ plugin.StaticChannelToIntercept = dump_static_channel_intercept_list;
+ plugin.DynChannelToIntercept = dump_dyn_channel_intercept_list;
+ plugin.DynChannelIntercept = dump_dyn_channel_intercept;
+
+ plugin.custom = new PluginData(plugins_manager);
+ if (!plugin.custom)
+ return FALSE;
+ plugin.userdata = userdata;
+
+ return plugins_manager->RegisterPlugin(plugins_manager, &plugin);
+}
diff --git a/server/proxy/pf_channel.c b/server/proxy/pf_channel.c
new file mode 100644
index 0000000..a8f66c4
--- /dev/null
+++ b/server/proxy/pf_channel.c
@@ -0,0 +1,355 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ * Copyright 2022 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/freerdp.h>
+#include <freerdp/server/proxy/proxy_log.h>
+
+#include "proxy_modules.h"
+#include "pf_channel.h"
+
+#define TAG PROXY_TAG("channel")
+
+/** @brief a tracker for channel packets */
+struct _ChannelStateTracker
+{
+ pServerStaticChannelContext* channel;
+ ChannelTrackerMode mode;
+ wStream* currentPacket;
+ size_t currentPacketReceived;
+ size_t currentPacketSize;
+ size_t currentPacketFragments;
+
+ ChannelTrackerPeekFn peekFn;
+ void* trackerData;
+ proxyData* pdata;
+};
+
+static BOOL channelTracker_resetCurrentPacket(ChannelStateTracker* tracker)
+{
+ WINPR_ASSERT(tracker);
+
+ BOOL create = TRUE;
+ if (tracker->currentPacket)
+ {
+ const size_t cap = Stream_Capacity(tracker->currentPacket);
+ if (cap < 1 * 1000 * 1000)
+ create = FALSE;
+ else
+ Stream_Free(tracker->currentPacket, TRUE);
+ }
+
+ if (create)
+ tracker->currentPacket = Stream_New(NULL, 10 * 1024);
+ if (!tracker->currentPacket)
+ return FALSE;
+ Stream_SetPosition(tracker->currentPacket, 0);
+ return TRUE;
+}
+
+ChannelStateTracker* channelTracker_new(pServerStaticChannelContext* channel,
+ ChannelTrackerPeekFn fn, void* data)
+{
+ ChannelStateTracker* ret = calloc(1, sizeof(ChannelStateTracker));
+ if (!ret)
+ return ret;
+
+ WINPR_ASSERT(fn);
+
+ ret->channel = channel;
+ ret->peekFn = fn;
+
+ if (!channelTracker_setCustomData(ret, data))
+ goto fail;
+
+ if (!channelTracker_resetCurrentPacket(ret))
+ goto fail;
+
+ return ret;
+
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ channelTracker_free(ret);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+PfChannelResult channelTracker_update(ChannelStateTracker* tracker, const BYTE* xdata, size_t xsize,
+ UINT32 flags, size_t totalSize)
+{
+ PfChannelResult result = PF_CHANNEL_RESULT_ERROR;
+ BOOL firstPacket = (flags & CHANNEL_FLAG_FIRST);
+ BOOL lastPacket = (flags & CHANNEL_FLAG_LAST);
+
+ WINPR_ASSERT(tracker);
+
+ WLog_VRB(TAG, "channelTracker_update(%s): sz=%" PRIuz " first=%d last=%d",
+ tracker->channel->channel_name, xsize, firstPacket, lastPacket);
+ if (flags & CHANNEL_FLAG_FIRST)
+ {
+ if (!channelTracker_resetCurrentPacket(tracker))
+ return FALSE;
+ channelTracker_setCurrentPacketSize(tracker, totalSize);
+ tracker->currentPacketReceived = 0;
+ tracker->currentPacketFragments = 0;
+ }
+
+ {
+ const size_t currentPacketSize = channelTracker_getCurrentPacketSize(tracker);
+ if (tracker->currentPacketReceived + xsize > currentPacketSize)
+ WLog_INFO(TAG, "cumulated size is bigger (%" PRIuz ") than total size (%" PRIuz ")",
+ tracker->currentPacketReceived + xsize, currentPacketSize);
+ }
+
+ tracker->currentPacketReceived += xsize;
+ tracker->currentPacketFragments++;
+
+ switch (channelTracker_getMode(tracker))
+ {
+ case CHANNEL_TRACKER_PEEK:
+ {
+ wStream* currentPacket = channelTracker_getCurrentPacket(tracker);
+ if (!Stream_EnsureRemainingCapacity(currentPacket, xsize))
+ return PF_CHANNEL_RESULT_ERROR;
+
+ Stream_Write(currentPacket, xdata, xsize);
+
+ WINPR_ASSERT(tracker->peekFn);
+ result = tracker->peekFn(tracker, firstPacket, lastPacket);
+ }
+ break;
+ case CHANNEL_TRACKER_PASS:
+ result = PF_CHANNEL_RESULT_PASS;
+ break;
+ case CHANNEL_TRACKER_DROP:
+ result = PF_CHANNEL_RESULT_DROP;
+ break;
+ }
+
+ if (lastPacket)
+ {
+ const size_t currentPacketSize = channelTracker_getCurrentPacketSize(tracker);
+ channelTracker_setMode(tracker, CHANNEL_TRACKER_PEEK);
+
+ if (tracker->currentPacketReceived != currentPacketSize)
+ WLog_INFO(TAG, "cumulated size(%" PRIuz ") does not match total size (%" PRIuz ")",
+ tracker->currentPacketReceived, currentPacketSize);
+ }
+
+ return result;
+}
+
+void channelTracker_free(ChannelStateTracker* t)
+{
+ if (!t)
+ return;
+
+ Stream_Free(t->currentPacket, TRUE);
+ free(t);
+}
+
+/**
+ * Flushes the current accumulated tracker content, if it's the first packet, then
+ * when can just return that the packet shall be passed, otherwise to have to refragment
+ * the accumulated current packet.
+ */
+
+PfChannelResult channelTracker_flushCurrent(ChannelStateTracker* t, BOOL first, BOOL last,
+ BOOL toBack)
+{
+ proxyData* pdata = NULL;
+ pServerContext* ps = NULL;
+ pServerStaticChannelContext* channel = NULL;
+ UINT32 flags = CHANNEL_FLAG_FIRST;
+ BOOL r = 0;
+ const char* direction = toBack ? "F->B" : "B->F";
+ const size_t currentPacketSize = channelTracker_getCurrentPacketSize(t);
+ wStream* currentPacket = channelTracker_getCurrentPacket(t);
+
+ WINPR_ASSERT(t);
+
+ WLog_VRB(TAG, "channelTracker_flushCurrent(%s): %s sz=%" PRIuz " first=%d last=%d",
+ t->channel->channel_name, direction, Stream_GetPosition(currentPacket), first, last);
+
+ if (first)
+ return PF_CHANNEL_RESULT_PASS;
+
+ pdata = t->pdata;
+ channel = t->channel;
+ if (last)
+ flags |= CHANNEL_FLAG_LAST;
+
+ if (toBack)
+ {
+ proxyChannelDataEventInfo ev;
+
+ ev.channel_id = channel->front_channel_id;
+ ev.channel_name = channel->channel_name;
+ ev.data = Stream_Buffer(currentPacket);
+ ev.data_len = Stream_GetPosition(currentPacket);
+ ev.flags = flags;
+ ev.total_size = currentPacketSize;
+
+ if (!pdata->pc->sendChannelData)
+ return PF_CHANNEL_RESULT_ERROR;
+
+ return pdata->pc->sendChannelData(pdata->pc, &ev) ? PF_CHANNEL_RESULT_DROP
+ : PF_CHANNEL_RESULT_ERROR;
+ }
+
+ ps = pdata->ps;
+ r = ps->context.peer->SendChannelPacket(ps->context.peer, channel->front_channel_id,
+ currentPacketSize, flags, Stream_Buffer(currentPacket),
+ Stream_GetPosition(currentPacket));
+
+ return r ? PF_CHANNEL_RESULT_DROP : PF_CHANNEL_RESULT_ERROR;
+}
+
+static PfChannelResult pf_channel_generic_back_data(proxyData* pdata,
+ const pServerStaticChannelContext* channel,
+ const BYTE* xdata, size_t xsize, UINT32 flags,
+ size_t totalSize)
+{
+ proxyChannelDataEventInfo ev = { 0 };
+
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ switch (channel->channelMode)
+ {
+ case PF_UTILS_CHANNEL_PASSTHROUGH:
+ ev.channel_id = channel->back_channel_id;
+ ev.channel_name = channel->channel_name;
+ ev.data = xdata;
+ ev.data_len = xsize;
+ ev.flags = flags;
+ ev.total_size = totalSize;
+
+ if (!pf_modules_run_filter(pdata->module, FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA,
+ pdata, &ev))
+ return PF_CHANNEL_RESULT_DROP; /* Silently drop */
+
+ return PF_CHANNEL_RESULT_PASS;
+
+ case PF_UTILS_CHANNEL_INTERCEPT:
+ /* TODO */
+ case PF_UTILS_CHANNEL_BLOCK:
+ default:
+ return PF_CHANNEL_RESULT_DROP;
+ }
+}
+
+static PfChannelResult pf_channel_generic_front_data(proxyData* pdata,
+ const pServerStaticChannelContext* channel,
+ const BYTE* xdata, size_t xsize, UINT32 flags,
+ size_t totalSize)
+{
+ proxyChannelDataEventInfo ev = { 0 };
+
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ switch (channel->channelMode)
+ {
+ case PF_UTILS_CHANNEL_PASSTHROUGH:
+ ev.channel_id = channel->front_channel_id;
+ ev.channel_name = channel->channel_name;
+ ev.data = xdata;
+ ev.data_len = xsize;
+ ev.flags = flags;
+ ev.total_size = totalSize;
+
+ if (!pf_modules_run_filter(pdata->module, FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA,
+ pdata, &ev))
+ return PF_CHANNEL_RESULT_DROP; /* Silently drop */
+
+ return PF_CHANNEL_RESULT_PASS;
+
+ case PF_UTILS_CHANNEL_INTERCEPT:
+ /* TODO */
+ case PF_UTILS_CHANNEL_BLOCK:
+ default:
+ return PF_CHANNEL_RESULT_DROP;
+ }
+}
+
+BOOL pf_channel_setup_generic(pServerStaticChannelContext* channel)
+{
+ channel->onBackData = pf_channel_generic_back_data;
+ channel->onFrontData = pf_channel_generic_front_data;
+ return TRUE;
+}
+
+BOOL channelTracker_setMode(ChannelStateTracker* tracker, ChannelTrackerMode mode)
+{
+ WINPR_ASSERT(tracker);
+ tracker->mode = mode;
+ return TRUE;
+}
+
+ChannelTrackerMode channelTracker_getMode(ChannelStateTracker* tracker)
+{
+ WINPR_ASSERT(tracker);
+ return tracker->mode;
+}
+
+BOOL channelTracker_setPData(ChannelStateTracker* tracker, proxyData* pdata)
+{
+ WINPR_ASSERT(tracker);
+ tracker->pdata = pdata;
+ return TRUE;
+}
+
+proxyData* channelTracker_getPData(ChannelStateTracker* tracker)
+{
+ WINPR_ASSERT(tracker);
+ return tracker->pdata;
+}
+
+wStream* channelTracker_getCurrentPacket(ChannelStateTracker* tracker)
+{
+ WINPR_ASSERT(tracker);
+ return tracker->currentPacket;
+}
+
+BOOL channelTracker_setCustomData(ChannelStateTracker* tracker, void* data)
+{
+ WINPR_ASSERT(tracker);
+ tracker->trackerData = data;
+ return TRUE;
+}
+
+void* channelTracker_getCustomData(ChannelStateTracker* tracker)
+{
+ WINPR_ASSERT(tracker);
+ return tracker->trackerData;
+}
+
+size_t channelTracker_getCurrentPacketSize(ChannelStateTracker* tracker)
+{
+ WINPR_ASSERT(tracker);
+ return tracker->currentPacketSize;
+}
+
+BOOL channelTracker_setCurrentPacketSize(ChannelStateTracker* tracker, size_t size)
+{
+ WINPR_ASSERT(tracker);
+ tracker->currentPacketSize = size;
+ return TRUE;
+}
diff --git a/server/proxy/pf_channel.h b/server/proxy/pf_channel.h
new file mode 100644
index 0000000..9e0da7e
--- /dev/null
+++ b/server/proxy/pf_channel.h
@@ -0,0 +1,64 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ *
+ *
+ * Copyright 2022 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.
+ */
+#ifndef SERVER_PROXY_PF_CHANNEL_H_
+#define SERVER_PROXY_PF_CHANNEL_H_
+
+#include <freerdp/server/proxy/proxy_context.h>
+
+/** @brief operating mode of a channel tracker */
+typedef enum
+{
+ CHANNEL_TRACKER_PEEK, /*!< inquiring content, accumulating packet fragments */
+ CHANNEL_TRACKER_PASS, /*!< pass all the fragments of the current packet */
+ CHANNEL_TRACKER_DROP /*!< drop all the fragments of the current packet */
+} ChannelTrackerMode;
+
+typedef struct _ChannelStateTracker ChannelStateTracker;
+typedef PfChannelResult (*ChannelTrackerPeekFn)(ChannelStateTracker* tracker, BOOL first,
+ BOOL lastPacket);
+
+void channelTracker_free(ChannelStateTracker* t);
+
+WINPR_ATTR_MALLOC(channelTracker_free, 1)
+ChannelStateTracker* channelTracker_new(pServerStaticChannelContext* channel,
+ ChannelTrackerPeekFn fn, void* data);
+
+BOOL channelTracker_setMode(ChannelStateTracker* tracker, ChannelTrackerMode mode);
+ChannelTrackerMode channelTracker_getMode(ChannelStateTracker* tracker);
+
+BOOL channelTracker_setPData(ChannelStateTracker* tracker, proxyData* pdata);
+proxyData* channelTracker_getPData(ChannelStateTracker* tracker);
+
+BOOL channelTracker_setCustomData(ChannelStateTracker* tracker, void* data);
+void* channelTracker_getCustomData(ChannelStateTracker* tracker);
+
+wStream* channelTracker_getCurrentPacket(ChannelStateTracker* tracker);
+
+size_t channelTracker_getCurrentPacketSize(ChannelStateTracker* tracker);
+BOOL channelTracker_setCurrentPacketSize(ChannelStateTracker* tracker, size_t size);
+
+PfChannelResult channelTracker_update(ChannelStateTracker* tracker, const BYTE* xdata, size_t xsize,
+ UINT32 flags, size_t totalSize);
+
+PfChannelResult channelTracker_flushCurrent(ChannelStateTracker* t, BOOL first, BOOL last,
+ BOOL toFront);
+
+BOOL pf_channel_setup_generic(pServerStaticChannelContext* channel);
+
+#endif /* SERVER_PROXY_PF_CHANNEL_H_ */
diff --git a/server/proxy/pf_client.c b/server/proxy/pf_client.c
new file mode 100644
index 0000000..2f34f97
--- /dev/null
+++ b/server/proxy/pf_client.c
@@ -0,0 +1,1102 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@gmail.com>
+ * Copyright 2021 Armin Novak <anovak@thincast.com>
+ * Copyright 2021 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/client/cmdline.h>
+
+#include <freerdp/server/proxy/proxy_log.h>
+#include <freerdp/channels/drdynvc.h>
+#include <freerdp/channels/encomsp.h>
+#include <freerdp/channels/rdpdr.h>
+#include <freerdp/channels/rdpsnd.h>
+#include <freerdp/channels/cliprdr.h>
+#include <freerdp/channels/channels.h>
+
+#include "pf_client.h"
+#include "pf_channel.h"
+#include <freerdp/server/proxy/proxy_context.h>
+#include "pf_update.h"
+#include "pf_input.h"
+#include <freerdp/server/proxy/proxy_config.h>
+#include "proxy_modules.h"
+#include "pf_utils.h"
+#include "channels/pf_channel_rdpdr.h"
+#include "channels/pf_channel_smartcard.h"
+
+#define TAG PROXY_TAG("client")
+
+static void channel_data_free(void* obj);
+static BOOL proxy_server_reactivate(rdpContext* ps, const rdpContext* pc)
+{
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(pc);
+
+ if (!pf_context_copy_settings(ps->settings, pc->settings))
+ return FALSE;
+
+ /*
+ * DesktopResize causes internal function rdp_server_reactivate to be called,
+ * which causes the reactivation.
+ */
+ WINPR_ASSERT(ps->update);
+ if (!ps->update->DesktopResize(ps))
+ return FALSE;
+
+ return TRUE;
+}
+
+static void pf_client_on_error_info(void* ctx, const ErrorInfoEventArgs* e)
+{
+ pClientContext* pc = (pClientContext*)ctx;
+ pServerContext* ps = NULL;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ WINPR_ASSERT(e);
+ ps = pc->pdata->ps;
+ WINPR_ASSERT(ps);
+
+ if (e->code == ERRINFO_NONE)
+ return;
+
+ PROXY_LOG_WARN(TAG, pc, "received ErrorInfo PDU. code=0x%08" PRIu32 ", message: %s", e->code,
+ freerdp_get_error_info_string(e->code));
+
+ /* forward error back to client */
+ freerdp_set_error_info(ps->context.rdp, e->code);
+ freerdp_send_error_info(ps->context.rdp);
+}
+
+static void pf_client_on_activated(void* ctx, const ActivatedEventArgs* e)
+{
+ pClientContext* pc = (pClientContext*)ctx;
+ pServerContext* ps = NULL;
+ freerdp_peer* peer = NULL;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ WINPR_ASSERT(e);
+
+ ps = pc->pdata->ps;
+ WINPR_ASSERT(ps);
+ peer = ps->context.peer;
+ WINPR_ASSERT(peer);
+ WINPR_ASSERT(peer->context);
+
+ PROXY_LOG_INFO(TAG, pc, "client activated, registering server input callbacks");
+
+ /* Register server input/update callbacks only after proxy client is fully activated */
+ pf_server_register_input_callbacks(peer->context->input);
+ pf_server_register_update_callbacks(peer->context->update);
+}
+
+static BOOL pf_client_load_rdpsnd(pClientContext* pc)
+{
+ rdpContext* context = (rdpContext*)pc;
+ pServerContext* ps = NULL;
+ const proxyConfig* config = NULL;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ ps = pc->pdata->ps;
+ WINPR_ASSERT(ps);
+ config = pc->pdata->config;
+ WINPR_ASSERT(config);
+
+ /*
+ * if AudioOutput is enabled in proxy and client connected with rdpsnd, use proxy as rdpsnd
+ * backend. Otherwise, use sys:fake.
+ */
+ if (!freerdp_static_channel_collection_find(context->settings, RDPSND_CHANNEL_NAME))
+ {
+ const char* params[2] = { RDPSND_CHANNEL_NAME, "sys:fake" };
+
+ if (!freerdp_client_add_static_channel(context->settings, ARRAYSIZE(params), params))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL pf_client_use_peer_load_balance_info(pClientContext* pc)
+{
+ pServerContext* ps = NULL;
+ rdpSettings* settings = NULL;
+ DWORD lb_info_len = 0;
+ const char* lb_info = NULL;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ ps = pc->pdata->ps;
+ WINPR_ASSERT(ps);
+ settings = pc->context.settings;
+ WINPR_ASSERT(settings);
+
+ lb_info = freerdp_nego_get_routing_token(&ps->context, &lb_info_len);
+ if (!lb_info)
+ return TRUE;
+
+ return freerdp_settings_set_pointer_len(settings, FreeRDP_LoadBalanceInfo, lb_info,
+ lb_info_len);
+}
+
+static BOOL str_is_empty(const char* str)
+{
+ if (!str)
+ return TRUE;
+ if (strlen(str) == 0)
+ return TRUE;
+ return FALSE;
+}
+
+static BOOL pf_client_use_proxy_smartcard_auth(const rdpSettings* settings)
+{
+ BOOL enable = freerdp_settings_get_bool(settings, FreeRDP_SmartcardLogon);
+ const char* key = freerdp_settings_get_string(settings, FreeRDP_SmartcardPrivateKey);
+ const char* cert = freerdp_settings_get_string(settings, FreeRDP_SmartcardCertificate);
+
+ if (!enable)
+ return FALSE;
+
+ if (str_is_empty(key))
+ return FALSE;
+
+ if (str_is_empty(cert))
+ return FALSE;
+
+ return TRUE;
+}
+
+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_INFO(TAG, "loading channelEx %s", name);
+ return TRUE;
+ }
+ }
+ else if (entry)
+ {
+ if (freerdp_channels_client_load(channels, settings, entry, data) == 0)
+ {
+ WLog_INFO(TAG, "loading channel %s", name);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static BOOL pf_client_pre_connect(freerdp* instance)
+{
+ pClientContext* pc = NULL;
+ pServerContext* ps = NULL;
+ const proxyConfig* config = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(instance);
+ pc = (pClientContext*)instance->context;
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ ps = pc->pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->pdata);
+ config = ps->pdata->config;
+ WINPR_ASSERT(config);
+ settings = instance->context->settings;
+ WINPR_ASSERT(settings);
+
+ /*
+ * as the client's settings are copied from the server's, GlyphSupportLevel might not be
+ * GLYPH_SUPPORT_NONE. the proxy currently do not support GDI & GLYPH_SUPPORT_CACHE, so
+ * GlyphCacheSupport must be explicitly set to GLYPH_SUPPORT_NONE.
+ *
+ * Also, OrderSupport need to be zeroed, because it is currently not supported.
+ */
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_GlyphSupportLevel, GLYPH_SUPPORT_NONE))
+ return FALSE;
+
+ void* OrderSupport = freerdp_settings_get_pointer_writable(settings, FreeRDP_OrderSupport);
+ ZeroMemory(OrderSupport, 32);
+
+ if (WTSVirtualChannelManagerIsChannelJoined(ps->vcm, DRDYNVC_SVC_CHANNEL_NAME))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportDynamicChannels, TRUE))
+ return FALSE;
+ }
+
+ /* Multimon */
+ if (!freerdp_settings_set_bool(settings, FreeRDP_UseMultimon, TRUE))
+ return FALSE;
+
+ /* Sound */
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AudioCapture, config->AudioInput) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_AudioPlayback, config->AudioOutput) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection,
+ config->DeviceRedirection) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_SupportDisplayControl,
+ config->DisplayControl) ||
+ !freerdp_settings_set_bool(settings, FreeRDP_MultiTouchInput, config->Multitouch))
+ return FALSE;
+
+ if (config->RemoteApp)
+ {
+ if (WTSVirtualChannelManagerIsChannelJoined(ps->vcm, RAIL_SVC_CHANNEL_NAME))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteApplicationMode, TRUE))
+ return FALSE;
+ }
+ }
+
+ if (config->DeviceRedirection)
+ {
+ if (WTSVirtualChannelManagerIsChannelJoined(ps->vcm, RDPDR_SVC_CHANNEL_NAME))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
+ return FALSE;
+ }
+ }
+
+ /* Display control */
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportDisplayControl, config->DisplayControl))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DynamicResolutionUpdate,
+ config->DisplayControl))
+ return FALSE;
+
+ if (WTSVirtualChannelManagerIsChannelJoined(ps->vcm, ENCOMSP_SVC_CHANNEL_NAME))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_EncomspVirtualChannel, TRUE))
+ return FALSE;
+ }
+
+ if (config->Clipboard)
+ {
+ if (WTSVirtualChannelManagerIsChannelJoined(ps->vcm, CLIPRDR_SVC_CHANNEL_NAME))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectClipboard, config->Clipboard))
+ return FALSE;
+ }
+ }
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_AutoReconnectionEnabled, TRUE))
+ return FALSE;
+
+ PubSub_SubscribeErrorInfo(instance->context->pubSub, pf_client_on_error_info);
+ PubSub_SubscribeActivated(instance->context->pubSub, pf_client_on_activated);
+ if (!pf_client_use_peer_load_balance_info(pc))
+ return FALSE;
+
+ return pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_PRE_CONNECT, pc->pdata, pc);
+}
+
+/** @brief arguments for updateBackIdFn */
+typedef struct
+{
+ pServerContext* ps;
+ const char* name;
+ UINT32 backId;
+} UpdateBackIdArgs;
+
+static BOOL updateBackIdFn(const void* key, void* value, void* arg)
+{
+ pServerStaticChannelContext* current = (pServerStaticChannelContext*)value;
+ UpdateBackIdArgs* updateArgs = (UpdateBackIdArgs*)arg;
+
+ if (strcmp(updateArgs->name, current->channel_name) != 0)
+ return TRUE;
+
+ current->back_channel_id = updateArgs->backId;
+ if (!HashTable_Insert(updateArgs->ps->channelsByBackId, &current->back_channel_id, current))
+ {
+ WLog_ERR(TAG, "error inserting channel in channelsByBackId table");
+ }
+ return FALSE;
+}
+
+static BOOL pf_client_update_back_id(pServerContext* ps, const char* name, UINT32 backId)
+{
+ UpdateBackIdArgs res = { ps, name, backId };
+
+ return HashTable_Foreach(ps->channelsByFrontId, updateBackIdFn, &res) == FALSE;
+}
+
+static BOOL pf_client_load_channels(freerdp* instance)
+{
+ pClientContext* pc = NULL;
+ pServerContext* ps = NULL;
+ const proxyConfig* config = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(instance);
+ pc = (pClientContext*)instance->context;
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ ps = pc->pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->pdata);
+ config = ps->pdata->config;
+ WINPR_ASSERT(config);
+ settings = instance->context->settings;
+ WINPR_ASSERT(settings);
+ /**
+ * Load all required plugins / channels / libraries specified by current
+ * settings.
+ */
+ PROXY_LOG_INFO(TAG, pc, "Loading addins");
+
+ if (!pf_client_load_rdpsnd(pc))
+ {
+ PROXY_LOG_ERR(TAG, pc, "Failed to load rdpsnd client");
+ return FALSE;
+ }
+
+ if (!pf_utils_is_passthrough(config))
+ {
+ if (!freerdp_client_load_addins(instance->context->channels, settings))
+ {
+ PROXY_LOG_ERR(TAG, pc, "Failed to load addins");
+ return FALSE;
+ }
+ }
+ else
+ {
+ if (!pf_channel_rdpdr_client_new(pc))
+ return FALSE;
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+ if (!pf_channel_smartcard_client_new(pc))
+ return FALSE;
+#endif
+ /* Copy the current channel settings from the peer connection to the client. */
+ if (!freerdp_channels_from_mcs(settings, &ps->context))
+ return FALSE;
+
+ /* Filter out channels we do not want */
+ {
+ CHANNEL_DEF* channels = (CHANNEL_DEF*)freerdp_settings_get_pointer_array_writable(
+ settings, FreeRDP_ChannelDefArray, 0);
+ size_t size = freerdp_settings_get_uint32(settings, FreeRDP_ChannelCount);
+ UINT32 id = MCS_GLOBAL_CHANNEL_ID + 1;
+
+ WINPR_ASSERT(channels || (size == 0));
+
+ size_t x = 0;
+ for (; x < size;)
+ {
+ CHANNEL_DEF* cur = &channels[x];
+ proxyChannelDataEventInfo dev = { 0 };
+
+ dev.channel_name = cur->name;
+ dev.flags = cur->options;
+
+ /* Filter out channels blocked by config */
+ if (!pf_modules_run_filter(pc->pdata->module,
+ FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_CREATE, pc->pdata,
+ &dev))
+ {
+ const size_t s = size - MIN(size, x + 1);
+ memmove(cur, &cur[1], sizeof(CHANNEL_DEF) * s);
+ size--;
+ }
+ else
+ {
+ if (!pf_client_update_back_id(ps, cur->name, id++))
+ {
+ WLog_ERR(TAG, "unable to update backid for channel %s", cur->name);
+ return FALSE;
+ }
+ x++;
+ }
+ }
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ChannelCount, x))
+ return FALSE;
+ }
+ }
+ return pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_LOAD_CHANNELS, pc->pdata, pc);
+}
+
+static BOOL pf_client_receive_channel_data_hook(freerdp* instance, UINT16 channelId,
+ const BYTE* xdata, size_t xsize, UINT32 flags,
+ size_t totalSize)
+{
+ pClientContext* pc = NULL;
+ pServerContext* ps = NULL;
+ proxyData* pdata = NULL;
+ pServerStaticChannelContext* channel = NULL;
+ UINT64 channelId64 = channelId;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(xdata || (xsize == 0));
+
+ pc = (pClientContext*)instance->context;
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+
+ ps = pc->pdata->ps;
+ WINPR_ASSERT(ps);
+
+ pdata = ps->pdata;
+ WINPR_ASSERT(pdata);
+
+ channel = HashTable_GetItemValue(ps->channelsByBackId, &channelId64);
+ if (!channel)
+ return TRUE;
+
+ WINPR_ASSERT(channel->onBackData);
+ switch (channel->onBackData(pdata, channel, xdata, xsize, flags, totalSize))
+ {
+ case PF_CHANNEL_RESULT_PASS:
+ /* Ignore messages for channels that can not be mapped.
+ * The client might not have enabled support for this specific channel,
+ * so just drop the message. */
+ if (channel->front_channel_id == 0)
+ return TRUE;
+
+ return ps->context.peer->SendChannelPacket(ps->context.peer, channel->front_channel_id,
+ totalSize, flags, xdata, xsize);
+ case PF_CHANNEL_RESULT_DROP:
+ return TRUE;
+ case PF_CHANNEL_RESULT_ERROR:
+ default:
+ return FALSE;
+ }
+}
+
+static BOOL pf_client_on_server_heartbeat(freerdp* instance, BYTE period, BYTE count1, BYTE count2)
+{
+ pClientContext* pc = NULL;
+ pServerContext* ps = NULL;
+
+ WINPR_ASSERT(instance);
+ pc = (pClientContext*)instance->context;
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ ps = pc->pdata->ps;
+ WINPR_ASSERT(ps);
+
+ return freerdp_heartbeat_send_heartbeat_pdu(ps->context.peer, period, count1, count2);
+}
+
+static BOOL pf_client_send_channel_data(pClientContext* pc, const proxyChannelDataEventInfo* ev)
+{
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(ev);
+
+ return Queue_Enqueue(pc->cached_server_channel_data, ev);
+}
+
+static BOOL sendQueuedChannelData(pClientContext* pc)
+{
+ BOOL rc = TRUE;
+
+ WINPR_ASSERT(pc);
+
+ if (pc->connected)
+ {
+ proxyChannelDataEventInfo* ev = NULL;
+
+ Queue_Lock(pc->cached_server_channel_data);
+ while (rc && (ev = Queue_Dequeue(pc->cached_server_channel_data)))
+ {
+ UINT16 channelId = 0;
+ WINPR_ASSERT(pc->context.instance);
+
+ channelId = freerdp_channels_get_id_by_name(pc->context.instance, ev->channel_name);
+ /* Ignore unmappable channels */
+ if ((channelId == 0) || (channelId == UINT16_MAX))
+ rc = TRUE;
+ else
+ {
+ WINPR_ASSERT(pc->context.instance->SendChannelPacket);
+ rc = pc->context.instance->SendChannelPacket(pc->context.instance, channelId,
+ ev->total_size, ev->flags, ev->data,
+ ev->data_len);
+ }
+ channel_data_free(ev);
+ }
+
+ Queue_Unlock(pc->cached_server_channel_data);
+ }
+
+ return rc;
+}
+
+/**
+ * Called after a RDP connection was successfully established.
+ * Settings might have changed during negotiation of client / server feature
+ * support.
+ *
+ * Set up local framebuffers and painting callbacks.
+ * If required, register pointer callbacks to change the local mouse cursor
+ * when hovering over the RDP window
+ */
+static BOOL pf_client_post_connect(freerdp* instance)
+{
+ rdpContext* context = NULL;
+ rdpSettings* settings = NULL;
+ rdpUpdate* update = NULL;
+ rdpContext* ps = NULL;
+ pClientContext* pc = NULL;
+ const proxyConfig* config = NULL;
+
+ WINPR_ASSERT(instance);
+ context = instance->context;
+ WINPR_ASSERT(context);
+ settings = context->settings;
+ WINPR_ASSERT(settings);
+ update = context->update;
+ WINPR_ASSERT(update);
+ pc = (pClientContext*)context;
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ ps = (rdpContext*)pc->pdata->ps;
+ WINPR_ASSERT(ps);
+ config = pc->pdata->config;
+ WINPR_ASSERT(config);
+
+ if (!pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_POST_CONNECT, pc->pdata, pc))
+ return FALSE;
+
+ if (!gdi_init(instance, PIXEL_FORMAT_BGRA32))
+ return FALSE;
+
+ WINPR_ASSERT(freerdp_settings_get_bool(settings, FreeRDP_SoftwareGdi));
+
+ pf_client_register_update_callbacks(update);
+
+ /* virtual channels receive data hook */
+ pc->client_receive_channel_data_original = instance->ReceiveChannelData;
+ instance->ReceiveChannelData = pf_client_receive_channel_data_hook;
+
+ instance->heartbeat->ServerHeartbeat = pf_client_on_server_heartbeat;
+
+ pc->connected = TRUE;
+
+ /* Send cached channel data */
+ sendQueuedChannelData(pc);
+
+ /*
+ * after the connection fully established and settings were negotiated with target server,
+ * send a reactivation sequence to the client with the negotiated settings. This way,
+ * settings are synchorinized between proxy's peer and and remote target.
+ */
+ return proxy_server_reactivate(ps, context);
+}
+
+/* This function is called whether a session ends by failure or success.
+ * Clean up everything allocated by pre_connect and post_connect.
+ */
+static void pf_client_post_disconnect(freerdp* instance)
+{
+ pClientContext* pc = NULL;
+ proxyData* pdata = NULL;
+
+ if (!instance)
+ return;
+
+ if (!instance->context)
+ return;
+
+ pc = (pClientContext*)instance->context;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+ pf_channel_smartcard_client_free(pc);
+#endif
+
+ pf_channel_rdpdr_client_free(pc);
+
+ pc->connected = FALSE;
+ pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_POST_DISCONNECT, pc->pdata, pc);
+
+ PubSub_UnsubscribeErrorInfo(instance->context->pubSub, pf_client_on_error_info);
+ gdi_free(instance);
+
+ /* Only close the connection if NLA fallback process is done */
+ if (!pc->allow_next_conn_failure)
+ proxy_data_abort_connect(pdata);
+}
+
+static BOOL pf_client_redirect(freerdp* instance)
+{
+ pClientContext* pc = NULL;
+ proxyData* pdata = NULL;
+
+ if (!instance)
+ return FALSE;
+
+ if (!instance->context)
+ return FALSE;
+
+ pc = (pClientContext*)instance->context;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+
+#if defined(WITH_PROXY_EMULATE_SMARTCARD)
+ pf_channel_smartcard_client_reset(pc);
+#endif
+ pf_channel_rdpdr_client_reset(pc);
+
+ return pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_REDIRECT, pc->pdata, pc);
+}
+
+/*
+ * pf_client_should_retry_without_nla:
+ *
+ * returns TRUE if in case of connection failure, the client should try again without NLA.
+ * Otherwise, returns FALSE.
+ */
+static BOOL pf_client_should_retry_without_nla(pClientContext* pc)
+{
+ rdpSettings* settings = NULL;
+ const proxyConfig* config = NULL;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ settings = pc->context.settings;
+ WINPR_ASSERT(settings);
+ config = pc->pdata->config;
+ WINPR_ASSERT(config);
+
+ if (!config->ClientAllowFallbackToTls ||
+ !freerdp_settings_get_bool(settings, FreeRDP_NlaSecurity))
+ return FALSE;
+
+ return config->ClientTlsSecurity || config->ClientRdpSecurity;
+}
+
+static void pf_client_set_security_settings(pClientContext* pc)
+{
+ rdpSettings* settings = NULL;
+ const proxyConfig* config = NULL;
+
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->pdata);
+ settings = pc->context.settings;
+ WINPR_ASSERT(settings);
+ config = pc->pdata->config;
+ WINPR_ASSERT(config);
+
+ freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, config->ClientRdpSecurity);
+ freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, config->ClientTlsSecurity);
+ freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, config->ClientNlaSecurity);
+
+ if (pf_client_use_proxy_smartcard_auth(settings))
+ {
+ /* Smartcard authentication requires smartcard redirection to be enabled */
+ freerdp_settings_set_bool(settings, FreeRDP_RedirectSmartCards, TRUE);
+
+ /* Reset username/domain, we will get that info later from the sc cert */
+ freerdp_settings_set_string(settings, FreeRDP_Username, NULL);
+ freerdp_settings_set_string(settings, FreeRDP_Domain, NULL);
+ }
+}
+
+static BOOL pf_client_connect_without_nla(pClientContext* pc)
+{
+ freerdp* instance = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(pc);
+ instance = pc->context.instance;
+ WINPR_ASSERT(instance);
+
+ if (!freerdp_context_reset(instance))
+ return FALSE;
+
+ settings = pc->context.settings;
+ WINPR_ASSERT(settings);
+
+ /* If already disabled abort early. */
+ if (!freerdp_settings_get_bool(settings, FreeRDP_NlaSecurity))
+ return FALSE;
+
+ /* disable NLA */
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE))
+ return FALSE;
+
+ /* do not allow next connection failure */
+ pc->allow_next_conn_failure = FALSE;
+ return freerdp_connect(instance);
+}
+
+static BOOL pf_client_connect(freerdp* instance)
+{
+ pClientContext* pc = NULL;
+ rdpSettings* settings = NULL;
+ BOOL rc = FALSE;
+ BOOL retry = FALSE;
+
+ WINPR_ASSERT(instance);
+ pc = (pClientContext*)instance->context;
+ WINPR_ASSERT(pc);
+ settings = instance->context->settings;
+ WINPR_ASSERT(settings);
+
+ PROXY_LOG_INFO(TAG, pc, "connecting using client info: Username: %s, Domain: %s",
+ freerdp_settings_get_string(settings, FreeRDP_Username),
+ freerdp_settings_get_string(settings, FreeRDP_Domain));
+
+ pf_client_set_security_settings(pc);
+ if (pf_client_should_retry_without_nla(pc))
+ retry = pc->allow_next_conn_failure = TRUE;
+
+ PROXY_LOG_INFO(TAG, pc, "connecting using security settings: rdp=%d, tls=%d, nla=%d",
+ freerdp_settings_get_bool(settings, FreeRDP_RdpSecurity),
+ freerdp_settings_get_bool(settings, FreeRDP_TlsSecurity),
+ freerdp_settings_get_bool(settings, FreeRDP_NlaSecurity));
+
+ if (!freerdp_connect(instance))
+ {
+ if (!pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_LOGIN_FAILURE, pc->pdata, pc))
+ goto out;
+
+ if (!retry)
+ goto out;
+
+ PROXY_LOG_ERR(TAG, pc, "failed to connect with NLA. retrying to connect without NLA");
+ if (!pf_client_connect_without_nla(pc))
+ {
+ PROXY_LOG_ERR(TAG, pc, "pf_client_connect_without_nla failed!");
+ goto out;
+ }
+ }
+
+ rc = TRUE;
+out:
+ pc->allow_next_conn_failure = FALSE;
+ return rc;
+}
+
+/**
+ * RDP main loop.
+ * Connects RDP, loops while running and handles event and dispatch, cleans up
+ * after the connection ends.
+ */
+static DWORD WINAPI pf_client_thread_proc(pClientContext* pc)
+{
+ freerdp* instance = NULL;
+ proxyData* pdata = NULL;
+ DWORD nCount = 0;
+ DWORD status = 0;
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+
+ WINPR_ASSERT(pc);
+
+ instance = pc->context.instance;
+ WINPR_ASSERT(instance);
+
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ /*
+ * during redirection, freerdp's abort event might be overriden (reset) by the library, after
+ * the server set it in order to shutdown the connection. it means that the server might signal
+ * the client to abort, but the library code will override the signal and the client will
+ * continue its work instead of exiting. That's why the client must wait on `pdata->abort_event`
+ * too, which will never be modified by the library.
+ */
+ handles[nCount++] = pdata->abort_event;
+
+ if (!pf_modules_run_hook(pdata->module, HOOK_TYPE_CLIENT_INIT_CONNECT, pdata, pc))
+ {
+ proxy_data_abort_connect(pdata);
+ goto end;
+ }
+
+ if (!pf_client_connect(instance))
+ {
+ proxy_data_abort_connect(pdata);
+ goto end;
+ }
+ handles[nCount++] = Queue_Event(pc->cached_server_channel_data);
+
+ while (!freerdp_shall_disconnect_context(instance->context))
+ {
+ UINT32 tmp = freerdp_get_event_handles(instance->context, &handles[nCount],
+ ARRAYSIZE(handles) - nCount);
+
+ if (tmp == 0)
+ {
+ PROXY_LOG_ERR(TAG, pc, "freerdp_get_event_handles failed!");
+ break;
+ }
+
+ status = WaitForMultipleObjects(nCount + tmp, handles, FALSE, INFINITE);
+
+ if (status == WAIT_FAILED)
+ {
+ WLog_ERR(TAG, "WaitForMultipleObjects failed with %" PRIu32 "", status);
+ break;
+ }
+
+ /* abort_event triggered */
+ if (status == WAIT_OBJECT_0)
+ break;
+
+ if (freerdp_shall_disconnect_context(instance->context))
+ break;
+
+ if (proxy_data_shall_disconnect(pdata))
+ break;
+
+ if (!freerdp_check_event_handles(instance->context))
+ {
+ if (freerdp_get_last_error(instance->context) == FREERDP_ERROR_SUCCESS)
+ WLog_ERR(TAG, "Failed to check FreeRDP event handles");
+
+ break;
+ }
+ sendQueuedChannelData(pc);
+ }
+
+ freerdp_disconnect(instance);
+
+end:
+ pf_modules_run_hook(pdata->module, HOOK_TYPE_CLIENT_UNINIT_CONNECT, pdata, pc);
+
+ return 0;
+}
+
+static int pf_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 void pf_client_context_free(freerdp* instance, rdpContext* context)
+{
+ pClientContext* pc = (pClientContext*)context;
+ WINPR_UNUSED(instance);
+
+ if (!pc)
+ return;
+
+ pc->sendChannelData = NULL;
+ Queue_Free(pc->cached_server_channel_data);
+ Stream_Free(pc->remote_pem, TRUE);
+ free(pc->remote_hostname);
+ free(pc->computerName.v);
+ HashTable_Free(pc->interceptContextMap);
+}
+
+static int pf_client_verify_X509_certificate(freerdp* instance, const BYTE* data, size_t length,
+ const char* hostname, UINT16 port, DWORD flags)
+{
+ pClientContext* pc = NULL;
+
+ WINPR_ASSERT(instance);
+ WINPR_ASSERT(data);
+ WINPR_ASSERT(length > 0);
+ WINPR_ASSERT(hostname);
+
+ pc = (pClientContext*)instance->context;
+ WINPR_ASSERT(pc);
+
+ if (!Stream_EnsureCapacity(pc->remote_pem, length))
+ return 0;
+ Stream_SetPosition(pc->remote_pem, 0);
+
+ free(pc->remote_hostname);
+ pc->remote_hostname = NULL;
+
+ if (length > 0)
+ Stream_Write(pc->remote_pem, data, length);
+
+ if (hostname)
+ pc->remote_hostname = _strdup(hostname);
+ pc->remote_port = port;
+ pc->remote_flags = flags;
+
+ Stream_SealLength(pc->remote_pem);
+ if (!pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_VERIFY_X509, pc->pdata, pc))
+ return 0;
+ return 1;
+}
+
+void channel_data_free(void* obj)
+{
+ union
+ {
+ const void* cpv;
+ void* pv;
+ } cnv;
+ proxyChannelDataEventInfo* dst = obj;
+ if (dst)
+ {
+ cnv.cpv = dst->data;
+ free(cnv.pv);
+
+ cnv.cpv = dst->channel_name;
+ free(cnv.pv);
+ free(dst);
+ }
+}
+
+static void* channel_data_copy(const void* obj)
+{
+ union
+ {
+ const void* cpv;
+ void* pv;
+ } cnv;
+ const proxyChannelDataEventInfo* src = obj;
+ proxyChannelDataEventInfo* dst = NULL;
+
+ WINPR_ASSERT(src);
+
+ dst = calloc(1, sizeof(proxyChannelDataEventInfo));
+ if (!dst)
+ goto fail;
+
+ *dst = *src;
+ if (src->channel_name)
+ {
+ dst->channel_name = _strdup(src->channel_name);
+ if (!dst->channel_name)
+ goto fail;
+ }
+ dst->data = malloc(src->data_len);
+ if (!dst->data)
+ goto fail;
+
+ cnv.cpv = dst->data;
+ memcpy(cnv.pv, src->data, src->data_len);
+ return dst;
+
+fail:
+ channel_data_free(dst);
+ return NULL;
+}
+
+static BOOL pf_client_client_new(freerdp* instance, rdpContext* context)
+{
+ wObject* obj = NULL;
+ pClientContext* pc = (pClientContext*)context;
+
+ if (!instance || !context)
+ return FALSE;
+
+ instance->LoadChannels = pf_client_load_channels;
+ instance->PreConnect = pf_client_pre_connect;
+ instance->PostConnect = pf_client_post_connect;
+ instance->PostDisconnect = pf_client_post_disconnect;
+ instance->Redirect = pf_client_redirect;
+ instance->LogonErrorInfo = pf_logon_error_info;
+ instance->VerifyX509Certificate = pf_client_verify_X509_certificate;
+
+ pc->remote_pem = Stream_New(NULL, 4096);
+ if (!pc->remote_pem)
+ return FALSE;
+
+ pc->sendChannelData = pf_client_send_channel_data;
+ pc->cached_server_channel_data = Queue_New(TRUE, -1, -1);
+ if (!pc->cached_server_channel_data)
+ return FALSE;
+ obj = Queue_Object(pc->cached_server_channel_data);
+ WINPR_ASSERT(obj);
+ obj->fnObjectNew = channel_data_copy;
+ obj->fnObjectFree = channel_data_free;
+
+ pc->interceptContextMap = HashTable_New(FALSE);
+ if (!pc->interceptContextMap)
+ return FALSE;
+
+ if (!HashTable_SetupForStringData(pc->interceptContextMap, FALSE))
+ return FALSE;
+
+ obj = HashTable_ValueObject(pc->interceptContextMap);
+ WINPR_ASSERT(obj);
+ obj->fnObjectFree = intercept_context_entry_free;
+
+ return TRUE;
+}
+
+static int pf_client_client_stop(rdpContext* context)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+
+ PROXY_LOG_DBG(TAG, pc, "aborting client connection");
+ proxy_data_abort_connect(pdata);
+ freerdp_abort_connect_context(context);
+
+ return 0;
+}
+
+int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints)
+{
+ WINPR_ASSERT(pEntryPoints);
+
+ ZeroMemory(pEntryPoints, sizeof(RDP_CLIENT_ENTRY_POINTS));
+ pEntryPoints->Version = RDP_CLIENT_INTERFACE_VERSION;
+ pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1);
+ pEntryPoints->ContextSize = sizeof(pClientContext);
+ /* Client init and finish */
+ pEntryPoints->ClientNew = pf_client_client_new;
+ pEntryPoints->ClientFree = pf_client_context_free;
+ pEntryPoints->ClientStop = pf_client_client_stop;
+ return 0;
+}
+
+/**
+ * Starts running a client connection towards target server.
+ */
+DWORD WINAPI pf_client_start(LPVOID arg)
+{
+ DWORD rc = 1;
+ pClientContext* pc = (pClientContext*)arg;
+
+ WINPR_ASSERT(pc);
+ if (freerdp_client_start(&pc->context) == 0)
+ rc = pf_client_thread_proc(pc);
+ freerdp_client_stop(&pc->context);
+ return rc;
+}
diff --git a/server/proxy/pf_client.h b/server/proxy/pf_client.h
new file mode 100644
index 0000000..cda87a8
--- /dev/null
+++ b/server/proxy/pf_client.h
@@ -0,0 +1,31 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@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.
+ */
+
+#ifndef FREERDP_SERVER_PROXY_PFCLIENT_H
+#define FREERDP_SERVER_PROXY_PFCLIENT_H
+
+#include <freerdp/freerdp.h>
+#include <winpr/wtypes.h>
+
+int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints);
+DWORD WINAPI pf_client_start(LPVOID arg);
+
+#endif /* FREERDP_SERVER_PROXY_PFCLIENT_H */
diff --git a/server/proxy/pf_config.c b/server/proxy/pf_config.c
new file mode 100644
index 0000000..bcce1b1
--- /dev/null
+++ b/server/proxy/pf_config.c
@@ -0,0 +1,1348 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@gmail.com>
+ * Copyright 2021,2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2021,2023 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.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/collections.h>
+#include <winpr/cmdline.h>
+
+#include "pf_server.h"
+#include <freerdp/server/proxy/proxy_config.h>
+
+#include <freerdp/server/proxy/proxy_log.h>
+
+#include <freerdp/crypto/crypto.h>
+#include <freerdp/channels/cliprdr.h>
+#include <freerdp/channels/rdpsnd.h>
+#include <freerdp/channels/audin.h>
+#include <freerdp/channels/rdpdr.h>
+#include <freerdp/channels/disp.h>
+#include <freerdp/channels/rail.h>
+#include <freerdp/channels/rdpei.h>
+#include <freerdp/channels/tsmf.h>
+#include <freerdp/channels/video.h>
+#include <freerdp/channels/rdpecam.h>
+
+#include "pf_utils.h"
+
+#define TAG PROXY_TAG("config")
+
+#define CONFIG_PRINT_SECTION(section) WLog_INFO(TAG, "\t%s:", section)
+#define CONFIG_PRINT_SECTION_KEY(section, key) WLog_INFO(TAG, "\t%s/%s:", section, key)
+#define CONFIG_PRINT_STR(config, key) WLog_INFO(TAG, "\t\t%s: %s", #key, config->key)
+#define CONFIG_PRINT_STR_CONTENT(config, key) \
+ WLog_INFO(TAG, "\t\t%s: %s", #key, config->key ? "set" : NULL)
+#define CONFIG_PRINT_BOOL(config, key) WLog_INFO(TAG, "\t\t%s: %s", #key, boolstr(config->key))
+#define CONFIG_PRINT_UINT16(config, key) WLog_INFO(TAG, "\t\t%s: %" PRIu16 "", #key, config->key)
+#define CONFIG_PRINT_UINT32(config, key) WLog_INFO(TAG, "\t\t%s: %" PRIu32 "", #key, config->key)
+
+static const char* bool_str_true = "true";
+static const char* bool_str_false = "false";
+static const char* boolstr(BOOL rc)
+{
+ return rc ? bool_str_true : bool_str_false;
+}
+
+static const char* section_server = "Server";
+static const char* key_host = "Host";
+static const char* key_port = "Port";
+
+static const char* section_target = "Target";
+static const char* key_target_fixed = "FixedTarget";
+static const char* key_target_user = "User";
+static const char* key_target_pwd = "Password";
+static const char* key_target_domain = "Domain";
+static const char* key_target_tls_seclevel = "TlsSecLevel";
+
+static const char* section_clipboard = "Clipboard";
+static const char* key_clip_text_only = "TextOnly";
+static const char* key_clip_text_max_len = "MaxTextLength";
+
+static const char* section_gfx_settings = "GFXSettings";
+static const char* key_gfx_decode = "DecodeGFX";
+
+static const char* section_plugins = "Plugins";
+static const char* key_plugins_modules = "Modules";
+static const char* key_plugins_required = "Required";
+
+static const char* section_channels = "Channels";
+static const char* key_channels_gfx = "GFX";
+static const char* key_channels_disp = "DisplayControl";
+static const char* key_channels_clip = "Clipboard";
+static const char* key_channels_mic = "AudioInput";
+static const char* key_channels_sound = "AudioOutput";
+static const char* key_channels_rdpdr = "DeviceRedirection";
+static const char* key_channels_video = "VideoRedirection";
+static const char* key_channels_camera = "CameraRedirection";
+static const char* key_channels_rails = "RemoteApp";
+static const char* key_channels_blacklist = "PassthroughIsBlacklist";
+static const char* key_channels_pass = "Passthrough";
+static const char* key_channels_intercept = "Intercept";
+
+static const char* section_input = "Input";
+static const char* key_input_kbd = "Keyboard";
+static const char* key_input_mouse = "Mouse";
+static const char* key_input_multitouch = "Multitouch";
+
+static const char* section_security = "Security";
+static const char* key_security_server_nla = "ServerNlaSecurity";
+static const char* key_security_server_tls = "ServerTlsSecurity";
+static const char* key_security_server_rdp = "ServerRdpSecurity";
+static const char* key_security_client_nla = "ClientNlaSecurity";
+static const char* key_security_client_tls = "ClientTlsSecurity";
+static const char* key_security_client_rdp = "ClientRdpSecurity";
+static const char* key_security_client_fallback = "ClientAllowFallbackToTls";
+
+static const char* section_certificates = "Certificates";
+static const char* key_private_key_file = "PrivateKeyFile";
+static const char* key_private_key_content = "PrivateKeyContent";
+static const char* key_cert_file = "CertificateFile";
+static const char* key_cert_content = "CertificateContent";
+
+static char** pf_config_parse_comma_separated_list(const char* list, size_t* count)
+{
+ if (!list || !count)
+ return NULL;
+
+ if (strlen(list) == 0)
+ {
+ *count = 0;
+ return NULL;
+ }
+
+ return CommandLineParseCommaSeparatedValues(list, count);
+}
+
+static BOOL pf_config_get_uint16(wIniFile* ini, const char* section, const char* key,
+ UINT16* result, BOOL required)
+{
+ int val = 0;
+ const char* strval = NULL;
+
+ WINPR_ASSERT(result);
+
+ strval = IniFile_GetKeyValueString(ini, section, key);
+ if (!strval && required)
+ {
+ WLog_ERR(TAG, "key '%s.%s' does not exist.", section, key);
+ return FALSE;
+ }
+ val = IniFile_GetKeyValueInt(ini, section, key);
+ if ((val <= 0) || (val > UINT16_MAX))
+ {
+ WLog_ERR(TAG, "invalid value %d for key '%s.%s'.", val, section, key);
+ return FALSE;
+ }
+
+ *result = (UINT16)val;
+ return TRUE;
+}
+
+static BOOL pf_config_get_uint32(wIniFile* ini, const char* section, const char* key,
+ UINT32* result, BOOL required)
+{
+ int val = 0;
+ const char* strval = NULL;
+
+ WINPR_ASSERT(result);
+
+ strval = IniFile_GetKeyValueString(ini, section, key);
+ if (!strval)
+ {
+ if (required)
+ WLog_ERR(TAG, "key '%s.%s' does not exist.", section, key);
+ return !required;
+ }
+
+ val = IniFile_GetKeyValueInt(ini, section, key);
+ if ((val < 0) || (val > INT32_MAX))
+ {
+ WLog_ERR(TAG, "invalid value %d for key '%s.%s'.", val, section, key);
+ return FALSE;
+ }
+
+ *result = (UINT32)val;
+ return TRUE;
+}
+
+static BOOL pf_config_get_bool(wIniFile* ini, const char* section, const char* key, BOOL fallback)
+{
+ int num_value = 0;
+ const char* str_value = NULL;
+
+ str_value = IniFile_GetKeyValueString(ini, section, key);
+ if (!str_value)
+ {
+ WLog_WARN(TAG, "key '%s.%s' not found, value defaults to %s.", section, key,
+ fallback ? bool_str_true : bool_str_false);
+ return fallback;
+ }
+
+ if (_stricmp(str_value, bool_str_true) == 0)
+ return TRUE;
+ if (_stricmp(str_value, bool_str_false) == 0)
+ return FALSE;
+
+ num_value = IniFile_GetKeyValueInt(ini, section, key);
+
+ if (num_value != 0)
+ return TRUE;
+
+ return FALSE;
+}
+
+static const char* pf_config_get_str(wIniFile* ini, const char* section, const char* key,
+ BOOL required)
+{
+ const char* value = NULL;
+
+ value = IniFile_GetKeyValueString(ini, section, key);
+
+ if (!value)
+ {
+ if (required)
+ WLog_ERR(TAG, "key '%s.%s' not found.", section, key);
+ return NULL;
+ }
+
+ return value;
+}
+
+static BOOL pf_config_load_server(wIniFile* ini, proxyConfig* config)
+{
+ const char* host = NULL;
+
+ WINPR_ASSERT(config);
+ host = pf_config_get_str(ini, section_server, key_host, FALSE);
+
+ if (!host)
+ return TRUE;
+
+ config->Host = _strdup(host);
+
+ if (!config->Host)
+ return FALSE;
+
+ if (!pf_config_get_uint16(ini, section_server, key_port, &config->Port, TRUE))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL pf_config_load_target(wIniFile* ini, proxyConfig* config)
+{
+ const char* target_value = NULL;
+
+ WINPR_ASSERT(config);
+ config->FixedTarget = pf_config_get_bool(ini, section_target, key_target_fixed, FALSE);
+
+ if (!pf_config_get_uint16(ini, section_target, key_port, &config->TargetPort,
+ config->FixedTarget))
+ return FALSE;
+
+ if (!pf_config_get_uint32(ini, section_target, key_target_tls_seclevel,
+ &config->TargetTlsSecLevel, FALSE))
+ return FALSE;
+
+ if (config->FixedTarget)
+ {
+ target_value = pf_config_get_str(ini, section_target, key_host, TRUE);
+ if (!target_value)
+ return FALSE;
+
+ config->TargetHost = _strdup(target_value);
+ if (!config->TargetHost)
+ return FALSE;
+ }
+
+ target_value = pf_config_get_str(ini, section_target, key_target_user, FALSE);
+ if (target_value)
+ {
+ config->TargetUser = _strdup(target_value);
+ if (!config->TargetUser)
+ return FALSE;
+ }
+
+ target_value = pf_config_get_str(ini, section_target, key_target_pwd, FALSE);
+ if (target_value)
+ {
+ config->TargetPassword = _strdup(target_value);
+ if (!config->TargetPassword)
+ return FALSE;
+ }
+
+ target_value = pf_config_get_str(ini, section_target, key_target_domain, FALSE);
+ if (target_value)
+ {
+ config->TargetDomain = _strdup(target_value);
+ if (!config->TargetDomain)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL pf_config_load_channels(wIniFile* ini, proxyConfig* config)
+{
+ WINPR_ASSERT(config);
+ config->GFX = pf_config_get_bool(ini, section_channels, key_channels_gfx, TRUE);
+ config->DisplayControl = pf_config_get_bool(ini, section_channels, key_channels_disp, TRUE);
+ config->Clipboard = pf_config_get_bool(ini, section_channels, key_channels_clip, FALSE);
+ config->AudioOutput = pf_config_get_bool(ini, section_channels, key_channels_mic, TRUE);
+ config->AudioInput = pf_config_get_bool(ini, section_channels, key_channels_sound, TRUE);
+ config->DeviceRedirection = pf_config_get_bool(ini, section_channels, key_channels_rdpdr, TRUE);
+ config->VideoRedirection = pf_config_get_bool(ini, section_channels, key_channels_video, TRUE);
+ config->CameraRedirection =
+ pf_config_get_bool(ini, section_channels, key_channels_camera, TRUE);
+ config->RemoteApp = pf_config_get_bool(ini, section_channels, key_channels_rails, FALSE);
+ config->PassthroughIsBlacklist =
+ pf_config_get_bool(ini, section_channels, key_channels_blacklist, FALSE);
+ config->Passthrough = pf_config_parse_comma_separated_list(
+ pf_config_get_str(ini, section_channels, key_channels_pass, FALSE),
+ &config->PassthroughCount);
+ config->Intercept = pf_config_parse_comma_separated_list(
+ pf_config_get_str(ini, section_channels, key_channels_intercept, FALSE),
+ &config->InterceptCount);
+
+ return TRUE;
+}
+
+static BOOL pf_config_load_input(wIniFile* ini, proxyConfig* config)
+{
+ WINPR_ASSERT(config);
+ config->Keyboard = pf_config_get_bool(ini, section_input, key_input_kbd, TRUE);
+ config->Mouse = pf_config_get_bool(ini, section_input, key_input_mouse, TRUE);
+ config->Multitouch = pf_config_get_bool(ini, section_input, key_input_multitouch, TRUE);
+ return TRUE;
+}
+
+static BOOL pf_config_load_security(wIniFile* ini, proxyConfig* config)
+{
+ WINPR_ASSERT(config);
+ config->ServerTlsSecurity =
+ pf_config_get_bool(ini, section_security, key_security_server_tls, TRUE);
+ config->ServerNlaSecurity =
+ pf_config_get_bool(ini, section_security, key_security_server_nla, FALSE);
+ config->ServerRdpSecurity =
+ pf_config_get_bool(ini, section_security, key_security_server_rdp, TRUE);
+
+ config->ClientTlsSecurity =
+ pf_config_get_bool(ini, section_security, key_security_client_tls, TRUE);
+ config->ClientNlaSecurity =
+ pf_config_get_bool(ini, section_security, key_security_client_nla, TRUE);
+ config->ClientRdpSecurity =
+ pf_config_get_bool(ini, section_security, key_security_client_rdp, TRUE);
+ config->ClientAllowFallbackToTls =
+ pf_config_get_bool(ini, section_security, key_security_client_fallback, TRUE);
+ return TRUE;
+}
+
+static BOOL pf_config_load_clipboard(wIniFile* ini, proxyConfig* config)
+{
+ WINPR_ASSERT(config);
+ config->TextOnly = pf_config_get_bool(ini, section_clipboard, key_clip_text_only, FALSE);
+
+ if (!pf_config_get_uint32(ini, section_clipboard, key_clip_text_max_len, &config->MaxTextLength,
+ FALSE))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL pf_config_load_modules(wIniFile* ini, proxyConfig* config)
+{
+ const char* modules_to_load = NULL;
+ const char* required_modules = NULL;
+
+ modules_to_load = pf_config_get_str(ini, section_plugins, key_plugins_modules, FALSE);
+ required_modules = pf_config_get_str(ini, section_plugins, key_plugins_required, FALSE);
+
+ WINPR_ASSERT(config);
+ config->Modules = pf_config_parse_comma_separated_list(modules_to_load, &config->ModulesCount);
+
+ config->RequiredPlugins =
+ pf_config_parse_comma_separated_list(required_modules, &config->RequiredPluginsCount);
+ return TRUE;
+}
+
+static BOOL pf_config_load_gfx_settings(wIniFile* ini, proxyConfig* config)
+{
+ WINPR_ASSERT(config);
+ config->DecodeGFX = pf_config_get_bool(ini, section_gfx_settings, key_gfx_decode, FALSE);
+ return TRUE;
+}
+
+static char* pf_config_decode_base64(const char* data, const char* name, size_t* pLength)
+{
+ const char* headers[] = { "-----BEGIN PUBLIC KEY-----", "-----BEGIN RSA PUBLIC KEY-----",
+ "-----BEGIN CERTIFICATE-----", "-----BEGIN PRIVATE KEY-----",
+ "-----BEGIN RSA PRIVATE KEY-----" };
+
+ size_t decoded_length = 0;
+ char* decoded = NULL;
+ if (!data)
+ {
+ WLog_ERR(TAG, "Invalid base64 data [%p] for %s", data, name);
+ return NULL;
+ }
+
+ WINPR_ASSERT(name);
+ WINPR_ASSERT(pLength);
+
+ const size_t length = strlen(data);
+
+ if (strncmp(data, "-----", 5) == 0)
+ {
+ BOOL expected = FALSE;
+ for (size_t x = 0; x < ARRAYSIZE(headers); x++)
+ {
+ const char* header = headers[x];
+
+ if (strncmp(data, header, strlen(header)) == 0)
+ expected = TRUE;
+ }
+
+ if (!expected)
+ {
+ /* Extract header for log message
+ * expected format is '----- SOMETEXT -----'
+ */
+ char hdr[128] = { 0 };
+ const char* end = strchr(&data[5], '-');
+ if (end)
+ {
+ while (*end == '-')
+ end++;
+
+ const size_t s = MIN(ARRAYSIZE(hdr) - 1, end - data);
+ memcpy(hdr, data, s);
+ }
+
+ WLog_WARN(TAG, "PEM has unexpected header '%s'. Known supported headers are:", hdr);
+ for (size_t x = 0; x < ARRAYSIZE(headers); x++)
+ {
+ const char* header = headers[x];
+ WLog_WARN(TAG, "%s", header);
+ }
+ }
+
+ *pLength = length + 1;
+ return _strdup(data);
+ }
+
+ crypto_base64_decode(data, length, (BYTE**)&decoded, &decoded_length);
+ if (!decoded || decoded_length == 0)
+ {
+ WLog_ERR(TAG, "Failed to decode base64 data of length %" PRIuz " for %s", length, name);
+ free(decoded);
+ return NULL;
+ }
+
+ *pLength = strnlen(decoded, decoded_length) + 1;
+ return decoded;
+}
+
+static BOOL pf_config_load_certificates(wIniFile* ini, proxyConfig* config)
+{
+ const char* tmp1 = NULL;
+ const char* tmp2 = NULL;
+
+ WINPR_ASSERT(ini);
+ WINPR_ASSERT(config);
+
+ tmp1 = pf_config_get_str(ini, section_certificates, key_cert_file, FALSE);
+ if (tmp1)
+ {
+ if (!winpr_PathFileExists(tmp1))
+ {
+ WLog_ERR(TAG, "%s/%s file %s does not exist", section_certificates, key_cert_file,
+ tmp1);
+ return FALSE;
+ }
+ config->CertificateFile = _strdup(tmp1);
+ config->CertificatePEM =
+ crypto_read_pem(config->CertificateFile, &config->CertificatePEMLength);
+ if (!config->CertificatePEM)
+ return FALSE;
+ config->CertificatePEMLength += 1;
+ }
+ tmp2 = pf_config_get_str(ini, section_certificates, key_cert_content, FALSE);
+ if (tmp2)
+ {
+ if (strlen(tmp2) < 1)
+ {
+ WLog_ERR(TAG, "%s/%s has invalid empty value", section_certificates, key_cert_content);
+ return FALSE;
+ }
+ config->CertificateContent = _strdup(tmp2);
+ config->CertificatePEM = pf_config_decode_base64(
+ config->CertificateContent, "CertificateContent", &config->CertificatePEMLength);
+ if (!config->CertificatePEM)
+ return FALSE;
+ }
+ if (tmp1 && tmp2)
+ {
+ WLog_ERR(TAG,
+ "%s/%s and %s/%s are "
+ "mutually exclusive options",
+ section_certificates, key_cert_file, section_certificates, key_cert_content);
+ return FALSE;
+ }
+ else if (!tmp1 && !tmp2)
+ {
+ WLog_ERR(TAG,
+ "%s/%s or %s/%s are "
+ "required settings",
+ section_certificates, key_cert_file, section_certificates, key_cert_content);
+ return FALSE;
+ }
+
+ tmp1 = pf_config_get_str(ini, section_certificates, key_private_key_file, FALSE);
+ if (tmp1)
+ {
+ if (!winpr_PathFileExists(tmp1))
+ {
+ WLog_ERR(TAG, "%s/%s file %s does not exist", section_certificates,
+ key_private_key_file, tmp1);
+ return FALSE;
+ }
+ config->PrivateKeyFile = _strdup(tmp1);
+ config->PrivateKeyPEM =
+ crypto_read_pem(config->PrivateKeyFile, &config->PrivateKeyPEMLength);
+ if (!config->PrivateKeyPEM)
+ return FALSE;
+ config->PrivateKeyPEMLength += 1;
+ }
+ tmp2 = pf_config_get_str(ini, section_certificates, key_private_key_content, FALSE);
+ if (tmp2)
+ {
+ if (strlen(tmp2) < 1)
+ {
+ WLog_ERR(TAG, "%s/%s has invalid empty value", section_certificates,
+ key_private_key_content);
+ return FALSE;
+ }
+ config->PrivateKeyContent = _strdup(tmp2);
+ config->PrivateKeyPEM = pf_config_decode_base64(
+ config->PrivateKeyContent, "PrivateKeyContent", &config->PrivateKeyPEMLength);
+ if (!config->PrivateKeyPEM)
+ return FALSE;
+ }
+
+ if (tmp1 && tmp2)
+ {
+ WLog_ERR(TAG,
+ "%s/%s and %s/%s are "
+ "mutually exclusive options",
+ section_certificates, key_private_key_file, section_certificates,
+ key_private_key_content);
+ return FALSE;
+ }
+ else if (!tmp1 && !tmp2)
+ {
+ WLog_ERR(TAG,
+ "%s/%s or %s/%s are "
+ "are required settings",
+ section_certificates, key_private_key_file, section_certificates,
+ key_private_key_content);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+proxyConfig* server_config_load_ini(wIniFile* ini)
+{
+ proxyConfig* config = NULL;
+
+ WINPR_ASSERT(ini);
+
+ config = calloc(1, sizeof(proxyConfig));
+ if (config)
+ {
+ /* Set default values != 0 */
+ config->TargetTlsSecLevel = 1;
+
+ /* Load from ini */
+ if (!pf_config_load_server(ini, config))
+ goto out;
+
+ if (!pf_config_load_target(ini, config))
+ goto out;
+
+ if (!pf_config_load_channels(ini, config))
+ goto out;
+
+ if (!pf_config_load_input(ini, config))
+ goto out;
+
+ if (!pf_config_load_security(ini, config))
+ goto out;
+
+ if (!pf_config_load_modules(ini, config))
+ goto out;
+
+ if (!pf_config_load_clipboard(ini, config))
+ goto out;
+
+ if (!pf_config_load_gfx_settings(ini, config))
+ goto out;
+
+ if (!pf_config_load_certificates(ini, config))
+ goto out;
+ config->ini = IniFile_Clone(ini);
+ if (!config->ini)
+ goto out;
+ }
+ return config;
+out:
+ pf_server_config_free(config);
+ return NULL;
+}
+
+BOOL pf_server_config_dump(const char* file)
+{
+ BOOL rc = FALSE;
+ wIniFile* ini = IniFile_New();
+ if (!ini)
+ return FALSE;
+
+ /* Proxy server configuration */
+ if (IniFile_SetKeyValueString(ini, section_server, key_host, "0.0.0.0") < 0)
+ goto fail;
+ if (IniFile_SetKeyValueInt(ini, section_server, key_port, 3389) < 0)
+ goto fail;
+
+ /* Target configuration */
+ if (IniFile_SetKeyValueString(ini, section_target, key_host, "somehost.example.com") < 0)
+ goto fail;
+ if (IniFile_SetKeyValueInt(ini, section_target, key_port, 3389) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_target, key_target_fixed, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueInt(ini, section_target, key_target_tls_seclevel, 1) < 0)
+ goto fail;
+
+ /* Channel configuration */
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_gfx, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_disp, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_clip, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_mic, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_sound, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_rdpdr, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_video, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_camera, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_rails, bool_str_false) < 0)
+ goto fail;
+
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_blacklist, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_pass, "") < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_channels, key_channels_intercept, "") < 0)
+ goto fail;
+
+ /* Input configuration */
+ if (IniFile_SetKeyValueString(ini, section_input, key_input_kbd, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_input, key_input_mouse, bool_str_true) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_input, key_input_multitouch, bool_str_true) < 0)
+ goto fail;
+
+ /* Security settings */
+ if (IniFile_SetKeyValueString(ini, section_security, key_security_server_tls, bool_str_true) <
+ 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_security, key_security_server_nla, bool_str_false) <
+ 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_security, key_security_server_rdp, bool_str_true) <
+ 0)
+ goto fail;
+
+ if (IniFile_SetKeyValueString(ini, section_security, key_security_client_tls, bool_str_true) <
+ 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_security, key_security_client_nla, bool_str_true) <
+ 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_security, key_security_client_rdp, bool_str_true) <
+ 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_security, key_security_client_fallback,
+ bool_str_true) < 0)
+ goto fail;
+
+ /* Module configuration */
+ if (IniFile_SetKeyValueString(ini, section_plugins, key_plugins_modules,
+ "module1,module2,...") < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_plugins, key_plugins_required,
+ "module1,module2,...") < 0)
+ goto fail;
+
+ /* Clipboard configuration */
+ if (IniFile_SetKeyValueString(ini, section_clipboard, key_clip_text_only, bool_str_false) < 0)
+ goto fail;
+ if (IniFile_SetKeyValueInt(ini, section_clipboard, key_clip_text_max_len, 0) < 0)
+ goto fail;
+
+ /* GFX configuration */
+ if (IniFile_SetKeyValueString(ini, section_gfx_settings, key_gfx_decode, bool_str_false) < 0)
+ goto fail;
+
+ /* Certificate configuration */
+ if (IniFile_SetKeyValueString(ini, section_certificates, key_cert_file,
+ "<absolute path to some certificate file> OR") < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_certificates, key_cert_content,
+ "<Contents of some certificate file in PEM format>") < 0)
+ goto fail;
+
+ if (IniFile_SetKeyValueString(ini, section_certificates, key_private_key_file,
+ "<absolute path to some private key file> OR") < 0)
+ goto fail;
+ if (IniFile_SetKeyValueString(ini, section_certificates, key_private_key_content,
+ "<Contents of some private key file in PEM format>") < 0)
+ goto fail;
+
+ /* store configuration */
+ if (IniFile_WriteFile(ini, file) < 0)
+ goto fail;
+
+ rc = TRUE;
+
+fail:
+ IniFile_Free(ini);
+ return rc;
+}
+
+proxyConfig* pf_server_config_load_buffer(const char* buffer)
+{
+ proxyConfig* config = NULL;
+ wIniFile* ini = NULL;
+
+ ini = IniFile_New();
+
+ if (!ini)
+ {
+ WLog_ERR(TAG, "IniFile_New() failed!");
+ return NULL;
+ }
+
+ if (IniFile_ReadBuffer(ini, buffer) < 0)
+ {
+ WLog_ERR(TAG, "failed to parse ini: '%s'", buffer);
+ goto out;
+ }
+
+ config = server_config_load_ini(ini);
+out:
+ IniFile_Free(ini);
+ return config;
+}
+
+proxyConfig* pf_server_config_load_file(const char* path)
+{
+ proxyConfig* config = NULL;
+ wIniFile* ini = IniFile_New();
+
+ if (!ini)
+ {
+ WLog_ERR(TAG, "IniFile_New() failed!");
+ return NULL;
+ }
+
+ if (IniFile_ReadFile(ini, path) < 0)
+ {
+ WLog_ERR(TAG, "failed to parse ini file: '%s'", path);
+ goto out;
+ }
+
+ config = server_config_load_ini(ini);
+out:
+ IniFile_Free(ini);
+ return config;
+}
+
+static void pf_server_config_print_list(char** list, size_t count)
+{
+ WINPR_ASSERT(list);
+ for (size_t i = 0; i < count; i++)
+ WLog_INFO(TAG, "\t\t- %s", list[i]);
+}
+
+void pf_server_config_print(const proxyConfig* config)
+{
+ WINPR_ASSERT(config);
+ WLog_INFO(TAG, "Proxy configuration:");
+
+ CONFIG_PRINT_SECTION(section_server);
+ CONFIG_PRINT_STR(config, Host);
+ CONFIG_PRINT_UINT16(config, Port);
+
+ if (config->FixedTarget)
+ {
+ CONFIG_PRINT_SECTION(section_target);
+ CONFIG_PRINT_STR(config, TargetHost);
+ CONFIG_PRINT_UINT16(config, TargetPort);
+ CONFIG_PRINT_UINT32(config, TargetTlsSecLevel);
+
+ if (config->TargetUser)
+ CONFIG_PRINT_STR(config, TargetUser);
+ if (config->TargetDomain)
+ CONFIG_PRINT_STR(config, TargetDomain);
+ }
+
+ CONFIG_PRINT_SECTION(section_input);
+ CONFIG_PRINT_BOOL(config, Keyboard);
+ CONFIG_PRINT_BOOL(config, Mouse);
+ CONFIG_PRINT_BOOL(config, Multitouch);
+
+ CONFIG_PRINT_SECTION(section_security);
+ CONFIG_PRINT_BOOL(config, ServerNlaSecurity);
+ CONFIG_PRINT_BOOL(config, ServerTlsSecurity);
+ CONFIG_PRINT_BOOL(config, ServerRdpSecurity);
+ CONFIG_PRINT_BOOL(config, ClientNlaSecurity);
+ CONFIG_PRINT_BOOL(config, ClientTlsSecurity);
+ CONFIG_PRINT_BOOL(config, ClientRdpSecurity);
+ CONFIG_PRINT_BOOL(config, ClientAllowFallbackToTls);
+
+ CONFIG_PRINT_SECTION(section_channels);
+ CONFIG_PRINT_BOOL(config, GFX);
+ CONFIG_PRINT_BOOL(config, DisplayControl);
+ CONFIG_PRINT_BOOL(config, Clipboard);
+ CONFIG_PRINT_BOOL(config, AudioOutput);
+ CONFIG_PRINT_BOOL(config, AudioInput);
+ CONFIG_PRINT_BOOL(config, DeviceRedirection);
+ CONFIG_PRINT_BOOL(config, VideoRedirection);
+ CONFIG_PRINT_BOOL(config, CameraRedirection);
+ CONFIG_PRINT_BOOL(config, RemoteApp);
+ CONFIG_PRINT_BOOL(config, PassthroughIsBlacklist);
+
+ if (config->PassthroughCount)
+ {
+ WLog_INFO(TAG, "\tStatic Channels Proxy:");
+ pf_server_config_print_list(config->Passthrough, config->PassthroughCount);
+ }
+
+ if (config->InterceptCount)
+ {
+ WLog_INFO(TAG, "\tStatic Channels Proxy-Intercept:");
+ pf_server_config_print_list(config->Intercept, config->InterceptCount);
+ }
+
+ CONFIG_PRINT_SECTION(section_clipboard);
+ CONFIG_PRINT_BOOL(config, TextOnly);
+ if (config->MaxTextLength > 0)
+ CONFIG_PRINT_UINT32(config, MaxTextLength);
+
+ CONFIG_PRINT_SECTION(section_gfx_settings);
+ CONFIG_PRINT_BOOL(config, DecodeGFX);
+
+ /* modules */
+ CONFIG_PRINT_SECTION_KEY(section_plugins, key_plugins_modules);
+ for (size_t x = 0; x < config->ModulesCount; x++)
+ CONFIG_PRINT_STR(config, Modules[x]);
+
+ /* Required plugins */
+ CONFIG_PRINT_SECTION_KEY(section_plugins, key_plugins_required);
+ for (size_t x = 0; x < config->RequiredPluginsCount; x++)
+ CONFIG_PRINT_STR(config, RequiredPlugins[x]);
+
+ CONFIG_PRINT_SECTION(section_certificates);
+ CONFIG_PRINT_STR(config, CertificateFile);
+ CONFIG_PRINT_STR_CONTENT(config, CertificateContent);
+ CONFIG_PRINT_STR(config, PrivateKeyFile);
+ CONFIG_PRINT_STR_CONTENT(config, PrivateKeyContent);
+}
+
+void pf_server_config_free(proxyConfig* config)
+{
+ if (config == NULL)
+ return;
+
+ free(config->Passthrough);
+ free(config->Intercept);
+ free(config->RequiredPlugins);
+ free(config->Modules);
+ free(config->TargetHost);
+ free(config->Host);
+ free(config->CertificateFile);
+ free(config->CertificateContent);
+ if (config->CertificatePEM)
+ memset(config->CertificatePEM, 0, config->CertificatePEMLength);
+ free(config->CertificatePEM);
+ free(config->PrivateKeyFile);
+ free(config->PrivateKeyContent);
+ if (config->PrivateKeyPEM)
+ memset(config->PrivateKeyPEM, 0, config->PrivateKeyPEMLength);
+ free(config->PrivateKeyPEM);
+ IniFile_Free(config->ini);
+ free(config);
+}
+
+size_t pf_config_required_plugins_count(const proxyConfig* config)
+{
+ WINPR_ASSERT(config);
+ return config->RequiredPluginsCount;
+}
+
+const char* pf_config_required_plugin(const proxyConfig* config, size_t index)
+{
+ WINPR_ASSERT(config);
+ if (index >= config->RequiredPluginsCount)
+ return NULL;
+
+ return config->RequiredPlugins[index];
+}
+
+size_t pf_config_modules_count(const proxyConfig* config)
+{
+ WINPR_ASSERT(config);
+ return config->ModulesCount;
+}
+
+const char** pf_config_modules(const proxyConfig* config)
+{
+ union
+ {
+ char** ppc;
+ const char** cppc;
+ } cnv;
+
+ WINPR_ASSERT(config);
+
+ cnv.ppc = config->Modules;
+ return cnv.cppc;
+}
+
+static BOOL pf_config_copy_string(char** dst, const char* src)
+{
+ *dst = NULL;
+ if (src)
+ *dst = _strdup(src);
+ return TRUE;
+}
+
+static BOOL pf_config_copy_string_n(char** dst, const char* src, size_t size)
+{
+ *dst = NULL;
+
+ if (src && (size > 0))
+ {
+ WINPR_ASSERT(strnlen(src, size) == size - 1);
+ *dst = calloc(size, sizeof(char));
+ if (!*dst)
+ return FALSE;
+ memcpy(*dst, src, size);
+ }
+
+ return TRUE;
+}
+
+static BOOL pf_config_copy_string_list(char*** dst, size_t* size, char** src, size_t srcSize)
+{
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(size);
+ WINPR_ASSERT(src || (srcSize == 0));
+
+ *dst = NULL;
+ *size = 0;
+ if (srcSize == 0)
+ return TRUE;
+ {
+ char* csv = CommandLineToCommaSeparatedValues(srcSize, src);
+ *dst = CommandLineParseCommaSeparatedValues(csv, size);
+ free(csv);
+ }
+
+ return TRUE;
+}
+
+BOOL pf_config_clone(proxyConfig** dst, const proxyConfig* config)
+{
+ proxyConfig* tmp = calloc(1, sizeof(proxyConfig));
+
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(config);
+
+ if (!tmp)
+ return FALSE;
+
+ *tmp = *config;
+
+ if (!pf_config_copy_string(&tmp->Host, config->Host))
+ goto fail;
+ if (!pf_config_copy_string(&tmp->TargetHost, config->TargetHost))
+ goto fail;
+
+ if (!pf_config_copy_string_list(&tmp->Passthrough, &tmp->PassthroughCount, config->Passthrough,
+ config->PassthroughCount))
+ goto fail;
+ if (!pf_config_copy_string_list(&tmp->Intercept, &tmp->InterceptCount, config->Intercept,
+ config->InterceptCount))
+ goto fail;
+ if (!pf_config_copy_string_list(&tmp->Modules, &tmp->ModulesCount, config->Modules,
+ config->ModulesCount))
+ goto fail;
+ if (!pf_config_copy_string_list(&tmp->RequiredPlugins, &tmp->RequiredPluginsCount,
+ config->RequiredPlugins, config->RequiredPluginsCount))
+ goto fail;
+ if (!pf_config_copy_string(&tmp->CertificateFile, config->CertificateFile))
+ goto fail;
+ if (!pf_config_copy_string(&tmp->CertificateContent, config->CertificateContent))
+ goto fail;
+ if (!pf_config_copy_string_n(&tmp->CertificatePEM, config->CertificatePEM,
+ config->CertificatePEMLength))
+ goto fail;
+ if (!pf_config_copy_string(&tmp->PrivateKeyFile, config->PrivateKeyFile))
+ goto fail;
+ if (!pf_config_copy_string(&tmp->PrivateKeyContent, config->PrivateKeyContent))
+ goto fail;
+ if (!pf_config_copy_string_n(&tmp->PrivateKeyPEM, config->PrivateKeyPEM,
+ config->PrivateKeyPEMLength))
+ goto fail;
+
+ tmp->ini = IniFile_Clone(config->ini);
+ if (!tmp->ini)
+ goto fail;
+
+ *dst = tmp;
+ return TRUE;
+
+fail:
+ pf_server_config_free(tmp);
+ return FALSE;
+}
+
+struct config_plugin_data
+{
+ proxyPluginsManager* mgr;
+ const proxyConfig* config;
+};
+
+static const char config_plugin_name[] = "config";
+static const char config_plugin_desc[] =
+ "A plugin filtering according to proxy configuration file rules";
+
+static BOOL config_plugin_unload(proxyPlugin* plugin)
+{
+ WINPR_ASSERT(plugin);
+
+ /* Here we have to free up our custom data storage. */
+ if (plugin)
+ {
+ free(plugin->custom);
+ plugin->custom = NULL;
+ }
+
+ return TRUE;
+}
+
+static BOOL config_plugin_keyboard_event(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ BOOL rc = 0;
+ const struct config_plugin_data* custom = NULL;
+ const proxyConfig* cfg = NULL;
+ const proxyKeyboardEventInfo* event_data = (const proxyKeyboardEventInfo*)(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(event_data);
+
+ WINPR_UNUSED(event_data);
+
+ custom = plugin->custom;
+ WINPR_ASSERT(custom);
+
+ cfg = custom->config;
+ WINPR_ASSERT(cfg);
+
+ rc = cfg->Keyboard;
+ WLog_DBG(TAG, "%s", boolstr(rc));
+ return rc;
+}
+
+static BOOL config_plugin_unicode_event(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ BOOL rc = 0;
+ const struct config_plugin_data* custom = NULL;
+ const proxyConfig* cfg = NULL;
+ const proxyUnicodeEventInfo* event_data = (const proxyUnicodeEventInfo*)(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(event_data);
+
+ WINPR_UNUSED(event_data);
+
+ custom = plugin->custom;
+ WINPR_ASSERT(custom);
+
+ cfg = custom->config;
+ WINPR_ASSERT(cfg);
+
+ rc = cfg->Keyboard;
+ WLog_DBG(TAG, "%s", boolstr(rc));
+ return rc;
+}
+
+static BOOL config_plugin_mouse_event(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ BOOL rc = 0;
+ const struct config_plugin_data* custom = NULL;
+ const proxyConfig* cfg = NULL;
+ const proxyMouseEventInfo* event_data = (const proxyMouseEventInfo*)(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(event_data);
+
+ WINPR_UNUSED(event_data);
+
+ custom = plugin->custom;
+ WINPR_ASSERT(custom);
+
+ cfg = custom->config;
+ WINPR_ASSERT(cfg);
+
+ rc = cfg->Mouse;
+ return rc;
+}
+
+static BOOL config_plugin_mouse_ex_event(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ BOOL rc = 0;
+ const struct config_plugin_data* custom = NULL;
+ const proxyConfig* cfg = NULL;
+ const proxyMouseExEventInfo* event_data = (const proxyMouseExEventInfo*)(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(event_data);
+
+ WINPR_UNUSED(event_data);
+
+ custom = plugin->custom;
+ WINPR_ASSERT(custom);
+
+ cfg = custom->config;
+ WINPR_ASSERT(cfg);
+
+ rc = cfg->Mouse;
+ return rc;
+}
+
+static BOOL config_plugin_client_channel_data(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ const proxyChannelDataEventInfo* channel = (const proxyChannelDataEventInfo*)(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ WLog_DBG(TAG, "%s [0x%04" PRIx16 "] got %" PRIuz, channel->channel_name, channel->channel_id,
+ channel->data_len);
+ return TRUE;
+}
+
+static BOOL config_plugin_server_channel_data(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ const proxyChannelDataEventInfo* channel = (const proxyChannelDataEventInfo*)(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ WLog_DBG(TAG, "%s [0x%04" PRIx16 "] got %" PRIuz, channel->channel_name, channel->channel_id,
+ channel->data_len);
+ return TRUE;
+}
+
+static BOOL config_plugin_dynamic_channel_create(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ BOOL accept = 0;
+ const proxyChannelDataEventInfo* channel = (const proxyChannelDataEventInfo*)(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ const struct config_plugin_data* custom = plugin->custom;
+ WINPR_ASSERT(custom);
+
+ const proxyConfig* cfg = custom->config;
+ WINPR_ASSERT(cfg);
+
+ pf_utils_channel_mode rc = pf_utils_get_channel_mode(cfg, channel->channel_name);
+ switch (rc)
+ {
+
+ case PF_UTILS_CHANNEL_INTERCEPT:
+ case PF_UTILS_CHANNEL_PASSTHROUGH:
+ accept = TRUE;
+ break;
+ case PF_UTILS_CHANNEL_BLOCK:
+ default:
+ accept = FALSE;
+ break;
+ }
+
+ if (accept)
+ {
+ if (strncmp(RDPGFX_DVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(RDPGFX_DVC_CHANNEL_NAME)) == 0)
+ accept = cfg->GFX;
+ else if (strncmp(RDPSND_DVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(RDPSND_DVC_CHANNEL_NAME)) == 0)
+ accept = cfg->AudioOutput;
+ else if (strncmp(RDPSND_LOSSY_DVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(RDPSND_LOSSY_DVC_CHANNEL_NAME)) == 0)
+ accept = cfg->AudioOutput;
+ else if (strncmp(AUDIN_DVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(AUDIN_DVC_CHANNEL_NAME)) == 0)
+ accept = cfg->AudioInput;
+ else if (strncmp(RDPEI_DVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(RDPEI_DVC_CHANNEL_NAME)) == 0)
+ accept = cfg->Multitouch;
+ else if (strncmp(TSMF_DVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(TSMF_DVC_CHANNEL_NAME)) == 0)
+ accept = cfg->VideoRedirection;
+ else if (strncmp(VIDEO_CONTROL_DVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(VIDEO_CONTROL_DVC_CHANNEL_NAME)) == 0)
+ accept = cfg->VideoRedirection;
+ else if (strncmp(VIDEO_DATA_DVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(VIDEO_DATA_DVC_CHANNEL_NAME)) == 0)
+ accept = cfg->VideoRedirection;
+ else if (strncmp(RDPECAM_DVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(RDPECAM_DVC_CHANNEL_NAME)) == 0)
+ accept = cfg->CameraRedirection;
+ }
+
+ WLog_DBG(TAG, "%s [0x%04" PRIx16 "]: %s", channel->channel_name, channel->channel_id,
+ boolstr(accept));
+ return accept;
+}
+
+static BOOL config_plugin_channel_create(proxyPlugin* plugin, proxyData* pdata, void* param)
+{
+ BOOL accept = 0;
+ const proxyChannelDataEventInfo* channel = (const proxyChannelDataEventInfo*)(param);
+
+ WINPR_ASSERT(plugin);
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(channel);
+
+ const struct config_plugin_data* custom = plugin->custom;
+ WINPR_ASSERT(custom);
+
+ const proxyConfig* cfg = custom->config;
+ WINPR_ASSERT(cfg);
+
+ pf_utils_channel_mode rc = pf_utils_get_channel_mode(cfg, channel->channel_name);
+ switch (rc)
+ {
+ case PF_UTILS_CHANNEL_INTERCEPT:
+ case PF_UTILS_CHANNEL_PASSTHROUGH:
+ accept = TRUE;
+ break;
+ case PF_UTILS_CHANNEL_BLOCK:
+ default:
+ accept = FALSE;
+ break;
+ }
+ if (accept)
+ {
+ if (strncmp(CLIPRDR_SVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(CLIPRDR_SVC_CHANNEL_NAME)) == 0)
+ accept = cfg->Clipboard;
+ else if (strncmp(RDPSND_CHANNEL_NAME, channel->channel_name, sizeof(RDPSND_CHANNEL_NAME)) ==
+ 0)
+ accept = cfg->AudioOutput;
+ else if (strncmp(RDPDR_SVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(RDPDR_SVC_CHANNEL_NAME)) == 0)
+ accept = cfg->DeviceRedirection;
+ else if (strncmp(DISP_DVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(DISP_DVC_CHANNEL_NAME)) == 0)
+ accept = cfg->DisplayControl;
+ else if (strncmp(RAIL_SVC_CHANNEL_NAME, channel->channel_name,
+ sizeof(RAIL_SVC_CHANNEL_NAME)) == 0)
+ accept = cfg->RemoteApp;
+ }
+
+ WLog_DBG(TAG, "%s [static]: %s", channel->channel_name, boolstr(accept));
+ return accept;
+}
+
+BOOL pf_config_plugin(proxyPluginsManager* plugins_manager, void* userdata)
+{
+ struct config_plugin_data* custom = NULL;
+ proxyPlugin plugin = { 0 };
+
+ plugin.name = config_plugin_name;
+ plugin.description = config_plugin_desc;
+ plugin.PluginUnload = config_plugin_unload;
+
+ plugin.KeyboardEvent = config_plugin_keyboard_event;
+ plugin.UnicodeEvent = config_plugin_unicode_event;
+ plugin.MouseEvent = config_plugin_mouse_event;
+ plugin.MouseExEvent = config_plugin_mouse_ex_event;
+ plugin.ClientChannelData = config_plugin_client_channel_data;
+ plugin.ServerChannelData = config_plugin_server_channel_data;
+ plugin.ChannelCreate = config_plugin_channel_create;
+ plugin.DynamicChannelCreate = config_plugin_dynamic_channel_create;
+ plugin.userdata = userdata;
+
+ custom = calloc(1, sizeof(struct config_plugin_data));
+ if (!custom)
+ return FALSE;
+
+ custom->mgr = plugins_manager;
+ custom->config = userdata;
+
+ plugin.custom = custom;
+ plugin.userdata = userdata;
+
+ return plugins_manager->RegisterPlugin(plugins_manager, &plugin);
+}
+
+const char* pf_config_get(const proxyConfig* config, const char* section, const char* key)
+{
+ WINPR_ASSERT(config);
+ WINPR_ASSERT(config->ini);
+ WINPR_ASSERT(section);
+ WINPR_ASSERT(key);
+
+ return IniFile_GetKeyValueString(config->ini, section, key);
+}
diff --git a/server/proxy/pf_context.c b/server/proxy/pf_context.c
new file mode 100644
index 0000000..04433d9
--- /dev/null
+++ b/server/proxy/pf_context.c
@@ -0,0 +1,394 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@gmail.com>
+ * Copyright 2021 Armin Novak <anovak@thincast.com>
+ * Copyright 2021 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.
+ */
+
+#include <winpr/crypto.h>
+#include <winpr/print.h>
+
+#include <freerdp/server/proxy/proxy_log.h>
+#include <freerdp/server/proxy/proxy_server.h>
+
+#include "pf_client.h"
+#include "pf_utils.h"
+#include "proxy_modules.h"
+
+#include <freerdp/server/proxy/proxy_context.h>
+
+#include "channels/pf_channel_rdpdr.h"
+
+#define TAG PROXY_TAG("server")
+
+static UINT32 ChannelId_Hash(const void* key)
+{
+ const UINT32* v = (const UINT32*)key;
+ return *v;
+}
+
+static BOOL ChannelId_Compare(const void* pv1, const void* pv2)
+{
+ const UINT32* v1 = pv1;
+ const UINT32* v2 = pv2;
+ WINPR_ASSERT(v1);
+ WINPR_ASSERT(v2);
+ return (*v1 == *v2);
+}
+
+pServerStaticChannelContext* StaticChannelContext_new(pServerContext* ps, const char* name,
+ UINT32 id)
+{
+ pServerStaticChannelContext* ret = calloc(1, sizeof(*ret));
+ if (!ret)
+ {
+ PROXY_LOG_ERR(TAG, ps, "error allocating channel context for '%s'", name);
+ return NULL;
+ }
+
+ ret->front_channel_id = id;
+ ret->channel_name = _strdup(name);
+ if (!ret->channel_name)
+ {
+ PROXY_LOG_ERR(TAG, ps, "error allocating name in channel context for '%s'", name);
+ free(ret);
+ return NULL;
+ }
+
+ proxyChannelToInterceptData channel = { .name = name, .channelId = id, .intercept = FALSE };
+
+ if (pf_modules_run_filter(ps->pdata->module, FILTER_TYPE_STATIC_INTERCEPT_LIST, ps->pdata,
+ &channel) &&
+ channel.intercept)
+ ret->channelMode = PF_UTILS_CHANNEL_INTERCEPT;
+ else
+ ret->channelMode = pf_utils_get_channel_mode(ps->pdata->config, name);
+ return ret;
+}
+
+void StaticChannelContext_free(pServerStaticChannelContext* ctx)
+{
+ if (!ctx)
+ return;
+
+ IFCALL(ctx->contextDtor, ctx->context);
+
+ free(ctx->channel_name);
+ free(ctx);
+}
+
+static void HashStaticChannelContext_free(void* ptr)
+{
+ pServerStaticChannelContext* ctx = (pServerStaticChannelContext*)ptr;
+ StaticChannelContext_free(ctx);
+}
+
+/* Proxy context initialization callback */
+static void client_to_proxy_context_free(freerdp_peer* client, rdpContext* ctx);
+static BOOL client_to_proxy_context_new(freerdp_peer* client, rdpContext* ctx)
+{
+ wObject* obj = NULL;
+ pServerContext* context = (pServerContext*)ctx;
+
+ WINPR_ASSERT(client);
+ WINPR_ASSERT(context);
+
+ context->dynvcReady = NULL;
+
+ context->vcm = WTSOpenServerA((LPSTR)client->context);
+
+ if (!context->vcm || context->vcm == INVALID_HANDLE_VALUE)
+ goto error;
+
+ if (!(context->dynvcReady = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ goto error;
+
+ context->interceptContextMap = HashTable_New(FALSE);
+ if (!context->interceptContextMap)
+ goto error;
+ if (!HashTable_SetupForStringData(context->interceptContextMap, FALSE))
+ goto error;
+ obj = HashTable_ValueObject(context->interceptContextMap);
+ WINPR_ASSERT(obj);
+ obj->fnObjectFree = intercept_context_entry_free;
+
+ /* channels by ids */
+ context->channelsByFrontId = HashTable_New(FALSE);
+ if (!context->channelsByFrontId)
+ goto error;
+ if (!HashTable_SetHashFunction(context->channelsByFrontId, ChannelId_Hash))
+ goto error;
+
+ obj = HashTable_KeyObject(context->channelsByFrontId);
+ obj->fnObjectEquals = ChannelId_Compare;
+
+ obj = HashTable_ValueObject(context->channelsByFrontId);
+ obj->fnObjectFree = HashStaticChannelContext_free;
+
+ context->channelsByBackId = HashTable_New(FALSE);
+ if (!context->channelsByBackId)
+ goto error;
+ if (!HashTable_SetHashFunction(context->channelsByBackId, ChannelId_Hash))
+ goto error;
+
+ obj = HashTable_KeyObject(context->channelsByBackId);
+ obj->fnObjectEquals = ChannelId_Compare;
+
+ return TRUE;
+
+error:
+ client_to_proxy_context_free(client, ctx);
+
+ return FALSE;
+}
+
+/* Proxy context free callback */
+void client_to_proxy_context_free(freerdp_peer* client, rdpContext* ctx)
+{
+ pServerContext* context = (pServerContext*)ctx;
+
+ WINPR_UNUSED(client);
+
+ if (!context)
+ return;
+
+ if (context->dynvcReady)
+ {
+ CloseHandle(context->dynvcReady);
+ context->dynvcReady = NULL;
+ }
+
+ HashTable_Free(context->interceptContextMap);
+ HashTable_Free(context->channelsByFrontId);
+ HashTable_Free(context->channelsByBackId);
+
+ if (context->vcm && (context->vcm != INVALID_HANDLE_VALUE))
+ WTSCloseServer((HANDLE)context->vcm);
+ context->vcm = NULL;
+}
+
+BOOL pf_context_init_server_context(freerdp_peer* client)
+{
+ WINPR_ASSERT(client);
+
+ client->ContextSize = sizeof(pServerContext);
+ client->ContextNew = client_to_proxy_context_new;
+ client->ContextFree = client_to_proxy_context_free;
+
+ return freerdp_peer_context_new(client);
+}
+
+static BOOL pf_context_revert_str_settings(rdpSettings* dst, const rdpSettings* before, size_t nr,
+ const FreeRDP_Settings_Keys_String* ids)
+{
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(before);
+ WINPR_ASSERT(ids || (nr == 0));
+
+ for (size_t x = 0; x < nr; x++)
+ {
+ FreeRDP_Settings_Keys_String id = ids[x];
+ const char* what = freerdp_settings_get_string(before, id);
+ if (!freerdp_settings_set_string(dst, id, what))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void intercept_context_entry_free(void* obj)
+{
+ InterceptContextMapEntry* entry = obj;
+ if (!entry)
+ return;
+ if (!entry->free)
+ return;
+ entry->free(entry);
+}
+
+BOOL pf_context_copy_settings(rdpSettings* dst, const rdpSettings* src)
+{
+ BOOL rc = FALSE;
+ rdpSettings* before_copy = NULL;
+ const FreeRDP_Settings_Keys_String to_revert[] = { FreeRDP_ConfigPath,
+ FreeRDP_CertificateName };
+
+ if (!dst || !src)
+ return FALSE;
+
+ before_copy = freerdp_settings_clone(dst);
+ if (!before_copy)
+ return FALSE;
+
+ if (!freerdp_settings_copy(dst, src))
+ goto out_fail;
+
+ /* keep original ServerMode value */
+ if (!freerdp_settings_copy_item(dst, before_copy, FreeRDP_ServerMode))
+ goto out_fail;
+
+ /* revert some values that must not be changed */
+ if (!pf_context_revert_str_settings(dst, before_copy, ARRAYSIZE(to_revert), to_revert))
+ goto out_fail;
+
+ if (!freerdp_settings_get_bool(dst, FreeRDP_ServerMode))
+ {
+ /* adjust instance pointer */
+ if (!freerdp_settings_copy_item(dst, before_copy, FreeRDP_instance))
+ goto out_fail;
+
+ /*
+ * RdpServerRsaKey must be set to NULL if `dst` is client's context
+ * it must be freed before setting it to NULL to avoid a memory leak!
+ */
+
+ if (!freerdp_settings_set_pointer_len(dst, FreeRDP_RdpServerRsaKey, NULL, 1))
+ goto out_fail;
+ }
+
+ /* We handle certificate management for this client ourselfes. */
+ rc = freerdp_settings_set_bool(dst, FreeRDP_ExternalCertificateManagement, TRUE);
+
+out_fail:
+ freerdp_settings_free(before_copy);
+ return rc;
+}
+
+pClientContext* pf_context_create_client_context(const rdpSettings* clientSettings)
+{
+ RDP_CLIENT_ENTRY_POINTS clientEntryPoints;
+ pClientContext* pc = NULL;
+ rdpContext* context = NULL;
+
+ WINPR_ASSERT(clientSettings);
+
+ RdpClientEntry(&clientEntryPoints);
+ context = freerdp_client_context_new(&clientEntryPoints);
+
+ if (!context)
+ return NULL;
+
+ pc = (pClientContext*)context;
+
+ if (!pf_context_copy_settings(context->settings, clientSettings))
+ goto error;
+
+ return pc;
+error:
+ freerdp_client_context_free(context);
+ return NULL;
+}
+
+proxyData* proxy_data_new(void)
+{
+ BYTE temp[16];
+ char* hex = NULL;
+ proxyData* pdata = NULL;
+
+ pdata = calloc(1, sizeof(proxyData));
+ if (!pdata)
+ return NULL;
+
+ if (!(pdata->abort_event = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ goto error;
+
+ if (!(pdata->gfx_server_ready = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ goto error;
+
+ winpr_RAND(&temp, 16);
+ hex = winpr_BinToHexString(temp, 16, FALSE);
+ if (!hex)
+ goto error;
+
+ CopyMemory(pdata->session_id, hex, PROXY_SESSION_ID_LENGTH);
+ pdata->session_id[PROXY_SESSION_ID_LENGTH] = '\0';
+ free(hex);
+
+ if (!(pdata->modules_info = HashTable_New(FALSE)))
+ goto error;
+
+ /* modules_info maps between plugin name to custom data */
+ if (!HashTable_SetupForStringData(pdata->modules_info, FALSE))
+ goto error;
+
+ return pdata;
+error:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ proxy_data_free(pdata);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+/* updates circular pointers between proxyData and pClientContext instances */
+void proxy_data_set_client_context(proxyData* pdata, pClientContext* context)
+{
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(context);
+ pdata->pc = context;
+ context->pdata = pdata;
+}
+
+/* updates circular pointers between proxyData and pServerContext instances */
+void proxy_data_set_server_context(proxyData* pdata, pServerContext* context)
+{
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(context);
+ pdata->ps = context;
+ context->pdata = pdata;
+}
+
+void proxy_data_free(proxyData* pdata)
+{
+ if (!pdata)
+ return;
+
+ if (pdata->abort_event)
+ CloseHandle(pdata->abort_event);
+
+ if (pdata->client_thread)
+ CloseHandle(pdata->client_thread);
+
+ if (pdata->gfx_server_ready)
+ CloseHandle(pdata->gfx_server_ready);
+
+ if (pdata->modules_info)
+ HashTable_Free(pdata->modules_info);
+
+ if (pdata->pc)
+ freerdp_client_context_free(&pdata->pc->context);
+
+ free(pdata);
+}
+
+void proxy_data_abort_connect(proxyData* pdata)
+{
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(pdata->abort_event);
+ SetEvent(pdata->abort_event);
+ if (pdata->pc)
+ freerdp_abort_connect_context(&pdata->pc->context);
+}
+
+BOOL proxy_data_shall_disconnect(proxyData* pdata)
+{
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(pdata->abort_event);
+ return WaitForSingleObject(pdata->abort_event, 0) == WAIT_OBJECT_0;
+}
diff --git a/server/proxy/pf_input.c b/server/proxy/pf_input.c
new file mode 100644
index 0000000..2ed1c14
--- /dev/null
+++ b/server/proxy/pf_input.c
@@ -0,0 +1,206 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@gmail.com>
+ * Copyright 2021 Armin Novak <anovak@thincast.com>
+ * Copyright 2021 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.
+ */
+
+#include <winpr/assert.h>
+
+#include "pf_input.h"
+#include <freerdp/server/proxy/proxy_config.h>
+#include <freerdp/server/proxy/proxy_context.h>
+
+#include "proxy_modules.h"
+
+static BOOL pf_server_check_and_sync_input_state(pClientContext* pc)
+{
+ WINPR_ASSERT(pc);
+
+ if (!freerdp_is_active_state(&pc->context))
+ return FALSE;
+ if (pc->input_state_sync_pending)
+ {
+ BOOL rc = freerdp_input_send_synchronize_event(pc->context.input, pc->input_state);
+ if (rc)
+ pc->input_state_sync_pending = FALSE;
+ }
+ return TRUE;
+}
+
+static BOOL pf_server_synchronize_event(rdpInput* input, UINT32 flags)
+{
+ pServerContext* ps = NULL;
+ pClientContext* pc = NULL;
+
+ WINPR_ASSERT(input);
+ ps = (pServerContext*)input->context;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->pdata);
+
+ pc = ps->pdata->pc;
+ WINPR_ASSERT(pc);
+
+ pc->input_state = flags;
+ pc->input_state_sync_pending = TRUE;
+
+ pf_server_check_and_sync_input_state(pc);
+ return TRUE;
+}
+
+static BOOL pf_server_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code)
+{
+ const proxyConfig* config = NULL;
+ proxyKeyboardEventInfo event = { 0 };
+ pServerContext* ps = NULL;
+ pClientContext* pc = NULL;
+
+ WINPR_ASSERT(input);
+ ps = (pServerContext*)input->context;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->pdata);
+
+ pc = ps->pdata->pc;
+ WINPR_ASSERT(pc);
+
+ config = ps->pdata->config;
+ WINPR_ASSERT(config);
+
+ if (!pf_server_check_and_sync_input_state(pc))
+ return TRUE;
+
+ if (!config->Keyboard)
+ return TRUE;
+
+ event.flags = flags;
+ event.rdp_scan_code = code;
+
+ if (pf_modules_run_filter(pc->pdata->module, FILTER_TYPE_KEYBOARD, pc->pdata, &event))
+ return freerdp_input_send_keyboard_event(pc->context.input, flags, code);
+
+ return TRUE;
+}
+
+static BOOL pf_server_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code)
+{
+ const proxyConfig* config = NULL;
+ proxyUnicodeEventInfo event = { 0 };
+ pServerContext* ps = NULL;
+ pClientContext* pc = NULL;
+
+ WINPR_ASSERT(input);
+ ps = (pServerContext*)input->context;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->pdata);
+
+ pc = ps->pdata->pc;
+ WINPR_ASSERT(pc);
+
+ config = ps->pdata->config;
+ WINPR_ASSERT(config);
+
+ if (!pf_server_check_and_sync_input_state(pc))
+ return TRUE;
+
+ if (!config->Keyboard)
+ return TRUE;
+
+ event.flags = flags;
+ event.code = code;
+ if (pf_modules_run_filter(pc->pdata->module, FILTER_TYPE_UNICODE, pc->pdata, &event))
+ return freerdp_input_send_unicode_keyboard_event(pc->context.input, flags, code);
+ return TRUE;
+}
+
+static BOOL pf_server_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ proxyMouseEventInfo event = { 0 };
+ const proxyConfig* config = NULL;
+ pServerContext* ps = NULL;
+ pClientContext* pc = NULL;
+
+ WINPR_ASSERT(input);
+ ps = (pServerContext*)input->context;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->pdata);
+
+ pc = ps->pdata->pc;
+ WINPR_ASSERT(pc);
+
+ config = ps->pdata->config;
+ WINPR_ASSERT(config);
+
+ if (!pf_server_check_and_sync_input_state(pc))
+ return TRUE;
+
+ if (!config->Mouse)
+ return TRUE;
+
+ event.flags = flags;
+ event.x = x;
+ event.y = y;
+
+ if (pf_modules_run_filter(pc->pdata->module, FILTER_TYPE_MOUSE, pc->pdata, &event))
+ return freerdp_input_send_mouse_event(pc->context.input, flags, x, y);
+
+ return TRUE;
+}
+
+static BOOL pf_server_extended_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
+{
+ const proxyConfig* config = NULL;
+ proxyMouseExEventInfo event = { 0 };
+ pServerContext* ps = NULL;
+ pClientContext* pc = NULL;
+
+ WINPR_ASSERT(input);
+ ps = (pServerContext*)input->context;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->pdata);
+
+ pc = ps->pdata->pc;
+ WINPR_ASSERT(pc);
+
+ config = ps->pdata->config;
+ WINPR_ASSERT(config);
+
+ if (!pf_server_check_and_sync_input_state(pc))
+ return TRUE;
+
+ if (!config->Mouse)
+ return TRUE;
+
+ event.flags = flags;
+ event.x = x;
+ event.y = y;
+ if (pf_modules_run_filter(pc->pdata->module, FILTER_TYPE_MOUSE, pc->pdata, &event))
+ return freerdp_input_send_extended_mouse_event(pc->context.input, flags, x, y);
+ return TRUE;
+}
+
+void pf_server_register_input_callbacks(rdpInput* input)
+{
+ WINPR_ASSERT(input);
+
+ input->SynchronizeEvent = pf_server_synchronize_event;
+ input->KeyboardEvent = pf_server_keyboard_event;
+ input->UnicodeKeyboardEvent = pf_server_unicode_keyboard_event;
+ input->MouseEvent = pf_server_mouse_event;
+ input->ExtendedMouseEvent = pf_server_extended_mouse_event;
+}
diff --git a/server/proxy/pf_input.h b/server/proxy/pf_input.h
new file mode 100644
index 0000000..ea9c542
--- /dev/null
+++ b/server/proxy/pf_input.h
@@ -0,0 +1,29 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@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.
+ */
+
+#ifndef FREERDP_SERVER_PROXY_PFINPUT_H
+#define FREERDP_SERVER_PROXY_PFINPUT_H
+
+#include <freerdp/freerdp.h>
+
+void pf_server_register_input_callbacks(rdpInput* input);
+
+#endif /* FREERDP_SERVER_PROXY_PFINPUT_H */
diff --git a/server/proxy/pf_modules.c b/server/proxy/pf_modules.c
new file mode 100644
index 0000000..e3447cb
--- /dev/null
+++ b/server/proxy/pf_modules.c
@@ -0,0 +1,633 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server modules API
+ *
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@gmail.com>
+ * Copyright 2021 Armin Novak <anovak@thincast.com>
+ * Copyright 2021 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.
+ */
+
+#include <winpr/assert.h>
+
+#include <winpr/file.h>
+#include <winpr/wlog.h>
+#include <winpr/path.h>
+#include <winpr/library.h>
+#include <freerdp/api.h>
+#include <freerdp/build-config.h>
+
+#include <freerdp/server/proxy/proxy_log.h>
+#include <freerdp/server/proxy/proxy_modules_api.h>
+
+#include <freerdp/server/proxy/proxy_context.h>
+#include "proxy_modules.h"
+
+#define TAG PROXY_TAG("modules")
+
+#define MODULE_ENTRY_POINT "proxy_module_entry_point"
+
+struct proxy_module
+{
+ proxyPluginsManager mgr;
+ wArrayList* plugins;
+ wArrayList* handles;
+};
+
+static const char* pf_modules_get_filter_type_string(PF_FILTER_TYPE result)
+{
+ switch (result)
+ {
+ case FILTER_TYPE_KEYBOARD:
+ return "FILTER_TYPE_KEYBOARD";
+ case FILTER_TYPE_UNICODE:
+ return "FILTER_TYPE_UNICODE";
+ case FILTER_TYPE_MOUSE:
+ return "FILTER_TYPE_MOUSE";
+ case FILTER_TYPE_MOUSE_EX:
+ return "FILTER_TYPE_MOUSE_EX";
+ case FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA:
+ return "FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA";
+ case FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA:
+ return "FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA";
+ case FILTER_TYPE_CLIENT_PASSTHROUGH_DYN_CHANNEL_CREATE:
+ return "FILTER_TYPE_CLIENT_PASSTHROUGH_DYN_CHANNEL_CREATE";
+ case FILTER_TYPE_SERVER_FETCH_TARGET_ADDR:
+ return "FILTER_TYPE_SERVER_FETCH_TARGET_ADDR";
+ case FILTER_TYPE_SERVER_PEER_LOGON:
+ return "FILTER_TYPE_SERVER_PEER_LOGON";
+ case FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_CREATE:
+ return "FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_CREATE";
+ case FILTER_LAST:
+ return "FILTER_LAST";
+ default:
+ return "FILTER_UNKNOWN";
+ }
+}
+
+static const char* pf_modules_get_hook_type_string(PF_HOOK_TYPE result)
+{
+ switch (result)
+ {
+ case HOOK_TYPE_CLIENT_INIT_CONNECT:
+ return "HOOK_TYPE_CLIENT_INIT_CONNECT";
+ case HOOK_TYPE_CLIENT_UNINIT_CONNECT:
+ return "HOOK_TYPE_CLIENT_UNINIT_CONNECT";
+ case HOOK_TYPE_CLIENT_PRE_CONNECT:
+ return "HOOK_TYPE_CLIENT_PRE_CONNECT";
+ case HOOK_TYPE_CLIENT_POST_CONNECT:
+ return "HOOK_TYPE_CLIENT_POST_CONNECT";
+ case HOOK_TYPE_CLIENT_POST_DISCONNECT:
+ return "HOOK_TYPE_CLIENT_POST_DISCONNECT";
+ case HOOK_TYPE_CLIENT_REDIRECT:
+ return "HOOK_TYPE_CLIENT_REDIRECT";
+ case HOOK_TYPE_CLIENT_VERIFY_X509:
+ return "HOOK_TYPE_CLIENT_VERIFY_X509";
+ case HOOK_TYPE_CLIENT_LOGIN_FAILURE:
+ return "HOOK_TYPE_CLIENT_LOGIN_FAILURE";
+ case HOOK_TYPE_CLIENT_END_PAINT:
+ return "HOOK_TYPE_CLIENT_END_PAINT";
+ case HOOK_TYPE_SERVER_POST_CONNECT:
+ return "HOOK_TYPE_SERVER_POST_CONNECT";
+ case HOOK_TYPE_SERVER_ACTIVATE:
+ return "HOOK_TYPE_SERVER_ACTIVATE";
+ case HOOK_TYPE_SERVER_CHANNELS_INIT:
+ return "HOOK_TYPE_SERVER_CHANNELS_INIT";
+ case HOOK_TYPE_SERVER_CHANNELS_FREE:
+ return "HOOK_TYPE_SERVER_CHANNELS_FREE";
+ case HOOK_TYPE_SERVER_SESSION_END:
+ return "HOOK_TYPE_SERVER_SESSION_END";
+ case HOOK_TYPE_CLIENT_LOAD_CHANNELS:
+ return "HOOK_TYPE_CLIENT_LOAD_CHANNELS";
+ case HOOK_TYPE_SERVER_SESSION_INITIALIZE:
+ return "HOOK_TYPE_SERVER_SESSION_INITIALIZE";
+ case HOOK_TYPE_SERVER_SESSION_STARTED:
+ return "HOOK_TYPE_SERVER_SESSION_STARTED";
+ case HOOK_LAST:
+ return "HOOK_LAST";
+ default:
+ return "HOOK_TYPE_UNKNOWN";
+ }
+}
+
+static BOOL pf_modules_proxy_ArrayList_ForEachFkt(void* data, size_t index, va_list ap)
+{
+ proxyPlugin* plugin = (proxyPlugin*)data;
+ BOOL ok = FALSE;
+
+ WINPR_UNUSED(index);
+
+ PF_HOOK_TYPE type = va_arg(ap, PF_HOOK_TYPE);
+ proxyData* pdata = va_arg(ap, proxyData*);
+ void* custom = va_arg(ap, void*);
+
+ WLog_VRB(TAG, "running hook %s.%s", plugin->name, pf_modules_get_hook_type_string(type));
+
+ switch (type)
+ {
+ case HOOK_TYPE_CLIENT_INIT_CONNECT:
+ ok = IFCALLRESULT(TRUE, plugin->ClientInitConnect, plugin, pdata, custom);
+ break;
+ case HOOK_TYPE_CLIENT_UNINIT_CONNECT:
+ ok = IFCALLRESULT(TRUE, plugin->ClientUninitConnect, plugin, pdata, custom);
+ break;
+ case HOOK_TYPE_CLIENT_PRE_CONNECT:
+ ok = IFCALLRESULT(TRUE, plugin->ClientPreConnect, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_CLIENT_POST_CONNECT:
+ ok = IFCALLRESULT(TRUE, plugin->ClientPostConnect, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_CLIENT_REDIRECT:
+ ok = IFCALLRESULT(TRUE, plugin->ClientRedirect, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_CLIENT_POST_DISCONNECT:
+ ok = IFCALLRESULT(TRUE, plugin->ClientPostDisconnect, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_CLIENT_VERIFY_X509:
+ ok = IFCALLRESULT(TRUE, plugin->ClientX509Certificate, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_CLIENT_LOGIN_FAILURE:
+ ok = IFCALLRESULT(TRUE, plugin->ClientLoginFailure, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_CLIENT_END_PAINT:
+ ok = IFCALLRESULT(TRUE, plugin->ClientEndPaint, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_CLIENT_LOAD_CHANNELS:
+ ok = IFCALLRESULT(TRUE, plugin->ClientLoadChannels, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_SERVER_POST_CONNECT:
+ ok = IFCALLRESULT(TRUE, plugin->ServerPostConnect, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_SERVER_ACTIVATE:
+ ok = IFCALLRESULT(TRUE, plugin->ServerPeerActivate, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_SERVER_CHANNELS_INIT:
+ ok = IFCALLRESULT(TRUE, plugin->ServerChannelsInit, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_SERVER_CHANNELS_FREE:
+ ok = IFCALLRESULT(TRUE, plugin->ServerChannelsFree, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_SERVER_SESSION_END:
+ ok = IFCALLRESULT(TRUE, plugin->ServerSessionEnd, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_SERVER_SESSION_INITIALIZE:
+ ok = IFCALLRESULT(TRUE, plugin->ServerSessionInitialize, plugin, pdata, custom);
+ break;
+
+ case HOOK_TYPE_SERVER_SESSION_STARTED:
+ ok = IFCALLRESULT(TRUE, plugin->ServerSessionStarted, plugin, pdata, custom);
+ break;
+
+ case HOOK_LAST:
+ default:
+ WLog_ERR(TAG, "invalid hook called");
+ }
+
+ if (!ok)
+ {
+ WLog_INFO(TAG, "plugin %s, hook %s failed!", plugin->name,
+ pf_modules_get_hook_type_string(type));
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/*
+ * runs all hooks of type `type`.
+ *
+ * @type: hook type to run.
+ * @server: pointer of server's rdpContext struct of the current session.
+ */
+BOOL pf_modules_run_hook(proxyModule* module, PF_HOOK_TYPE type, proxyData* pdata, void* custom)
+{
+ WINPR_ASSERT(module);
+ WINPR_ASSERT(module->plugins);
+ return ArrayList_ForEach(module->plugins, pf_modules_proxy_ArrayList_ForEachFkt, type, pdata,
+ custom);
+}
+
+static BOOL pf_modules_ArrayList_ForEachFkt(void* data, size_t index, va_list ap)
+{
+ proxyPlugin* plugin = (proxyPlugin*)data;
+ BOOL result = FALSE;
+
+ WINPR_UNUSED(index);
+
+ PF_FILTER_TYPE type = va_arg(ap, PF_FILTER_TYPE);
+ proxyData* pdata = va_arg(ap, proxyData*);
+ void* param = va_arg(ap, void*);
+
+ WLog_VRB(TAG, "running filter: %s", plugin->name);
+
+ switch (type)
+ {
+ case FILTER_TYPE_KEYBOARD:
+ result = IFCALLRESULT(TRUE, plugin->KeyboardEvent, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_UNICODE:
+ result = IFCALLRESULT(TRUE, plugin->UnicodeEvent, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_MOUSE:
+ result = IFCALLRESULT(TRUE, plugin->MouseEvent, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_MOUSE_EX:
+ result = IFCALLRESULT(TRUE, plugin->MouseExEvent, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA:
+ result = IFCALLRESULT(TRUE, plugin->ClientChannelData, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA:
+ result = IFCALLRESULT(TRUE, plugin->ServerChannelData, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_CREATE:
+ result = IFCALLRESULT(TRUE, plugin->ChannelCreate, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_CLIENT_PASSTHROUGH_DYN_CHANNEL_CREATE:
+ result = IFCALLRESULT(TRUE, plugin->DynamicChannelCreate, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_SERVER_FETCH_TARGET_ADDR:
+ result = IFCALLRESULT(TRUE, plugin->ServerFetchTargetAddr, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_SERVER_PEER_LOGON:
+ result = IFCALLRESULT(TRUE, plugin->ServerPeerLogon, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_INTERCEPT_CHANNEL:
+ result = IFCALLRESULT(TRUE, plugin->DynChannelIntercept, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_DYN_INTERCEPT_LIST:
+ result = IFCALLRESULT(TRUE, plugin->DynChannelToIntercept, plugin, pdata, param);
+ break;
+
+ case FILTER_TYPE_STATIC_INTERCEPT_LIST:
+ result = IFCALLRESULT(TRUE, plugin->StaticChannelToIntercept, plugin, pdata, param);
+ break;
+
+ case FILTER_LAST:
+ default:
+ WLog_ERR(TAG, "invalid filter called");
+ }
+
+ if (!result)
+ {
+ /* current filter return FALSE, no need to run other filters. */
+ WLog_DBG(TAG, "plugin %s, filter type [%s] returned FALSE", plugin->name,
+ pf_modules_get_filter_type_string(type));
+ }
+ return result;
+}
+
+/*
+ * runs all filters of type `type`.
+ *
+ * @type: filter type to run.
+ * @server: pointer of server's rdpContext struct of the current session.
+ */
+BOOL pf_modules_run_filter(proxyModule* module, PF_FILTER_TYPE type, proxyData* pdata, void* param)
+{
+ WINPR_ASSERT(module);
+ WINPR_ASSERT(module->plugins);
+
+ return ArrayList_ForEach(module->plugins, pf_modules_ArrayList_ForEachFkt, type, pdata, param);
+}
+
+/*
+ * stores per-session data needed by a plugin.
+ *
+ * @context: current session server's rdpContext instance.
+ * @info: pointer to per-session data.
+ */
+static BOOL pf_modules_set_plugin_data(proxyPluginsManager* mgr, const char* plugin_name,
+ proxyData* pdata, void* data)
+{
+ union
+ {
+ const char* ccp;
+ char* cp;
+ } ccharconv;
+
+ WINPR_ASSERT(plugin_name);
+
+ ccharconv.ccp = plugin_name;
+ if (data == NULL) /* no need to store anything */
+ return FALSE;
+
+ if (!HashTable_Insert(pdata->modules_info, ccharconv.cp, data))
+ {
+ WLog_ERR(TAG, "[%s]: HashTable_Insert failed!");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*
+ * returns per-session data needed a plugin.
+ *
+ * @context: current session server's rdpContext instance.
+ * if there's no data related to `plugin_name` in `context` (current session), a NULL will be
+ * returned.
+ */
+static void* pf_modules_get_plugin_data(proxyPluginsManager* mgr, const char* plugin_name,
+ proxyData* pdata)
+{
+ union
+ {
+ const char* ccp;
+ char* cp;
+ } ccharconv;
+ WINPR_ASSERT(plugin_name);
+ WINPR_ASSERT(pdata);
+ ccharconv.ccp = plugin_name;
+
+ return HashTable_GetItemValue(pdata->modules_info, ccharconv.cp);
+}
+
+static void pf_modules_abort_connect(proxyPluginsManager* mgr, proxyData* pdata)
+{
+ WINPR_ASSERT(pdata);
+ WLog_DBG(TAG, "is called!");
+ proxy_data_abort_connect(pdata);
+}
+
+static BOOL pf_modules_register_ArrayList_ForEachFkt(void* data, size_t index, va_list ap)
+{
+ proxyPlugin* plugin = (proxyPlugin*)data;
+ proxyPlugin* plugin_to_register = va_arg(ap, proxyPlugin*);
+
+ WINPR_UNUSED(index);
+
+ if (strcmp(plugin->name, plugin_to_register->name) == 0)
+ {
+ WLog_ERR(TAG, "can not register plugin '%s', it is already registered!", plugin->name);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static BOOL pf_modules_register_plugin(proxyPluginsManager* mgr,
+ const proxyPlugin* plugin_to_register)
+{
+ proxyPlugin internal = { 0 };
+ proxyModule* module = (proxyModule*)mgr;
+ WINPR_ASSERT(module);
+
+ if (!plugin_to_register)
+ return FALSE;
+
+ internal = *plugin_to_register;
+ internal.mgr = mgr;
+
+ /* make sure there's no other loaded plugin with the same name of `plugin_to_register`. */
+ if (!ArrayList_ForEach(module->plugins, pf_modules_register_ArrayList_ForEachFkt, &internal))
+ return FALSE;
+
+ if (!ArrayList_Append(module->plugins, &internal))
+ {
+ WLog_ERR(TAG, "failed adding plugin to list: %s", plugin_to_register->name);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL pf_modules_load_ArrayList_ForEachFkt(void* data, size_t index, va_list ap)
+{
+ proxyPlugin* plugin = (proxyPlugin*)data;
+ const char* plugin_name = va_arg(ap, const char*);
+ BOOL* res = va_arg(ap, BOOL*);
+
+ WINPR_UNUSED(index);
+ WINPR_UNUSED(ap);
+ WINPR_ASSERT(res);
+
+ if (strcmp(plugin->name, plugin_name) == 0)
+ *res = TRUE;
+ return TRUE;
+}
+
+BOOL pf_modules_is_plugin_loaded(proxyModule* module, const char* plugin_name)
+{
+ BOOL rc = FALSE;
+ WINPR_ASSERT(module);
+ if (ArrayList_Count(module->plugins) < 1)
+ return FALSE;
+ if (!ArrayList_ForEach(module->plugins, pf_modules_load_ArrayList_ForEachFkt, plugin_name, &rc))
+ return FALSE;
+ return rc;
+}
+
+static BOOL pf_modules_print_ArrayList_ForEachFkt(void* data, size_t index, va_list ap)
+{
+ proxyPlugin* plugin = (proxyPlugin*)data;
+
+ WINPR_UNUSED(index);
+ WINPR_UNUSED(ap);
+
+ WLog_INFO(TAG, "\tName: %s", plugin->name);
+ WLog_INFO(TAG, "\tDescription: %s", plugin->description);
+ return TRUE;
+}
+
+void pf_modules_list_loaded_plugins(proxyModule* module)
+{
+ size_t count = 0;
+
+ WINPR_ASSERT(module);
+ WINPR_ASSERT(module->plugins);
+
+ count = ArrayList_Count(module->plugins);
+
+ if (count > 0)
+ WLog_INFO(TAG, "Loaded plugins:");
+
+ ArrayList_ForEach(module->plugins, pf_modules_print_ArrayList_ForEachFkt);
+}
+
+static BOOL pf_modules_load_module(const char* module_path, proxyModule* module, void* userdata)
+{
+ HMODULE handle = NULL;
+ proxyModuleEntryPoint pEntryPoint = NULL;
+ WINPR_ASSERT(module);
+
+ handle = LoadLibraryX(module_path);
+
+ if (handle == NULL)
+ {
+ WLog_ERR(TAG, "failed loading external library: %s", module_path);
+ return FALSE;
+ }
+
+ pEntryPoint = (proxyModuleEntryPoint)GetProcAddress(handle, MODULE_ENTRY_POINT);
+ if (!pEntryPoint)
+ {
+ WLog_ERR(TAG, "GetProcAddress failed while loading %s", module_path);
+ goto error;
+ }
+ if (!ArrayList_Append(module->handles, handle))
+ {
+ WLog_ERR(TAG, "ArrayList_Append failed!");
+ goto error;
+ }
+ return pf_modules_add(module, pEntryPoint, userdata);
+
+error:
+ FreeLibrary(handle);
+ return FALSE;
+}
+
+static void free_handle(void* obj)
+{
+ HANDLE handle = (HANDLE)obj;
+ if (handle)
+ FreeLibrary(handle);
+}
+
+static void free_plugin(void* obj)
+{
+ proxyPlugin* plugin = (proxyPlugin*)obj;
+ WINPR_ASSERT(plugin);
+
+ if (!IFCALLRESULT(TRUE, plugin->PluginUnload, plugin))
+ WLog_WARN(TAG, "PluginUnload failed for plugin '%s'", plugin->name);
+
+ free(plugin);
+}
+
+static void* new_plugin(const void* obj)
+{
+ const proxyPlugin* src = obj;
+ proxyPlugin* proxy = calloc(1, sizeof(proxyPlugin));
+ if (!proxy)
+ return NULL;
+ *proxy = *src;
+ return proxy;
+}
+
+proxyModule* pf_modules_new(const char* root_dir, const char** modules, size_t count)
+{
+ wObject* obj = NULL;
+ char* path = NULL;
+ proxyModule* module = calloc(1, sizeof(proxyModule));
+ if (!module)
+ return NULL;
+
+ module->mgr.RegisterPlugin = pf_modules_register_plugin;
+ module->mgr.SetPluginData = pf_modules_set_plugin_data;
+ module->mgr.GetPluginData = pf_modules_get_plugin_data;
+ module->mgr.AbortConnect = pf_modules_abort_connect;
+ module->plugins = ArrayList_New(FALSE);
+
+ if (module->plugins == NULL)
+ {
+ WLog_ERR(TAG, "ArrayList_New failed!");
+ goto error;
+ }
+ obj = ArrayList_Object(module->plugins);
+ WINPR_ASSERT(obj);
+
+ obj->fnObjectFree = free_plugin;
+ obj->fnObjectNew = new_plugin;
+
+ module->handles = ArrayList_New(FALSE);
+ if (module->handles == NULL)
+ {
+
+ WLog_ERR(TAG, "ArrayList_New failed!");
+ goto error;
+ }
+ ArrayList_Object(module->handles)->fnObjectFree = free_handle;
+
+ if (count > 0)
+ {
+ WINPR_ASSERT(root_dir);
+ if (!winpr_PathFileExists(root_dir))
+ path = GetCombinedPath(FREERDP_INSTALL_PREFIX, root_dir);
+ else
+ path = _strdup(root_dir);
+
+ if (!winpr_PathFileExists(path))
+ {
+ if (!winpr_PathMakePath(path, NULL))
+ {
+ WLog_ERR(TAG, "error occurred while creating modules directory: %s", root_dir);
+ goto error;
+ }
+ }
+
+ if (winpr_PathFileExists(path))
+ WLog_DBG(TAG, "modules root directory: %s", path);
+
+ for (size_t i = 0; i < count; i++)
+ {
+ char name[8192] = { 0 };
+ char* fullpath = NULL;
+ _snprintf(name, sizeof(name), "proxy-%s-plugin%s", modules[i],
+ FREERDP_SHARED_LIBRARY_SUFFIX);
+ fullpath = GetCombinedPath(path, name);
+ pf_modules_load_module(fullpath, module, NULL);
+ free(fullpath);
+ }
+ }
+
+ free(path);
+ return module;
+
+error:
+ free(path);
+ pf_modules_free(module);
+ return NULL;
+}
+
+void pf_modules_free(proxyModule* module)
+{
+ if (!module)
+ return;
+
+ ArrayList_Free(module->plugins);
+ ArrayList_Free(module->handles);
+ free(module);
+}
+
+BOOL pf_modules_add(proxyModule* module, proxyModuleEntryPoint ep, void* userdata)
+{
+ WINPR_ASSERT(module);
+ WINPR_ASSERT(ep);
+
+ return ep(&module->mgr, userdata);
+}
diff --git a/server/proxy/pf_server.c b/server/proxy/pf_server.c
new file mode 100644
index 0000000..545ab93
--- /dev/null
+++ b/server/proxy/pf_server.c
@@ -0,0 +1,1073 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@gmail.com>
+ * Copyright 2021 Armin Novak <anovak@thincast.com>
+ * Copyright 2021 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/ssl.h>
+#include <winpr/path.h>
+#include <winpr/synch.h>
+#include <winpr/string.h>
+#include <winpr/winsock.h>
+#include <winpr/thread.h>
+#include <errno.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/streamdump.h>
+#include <freerdp/channels/wtsvc.h>
+#include <freerdp/channels/channels.h>
+#include <freerdp/channels/drdynvc.h>
+#include <freerdp/build-config.h>
+
+#include <freerdp/channels/rdpdr.h>
+
+#include <freerdp/server/proxy/proxy_server.h>
+#include <freerdp/server/proxy/proxy_log.h>
+
+#include "pf_server.h"
+#include "pf_channel.h"
+#include <freerdp/server/proxy/proxy_config.h>
+#include "pf_client.h"
+#include <freerdp/server/proxy/proxy_context.h>
+#include "pf_update.h"
+#include "proxy_modules.h"
+#include "pf_utils.h"
+#include "channels/pf_channel_drdynvc.h"
+#include "channels/pf_channel_rdpdr.h"
+
+#define TAG PROXY_TAG("server")
+
+typedef struct
+{
+ HANDLE thread;
+ freerdp_peer* client;
+} peer_thread_args;
+
+static BOOL pf_server_parse_target_from_routing_token(rdpContext* context, rdpSettings* settings,
+ FreeRDP_Settings_Keys_String targetID,
+ FreeRDP_Settings_Keys_UInt32 portID)
+{
+#define TARGET_MAX (100)
+#define ROUTING_TOKEN_PREFIX "Cookie: msts="
+ char* colon = NULL;
+ size_t len = 0;
+ DWORD routing_token_length = 0;
+ const size_t prefix_len = strnlen(ROUTING_TOKEN_PREFIX, sizeof(ROUTING_TOKEN_PREFIX));
+ const char* routing_token = freerdp_nego_get_routing_token(context, &routing_token_length);
+ pServerContext* ps = (pServerContext*)context;
+
+ if (!routing_token)
+ return FALSE;
+
+ if ((routing_token_length <= prefix_len) || (routing_token_length >= TARGET_MAX))
+ {
+ PROXY_LOG_ERR(TAG, ps, "invalid routing token length: %" PRIu32 "", routing_token_length);
+ return FALSE;
+ }
+
+ len = routing_token_length - prefix_len;
+
+ if (!freerdp_settings_set_string_len(settings, targetID, routing_token + prefix_len, len))
+ return FALSE;
+
+ const char* target = freerdp_settings_get_string(settings, targetID);
+ colon = strchr(target, ':');
+
+ if (colon)
+ {
+ /* port is specified */
+ unsigned long p = strtoul(colon + 1, NULL, 10);
+
+ if (p > USHRT_MAX)
+ return FALSE;
+
+ if (!freerdp_settings_set_uint32(settings, portID, p))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL pf_server_get_target_info(rdpContext* context, rdpSettings* settings,
+ const proxyConfig* config)
+{
+ pServerContext* ps = (pServerContext*)context;
+ proxyFetchTargetEventInfo ev = { 0 };
+
+ WINPR_ASSERT(settings);
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->pdata);
+
+ ev.fetch_method = config->FixedTarget ? PROXY_FETCH_TARGET_METHOD_CONFIG
+ : PROXY_FETCH_TARGET_METHOD_LOAD_BALANCE_INFO;
+
+ if (!pf_modules_run_filter(ps->pdata->module, FILTER_TYPE_SERVER_FETCH_TARGET_ADDR, ps->pdata,
+ &ev))
+ return FALSE;
+
+ switch (ev.fetch_method)
+ {
+ case PROXY_FETCH_TARGET_METHOD_DEFAULT:
+ case PROXY_FETCH_TARGET_METHOD_LOAD_BALANCE_INFO:
+ return pf_server_parse_target_from_routing_token(
+ context, settings, FreeRDP_ServerHostname, FreeRDP_ServerPort);
+
+ case PROXY_FETCH_TARGET_METHOD_CONFIG:
+ {
+ WINPR_ASSERT(config);
+
+ if (config->TargetPort > 0)
+ freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, config->TargetPort);
+ else
+ freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, 3389);
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_TlsSecLevel,
+ config->TargetTlsSecLevel))
+ return FALSE;
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_ServerHostname, config->TargetHost))
+ {
+ PROXY_LOG_ERR(TAG, ps, "strdup failed!");
+ return FALSE;
+ }
+
+ if (config->TargetUser)
+ freerdp_settings_set_string(settings, FreeRDP_Username, config->TargetUser);
+
+ if (config->TargetDomain)
+ freerdp_settings_set_string(settings, FreeRDP_Domain, config->TargetDomain);
+
+ if (config->TargetPassword)
+ freerdp_settings_set_string(settings, FreeRDP_Password, config->TargetPassword);
+
+ return TRUE;
+ }
+ case PROXY_FETCH_TARGET_USE_CUSTOM_ADDR:
+ {
+ if (!ev.target_address)
+ {
+ PROXY_LOG_ERR(TAG, ps,
+ "router: using CUSTOM_ADDR fetch method, but target_address == NULL");
+ return FALSE;
+ }
+
+ if (!freerdp_settings_set_string(settings, FreeRDP_ServerHostname, ev.target_address))
+ {
+ PROXY_LOG_ERR(TAG, ps, "strdup failed!");
+ return FALSE;
+ }
+
+ free(ev.target_address);
+ return freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, ev.target_port);
+ }
+ default:
+ PROXY_LOG_ERR(TAG, ps, "unknown target fetch method: %d", ev.fetch_method);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL pf_server_setup_channels(freerdp_peer* peer)
+{
+ char** accepted_channels = NULL;
+ size_t accepted_channels_count = 0;
+ pServerContext* ps = (pServerContext*)peer->context;
+
+ accepted_channels = WTSGetAcceptedChannelNames(peer, &accepted_channels_count);
+ if (!accepted_channels)
+ return TRUE;
+
+ for (size_t i = 0; i < accepted_channels_count; i++)
+ {
+ pServerStaticChannelContext* channelContext = NULL;
+ const char* cname = accepted_channels[i];
+ UINT16 channelId = WTSChannelGetId(peer, cname);
+
+ PROXY_LOG_INFO(TAG, ps, "Accepted channel: %s (%" PRIu16 ")", cname, channelId);
+ channelContext = StaticChannelContext_new(ps, cname, channelId);
+ if (!channelContext)
+ {
+ PROXY_LOG_ERR(TAG, ps, "error seting up channelContext for '%s'", cname);
+ return FALSE;
+ }
+
+ if (strcmp(cname, DRDYNVC_SVC_CHANNEL_NAME) == 0)
+ {
+ if (!pf_channel_setup_drdynvc(ps->pdata, channelContext))
+ {
+ PROXY_LOG_ERR(TAG, ps, "error while setting up dynamic channel");
+ StaticChannelContext_free(channelContext);
+ return FALSE;
+ }
+ }
+ else if (strcmp(cname, RDPDR_SVC_CHANNEL_NAME) == 0 &&
+ (channelContext->channelMode == PF_UTILS_CHANNEL_INTERCEPT))
+ {
+ if (!pf_channel_setup_rdpdr(ps, channelContext))
+ {
+ PROXY_LOG_ERR(TAG, ps, "error while setting up redirection channel");
+ StaticChannelContext_free(channelContext);
+ return FALSE;
+ }
+ }
+ else
+ {
+ if (!pf_channel_setup_generic(channelContext))
+ {
+ PROXY_LOG_ERR(TAG, ps, "error while setting up generic channel");
+ StaticChannelContext_free(channelContext);
+ return FALSE;
+ }
+ }
+
+ if (!HashTable_Insert(ps->channelsByFrontId, &channelContext->front_channel_id,
+ channelContext))
+ {
+ StaticChannelContext_free(channelContext);
+ PROXY_LOG_ERR(TAG, ps, "error inserting channelContext in byId table for '%s'", cname);
+ return FALSE;
+ }
+ }
+
+ free(accepted_channels);
+ return TRUE;
+}
+
+/* Event callbacks */
+
+/**
+ * This callback is called when the entire connection sequence is done (as
+ * described in MS-RDPBCGR section 1.3)
+ *
+ * The server may start sending graphics output and receiving keyboard/mouse
+ * input after this callback returns.
+ */
+static BOOL pf_server_post_connect(freerdp_peer* peer)
+{
+ pServerContext* ps = NULL;
+ pClientContext* pc = NULL;
+ rdpSettings* client_settings = NULL;
+ proxyData* pdata = NULL;
+ rdpSettings* frontSettings = NULL;
+
+ WINPR_ASSERT(peer);
+
+ ps = (pServerContext*)peer->context;
+ WINPR_ASSERT(ps);
+
+ frontSettings = peer->context->settings;
+ WINPR_ASSERT(frontSettings);
+
+ pdata = ps->pdata;
+ WINPR_ASSERT(pdata);
+
+ const char* ClientHostname = freerdp_settings_get_string(frontSettings, FreeRDP_ClientHostname);
+ PROXY_LOG_INFO(TAG, ps, "Accepted client: %s", ClientHostname);
+ if (!pf_server_setup_channels(peer))
+ {
+ PROXY_LOG_ERR(TAG, ps, "error setting up channels");
+ return FALSE;
+ }
+
+ pc = pf_context_create_client_context(frontSettings);
+ if (pc == NULL)
+ {
+ PROXY_LOG_ERR(TAG, ps, "failed to create client context!");
+ return FALSE;
+ }
+
+ client_settings = pc->context.settings;
+
+ /* keep both sides of the connection in pdata */
+ proxy_data_set_client_context(pdata, pc);
+
+ if (!pf_server_get_target_info(peer->context, client_settings, pdata->config))
+ {
+ PROXY_LOG_INFO(TAG, ps, "pf_server_get_target_info failed!");
+ return FALSE;
+ }
+
+ PROXY_LOG_INFO(TAG, ps, "remote target is %s:%" PRIu32 "",
+ freerdp_settings_get_string(client_settings, FreeRDP_ServerHostname),
+ freerdp_settings_get_uint32(client_settings, FreeRDP_ServerPort));
+
+ if (!pf_modules_run_hook(pdata->module, HOOK_TYPE_SERVER_POST_CONNECT, pdata, peer))
+ return FALSE;
+
+ /* Start a proxy's client in it's own thread */
+ if (!(pdata->client_thread = CreateThread(NULL, 0, pf_client_start, pc, 0, NULL)))
+ {
+ PROXY_LOG_ERR(TAG, ps, "failed to create client thread");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL pf_server_activate(freerdp_peer* peer)
+{
+ pServerContext* ps = NULL;
+ proxyData* pdata = NULL;
+ rdpSettings* settings = NULL;
+
+ WINPR_ASSERT(peer);
+
+ ps = (pServerContext*)peer->context;
+ WINPR_ASSERT(ps);
+
+ pdata = ps->pdata;
+ WINPR_ASSERT(pdata);
+
+ settings = peer->context->settings;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_CompressionLevel, PACKET_COMPR_TYPE_RDP8))
+ return FALSE;
+ if (!pf_modules_run_hook(pdata->module, HOOK_TYPE_SERVER_ACTIVATE, pdata, peer))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL pf_server_logon(freerdp_peer* peer, const SEC_WINNT_AUTH_IDENTITY* identity,
+ BOOL automatic)
+{
+ pServerContext* ps = NULL;
+ proxyData* pdata = NULL;
+ proxyServerPeerLogon info = { 0 };
+
+ WINPR_ASSERT(peer);
+
+ ps = (pServerContext*)peer->context;
+ WINPR_ASSERT(ps);
+
+ pdata = ps->pdata;
+ WINPR_ASSERT(pdata);
+ WINPR_ASSERT(identity);
+
+ info.identity = identity;
+ info.automatic = automatic;
+ if (!pf_modules_run_filter(pdata->module, FILTER_TYPE_SERVER_PEER_LOGON, pdata, &info))
+ return FALSE;
+ return TRUE;
+}
+
+static BOOL pf_server_adjust_monitor_layout(freerdp_peer* peer)
+{
+ WINPR_ASSERT(peer);
+ /* proxy as is, there's no need to do anything here */
+ return TRUE;
+}
+
+static BOOL pf_server_receive_channel_data_hook(freerdp_peer* peer, UINT16 channelId,
+ const BYTE* data, size_t size, UINT32 flags,
+ size_t totalSize)
+{
+ pServerContext* ps = NULL;
+ pClientContext* pc = NULL;
+ proxyData* pdata = NULL;
+ const proxyConfig* config = NULL;
+ const pServerStaticChannelContext* channel = NULL;
+ UINT64 channelId64 = channelId;
+
+ WINPR_ASSERT(peer);
+
+ ps = (pServerContext*)peer->context;
+ WINPR_ASSERT(ps);
+
+ pdata = ps->pdata;
+ WINPR_ASSERT(pdata);
+
+ pc = pdata->pc;
+ config = pdata->config;
+ WINPR_ASSERT(config);
+ /*
+ * client side is not initialized yet, call original callback.
+ * this is probably a drdynvc message between peer and proxy server,
+ * which doesn't need to be proxied.
+ */
+ if (!pc)
+ goto original_cb;
+
+ channel = HashTable_GetItemValue(ps->channelsByFrontId, &channelId64);
+ if (!channel)
+ {
+ PROXY_LOG_ERR(TAG, ps, "channel id=%" PRIu64 " not registered here, dropping", channelId64);
+ return TRUE;
+ }
+
+ WINPR_ASSERT(channel->onFrontData);
+ switch (channel->onFrontData(pdata, channel, data, size, flags, totalSize))
+ {
+ case PF_CHANNEL_RESULT_PASS:
+ {
+ proxyChannelDataEventInfo ev = { 0 };
+
+ ev.channel_id = channelId;
+ ev.channel_name = channel->channel_name;
+ ev.data = data;
+ ev.data_len = size;
+ ev.flags = flags;
+ ev.total_size = totalSize;
+ return IFCALLRESULT(TRUE, pc->sendChannelData, pc, &ev);
+ }
+ case PF_CHANNEL_RESULT_DROP:
+ return TRUE;
+ case PF_CHANNEL_RESULT_ERROR:
+ return FALSE;
+ }
+
+original_cb:
+ WINPR_ASSERT(pdata->server_receive_channel_data_original);
+ return pdata->server_receive_channel_data_original(peer, channelId, data, size, flags,
+ totalSize);
+}
+
+static BOOL pf_server_initialize_peer_connection(freerdp_peer* peer)
+{
+ WINPR_ASSERT(peer);
+
+ pServerContext* ps = (pServerContext*)peer->context;
+ if (!ps)
+ return FALSE;
+
+ rdpSettings* settings = peer->context->settings;
+ WINPR_ASSERT(settings);
+
+ proxyData* pdata = proxy_data_new();
+ if (!pdata)
+ return FALSE;
+ proxyServer* server = (proxyServer*)peer->ContextExtra;
+ WINPR_ASSERT(server);
+ proxy_data_set_server_context(pdata, ps);
+
+ pdata->module = server->module;
+ const proxyConfig* config = pdata->config = server->config;
+
+ rdpPrivateKey* key = freerdp_key_new_from_pem(config->PrivateKeyPEM);
+ if (!key)
+ return FALSE;
+
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerRsaKey, key, 1))
+ return FALSE;
+
+ rdpCertificate* cert = freerdp_certificate_new_from_pem(config->CertificatePEM);
+ if (!cert)
+ return FALSE;
+
+ if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RdpServerCertificate, cert, 1))
+ return FALSE;
+
+ /* currently not supporting GDI orders */
+ {
+ void* OrderSupport = freerdp_settings_get_pointer_writable(settings, FreeRDP_OrderSupport);
+ ZeroMemory(OrderSupport, 32);
+ }
+
+ WINPR_ASSERT(peer->context->update);
+ peer->context->update->autoCalculateBitmapData = FALSE;
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportMonitorLayoutPdu, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGraphicsPipeline, config->GFX))
+ return FALSE;
+
+ if (pf_utils_is_passthrough(config))
+ {
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DeactivateClientDecoding, TRUE))
+ return FALSE;
+ }
+
+ if (config->RemoteApp)
+ {
+ const UINT32 mask =
+ RAIL_LEVEL_SUPPORTED | RAIL_LEVEL_DOCKED_LANGBAR_SUPPORTED |
+ RAIL_LEVEL_SHELL_INTEGRATION_SUPPORTED | RAIL_LEVEL_LANGUAGE_IME_SYNC_SUPPORTED |
+ RAIL_LEVEL_SERVER_TO_CLIENT_IME_SYNC_SUPPORTED |
+ RAIL_LEVEL_HIDE_MINIMIZED_APPS_SUPPORTED | RAIL_LEVEL_WINDOW_CLOAKING_SUPPORTED |
+ RAIL_LEVEL_HANDSHAKE_EX_SUPPORTED;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_RemoteApplicationSupportLevel, mask))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteAppLanguageBarSupported, TRUE))
+ return FALSE;
+ }
+
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, config->ServerRdpSecurity))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, config->ServerTlsSecurity))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, config->ServerNlaSecurity))
+ return FALSE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_EncryptionLevel,
+ ENCRYPTION_LEVEL_CLIENT_COMPATIBLE))
+ return FALSE;
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, 32))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_SuppressOutput, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_RefreshRect, TRUE))
+ return FALSE;
+ if (!freerdp_settings_set_bool(settings, FreeRDP_DesktopResize, TRUE))
+ return FALSE;
+
+ if (!freerdp_settings_set_uint32(settings, FreeRDP_MultifragMaxRequestSize,
+ 0xFFFFFF)) /* FIXME */
+ return FALSE;
+
+ peer->PostConnect = pf_server_post_connect;
+ peer->Activate = pf_server_activate;
+ peer->Logon = pf_server_logon;
+ peer->AdjustMonitorsLayout = pf_server_adjust_monitor_layout;
+
+ /* virtual channels receive data hook */
+ pdata->server_receive_channel_data_original = peer->ReceiveChannelData;
+ peer->ReceiveChannelData = pf_server_receive_channel_data_hook;
+
+ if (!stream_dump_register_handlers(peer->context, CONNECTION_STATE_NEGO, TRUE))
+ return FALSE;
+ return TRUE;
+}
+
+/**
+ * Handles an incoming client connection, to be run in it's own thread.
+ *
+ * arg is a pointer to a freerdp_peer representing the client.
+ */
+static DWORD WINAPI pf_server_handle_peer(LPVOID arg)
+{
+ HANDLE eventHandles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ pServerContext* ps = NULL;
+ proxyData* pdata = NULL;
+ peer_thread_args* args = arg;
+
+ WINPR_ASSERT(args);
+
+ freerdp_peer* client = args->client;
+ WINPR_ASSERT(client);
+
+ proxyServer* server = (proxyServer*)client->ContextExtra;
+ WINPR_ASSERT(server);
+
+ size_t count = ArrayList_Count(server->peer_list);
+
+ if (!pf_context_init_server_context(client))
+ goto out_free_peer;
+
+ if (!pf_server_initialize_peer_connection(client))
+ goto out_free_peer;
+
+ ps = (pServerContext*)client->context;
+ WINPR_ASSERT(ps);
+ PROXY_LOG_DBG(TAG, ps, "Added peer, %" PRIuz " connected", count);
+
+ pdata = ps->pdata;
+ WINPR_ASSERT(pdata);
+
+ if (!pf_modules_run_hook(pdata->module, HOOK_TYPE_SERVER_SESSION_INITIALIZE, pdata, client))
+ goto out_free_peer;
+
+ WINPR_ASSERT(client->Initialize);
+ client->Initialize(client);
+
+ PROXY_LOG_INFO(TAG, ps, "new connection: proxy address: %s, client address: %s",
+ pdata->config->Host, client->hostname);
+
+ if (!pf_modules_run_hook(pdata->module, HOOK_TYPE_SERVER_SESSION_STARTED, pdata, client))
+ goto out_free_peer;
+
+ while (1)
+ {
+ HANDLE ChannelEvent = INVALID_HANDLE_VALUE;
+ DWORD eventCount = 0;
+ {
+ WINPR_ASSERT(client->GetEventHandles);
+ const DWORD tmp = client->GetEventHandles(client, &eventHandles[eventCount],
+ ARRAYSIZE(eventHandles) - eventCount);
+
+ if (tmp == 0)
+ {
+ PROXY_LOG_ERR(TAG, ps, "Failed to get FreeRDP transport event handles");
+ break;
+ }
+
+ eventCount += tmp;
+ }
+ /* Main client event handling loop */
+ ChannelEvent = WTSVirtualChannelManagerGetEventHandle(ps->vcm);
+
+ WINPR_ASSERT(ChannelEvent && (ChannelEvent != INVALID_HANDLE_VALUE));
+ WINPR_ASSERT(pdata->abort_event && (pdata->abort_event != INVALID_HANDLE_VALUE));
+ eventHandles[eventCount++] = ChannelEvent;
+ eventHandles[eventCount++] = pdata->abort_event;
+ eventHandles[eventCount++] = server->stopEvent;
+
+ const DWORD status = WaitForMultipleObjects(
+ eventCount, eventHandles, FALSE, 1000); /* Do periodic polling to avoid client hang */
+
+ if (status == WAIT_FAILED)
+ {
+ PROXY_LOG_ERR(TAG, ps, "WaitForMultipleObjects failed (status: %" PRIu32 ")", status);
+ break;
+ }
+
+ WINPR_ASSERT(client->CheckFileDescriptor);
+ if (client->CheckFileDescriptor(client) != TRUE)
+ break;
+
+ if (WaitForSingleObject(ChannelEvent, 0) == WAIT_OBJECT_0)
+ {
+ if (!WTSVirtualChannelManagerCheckFileDescriptor(ps->vcm))
+ {
+ PROXY_LOG_ERR(TAG, ps, "WTSVirtualChannelManagerCheckFileDescriptor failure");
+ goto fail;
+ }
+ }
+
+ /* only disconnect after checking client's and vcm's file descriptors */
+ if (proxy_data_shall_disconnect(pdata))
+ {
+ PROXY_LOG_INFO(TAG, ps, "abort event is set, closing connection with peer %s",
+ client->hostname);
+ break;
+ }
+
+ if (WaitForSingleObject(server->stopEvent, 0) == WAIT_OBJECT_0)
+ {
+ PROXY_LOG_INFO(TAG, ps, "Server shutting down, terminating peer");
+ break;
+ }
+
+ switch (WTSVirtualChannelManagerGetDrdynvcState(ps->vcm))
+ {
+ /* Dynamic channel status may have been changed after processing */
+ case DRDYNVC_STATE_NONE:
+
+ /* Initialize drdynvc channel */
+ if (!WTSVirtualChannelManagerCheckFileDescriptor(ps->vcm))
+ {
+ PROXY_LOG_ERR(TAG, ps, "Failed to initialize drdynvc channel");
+ goto fail;
+ }
+
+ break;
+
+ case DRDYNVC_STATE_READY:
+ if (WaitForSingleObject(ps->dynvcReady, 0) == WAIT_TIMEOUT)
+ {
+ SetEvent(ps->dynvcReady);
+ }
+
+ break;
+
+ default:
+ break;
+ }
+ }
+
+fail:
+
+ PROXY_LOG_INFO(TAG, ps, "starting shutdown of connection");
+ PROXY_LOG_INFO(TAG, ps, "stopping proxy's client");
+
+ /* Abort the client. */
+ proxy_data_abort_connect(pdata);
+
+ pf_modules_run_hook(pdata->module, HOOK_TYPE_SERVER_SESSION_END, pdata, client);
+
+ PROXY_LOG_INFO(TAG, ps, "freeing server's channels");
+
+ WINPR_ASSERT(client->Close);
+ client->Close(client);
+
+ WINPR_ASSERT(client->Disconnect);
+ client->Disconnect(client);
+
+out_free_peer:
+ PROXY_LOG_INFO(TAG, ps, "freeing proxy data");
+
+ if (pdata && pdata->client_thread)
+ {
+ proxy_data_abort_connect(pdata);
+ WaitForSingleObject(pdata->client_thread, INFINITE);
+ }
+
+ {
+ ArrayList_Lock(server->peer_list);
+ ArrayList_Remove(server->peer_list, args->thread);
+ count = ArrayList_Count(server->peer_list);
+ ArrayList_Unlock(server->peer_list);
+ }
+ PROXY_LOG_DBG(TAG, ps, "Removed peer, %" PRIuz " connected", count);
+ freerdp_peer_context_free(client);
+ freerdp_peer_free(client);
+ proxy_data_free(pdata);
+
+#if defined(WITH_DEBUG_EVENTS)
+ DumpEventHandles();
+#endif
+ free(args);
+ ExitThread(0);
+ return 0;
+}
+
+static BOOL pf_server_start_peer(freerdp_peer* client)
+{
+ HANDLE hThread = NULL;
+ proxyServer* server = NULL;
+ peer_thread_args* args = calloc(1, sizeof(peer_thread_args));
+ if (!args)
+ return FALSE;
+
+ WINPR_ASSERT(client);
+ args->client = client;
+
+ server = (proxyServer*)client->ContextExtra;
+ WINPR_ASSERT(server);
+
+ hThread = CreateThread(NULL, 0, pf_server_handle_peer, args, CREATE_SUSPENDED, NULL);
+ if (!hThread)
+ return FALSE;
+
+ args->thread = hThread;
+ if (!ArrayList_Append(server->peer_list, hThread))
+ {
+ CloseHandle(hThread);
+ return FALSE;
+ }
+
+ return ResumeThread(hThread) != (DWORD)-1;
+}
+
+static BOOL pf_server_peer_accepted(freerdp_listener* listener, freerdp_peer* client)
+{
+ WINPR_ASSERT(listener);
+ WINPR_ASSERT(client);
+
+ client->ContextExtra = listener->info;
+
+ return pf_server_start_peer(client);
+}
+
+BOOL pf_server_start(proxyServer* server)
+{
+ WSADATA wsaData;
+
+ WINPR_ASSERT(server);
+
+ WTSRegisterWtsApiFunctionTable(FreeRDP_InitWtsApi());
+ winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT);
+
+ if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
+ goto error;
+
+ WINPR_ASSERT(server->config);
+ WINPR_ASSERT(server->listener);
+ WINPR_ASSERT(server->listener->Open);
+ if (!server->listener->Open(server->listener, server->config->Host, server->config->Port))
+ {
+ switch (errno)
+ {
+ case EADDRINUSE:
+ WLog_ERR(TAG, "failed to start listener: address already in use!");
+ break;
+ case EACCES:
+ WLog_ERR(TAG, "failed to start listener: insufficent permissions!");
+ break;
+ default:
+ WLog_ERR(TAG, "failed to start listener: errno=%d", errno);
+ break;
+ }
+
+ goto error;
+ }
+
+ return TRUE;
+
+error:
+ WSACleanup();
+ return FALSE;
+}
+
+BOOL pf_server_start_from_socket(proxyServer* server, int socket)
+{
+ WSADATA wsaData;
+
+ WINPR_ASSERT(server);
+
+ WTSRegisterWtsApiFunctionTable(FreeRDP_InitWtsApi());
+ winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT);
+
+ if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
+ goto error;
+
+ WINPR_ASSERT(server->listener);
+ WINPR_ASSERT(server->listener->OpenFromSocket);
+ if (!server->listener->OpenFromSocket(server->listener, socket))
+ {
+ switch (errno)
+ {
+ case EADDRINUSE:
+ WLog_ERR(TAG, "failed to start listener: address already in use!");
+ break;
+ case EACCES:
+ WLog_ERR(TAG, "failed to start listener: insufficent permissions!");
+ break;
+ default:
+ WLog_ERR(TAG, "failed to start listener: errno=%d", errno);
+ break;
+ }
+
+ goto error;
+ }
+
+ return TRUE;
+
+error:
+ WSACleanup();
+ return FALSE;
+}
+
+BOOL pf_server_start_with_peer_socket(proxyServer* server, int peer_fd)
+{
+ struct sockaddr_storage peer_addr;
+ socklen_t len = sizeof(peer_addr);
+ freerdp_peer* client = NULL;
+
+ WINPR_ASSERT(server);
+
+ if (WaitForSingleObject(server->stopEvent, 0) == WAIT_OBJECT_0)
+ goto fail;
+
+ client = freerdp_peer_new(peer_fd);
+ if (!client)
+ goto fail;
+
+ if (getpeername(peer_fd, (struct sockaddr*)&peer_addr, &len) != 0)
+ goto fail;
+
+ if (!freerdp_peer_set_local_and_hostname(client, &peer_addr))
+ goto fail;
+
+ client->ContextExtra = server;
+
+ if (!pf_server_start_peer(client))
+ goto fail;
+
+ return TRUE;
+
+fail:
+ WLog_ERR(TAG, "PeerAccepted callback failed");
+ freerdp_peer_free(client);
+ return FALSE;
+}
+
+static BOOL are_all_required_modules_loaded(proxyModule* module, const proxyConfig* config)
+{
+ for (size_t i = 0; i < pf_config_required_plugins_count(config); i++)
+ {
+ const char* plugin_name = pf_config_required_plugin(config, i);
+
+ if (!pf_modules_is_plugin_loaded(module, plugin_name))
+ {
+ WLog_ERR(TAG, "Required plugin '%s' is not loaded. stopping.", plugin_name);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static void peer_free(void* obj)
+{
+ HANDLE hdl = (HANDLE)obj;
+ CloseHandle(hdl);
+}
+
+proxyServer* pf_server_new(const proxyConfig* config)
+{
+ wObject* obj = NULL;
+ proxyServer* server = NULL;
+
+ WINPR_ASSERT(config);
+
+ server = calloc(1, sizeof(proxyServer));
+ if (!server)
+ return NULL;
+
+ if (!pf_config_clone(&server->config, config))
+ goto out;
+
+ server->module = pf_modules_new(FREERDP_PROXY_PLUGINDIR, pf_config_modules(server->config),
+ pf_config_modules_count(server->config));
+ if (!server->module)
+ {
+ WLog_ERR(TAG, "failed to initialize proxy modules!");
+ goto out;
+ }
+
+ pf_modules_list_loaded_plugins(server->module);
+ if (!are_all_required_modules_loaded(server->module, server->config))
+ goto out;
+
+ server->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!server->stopEvent)
+ goto out;
+
+ server->listener = freerdp_listener_new();
+ if (!server->listener)
+ goto out;
+
+ server->peer_list = ArrayList_New(FALSE);
+ if (!server->peer_list)
+ goto out;
+
+ obj = ArrayList_Object(server->peer_list);
+ WINPR_ASSERT(obj);
+
+ obj->fnObjectFree = peer_free;
+
+ server->listener->info = server;
+ server->listener->PeerAccepted = pf_server_peer_accepted;
+
+ if (!pf_modules_add(server->module, pf_config_plugin, (void*)server->config))
+ goto out;
+
+ return server;
+
+out:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ pf_server_free(server);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+BOOL pf_server_run(proxyServer* server)
+{
+ BOOL rc = TRUE;
+ HANDLE eventHandles[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ DWORD eventCount = 0;
+ DWORD status = 0;
+ freerdp_listener* listener = NULL;
+
+ WINPR_ASSERT(server);
+
+ listener = server->listener;
+ WINPR_ASSERT(listener);
+
+ while (1)
+ {
+ WINPR_ASSERT(listener->GetEventHandles);
+ eventCount = listener->GetEventHandles(listener, eventHandles, ARRAYSIZE(eventHandles));
+
+ if ((0 == eventCount) || (eventCount >= ARRAYSIZE(eventHandles)))
+ {
+ WLog_ERR(TAG, "Failed to get FreeRDP event handles");
+ break;
+ }
+
+ WINPR_ASSERT(server->stopEvent);
+ eventHandles[eventCount++] = server->stopEvent;
+ status = WaitForMultipleObjects(eventCount, eventHandles, FALSE, 1000);
+
+ if (WAIT_FAILED == status)
+ break;
+
+ if (WaitForSingleObject(server->stopEvent, 0) == WAIT_OBJECT_0)
+ break;
+
+ if (WAIT_FAILED == status)
+ {
+ WLog_ERR(TAG, "select failed");
+ rc = FALSE;
+ break;
+ }
+
+ WINPR_ASSERT(listener->CheckFileDescriptor);
+ if (listener->CheckFileDescriptor(listener) != TRUE)
+ {
+ WLog_ERR(TAG, "Failed to accept new peer");
+ // TODO: Set out of resource error
+ continue;
+ }
+ }
+
+ WINPR_ASSERT(listener->Close);
+ listener->Close(listener);
+ return rc;
+}
+
+void pf_server_stop(proxyServer* server)
+{
+
+ if (!server)
+ return;
+
+ /* signal main thread to stop and wait for the thread to exit */
+ SetEvent(server->stopEvent);
+}
+
+void pf_server_free(proxyServer* server)
+{
+ if (!server)
+ return;
+
+ pf_server_stop(server);
+
+ if (server->peer_list)
+ {
+ while (ArrayList_Count(server->peer_list) > 0)
+ {
+ /* pf_server_stop triggers the threads to shut down.
+ * loop here until all of them stopped.
+ *
+ * This must be done before ArrayList_Free otherwise the thread removal
+ * in pf_server_handle_peer will deadlock due to both threads trying to
+ * lock the list.
+ */
+ Sleep(100);
+ }
+ }
+ ArrayList_Free(server->peer_list);
+ freerdp_listener_free(server->listener);
+
+ if (server->stopEvent)
+ CloseHandle(server->stopEvent);
+
+ pf_server_config_free(server->config);
+ pf_modules_free(server->module);
+ free(server);
+
+#if defined(WITH_DEBUG_EVENTS)
+ DumpEventHandles();
+#endif
+}
+
+BOOL pf_server_add_module(proxyServer* server, proxyModuleEntryPoint ep, void* userdata)
+{
+ WINPR_ASSERT(server);
+ WINPR_ASSERT(ep);
+
+ return pf_modules_add(server->module, ep, userdata);
+}
diff --git a/server/proxy/pf_server.h b/server/proxy/pf_server.h
new file mode 100644
index 0000000..2ac84f2
--- /dev/null
+++ b/server/proxy/pf_server.h
@@ -0,0 +1,43 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@gmail.com>
+ * Copyright 2021 Armin Novak <anovak@thincast.com>
+ * Copyright 2021 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 INT_FREERDP_SERVER_PROXY_SERVER_H
+#define INT_FREERDP_SERVER_PROXY_SERVER_H
+
+#include <winpr/collections.h>
+#include <freerdp/listener.h>
+
+#include <freerdp/server/proxy/proxy_config.h>
+#include "proxy_modules.h"
+
+struct proxy_server
+{
+ proxyModule* module;
+ proxyConfig* config;
+
+ freerdp_listener* listener;
+ HANDLE stopEvent; /* an event used to signal the main thread to stop */
+ wArrayList* peer_list;
+};
+
+#endif /* INT_FREERDP_SERVER_PROXY_SERVER_H */
diff --git a/server/proxy/pf_update.c b/server/proxy/pf_update.c
new file mode 100644
index 0000000..e572810
--- /dev/null
+++ b/server/proxy/pf_update.c
@@ -0,0 +1,629 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@gmail.com>
+ * Copyright 2021 Armin Novak <anovak@thincast.com>
+ * Copyright 2021 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.
+ */
+
+#include <freerdp/display.h>
+#include <freerdp/session.h>
+#include <winpr/assert.h>
+#include <winpr/image.h>
+#include <winpr/sysinfo.h>
+
+#include <freerdp/server/proxy/proxy_log.h>
+
+#include "pf_update.h"
+#include <freerdp/server/proxy/proxy_context.h>
+#include "proxy_modules.h"
+
+#define TAG PROXY_TAG("update")
+
+static BOOL pf_server_refresh_rect(rdpContext* context, BYTE count, const RECTANGLE_16* areas)
+{
+ pServerContext* ps = (pServerContext*)context;
+ rdpContext* pc = NULL;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->pdata);
+ pc = (rdpContext*)ps->pdata->pc;
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->update);
+ WINPR_ASSERT(pc->update->RefreshRect);
+ return pc->update->RefreshRect(pc, count, areas);
+}
+
+static BOOL pf_server_suppress_output(rdpContext* context, BYTE allow, const RECTANGLE_16* area)
+{
+ pServerContext* ps = (pServerContext*)context;
+ rdpContext* pc = NULL;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->pdata);
+ pc = (rdpContext*)ps->pdata->pc;
+ WINPR_ASSERT(pc);
+ WINPR_ASSERT(pc->update);
+ WINPR_ASSERT(pc->update->SuppressOutput);
+ return pc->update->SuppressOutput(pc, allow, area);
+}
+
+/* Proxy from PC to PS */
+
+/**
+ * This function is called whenever a new frame starts.
+ * It can be used to reset invalidated areas.
+ */
+static BOOL pf_client_begin_paint(rdpContext* context)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->BeginPaint);
+ WLog_DBG(TAG, "called");
+ return ps->update->BeginPaint(ps);
+}
+
+/**
+ * This function is called when the library completed composing a new
+ * frame. Read out the changed areas and blit them to your output device.
+ * The image buffer will have the format specified by gdi_init
+ */
+static BOOL pf_client_end_paint(rdpContext* context)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->EndPaint);
+
+ WLog_DBG(TAG, "called");
+
+ /* proxy end paint */
+ if (!ps->update->EndPaint(ps))
+ return FALSE;
+
+ if (!pf_modules_run_hook(pdata->module, HOOK_TYPE_CLIENT_END_PAINT, pdata, context))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL pf_client_bitmap_update(rdpContext* context, const BITMAP_UPDATE* bitmap)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->BitmapUpdate);
+ WLog_DBG(TAG, "called");
+ return ps->update->BitmapUpdate(ps, bitmap);
+}
+
+static BOOL pf_client_desktop_resize(rdpContext* context)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->DesktopResize);
+ WINPR_ASSERT(context->settings);
+ WINPR_ASSERT(ps->settings);
+ WLog_DBG(TAG, "called");
+ if (!freerdp_settings_copy_item(ps->settings, context->settings, FreeRDP_DesktopWidth))
+ return FALSE;
+ if (!freerdp_settings_copy_item(ps->settings, context->settings, FreeRDP_DesktopHeight))
+ return FALSE;
+ return ps->update->DesktopResize(ps);
+}
+
+static BOOL pf_client_remote_monitors(rdpContext* context, UINT32 count,
+ const MONITOR_DEF* monitors)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WLog_DBG(TAG, "called");
+ return freerdp_display_send_monitor_layout(ps, count, monitors);
+}
+
+static BOOL pf_client_send_pointer_system(rdpContext* context,
+ const POINTER_SYSTEM_UPDATE* pointer_system)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->pointer);
+ WINPR_ASSERT(ps->update->pointer->PointerSystem);
+ WLog_DBG(TAG, "called");
+ return ps->update->pointer->PointerSystem(ps, pointer_system);
+}
+
+static BOOL pf_client_send_pointer_position(rdpContext* context,
+ const POINTER_POSITION_UPDATE* pointerPosition)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->pointer);
+ WINPR_ASSERT(ps->update->pointer->PointerPosition);
+ WLog_DBG(TAG, "called");
+ return ps->update->pointer->PointerPosition(ps, pointerPosition);
+}
+
+static BOOL pf_client_send_pointer_color(rdpContext* context,
+ const POINTER_COLOR_UPDATE* pointer_color)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->pointer);
+ WINPR_ASSERT(ps->update->pointer->PointerColor);
+ WLog_DBG(TAG, "called");
+ return ps->update->pointer->PointerColor(ps, pointer_color);
+}
+
+static BOOL pf_client_send_pointer_large(rdpContext* context,
+ const POINTER_LARGE_UPDATE* pointer_large)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->pointer);
+ WINPR_ASSERT(ps->update->pointer->PointerLarge);
+ WLog_DBG(TAG, "called");
+ return ps->update->pointer->PointerLarge(ps, pointer_large);
+}
+
+static BOOL pf_client_send_pointer_new(rdpContext* context, const POINTER_NEW_UPDATE* pointer_new)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->pointer);
+ WINPR_ASSERT(ps->update->pointer->PointerNew);
+ WLog_DBG(TAG, "called");
+ return ps->update->pointer->PointerNew(ps, pointer_new);
+}
+
+static BOOL pf_client_send_pointer_cached(rdpContext* context,
+ const POINTER_CACHED_UPDATE* pointer_cached)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->pointer);
+ WINPR_ASSERT(ps->update->pointer->PointerCached);
+ WLog_DBG(TAG, "called");
+ return ps->update->pointer->PointerCached(ps, pointer_cached);
+}
+
+static BOOL pf_client_save_session_info(rdpContext* context, UINT32 type, void* data)
+{
+ logon_info* logonInfo = NULL;
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->SaveSessionInfo);
+
+ WLog_DBG(TAG, "called");
+
+ switch (type)
+ {
+ case INFO_TYPE_LOGON:
+ case INFO_TYPE_LOGON_LONG:
+ {
+ logonInfo = (logon_info*)data;
+ PROXY_LOG_INFO(TAG, pc, "client logon info: Username: %s, Domain: %s",
+ logonInfo->username, logonInfo->domain);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return ps->update->SaveSessionInfo(ps, type, data);
+}
+
+static BOOL pf_client_server_status_info(rdpContext* context, UINT32 status)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->ServerStatusInfo);
+
+ WLog_DBG(TAG, "called");
+ return ps->update->ServerStatusInfo(ps, status);
+}
+
+static BOOL pf_client_set_keyboard_indicators(rdpContext* context, UINT16 led_flags)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->SetKeyboardIndicators);
+
+ WLog_DBG(TAG, "called");
+ return ps->update->SetKeyboardIndicators(ps, led_flags);
+}
+
+static BOOL pf_client_set_keyboard_ime_status(rdpContext* context, UINT16 imeId, UINT32 imeState,
+ UINT32 imeConvMode)
+{
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->SetKeyboardImeStatus);
+
+ WLog_DBG(TAG, "called");
+ return ps->update->SetKeyboardImeStatus(ps, imeId, imeState, imeConvMode);
+}
+
+static BOOL pf_client_window_create(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_STATE_ORDER* windowState)
+{
+ BOOL rc = 0;
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->window);
+ WINPR_ASSERT(ps->update->window->WindowCreate);
+
+ WLog_DBG(TAG, "called");
+ rdp_update_lock(ps->update);
+ rc = ps->update->window->WindowCreate(ps, orderInfo, windowState);
+ rdp_update_unlock(ps->update);
+ return rc;
+}
+
+static BOOL pf_client_window_update(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_STATE_ORDER* windowState)
+{
+ BOOL rc = 0;
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->window);
+ WINPR_ASSERT(ps->update->window->WindowUpdate);
+
+ WLog_DBG(TAG, "called");
+ rdp_update_lock(ps->update);
+ rc = ps->update->window->WindowUpdate(ps, orderInfo, windowState);
+ rdp_update_unlock(ps->update);
+ return rc;
+}
+
+static BOOL pf_client_window_icon(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_ICON_ORDER* windowIcon)
+{
+ BOOL rc = 0;
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->window);
+ WINPR_ASSERT(ps->update->window->WindowIcon);
+
+ WLog_DBG(TAG, "called");
+ rdp_update_lock(ps->update);
+ rc = ps->update->window->WindowIcon(ps, orderInfo, windowIcon);
+ rdp_update_unlock(ps->update);
+ return rc;
+}
+
+static BOOL pf_client_window_cached_icon(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const WINDOW_CACHED_ICON_ORDER* windowCachedIcon)
+{
+ BOOL rc = 0;
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->window);
+ WINPR_ASSERT(ps->update->window->WindowCachedIcon);
+
+ WLog_DBG(TAG, "called");
+ rdp_update_lock(ps->update);
+ rc = ps->update->window->WindowCachedIcon(ps, orderInfo, windowCachedIcon);
+ rdp_update_unlock(ps->update);
+ return rc;
+}
+
+static BOOL pf_client_window_delete(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo)
+{
+ BOOL rc = 0;
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->window);
+ WINPR_ASSERT(ps->update->window->WindowDelete);
+
+ WLog_DBG(TAG, "called");
+ rdp_update_lock(ps->update);
+ rc = ps->update->window->WindowDelete(ps, orderInfo);
+ rdp_update_unlock(ps->update);
+ return rc;
+}
+
+static BOOL pf_client_notify_icon_create(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const NOTIFY_ICON_STATE_ORDER* notifyIconState)
+{
+ BOOL rc = 0;
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->window);
+ WINPR_ASSERT(ps->update->window->NotifyIconCreate);
+
+ WLog_DBG(TAG, "called");
+ rdp_update_lock(ps->update);
+ rc = ps->update->window->NotifyIconCreate(ps, orderInfo, notifyIconState);
+ rdp_update_unlock(ps->update);
+ return rc;
+}
+
+static BOOL pf_client_notify_icon_update(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const NOTIFY_ICON_STATE_ORDER* notifyIconState)
+{
+ BOOL rc = 0;
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->window);
+ WINPR_ASSERT(ps->update->window->NotifyIconUpdate);
+
+ WLog_DBG(TAG, "called");
+ rdp_update_lock(ps->update);
+ rc = ps->update->window->NotifyIconUpdate(ps, orderInfo, notifyIconState);
+ rdp_update_unlock(ps->update);
+ return rc;
+}
+
+static BOOL pf_client_notify_icon_delete(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo)
+{
+ BOOL rc = 0;
+
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->window);
+ WINPR_ASSERT(ps->update->window->NotifyIconDelete);
+
+ WLog_DBG(TAG, "called");
+ rdp_update_lock(ps->update);
+ rc = ps->update->window->NotifyIconDelete(ps, orderInfo);
+ rdp_update_unlock(ps->update);
+ return rc;
+}
+
+static BOOL pf_client_monitored_desktop(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
+ const MONITORED_DESKTOP_ORDER* monitoredDesktop)
+{
+ BOOL rc = 0;
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->window);
+ WINPR_ASSERT(ps->update->window->MonitoredDesktop);
+
+ WLog_DBG(TAG, "called");
+ rdp_update_lock(ps->update);
+ rc = ps->update->window->MonitoredDesktop(ps, orderInfo, monitoredDesktop);
+ rdp_update_unlock(ps->update);
+ return rc;
+}
+
+static BOOL pf_client_non_monitored_desktop(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo)
+{
+ BOOL rc = 0;
+ pClientContext* pc = (pClientContext*)context;
+ proxyData* pdata = NULL;
+ rdpContext* ps = NULL;
+ WINPR_ASSERT(pc);
+ pdata = pc->pdata;
+ WINPR_ASSERT(pdata);
+ ps = (rdpContext*)pdata->ps;
+ WINPR_ASSERT(ps);
+ WINPR_ASSERT(ps->update);
+ WINPR_ASSERT(ps->update->window);
+ WINPR_ASSERT(ps->update->window->NonMonitoredDesktop);
+
+ WLog_DBG(TAG, "called");
+ rdp_update_lock(ps->update);
+ rc = ps->update->window->NonMonitoredDesktop(ps, orderInfo);
+ rdp_update_unlock(ps->update);
+ return rc;
+}
+
+void pf_server_register_update_callbacks(rdpUpdate* update)
+{
+ WINPR_ASSERT(update);
+ update->RefreshRect = pf_server_refresh_rect;
+ update->SuppressOutput = pf_server_suppress_output;
+}
+
+void pf_client_register_update_callbacks(rdpUpdate* update)
+{
+ WINPR_ASSERT(update);
+ update->BeginPaint = pf_client_begin_paint;
+ update->EndPaint = pf_client_end_paint;
+ update->BitmapUpdate = pf_client_bitmap_update;
+ update->DesktopResize = pf_client_desktop_resize;
+ update->RemoteMonitors = pf_client_remote_monitors;
+ update->SaveSessionInfo = pf_client_save_session_info;
+ update->ServerStatusInfo = pf_client_server_status_info;
+ update->SetKeyboardIndicators = pf_client_set_keyboard_indicators;
+ update->SetKeyboardImeStatus = pf_client_set_keyboard_ime_status;
+
+ /* Rail window updates */
+ update->window->WindowCreate = pf_client_window_create;
+ update->window->WindowUpdate = pf_client_window_update;
+ update->window->WindowIcon = pf_client_window_icon;
+ update->window->WindowCachedIcon = pf_client_window_cached_icon;
+ update->window->WindowDelete = pf_client_window_delete;
+ update->window->NotifyIconCreate = pf_client_notify_icon_create;
+ update->window->NotifyIconUpdate = pf_client_notify_icon_update;
+ update->window->NotifyIconDelete = pf_client_notify_icon_delete;
+ update->window->MonitoredDesktop = pf_client_monitored_desktop;
+ update->window->NonMonitoredDesktop = pf_client_non_monitored_desktop;
+
+ /* Pointer updates */
+ update->pointer->PointerSystem = pf_client_send_pointer_system;
+ update->pointer->PointerPosition = pf_client_send_pointer_position;
+ update->pointer->PointerColor = pf_client_send_pointer_color;
+ update->pointer->PointerLarge = pf_client_send_pointer_large;
+ update->pointer->PointerNew = pf_client_send_pointer_new;
+ update->pointer->PointerCached = pf_client_send_pointer_cached;
+}
diff --git a/server/proxy/pf_update.h b/server/proxy/pf_update.h
new file mode 100644
index 0000000..81a9c32
--- /dev/null
+++ b/server/proxy/pf_update.h
@@ -0,0 +1,34 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@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.
+ */
+
+#ifndef FREERDP_SERVER_PROXY_PFUPDATE_H
+#define FREERDP_SERVER_PROXY_PFUPDATE_H
+
+#include <freerdp/freerdp.h>
+#include <freerdp/gdi/gdi.h>
+#include <freerdp/gdi/bitmap.h>
+
+#include <freerdp/server/proxy/proxy_context.h>
+
+void pf_server_register_update_callbacks(rdpUpdate* update);
+void pf_client_register_update_callbacks(rdpUpdate* update);
+
+#endif /* FREERDP_SERVER_PROXY_PFUPDATE_H */
diff --git a/server/proxy/pf_utils.c b/server/proxy/pf_utils.c
new file mode 100644
index 0000000..f8c17af
--- /dev/null
+++ b/server/proxy/pf_utils.c
@@ -0,0 +1,95 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2021 Armin Novak <armin.novak@thincast.com>
+ * * Copyright 2021 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.
+ */
+
+#include <winpr/assert.h>
+#include <winpr/string.h>
+#include <winpr/wtsapi.h>
+
+#include <freerdp/server/proxy/proxy_log.h>
+#include "pf_utils.h"
+
+#define TAG PROXY_TAG("utils")
+
+pf_utils_channel_mode pf_utils_get_channel_mode(const proxyConfig* config, const char* name)
+{
+ pf_utils_channel_mode rc = PF_UTILS_CHANNEL_NOT_HANDLED;
+ BOOL found = FALSE;
+
+ WINPR_ASSERT(config);
+ WINPR_ASSERT(name);
+
+ for (size_t i = 0; i < config->InterceptCount; i++)
+ {
+ const char* channel_name = config->Intercept[i];
+ if (strcmp(name, channel_name) == 0)
+ {
+ rc = PF_UTILS_CHANNEL_INTERCEPT;
+ goto end;
+ }
+ }
+
+ for (size_t i = 0; i < config->PassthroughCount; i++)
+ {
+ const char* channel_name = config->Passthrough[i];
+ if (strcmp(name, channel_name) == 0)
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (found)
+ {
+ if (config->PassthroughIsBlacklist)
+ rc = PF_UTILS_CHANNEL_BLOCK;
+ else
+ rc = PF_UTILS_CHANNEL_PASSTHROUGH;
+ }
+ else if (config->PassthroughIsBlacklist)
+ rc = PF_UTILS_CHANNEL_PASSTHROUGH;
+
+end:
+ WLog_DBG(TAG, "%s -> %s", name, pf_utils_channel_mode_string(rc));
+ return rc;
+}
+
+BOOL pf_utils_is_passthrough(const proxyConfig* config)
+{
+ WINPR_ASSERT(config);
+
+ /* TODO: For the time being only passthrough mode is supported. */
+ return TRUE;
+}
+
+const char* pf_utils_channel_mode_string(pf_utils_channel_mode mode)
+{
+ switch (mode)
+ {
+ case PF_UTILS_CHANNEL_BLOCK:
+ return "blocked";
+ case PF_UTILS_CHANNEL_PASSTHROUGH:
+ return "passthrough";
+ case PF_UTILS_CHANNEL_INTERCEPT:
+ return "intercepted";
+ case PF_UTILS_CHANNEL_NOT_HANDLED:
+ default:
+ return "ignored";
+ }
+}
diff --git a/server/proxy/pf_utils.h b/server/proxy/pf_utils.h
new file mode 100644
index 0000000..0e899e9
--- /dev/null
+++ b/server/proxy/pf_utils.h
@@ -0,0 +1,43 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2021 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2021 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 FREERDP_SERVER_PROXY_PFUTILS_H
+#define FREERDP_SERVER_PROXY_PFUTILS_H
+
+#include <freerdp/server/proxy/proxy_config.h>
+#include <freerdp/server/proxy/proxy_context.h>
+
+/**
+ * @brief pf_utils_channel_is_passthrough Checks of a channel identified by 'name'
+ * should be handled as passthrough.
+ *
+ * @param config The proxy configuration to check against. Must NOT be NULL.
+ * @param name The name of the channel. Must NOT be NULL.
+ * @return -1 if the channel is not handled, 0 if the channel should be ignored,
+ * 1 if the channel should be passed, 2 the channel will be intercepted
+ * e.g. proxy client and server are termination points and data passed
+ * between.
+ */
+pf_utils_channel_mode pf_utils_get_channel_mode(const proxyConfig* config, const char* name);
+const char* pf_utils_channel_mode_string(pf_utils_channel_mode mode);
+
+BOOL pf_utils_is_passthrough(const proxyConfig* config);
+
+#endif /* FREERDP_SERVER_PROXY_PFUTILS_H */
diff --git a/server/proxy/proxy_modules.h b/server/proxy/proxy_modules.h
new file mode 100644
index 0000000..d5883a5
--- /dev/null
+++ b/server/proxy/proxy_modules.h
@@ -0,0 +1,100 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * FreeRDP Proxy Server
+ *
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
+ * Copyright 2019 Idan Freiberg <speidy@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.
+ */
+
+#ifndef FREERDP_SERVER_PROXY_MODULES_H
+#define FREERDP_SERVER_PROXY_MODULES_H
+
+#include <winpr/wtypes.h>
+#include <winpr/collections.h>
+
+#include <freerdp/server/proxy/proxy_modules_api.h>
+
+typedef enum
+{
+ FILTER_TYPE_KEYBOARD, /* proxyKeyboardEventInfo */
+ FILTER_TYPE_UNICODE, /* proxyUnicodeEventInfo */
+ FILTER_TYPE_MOUSE, /* proxyMouseEventInfo */
+ FILTER_TYPE_MOUSE_EX, /* proxyMouseExEventInfo */
+ FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA, /* proxyChannelDataEventInfo */
+ FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA, /* proxyChannelDataEventInfo */
+ FILTER_TYPE_CLIENT_PASSTHROUGH_DYN_CHANNEL_CREATE, /* proxyChannelDataEventInfo */
+ FILTER_TYPE_SERVER_FETCH_TARGET_ADDR, /* proxyFetchTargetEventInfo */
+ FILTER_TYPE_SERVER_PEER_LOGON, /* proxyServerPeerLogon */
+ FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_CREATE, /* proxyChannelDataEventInfo */
+
+ FILTER_TYPE_STATIC_INTERCEPT_LIST, /* proxyChannelToInterceptData */
+ FILTER_TYPE_DYN_INTERCEPT_LIST, /* proxyChannelToInterceptData */
+ FILTER_TYPE_INTERCEPT_CHANNEL, /* proxyDynChannelInterceptData */
+ FILTER_LAST
+} PF_FILTER_TYPE;
+
+typedef enum
+{
+ HOOK_TYPE_CLIENT_INIT_CONNECT,
+ HOOK_TYPE_CLIENT_UNINIT_CONNECT,
+ HOOK_TYPE_CLIENT_PRE_CONNECT,
+ HOOK_TYPE_CLIENT_POST_CONNECT,
+ HOOK_TYPE_CLIENT_POST_DISCONNECT,
+ HOOK_TYPE_CLIENT_REDIRECT,
+ HOOK_TYPE_CLIENT_VERIFY_X509,
+ HOOK_TYPE_CLIENT_LOGIN_FAILURE,
+ HOOK_TYPE_CLIENT_END_PAINT,
+ HOOK_TYPE_CLIENT_LOAD_CHANNELS,
+
+ HOOK_TYPE_SERVER_POST_CONNECT,
+ HOOK_TYPE_SERVER_ACTIVATE,
+ HOOK_TYPE_SERVER_CHANNELS_INIT,
+ HOOK_TYPE_SERVER_CHANNELS_FREE,
+ HOOK_TYPE_SERVER_SESSION_END,
+ HOOK_TYPE_SERVER_SESSION_INITIALIZE,
+ HOOK_TYPE_SERVER_SESSION_STARTED,
+
+ HOOK_LAST
+} PF_HOOK_TYPE;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ proxyModule* pf_modules_new(const char* root_dir, const char** modules, size_t count);
+
+ /**
+ * @brief pf_modules_add Registers a new plugin
+ * @param ep A module entry point function, must NOT be NULL
+ * @return TRUE for success, FALSE otherwise
+ */
+ BOOL pf_modules_add(proxyModule* module, proxyModuleEntryPoint ep, void* userdata);
+
+ BOOL pf_modules_is_plugin_loaded(proxyModule* module, const char* plugin_name);
+ void pf_modules_list_loaded_plugins(proxyModule* module);
+
+ BOOL pf_modules_run_filter(proxyModule* module, PF_FILTER_TYPE type, proxyData* pdata,
+ void* param);
+ BOOL pf_modules_run_hook(proxyModule* module, PF_HOOK_TYPE type, proxyData* pdata,
+ void* custom);
+
+ void pf_modules_free(proxyModule* module);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERDP_SERVER_PROXY_MODULES_H */