Milestone 5: deliver embedded RDP sessions and lifecycle hardening

This commit is contained in:
Keith Smith
2026-03-03 18:59:26 -07:00
parent 230a401386
commit 36006bd4aa
2941 changed files with 724359 additions and 77 deletions

View File

@@ -0,0 +1,127 @@
# 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")
addtargetwithresourcefile(${MODULE_NAME} FALSE "${FREERDP_VERSION}" ${MODULE_PREFIX}_SRCS)
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})
installwithrpath(
TARGETS
${MODULE_NAME}
COMPONENT
server
EXPORT
FreeRDP-ProxyTargets
ARCHIVE
DESTINATION
${CMAKE_INSTALL_LIBDIR}
LIBRARY
DESTINATION
${CMAKE_INSTALL_LIBDIR}
RUNTIME
DESTINATION
${CMAKE_INSTALL_BINDIR}
)
set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/Proxy")
# pkg-config
# Do not set Requires.Private if not a static build
if(NOT BUILD_SHARED_LIBS)
set(FREERDP_PROXY_PC_REQUIRES_PRIVATE "freerdp-client${FREERDP_API_VERSION} freerdp-server${FREERDP_API_VERSION}")
set(FREERDP_PROXY_PC_LIBS_PRIVATE "-ldl -lpthread")
endif()
set(FREERDP_PROXY_PC_REQUIRES freerdp-server${FREERDP_API_VERSION})
include(pkg-config-install-prefix)
cleaning_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()

View File

@@ -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")

View File

@@ -0,0 +1,8 @@
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})

View File

