diff --git a/include/freerdp/server/proxy/proxy_config.h b/include/freerdp/server/proxy/proxy_config.h index 61cc26139..aff883411 100644 --- a/include/freerdp/server/proxy/proxy_config.h +++ b/include/freerdp/server/proxy/proxy_config.h @@ -69,6 +69,8 @@ struct proxy_config BOOL PassthroughIsBlacklist; char** Passthrough; size_t PassthroughCount; + char** Intercept; + size_t InterceptCount; /* clipboard specific settings */ BOOL TextOnly; diff --git a/include/freerdp/server/proxy/proxy_context.h b/include/freerdp/server/proxy/proxy_context.h index dc873379c..bec1db61f 100644 --- a/include/freerdp/server/proxy/proxy_context.h +++ b/include/freerdp/server/proxy/proxy_context.h @@ -33,6 +33,23 @@ typedef struct proxy_data proxyData; typedef struct proxy_module proxyModule; typedef struct channel_data_event_info proxyChannelDataEventInfo; +typedef struct _InterceptContextMapEntry +{ + void (*free)(struct _InterceptContextMapEntry*); +} InterceptContextMapEntry; + +/* All proxy interception channels derive from this base struct + * and set their cleanup function accordingly. */ +static INLINE void intercept_context_entry_free(void* obj) +{ + InterceptContextMapEntry* entry = obj; + if (!entry) + return; + if (!entry->free) + return; + entry->free(entry); +} + /** * Wraps rdpContext and holds the state for the proxy's server. */ @@ -44,6 +61,8 @@ struct p_server_context HANDLE vcm; HANDLE dynvcReady; + + wHashTable* interceptContextMap; }; typedef struct p_server_context pServerContext; @@ -83,6 +102,16 @@ struct p_client_context BOOL input_state_sync_pending; UINT32 input_state; + + wHashTable* interceptContextMap; + UINT32 computerNameLen; + BOOL computerNameUnicode; + union + { + WCHAR* wc; + char* c; + void* v; + } computerName; }; /** diff --git a/include/freerdp/server/proxy/proxy_modules_api.h b/include/freerdp/server/proxy/proxy_modules_api.h index 87cc920b8..91598c5ce 100644 --- a/include/freerdp/server/proxy/proxy_modules_api.h +++ b/include/freerdp/server/proxy/proxy_modules_api.h @@ -68,8 +68,8 @@ struct proxy_plugin proxyHookFn ClientX509Certificate; /* 71 custom=rdpContext* */ proxyHookFn ClientLoginFailure; /* 72 custom=rdpContext* */ proxyHookFn ClientEndPaint; /* 73 custom=rdpContext* */ - - UINT64 reserved3[96 - 74]; /* 74-95 */ + proxyHookFn ClientRedirect; /* 74 custom=rdpContext* */ + UINT64 reserved3[96 - 75]; /* 75-95 */ proxyHookFn ServerPostConnect; /* 96 custom=freerdp_peer* */ proxyHookFn ServerPeerActivate; /* 97 custom=freerdp_peer* */ diff --git a/include/freerdp/utils/smartcard_call.h b/include/freerdp/utils/smartcard_call.h index 2467a14f3..7708492bf 100644 --- a/include/freerdp/utils/smartcard_call.h +++ b/include/freerdp/utils/smartcard_call.h @@ -35,7 +35,7 @@ typedef struct _scard_call_context scard_call_context; FREERDP_API scard_call_context* smartcard_call_context_new(const rdpSettings* settings); FREERDP_API void smartcard_call_context_free(scard_call_context* ctx); -FREERDP_API BOOL smartcard_call_context_signal_stop(scard_call_context* ctx); +FREERDP_API BOOL smartcard_call_context_signal_stop(scard_call_context* ctx, BOOL reset); FREERDP_API BOOL smartcard_call_context_add(scard_call_context* ctx, const char* name); FREERDP_API BOOL smartcard_call_cancel_context(scard_call_context* ctx, SCARDCONTEXT context); FREERDP_API BOOL smartcard_call_cancel_all_context(scard_call_context* ctx); diff --git a/libfreerdp/utils/smartcard_call.c b/libfreerdp/utils/smartcard_call.c index 97f50f2bc..789083ab8 100644 --- a/libfreerdp/utils/smartcard_call.c +++ b/libfreerdp/utils/smartcard_call.c @@ -1862,7 +1862,7 @@ void smartcard_call_context_free(scard_call_context* ctx) if (!ctx) return; - smartcard_call_context_signal_stop(ctx); + smartcard_call_context_signal_stop(ctx, FALSE); LinkedList_Free(ctx->names); if (ctx->StartedEvent) @@ -1941,10 +1941,14 @@ BOOL smartcard_call_is_configured(scard_call_context* ctx) #endif } -BOOL smartcard_call_context_signal_stop(scard_call_context* ctx) +BOOL smartcard_call_context_signal_stop(scard_call_context* ctx, BOOL reset) { WINPR_ASSERT(ctx); if (!ctx->stopEvent) return TRUE; - return SetEvent(ctx->stopEvent); + + if (reset) + return ResetEvent(ctx->stopEvent); + else + return SetEvent(ctx->stopEvent); } diff --git a/server/proxy/CMakeLists.txt b/server/proxy/CMakeLists.txt index d01fa5420..c7076009d 100644 --- a/server/proxy/CMakeLists.txt +++ b/server/proxy/CMakeLists.txt @@ -41,6 +41,8 @@ set(${MODULE_PREFIX}_SRCS set(PROXY_APP_SRCS freerdp_proxy.c) +add_subdirectory("channels") + # On windows create dll version information. # Vendor, product and year are already set in top level CMakeLists.txt if (WIN32) @@ -58,7 +60,7 @@ if (WIN32) list(APPEND PROXY_APP_SRCS ${CMAKE_CURRENT_BINARY_DIR}/version.rc) endif() -add_library(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS}) +add_library(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS} $) set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME ${MODULE_NAME}${FREERDP_API_VERSION}) if (WITH_LIBRARY_VERSIONING) @@ -113,3 +115,8 @@ option(WITH_PROXY_MODULES "Compile proxy modules" ON) if (WITH_PROXY_MODULES) add_subdirectory("modules") endif() + +option(WITH_PROXY_EMULATE_SMARTCARD "Compile proxy smartcard emulation" OFF) +if (WITH_PROXY_EMULATE_SMARTCARD) + add_definitions("-DWITH_PROXY_EMULATE_SMARTCARD") +endif() diff --git a/server/proxy/channels/CMakeLists.txt b/server/proxy/channels/CMakeLists.txt new file mode 100644 index 000000000..e5df5f702 --- /dev/null +++ b/server/proxy/channels/CMakeLists.txt @@ -0,0 +1,15 @@ + +set(MODULE_NAME pf_channels) +set(SOURCES + pf_channel_rdpdr.c + pf_channel_rdpdr.h +) + +if (WITH_PROXY_EMULATE_SMARTCARD) + list(APPEND SOURCES + pf_channel_smartcard.c + pf_channel_smartcard.h + ) +endif() + +add_library(${MODULE_NAME} OBJECT ${SOURCES}) diff --git a/server/proxy/channels/pf_channel_rdpdr.c b/server/proxy/channels/pf_channel_rdpdr.c new file mode 100644 index 000000000..462c184fa --- /dev/null +++ b/server/proxy/channels/pf_channel_rdpdr.c @@ -0,0 +1,1713 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Proxy Server + * + * Copyright 2021 Armin Novak + * 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 +#include +#include + +#include "pf_channel_rdpdr.h" +#include "pf_channel_smartcard.h" + +#include +#include +#include +#include + +#define TAG PROXY_TAG("channel.rdpdr") + +#define SCARD_DEVICE_ID UINT32_MAX + +typedef struct +{ + InterceptContextMapEntry base; + wStream* s; + wStream* buffer; + UINT16 versionMajor; + UINT16 versionMinor; + UINT32 clientID; + UINT32 computerNameLen; + BOOL computerNameUnicode; + union + { + WCHAR* wc; + char* c; + void* v; + } computerName; + UINT32 SpecialDeviceCount; +} pf_channel_common_context; + +typedef enum +{ + STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST = 0x01, + STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST = 0x02, + STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM = 0x04, + STATE_CLIENT_CHANNEL_RUNNING = 0x10 +} pf_channel_client_state; + +typedef struct _pf_channel_client_context +{ + pf_channel_common_context common; + pf_channel_client_state state; + UINT32 flags; + UINT16 maxMajorVersion; + UINT16 maxMinorVersion; + wQueue* queue; +} pf_channel_client_context; + +typedef enum +{ + STATE_SERVER_INITIAL, + STATE_SERVER_EXPECT_CLIENT_ANNOUNCE_REPLY, + STATE_SERVER_EXPECT_CLIENT_NAME_REQUEST, + STATE_SERVER_EXPECT_EXPECT_CLIENT_CAPABILITY_RESPONE, + STATE_SERVER_CHANNEL_RUNNING +} pf_channel_server_state; + +typedef struct +{ + pf_channel_common_context common; + pf_channel_server_state state; + DWORD SessionId; + HANDLE handle; + wArrayList* blockedDevices; +} pf_channel_server_context; + +static const char* rdpdr_server_state_to_string(pf_channel_server_state state) +{ + switch (state) + { + case STATE_SERVER_INITIAL: + return "STATE_SERVER_INITIAL"; + case STATE_SERVER_EXPECT_CLIENT_ANNOUNCE_REPLY: + return "STATE_SERVER_EXPECT_CLIENT_ANNOUNCE_REPLY"; + case STATE_SERVER_EXPECT_CLIENT_NAME_REQUEST: + return "STATE_SERVER_EXPECT_CLIENT_NAME_REQUEST"; + case STATE_SERVER_EXPECT_EXPECT_CLIENT_CAPABILITY_RESPONE: + return "STATE_SERVER_EXPECT_EXPECT_CLIENT_CAPABILITY_RESPONE"; + case STATE_SERVER_CHANNEL_RUNNING: + return "STATE_SERVER_CHANNEL_RUNNING"; + default: + return "STATE_SERVER_UNKNOWN"; + } +} + +static const char* rdpdr_client_state_to_string(pf_channel_client_state state) +{ + switch (state) + { + case STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST: + return "STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST"; + case STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST: + return "STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST"; + case STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM: + return "STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM"; + case STATE_CLIENT_CHANNEL_RUNNING: + return "STATE_CLIENT_CHANNEL_RUNNING"; + default: + return "STATE_CLIENT_UNKNOWN"; + } +} + +static wStream* rdpdr_get_send_buffer(pf_channel_common_context* rdpdr, UINT16 component, + UINT16 PacketID, size_t capacity) +{ + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(rdpdr->s); + if (!Stream_SetPosition(rdpdr->s, 0)) + return NULL; + if (!Stream_EnsureCapacity(rdpdr->s, capacity + 4)) + return NULL; + Stream_Write_UINT16(rdpdr->s, component); + Stream_Write_UINT16(rdpdr->s, PacketID); + return rdpdr->s; +} + +static wStream* rdpdr_client_get_send_buffer(pf_channel_client_context* rdpdr, UINT16 component, + UINT16 PacketID, size_t capacity) +{ + WINPR_ASSERT(rdpdr); + return rdpdr_get_send_buffer(&rdpdr->common, component, PacketID, capacity); +} + +static wStream* rdpdr_server_get_send_buffer(pf_channel_server_context* rdpdr, UINT16 component, + UINT16 PacketID, size_t capacity) +{ + WINPR_ASSERT(rdpdr); + return rdpdr_get_send_buffer(&rdpdr->common, component, PacketID, capacity); +} + +static UINT rdpdr_client_send(pClientContext* pc, wStream* s) +{ + UINT16 channelId; + + WINPR_ASSERT(pc); + WINPR_ASSERT(s); + WINPR_ASSERT(pc->context.instance); + + if (!pc->connected) + { + WLog_WARN(TAG, "Ignoring channel %s message, not connected!", RDPDR_SVC_CHANNEL_NAME); + return CHANNEL_RC_OK; + } + + channelId = freerdp_channels_get_id_by_name(pc->context.instance, RDPDR_SVC_CHANNEL_NAME); + /* Ignore unmappable channels. Might happen when the channel was already down and + * some delayed message is tried to be sent. */ + if ((channelId == 0) || (channelId == UINT16_MAX)) + return ERROR_INTERNAL_ERROR; + + Stream_SealLength(s); + rdpdr_dump_send_packet(s, "proxy-client"); + WINPR_ASSERT(pc->context.instance->SendChannelData); + if (!pc->context.instance->SendChannelData(pc->context.instance, channelId, Stream_Buffer(s), + Stream_Length(s))) + return ERROR_EVT_CHANNEL_NOT_FOUND; + return CHANNEL_RC_OK; +} + +static UINT rdpdr_seal_send_free_request(pf_channel_server_context* context, wStream* s) +{ + BOOL status; + size_t len; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->handle); + WINPR_ASSERT(s); + + Stream_SealLength(s); + len = Stream_Length(s); + WINPR_ASSERT(len <= ULONG_MAX); + + status = WTSVirtualChannelWrite(context->handle, (char*)Stream_Buffer(s), (ULONG)len, NULL); + return (status) ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +} + +static BOOL rdpdr_process_server_header(wStream* s, UINT16 component, UINT16 PacketId, + size_t expect) +{ + UINT16 rpacketid, rcomponent; + + WINPR_ASSERT(s); + if (Stream_GetRemainingLength(s) < 4) + { + WLog_WARN(TAG, "RDPDR_HEADER[%s | %s]: expected length 4, got %" PRIuz, + rdpdr_component_string(component), rdpdr_packetid_string(PacketId), + Stream_GetRemainingLength(s)); + return FALSE; + } + + Stream_Read_UINT16(s, rcomponent); + Stream_Read_UINT16(s, rpacketid); + + if (rcomponent != component) + { + WLog_WARN(TAG, "RDPDR_HEADER[%s | %s]: got component %s", rdpdr_component_string(component), + rdpdr_packetid_string(PacketId), rdpdr_component_string(rcomponent)); + return FALSE; + } + + if (rpacketid != PacketId) + { + WLog_WARN(TAG, "RDPDR_HEADER[%s | %s]: got PacketID %s", rdpdr_component_string(component), + rdpdr_packetid_string(PacketId), rdpdr_packetid_string(rpacketid)); + return FALSE; + } + + if (Stream_GetRemainingLength(s) < expect) + { + WLog_WARN(TAG, + "RDPDR_HEADER[%s | %s] not enought data, expected %" PRIuz ", " + "got %" PRIuz, + rdpdr_component_string(component), rdpdr_packetid_string(PacketId), expect, + Stream_GetRemainingLength(s)); + return ERROR_INVALID_DATA; + } + + WLog_DBG(TAG, "RDPDR_HEADER[%s | %s] -> got %" PRIuz " bytes", + rdpdr_component_string(component), rdpdr_packetid_string(PacketId), + Stream_GetRemainingLength(s)); + return TRUE; +} + +static BOOL rdpdr_check_version(UINT16 versionMajor, UINT16 versionMinor, UINT16 component, + UINT16 PacketId) +{ + if (versionMajor != RDPDR_VERSION_MAJOR) + { + WLog_WARN(TAG, "[%s | %s] expected MajorVersion %" PRIu16 ", got %" PRIu16, + rdpdr_component_string(component), rdpdr_packetid_string(PacketId), + RDPDR_VERSION_MAJOR, versionMajor); + return FALSE; + } + switch (versionMinor) + { + case RDPDR_VERSION_MINOR_RDP50: + case RDPDR_VERSION_MINOR_RDP51: + case RDPDR_VERSION_MINOR_RDP52: + case RDPDR_VERSION_MINOR_RDP6X: + case RDPDR_VERSION_MINOR_RDP10X: + break; + default: + { + WLog_WARN(TAG, "[%s | %s] unsupported MinorVersion %" PRIu16, + rdpdr_component_string(component), rdpdr_packetid_string(PacketId), + versionMinor); + return FALSE; + } + } + return TRUE; +} + +static UINT rdpdr_process_server_announce_request(pf_channel_client_context* rdpdr, wStream* s) +{ + const UINT16 component = RDPDR_CTYP_CORE; + const UINT16 packetid = PAKID_CORE_SERVER_ANNOUNCE; + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + if (!rdpdr_process_server_header(s, component, packetid, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, rdpdr->common.versionMajor); + Stream_Read_UINT16(s, rdpdr->common.versionMinor); + + if (!rdpdr_check_version(rdpdr->common.versionMajor, rdpdr->common.versionMinor, component, + packetid)) + return ERROR_INVALID_DATA; + + /* Limit maximum channel protocol version to the one set by proxy server */ + if (rdpdr->common.versionMajor > rdpdr->maxMajorVersion) + { + rdpdr->common.versionMajor = rdpdr->maxMajorVersion; + rdpdr->common.versionMinor = rdpdr->maxMinorVersion; + } + else if (rdpdr->common.versionMinor > rdpdr->maxMinorVersion) + rdpdr->common.versionMinor = rdpdr->maxMinorVersion; + + Stream_Read_UINT32(s, rdpdr->common.clientID); + WLog_DBG(TAG, "[receive] server->client clientID=0x%08" PRIx32, rdpdr->common.clientID); + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_send_announce_request(pf_channel_server_context* context) +{ + wStream* s = + rdpdr_server_get_send_buffer(context, RDPDR_CTYP_CORE, PAKID_CORE_SERVER_ANNOUNCE, 8); + if (!s) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write_UINT16(s, context->common.versionMajor); /* VersionMajor (2 bytes) */ + Stream_Write_UINT16(s, context->common.versionMinor); /* VersionMinor (2 bytes) */ + Stream_Write_UINT32(s, context->common.clientID); /* ClientId (4 bytes) */ + WLog_DBG(TAG, "[send] server->client clientID=0x%08" PRIx32, context->common.clientID); + return rdpdr_seal_send_free_request(context, s); +} + +static UINT rdpdr_process_client_announce_reply(pf_channel_server_context* rdpdr, wStream* s) +{ + const UINT16 component = RDPDR_CTYP_CORE; + const UINT16 packetid = PAKID_CORE_CLIENTID_CONFIRM; + UINT16 versionMajor, versionMinor; + UINT32 clientID; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + if (!rdpdr_process_server_header(s, component, packetid, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, versionMajor); + Stream_Read_UINT16(s, versionMinor); + + if (!rdpdr_check_version(versionMajor, versionMinor, component, packetid)) + return ERROR_INVALID_DATA; + + if ((rdpdr->common.versionMajor != versionMajor) || + (rdpdr->common.versionMinor != versionMinor)) + { + WLog_WARN( + TAG, + "[%s | %s] downgrading version from %" PRIu16 ".%" PRIu16 " to %" PRIu16 ".%" PRIu16, + rdpdr_component_string(component), rdpdr_packetid_string(packetid), + rdpdr->common.versionMajor, rdpdr->common.versionMinor, versionMajor, versionMinor); + rdpdr->common.versionMajor = versionMajor; + rdpdr->common.versionMinor = versionMinor; + } + Stream_Read_UINT32(s, clientID); + WLog_DBG(TAG, "[receive] client->server clientID=0x%08" PRIx32, clientID); + if (rdpdr->common.clientID != clientID) + { + WLog_WARN(TAG, "[%s | %s] changing clientID 0x%08" PRIu32 " to 0x%08" PRIu32, + rdpdr_component_string(component), rdpdr_packetid_string(packetid), + rdpdr->common.clientID, clientID); + rdpdr->common.clientID = clientID; + } + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_send_client_announce_reply(pClientContext* pc, pf_channel_client_context* rdpdr) +{ + wStream* s = + rdpdr_client_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_CLIENTID_CONFIRM, 8); + if (!s) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write_UINT16(s, rdpdr->common.versionMajor); + Stream_Write_UINT16(s, rdpdr->common.versionMinor); + Stream_Write_UINT32(s, rdpdr->common.clientID); + WLog_DBG(TAG, "[send] client->server clientID=0x%08" PRIx32, rdpdr->common.clientID); + return rdpdr_client_send(pc, s); +} + +static UINT rdpdr_process_client_name_request(pf_channel_server_context* rdpdr, wStream* s, + pClientContext* pc) +{ + UINT32 unicodeFlag; + UINT32 codePage; + void* tmp; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + WINPR_ASSERT(pc); + + if (!rdpdr_process_server_header(s, RDPDR_CTYP_CORE, PAKID_CORE_CLIENT_NAME, 12)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, unicodeFlag); + switch (unicodeFlag) + { + case 1: + rdpdr->common.computerNameUnicode = TRUE; + break; + case 0: + rdpdr->common.computerNameUnicode = FALSE; + break; + default: + WLog_WARN(TAG, "[%s | %s]: Invalid unicodeFlag value 0x%08" PRIx32, + rdpdr_component_string(RDPDR_CTYP_CORE), + rdpdr_packetid_string(PAKID_CORE_CLIENT_NAME), unicodeFlag); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, codePage); + WINPR_UNUSED(codePage); /* Field is ignored */ + Stream_Read_UINT32(s, rdpdr->common.computerNameLen); + if (Stream_GetRemainingLength(s) < rdpdr->common.computerNameLen) + { + WLog_WARN(TAG, "[%s | %s]: missing data, got " PRIu32 ", expected %" PRIu32, + rdpdr_component_string(RDPDR_CTYP_CORE), + rdpdr_packetid_string(PAKID_CORE_CLIENT_NAME), Stream_GetRemainingLength(s), + rdpdr->common.computerNameLen); + return ERROR_INVALID_DATA; + } + tmp = realloc(rdpdr->common.computerName.v, rdpdr->common.computerNameLen); + if (!tmp) + return CHANNEL_RC_NO_MEMORY; + rdpdr->common.computerName.v = tmp; + + Stream_Read(s, rdpdr->common.computerName.v, rdpdr->common.computerNameLen); + + pc->computerNameLen = rdpdr->common.computerNameLen; + pc->computerNameUnicode = rdpdr->common.computerNameUnicode; + tmp = realloc(pc->computerName.v, pc->computerNameLen); + if (!tmp) + return CHANNEL_RC_NO_MEMORY; + pc->computerName.v = tmp; + memcpy(pc->computerName.v, rdpdr->common.computerName.v, pc->computerNameLen); + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_send_client_name_request(pClientContext* pc, pf_channel_client_context* rdpdr) +{ + wStream* s; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(pc); + + { + void* tmp = realloc(rdpdr->common.computerName.v, pc->computerNameLen); + if (!tmp) + return CHANNEL_RC_NO_MEMORY; + rdpdr->common.computerName.v = tmp; + rdpdr->common.computerNameLen = pc->computerNameLen; + rdpdr->common.computerNameUnicode = pc->computerNameUnicode; + memcpy(rdpdr->common.computerName.v, pc->computerName.v, pc->computerNameLen); + } + s = rdpdr_client_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_CLIENT_NAME, + 12U + rdpdr->common.computerNameLen); + if (!s) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write_UINT32(s, rdpdr->common.computerNameUnicode + ? 1 + : 0); /* unicodeFlag, 0 for ASCII and 1 for Unicode */ + Stream_Write_UINT32(s, 0); /* codePage, must be set to zero */ + Stream_Write_UINT32(s, rdpdr->common.computerNameLen); + Stream_Write(s, rdpdr->common.computerName.v, rdpdr->common.computerNameLen); + return rdpdr_client_send(pc, s); +} + +#define rdpdr_ignore_capset(s, length) rdpdr_ignore_capset_((s), length, __FUNCTION__) +static UINT rdpdr_ignore_capset_(wStream* s, size_t capabilityLength, const char* fkt) +{ + WINPR_ASSERT(s); + + if (capabilityLength < 4) + { + WLog_ERR(TAG, "[%s] invalid capabilityLength %" PRIu16 " < 4", fkt, capabilityLength); + return ERROR_INVALID_DATA; + } + + if (Stream_GetRemainingLength(s) < capabilityLength - 4U) + { + WLog_ERR(TAG, "[%s] missing capability data, got %" PRIuz ", expected %" PRIu16, fkt, + Stream_GetRemainingLength(s), capabilityLength - 4); + return ERROR_INVALID_DATA; + } + + Stream_Seek(s, capabilityLength - 4U); + return CHANNEL_RC_OK; +} + +static UINT rdpdr_client_process_general_capset(pf_channel_client_context* rdpdr, wStream* s, + size_t length) +{ + WINPR_UNUSED(rdpdr); + return rdpdr_ignore_capset(s, length); +} + +static UINT rdpdr_process_printer_capset(pf_channel_client_context* rdpdr, wStream* s, + size_t length) +{ + WINPR_UNUSED(rdpdr); + return rdpdr_ignore_capset(s, length); +} + +static UINT rdpdr_process_port_capset(pf_channel_client_context* rdpdr, wStream* s, size_t length) +{ + WINPR_UNUSED(rdpdr); + return rdpdr_ignore_capset(s, length); +} + +static UINT rdpdr_process_drive_capset(pf_channel_client_context* rdpdr, wStream* s, size_t length) +{ + WINPR_UNUSED(rdpdr); + return rdpdr_ignore_capset(s, length); +} + +static UINT rdpdr_process_smartcard_capset(pf_channel_client_context* rdpdr, wStream* s, + size_t length) +{ + WINPR_UNUSED(rdpdr); + return rdpdr_ignore_capset(s, length); +} + +static UINT rdpdr_process_server_core_capability_request(pf_channel_client_context* rdpdr, + wStream* s) +{ + UINT status = CHANNEL_RC_OK; + UINT16 i; + UINT16 numCapabilities; + + WINPR_ASSERT(rdpdr); + + if (!rdpdr_process_server_header(s, RDPDR_CTYP_CORE, PAKID_CORE_SERVER_CAPABILITY, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, numCapabilities); + Stream_Seek(s, 2); /* pad (2 bytes) */ + + WLog_DBG(TAG, "[server] got %" PRIu16 " capabilities:", numCapabilities); + for (i = 0; i < numCapabilities; i++) + { + UINT16 capabilityType; + UINT16 capabilityLength; + + if (Stream_GetRemainingLength(s) < 2 * sizeof(UINT16)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, capabilityType); + Stream_Read_UINT16(s, capabilityLength); + + WLog_DBG(TAG, "[server] [%" PRIu16 "] got capability %s [%" PRIu16 "]", i, + rdpdr_cap_type_string(capabilityType), capabilityLength); + switch (capabilityType) + { + case CAP_GENERAL_TYPE: + status = rdpdr_client_process_general_capset(rdpdr, s, capabilityLength); + break; + + case CAP_PRINTER_TYPE: + status = rdpdr_process_printer_capset(rdpdr, s, capabilityLength); + break; + + case CAP_PORT_TYPE: + status = rdpdr_process_port_capset(rdpdr, s, capabilityLength); + break; + + case CAP_DRIVE_TYPE: + status = rdpdr_process_drive_capset(rdpdr, s, capabilityLength); + break; + + case CAP_SMARTCARD_TYPE: + status = rdpdr_process_smartcard_capset(rdpdr, s, capabilityLength); + break; + + default: + break; + } + + if (status != CHANNEL_RC_OK) + return status; + } + + return CHANNEL_RC_OK; +} + +static BOOL rdpdr_write_capset_header(wStream* s, UINT16 capabilityType, UINT16 capabilityLength, + UINT32 version) +{ + WINPR_ASSERT(s); + if (!Stream_EnsureRemainingCapacity(s, capabilityLength + 8)) + return FALSE; + Stream_Write_UINT16(s, capabilityType); + Stream_Write_UINT16(s, capabilityLength + 8); + Stream_Write_UINT32(s, version); + return TRUE; +} + +static BOOL rdpdr_write_general_capset(pf_channel_common_context* rdpdr, wStream* s) +{ + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + if (!rdpdr_write_capset_header(s, CAP_GENERAL_TYPE, 36, GENERAL_CAPABILITY_VERSION_02)) + return FALSE; + Stream_Write_UINT32(s, 0); /* osType, ignored on receipt */ + Stream_Write_UINT32(s, 0); /* osVersion, should be ignored */ + Stream_Write_UINT16(s, rdpdr->versionMajor); /* protocolMajorVersion, must be set to 1 */ + Stream_Write_UINT16(s, rdpdr->versionMinor); /* protocolMinorVersion */ + Stream_Write_UINT32(s, 0x0000FFFF); /* ioCode1 */ + Stream_Write_UINT32(s, 0); /* ioCode2, must be set to zero, reserved for future use */ + Stream_Write_UINT32(s, RDPDR_DEVICE_REMOVE_PDUS | RDPDR_CLIENT_DISPLAY_NAME_PDU | + RDPDR_USER_LOGGEDON_PDU); /* extendedPDU */ + Stream_Write_UINT32(s, ENABLE_ASYNCIO); /* extraFlags1 */ + Stream_Write_UINT32(s, 0); /* extraFlags2, must be set to zero, reserved for future use */ + Stream_Write_UINT32(s, rdpdr->SpecialDeviceCount); /* SpecialTypeDeviceCap, number of special + devices to be redirected before logon */ + return TRUE; +} + +static BOOL rdpdr_write_printer_capset(pf_channel_common_context* rdpdr, wStream* s) +{ + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + if (!rdpdr_write_capset_header(s, CAP_PRINTER_TYPE, 0, GENERAL_CAPABILITY_VERSION_01)) + return FALSE; + return TRUE; +} + +static BOOL rdpdr_write_port_capset(pf_channel_common_context* rdpdr, wStream* s) +{ + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + if (!rdpdr_write_capset_header(s, CAP_PORT_TYPE, 0, GENERAL_CAPABILITY_VERSION_01)) + return FALSE; + return TRUE; +} + +static BOOL rdpdr_write_drive_capset(pf_channel_common_context* rdpdr, wStream* s) +{ + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + if (!rdpdr_write_capset_header(s, CAP_DRIVE_TYPE, 0, DRIVE_CAPABILITY_VERSION_02)) + return FALSE; + return TRUE; +} + +static BOOL rdpdr_write_smartcard_capset(pf_channel_common_context* rdpdr, wStream* s) +{ + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + if (!rdpdr_write_capset_header(s, CAP_SMARTCARD_TYPE, 0, GENERAL_CAPABILITY_VERSION_01)) + return FALSE; + return TRUE; +} + +static UINT rdpdr_send_server_capability_request(pf_channel_server_context* rdpdr) +{ + wStream* s = + rdpdr_server_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_SERVER_CAPABILITY, 8); + if (!s) + return CHANNEL_RC_NO_MEMORY; + Stream_Write_UINT16(s, 5); /* numCapabilities */ + Stream_Write_UINT16(s, 0); /* pad */ + if (!rdpdr_write_general_capset(&rdpdr->common, s)) + return CHANNEL_RC_NO_MEMORY; + if (!rdpdr_write_printer_capset(&rdpdr->common, s)) + return CHANNEL_RC_NO_MEMORY; + if (!rdpdr_write_port_capset(&rdpdr->common, s)) + return CHANNEL_RC_NO_MEMORY; + if (!rdpdr_write_drive_capset(&rdpdr->common, s)) + return CHANNEL_RC_NO_MEMORY; + if (!rdpdr_write_smartcard_capset(&rdpdr->common, s)) + return CHANNEL_RC_NO_MEMORY; + return rdpdr_seal_send_free_request(rdpdr, s); +} + +static UINT rdpdr_process_client_capability_response(pf_channel_server_context* rdpdr, wStream* s) +{ + const UINT16 component = RDPDR_CTYP_CORE; + const UINT16 packetid = PAKID_CORE_CLIENT_CAPABILITY; + UINT status = CHANNEL_RC_OK; + UINT16 numCapabilities, x; + WINPR_ASSERT(rdpdr); + + if (!rdpdr_process_server_header(s, component, packetid, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, numCapabilities); + Stream_Seek_UINT16(s); /* padding */ + WLog_DBG(TAG, "[client] got %" PRIu16 " capabilities:", numCapabilities); + + for (x = 0; x < numCapabilities; x++) + { + UINT16 capabilityType; + UINT16 capabilityLength; + + if (Stream_GetRemainingLength(s) < 2 * sizeof(UINT16)) + { + WLog_WARN(TAG, + "[%s | %s] invalid capability length 0x" PRIu16 ", expected at least %" PRIuz, + rdpdr_component_string(component), rdpdr_packetid_string(packetid), + Stream_GetRemainingLength(s), sizeof(UINT16)); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, capabilityType); + Stream_Read_UINT16(s, capabilityLength); + WLog_DBG(TAG, "[client] [%" PRIu16 "] got capability %s [%" PRIu16 "]", x, + rdpdr_cap_type_string(capabilityType), capabilityLength); + + switch (capabilityType) + { + case CAP_GENERAL_TYPE: + status = rdpdr_ignore_capset(s, capabilityLength); + break; + + case CAP_PRINTER_TYPE: + status = rdpdr_ignore_capset(s, capabilityLength); + break; + + case CAP_PORT_TYPE: + status = rdpdr_ignore_capset(s, capabilityLength); + break; + + case CAP_DRIVE_TYPE: + status = rdpdr_ignore_capset(s, capabilityLength); + break; + + case CAP_SMARTCARD_TYPE: + status = rdpdr_ignore_capset(s, capabilityLength); + break; + + default: + WLog_WARN(TAG, "[%s | %s] invalid capability type 0x%04" PRIx16, + rdpdr_component_string(component), rdpdr_packetid_string(packetid), + capabilityType); + status = ERROR_INVALID_DATA; + break; + } + + if (status != CHANNEL_RC_OK) + break; + } + + return status; +} + +static UINT rdpdr_send_client_capability_response(pClientContext* pc, + pf_channel_client_context* rdpdr) +{ + wStream* s; + + WINPR_ASSERT(rdpdr); + s = rdpdr_client_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_CLIENT_CAPABILITY, 4); + if (!s) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write_UINT16(s, 5); /* numCapabilities */ + Stream_Write_UINT16(s, 0); /* pad */ + if (!rdpdr_write_general_capset(&rdpdr->common, s)) + return CHANNEL_RC_NO_MEMORY; + if (!rdpdr_write_printer_capset(&rdpdr->common, s)) + return CHANNEL_RC_NO_MEMORY; + if (!rdpdr_write_port_capset(&rdpdr->common, s)) + return CHANNEL_RC_NO_MEMORY; + if (!rdpdr_write_drive_capset(&rdpdr->common, s)) + return CHANNEL_RC_NO_MEMORY; + if (!rdpdr_write_smartcard_capset(&rdpdr->common, s)) + return CHANNEL_RC_NO_MEMORY; + return rdpdr_client_send(pc, s); +} + +static UINT rdpdr_send_server_clientid_confirm(pf_channel_server_context* rdpdr) +{ + wStream* s; + + s = rdpdr_server_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_CLIENTID_CONFIRM, 8); + if (!s) + return CHANNEL_RC_NO_MEMORY; + Stream_Write_UINT16(s, rdpdr->common.versionMajor); + Stream_Write_UINT16(s, rdpdr->common.versionMinor); + Stream_Write_UINT32(s, rdpdr->common.clientID); + WLog_DBG(TAG, "[send] server->client clientID=0x%08" PRIx32, rdpdr->common.clientID); + return rdpdr_seal_send_free_request(rdpdr, s); +} + +static UINT rdpdr_process_server_clientid_confirm(pf_channel_client_context* rdpdr, wStream* s) +{ + UINT16 versionMajor; + UINT16 versionMinor; + UINT32 clientID; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + if (!rdpdr_process_server_header(s, RDPDR_CTYP_CORE, PAKID_CORE_CLIENTID_CONFIRM, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, versionMajor); + Stream_Read_UINT16(s, versionMinor); + if (!rdpdr_check_version(versionMajor, versionMinor, RDPDR_CTYP_CORE, + PAKID_CORE_CLIENTID_CONFIRM)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, clientID); + + WLog_DBG(TAG, "[receive] server->client clientID=0x%08" PRIx32, clientID); + if ((versionMajor != rdpdr->common.versionMajor) || + (versionMinor != rdpdr->common.versionMinor)) + { + WLog_WARN(TAG, + "[%s | %s] Version mismatch, sent %" PRIu16 ".%" PRIu16 ", downgraded to %" PRIu16 + ".%" PRIu16, + rdpdr_component_string(RDPDR_CTYP_CORE), + rdpdr_packetid_string(PAKID_CORE_CLIENTID_CONFIRM), rdpdr->common.versionMajor, + rdpdr->common.versionMinor, versionMajor, versionMinor); + rdpdr->common.versionMajor = versionMajor; + rdpdr->common.versionMinor = versionMinor; + } + + if (clientID != rdpdr->common.clientID) + { + WLog_WARN(TAG, "[%s | %s] clientID mismatch, sent 0x%08" PRIx32 ", changed to 0x%08" PRIx32, + rdpdr_component_string(RDPDR_CTYP_CORE), + rdpdr_packetid_string(PAKID_CORE_CLIENTID_CONFIRM), rdpdr->common.clientID, + clientID); + rdpdr->common.clientID = clientID; + } + + return CHANNEL_RC_OK; +} + +static BOOL +rdpdr_process_server_capability_request_or_clientid_confirm(pf_channel_client_context* rdpdr, + wStream* s) +{ + const UINT32 mask = STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM | + STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST; + const UINT16 rcomponent = RDPDR_CTYP_CORE; + UINT16 component, packetid; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + if ((rdpdr->flags & mask) == mask) + { + WLog_WARN(TAG, "[%s]: already past this state, abort!", __FUNCTION__); + return FALSE; + } + + if (Stream_GetRemainingLength(s) < 4) + { + WLog_WARN(TAG, "[%s]: expected length >= 4, got %" PRIuz, __FUNCTION__, + Stream_GetRemainingLength(s)); + return FALSE; + } + + Stream_Read_UINT16(s, component); + if (rcomponent != component) + { + WLog_WARN(TAG, "[%s]: got component %s, expected %s", __FUNCTION__, + rdpdr_component_string(component), rdpdr_component_string(rcomponent)); + return FALSE; + } + Stream_Read_UINT16(s, packetid); + Stream_Rewind(s, 4); + + switch (packetid) + { + case PAKID_CORE_SERVER_CAPABILITY: + if (rdpdr->flags & STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST) + { + WLog_WARN(TAG, "[%s]: got duplicate packetid %s", __FUNCTION__, + rdpdr_packetid_string(packetid)); + return FALSE; + } + rdpdr->flags |= STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST; + return rdpdr_process_server_core_capability_request(rdpdr, s) == CHANNEL_RC_OK; + case PAKID_CORE_CLIENTID_CONFIRM: + default: + if (rdpdr->flags & STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM) + { + WLog_WARN(TAG, "[%s]: got duplicate packetid %s", __FUNCTION__, + rdpdr_packetid_string(packetid)); + return FALSE; + } + rdpdr->flags |= STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM; + return rdpdr_process_server_clientid_confirm(rdpdr, s) == CHANNEL_RC_OK; + } +} + +#if defined(WITH_PROXY_EMULATE_SMARTCARD) +static UINT rdpdr_send_emulated_scard_device_list_announce_request(pClientContext* pc, + pf_channel_client_context* rdpdr) +{ + wStream* s; + + s = rdpdr_client_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_DEVICELIST_ANNOUNCE, 24); + if (!s) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write_UINT32(s, 1); /* deviceCount -> our emulated smartcard only */ + Stream_Write_UINT32(s, RDPDR_DTYP_SMARTCARD); /* deviceType */ + Stream_Write_UINT32( + s, SCARD_DEVICE_ID); /* deviceID -> reserve highest value for the emulated smartcard */ + Stream_Write(s, "SCARD\0\0\0", 8); + Stream_Write_UINT32(s, 6); + Stream_Write(s, "SCARD\0", 6); + + return rdpdr_client_send(pc, s); +} + +static UINT rdpdr_send_emulated_scard_device_remove(pClientContext* pc, + pf_channel_client_context* rdpdr) +{ + wStream* s; + + s = rdpdr_client_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_DEVICELIST_REMOVE, 24); + if (!s) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write_UINT32(s, 1); /* deviceCount -> our emulated smartcard only */ + Stream_Write_UINT32( + s, SCARD_DEVICE_ID); /* deviceID -> reserve highest value for the emulated smartcard */ + + return rdpdr_client_send(pc, s); +} + +static UINT rdpdr_process_server_device_announce_response(pf_channel_client_context* rdpdr, + wStream* s) +{ + const UINT16 component = RDPDR_CTYP_CORE; + const UINT16 packetid = PAKID_CORE_DEVICE_REPLY; + UINT32 deviceID; + UINT32 resultCode; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + if (!rdpdr_process_server_header(s, component, packetid, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, deviceID); + Stream_Read_UINT32(s, resultCode); + + if (deviceID != SCARD_DEVICE_ID) + { + WLog_WARN(TAG, "[%s | %s] deviceID mismatch, sent 0x%08" PRIx32 ", changed to 0x%08" PRIx32, + rdpdr_component_string(component), rdpdr_packetid_string(packetid), + SCARD_DEVICE_ID, deviceID); + } + else if (resultCode != 0) + { + WLog_WARN(TAG, "[%s | %s] deviceID 0x%08" PRIx32 " resultCode=0x%08" PRIx32, + rdpdr_component_string(component), rdpdr_packetid_string(packetid), deviceID, + resultCode); + } + else + WLog_DBG(TAG, + "[%s | %s] deviceID 0x%08" PRIx32 " resultCode=0x%08" PRIx32 + " -> emulated smartcard redirected!", + rdpdr_component_string(component), rdpdr_packetid_string(packetid), deviceID, + resultCode); + + return CHANNEL_RC_OK; +} +#endif + +static BOOL pf_channel_rdpdr_client_send_to_server(pServerContext* ps, wStream* s) +{ + if (ps) + { + UINT16 server_channel_id = WTSChannelGetId(ps->context.peer, RDPDR_SVC_CHANNEL_NAME); + + /* Ignore messages for channels that can not be mapped. + * The client might not have enabled support for this specific channel, + * so just drop the message. */ + if (server_channel_id == 0) + return TRUE; + + WINPR_ASSERT(ps->context.peer); + WINPR_ASSERT(ps->context.peer->SendChannelData); + return ps->context.peer->SendChannelData(ps->context.peer, server_channel_id, + Stream_Buffer(s), Stream_Length(s)); + } + return TRUE; +} + +static BOOL pf_channel_send_client_queue(pClientContext* pc, pf_channel_client_context* rdpdr); + +#if defined(WITH_PROXY_EMULATE_SMARTCARD) +static BOOL rdpdr_process_server_loggedon_request(pServerContext* ps, pClientContext* pc, + pf_channel_client_context* rdpdr, wStream* s, + UINT16 component, UINT16 packetid) +{ + WLog_DBG(TAG, "[%s | %s]", rdpdr_component_string(component), rdpdr_packetid_string(packetid)); + if (rdpdr_send_emulated_scard_device_remove(pc, rdpdr) != CHANNEL_RC_OK) + return FALSE; + if (rdpdr_send_emulated_scard_device_list_announce_request(pc, rdpdr) != CHANNEL_RC_OK) + return FALSE; + return pf_channel_rdpdr_client_send_to_server(ps, s); +} + +static BOOL filter_smartcard_io_requests(pf_channel_client_context* rdpdr, wStream* s, + UINT16* pPacketid) +{ + BOOL rc = FALSE; + UINT16 component, packetid; + UINT32 deviceID = 0; + size_t pos; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(pPacketid); + + if (Stream_GetRemainingLength(s) < 4) + return FALSE; + + pos = Stream_GetPosition(s); + Stream_Read_UINT16(s, component); + Stream_Read_UINT16(s, packetid); + + if (Stream_GetRemainingLength(s) >= 4) + Stream_Read_UINT32(s, deviceID); + + WLog_DBG(TAG, "got: [%s | %s]: [0x%08]" PRIx32, rdpdr_component_string(component), + rdpdr_packetid_string(packetid), deviceID); + + if (component != RDPDR_CTYP_CORE) + goto fail; + + switch (packetid) + { + case PAKID_CORE_SERVER_ANNOUNCE: + case PAKID_CORE_CLIENTID_CONFIRM: + case PAKID_CORE_CLIENT_NAME: + case PAKID_CORE_DEVICELIST_ANNOUNCE: + case PAKID_CORE_DEVICELIST_REMOVE: + case PAKID_CORE_SERVER_CAPABILITY: + case PAKID_CORE_CLIENT_CAPABILITY: + WLog_WARN(TAG, "Filtering client -> server message [%s | %s]", + rdpdr_component_string(component), rdpdr_packetid_string(packetid)); + *pPacketid = packetid; + break; + case PAKID_CORE_USER_LOGGEDON: + *pPacketid = packetid; + break; + case PAKID_CORE_DEVICE_REPLY: + case PAKID_CORE_DEVICE_IOREQUEST: + if (deviceID != SCARD_DEVICE_ID) + goto fail; + *pPacketid = packetid; + break; + default: + if (deviceID != SCARD_DEVICE_ID) + goto fail; + WLog_WARN(TAG, "Got [%s | %s] for deviceID 0x%08" PRIx32 ", TODO: Not handled!", + rdpdr_component_string(component), rdpdr_packetid_string(packetid), deviceID); + goto fail; + } + + rc = TRUE; + +fail: + Stream_SetPosition(s, pos); + return rc; +} +#endif + +BOOL pf_channel_send_client_queue(pClientContext* pc, pf_channel_client_context* rdpdr) +{ + UINT16 channelId; + + WINPR_ASSERT(pc); + WINPR_ASSERT(rdpdr); + + if (rdpdr->state != STATE_CLIENT_CHANNEL_RUNNING) + return FALSE; + channelId = freerdp_channels_get_id_by_name(pc->context.instance, RDPDR_SVC_CHANNEL_NAME); + if ((channelId == 0) || (channelId == UINT16_MAX)) + return TRUE; + + Queue_Lock(rdpdr->queue); + while (Queue_Count(rdpdr->queue) > 0) + { + wStream* s = Queue_Dequeue(rdpdr->queue); + if (!s) + continue; + + Stream_SetPosition(s, Stream_Length(s)); + rdpdr_dump_send_packet(s, "proxy-client-queue"); + WINPR_ASSERT(pc->context.instance->SendChannelData); + if (!pc->context.instance->SendChannelData(pc->context.instance, channelId, + Stream_Buffer(s), Stream_Length(s))) + { + WLog_WARN(TAG, "xxxxxx TODO: Failed to send data!"); + } + Stream_Free(s, TRUE); + } + Queue_Unlock(rdpdr->queue); + return TRUE; +} + +static BOOL rdpdr_handle_server_announce_request(pClientContext* pc, + pf_channel_client_context* rdpdr, wStream* s) +{ + WINPR_ASSERT(pc); + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + if (rdpdr_process_server_announce_request(rdpdr, s) != CHANNEL_RC_OK) + return FALSE; + if (rdpdr_send_client_announce_reply(pc, rdpdr) != CHANNEL_RC_OK) + return FALSE; + if (rdpdr_send_client_name_request(pc, rdpdr) != CHANNEL_RC_OK) + return FALSE; + rdpdr->state = STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST; + return TRUE; +} + +BOOL pf_channel_rdpdr_client_handle(pClientContext* pc, UINT16 channelId, const char* channel_name, + const BYTE* xdata, size_t xsize, UINT32 flags, size_t totalSize) +{ + pf_channel_client_context* rdpdr; + pServerContext* ps; + wStream* s; + UINT16 packetid; + + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->pdata); + WINPR_ASSERT(pc->interceptContextMap); + WINPR_ASSERT(channel_name); + WINPR_ASSERT(xdata); + + ps = pc->pdata->ps; + + rdpdr = HashTable_GetItemValue(pc->interceptContextMap, channel_name); + if (!rdpdr) + { + WLog_ERR(TAG, "[%s]: Channel %s [0x%04" PRIx16 "] missing context in interceptContextMap", + __FUNCTION__, channel_name, channelId); + return FALSE; + } + s = rdpdr->common.buffer; + if (flags & CHANNEL_FLAG_FIRST) + Stream_SetPosition(s, 0); + if (!Stream_EnsureRemainingCapacity(s, xsize)) + { + WLog_ERR(TAG, "[%s]: Channel %s [0x%04" PRIx16 "] not enough memory [need %" PRIuz "]", + __FUNCTION__, channel_name, channelId, xsize); + return FALSE; + } + Stream_Write(s, xdata, xsize); + if ((flags & CHANNEL_FLAG_LAST) == 0) + return TRUE; + + Stream_SealLength(s); + Stream_SetPosition(s, 0); + if (Stream_Length(s) != totalSize) + { + WLog_WARN(TAG, + "Received invalid %s channel data (server -> proxy), expected %" PRIuz + "bytes, got %" PRIuz, + channel_name, totalSize, Stream_Length(s)); + return FALSE; + } + + rdpdr_dump_received_packet(s, "proxy-client"); + switch (rdpdr->state) + { + case STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST: + if (!rdpdr_handle_server_announce_request(pc, rdpdr, s)) + return FALSE; + break; + case STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST: + if (!rdpdr_process_server_capability_request_or_clientid_confirm(rdpdr, s)) + return FALSE; + rdpdr->state = STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM; + break; + case STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM: + if (!rdpdr_process_server_capability_request_or_clientid_confirm(rdpdr, s)) + return FALSE; + if (rdpdr_send_client_capability_response(pc, rdpdr) != CHANNEL_RC_OK) + return FALSE; +#if defined(WITH_PROXY_EMULATE_SMARTCARD) + if (pf_channel_smartcard_client_emulate(pc)) + { + if (rdpdr_send_emulated_scard_device_list_announce_request(pc, rdpdr) != + CHANNEL_RC_OK) + return FALSE; + rdpdr->state = STATE_CLIENT_CHANNEL_RUNNING; + } + else +#endif + { + rdpdr->state = STATE_CLIENT_CHANNEL_RUNNING; + pf_channel_send_client_queue(pc, rdpdr); + } + + break; + case STATE_CLIENT_CHANNEL_RUNNING: +#if defined(WITH_PROXY_EMULATE_SMARTCARD) + if (!pf_channel_smartcard_client_emulate(pc) || + !filter_smartcard_io_requests(rdpdr, s, &packetid)) + return pf_channel_rdpdr_client_send_to_server(ps, s); + else + { + switch (packetid) + { + case PAKID_CORE_USER_LOGGEDON: + return rdpdr_process_server_loggedon_request(ps, pc, rdpdr, s, + RDPDR_CTYP_CORE, packetid); + case PAKID_CORE_DEVICE_IOREQUEST: + { + wStream* out = rdpdr_client_get_send_buffer( + rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_DEVICE_IOCOMPLETION, 0); + WINPR_ASSERT(out); + + if (!rdpdr_process_server_header(s, RDPDR_CTYP_CORE, + PAKID_CORE_DEVICE_IOREQUEST, 20)) + return FALSE; + + if (!pf_channel_smartcard_client_handle(pc, s, out, rdpdr_client_send)) + return FALSE; + } + break; + case PAKID_CORE_SERVER_ANNOUNCE: + pf_channel_rdpdr_client_reset(pc); + if (!rdpdr_handle_server_announce_request(pc, rdpdr, s)) + return FALSE; + break; + case PAKID_CORE_DEVICE_REPLY: + break; + default: + WLog_ERR(TAG, + "[%s]: Channel %s [0x%04" PRIx16 + "] we´ve reached an impossible state %s! [%s] aliens invaded!", + __FUNCTION__, channel_name, channelId, + rdpdr_client_state_to_string(rdpdr->state), + rdpdr_packetid_string(packetid)); + return FALSE; + } + } +#else + return pf_channel_rdpdr_client_send_to_server(ps, s); +#endif + break; + default: + WLog_ERR(TAG, + "[%s]: Channel %s [0x%04" PRIx16 + "] we´ve reached an impossible state %s! [%s] aliens invaded!", + __FUNCTION__, channel_name, channelId, + rdpdr_client_state_to_string(rdpdr->state), rdpdr_packetid_string(packetid)); + return FALSE; + } + + return TRUE; +} + +static void pf_channel_rdpdr_common_context_free(pf_channel_common_context* common) +{ + if (!common) + return; + free(common->computerName.v); + Stream_Free(common->s, TRUE); + Stream_Free(common->buffer, TRUE); +} + +static void pf_channel_rdpdr_client_context_free(InterceptContextMapEntry* base) +{ + pf_channel_client_context* entry = (pf_channel_client_context*)base; + if (!entry) + return; + + pf_channel_rdpdr_common_context_free(&entry->common); + Queue_Free(entry->queue); + free(entry); +} + +static BOOL pf_channel_rdpdr_common_context_new(pf_channel_common_context* common, + void (*fkt)(InterceptContextMapEntry*)) +{ + if (!common) + return FALSE; + common->base.free = fkt; + common->s = Stream_New(NULL, 1024); + if (!common->s) + return FALSE; + common->buffer = Stream_New(NULL, 1024); + if (!common->buffer) + return FALSE; + common->computerNameUnicode = 1; + common->computerName.v = NULL; + common->versionMajor = RDPDR_VERSION_MAJOR; + common->versionMinor = RDPDR_VERSION_MINOR_RDP10X; + common->clientID = SCARD_DEVICE_ID; + return TRUE; +} + +static BOOL pf_channel_rdpdr_client_pass_message(pClientContext* pc, UINT16 channelId, + const char* channel_name, wStream* s) +{ + pf_channel_client_context* rdpdr; + + WINPR_ASSERT(pc); + + rdpdr = HashTable_GetItemValue(pc->interceptContextMap, channel_name); + if (!rdpdr) + return TRUE; /* Ignore data for channels not available on proxy -> server connection */ + WINPR_ASSERT(rdpdr->queue); + + if (!Queue_Enqueue(rdpdr->queue, s)) + return FALSE; + pf_channel_send_client_queue(pc, rdpdr); + return TRUE; +} + +#if defined(WITH_PROXY_EMULATE_SMARTCARD) +static BOOL filter_smartcard_device_list_remove(pf_channel_server_context* rdpdr, wStream* s) +{ + size_t pos; + UINT32 x, count; + + if (Stream_GetRemainingLength(s) < sizeof(UINT32)) + return TRUE; + pos = Stream_GetPosition(s); + Stream_Read_UINT32(s, count); + + if (count == 0) + return TRUE; + + if (Stream_GetRemainingLength(s) < count * sizeof(UINT32)) + return TRUE; + + for (x = 0; x < count; x++) + { + UINT32 deviceID; + BYTE* dst = Stream_Pointer(s); + Stream_Read_UINT32(s, deviceID); + if (deviceID == SCARD_DEVICE_ID) + { + ArrayList_Remove(rdpdr->blockedDevices, (void*)deviceID); + + /* This is the only device, filter it! */ + if (count == 1) + return TRUE; + + /* Remove this device from the list */ + memmove(dst, Stream_Pointer(s), (count - x - 1) * sizeof(UINT32)); + + count--; + Stream_SetPosition(s, pos); + Stream_Write_UINT32(s, count); + return FALSE; + } + } + + return FALSE; +} + +static BOOL filter_smartcard_device_io_request(pf_channel_server_context* rdpdr, wStream* s) +{ + UINT32 DeviceID; + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + Stream_Read_UINT32(s, DeviceID); + return ArrayList_Contains(rdpdr->blockedDevices, (void*)DeviceID); +} + +static BOOL filter_smartcard_device_list_announce(pf_channel_server_context* rdpdr, wStream* s) +{ + size_t pos; + UINT32 x, count; + + if (Stream_GetRemainingLength(s) < sizeof(UINT32)) + return TRUE; + pos = Stream_GetPosition(s); + Stream_Read_UINT32(s, count); + + if (count == 0) + return TRUE; + + for (x = 0; x < count; x++) + { + UINT32 DeviceType; + UINT32 DeviceId; + char PreferredDosName[8]; + UINT32 DeviceDataLength; + BYTE* dst = Stream_Pointer(s); + if (Stream_GetRemainingLength(s) < 20) + return TRUE; + Stream_Read_UINT32(s, DeviceType); + Stream_Read_UINT32(s, DeviceId); + Stream_Read(s, PreferredDosName, ARRAYSIZE(PreferredDosName)); + Stream_Read_UINT32(s, DeviceDataLength); + if (!Stream_SafeSeek(s, DeviceDataLength)) + return TRUE; + if (DeviceType == RDPDR_DTYP_SMARTCARD) + { + ArrayList_Append(rdpdr->blockedDevices, (void*)DeviceId); + if (count == 1) + return TRUE; + + WLog_INFO(TAG, "Filtering smartcard device 0x%08" PRIx32 "", DeviceId); + + memmove(dst, Stream_Pointer(s), Stream_GetRemainingLength(s)); + Stream_SetPosition(s, pos); + Stream_Write_UINT32(s, count - 1); + return FALSE; + } + } + + return FALSE; +} + +static BOOL filter_smartcard_device_list_announce_request(pf_channel_server_context* rdpdr, + wStream* s) +{ + BOOL rc = TRUE; + size_t pos; + UINT16 component, packetid; + + if (Stream_GetRemainingLength(s) < 12) + return FALSE; + + pos = Stream_GetPosition(s); + + Stream_Read_UINT16(s, component); + Stream_Read_UINT16(s, packetid); + + if (component != RDPDR_CTYP_CORE) + goto fail; + + switch (packetid) + { + case PAKID_CORE_DEVICELIST_ANNOUNCE: + if (filter_smartcard_device_list_announce(rdpdr, s)) + goto fail; + break; + case PAKID_CORE_DEVICELIST_REMOVE: + if (filter_smartcard_device_list_remove(rdpdr, s)) + goto fail; + break; + case PAKID_CORE_DEVICE_IOREQUEST: + if (filter_smartcard_device_io_request(rdpdr, s)) + goto fail; + break; + + case PAKID_CORE_SERVER_ANNOUNCE: + case PAKID_CORE_CLIENTID_CONFIRM: + case PAKID_CORE_CLIENT_NAME: + case PAKID_CORE_DEVICE_REPLY: + case PAKID_CORE_SERVER_CAPABILITY: + case PAKID_CORE_CLIENT_CAPABILITY: + case PAKID_CORE_USER_LOGGEDON: + WLog_WARN(TAG, "Filtering client -> server message [%s | %s]", + rdpdr_component_string(component), rdpdr_packetid_string(packetid)); + goto fail; + default: + break; + } + + rc = FALSE; +fail: + Stream_SetPosition(s, pos); + return rc; +} +#endif + +static void* stream_copy(const void* obj) +{ + wStream* src = obj; + wStream* dst = Stream_New(NULL, Stream_Capacity(src)); + if (!dst) + return NULL; + memcpy(Stream_Buffer(dst), Stream_Buffer(src), Stream_Capacity(dst)); + Stream_SetLength(dst, Stream_Length(src)); + Stream_SetPosition(dst, Stream_GetPosition(src)); + return dst; +} + +static void stream_free(void* obj) +{ + wStream* s = obj; + Stream_Free(s, TRUE); +} + +BOOL pf_channel_rdpdr_client_new(pClientContext* pc) +{ + wObject* obj; + pf_channel_client_context* rdpdr; + + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->interceptContextMap); + + rdpdr = calloc(1, sizeof(pf_channel_client_context)); + if (!rdpdr) + return FALSE; + if (!pf_channel_rdpdr_common_context_new(&rdpdr->common, pf_channel_rdpdr_client_context_free)) + goto fail; + + rdpdr->maxMajorVersion = RDPDR_VERSION_MAJOR; + rdpdr->maxMinorVersion = RDPDR_VERSION_MINOR_RDP10X; + rdpdr->state = STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST; + + rdpdr->queue = Queue_New(TRUE, 0, 0); + if (!rdpdr->queue) + goto fail; + obj = Queue_Object(rdpdr->queue); + WINPR_ASSERT(obj); + obj->fnObjectNew = stream_copy; + obj->fnObjectFree = stream_free; + return HashTable_Insert(pc->interceptContextMap, RDPDR_SVC_CHANNEL_NAME, rdpdr); +fail: + pf_channel_rdpdr_client_context_free(&rdpdr->common.base); + return FALSE; +} + +void pf_channel_rdpdr_client_free(pClientContext* pc) +{ + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->interceptContextMap); + HashTable_Remove(pc->interceptContextMap, RDPDR_SVC_CHANNEL_NAME); +} + +static void pf_channel_rdpdr_server_context_free(InterceptContextMapEntry* base) +{ + pf_channel_server_context* entry = (pf_channel_server_context*)base; + if (!entry) + return; + + WTSVirtualChannelClose(entry->handle); + pf_channel_rdpdr_common_context_free(&entry->common); + ArrayList_Free(entry->blockedDevices); + free(entry); +} + +BOOL pf_channel_rdpdr_server_new(pServerContext* ps) +{ + pf_channel_server_context* rdpdr; + PULONG pSessionId = NULL; + DWORD BytesReturned = 0; + + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->interceptContextMap); + + rdpdr = calloc(1, sizeof(pf_channel_server_context)); + if (!rdpdr) + return FALSE; + if (!pf_channel_rdpdr_common_context_new(&rdpdr->common, pf_channel_rdpdr_server_context_free)) + goto fail; + rdpdr->state = STATE_SERVER_INITIAL; + + rdpdr->blockedDevices = ArrayList_New(FALSE); + if (!rdpdr->blockedDevices) + goto fail; + + rdpdr->SessionId = WTS_CURRENT_SESSION; + if (WTSQuerySessionInformationA(ps->vcm, WTS_CURRENT_SESSION, WTSSessionId, (LPSTR*)&pSessionId, + &BytesReturned)) + { + rdpdr->SessionId = (DWORD)*pSessionId; + WTSFreeMemory(pSessionId); + } + + rdpdr->handle = WTSVirtualChannelOpenEx(rdpdr->SessionId, RDPDR_SVC_CHANNEL_NAME, 0); + if (rdpdr->handle == 0) + goto fail; + return HashTable_Insert(ps->interceptContextMap, RDPDR_SVC_CHANNEL_NAME, rdpdr); +fail: + pf_channel_rdpdr_server_context_free(&rdpdr->common.base); + return FALSE; +} + +void pf_channel_rdpdr_server_free(pServerContext* ps) +{ + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->interceptContextMap); + HashTable_Remove(ps->interceptContextMap, RDPDR_SVC_CHANNEL_NAME); +} + +static pf_channel_server_context* get_channel(pServerContext* ps) +{ + pf_channel_server_context* rdpdr; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->interceptContextMap); + + rdpdr = HashTable_GetItemValue(ps->interceptContextMap, RDPDR_SVC_CHANNEL_NAME); + if (!rdpdr) + { + WLog_ERR(TAG, "[%s]: Channel %s missing context in interceptContextMap", __FUNCTION__, + RDPDR_SVC_CHANNEL_NAME); + return NULL; + } + + return rdpdr; +} + +BOOL pf_channel_rdpdr_server_handle(pServerContext* ps, UINT16 channelId, const char* channel_name, + const BYTE* xdata, size_t xsize, UINT32 flags, size_t totalSize) +{ + wStream* s; + pClientContext* pc; + pf_channel_server_context* rdpdr = get_channel(ps); + if (!rdpdr) + return FALSE; + + WINPR_ASSERT(ps->pdata); + pc = ps->pdata->pc; + + s = rdpdr->common.buffer; + + if (flags & CHANNEL_FLAG_FIRST) + Stream_SetPosition(s, 0); + + if (!Stream_EnsureRemainingCapacity(s, xsize)) + return FALSE; + Stream_Write(s, xdata, xsize); + + if ((flags & CHANNEL_FLAG_LAST) == 0) + return TRUE; + + Stream_SealLength(s); + Stream_SetPosition(s, 0); + + if (Stream_Length(s) != totalSize) + { + WLog_WARN(TAG, + "Received invalid %s channel data (client -> proxy), expected %" PRIuz + "bytes, got %" PRIuz, + channel_name, totalSize, Stream_Length(s)); + return FALSE; + } + + switch (rdpdr->state) + { + case STATE_SERVER_EXPECT_CLIENT_ANNOUNCE_REPLY: + if (rdpdr_process_client_announce_reply(rdpdr, s) != CHANNEL_RC_OK) + return FALSE; + rdpdr->state = STATE_SERVER_EXPECT_CLIENT_NAME_REQUEST; + break; + case STATE_SERVER_EXPECT_CLIENT_NAME_REQUEST: + if (rdpdr_process_client_name_request(rdpdr, s, pc) != CHANNEL_RC_OK) + return FALSE; + if (rdpdr_send_server_capability_request(rdpdr) != CHANNEL_RC_OK) + return FALSE; + if (rdpdr_send_server_clientid_confirm(rdpdr) != CHANNEL_RC_OK) + return FALSE; + rdpdr->state = STATE_SERVER_EXPECT_EXPECT_CLIENT_CAPABILITY_RESPONE; + break; + case STATE_SERVER_EXPECT_EXPECT_CLIENT_CAPABILITY_RESPONE: + if (rdpdr_process_client_capability_response(rdpdr, s) != CHANNEL_RC_OK) + return FALSE; + rdpdr->state = STATE_SERVER_CHANNEL_RUNNING; + break; + case STATE_SERVER_CHANNEL_RUNNING: +#if defined(WITH_PROXY_EMULATE_SMARTCARD) + if (!pf_channel_smartcard_client_emulate(pc) || + !filter_smartcard_device_list_announce_request(rdpdr, s)) + { + if (!pf_channel_rdpdr_client_pass_message(pc, channelId, channel_name, s)) + return FALSE; + } + else + return pf_channel_smartcard_server_handle(ps, s); +#else + if (!pf_channel_rdpdr_client_pass_message(pc, channelId, channel_name, s)) + return FALSE; +#endif + break; + default: + case STATE_SERVER_INITIAL: + WLog_WARN(TAG, "Invalid state %s", rdpdr_server_state_to_string(rdpdr->state)); + return FALSE; + } + + return TRUE; +} + +BOOL pf_channel_rdpdr_server_announce(pServerContext* ps) +{ + pf_channel_server_context* rdpdr = get_channel(ps); + if (!rdpdr) + return FALSE; + + WINPR_ASSERT(rdpdr->state == STATE_SERVER_INITIAL); + if (rdpdr_server_send_announce_request(rdpdr) != CHANNEL_RC_OK) + return FALSE; + rdpdr->state = STATE_SERVER_EXPECT_CLIENT_ANNOUNCE_REPLY; + return TRUE; +} + +BOOL pf_channel_rdpdr_client_reset(pClientContext* pc) +{ + pf_channel_client_context* rdpdr; + + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->pdata); + WINPR_ASSERT(pc->interceptContextMap); + + rdpdr = HashTable_GetItemValue(pc->interceptContextMap, RDPDR_SVC_CHANNEL_NAME); + if (!rdpdr) + return TRUE; + + Queue_Clear(rdpdr->queue); + rdpdr->flags = 0; + rdpdr->state = STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST; + + return TRUE; +} diff --git a/server/proxy/channels/pf_channel_rdpdr.h b/server/proxy/channels/pf_channel_rdpdr.h new file mode 100644 index 000000000..57dc54970 --- /dev/null +++ b/server/proxy/channels/pf_channel_rdpdr.h @@ -0,0 +1,43 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Proxy Server + * + * Copyright 2021 Armin Novak + * 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 + +BOOL pf_channel_rdpdr_client_new(pClientContext* pc); +void pf_channel_rdpdr_client_free(pClientContext* pc); + +BOOL pf_channel_rdpdr_client_reset(pClientContext* pc); + +BOOL pf_channel_rdpdr_client_handle(pClientContext* pc, UINT16 channelId, const char* channel_name, + const BYTE* xdata, size_t xsize, UINT32 flags, + size_t totalSize); + +BOOL pf_channel_rdpdr_server_new(pServerContext* ps); +void pf_channel_rdpdr_server_free(pServerContext* ps); + +BOOL pf_channel_rdpdr_server_announce(pServerContext* ps); +BOOL pf_channel_rdpdr_server_handle(pServerContext* ps, UINT16 channelId, const char* channel_name, + const BYTE* xdata, size_t xsize, UINT32 flags, + size_t totalSize); + +#endif /* FREERDP_SERVER_PROXY_RDPDR_H */ diff --git a/server/proxy/channels/pf_channel_smartcard.c b/server/proxy/channels/pf_channel_smartcard.c new file mode 100644 index 000000000..329748fca --- /dev/null +++ b/server/proxy/channels/pf_channel_smartcard.c @@ -0,0 +1,396 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Proxy Server + * + * Copyright 2021 Armin Novak + * 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 +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include "pf_channel_smartcard.h" +#include "pf_channel_rdpdr.h" + +#define TAG PROXY_TAG("channel.scard") + +#define SCARD_SVC_CHANNEL_NAME "SCARD" + +typedef struct +{ + InterceptContextMapEntry base; + scard_call_context* callctx; + PTP_POOL ThreadPool; + TP_CALLBACK_ENVIRON ThreadPoolEnv; + wArrayList* workObjects; +} pf_channel_client_context; + +typedef struct +{ + SMARTCARD_OPERATION op; + wStream* out; + pClientContext* pc; + UINT (*send_fkt)(pClientContext*, wStream*); +} pf_channel_client_queue_element; + +static pf_channel_client_context* scard_get_client_context(pClientContext* pc) +{ + pf_channel_client_context* scard; + + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->interceptContextMap); + + scard = HashTable_GetItemValue(pc->interceptContextMap, SCARD_SVC_CHANNEL_NAME); + if (!scard) + WLog_WARN(TAG, "[%s] missing in pc->interceptContextMap", SCARD_SVC_CHANNEL_NAME); + return scard; +} + +static BOOL pf_channel_client_write_iostatus(wStream* out, const SMARTCARD_OPERATION* op, + UINT32 ioStatus) +{ + UINT16 component, packetid; + UINT32 dID, cID; + size_t pos; + + WINPR_ASSERT(op); + WINPR_ASSERT(out); + + pos = Stream_GetPosition(out); + if (Stream_Length(out) < 16) + return FALSE; + Stream_SetPosition(out, 0); + + Stream_Read_UINT16(out, component); + Stream_Read_UINT16(out, packetid); + + Stream_Read_UINT32(out, dID); + Stream_Read_UINT32(out, cID); + + WINPR_ASSERT(component == RDPDR_CTYP_CORE); + WINPR_ASSERT(packetid == PAKID_CORE_DEVICE_IOCOMPLETION); + WINPR_ASSERT(dID == op->deviceID); + WINPR_ASSERT(cID == op->completionID); + + Stream_Write_UINT32(out, ioStatus); + Stream_SetPosition(out, pos); + return TRUE; +} + +struct thread_arg +{ + pf_channel_client_context* scard; + pf_channel_client_queue_element* e; +}; + +static void queue_free(void* obj); +static void* queue_copy(const void* obj); + +static VOID irp_thread(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WORK Work) +{ + struct thread_arg* arg = Context; + pf_channel_client_context* scard = arg->scard; + { + UINT32 ioStatus; + 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->pc, arg->e->out); + } + } + queue_free(arg->e); + free(arg); + ArrayList_Remove(scard->workObjects, Work); +} + +static BOOL start_irp_thread(pf_channel_client_context* scard, + const pf_channel_client_queue_element* e) +{ + PTP_WORK work; + struct thread_arg* arg = calloc(1, sizeof(struct thread_arg)); + if (!arg) + return FALSE; + arg->scard = scard; + arg->e = queue_copy(e); + if (!arg->e) + goto fail; + + work = CreateThreadpoolWork(irp_thread, arg, &scard->ThreadPoolEnv); + if (!work) + goto fail; + ArrayList_Append(scard->workObjects, work); + SubmitThreadpoolWork(work); + + return TRUE; + +fail: + if (arg) + queue_free(arg->e); + free(arg); + return FALSE; +} + +BOOL pf_channel_smartcard_client_handle(pClientContext* pc, wStream* s, wStream* out, + UINT (*send_fkt)(pClientContext*, wStream*)) +{ + BOOL rc = FALSE; + LONG status; + UINT32 FileId; + UINT32 CompletionId; + UINT32 ioStatus; + pf_channel_client_queue_element e = { 0 }; + pf_channel_client_context* scard = scard_get_client_context(pc); + + WINPR_ASSERT(send_fkt); + WINPR_ASSERT(s); + + if (!scard) + return FALSE; + + e.pc = pc; + e.out = out; + e.send_fkt = send_fkt; + + /* Skip IRP header */ + if (Stream_GetRemainingLength(s) < 20) + { + WLog_WARN(TAG, "[%s] Invalid IRP received, expected 20 bytes, have %" PRIuz, + SCARD_SVC_CHANNEL_NAME, Stream_GetRemainingLength(s)); + return FALSE; + } + else + { + UINT32 DeviceId; + UINT32 MajorFunction; + UINT32 MinorFunction; + + Stream_Read_UINT32(s, DeviceId); /* DeviceId (4 bytes) */ + Stream_Read_UINT32(s, FileId); /* FileId (4 bytes) */ + Stream_Read_UINT32(s, CompletionId); /* CompletionId (4 bytes) */ + Stream_Read_UINT32(s, MajorFunction); /* MajorFunction (4 bytes) */ + Stream_Read_UINT32(s, MinorFunction); /* MinorFunction (4 bytes) */ + + if (MajorFunction != IRP_MJ_DEVICE_CONTROL) + { + WLog_WARN(TAG, "[%s] Invalid IRP received, expected %s, got %2", SCARD_SVC_CHANNEL_NAME, + rdpdr_irp_string(IRP_MJ_DEVICE_CONTROL), rdpdr_irp_string(MajorFunction)); + return FALSE; + } + e.op.completionID = CompletionId; + e.op.deviceID = DeviceId; + + if (!rdpdr_write_iocompletion_header(out, DeviceId, CompletionId, 0)) + return FALSE; + } + + status = smartcard_irp_device_control_decode(s, CompletionId, FileId, &e.op); + if (status != 0) + goto fail; + + switch (e.op.ioControlCode) + { + case SCARD_IOCTL_LISTREADERGROUPSA: + case SCARD_IOCTL_LISTREADERGROUPSW: + case SCARD_IOCTL_LISTREADERSA: + case SCARD_IOCTL_LISTREADERSW: + case SCARD_IOCTL_LOCATECARDSA: + case SCARD_IOCTL_LOCATECARDSW: + case SCARD_IOCTL_LOCATECARDSBYATRA: + case SCARD_IOCTL_LOCATECARDSBYATRW: + case SCARD_IOCTL_GETSTATUSCHANGEA: + case SCARD_IOCTL_GETSTATUSCHANGEW: + case SCARD_IOCTL_CONNECTA: + case SCARD_IOCTL_CONNECTW: + case SCARD_IOCTL_RECONNECT: + case SCARD_IOCTL_DISCONNECT: + case SCARD_IOCTL_BEGINTRANSACTION: + case SCARD_IOCTL_ENDTRANSACTION: + case SCARD_IOCTL_STATE: + case SCARD_IOCTL_STATUSA: + case SCARD_IOCTL_STATUSW: + case SCARD_IOCTL_TRANSMIT: + case SCARD_IOCTL_CONTROL: + case SCARD_IOCTL_GETATTRIB: + case SCARD_IOCTL_SETATTRIB: + if (!start_irp_thread(scard, &e)) + goto fail; + return TRUE; + + default: + status = smartcard_irp_device_control_call(scard->callctx, out, &ioStatus, &e.op); + if (status != 0) + goto fail; + if (!pf_channel_client_write_iostatus(out, &e.op, ioStatus)) + goto fail; + break; + } + + rc = send_fkt(pc, out) == CHANNEL_RC_OK; + +fail: + smartcard_operation_free(&e.op, FALSE); + return rc; +} + +BOOL pf_channel_smartcard_server_handle(pServerContext* ps, wStream* s) +{ + WLog_ERR(TAG, "TODO: %s unimplemented", __FUNCTION__); + return TRUE; +} + +static void channel_stop_and_wait(pf_channel_client_context* scard, BOOL reset) +{ + WINPR_ASSERT(scard); + smartcard_call_context_signal_stop(scard->callctx, FALSE); + + while (ArrayList_Count(scard->workObjects) > 0) + { + PTP_WORK work = ArrayList_GetItem(scard->workObjects, 0); + if (!work) + continue; + WaitForThreadpoolWorkCallbacks(work, TRUE); + } + + smartcard_call_context_signal_stop(scard->callctx, reset); +} + +static void pf_channel_scard_client_context_free(InterceptContextMapEntry* base) +{ + pf_channel_client_context* entry = (pf_channel_client_context*)base; + if (!entry) + return; + + /* Set the stop event. + * All threads waiting in blocking operations will abort at the next + * available polling slot */ + channel_stop_and_wait(entry, FALSE); + ArrayList_Free(entry->workObjects); + CloseThreadpool(entry->ThreadPool); + DestroyThreadpoolEnvironment(&entry->ThreadPoolEnv); + + smartcard_call_context_free(entry->callctx); + free(entry); +} + +static void queue_free(void* obj) +{ + pf_channel_client_queue_element* element = obj; + if (!element) + return; + smartcard_operation_free(&element->op, FALSE); + Stream_Free(element->out, TRUE); + free(element); +} + +static void* queue_copy(const void* obj) +{ + const pf_channel_client_queue_element* other = obj; + pf_channel_client_queue_element* copy; + if (!other) + return NULL; + copy = calloc(1, sizeof(pf_channel_client_queue_element)); + if (!copy) + return NULL; + + *copy = *other; + copy->out = Stream_New(NULL, Stream_Capacity(other->out)); + if (!copy->out) + goto fail; + Stream_Write(copy->out, Stream_Buffer(other->out), Stream_GetPosition(other->out)); + return copy; +fail: + queue_free(copy); + return NULL; +} + +static void work_object_free(void* arg) +{ + PTP_WORK work = arg; + CloseThreadpoolWork(work); +} + +BOOL pf_channel_smartcard_client_new(pClientContext* pc) +{ + pf_channel_client_context* scard; + wObject* obj; + + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->interceptContextMap); + + scard = calloc(1, sizeof(pf_channel_client_context)); + if (!scard) + return FALSE; + scard->base.free = pf_channel_scard_client_context_free; + scard->callctx = smartcard_call_context_new(pc->context.settings); + if (!scard->callctx) + goto fail; + + scard->workObjects = ArrayList_New(TRUE); + if (!scard->workObjects) + goto fail; + obj = ArrayList_Object(scard->workObjects); + WINPR_ASSERT(obj); + obj->fnObjectFree = work_object_free; + + scard->ThreadPool = CreateThreadpool(NULL); + if (!scard->ThreadPool) + goto fail; + InitializeThreadpoolEnvironment(&scard->ThreadPoolEnv); + SetThreadpoolCallbackPool(&scard->ThreadPoolEnv, scard->ThreadPool); + + return HashTable_Insert(pc->interceptContextMap, SCARD_SVC_CHANNEL_NAME, scard); +fail: + pf_channel_scard_client_context_free(&scard->base); + return FALSE; +} + +void pf_channel_smartcard_client_free(pClientContext* pc) +{ + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->interceptContextMap); + HashTable_Remove(pc->interceptContextMap, SCARD_SVC_CHANNEL_NAME); +} + +BOOL pf_channel_smartcard_client_emulate(pClientContext* pc) +{ + pf_channel_client_context* scard = scard_get_client_context(pc); + if (!scard) + return FALSE; + return smartcard_call_is_configured(scard->callctx); +} + +BOOL pf_channel_smartcard_client_reset(pClientContext* pc) +{ + pf_channel_client_context* scard = scard_get_client_context(pc); + if (!scard) + return TRUE; + + channel_stop_and_wait(scard, TRUE); + return TRUE; +} diff --git a/server/proxy/channels/pf_channel_smartcard.h b/server/proxy/channels/pf_channel_smartcard.h new file mode 100644 index 000000000..e50632a54 --- /dev/null +++ b/server/proxy/channels/pf_channel_smartcard.h @@ -0,0 +1,36 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Proxy Server + * + * Copyright 2021 Armin Novak + * 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 + +BOOL pf_channel_smartcard_client_new(pClientContext* pc); +void pf_channel_smartcard_client_free(pClientContext* pc); + +BOOL pf_channel_smartcard_client_reset(pClientContext* pc); +BOOL pf_channel_smartcard_client_emulate(pClientContext* pc); + +BOOL pf_channel_smartcard_client_handle(pClientContext* pc, wStream* s, wStream* out, + UINT (*send_fkt)(pClientContext*, wStream*)); +BOOL pf_channel_smartcard_server_handle(pServerContext* ps, wStream* s); + +#endif /* FREERDP_SERVER_PROXY_SCARD_H */ diff --git a/server/proxy/pf_client.c b/server/proxy/pf_client.c index 634f44c7a..642929a8c 100644 --- a/server/proxy/pf_client.c +++ b/server/proxy/pf_client.c @@ -44,6 +44,8 @@ #include #include "proxy_modules.h" #include "pf_utils.h" +#include "channels/pf_channel_rdpdr.h" +#include "channels/pf_channel_smartcard.h" #define TAG PROXY_TAG("client") @@ -130,13 +132,7 @@ static BOOL pf_client_load_rdpsnd(pClientContext* pc) */ if (!freerdp_static_channel_collection_find(context->settings, RDPSND_CHANNEL_NAME)) { - const char* params[2] = { RDPSND_CHANNEL_NAME }; - - if (config->AudioOutput && - WTSVirtualChannelManagerIsChannelJoined(ps->vcm, RDPSND_CHANNEL_NAME)) - params[1] = "sys:proxy"; - else - params[1] = "sys:fake"; + const char* params[2] = { RDPSND_CHANNEL_NAME, "sys:fake" }; if (!freerdp_client_add_static_channel(context->settings, ARRAYSIZE(params), params)) return FALSE; @@ -175,6 +171,64 @@ static BOOL pf_client_use_peer_load_balance_info(pClientContext* pc) return TRUE; } +static BOOL str_is_empty(const char* str) +{ + if (!str) + return TRUE; + if (strlen(str) == 0) + return TRUE; + return FALSE; +} + +static BOOL pf_client_use_proxy_smartcard_auth(const rdpSettings* settings) +{ + BOOL enable = freerdp_settings_get_bool(settings, FreeRDP_SmartcardLogon); + const char* key = freerdp_settings_get_string(settings, FreeRDP_SmartcardPrivateKey); + const char* cert = freerdp_settings_get_string(settings, FreeRDP_SmartcardCertificate); + + if (!enable) + return FALSE; + + if (str_is_empty(key)) + return FALSE; + + if (str_is_empty(cert)) + return FALSE; + + return TRUE; +} + +static BOOL freerdp_client_load_static_channel_addin(rdpChannels* channels, rdpSettings* settings, + const char* name, void* data) +{ + PVIRTUALCHANNELENTRY entry = NULL; + PVIRTUALCHANNELENTRYEX entryEx = NULL; + entryEx = (PVIRTUALCHANNELENTRYEX)(void*)freerdp_load_channel_addin_entry( + name, NULL, NULL, FREERDP_ADDIN_CHANNEL_STATIC | FREERDP_ADDIN_CHANNEL_ENTRYEX); + + if (!entryEx) + entry = freerdp_load_channel_addin_entry(name, NULL, NULL, FREERDP_ADDIN_CHANNEL_STATIC); + + if (entryEx) + { + if (freerdp_channels_client_load_ex(channels, settings, entryEx, data) == 0) + { + WLog_INFO(TAG, "loading channelEx %s", name); + return TRUE; + } + } + else if (entry) + { + if (freerdp_channels_client_load(channels, settings, entry, data) == 0) + { + WLog_INFO(TAG, "loading channel %s", name); + return TRUE; + } + } + + return FALSE; +} + static BOOL pf_client_pre_connect(freerdp* instance) { pClientContext* pc; @@ -217,7 +271,7 @@ static BOOL pf_client_pre_connect(freerdp* instance) config->DeviceRedirection) || !freerdp_settings_set_bool(settings, FreeRDP_SupportDisplayControl, config->DisplayControl) || - !freerdp_settings_set_bool(settings, FreeRDP_RemoteAssistanceMode, config->RemoteApp) || + !freerdp_settings_set_bool(settings, FreeRDP_RemoteApplicationMode, config->RemoteApp) || !freerdp_settings_set_bool(settings, FreeRDP_MultiTouchInput, config->Multitouch)) return FALSE; @@ -275,6 +329,13 @@ static BOOL pf_client_pre_connect(freerdp* instance) } else { + if (!pf_channel_rdpdr_client_new(pc)) + return FALSE; +#if defined(WITH_PROXY_EMULATE_SMARTCARD) + if (!pf_channel_smartcard_client_new(pc)) + return FALSE; +#endif + /* Copy the current channel settings from the peer connection to the client. */ if (!freerdp_channels_from_mcs(settings, &ps->context)) return FALSE; @@ -315,6 +376,125 @@ static BOOL pf_client_pre_connect(freerdp* instance) return pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_PRE_CONNECT, pc->pdata, pc); } +static BOOL pf_client_receive_channel_passthrough(proxyData* pdata, UINT16 channelId, + const char* channel_name, const BYTE* xdata, + size_t xsize, UINT32 flags, size_t totalSize) +{ + proxyChannelDataEventInfo ev; + UINT16 server_channel_id; + pServerContext* ps; + + WINPR_ASSERT(pdata); + + ps = pdata->ps; + WINPR_ASSERT(ps); + + ev.channel_id = channelId; + ev.channel_name = 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 TRUE; /* Silently drop */ + + /* Dynamic channels need special treatment + * + * We need to check every message with CHANNEL_FLAG_FIRST set if it + * is a CREATE_REQUEST_PDU (0x01) and extract channelId and name + * from it. + * + * To avoid issues with (misbehaving) clients assume all packets + * that do not have at least a length of 1 byte and all incomplete + * CREATE_REQUEST_PDU (0x01) packets as invalid. + */ + if ((flags & CHANNEL_FLAG_FIRST) && + (strncmp(channel_name, DRDYNVC_SVC_CHANNEL_NAME, CHANNEL_NAME_LEN + 1) == 0)) + { + BYTE cmd, first; + wStream *s, sbuffer; + + s = Stream_StaticConstInit(&sbuffer, xdata, xsize); + if (Stream_Length(s) < 1) + return FALSE; + + Stream_Read_UINT8(s, first); + cmd = first >> 4; + + if (cmd == CREATE_REQUEST_PDU) + { + proxyChannelDataEventInfo dev; + size_t len, nameLen; + const char* name; + UINT32 dynChannelId; + BYTE cbId = first & 0x03; + switch (cbId) + { + case 0x00: + if (Stream_GetRemainingLength(s) < 1) + return FALSE; + Stream_Read_UINT8(s, dynChannelId); + break; + case 0x01: + if (Stream_GetRemainingLength(s) < 2) + return FALSE; + Stream_Read_UINT16(s, dynChannelId); + break; + case 0x02: + if (Stream_GetRemainingLength(s) < 4) + return FALSE; + Stream_Read_UINT32(s, dynChannelId); + break; + default: + return FALSE; + } + + name = (const char*)Stream_Pointer(s); + nameLen = Stream_GetRemainingLength(s); + len = strnlen(name, nameLen); + if ((len == 0) || (len == nameLen)) + return FALSE; + dev.channel_id = dynChannelId; + dev.channel_name = name; + dev.data = xdata; + dev.data_len = xsize; + dev.flags = flags; + dev.total_size = totalSize; + + if (!pf_modules_run_filter( + pdata->module, FILTER_TYPE_CLIENT_PASSTHROUGH_DYN_CHANNEL_CREATE, pdata, &dev)) + return TRUE; /* Silently drop */ + } + } + server_channel_id = WTSChannelGetId(ps->context.peer, channel_name); + + /* Ignore messages for channels that can not be mapped. + * The client might not have enabled support for this specific channel, + * so just drop the message. */ + if (server_channel_id == 0) + return TRUE; + return ps->context.peer->SendChannelPacket(ps->context.peer, server_channel_id, totalSize, + flags, xdata, xsize); +} + +static BOOL pf_client_receive_channel_intercept(proxyData* pdata, UINT16 channelId, + const char* channel_name, const BYTE* xdata, + size_t xsize, UINT32 flags, size_t totalSize) +{ + WINPR_ASSERT(pdata); + WINPR_ASSERT(channel_name); + + if (strcmp(channel_name, RDPDR_SVC_CHANNEL_NAME) == 0) + return pf_channel_rdpdr_client_handle(pdata->pc, channelId, channel_name, xdata, xsize, + flags, totalSize); + + WLog_ERR(TAG, "[%s]: Channel %s [0x%04" PRIx16 "] intercept mode not implemented, aborting", + __FUNCTION__, channel_name, channelId); + return FALSE; +} + static BOOL pf_client_receive_channel_data_hook(freerdp* instance, UINT16 channelId, const BYTE* xdata, size_t xsize, UINT32 flags, size_t totalSize) @@ -324,7 +504,7 @@ static BOOL pf_client_receive_channel_data_hook(freerdp* instance, UINT16 channe pServerContext* ps; proxyData* pdata; const proxyConfig* config; - int pass; + pf_utils_channel_mode pass; WINPR_ASSERT(instance); WINPR_ASSERT(xdata || (xsize == 0)); @@ -342,107 +522,19 @@ static BOOL pf_client_receive_channel_data_hook(freerdp* instance, UINT16 channe config = pdata->config; WINPR_ASSERT(config); - pass = pf_utils_channel_is_passthrough(config, channel_name); + pass = pf_utils_get_channel_mode(config, channel_name); switch (pass) { - case 0: + case PF_UTILS_CHANNEL_BLOCK: return TRUE; /* Silently drop */ - case 1: - { - proxyChannelDataEventInfo ev; - UINT16 server_channel_id; - - ev.channel_id = channelId; - ev.channel_name = 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 TRUE; /* Silently drop */ - - /* Dynamic channels need special treatment - * - * We need to check every message with CHANNEL_FLAG_FIRST set if it - * is a CREATE_REQUEST_PDU (0x01) and extract channelId and name - * from it. - * - * To avoid issues with (misbehaving) clients assume all packets - * that do not have at least a length of 1 byte and all incomplete - * CREATE_REQUEST_PDU (0x01) packets as invalid. - */ - if ((flags & CHANNEL_FLAG_FIRST) && - (strncmp(channel_name, DRDYNVC_SVC_CHANNEL_NAME, CHANNEL_NAME_LEN + 1) == 0)) - { - BYTE cmd, first; - wStream *s, sbuffer; - - s = Stream_StaticConstInit(&sbuffer, xdata, xsize); - if (Stream_Length(s) < 1) - return FALSE; - - Stream_Read_UINT8(s, first); - cmd = first >> 4; - - if (cmd == CREATE_REQUEST_PDU) - { - proxyChannelDataEventInfo dev; - size_t len, nameLen; - const char* name; - UINT32 dynChannelId; - BYTE cbId = first & 0x03; - switch (cbId) - { - case 0x00: - if (Stream_GetRemainingLength(s) < 1) - return FALSE; - Stream_Read_UINT8(s, dynChannelId); - break; - case 0x01: - if (Stream_GetRemainingLength(s) < 2) - return FALSE; - Stream_Read_UINT16(s, dynChannelId); - break; - case 0x02: - if (Stream_GetRemainingLength(s) < 4) - return FALSE; - Stream_Read_UINT32(s, dynChannelId); - break; - default: - return FALSE; - } - - name = (const char*)Stream_Pointer(s); - nameLen = Stream_GetRemainingLength(s); - len = strnlen(name, nameLen); - if ((len == 0) || (len == nameLen)) - return FALSE; - dev.channel_id = dynChannelId; - dev.channel_name = name; - dev.data = xdata; - dev.data_len = xsize; - dev.flags = flags; - dev.total_size = totalSize; - - if (!pf_modules_run_filter(pdata->module, - FILTER_TYPE_CLIENT_PASSTHROUGH_DYN_CHANNEL_CREATE, - pdata, &dev)) - return TRUE; /* Silently drop */ - } - } - server_channel_id = WTSChannelGetId(ps->context.peer, channel_name); - - /* Ignore messages for channels that can not be mapped. - * The client might not have enabled support for this specific channel, - * so just drop the message. */ - if (server_channel_id == 0) - return TRUE; - return ps->context.peer->SendChannelPacket(ps->context.peer, server_channel_id, - totalSize, flags, xdata, xsize); - } + case PF_UTILS_CHANNEL_PASSTHROUGH: + return pf_client_receive_channel_passthrough(pdata, channelId, channel_name, xdata, + xsize, flags, totalSize); + case PF_UTILS_CHANNEL_INTERCEPT: + return pf_client_receive_channel_intercept(pdata, channelId, channel_name, xdata, xsize, + flags, totalSize); + case PF_UTILS_CHANNEL_NOT_HANDLED: default: WINPR_ASSERT(pc->client_receive_channel_data_original); return pc->client_receive_channel_data_original(instance, channelId, xdata, xsize, @@ -494,10 +586,9 @@ static BOOL sendQueuedChannelData(pClientContext* pc) rc = TRUE; else { - WINPR_ASSERT(pc->context.instance->SendChannelPacket); - rc = pc->context.instance->SendChannelPacket(pc->context.instance, channelId, - ev->total_size, ev->flags, ev->data, - ev->data_len); + WINPR_ASSERT(pc->context.instance->SendChannelData); + rc = pc->context.instance->SendChannelData(pc->context.instance, channelId, + ev->data, ev->data_len); } channel_data_free(ev); } @@ -589,8 +680,14 @@ static void pf_client_post_disconnect(freerdp* instance) pdata = pc->pdata; WINPR_ASSERT(pdata); +#if defined(WITH_PROXY_EMULATE_SMARTCARD) + pf_channel_smartcard_client_free(pc); +#endif + + pf_channel_rdpdr_client_free(pc); + pc->connected = FALSE; - pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_POST_CONNECT, pc->pdata, pc); + pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_POST_DISCONNECT, pc->pdata, pc); PubSub_UnsubscribeErrorInfo(instance->context->pubSub, pf_client_on_error_info); gdi_free(instance); @@ -600,6 +697,30 @@ static void pf_client_post_disconnect(freerdp* instance) proxy_data_abort_connect(pdata); } +static BOOL pf_client_redirect(freerdp* instance) +{ + pClientContext* pc; + proxyData* pdata; + + if (!instance) + return FALSE; + + if (!instance->context) + return FALSE; + + pc = (pClientContext*)instance->context; + WINPR_ASSERT(pc); + pdata = pc->pdata; + WINPR_ASSERT(pdata); + +#if defined(WITH_PROXY_EMULATE_SMARTCARD) + pf_channel_smartcard_client_reset(pc); +#endif + pf_channel_rdpdr_client_reset(pc); + + return pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_REDIRECT, pc->pdata, pc); +} + /* * pf_client_should_retry_without_nla: * @@ -640,6 +761,14 @@ static void pf_client_set_security_settings(pClientContext* pc) settings->TlsSecurity = config->ClientTlsSecurity; settings->NlaSecurity = config->ClientNlaSecurity; + /* Smartcard authentication currently does not work with NLA */ + if (pf_client_use_proxy_smartcard_auth(settings)) + { + freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE); + freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, TRUE); + freerdp_settings_set_bool(settings, FreeRDP_RedirectSmartCards, TRUE); + } + if (!config->ClientNlaSecurity) return; @@ -827,6 +956,8 @@ static void pf_client_context_free(freerdp* instance, rdpContext* context) Queue_Free(pc->cached_server_channel_data); Stream_Free(pc->remote_pem, TRUE); free(pc->remote_hostname); + free(pc->computerName.v); + HashTable_Free(pc->interceptContextMap); } static int pf_client_verify_X509_certificate(freerdp* instance, const BYTE* data, size_t length, @@ -914,6 +1045,7 @@ static BOOL pf_client_client_new(freerdp* instance, rdpContext* context) instance->PreConnect = pf_client_pre_connect; instance->PostConnect = pf_client_post_connect; instance->PostDisconnect = pf_client_post_disconnect; + instance->Redirect = pf_client_redirect; instance->LogonErrorInfo = pf_logon_error_info; instance->VerifyX509Certificate = pf_client_verify_X509_certificate; @@ -929,6 +1061,18 @@ static BOOL pf_client_client_new(freerdp* instance, rdpContext* context) WINPR_ASSERT(obj); obj->fnObjectNew = channel_data_copy; obj->fnObjectFree = channel_data_free; + + pc->interceptContextMap = HashTable_New(FALSE); + if (!pc->interceptContextMap) + return FALSE; + + if (!HashTable_SetupForStringData(pc->interceptContextMap, FALSE)) + return FALSE; + + obj = HashTable_ValueObject(pc->interceptContextMap); + WINPR_ASSERT(obj); + obj->fnObjectFree = intercept_context_entry_free; + return TRUE; } diff --git a/server/proxy/pf_config.c b/server/proxy/pf_config.c index da2d8bb1c..7d2055d5b 100644 --- a/server/proxy/pf_config.c +++ b/server/proxy/pf_config.c @@ -222,9 +222,10 @@ static BOOL pf_config_load_channels(wIniFile* ini, proxyConfig* config) config->RemoteApp = pf_config_get_bool(ini, "Channels", "RemoteApp", FALSE); config->PassthroughIsBlacklist = pf_config_get_bool(ini, "Channels", "PassthroughIsBlacklist", FALSE); - config->Passthrough = pf_config_parse_comma_separated_list( pf_config_get_str(ini, "Channels", "Passthrough", FALSE), &config->PassthroughCount); + config->Intercept = pf_config_parse_comma_separated_list( + pf_config_get_str(ini, "Channels", "Intercept", FALSE), &config->InterceptCount); return TRUE; } @@ -485,6 +486,8 @@ BOOL pf_server_config_dump(const char* file) goto fail; if (IniFile_SetKeyValueString(ini, "Channels", "Passthrough", "") < 0) goto fail; + if (IniFile_SetKeyValueString(ini, "Channels", "Intercept", "") < 0) + goto fail; /* Input configuration */ if (IniFile_SetKeyValueString(ini, "Input", "Keyboard", "true") < 0) @@ -668,6 +671,12 @@ void pf_server_config_print(const proxyConfig* config) pf_server_config_print_list(config->Passthrough, config->PassthroughCount); } + if (config->InterceptCount) + { + WLog_INFO(TAG, "\tStatic Channels Proxy-Intercept:"); + pf_server_config_print_list(config->Intercept, config->InterceptCount); + } + CONFIG_PRINT_SECTION("Clipboard"); CONFIG_PRINT_BOOL(config, TextOnly); if (config->MaxTextLength > 0) @@ -701,6 +710,7 @@ void pf_server_config_free(proxyConfig* config) return; free(config->Passthrough); + free(config->Intercept); free(config->RequiredPlugins); free(config->Modules); free(config->TargetHost); @@ -788,6 +798,9 @@ BOOL pf_config_clone(proxyConfig** dst, const proxyConfig* config) if (!pf_config_copy_string_list(&tmp->Passthrough, &tmp->PassthroughCount, config->Passthrough, config->PassthroughCount)) goto fail; + if (!pf_config_copy_string_list(&tmp->Intercept, &tmp->InterceptCount, config->Intercept, + config->InterceptCount)) + goto fail; if (!pf_config_copy_string_list(&tmp->Modules, &tmp->ModulesCount, config->Modules, config->ModulesCount)) goto fail; @@ -883,7 +896,6 @@ static BOOL config_plugin_mouse_event(proxyPlugin* plugin, proxyData* pdata, voi WINPR_ASSERT(cfg); rc = cfg->Mouse; - WLog_DBG(TAG, "%s: %s", __FUNCTION__, rc ? "TRUE" : "FALSE"); return rc; } @@ -915,7 +927,7 @@ static BOOL config_plugin_server_channel_data(proxyPlugin* plugin, proxyData* pd static BOOL config_plugin_dynamic_channel_create(proxyPlugin* plugin, proxyData* pdata, void* param) { - int rc; + pf_utils_channel_mode rc; BOOL accept; const struct config_plugin_data* custom; const proxyConfig* cfg; @@ -931,8 +943,21 @@ static BOOL config_plugin_dynamic_channel_create(proxyPlugin* plugin, proxyData* cfg = custom->config; WINPR_ASSERT(cfg); - rc = pf_utils_channel_is_passthrough(cfg, channel->channel_name); - accept = rc > 0; + rc = pf_utils_get_channel_mode(cfg, channel->channel_name); + switch (rc) + { + + case PF_UTILS_CHANNEL_INTERCEPT: + case PF_UTILS_CHANNEL_PASSTHROUGH: + accept = TRUE; + break; + case PF_UTILS_CHANNEL_BLOCK: + case PF_UTILS_CHANNEL_NOT_HANDLED: + default: + accept = FALSE; + break; + } + if (accept) { if (strncmp(RDPGFX_DVC_CHANNEL_NAME, channel->channel_name, @@ -971,7 +996,7 @@ static BOOL config_plugin_dynamic_channel_create(proxyPlugin* plugin, proxyData* static BOOL config_plugin_channel_create(proxyPlugin* plugin, proxyData* pdata, void* param) { - int rc; + pf_utils_channel_mode rc; BOOL accept; const struct config_plugin_data* custom; const proxyConfig* cfg; @@ -987,8 +1012,19 @@ static BOOL config_plugin_channel_create(proxyPlugin* plugin, proxyData* pdata, cfg = custom->config; WINPR_ASSERT(cfg); - rc = pf_utils_channel_is_passthrough(cfg, channel->channel_name); - accept = rc > 0; + rc = pf_utils_get_channel_mode(cfg, channel->channel_name); + switch (rc) + { + case PF_UTILS_CHANNEL_INTERCEPT: + case PF_UTILS_CHANNEL_PASSTHROUGH: + accept = TRUE; + break; + case PF_UTILS_CHANNEL_BLOCK: + case PF_UTILS_CHANNEL_NOT_HANDLED: + default: + accept = FALSE; + break; + } if (accept) { if (strncmp(CLIPRDR_SVC_CHANNEL_NAME, channel->channel_name, diff --git a/server/proxy/pf_context.c b/server/proxy/pf_context.c index 293135dfc..1506381d4 100644 --- a/server/proxy/pf_context.c +++ b/server/proxy/pf_context.c @@ -29,10 +29,13 @@ #include "pf_client.h" #include +#include "channels/pf_channel_rdpdr.h" + /* Proxy context initialization callback */ static void client_to_proxy_context_free(freerdp_peer* client, rdpContext* ctx); static BOOL client_to_proxy_context_new(freerdp_peer* client, rdpContext* ctx) { + wObject* obj; pServerContext* context = (pServerContext*)ctx; WINPR_ASSERT(client); @@ -48,6 +51,15 @@ static BOOL client_to_proxy_context_new(freerdp_peer* client, rdpContext* ctx) if (!(context->dynvcReady = CreateEvent(NULL, TRUE, FALSE, NULL))) goto error; + context->interceptContextMap = HashTable_New(FALSE); + if (!context->interceptContextMap) + goto error; + if (!HashTable_SetupForStringData(context->interceptContextMap, FALSE)) + goto error; + obj = HashTable_ValueObject(context->interceptContextMap); + WINPR_ASSERT(obj); + obj->fnObjectFree = intercept_context_entry_free; + return TRUE; error: @@ -66,15 +78,17 @@ void client_to_proxy_context_free(freerdp_peer* client, rdpContext* ctx) if (!context) return; - if (context->vcm && (context->vcm != INVALID_HANDLE_VALUE)) - WTSCloseServer((HANDLE)context->vcm); - context->vcm = NULL; - if (context->dynvcReady) { CloseHandle(context->dynvcReady); context->dynvcReady = NULL; } + + HashTable_Free(context->interceptContextMap); + + if (context->vcm && (context->vcm != INVALID_HANDLE_VALUE)) + WTSCloseServer((HANDLE)context->vcm); + context->vcm = NULL; } BOOL pf_context_init_server_context(freerdp_peer* client) diff --git a/server/proxy/pf_modules.c b/server/proxy/pf_modules.c index 75f74cb17..5e21c84a6 100644 --- a/server/proxy/pf_modules.c +++ b/server/proxy/pf_modules.c @@ -87,6 +87,8 @@ static const char* pf_modules_get_hook_type_string(PF_HOOK_TYPE result) 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: @@ -142,6 +144,10 @@ static BOOL pf_modules_proxy_ArrayList_ForEachFkt(void* data, size_t index, va_l 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; diff --git a/server/proxy/pf_server.c b/server/proxy/pf_server.c index 5b7c7f164..f74518a26 100644 --- a/server/proxy/pf_server.c +++ b/server/proxy/pf_server.c @@ -39,6 +39,8 @@ #include #include +#include + #include #include @@ -49,9 +51,16 @@ #include "pf_update.h" #include "proxy_modules.h" #include "pf_utils.h" +#include "channels/pf_channel_rdpdr.h" #define TAG PROXY_TAG("server") +typedef struct +{ + HANDLE thread; + freerdp_peer* client; +} peer_thread_args; + static BOOL pf_server_parse_target_from_routing_token(rdpContext* context, char** target, DWORD* port) { @@ -229,6 +238,18 @@ static BOOL pf_server_post_connect(freerdp_peer* peer) if (!pf_modules_run_hook(pdata->module, HOOK_TYPE_SERVER_POST_CONNECT, pdata, peer)) return FALSE; + switch (pf_utils_get_channel_mode(ps->pdata->config, RDPDR_SVC_CHANNEL_NAME)) + { + case PF_UTILS_CHANNEL_INTERCEPT: + if (!pf_channel_rdpdr_server_new(ps)) + return FALSE; + if (!pf_channel_rdpdr_server_announce(ps)) + return FALSE; + break; + default: + break; + } + /* Start a proxy's client in it's own thread */ if (!(pdata->client_thread = CreateThread(NULL, 0, pf_client_start, pc, 0, NULL))) { @@ -290,6 +311,22 @@ static BOOL pf_server_adjust_monitor_layout(freerdp_peer* peer) return TRUE; } +static BOOL pf_server_receive_channel_intercept(proxyData* pdata, UINT16 channelId, + const char* channel_name, const BYTE* data, + size_t size, UINT32 flags, size_t totalSize) +{ + WINPR_ASSERT(pdata); + WINPR_ASSERT(channel_name); + + if (strcmp(channel_name, RDPDR_SVC_CHANNEL_NAME) == 0) + return pf_channel_rdpdr_server_handle(pdata->ps, channelId, channel_name, data, size, flags, + totalSize); + + WLog_ERR(TAG, "[%s]: Channel %s [0x%04" PRIx16 "] intercept mode not implemented, aborting", + __FUNCTION__, channel_name, channelId); + return FALSE; +} + static BOOL pf_server_receive_channel_data_hook(freerdp_peer* peer, UINT16 channelId, const BYTE* data, size_t size, UINT32 flags, size_t totalSize) @@ -298,7 +335,7 @@ static BOOL pf_server_receive_channel_data_hook(freerdp_peer* peer, UINT16 chann pClientContext* pc; proxyData* pdata; const proxyConfig* config; - int pass; + pf_utils_channel_mode pass; const char* channel_name = WTSChannelGetName(peer, channelId); WINPR_ASSERT(peer); @@ -320,12 +357,12 @@ static BOOL pf_server_receive_channel_data_hook(freerdp_peer* peer, UINT16 chann if (!pc) goto original_cb; - pass = pf_utils_channel_is_passthrough(config, channel_name); + pass = pf_utils_get_channel_mode(config, channel_name); switch (pass) { - case 0: + case PF_UTILS_CHANNEL_BLOCK: return TRUE; - case 1: + case PF_UTILS_CHANNEL_PASSTHROUGH: { proxyChannelDataEventInfo ev; @@ -342,6 +379,9 @@ static BOOL pf_server_receive_channel_data_hook(freerdp_peer* peer, UINT16 chann return IFCALLRESULT(TRUE, pc->sendChannelData, pc, &ev); } + case PF_UTILS_CHANNEL_INTERCEPT: + return pf_server_receive_channel_intercept(pdata, channelId, channel_name, data, size, + flags, totalSize); default: break; } @@ -453,10 +493,14 @@ static DWORD WINAPI pf_server_handle_peer(LPVOID arg) DWORD status; pServerContext* ps = NULL; proxyData* pdata = NULL; - freerdp_peer* client = (freerdp_peer*)arg; + freerdp_peer* client; proxyServer* server; size_t count; + peer_thread_args* args = arg; + WINPR_ASSERT(args); + + client = args->client; WINPR_ASSERT(client); server = (proxyServer*)client->ContextExtra; @@ -599,13 +643,9 @@ out_free_peer: WaitForSingleObject(pdata->client_thread, INFINITE); } - /* If the server is shutting down the peer_list is already - * locked and we should not try to remove it here but instead - * let the ArrayList_Clear handle that. */ - if (WaitForSingleObject(server->stopEvent, 0) != WAIT_OBJECT_0) { ArrayList_Lock(server->peer_list); - ArrayList_Remove(server->peer_list, _GetCurrentThread()); + ArrayList_Remove(server->peer_list, args->thread); count = ArrayList_Count(server->peer_list); ArrayList_Unlock(server->peer_list); } @@ -617,6 +657,7 @@ out_free_peer: #if defined(WITH_DEBUG_EVENTS) DumpEventHandles(); #endif + free(args); ExitThread(0); return 0; } @@ -625,23 +666,28 @@ static BOOL pf_server_start_peer(freerdp_peer* client) { HANDLE hThread; proxyServer* server; + peer_thread_args* args = calloc(1, sizeof(peer_thread_args)); + if (!args) + return FALSE; WINPR_ASSERT(client); + args->client = client; server = (proxyServer*)client->ContextExtra; WINPR_ASSERT(server); - hThread = CreateThread(NULL, 0, pf_server_handle_peer, (void*)client, CREATE_SUSPENDED, NULL); + hThread = CreateThread(NULL, 0, pf_server_handle_peer, args, CREATE_SUSPENDED, NULL); if (!hThread) return FALSE; + args->thread = hThread; if (!ArrayList_Append(server->peer_list, hThread)) { CloseHandle(hThread); return FALSE; } - return ResumeThread(hThread) == 0; + return ResumeThread(hThread) != (DWORD)-1; } static BOOL pf_server_peer_accepted(freerdp_listener* listener, freerdp_peer* client) @@ -788,11 +834,6 @@ static BOOL are_all_required_modules_loaded(proxyModule* module, const proxyConf static void peer_free(void* obj) { HANDLE hdl = (HANDLE)obj; - - /* Threads have been notified about pending termination at this point. - */ - if (hdl != _GetCurrentThread()) - WaitForSingleObject(hdl, INFINITE); CloseHandle(hdl); } @@ -830,7 +871,7 @@ proxyServer* pf_server_new(const proxyConfig* config) if (!server->listener) goto out; - server->peer_list = ArrayList_New(TRUE); + server->peer_list = ArrayList_New(FALSE); if (!server->peer_list) goto out; @@ -925,6 +966,17 @@ void pf_server_free(proxyServer* server) pf_server_stop(server); + while (ArrayList_Count(server->peer_list) > 0) + { + /* pf_server_stop triggers the threads to shut down. + * loop here until all of them stopped. + * + * This must be done before ArrayList_Free otherwise the thread removal + * in pf_server_handle_peer will deadlock due to both threads trying to + * lock the list. + */ + Sleep(100); + } ArrayList_Free(server->peer_list); freerdp_listener_free(server->listener); diff --git a/server/proxy/pf_utils.c b/server/proxy/pf_utils.c index e76fdfe6c..9a6b5636b 100644 --- a/server/proxy/pf_utils.c +++ b/server/proxy/pf_utils.c @@ -22,16 +22,30 @@ #include #include +#include #include "pf_utils.h" -int pf_utils_channel_is_passthrough(const proxyConfig* config, const char* name) +#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; size_t i; BOOL found = FALSE; WINPR_ASSERT(config); WINPR_ASSERT(name); + for (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 (i = 0; i < config->PassthroughCount; i++) { const char* channel_name = config->Passthrough[i]; @@ -45,13 +59,16 @@ int pf_utils_channel_is_passthrough(const proxyConfig* config, const char* name) if (found) { if (config->PassthroughIsBlacklist) - return 0; - return 1; + rc = PF_UTILS_CHANNEL_BLOCK; + else + rc = PF_UTILS_CHANNEL_PASSTHROUGH; } + else if (config->PassthroughIsBlacklist) + rc = PF_UTILS_CHANNEL_PASSTHROUGH; - if (config->PassthroughIsBlacklist) - return 1; - return -1; +end: + WLog_DBG(TAG, "%s -> %s", name, pf_utils_channel_mode_string(rc)); + return rc; } BOOL pf_utils_is_passthrough(const proxyConfig* config) @@ -61,3 +78,19 @@ BOOL pf_utils_is_passthrough(const proxyConfig* config) /* TODO: For the time being only passthrough mode is supported. */ return TRUE; } + +const char* pf_utils_channel_mode_string(pf_utils_channel_mode mode) +{ + switch (mode) + { + case PF_UTILS_CHANNEL_BLOCK: + return "blocked"; + case PF_UTILS_CHANNEL_PASSTHROUGH: + return "passthrough"; + case PF_UTILS_CHANNEL_INTERCEPT: + return "intercepted"; + case PF_UTILS_CHANNEL_NOT_HANDLED: + default: + return "ignored"; + } +} diff --git a/server/proxy/pf_utils.h b/server/proxy/pf_utils.h index b54d81c4f..0d03b3c91 100644 --- a/server/proxy/pf_utils.h +++ b/server/proxy/pf_utils.h @@ -30,9 +30,20 @@ * @param config The proxy configuration to check against. Must NOT be NULL. * @param name The name of the channel. Must NOT be NULL. * @return -1 if the channel is not handled, 0 if the channel should be ignored, - * 1 if the channel should be passed. + * 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. */ -int pf_utils_channel_is_passthrough(const proxyConfig* config, const char* name); +typedef enum +{ + PF_UTILS_CHANNEL_NOT_HANDLED, + PF_UTILS_CHANNEL_BLOCK, + PF_UTILS_CHANNEL_PASSTHROUGH, + PF_UTILS_CHANNEL_INTERCEPT, +} pf_utils_channel_mode; + +pf_utils_channel_mode pf_utils_get_channel_mode(const proxyConfig* config, const char* name); +const char* pf_utils_channel_mode_string(pf_utils_channel_mode mode); BOOL pf_utils_is_passthrough(const proxyConfig* config); diff --git a/server/proxy/proxy_modules.h b/server/proxy/proxy_modules.h index 38aab1b38..ad43ff5bf 100644 --- a/server/proxy/proxy_modules.h +++ b/server/proxy/proxy_modules.h @@ -48,6 +48,7 @@ enum _PF_HOOK_TYPE 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,