@@ -0,0 +1,924 @@
/**
* 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")
#define Stream_CheckAndLogRequiredLengthWLogWithBackend(log, s, nmemb, backdata) \
Stream_CheckAndLogRequiredLengthWLogEx(log, WLOG_WARN, s, nmemb, 1, "%s(%s:%" PRIuz ")[%s]", \
__func__, __FILE__, (size_t)__LINE__, \
getDirection(backdata))
/** @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;
WINPR_ATTR_NODISCARD 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";
}
}
#define DynvcTrackerLog(log, level, dynChannel, cmd, isBackData, ...) \
dyn_log_((log), (level), (dynChannel), (cmd), (isBackData), __func__, __FILE__, __LINE__, \
__VA_ARGS__)
WINPR_ATTR_NODISCARD
static const char* getDirection(BOOL isBackData)
{
return isBackData ? "B->F" : "F->B";
}
static void dyn_log_(wLog* log, DWORD level, const pServerDynamicChannelContext* dynChannel,
BYTE cmd, BOOL isBackData, const char* fkt, const char* file, size_t line,
const char* fmt, ...)
{
if (!WLog_IsLevelActive(log, level))
return;
char* prefix = nullptr;
char* msg = nullptr;
size_t prefixlen = 0;
size_t msglen = 0;
uint32_t channelId = dynChannel ? dynChannel->channelId : UINT32_MAX;
const char* channelName = dynChannel ? dynChannel->channelName : "<nullptr>";
(void)winpr_asprintf(&prefix, &prefixlen, "DynvcTracker[%s](%s [%s:%" PRIu32 "])",
getDirection(isBackData), channelName, drdynvc_get_packet_type(cmd),
channelId);
va_list ap = WINPR_C_ARRAY_INIT;
va_start(ap, fmt);
(void)winpr_vasprintf(&msg, &msglen, fmt, ap);
va_end(ap);
WLog_PrintTextMessage(log, level, line, file, fkt, "%s: %s", prefix, msg);
free(prefix);
free(msg);
}
WINPR_ATTR_NODISCARD
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 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);
}
WINPR_ATTR_MALLOC(DynamicChannelContext_free, 1)
WINPR_ATTR_NODISCARD
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 nullptr;
}
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 nullptr;
}
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;
}
WINPR_ATTR_NODISCARD
static UINT32 ChannelId_Hash(const void* key)
{
const UINT32* v = (const UINT32*)key;
return *v;
}
WINPR_ATTR_NODISCARD
static BOOL ChannelId_Compare(const void* objA, const void* objB)
{
const UINT32* v1 = objA;
const UINT32* v2 = objB;
return (*v1 == *v2);
}
WINPR_ATTR_NODISCARD
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;
}
WINPR_ATTR_NODISCARD
static PfChannelResult DynvcTrackerPeekHandleByMode(ChannelStateTracker* tracker,
DynChannelTrackerState* trackerState,
pServerDynamicChannelContext* dynChannel,
BYTE cmd, BOOL firstPacket, BOOL lastPacket)
{
WINPR_ASSERT(tracker);
WINPR_ASSERT(trackerState);
WINPR_ASSERT(dynChannel);
PfChannelResult result = PF_CHANNEL_RESULT_ERROR;
DynChannelContext* dynChannelContext =
(DynChannelContext*)channelTracker_getCustomData(tracker);
WINPR_ASSERT(dynChannelContext);
proxyData* pdata = channelTracker_getPData(tracker);
WINPR_ASSERT(pdata);
const BOOL isBackData = (tracker == dynChannelContext->backTracker);
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
{
DynvcTrackerLog(dynChannelContext->log, WLOG_ERROR, dynChannel, cmd, isBackData,
"no intercept callback for channel, dropping packet");
result = PF_CHANNEL_RESULT_DROP;
}
break;
default:
DynvcTrackerLog(dynChannelContext->log, WLOG_ERROR, dynChannel, cmd, isBackData,
"unknown channel mode %u", 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_ResetPosition(trackerState->currentPacket);
}
return result;
}
WINPR_ATTR_NODISCARD
static PfChannelResult DynvcTrackerHandleClose(ChannelStateTracker* tracker,
pServerDynamicChannelContext* dynChannel,
DynChannelContext* dynChannelContext,
BOOL firstPacket, BOOL lastPacket)
{
WINPR_ASSERT(dynChannelContext);
const BOOL isBackData = (tracker == dynChannelContext->backTracker);
if (!lastPacket || !dynChannel)
return PF_CHANNEL_RESULT_DROP;
DynvcTrackerLog(dynChannelContext->log, WLOG_DEBUG, dynChannel, CLOSE_REQUEST_PDU, isBackData,
"Close request");
channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS);
if (dynChannel->openStatus != CHANNEL_OPENSTATE_OPENED)
{
DynvcTrackerLog(dynChannelContext->log, WLOG_DEBUG, dynChannel, CLOSE_REQUEST_PDU,
isBackData, "is in state %s, expected %s",
openstatus2str(dynChannel->openStatus),
openstatus2str(CHANNEL_OPENSTATE_OPENED));
}
dynChannel->openStatus = CHANNEL_OPENSTATE_CLOSED;
return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, !isBackData);
}
WINPR_ATTR_NODISCARD
static PfChannelResult DynvcTrackerHandleCreateBack(ChannelStateTracker* tracker, wStream* s,
DWORD flags, proxyData* pdata,
pServerDynamicChannelContext* dynChannel,
DynChannelContext* dynChannelContext,
UINT64 dynChannelId)
{
proxyChannelDataEventInfo dev = WINPR_C_ARRAY_INIT;
const char* name = Stream_ConstPointer(s);
const size_t nameLen = Stream_GetRemainingLength(s);
const size_t len = strnlen(name, nameLen);
const BOOL isBackData = (tracker == dynChannelContext->backTracker);
const BYTE cmd = CREATE_REQUEST_PDU;
if ((len == 0) || (len == nameLen) || (dynChannelId > UINT16_MAX))
{
char namebuffer[64] = WINPR_C_ARRAY_INIT;
(void)_snprintf(namebuffer, sizeof(namebuffer) - 1, "%s", name);
DynvcTrackerLog(dynChannelContext->log, WLOG_ERROR, dynChannel, cmd, isBackData,
"channel id %" PRIu64 ", name=%s [%" PRIuz "|%" PRIuz "], status=%s",
dynChannelId, namebuffer, len, nameLen,
dynChannel ? openstatus2str(dynChannel->openStatus) : "nullptr");
return PF_CHANNEL_RESULT_ERROR;
}
wStream* currentPacket = channelTracker_getCurrentPacket(tracker);
dev.channel_id = (UINT16)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)
{
DynvcTrackerLog(dynChannelContext->log, WLOG_WARN, dynChannel, cmd, isBackData,
"Reusing channel id, now %s", 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, (UINT32)dynChannelId);
if (!dynChannel)
{
DynvcTrackerLog(dynChannelContext->log, WLOG_ERROR, dynChannel, cmd, isBackData,
"unable to create dynamic channel context data");
return PF_CHANNEL_RESULT_ERROR;
}
DynvcTrackerLog(dynChannelContext->log, WLOG_DEBUG, dynChannel, cmd, isBackData,
"Adding channel");
if (!HashTable_Insert(dynChannelContext->channels, &dynChannel->channelId, dynChannel))
{
DynvcTrackerLog(dynChannelContext->log, WLOG_ERROR, dynChannel, cmd, isBackData,
"unable register dynamic channel context data");
DynamicChannelContext_free(dynChannel);
return PF_CHANNEL_RESULT_ERROR;
}
dynChannel->openStatus = CHANNEL_OPENSTATE_WAITING_OPEN_STATUS;
const BOOL firstPacket = (flags & CHANNEL_FLAG_FIRST) != 0;
const BOOL lastPacket = (flags & CHANNEL_FLAG_LAST) != 0;
// NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert owns dynChannel
return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, FALSE);
}
WINPR_ATTR_NODISCARD
static PfChannelResult DynvcTrackerHandleCreateFront(ChannelStateTracker* tracker, wStream* s,
DWORD flags,
WINPR_ATTR_UNUSED proxyData* pdata,
pServerDynamicChannelContext* dynChannel,
DynChannelContext* dynChannelContext,
WINPR_ATTR_UNUSED UINT64 dynChannelId)
{
const BOOL isBackData = (tracker == dynChannelContext->backTracker);
const BYTE cmd = CREATE_REQUEST_PDU;
/* CREATE_REQUEST_PDU response */
if (!Stream_CheckAndLogRequiredLengthWLogWithBackend(dynChannelContext->log, s, 4, FALSE))
return PF_CHANNEL_RESULT_ERROR;
const UINT32 creationStatus = Stream_Get_UINT32(s);
DynvcTrackerLog(dynChannelContext->log, WLOG_DEBUG, dynChannel, cmd, isBackData,
"CREATE_RESPONSE openStatus=%" PRIu32, creationStatus);
if (dynChannel && (creationStatus == 0))
dynChannel->openStatus = CHANNEL_OPENSTATE_OPENED;
const BOOL firstPacket = (flags & CHANNEL_FLAG_FIRST) != 0;
const BOOL lastPacket = (flags & CHANNEL_FLAG_LAST) != 0;
return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, TRUE);
}
WINPR_ATTR_NODISCARD
static PfChannelResult DynvcTrackerHandleCreate(ChannelStateTracker* tracker, wStream* s,
DWORD flags,
pServerDynamicChannelContext* dynChannel,
UINT64 dynChannelId)
{
WINPR_ASSERT(tracker);
WINPR_ASSERT(s);
DynChannelContext* dynChannelContext =
(DynChannelContext*)channelTracker_getCustomData(tracker);
WINPR_ASSERT(dynChannelContext);
const BOOL lastPacket = (flags & CHANNEL_FLAG_LAST) != 0;
const BOOL isBackData = (tracker == dynChannelContext->backTracker);
proxyData* pdata = channelTracker_getPData(tracker);
WINPR_ASSERT(pdata);
/* we only want the full packet */
if (!lastPacket)
return PF_CHANNEL_RESULT_DROP;
if (isBackData)
return DynvcTrackerHandleCreateBack(tracker, s, flags, pdata, dynChannel, dynChannelContext,
dynChannelId);
return DynvcTrackerHandleCreateFront(tracker, s, flags, pdata, dynChannel, dynChannelContext,
dynChannelId);
}
WINPR_ATTR_NODISCARD
static PfChannelResult DynvcTrackerHandleCmdDATA(ChannelStateTracker* tracker,
pServerDynamicChannelContext* dynChannel,
wStream* s, BYTE cmd, UINT64 Length,
BOOL firstPacket, BOOL lastPacket)
{
WINPR_ASSERT(tracker);
WINPR_ASSERT(s);
DynChannelContext* dynChannelContext =
(DynChannelContext*)channelTracker_getCustomData(tracker);
WINPR_ASSERT(dynChannelContext);
const BOOL isBackData = (tracker == dynChannelContext->backTracker);
if (!dynChannel)
{
DynvcTrackerLog(dynChannelContext->log, WLOG_WARN, dynChannel, cmd, isBackData,
"channel is nullptr, dropping packet");
return PF_CHANNEL_RESULT_DROP;
}
DynChannelTrackerState* trackerState =
isBackData ? &dynChannel->backTracker : &dynChannel->frontTracker;
if (dynChannel->openStatus != CHANNEL_OPENSTATE_OPENED)
{
DynvcTrackerLog(dynChannelContext->log, WLOG_WARN, dynChannel, cmd, isBackData,
"channel is not opened, dropping packet");
return PF_CHANNEL_RESULT_DROP;
}
switch (cmd)
{
case DATA_FIRST_PDU:
case DATA_FIRST_COMPRESSED_PDU:
{
DynvcTrackerLog(dynChannelContext->log, WLOG_DEBUG, dynChannel, cmd, isBackData,
"DATA_FIRST currentPacketLength=%" PRIu64 "", Length);
if (Length > UINT32_MAX)
{
DynvcTrackerLog(dynChannelContext->log, WLOG_ERROR, dynChannel, cmd, isBackData,
"Length out of bounds: %" PRIu64, Length);
return PF_CHANNEL_RESULT_ERROR;
}
trackerState->currentDataLength = (UINT32)Length;
trackerState->CurrentDataReceived = 0;
trackerState->CurrentDataFragments = 0;
if (dynChannel->packetReassembly)
{
if (trackerState->currentPacket)
Stream_ResetPosition(trackerState->currentPacket);
}
}
break;
default:
break;
}
switch (cmd)
{
case DATA_PDU:
case DATA_FIRST_PDU:
{
size_t extraSize = Stream_GetRemainingLength(s);
trackerState->CurrentDataFragments++;
trackerState->CurrentDataReceived += WINPR_ASSERTING_INT_CAST(uint32_t, extraSize);
if (dynChannel->packetReassembly)
{
if (!trackerState->currentPacket)
{
trackerState->currentPacket = Stream_New(nullptr, 1024);
if (!trackerState->currentPacket)
{
DynvcTrackerLog(dynChannelContext->log, WLOG_ERROR, dynChannel, cmd,
isBackData, "unable to create current packet",
getDirection(isBackData), dynChannel->channelName,
drdynvc_get_packet_type(cmd));
return PF_CHANNEL_RESULT_ERROR;
}
}
if (!Stream_EnsureRemainingCapacity(trackerState->currentPacket, extraSize))
{
DynvcTrackerLog(dynChannelContext->log, WLOG_ERROR, dynChannel, cmd, isBackData,
"unable to grow current packet", getDirection(isBackData),
dynChannel->channelName, drdynvc_get_packet_type(cmd));
return PF_CHANNEL_RESULT_ERROR;
}
Stream_Write(trackerState->currentPacket, Stream_ConstPointer(s), extraSize);
}
DynvcTrackerLog(dynChannelContext->log, WLOG_DEBUG, dynChannel, cmd, isBackData,
"frags=%" PRIu32 " received=%" PRIu32 "(%" PRIu32 ")",
trackerState->CurrentDataFragments, trackerState->CurrentDataReceived,
trackerState->currentDataLength);
}
break;
default:
break;
}
switch (cmd)
{
case DATA_PDU:
{
if (trackerState->currentDataLength)
{
if (trackerState->CurrentDataReceived > trackerState->currentDataLength)
{
DynvcTrackerLog(dynChannelContext->log, WLOG_ERROR, dynChannel, cmd, isBackData,
"reassembled packet (%" PRIu32
") is bigger than announced length (%" PRIu32 ")",
trackerState->CurrentDataReceived,
trackerState->currentDataLength);
return PF_CHANNEL_RESULT_ERROR;
}
}
else
{
trackerState->CurrentDataFragments = 0;
trackerState->CurrentDataReceived = 0;
}
}
break;
default:
break;
}
return DynvcTrackerPeekHandleByMode(tracker, trackerState, dynChannel, cmd, firstPacket,
lastPacket);
}
WINPR_ATTR_NODISCARD
static PfChannelResult DynvcTrackerHandleCmd(ChannelStateTracker* tracker,
pServerDynamicChannelContext* dynChannel, wStream* s,
BYTE cmd, UINT32 flags, UINT64 Length,
UINT64 dynChannelId, BOOL firstPacket, BOOL lastPacket)
{
WINPR_ASSERT(tracker);
WINPR_ASSERT(s);
DynChannelContext* dynChannelContext =
(DynChannelContext*)channelTracker_getCustomData(tracker);
WINPR_ASSERT(dynChannelContext);
const BOOL isBackData = (tracker == dynChannelContext->backTracker);
switch (cmd)
{
case CAPABILITY_REQUEST_PDU:
DynvcTrackerLog(dynChannelContext->log, WLOG_DEBUG, dynChannel, cmd, isBackData,
"CAPABILITY_%s", isBackData ? "REQUEST" : "RESPONSE");
channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS);
return PF_CHANNEL_RESULT_PASS;
case CREATE_REQUEST_PDU:
return DynvcTrackerHandleCreate(tracker, s, flags, dynChannel, dynChannelId);
case CLOSE_REQUEST_PDU:
return DynvcTrackerHandleClose(tracker, dynChannel, dynChannelContext, firstPacket,
lastPacket);
case SOFT_SYNC_REQUEST_PDU:
/* just pass then as is for now */
DynvcTrackerLog(dynChannelContext->log, WLOG_DEBUG, dynChannel, cmd, isBackData,
"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 */
DynvcTrackerLog(dynChannelContext->log, WLOG_DEBUG, dynChannel, cmd, isBackData,
"SOFT_SYNC_RESPONSE_PDU");
channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS);
return PF_CHANNEL_RESULT_PASS;
case DATA_FIRST_PDU:
case DATA_PDU:
return DynvcTrackerHandleCmdDATA(tracker, dynChannel, s, cmd, Length, firstPacket,
lastPacket);
case DATA_FIRST_COMPRESSED_PDU:
case DATA_COMPRESSED_PDU:
DynvcTrackerLog(dynChannelContext->log, WLOG_DEBUG, dynChannel, cmd, isBackData,
"TODO: compressed data packets, pass them as is for now");
channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS);
return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, !isBackData);
default:
DynvcTrackerLog(dynChannelContext->log, WLOG_ERROR, dynChannel, cmd, isBackData,
"Invalid command ID");
return PF_CHANNEL_RESULT_ERROR;
}
}
WINPR_ATTR_NODISCARD
static PfChannelResult DynvcTrackerPeekFn(ChannelStateTracker* tracker, BOOL firstPacket,
BOOL lastPacket)
{
wStream* s = nullptr;
wStream sbuffer;
BOOL haveChannelId = 0;
BOOL haveLength = 0;
UINT64 dynChannelId = 0;
UINT64 Length = 0;
pServerDynamicChannelContext* dynChannel = nullptr;
WINPR_ASSERT(tracker);
DynChannelContext* dynChannelContext =
(DynChannelContext*)channelTracker_getCustomData(tracker);
WINPR_ASSERT(dynChannelContext);
const BOOL isBackData = (tracker == dynChannelContext->backTracker);
UINT32 flags = lastPacket ? CHANNEL_FLAG_LAST : 0;
if (firstPacket)
flags |= CHANNEL_FLAG_FIRST;
{
wStream* currentPacket = channelTracker_getCurrentPacket(tracker);
s = Stream_StaticConstInit(&sbuffer, Stream_Buffer(currentPacket),
Stream_GetPosition(currentPacket));
}
if (!Stream_CheckAndLogRequiredLengthWLogWithBackend(dynChannelContext->log, s, 1, isBackData))
return PF_CHANNEL_RESULT_ERROR;
const BYTE byte0 = Stream_Get_UINT8(s);
const BYTE 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:
DynvcTrackerLog(dynChannelContext->log, WLOG_ERROR, dynChannel, cmd, isBackData,
"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 || (dynChannel->openStatus == CHANNEL_OPENSTATE_CLOSED))
{
/* 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:
DynvcTrackerLog(dynChannelContext->log, WLOG_ERROR, dynChannel, cmd, isBackData,
"invalid length field");
return PF_CHANNEL_RESULT_ERROR;
}
}
return DynvcTrackerHandleCmd(tracker, dynChannel, s, cmd, flags, Length, dynChannelId,
firstPacket, lastPacket);
}
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);
}
WINPR_ATTR_NODISCARD
static const char* dynamic_context(void* arg)
{
proxyData* pdata = arg;
if (!pdata)
return "pdata=null";
return pdata->session_id;
}
WINPR_ATTR_MALLOC(DynChannelContext_free, 1)
WINPR_ATTR_NODISCARD
static DynChannelContext* DynChannelContext_new(proxyData* pdata,
pServerStaticChannelContext* channel)
{
DynChannelContext* dyn = calloc(1, sizeof(DynChannelContext));
if (!dyn)
return nullptr;
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 nullptr;
}
WINPR_ATTR_NODISCARD
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);
}
WINPR_ATTR_NODISCARD
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;
}

View File

@@ -0,0 +1,27 @@
/**
* 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>
WINPR_ATTR_NODISCARD BOOL pf_channel_setup_drdynvc(proxyData* pdata,
pServerStaticChannelContext* channel);
#endif /* SERVER_PROXY_CHANNELS_PF_CHANNEL_DRDYNVC_H_ */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,50 @@
/**
* 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>
WINPR_ATTR_NODISCARD BOOL pf_channel_setup_rdpdr(pServerContext* ps,
pServerStaticChannelContext* channel);
void pf_channel_rdpdr_client_free(pClientContext* pc);
WINPR_ATTR_NODISCARD BOOL pf_channel_rdpdr_client_new(pClientContext* pc);
BOOL pf_channel_rdpdr_client_reset(pClientContext* pc);
WINPR_ATTR_NODISCARD 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);
WINPR_ATTR_NODISCARD BOOL pf_channel_rdpdr_server_new(pServerContext* ps);
WINPR_ATTR_NODISCARD BOOL pf_channel_rdpdr_server_announce(pServerContext* ps);
WINPR_ATTR_NODISCARD 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 */

View File

@@ -0,0 +1,394 @@
/**
* 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;
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;
WINPR_ATTR_NODISCARD
static pf_channel_client_context* scard_get_client_context(pClientContext* pc)
{
pf_channel_client_context* scard = nullptr;
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;
}
WINPR_ATTR_NODISCARD
static BOOL pf_channel_client_write_iostatus(wStream* out, const SMARTCARD_OPERATION* op,
NTSTATUS 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_ResetPosition(out);
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_INT32(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);
WINPR_ATTR_MALLOC(queue_free, 1)
WINPR_ATTR_NODISCARD
static void* queue_copy(const void* obj);
static VOID irp_thread(WINPR_ATTR_UNUSED PTP_CALLBACK_INSTANCE Instance, PVOID Context,
PTP_WORK Work)
{
struct thread_arg* arg = Context;
pf_channel_client_context* scard = arg->scard;
{
NTSTATUS 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);
}
WINPR_ATTR_NODISCARD
static BOOL start_irp_thread(pf_channel_client_context* scard,
const pf_channel_client_queue_element* e)
{
PTP_WORK work = nullptr;
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, nullptr);
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;
NTSTATUS ioStatus = 0;
pf_channel_client_queue_element e = WINPR_C_ARRAY_INIT;
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
{
const uint32_t DeviceId = Stream_Get_UINT32(s); /* DeviceId (4 bytes) */
FileId = Stream_Get_UINT32(s); /* FileId (4 bytes) */
CompletionId = Stream_Get_UINT32(s); /* CompletionId (4 bytes) */
const uint32_t MajorFunction = Stream_Get_UINT32(s); /* MajorFunction (4 bytes) */
const uint32_t MinorFunction = Stream_Get_UINT32(s); /* MinorFunction (4 bytes) */
if (MajorFunction != IRP_MJ_DEVICE_CONTROL)
{
WLog_WARN(TAG, "[%s] Invalid IRP received, expected %s, got %s [0x%08" PRIx32 "]",
SCARD_SVC_CHANNEL_NAME, rdpdr_irp_string(IRP_MJ_DEVICE_CONTROL),
rdpdr_irp_string(MajorFunction), MinorFunction);
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(WINPR_ATTR_UNUSED pServerContext* ps,
WINPR_ATTR_UNUSED 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);
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);
}
WINPR_ATTR_MALLOC(queue_free, 1)
WINPR_ATTR_NODISCARD
static void* queue_copy(const void* obj)
{
const pf_channel_client_queue_element* other = obj;
pf_channel_client_queue_element* copy = nullptr;
if (!other)
return nullptr;
copy = calloc(1, sizeof(pf_channel_client_queue_element));
if (!copy)
return nullptr;
*copy = *other;
copy->out = Stream_New(nullptr, 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 nullptr;
}
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 = nullptr;
wObject* obj = nullptr;
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;
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;
}

View File

@@ -0,0 +1,40 @@
/**
* 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*);
WINPR_ATTR_NODISCARD BOOL pf_channel_smartcard_client_new(pClientContext* pc);
void pf_channel_smartcard_client_free(pClientContext* pc);
BOOL pf_channel_smartcard_client_reset(pClientContext* pc);
WINPR_ATTR_NODISCARD BOOL pf_channel_smartcard_client_emulate(pClientContext* pc);
WINPR_ATTR_NODISCARD BOOL pf_channel_smartcard_client_handle(wLog* log, pClientContext* pc,
wStream* s, wStream* out,
pf_scard_send_fkt_t fkt);
WINPR_ATTR_NODISCARD BOOL pf_channel_smartcard_server_handle(pServerContext* ps, wStream* s);
#endif /* FREERDP_SERVER_PROXY_SCARD_H */

View File

@@ -0,0 +1,29 @@
# 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)
set(APP_NAME "freerdp-proxy")
addtargetwithresourcefile(${APP_NAME} TRUE "${FREERDP_VERSION}" PROXY_APP_SRCS)
target_link_libraries(${APP_NAME} ${MODULE_NAME})
installwithrpath(TARGETS ${APP_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT server)
set_property(TARGET ${APP_NAME} PROPERTY FOLDER "Server/proxy")
generate_and_install_freerdp_man_from_template(${APP_NAME} "1" "${FREERDP_API_VERSION}")

View File

@@ -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 (preferably 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>

View File

@@ -0,0 +1,193 @@
/**
* 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 = nullptr;
#if defined(_WIN32)
WINPR_ATTR_NODISCARD
static const char* strsignal(int signum)
{
switch (signum)
{
case SIGINT:
return "SIGINT";
case SIGTERM:
return "SIGTERM";
default:
return "UNKNOWN";
}
}
#endif
// NOLINTBEGIN(bugprone-signal-handler,cert-msc54-cpp,cert-sig30-c)
static void cleanup_handler(int signum)
{
// NOLINTNEXTLINE(concurrency-mt-unsafe)
WLog_INFO(TAG, "caught signal %s [%d], starting cleanup...", strsignal(signum), signum);
WLog_INFO(TAG, "stopping all connections.");
pf_server_stop(server);
}
// NOLINTEND(bugprone-signal-handler,cert-msc54-cpp,cert-sig30-c)
static void pf_server_register_signal_handlers(void)
{
(void)signal(SIGINT, cleanup_handler);
(void)signal(SIGTERM, cleanup_handler);
#ifndef _WIN32
(void)signal(SIGQUIT, cleanup_handler);
(void)signal(SIGKILL, cleanup_handler);
#endif
}
static int 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);
return 0;
}
WINPR_ATTR_NODISCARD
static int version(const char* app)
{
printf("%s version %s", app, freerdp_get_version_string());
return 0;
}
WINPR_ATTR_NODISCARD
static int buildconfig(WINPR_ATTR_UNUSED const char* app)
{
printf("This is FreeRDP version %s (%s)\n", FREERDP_VERSION_FULL, FREERDP_GIT_REVISION);
printf("%s", freerdp_get_build_config());
return 0;
}
int main(int argc, char* argv[])
{
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)
{
status = usage(argv[0]);
goto fail;
}
{
const char* arg = argv[1];
if (_stricmp(arg, "-h") == 0)
{
status = usage(argv[0]);
goto fail;
}
else if (_stricmp(arg, "--help") == 0)
{
status = usage(argv[0]);
goto fail;
}
else if (_stricmp(arg, "--buildconfig") == 0)
{
status = buildconfig(argv[0]);
goto fail;
}
else if (_stricmp(arg, "--dump-config") == 0)
{
if (argc != 3)
{
status = usage(argv[0]);
goto fail;
}
status = pf_server_config_dump(argv[2]) ? 0 : -1;
goto fail;
}
else if (_stricmp(arg, "-v") == 0)
{
status = version(argv[0]);
goto fail;
}
else if (_stricmp(arg, "--version") == 0)
{
status = version(argv[0]);
goto fail;
}
}
{
const char* config_path = argv[1];
if (argc != 2)
{
status = usage(argv[0]);
goto fail;
}
{
proxyConfig* 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;
}

View File

@@ -0,0 +1,15 @@
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: @FREERDP_PROXY_PC_REQUIRES@
Requires.private: @FREERDP_PROXY_PC_REQUIRES_PRIVATE@
Libs: -L${libdir} ${libs}
Libs.private: @FREERDP_PROXY_PC_LIBS_PRIVATE@
Cflags: -I${includedir}

View File

@@ -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)

View File

@@ -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 libraries 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`.

View File

@@ -0,0 +1,50 @@
#
# 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}")
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../../../cmake/)
include(ProjectCXXStandard)
include(CommonConfigOptions)
include(CXXCompilerFlags)
set(SRCS bitmap-filter.cpp)
addtargetwithresourcefile(${PROJECT_NAME} FALSE "${PROJECT_VERSION}" SRCS FALSE)
target_compile_definitions(${PROJECT_NAME} PRIVATE $<$<BOOL:${BUILD_SHARED_LIBS}>:BUILD_SHARED_LIBS>)
target_link_libraries(${PROJECT_NAME} winpr freerdp)
installwithrpath(TARGETS ${PROJECT_NAME} DESTINATION ${FREERDP_PROXY_PLUGINDIR})
set(PROJECT_PC_REQUIRES_PRIVATE "winpr${FREERDP_API_VERSION} freerdp${FREERDP_API_VERSION}")
include(ProxyModuleConfig)
generate_proxy_module_config()

View File

@@ -0,0 +1,504 @@
/**
* 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")
// #define REPLY_WITH_EMPTY_OFFER
static constexpr char plugin_name[] = "bitmap-filter";
static constexpr char plugin_desc[] =
"this plugin deactivates and filters persistent bitmap cache.";
[[nodiscard]] static const std::vector<std::string>& plugin_static_intercept()
{
static std::vector<std::string> vec;
if (vec.empty())
vec.emplace_back(DRDYNVC_SVC_CHANNEL_NAME);
return vec;
}
[[nodiscard]] static const std::vector<std::string>& plugin_dyn_intercept()
{
static std::vector<std::string> vec;
if (vec.empty())
vec.emplace_back(RDPGFX_DVC_CHANNEL_NAME);
return vec;
}
class DynChannelState
{
public:
[[nodiscard]] bool skip() const
{
return _toSkip != 0;
}
[[nodiscard]] bool skip(size_t s)
{
if (s > _toSkip)
_toSkip = 0;
else
_toSkip -= s;
return skip();
}
[[nodiscard]] size_t remaining() const
{
return _toSkip;
}
[[nodiscard]] size_t total() const
{
return _totalSkipSize;
}
void setSkipSize(size_t len)
{
_toSkip = _totalSkipSize = len;
}
[[nodiscard]] bool drop() const
{
return _drop;
}
void setDrop(bool d)
{
_drop = d;
}
[[nodiscard]] 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;
};
[[nodiscard]] static BOOL filter_client_pre_connect([[maybe_unused]] proxyPlugin* plugin,
[[maybe_unused]] proxyData* pdata,
[[maybe_unused]] 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);
}
[[nodiscard]] static BOOL filter_dyn_channel_intercept_list([[maybe_unused]] proxyPlugin* plugin,
[[maybe_unused]] proxyData* pdata,
[[maybe_unused]] 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;
}
[[nodiscard]] static BOOL filter_static_channel_intercept_list([[maybe_unused]] proxyPlugin* plugin,
[[maybe_unused]] proxyData* pdata,
[[maybe_unused]] 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;
}
[[nodiscard]] static size_t drdynvc_cblen_to_bytes(UINT8 cbLen)
{
switch (cbLen)
{
case 0:
return 1;
case 1:
return 2;
default:
return 4;
}
}
[[nodiscard]] 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;
}
[[nodiscard]] static BOOL drdynvc_try_read_header(wStream* s, uint32_t& channelId, size_t& length)
{
UINT8 value = 0;
Stream_ResetPosition(s);
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;
}
[[nodiscard]] 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));
}
[[nodiscard]] 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);
}
#if defined(REPLY_WITH_EMPTY_OFFER)
[[nodiscard]] static UINT8 drdynvc_value_to_cblen(UINT32 value)
{
if (value <= 0xFF)
return 0;
if (value <= 0xFFFF)
return 1;
return 2;
}
[[nodiscard]] 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;
}
[[nodiscard]] 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);
}
[[nodiscard]] 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;
}
#endif
[[nodiscard]] 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)
{
uint32_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())
{
if (state->skip(inputDataLength))
{
WLog_DBG(TAG,
"skipping data, but %" PRIuz " bytes left [stream has %" PRIuz " bytes]",
state->remaining(), 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 defined(REPLY_WITH_EMPTY_OFFER) // 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;
}
[[nodiscard]] static BOOL filter_server_session_started(proxyPlugin* plugin, proxyData* pdata,
void* /*unused*/)
{
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;
}
[[nodiscard]] static BOOL filter_server_session_end(proxyPlugin* plugin, proxyData* pdata,
void* /*unused*/)
{
WINPR_ASSERT(plugin);
WINPR_ASSERT(pdata);
auto state = filter_get_plugin_data(plugin, pdata);
delete state;
return filter_set_plugin_data(plugin, pdata, nullptr);
}
[[nodiscard]] static BOOL int_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);
}
#ifdef __cplusplus
extern "C"
{
#endif
#if defined(BUILD_SHARED_LIBS)
[[nodiscard]]
FREERDP_API BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata);
BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata)
{
return int_proxy_module_entry_point(plugins_manager, userdata);
}
#else
[[nodiscard]]
FREERDP_API BOOL bitmap_filter_proxy_module_entry_point(proxyPluginsManager* plugins_manager,
void* userdata);
BOOL bitmap_filter_proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata)
{
return int_proxy_module_entry_point(plugins_manager, userdata);
}
#endif
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,50 @@
#
# 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(ProjectCXXStandard)
include(CommonConfigOptions)
include(CXXCompilerFlags)
set(SRCS demo.cpp)
addtargetwithresourcefile(${PROJECT_NAME} FALSE "${PROJECT_VERSION}" SRCS FALSE)
target_compile_definitions(${PROJECT_NAME} PRIVATE $<$<BOOL:${BUILD_SHARED_LIBS}>:BUILD_SHARED_LIBS>)
target_link_libraries(${PROJECT_NAME} winpr)
installwithrpath(TARGETS ${PROJECT_NAME} DESTINATION ${FREERDP_PROXY_PLUGINDIR})
set(PROJECT_PC_REQUIRES_PRIVATE "winpr${FREERDP_API_VERSION}")
include(ProxyModuleConfig)
generate_proxy_module_config()

View File

@@ -0,0 +1,513 @@
/**
* 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";
[[nodiscard]]
static BOOL demo_plugin_unload([[maybe_unused]] 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;
}
[[nodiscard]]
static BOOL demo_client_init_connect([[maybe_unused]] proxyPlugin* plugin,
[[maybe_unused]] proxyData* pdata,
[[maybe_unused]] void* custom)
{
WINPR_ASSERT(plugin);
WINPR_ASSERT(pdata);
WINPR_ASSERT(custom);
WLog_INFO(TAG, "called");
return TRUE;
}
[[nodiscard]]
static BOOL demo_client_uninit_connect([[maybe_unused]] proxyPlugin* plugin,
[[maybe_unused]] proxyData* pdata,
[[maybe_unused]] void* custom)
{
WINPR_ASSERT(plugin);
WINPR_ASSERT(pdata);
WINPR_ASSERT(custom);
WLog_INFO(TAG, "called");
return TRUE;
}
[[nodiscard]]
static BOOL demo_client_pre_connect([[maybe_unused]] proxyPlugin* plugin,
[[maybe_unused]] proxyData* pdata,
[[maybe_unused]] void* custom)
{
WINPR_ASSERT(plugin);
WINPR_ASSERT(pdata);
WINPR_ASSERT(custom);
WLog_INFO(TAG, "called");
return TRUE;
}
[[nodiscard]]
static BOOL demo_client_post_connect([[maybe_unused]] proxyPlugin* plugin,
[[maybe_unused]] proxyData* pdata,
[[maybe_unused]] void* custom)
{
WINPR_ASSERT(plugin);
WINPR_ASSERT(pdata);
WINPR_ASSERT(custom);
WLog_INFO(TAG, "called");
return TRUE;
}
[[nodiscard]]
static BOOL demo_client_post_disconnect([[maybe_unused]] proxyPlugin* plugin,
[[maybe_unused]] proxyData* pdata,
[[maybe_unused]] void* custom)
{
WINPR_ASSERT(plugin);
WINPR_ASSERT(pdata);
WINPR_ASSERT(custom);
WLog_INFO(TAG, "called");
return TRUE;
}
[[nodiscard]]
static BOOL demo_client_x509_certificate([[maybe_unused]] proxyPlugin* plugin,
[[maybe_unused]] proxyData* pdata,
[[maybe_unused]] void* custom)
{
WINPR_ASSERT(plugin);
WINPR_ASSERT(pdata);
WINPR_ASSERT(custom);
WLog_INFO(TAG, "called");
return TRUE;
}
[[nodiscard]]
static BOOL demo_client_login_failure([[maybe_unused]] proxyPlugin* plugin,
[[maybe_unused]] proxyData* pdata,
[[maybe_unused]] void* custom)
{
WINPR_ASSERT(plugin);
WINPR_ASSERT(pdata);
WINPR_ASSERT(custom);
WLog_INFO(TAG, "called");
return TRUE;
}
[[nodiscard]]
static BOOL demo_client_end_paint([[maybe_unused]] proxyPlugin* plugin,
[[maybe_unused]] proxyData* pdata, [[maybe_unused]] void* custom)
{
WINPR_ASSERT(plugin);
WINPR_ASSERT(pdata);
WINPR_ASSERT(custom);
WLog_INFO(TAG, "called");
return TRUE;
}
[[nodiscard]]
static BOOL demo_client_redirect([[maybe_unused]] proxyPlugin* plugin,
[[maybe_unused]] proxyData* pdata, [[maybe_unused]] void* custom)
{
WINPR_ASSERT(plugin);
WINPR_ASSERT(pdata);
WINPR_ASSERT(custom);
WLog_INFO(TAG, "called");
return TRUE;
}
[[nodiscard]]
static BOOL demo_server_post_connect([[maybe_unused]] proxyPlugin* plugin,
[[maybe_unused]] proxyData* pdata,
[[maybe_unused]] void* custom)
{
WINPR_ASSERT(plugin);
WINPR_ASSERT(pdata);
WINPR_ASSERT(custom);
WLog_INFO(TAG, "called");
return TRUE;
}
[[nodiscard]]
static BOOL demo_server_peer_activate([[maybe_unused]] proxyPlugin* plugin,
[[maybe_unused]] proxyData* pdata,
[[maybe_unused]] void* custom)
{
WINPR_ASSERT(plugin);
WINPR_ASSERT(pdata);
WINPR_ASSERT(custom);
WLog_INFO(TAG, "called");
return TRUE;
}
[[nodiscard]]
static BOOL demo_server_channels_init([[maybe_unused]] proxyPlugin* plugin,
[[maybe_unused]] proxyData* pdata,
[[maybe_unused]] void* custom)
{
WINPR_ASSERT(plugin);
WINPR_ASSERT(pdata);
WINPR_ASSERT(custom);
WLog_INFO(TAG, "called");
return TRUE;
}
[[nodiscard]]
static BOOL demo_server_channels_free([[maybe_unused]] proxyPlugin* plugin,
[[maybe_unused]] proxyData* pdata,
[[maybe_unused]] void* custom)
{
WINPR_ASSERT(plugin);
WINPR_ASSERT(pdata);
WINPR_ASSERT(custom);
WLog_INFO(TAG, "called");
return TRUE;
}
[[nodiscard]]
static BOOL demo_server_session_end([[maybe_unused]] proxyPlugin* plugin,
[[maybe_unused]] proxyData* pdata,
[[maybe_unused]] void* custom)
{
WINPR_ASSERT(plugin);
WINPR_ASSERT(pdata);
WINPR_ASSERT(custom);
WLog_INFO(TAG, "called");
return TRUE;
}
[[nodiscard]]
static BOOL demo_filter_keyboard_event([[maybe_unused]] proxyPlugin* plugin,
[[maybe_unused]] proxyData* pdata,
[[maybe_unused]] 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;
}
[[nodiscard]]
static BOOL demo_filter_unicode_event([[maybe_unused]] proxyPlugin* plugin,
[[maybe_unused]] proxyData* pdata,
[[maybe_unused]] 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;
}
[[nodiscard]]
static BOOL demo_mouse_event([[maybe_unused]] proxyPlugin* plugin,
[[maybe_unused]] proxyData* pdata, [[maybe_unused]] void* param)
{
auto event_data = static_cast<const proxyMouseEventInfo*>(param);
WINPR_ASSERT(plugin);
WINPR_ASSERT(pdata);
WINPR_ASSERT(event_data);
WLog_INFO(TAG, "called %p", WINPR_CXX_COMPAT_CAST(const void*, event_data));
return TRUE;
}
[[nodiscard]]
static BOOL demo_mouse_ex_event([[maybe_unused]] proxyPlugin* plugin,
[[maybe_unused]] proxyData* pdata, [[maybe_unused]] void* param)
{
auto event_data = static_cast<const proxyMouseExEventInfo*>(param);
WINPR_ASSERT(plugin);
WINPR_ASSERT(pdata);
WINPR_ASSERT(event_data);
WLog_INFO(TAG, "called %p", WINPR_CXX_COMPAT_CAST(const void*, event_data));
return TRUE;
}
[[nodiscard]]
static BOOL demo_client_channel_data([[maybe_unused]] proxyPlugin* plugin,
[[maybe_unused]] proxyData* pdata,
[[maybe_unused]] void* param)
{
const auto* 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;
}
[[nodiscard]]
static BOOL demo_server_channel_data([[maybe_unused]] proxyPlugin* plugin,
[[maybe_unused]] proxyData* pdata,
[[maybe_unused]] void* param)
{
const auto* 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;
}
[[nodiscard]]
static BOOL demo_dynamic_channel_create([[maybe_unused]] proxyPlugin* plugin,
[[maybe_unused]] proxyData* pdata,
[[maybe_unused]] void* param)
{
const auto* 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;
}
[[nodiscard]]
static BOOL demo_server_fetch_target_addr([[maybe_unused]] proxyPlugin* plugin,
[[maybe_unused]] proxyData* pdata,
[[maybe_unused]] void* param)
{
auto event_data = static_cast<const proxyFetchTargetEventInfo*>(param);
WINPR_ASSERT(plugin);
WINPR_ASSERT(pdata);
WINPR_ASSERT(event_data);
WLog_INFO(TAG, "called %p", WINPR_CXX_COMPAT_CAST(const void*, event_data));
return TRUE;
}
[[nodiscard]]
static BOOL demo_server_peer_logon([[maybe_unused]] proxyPlugin* plugin,
[[maybe_unused]] proxyData* pdata, [[maybe_unused]] 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;
}
[[nodiscard]]
static BOOL demo_dyn_channel_intercept_list([[maybe_unused]] proxyPlugin* plugin,
[[maybe_unused]] proxyData* pdata,
[[maybe_unused]] void* arg)
{
auto data = static_cast<proxyChannelToInterceptData*>(arg);
WINPR_ASSERT(plugin);
WINPR_ASSERT(pdata);
WINPR_ASSERT(data);
WLog_INFO(TAG, "%s: %p", __func__, WINPR_CXX_COMPAT_CAST(const void*, data));
return TRUE;
}
[[nodiscard]]
static BOOL demo_static_channel_intercept_list([[maybe_unused]] proxyPlugin* plugin,
[[maybe_unused]] proxyData* pdata,
[[maybe_unused]] void* arg)
{
auto data = static_cast<proxyChannelToInterceptData*>(arg);
WINPR_ASSERT(plugin);
WINPR_ASSERT(pdata);
WINPR_ASSERT(data);
WLog_INFO(TAG, "%s: %p", __func__, WINPR_CXX_COMPAT_CAST(const void*, data));
return TRUE;
}
[[nodiscard]]
static BOOL demo_dyn_channel_intercept([[maybe_unused]] proxyPlugin* plugin,
[[maybe_unused]] proxyData* pdata,
[[maybe_unused]] void* arg)
{
auto data = static_cast<proxyDynChannelInterceptData*>(arg);
WINPR_ASSERT(plugin);
WINPR_ASSERT(pdata);
WINPR_ASSERT(data);
WLog_INFO(TAG, "%s: %p", __func__, WINPR_CXX_COMPAT_CAST(const void*, data));
return TRUE;
}
[[nodiscard]]
static BOOL int_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);
}
#ifdef __cplusplus
extern "C"
{
#endif
#if defined(BUILD_SHARED_LIBS)
[[nodiscard]]
FREERDP_API BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata);
BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata)
{
return int_proxy_module_entry_point(plugins_manager, userdata);
}
#else
[[nodiscard]]
FREERDP_API BOOL demo_proxy_module_entry_point(proxyPluginsManager* plugins_manager,
void* userdata);
BOOL demo_proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata)
{
return int_proxy_module_entry_point(plugins_manager, userdata);
}
#endif
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,51 @@
#
# 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)
include(CXXCompilerFlags)
include(ProjectCXXStandard)
set(SRCS dyn-channel-dump.cpp)
addtargetwithresourcefile(${PROJECT_NAME} FALSE "${PROJECT_VERSION}" SRCS FALSE)
target_compile_definitions(${PROJECT_NAME} PRIVATE $<$<BOOL:${BUILD_SHARED_LIBS}>:BUILD_SHARED_LIBS>)
target_link_libraries(${PROJECT_NAME} PRIVATE winpr freerdp freerdp-client freerdp-server freerdp-server-proxy)
installwithrpath(TARGETS ${PROJECT_NAME} DESTINATION ${FREERDP_PROXY_PLUGINDIR})
set(PROJECT_PC_REQUIRES_PRIVATE
"winpr${FREERDP_API_VERSION} freerdp${FREERDP_API_VERSION} freerdp-server${FREERDP_API_VERSION} freerdp-client${FREERDP_API_VERSION} freerdp-server-proxy${FREERDP_API_VERSION}"
)
include(ProxyModuleConfig)
generate_proxy_module_config()

View File

@@ -0,0 +1,480 @@
/**
* 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 <limits>
#include <utility>
#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.";
[[nodiscard]] static const std::vector<std::string>& plugin_static_intercept()
{
static std::vector<std::string> vec;
if (vec.empty())
vec.emplace_back(DRDYNVC_SVC_CHANNEL_NAME);
return vec;
}
static constexpr char key_path[] = "path";
static constexpr char key_channels[] = "channels";
class PluginData
{
public:
explicit PluginData(proxyPluginsManager* mgr) : _mgr(mgr)
{
}
[[nodiscard]] proxyPluginsManager* mgr() const
{
return _mgr;
}
uint64_t session()
{
return _sessionid++;
}
private:
proxyPluginsManager* _mgr;
uint64_t _sessionid{ 0 };
};
class ChannelData
{
public:
ChannelData(const std::string& base, std::vector<std::string> list, uint64_t sessionid)
: _base(base), _channels_to_dump(std::move(list)), _session_id(sessionid)
{
char str[64] = {};
(void)_snprintf(str, sizeof(str), "session-%016" PRIx64, _session_id);
_base /= str;
}
bool add(const std::string& name, WINPR_ATTR_UNUSED bool back)
{
std::scoped_lock 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::scoped_lock 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);
}
[[nodiscard]] 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;
}
[[nodiscard]] uint64_t session() const
{
return _session_id;
}
private:
[[nodiscard]] fs::path filepath(const std::string& channel, bool back, uint64_t count) const
{
auto name = idstr(channel, back);
char cstr[32] = {};
(void)_snprintf(cstr, sizeof(cstr), "%016" PRIx64 "-", count);
auto path = _base / cstr;
path += name;
path += ".dump";
return path;
}
[[nodiscard]] static std::string idstr(const std::string& name, bool back)
{
std::stringstream ss;
ss << name << ".";
if (back)
ss << "back";
else
ss << "front";
return ss.str();
}
fs::path _base;
std::vector<std::string> _channels_to_dump;
std::mutex _mux;
std::map<std::string, uint64_t> _map;
uint64_t _session_id;
};
[[nodiscard]] static PluginData* dump_get_plugin_data(proxyPlugin* plugin)
{
WINPR_ASSERT(plugin);
auto plugindata = static_cast<PluginData*>(plugin->custom);
WINPR_ASSERT(plugindata);
return plugindata;
}
[[nodiscard]] 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));
}
[[nodiscard]] 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);
}
[[nodiscard]] 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);
}
[[nodiscard]] 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;
}
[[nodiscard]] static BOOL dump_static_channel_intercept_list([[maybe_unused]] proxyPlugin* plugin,
[[maybe_unused]] 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;
}
[[nodiscard]] 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;
}
const auto s = Stream_Length(data->data);
if (s > std::numeric_limits<std::streamsize>::max())
{
WLog_ERR(TAG, "Stream length %" PRIuz " exceeds std::streamsize::max", s);
return FALSE;
}
stream.write(buffer, static_cast<std::streamsize>(s));
if (stream.fail())
{
WLog_ERR(TAG, "Could not write to stream");
return FALSE;
}
stream.flush();
}
return TRUE;
}
[[nodiscard]] 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 };
}
[[nodiscard]] static BOOL dump_session_started(proxyPlugin* plugin, proxyData* pdata,
void* /*unused*/)
{
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, std::move(list), custom->session());
if (!cfg || !cfg->create())
{
delete cfg;
return FALSE;
}
if (!dump_set_plugin_data(plugin, pdata, cfg))
return FALSE;
WLog_DBG(TAG, "starting session dump %" PRIu64, cfg->session());
return TRUE;
}
[[nodiscard]] static BOOL dump_session_end(proxyPlugin* plugin, proxyData* pdata, void* /*unused*/)
{
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());
return dump_set_plugin_data(plugin, pdata, nullptr);
}
[[nodiscard]] static BOOL dump_unload(proxyPlugin* plugin)
{
if (!plugin)
return TRUE;
delete static_cast<PluginData*>(plugin->custom);
return TRUE;
}
[[nodiscard]] static BOOL int_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);
}
#ifdef __cplusplus
extern "C"
{
#endif
#if defined(BUILD_SHARED_LIBS)
[[nodiscard]]
FREERDP_API BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata);
BOOL proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata)
{
return int_proxy_module_entry_point(plugins_manager, userdata);
}
#else
[[nodiscard]]
FREERDP_API BOOL dyn_channel_dump_proxy_module_entry_point(proxyPluginsManager* plugins_manager,
void* userdata);
BOOL dyn_channel_dump_proxy_module_entry_point(proxyPluginsManager* plugins_manager, void* userdata)
{
return int_proxy_module_entry_point(plugins_manager, userdata);
}
#endif
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,16 @@
prefix=@PKG_CONFIG_INSTALL_PREFIX@
exec_prefix=${prefix}
libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
includedir=${prefix}/@WINPR_INCLUDE_DIR@
plugindir=${libdir}/@FREERDP_MAJOR_DIR@
proxy_plugindir=${plugindir}/proxy
Name: @PROJECT_NAME@
Description: FreeRDP proxy module
URL: http://www.freerdp.com/
Version: @PROJECT_VERSION@
Requires: @PROJECT_PC_REQUIRES@
Requires.private: @PROJECT_PC_REQUIRES_PRIVATE@
Libs: -L${libdir}
Libs.private: -Wl,--whole-archive \${proxy_plugindir}/@PROJECT_LIBRARY_NAME@ -u @PROJECT_SHORT_NAME_UNDERSCORE@_proxy_module_entry_point -Wl,--no-whole-archive
Cflags: -I${includedir}

View File

@@ -0,0 +1,362 @@
/**
* 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 <winpr/cast.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 sChannelStateTracker
{
pServerStaticChannelContext* channel;
ChannelTrackerMode mode;
wStream* currentPacket;
size_t currentPacketReceived;
size_t currentPacketSize;
size_t currentPacketFragments;
ChannelTrackerPeekFn peekFn;
void* trackerData;
proxyData* pdata;
};
WINPR_ATTR_NODISCARD
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 < 1ULL * 1000ULL * 1000ULL)
create = FALSE;
else
Stream_Free(tracker->currentPacket, TRUE);
}
if (create)
tracker->currentPacket = Stream_New(nullptr, 10ULL * 1024ULL);
if (!tracker->currentPacket)
return FALSE;
Stream_ResetPosition(tracker->currentPacket);
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 nullptr;
}
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) != 0;
BOOL lastPacket = (flags & CHANNEL_FLAG_LAST) != 0;
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 PF_CHANNEL_RESULT_ERROR;
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;
default:
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 = nullptr;
pServerContext* ps = nullptr;
pServerStaticChannelContext* channel = nullptr;
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 = WINPR_C_ARRAY_INIT;
ev.channel_id = WINPR_ASSERTING_INT_CAST(UINT16, 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, WINPR_ASSERTING_INT_CAST(UINT16, channel->front_channel_id),
currentPacketSize, flags, Stream_Buffer(currentPacket), Stream_GetPosition(currentPacket));
return r ? PF_CHANNEL_RESULT_DROP : PF_CHANNEL_RESULT_ERROR;
}
WINPR_ATTR_NODISCARD
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 = WINPR_C_ARRAY_INIT;
WINPR_ASSERT(pdata);
WINPR_ASSERT(channel);
switch (channel->channelMode)
{
case PF_UTILS_CHANNEL_PASSTHROUGH:
ev.channel_id = WINPR_ASSERTING_INT_CAST(UINT16, 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;
}
}
WINPR_ATTR_NODISCARD
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 = WINPR_C_ARRAY_INIT;
WINPR_ASSERT(pdata);
WINPR_ASSERT(channel);
switch (channel->channelMode)
{
case PF_UTILS_CHANNEL_PASSTHROUGH:
ev.channel_id = WINPR_ASSERTING_INT_CAST(UINT16, 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)
{
WINPR_ASSERT(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;
}

View File

@@ -0,0 +1,66 @@
/**
* 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 sChannelStateTracker ChannelStateTracker;
typedef PfChannelResult (*ChannelTrackerPeekFn)(ChannelStateTracker* tracker, BOOL first,
BOOL lastPacket);
void channelTracker_free(ChannelStateTracker* t);
WINPR_ATTR_MALLOC(channelTracker_free, 1)
WINPR_ATTR_NODISCARD
ChannelStateTracker* channelTracker_new(pServerStaticChannelContext* channel,
ChannelTrackerPeekFn fn, void* data);
BOOL channelTracker_setMode(ChannelStateTracker* tracker, ChannelTrackerMode mode);
WINPR_ATTR_NODISCARD ChannelTrackerMode channelTracker_getMode(ChannelStateTracker* tracker);
WINPR_ATTR_NODISCARD BOOL channelTracker_setPData(ChannelStateTracker* tracker, proxyData* pdata);
WINPR_ATTR_NODISCARD proxyData* channelTracker_getPData(ChannelStateTracker* tracker);
WINPR_ATTR_NODISCARD BOOL channelTracker_setCustomData(ChannelStateTracker* tracker, void* data);
WINPR_ATTR_NODISCARD void* channelTracker_getCustomData(ChannelStateTracker* tracker);
WINPR_ATTR_NODISCARD wStream* channelTracker_getCurrentPacket(ChannelStateTracker* tracker);
WINPR_ATTR_NODISCARD size_t channelTracker_getCurrentPacketSize(ChannelStateTracker* tracker);
BOOL channelTracker_setCurrentPacketSize(ChannelStateTracker* tracker, size_t size);
WINPR_ATTR_NODISCARD PfChannelResult channelTracker_update(ChannelStateTracker* tracker,
const BYTE* xdata, size_t xsize,
UINT32 flags, size_t totalSize);
WINPR_ATTR_NODISCARD PfChannelResult channelTracker_flushCurrent(ChannelStateTracker* t, BOOL first,
BOOL last, BOOL toBack);
WINPR_ATTR_NODISCARD BOOL pf_channel_setup_generic(pServerStaticChannelContext* channel);
#endif /* SERVER_PROXY_PF_CHANNEL_H_ */

File diff suppressed because it is too large Load Diff

View File

@@ -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);
WINPR_ATTR_NODISCARD DWORD WINAPI pf_client_start(LPVOID arg);
#endif /* FREERDP_SERVER_PROXY_PFCLIENT_H */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,427 @@
/**
* 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 <freerdp/channels/drdynvc.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")
WINPR_ATTR_NODISCARD
static UINT32 ChannelId_Hash(const void* key)
{
const UINT32* v = (const UINT32*)key;
return *v;
}
WINPR_ATTR_NODISCARD
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);
}
WINPR_ATTR_NODISCARD
static BOOL dyn_intercept(pServerContext* ps, const char* name)
{
if (strncmp(DRDYNVC_SVC_CHANNEL_NAME, name, sizeof(DRDYNVC_SVC_CHANNEL_NAME)) != 0)
return FALSE;
WINPR_ASSERT(ps);
WINPR_ASSERT(ps->pdata);
const proxyConfig* cfg = ps->pdata->config;
WINPR_ASSERT(cfg);
if (!cfg->GFX)
return TRUE;
if (!cfg->AudioOutput)
return TRUE;
if (!cfg->AudioInput)
return TRUE;
if (!cfg->Multitouch)
return TRUE;
if (!cfg->VideoRedirection)
return TRUE;
if (!cfg->CameraRedirection)
return TRUE;
return FALSE;
}
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 nullptr;
}
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 nullptr;
}
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 if (dyn_intercept(ps, name))
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);
WINPR_ATTR_NODISCARD
static BOOL client_to_proxy_context_new(freerdp_peer* client, rdpContext* ctx)
{
wObject* obj = nullptr;
pServerContext* context = (pServerContext*)ctx;
WINPR_ASSERT(client);
WINPR_ASSERT(context);
context->dynvcReady = nullptr;
context->vcm = WTSOpenServerA((LPSTR)client->context);
if (!context->vcm || context->vcm == INVALID_HANDLE_VALUE)
goto error;
if (!(context->dynvcReady = CreateEvent(nullptr, TRUE, FALSE, nullptr)))
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)
{
(void)CloseHandle(context->dynvcReady);
context->dynvcReady = nullptr;
}
HashTable_Free(context->interceptContextMap);
HashTable_Free(context->channelsByFrontId);
HashTable_Free(context->channelsByBackId);
if (context->vcm && (context->vcm != INVALID_HANDLE_VALUE))
WTSCloseServer(context->vcm);
context->vcm = nullptr;
}
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);
}
WINPR_ATTR_NODISCARD
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 = nullptr;
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 nullptr if `dst` is client's context
* it must be freed before setting it to nullptr to avoid a memory leak!
*/
if (!freerdp_settings_set_pointer_len(dst, FreeRDP_RdpServerRsaKey, nullptr, 1))
goto out_fail;
}
/* We handle certificate management for this client ourselves. */
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 = WINPR_C_ARRAY_INIT;
WINPR_ASSERT(clientSettings);
RdpClientEntry(&clientEntryPoints);
rdpContext* context = freerdp_client_context_new(&clientEntryPoints);
if (!context)
return nullptr;
pClientContext* pc = (pClientContext*)context;
if (!pf_context_copy_settings(context->settings, clientSettings))
goto error;
return pc;
error:
freerdp_client_context_free(context);
return nullptr;
}
proxyData* proxy_data_new(void)
{
BYTE temp[16];
char* hex = nullptr;
proxyData* pdata = nullptr;
pdata = calloc(1, sizeof(proxyData));
if (!pdata)
return nullptr;
if (!(pdata->abort_event = CreateEvent(nullptr, TRUE, FALSE, nullptr)))
goto error;
if (!(pdata->gfx_server_ready = CreateEvent(nullptr, TRUE, FALSE, nullptr)))
goto error;
if (winpr_RAND(&temp, 16) < 0)
goto error;
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 nullptr;
}
/* 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)
(void)CloseHandle(pdata->abort_event);
if (pdata->client_thread)
(void)CloseHandle(pdata->client_thread);
if (pdata->gfx_server_ready)
(void)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);
(void)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;
}

View File

@@ -0,0 +1,211 @@
/**
* 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"
WINPR_ATTR_NODISCARD
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;
}
WINPR_ATTR_NODISCARD
static BOOL pf_server_synchronize_event(rdpInput* input, UINT32 flags)
{
pServerContext* ps = nullptr;
pClientContext* pc = nullptr;
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;
return pf_server_check_and_sync_input_state(pc);
}
WINPR_ATTR_NODISCARD
static BOOL pf_server_keyboard_event(rdpInput* input, UINT16 flags, UINT8 code)
{
const proxyConfig* config = nullptr;
proxyKeyboardEventInfo event = WINPR_C_ARRAY_INIT;
pServerContext* ps = nullptr;
pClientContext* pc = nullptr;
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;
}
WINPR_ATTR_NODISCARD
static BOOL pf_server_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code)
{
const proxyConfig* config = nullptr;
proxyUnicodeEventInfo event = WINPR_C_ARRAY_INIT;
pServerContext* ps = nullptr;
pClientContext* pc = nullptr;
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;
}
WINPR_ATTR_NODISCARD
static BOOL pf_server_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
{
proxyMouseEventInfo event = WINPR_C_ARRAY_INIT;
const proxyConfig* config = nullptr;
pServerContext* ps = nullptr;
pClientContext* pc = nullptr;
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;
}
WINPR_ATTR_NODISCARD
static BOOL pf_server_extended_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
{
const proxyConfig* config = nullptr;
proxyMouseExEventInfo event = WINPR_C_ARRAY_INIT;
pServerContext* ps = nullptr;
pClientContext* pc = nullptr;
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;
}

View File

@@ -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 */

View File

@@ -0,0 +1,745 @@
/**
* 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/version.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;
};
WINPR_ATTR_NODISCARD
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_TYPE_STATIC_INTERCEPT_LIST:
return "FILTER_TYPE_STATIC_INTERCEPT_LIST";
case FILTER_TYPE_DYN_INTERCEPT_LIST:
return "FILTER_TYPE_DYN_INTERCEPT_LIST";
case FILTER_TYPE_INTERCEPT_CHANNEL:
return "FILTER_TYPE_INTERCEPT_CHANNEL";
case FILTER_LAST:
return "FILTER_LAST";
default:
return "FILTER_UNKNOWN";
}
}
WINPR_ATTR_NODISCARD
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";
}
}
WINPR_ATTR_NODISCARD
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);
}
WINPR_ATTR_NODISCARD
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.
*/
WINPR_ATTR_NODISCARD
static BOOL pf_modules_set_plugin_data(WINPR_ATTR_UNUSED 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 == nullptr) /* no need to store anything */
return FALSE;
if (!HashTable_Insert(pdata->modules_info, ccharconv.cp, data))
{
WLog_ERR(TAG, "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 nullptr will be
* returned.
*/
WINPR_ATTR_NODISCARD
static void* pf_modules_get_plugin_data(WINPR_ATTR_UNUSED 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(WINPR_ATTR_UNUSED proxyPluginsManager* mgr, proxyData* pdata)
{
WINPR_ASSERT(pdata);
WLog_DBG(TAG, "is called!");
proxy_data_abort_connect(pdata);
}
WINPR_ATTR_NODISCARD
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;
}
WINPR_ATTR_NODISCARD
static BOOL pf_modules_register_plugin(proxyPluginsManager* mgr,
const proxyPlugin* plugin_to_register)
{
proxyPlugin internal = WINPR_C_ARRAY_INIT;
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;
}
WLog_INFO(TAG, "Successfully registered proxy plugin '%s'", plugin_to_register->name);
return TRUE;
}
WINPR_ATTR_NODISCARD
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;
}
WINPR_ATTR_NODISCARD
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);
}
WINPR_ATTR_NODISCARD
static BOOL pf_modules_load_static_module(const char* module_name, proxyModule* module,
void* userdata)
{
WINPR_ASSERT(module);
HANDLE handle = GetModuleHandleA(nullptr);
if (handle == nullptr)
{
WLog_DBG(TAG, "failed loading static library: %s", module_name);
return FALSE;
}
char name[256] = WINPR_C_ARRAY_INIT;
(void)_snprintf(name, sizeof(name), "%s_%s", module_name, MODULE_ENTRY_POINT);
for (size_t x = 0; x < strnlen(name, sizeof(name)); x++)
{
if (name[x] == '-')
name[x] = '_';
}
proxyModuleEntryPoint pEntryPoint = GetProcAddressAs(handle, name, proxyModuleEntryPoint);
if (!pEntryPoint)
{
WLog_DBG(TAG, "GetProcAddress failed for static %s (module %s)", name, module_name);
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;
}
WINPR_ATTR_NODISCARD
static BOOL pf_modules_load_dynamic_module(const char* module_path, proxyModule* module,
void* userdata)
{
if (!winpr_PathFileExists(module_path))
{
WLog_DBG(TAG, "failed loading external library: file '%s' does not exist", module_path);
return FALSE;
}
HANDLE handle = LoadLibraryX(module_path);
if (handle == nullptr)
{
WLog_DBG(TAG, "failed loading external library: %s", module_path);
return FALSE;
}
proxyModuleEntryPoint pEntryPoint =
GetProcAddressAs(handle, MODULE_ENTRY_POINT, proxyModuleEntryPoint);
if (!pEntryPoint)
{
WLog_DBG(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;
}
WINPR_ATTR_NODISCARD
static BOOL pf_modules_try_load_dynamic_module(const char* module_path, proxyModule* module,
void* userdata, size_t count, const char* names[])
{
for (size_t x = 0; x < count; x++)
{
const char* name = names[x];
char* fullpath = GetCombinedPath(module_path, name);
if (!fullpath)
continue;
const BOOL rc = pf_modules_load_dynamic_module(fullpath, module, userdata);
free(fullpath);
if (rc)
return TRUE;
}
return FALSE;
}
WINPR_ATTR_NODISCARD
static BOOL pf_modules_load_module(const char* module_path, const char* module_name,
proxyModule* module, void* userdata)
{
WINPR_ASSERT(module);
if (pf_modules_load_static_module(module_name, module, userdata))
return TRUE;
char names[5][MAX_PATH] = WINPR_C_ARRAY_INIT;
(void)_snprintf(names[0], sizeof(names[0]), "proxy-%s-plugin%s", module_name,
FREERDP_SHARED_LIBRARY_SUFFIX);
(void)_snprintf(names[1], sizeof(names[1]), "%sproxy-%s-plugin%s",
FREERDP_SHARED_LIBRARY_PREFIX, module_name, FREERDP_SHARED_LIBRARY_SUFFIX);
(void)_snprintf(names[2], sizeof(names[2]), "%sproxy-%s-plugin%s.%d",
FREERDP_SHARED_LIBRARY_PREFIX, module_name, FREERDP_SHARED_LIBRARY_SUFFIX,
FREERDP_VERSION_MAJOR);
(void)_snprintf(names[3], sizeof(names[3]), "%sproxy-%s-plugin%d%s",
FREERDP_SHARED_LIBRARY_PREFIX, module_name, FREERDP_VERSION_MAJOR,
FREERDP_SHARED_LIBRARY_SUFFIX);
(void)_snprintf(names[4], sizeof(names[4]), "%sproxy-%s-plugin%d%s.%d",
FREERDP_SHARED_LIBRARY_PREFIX, module_name, FREERDP_VERSION_MAJOR,
FREERDP_SHARED_LIBRARY_SUFFIX, FREERDP_VERSION_MAJOR);
const char* cnames[5] = { names[0], names[1], names[2], names[3], names[4] };
return pf_modules_try_load_dynamic_module(module_path, module, userdata, ARRAYSIZE(cnames),
cnames);
}
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);
}
WINPR_ATTR_MALLOC(free_plugin, 1)
WINPR_ATTR_NODISCARD
static void* new_plugin(const void* obj)
{
const proxyPlugin* src = obj;
proxyPlugin* proxy = calloc(1, sizeof(proxyPlugin));
if (!proxy)
return nullptr;
*proxy = *src;
return proxy;
}
proxyModule* pf_modules_new(const char* root_dir, const char** modules, size_t count)
{
wObject* obj = nullptr;
char* path = nullptr;
proxyModule* module = calloc(1, sizeof(proxyModule));
if (!module)
return nullptr;
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 == nullptr)
{
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 == nullptr)
{
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, nullptr))
{
WLog_ERR(TAG, "error occurred while creating modules directory: %s", root_dir);
}
}
if (winpr_PathFileExists(path))
WLog_DBG(TAG, "modules root directory: %s", path);
for (size_t i = 0; i < count; i++)
{
const char* module_name = modules[i];
if (!pf_modules_load_module(path, module_name, module, nullptr))
WLog_WARN(TAG, "Failed to load proxy module '%s'", module_name);
else
WLog_INFO(TAG, "Successfully loaded proxy module '%s'", module_name);
}
}
free(path);
return module;
error:
free(path);
pf_modules_free(module);
return nullptr;
}
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);
}

File diff suppressed because it is too large Load Diff

View File

@@ -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 */

View File

@@ -0,0 +1,656 @@
/**
* 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")
WINPR_ATTR_NODISCARD
static BOOL pf_server_refresh_rect(rdpContext* context, BYTE count, const RECTANGLE_16* areas)
{
pServerContext* ps = (pServerContext*)context;
rdpContext* pc = nullptr;
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);
}
WINPR_ATTR_NODISCARD
static BOOL pf_server_suppress_output(rdpContext* context, BYTE allow, const RECTANGLE_16* area)
{
pServerContext* ps = (pServerContext*)context;
rdpContext* pc = nullptr;
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.
*/
WINPR_ATTR_NODISCARD
static BOOL pf_client_begin_paint(rdpContext* context)
{
pClientContext* pc = (pClientContext*)context;
proxyData* pdata = nullptr;
rdpContext* ps = nullptr;
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
*/
WINPR_ATTR_NODISCARD
static BOOL pf_client_end_paint(rdpContext* context)
{
pClientContext* pc = (pClientContext*)context;
proxyData* pdata = nullptr;
rdpContext* ps = nullptr;
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;
}
WINPR_ATTR_NODISCARD
static BOOL pf_client_bitmap_update(rdpContext* context, const BITMAP_UPDATE* bitmap)
{
pClientContext* pc = (pClientContext*)context;
proxyData* pdata = nullptr;
rdpContext* ps = nullptr;
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);
}
WINPR_ATTR_NODISCARD
static BOOL pf_client_desktop_resize(rdpContext* context)
{
pClientContext* pc = (pClientContext*)context;
proxyData* pdata = nullptr;
rdpContext* ps = nullptr;
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);
}
WINPR_ATTR_NODISCARD
static BOOL pf_client_remote_monitors(rdpContext* context, UINT32 count,
const MONITOR_DEF* monitors)
{
pClientContext* pc = (pClientContext*)context;
proxyData* pdata = nullptr;
rdpContext* ps = nullptr;
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);
}
WINPR_ATTR_NODISCARD
static BOOL pf_client_send_pointer_system(rdpContext* context,
const POINTER_SYSTEM_UPDATE* pointer_system)
{
pClientContext* pc = (pClientContext*)context;
proxyData* pdata = nullptr;
rdpContext* ps = nullptr;
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);
}
WINPR_ATTR_NODISCARD
static BOOL pf_client_send_pointer_position(rdpContext* context,
const POINTER_POSITION_UPDATE* pointerPosition)
{
pClientContext* pc = (pClientContext*)context;
proxyData* pdata = nullptr;
rdpContext* ps = nullptr;
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);
}
WINPR_ATTR_NODISCARD
static BOOL pf_client_send_pointer_color(rdpContext* context,
const POINTER_COLOR_UPDATE* pointer_color)
{
pClientContext* pc = (pClientContext*)context;
proxyData* pdata = nullptr;
rdpContext* ps = nullptr;
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);
}
WINPR_ATTR_NODISCARD
static BOOL pf_client_send_pointer_large(rdpContext* context,
const POINTER_LARGE_UPDATE* pointer_large)
{
pClientContext* pc = (pClientContext*)context;
proxyData* pdata = nullptr;
rdpContext* ps = nullptr;
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);
}
WINPR_ATTR_NODISCARD
static BOOL pf_client_send_pointer_new(rdpContext* context, const POINTER_NEW_UPDATE* pointer_new)
{
pClientContext* pc = (pClientContext*)context;
proxyData* pdata = nullptr;
rdpContext* ps = nullptr;
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);
}
WINPR_ATTR_NODISCARD
static BOOL pf_client_send_pointer_cached(rdpContext* context,
const POINTER_CACHED_UPDATE* pointer_cached)
{
pClientContext* pc = (pClientContext*)context;
proxyData* pdata = nullptr;
rdpContext* ps = nullptr;
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);
}
WINPR_ATTR_NODISCARD
static BOOL pf_client_save_session_info(rdpContext* context, UINT32 type, void* data)
{
logon_info* logonInfo = nullptr;
pClientContext* pc = (pClientContext*)context;
proxyData* pdata = nullptr;
rdpContext* ps = nullptr;
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);
}
WINPR_ATTR_NODISCARD
static BOOL pf_client_server_status_info(rdpContext* context, UINT32 status)
{
pClientContext* pc = (pClientContext*)context;
proxyData* pdata = nullptr;
rdpContext* ps = nullptr;
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);
}
WINPR_ATTR_NODISCARD
static BOOL pf_client_set_keyboard_indicators(rdpContext* context, UINT16 led_flags)
{
pClientContext* pc = (pClientContext*)context;
proxyData* pdata = nullptr;
rdpContext* ps = nullptr;
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);
}
WINPR_ATTR_NODISCARD
static BOOL pf_client_set_keyboard_ime_status(rdpContext* context, UINT16 imeId, UINT32 imeState,
UINT32 imeConvMode)
{
pClientContext* pc = (pClientContext*)context;
proxyData* pdata = nullptr;
rdpContext* ps = nullptr;
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);
}
WINPR_ATTR_NODISCARD
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 = nullptr;
rdpContext* ps = nullptr;
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;
}
WINPR_ATTR_NODISCARD
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 = nullptr;
rdpContext* ps = nullptr;
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;
}
WINPR_ATTR_NODISCARD
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 = nullptr;
rdpContext* ps = nullptr;
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;
}
WINPR_ATTR_NODISCARD
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 = nullptr;
rdpContext* ps = nullptr;
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;
}
WINPR_ATTR_NODISCARD
static BOOL pf_client_window_delete(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo)
{
BOOL rc = 0;
pClientContext* pc = (pClientContext*)context;
proxyData* pdata = nullptr;
rdpContext* ps = nullptr;
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;
}
WINPR_ATTR_NODISCARD
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 = nullptr;
rdpContext* ps = nullptr;
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;
}
WINPR_ATTR_NODISCARD
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 = nullptr;
rdpContext* ps = nullptr;
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;
}
WINPR_ATTR_NODISCARD
static BOOL pf_client_notify_icon_delete(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo)
{
BOOL rc = 0;
pClientContext* pc = (pClientContext*)context;
proxyData* pdata = nullptr;
rdpContext* ps = nullptr;
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;
}
WINPR_ATTR_NODISCARD
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 = nullptr;
rdpContext* ps = nullptr;
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;
}
WINPR_ATTR_NODISCARD
static BOOL pf_client_non_monitored_desktop(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo)
{
BOOL rc = 0;
pClientContext* pc = (pClientContext*)context;
proxyData* pdata = nullptr;
rdpContext* ps = nullptr;
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;
}

View File

@@ -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 */

View File

@@ -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(WINPR_ATTR_UNUSED 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";
}
}

View File

@@ -0,0 +1,44 @@
/**
* 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 nullptr.
* @param name The name of the channel. Must NOT be nullptr.
* @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.
*/
WINPR_ATTR_NODISCARD pf_utils_channel_mode pf_utils_get_channel_mode(const proxyConfig* config,
const char* name);
WINPR_ATTR_NODISCARD const char* pf_utils_channel_mode_string(pf_utils_channel_mode mode);
WINPR_ATTR_NODISCARD BOOL pf_utils_is_passthrough(const proxyConfig* config);
#endif /* FREERDP_SERVER_PROXY_PFUTILS_H */

View File

@@ -0,0 +1,106 @@
/**
* 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
void pf_modules_free(proxyModule* module);
WINPR_ATTR_MALLOC(pf_modules_free, 1)
WINPR_ATTR_NODISCARD
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 nullptr
* @return TRUE for success, FALSE otherwise
*/
WINPR_ATTR_NODISCARD BOOL pf_modules_add(proxyModule* module, proxyModuleEntryPoint ep,
void* userdata);
WINPR_ATTR_NODISCARD BOOL pf_modules_is_plugin_loaded(proxyModule* module,
const char* plugin_name);
void pf_modules_list_loaded_plugins(proxyModule* module);
WINPR_ATTR_NODISCARD BOOL pf_modules_run_filter(proxyModule* module, PF_FILTER_TYPE type,
proxyData* pdata, void* param);
WINPR_ATTR_NODISCARD
BOOL pf_modules_run_hook(proxyModule* module, PF_HOOK_TYPE type, proxyData* pdata,
void* custom);
#ifdef __cplusplus
}
#endif
#endif /* FREERDP_SERVER_PROXY_MODULES_H */