diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
commit | a9bcc81f821d7c66f623779fa5147e728eb3c388 (patch) | |
tree | 98676963bcdd537ae5908a067a8eb110b93486a6 /server/proxy/channels/pf_channel_rdpdr.c | |
parent | Initial commit. (diff) | |
download | freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.tar.xz freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.zip |
Adding upstream version 3.3.0+dfsg1.upstream/3.3.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'server/proxy/channels/pf_channel_rdpdr.c')
-rw-r--r-- | server/proxy/channels/pf_channel_rdpdr.c | 2017 |
1 files changed, 2017 insertions, 0 deletions
diff --git a/server/proxy/channels/pf_channel_rdpdr.c b/server/proxy/channels/pf_channel_rdpdr.c new file mode 100644 index 0000000..cb79266 --- /dev/null +++ b/server/proxy/channels/pf_channel_rdpdr.c @@ -0,0 +1,2017 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Proxy Server + * + * Copyright 2021 Armin Novak <armin.novak@thincast.com> + * Copyright 2021 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <winpr/assert.h> +#include <winpr/string.h> +#include <winpr/print.h> + +#include "pf_channel_rdpdr.h" +#include "pf_channel_smartcard.h" + +#include <freerdp/server/proxy/proxy_log.h> +#include <freerdp/channels/rdpdr.h> +#include <freerdp/channels/channels.h> +#include <freerdp/utils/rdpdr_utils.h> + +#define RTAG PROXY_TAG("channel.rdpdr") + +#define SCARD_DEVICE_ID UINT32_MAX + +typedef struct +{ + InterceptContextMapEntry base; + wStream* s; + wStream* buffer; + UINT16 versionMajor; + UINT16 versionMinor; + UINT32 clientID; + UINT32 computerNameLen; + BOOL computerNameUnicode; + union + { + WCHAR* wc; + char* c; + void* v; + } computerName; + UINT32 SpecialDeviceCount; + UINT32 capabilityVersions[6]; +} pf_channel_common_context; + +typedef enum +{ + STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST = 0x01, + STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST = 0x02, + STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM = 0x04, + STATE_CLIENT_CHANNEL_RUNNING = 0x10 +} pf_channel_client_state; + +typedef struct +{ + pf_channel_common_context common; + pf_channel_client_state state; + UINT32 flags; + UINT16 maxMajorVersion; + UINT16 maxMinorVersion; + wQueue* queue; + wLog* log; +} pf_channel_client_context; + +typedef enum +{ + STATE_SERVER_INITIAL, + STATE_SERVER_EXPECT_CLIENT_ANNOUNCE_REPLY, + STATE_SERVER_EXPECT_CLIENT_NAME_REQUEST, + STATE_SERVER_EXPECT_EXPECT_CLIENT_CAPABILITY_RESPONE, + STATE_SERVER_CHANNEL_RUNNING +} pf_channel_server_state; + +typedef struct +{ + pf_channel_common_context common; + pf_channel_server_state state; + DWORD SessionId; + HANDLE handle; + wArrayList* blockedDevices; + wLog* log; +} pf_channel_server_context; + +#define proxy_client "[proxy<-->client]" +#define proxy_server "[proxy<-->server]" + +#define proxy_client_rx proxy_client " receive" +#define proxy_client_tx proxy_client " send" +#define proxy_server_rx proxy_server " receive" +#define proxy_server_tx proxy_server " send" + +#define SERVER_RX_LOG(log, lvl, fmt, ...) WLog_Print(log, lvl, proxy_client_rx fmt, ##__VA_ARGS__) +#define CLIENT_RX_LOG(log, lvl, fmt, ...) WLog_Print(log, lvl, proxy_server_rx fmt, ##__VA_ARGS__) +#define SERVER_TX_LOG(log, lvl, fmt, ...) WLog_Print(log, lvl, proxy_client_tx fmt, ##__VA_ARGS__) +#define CLIENT_TX_LOG(log, lvl, fmt, ...) WLog_Print(log, lvl, proxy_server_tx fmt, ##__VA_ARGS__) +#define RX_LOG(srv, lvl, fmt, ...) \ + do \ + { \ + if (srv) \ + { \ + SERVER_RX_LOG(lvl, fmt, ##__VA_ARGS__); \ + } \ + else \ + { \ + CLIENT_RX_LOG(lvl, fmt, ##__VA_ARGS__); \ + } \ + } while (0) + +#define SERVER_RXTX_LOG(send, log, lvl, fmt, ...) \ + do \ + { \ + if (send) \ + { \ + SERVER_TX_LOG(log, lvl, fmt, ##__VA_ARGS__); \ + } \ + else \ + { \ + SERVER_RX_LOG(log, lvl, fmt, ##__VA_ARGS__); \ + } \ + } while (0) + +#define Stream_CheckAndLogRequiredLengthSrv(log, s, len) \ + Stream_CheckAndLogRequiredLengthWLogEx(log, WLOG_WARN, s, len, 1, \ + proxy_client_rx " %s(%s:%" PRIuz ")", __func__, \ + __FILE__, (size_t)__LINE__) +#define Stream_CheckAndLogRequiredLengthClient(log, s, len) \ + Stream_CheckAndLogRequiredLengthWLogEx(log, WLOG_WARN, s, len, 1, \ + proxy_server_rx " %s(%s:%" PRIuz ")", __func__, \ + __FILE__, (size_t)__LINE__) +#define Stream_CheckAndLogRequiredLengthRx(srv, log, s, len) \ + Stream_CheckAndLogRequiredLengthRx_(srv, log, s, len, 1, __func__, __FILE__, __LINE__) +static BOOL Stream_CheckAndLogRequiredLengthRx_(BOOL srv, wLog* log, wStream* s, size_t nmemb, + size_t size, const char* fkt, const char* file, + size_t line) +{ + const char* fmt = + srv ? proxy_server_rx " %s(%s:%" PRIuz ")" : proxy_client_rx " %s(%s:%" PRIuz ")"; + + return Stream_CheckAndLogRequiredLengthWLogEx(log, WLOG_WARN, s, nmemb, size, fmt, fkt, file, + line); +} + +static const char* rdpdr_server_state_to_string(pf_channel_server_state state) +{ + switch (state) + { + case STATE_SERVER_INITIAL: + return "STATE_SERVER_INITIAL"; + case STATE_SERVER_EXPECT_CLIENT_ANNOUNCE_REPLY: + return "STATE_SERVER_EXPECT_CLIENT_ANNOUNCE_REPLY"; + case STATE_SERVER_EXPECT_CLIENT_NAME_REQUEST: + return "STATE_SERVER_EXPECT_CLIENT_NAME_REQUEST"; + case STATE_SERVER_EXPECT_EXPECT_CLIENT_CAPABILITY_RESPONE: + return "STATE_SERVER_EXPECT_EXPECT_CLIENT_CAPABILITY_RESPONE"; + case STATE_SERVER_CHANNEL_RUNNING: + return "STATE_SERVER_CHANNEL_RUNNING"; + default: + return "STATE_SERVER_UNKNOWN"; + } +} + +static const char* rdpdr_client_state_to_string(pf_channel_client_state state) +{ + switch (state) + { + case STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST: + return "STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST"; + case STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST: + return "STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST"; + case STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM: + return "STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM"; + case STATE_CLIENT_CHANNEL_RUNNING: + return "STATE_CLIENT_CHANNEL_RUNNING"; + default: + return "STATE_CLIENT_UNKNOWN"; + } +} + +static wStream* rdpdr_get_send_buffer(pf_channel_common_context* rdpdr, UINT16 component, + UINT16 PacketID, size_t capacity) +{ + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(rdpdr->s); + if (!Stream_SetPosition(rdpdr->s, 0)) + return NULL; + if (!Stream_EnsureCapacity(rdpdr->s, capacity + 4)) + return NULL; + Stream_Write_UINT16(rdpdr->s, component); + Stream_Write_UINT16(rdpdr->s, PacketID); + return rdpdr->s; +} + +static wStream* rdpdr_client_get_send_buffer(pf_channel_client_context* rdpdr, UINT16 component, + UINT16 PacketID, size_t capacity) +{ + WINPR_ASSERT(rdpdr); + return rdpdr_get_send_buffer(&rdpdr->common, component, PacketID, capacity); +} + +static wStream* rdpdr_server_get_send_buffer(pf_channel_server_context* rdpdr, UINT16 component, + UINT16 PacketID, size_t capacity) +{ + WINPR_ASSERT(rdpdr); + return rdpdr_get_send_buffer(&rdpdr->common, component, PacketID, capacity); +} + +static UINT rdpdr_client_send(wLog* log, pClientContext* pc, wStream* s) +{ + UINT16 channelId = 0; + + WINPR_ASSERT(log); + WINPR_ASSERT(pc); + WINPR_ASSERT(s); + WINPR_ASSERT(pc->context.instance); + + if (!pc->connected) + { + CLIENT_TX_LOG(log, WLOG_WARN, "Ignoring channel %s message, not connected!", + RDPDR_SVC_CHANNEL_NAME); + return CHANNEL_RC_OK; + } + + channelId = freerdp_channels_get_id_by_name(pc->context.instance, RDPDR_SVC_CHANNEL_NAME); + /* Ignore unmappable channels. Might happen when the channel was already down and + * some delayed message is tried to be sent. */ + if ((channelId == 0) || (channelId == UINT16_MAX)) + return ERROR_INTERNAL_ERROR; + + Stream_SealLength(s); + rdpdr_dump_send_packet(log, WLOG_TRACE, s, proxy_server_tx); + WINPR_ASSERT(pc->context.instance->SendChannelData); + if (!pc->context.instance->SendChannelData(pc->context.instance, channelId, Stream_Buffer(s), + Stream_Length(s))) + return ERROR_EVT_CHANNEL_NOT_FOUND; + return CHANNEL_RC_OK; +} + +static UINT rdpdr_seal_send_free_request(pf_channel_server_context* context, wStream* s) +{ + BOOL status = 0; + size_t len = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->handle); + WINPR_ASSERT(s); + + Stream_SealLength(s); + len = Stream_Length(s); + WINPR_ASSERT(len <= ULONG_MAX); + + rdpdr_dump_send_packet(context->log, WLOG_TRACE, s, proxy_client_tx); + status = WTSVirtualChannelWrite(context->handle, (char*)Stream_Buffer(s), (ULONG)len, NULL); + return (status) ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +} + +static BOOL rdpdr_process_server_header(BOOL server, wLog* log, wStream* s, UINT16 component, + UINT16 PacketId, size_t expect) +{ + UINT16 rpacketid = 0; + UINT16 rcomponent = 0; + + WINPR_ASSERT(s); + if (!Stream_CheckAndLogRequiredLengthRx(server, log, s, 4)) + { + RX_LOG(server, log, WLOG_WARN, "RDPDR_HEADER[%s | %s]: expected length 4, got %" PRIuz, + rdpdr_component_string(component), rdpdr_packetid_string(PacketId), + Stream_GetRemainingLength(s)); + return FALSE; + } + + Stream_Read_UINT16(s, rcomponent); + Stream_Read_UINT16(s, rpacketid); + + if (rcomponent != component) + { + RX_LOG(server, log, WLOG_WARN, "RDPDR_HEADER[%s | %s]: got component %s", + rdpdr_component_string(component), rdpdr_packetid_string(PacketId), + rdpdr_component_string(rcomponent)); + return FALSE; + } + + if (rpacketid != PacketId) + { + RX_LOG(server, log, WLOG_WARN, "RDPDR_HEADER[%s | %s]: got PacketID %s", + rdpdr_component_string(component), rdpdr_packetid_string(PacketId), + rdpdr_packetid_string(rpacketid)); + return FALSE; + } + + if (!Stream_CheckAndLogRequiredLengthRx(server, log, s, expect)) + { + RX_LOG(server, log, WLOG_WARN, + "RDPDR_HEADER[%s | %s] not enought data, expected %" PRIuz ", " + "got %" PRIuz, + rdpdr_component_string(component), rdpdr_packetid_string(PacketId), expect, + Stream_GetRemainingLength(s)); + return ERROR_INVALID_DATA; + } + + return TRUE; +} + +static BOOL rdpdr_check_version(BOOL server, wLog* log, UINT16 versionMajor, UINT16 versionMinor, + UINT16 component, UINT16 PacketId) +{ + if (versionMajor != RDPDR_VERSION_MAJOR) + { + RX_LOG(server, log, WLOG_WARN, "[%s | %s] expected MajorVersion %" PRIu16 ", got %" PRIu16, + rdpdr_component_string(component), rdpdr_packetid_string(PacketId), + RDPDR_VERSION_MAJOR, versionMajor); + return FALSE; + } + switch (versionMinor) + { + case RDPDR_VERSION_MINOR_RDP50: + case RDPDR_VERSION_MINOR_RDP51: + case RDPDR_VERSION_MINOR_RDP52: + case RDPDR_VERSION_MINOR_RDP6X: + case RDPDR_VERSION_MINOR_RDP10X: + break; + default: + { + RX_LOG(server, log, WLOG_WARN, "[%s | %s] unsupported MinorVersion %" PRIu16, + rdpdr_component_string(component), rdpdr_packetid_string(PacketId), + versionMinor); + return FALSE; + } + } + return TRUE; +} + +static UINT rdpdr_process_server_announce_request(pf_channel_client_context* rdpdr, wStream* s) +{ + const UINT16 component = RDPDR_CTYP_CORE; + const UINT16 packetid = PAKID_CORE_SERVER_ANNOUNCE; + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + if (!rdpdr_process_server_header(FALSE, rdpdr->log, s, component, packetid, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, rdpdr->common.versionMajor); + Stream_Read_UINT16(s, rdpdr->common.versionMinor); + + if (!rdpdr_check_version(FALSE, rdpdr->log, rdpdr->common.versionMajor, + rdpdr->common.versionMinor, component, packetid)) + return ERROR_INVALID_DATA; + + /* Limit maximum channel protocol version to the one set by proxy server */ + if (rdpdr->common.versionMajor > rdpdr->maxMajorVersion) + { + rdpdr->common.versionMajor = rdpdr->maxMajorVersion; + rdpdr->common.versionMinor = rdpdr->maxMinorVersion; + } + else if (rdpdr->common.versionMinor > rdpdr->maxMinorVersion) + rdpdr->common.versionMinor = rdpdr->maxMinorVersion; + + Stream_Read_UINT32(s, rdpdr->common.clientID); + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_send_announce_request(pf_channel_server_context* context) +{ + wStream* s = + rdpdr_server_get_send_buffer(context, RDPDR_CTYP_CORE, PAKID_CORE_SERVER_ANNOUNCE, 8); + if (!s) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write_UINT16(s, context->common.versionMajor); /* VersionMajor (2 bytes) */ + Stream_Write_UINT16(s, context->common.versionMinor); /* VersionMinor (2 bytes) */ + Stream_Write_UINT32(s, context->common.clientID); /* ClientId (4 bytes) */ + return rdpdr_seal_send_free_request(context, s); +} + +static UINT rdpdr_process_client_announce_reply(pf_channel_server_context* rdpdr, wStream* s) +{ + const UINT16 component = RDPDR_CTYP_CORE; + const UINT16 packetid = PAKID_CORE_CLIENTID_CONFIRM; + UINT16 versionMajor = 0; + UINT16 versionMinor = 0; + UINT32 clientID = 0; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + if (!rdpdr_process_server_header(TRUE, rdpdr->log, s, component, packetid, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, versionMajor); + Stream_Read_UINT16(s, versionMinor); + + if (!rdpdr_check_version(TRUE, rdpdr->log, versionMajor, versionMinor, component, packetid)) + return ERROR_INVALID_DATA; + + if ((rdpdr->common.versionMajor != versionMajor) || + (rdpdr->common.versionMinor != versionMinor)) + { + SERVER_RX_LOG( + rdpdr->log, WLOG_WARN, + "[%s | %s] downgrading version from %" PRIu16 ".%" PRIu16 " to %" PRIu16 ".%" PRIu16, + rdpdr_component_string(component), rdpdr_packetid_string(packetid), + rdpdr->common.versionMajor, rdpdr->common.versionMinor, versionMajor, versionMinor); + rdpdr->common.versionMajor = versionMajor; + rdpdr->common.versionMinor = versionMinor; + } + Stream_Read_UINT32(s, clientID); + if (rdpdr->common.clientID != clientID) + { + SERVER_RX_LOG(rdpdr->log, WLOG_WARN, + "[%s | %s] changing clientID 0x%08" PRIu32 " to 0x%08" PRIu32, + rdpdr_component_string(component), rdpdr_packetid_string(packetid), + rdpdr->common.clientID, clientID); + rdpdr->common.clientID = clientID; + } + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_send_client_announce_reply(pClientContext* pc, pf_channel_client_context* rdpdr) +{ + wStream* s = + rdpdr_client_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_CLIENTID_CONFIRM, 8); + if (!s) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write_UINT16(s, rdpdr->common.versionMajor); + Stream_Write_UINT16(s, rdpdr->common.versionMinor); + Stream_Write_UINT32(s, rdpdr->common.clientID); + return rdpdr_client_send(rdpdr->log, pc, s); +} + +static UINT rdpdr_process_client_name_request(pf_channel_server_context* rdpdr, wStream* s, + pClientContext* pc) +{ + UINT32 unicodeFlag = 0; + UINT32 codePage = 0; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + WINPR_ASSERT(pc); + + if (!rdpdr_process_server_header(TRUE, rdpdr->log, s, RDPDR_CTYP_CORE, PAKID_CORE_CLIENT_NAME, + 12)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, unicodeFlag); + rdpdr->common.computerNameUnicode = (unicodeFlag & 1); + + Stream_Read_UINT32(s, codePage); + WINPR_UNUSED(codePage); /* Field is ignored */ + Stream_Read_UINT32(s, rdpdr->common.computerNameLen); + if (!Stream_CheckAndLogRequiredLengthSrv(rdpdr->log, s, rdpdr->common.computerNameLen)) + { + SERVER_RX_LOG( + rdpdr->log, WLOG_WARN, "[%s | %s]: missing data, got %" PRIu32 ", expected %" PRIu32, + rdpdr_component_string(RDPDR_CTYP_CORE), rdpdr_packetid_string(PAKID_CORE_CLIENT_NAME), + Stream_GetRemainingLength(s), rdpdr->common.computerNameLen); + return ERROR_INVALID_DATA; + } + void* tmp = realloc(rdpdr->common.computerName.v, rdpdr->common.computerNameLen); + if (!tmp) + return CHANNEL_RC_NO_MEMORY; + rdpdr->common.computerName.v = tmp; + + Stream_Read(s, rdpdr->common.computerName.v, rdpdr->common.computerNameLen); + + pc->computerNameLen = rdpdr->common.computerNameLen; + pc->computerNameUnicode = rdpdr->common.computerNameUnicode; + tmp = realloc(pc->computerName.v, pc->computerNameLen); + if (!tmp) + return CHANNEL_RC_NO_MEMORY; + pc->computerName.v = tmp; + memcpy(pc->computerName.v, rdpdr->common.computerName.v, pc->computerNameLen); + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_send_client_name_request(pClientContext* pc, pf_channel_client_context* rdpdr) +{ + wStream* s = NULL; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(pc); + + { + void* tmp = realloc(rdpdr->common.computerName.v, pc->computerNameLen); + if (!tmp) + return CHANNEL_RC_NO_MEMORY; + rdpdr->common.computerName.v = tmp; + rdpdr->common.computerNameLen = pc->computerNameLen; + rdpdr->common.computerNameUnicode = pc->computerNameUnicode; + memcpy(rdpdr->common.computerName.v, pc->computerName.v, pc->computerNameLen); + } + s = rdpdr_client_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_CLIENT_NAME, + 12U + rdpdr->common.computerNameLen); + if (!s) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write_UINT32(s, rdpdr->common.computerNameUnicode + ? 1 + : 0); /* unicodeFlag, 0 for ASCII and 1 for Unicode */ + Stream_Write_UINT32(s, 0); /* codePage, must be set to zero */ + Stream_Write_UINT32(s, rdpdr->common.computerNameLen); + Stream_Write(s, rdpdr->common.computerName.v, rdpdr->common.computerNameLen); + return rdpdr_client_send(rdpdr->log, pc, s); +} + +#define rdpdr_ignore_capset(srv, log, s, header) \ + rdpdr_ignore_capset_((srv), (log), (s), header, __func__) +static UINT rdpdr_ignore_capset_(BOOL srv, wLog* log, wStream* s, + const RDPDR_CAPABILITY_HEADER* header, const char* fkt) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(header); + + Stream_Seek(s, header->CapabilityLength); + return CHANNEL_RC_OK; +} + +static UINT rdpdr_client_process_general_capset(pf_channel_client_context* rdpdr, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + WINPR_UNUSED(rdpdr); + return rdpdr_ignore_capset(FALSE, rdpdr->log, s, header); +} + +static UINT rdpdr_process_printer_capset(pf_channel_client_context* rdpdr, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + WINPR_UNUSED(rdpdr); + return rdpdr_ignore_capset(FALSE, rdpdr->log, s, header); +} + +static UINT rdpdr_process_port_capset(pf_channel_client_context* rdpdr, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + WINPR_UNUSED(rdpdr); + return rdpdr_ignore_capset(FALSE, rdpdr->log, s, header); +} + +static UINT rdpdr_process_drive_capset(pf_channel_client_context* rdpdr, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + WINPR_UNUSED(rdpdr); + return rdpdr_ignore_capset(FALSE, rdpdr->log, s, header); +} + +static UINT rdpdr_process_smartcard_capset(pf_channel_client_context* rdpdr, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + WINPR_UNUSED(rdpdr); + return rdpdr_ignore_capset(FALSE, rdpdr->log, s, header); +} + +static UINT rdpdr_process_server_core_capability_request(pf_channel_client_context* rdpdr, + wStream* s) +{ + UINT status = CHANNEL_RC_OK; + UINT16 numCapabilities = 0; + + WINPR_ASSERT(rdpdr); + + if (!rdpdr_process_server_header(FALSE, rdpdr->log, s, RDPDR_CTYP_CORE, + PAKID_CORE_SERVER_CAPABILITY, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, numCapabilities); + Stream_Seek(s, 2); /* pad (2 bytes) */ + + for (UINT16 i = 0; i < numCapabilities; i++) + { + RDPDR_CAPABILITY_HEADER header = { 0 }; + UINT error = rdpdr_read_capset_header(rdpdr->log, s, &header); + if (error != CHANNEL_RC_OK) + return error; + + if (header.CapabilityType < ARRAYSIZE(rdpdr->common.capabilityVersions)) + { + if (rdpdr->common.capabilityVersions[header.CapabilityType] > header.Version) + rdpdr->common.capabilityVersions[header.CapabilityType] = header.Version; + + WLog_Print(rdpdr->log, WLOG_TRACE, + "capability %s got version %" PRIu32 ", will use version %" PRIu32, + rdpdr_cap_type_string(header.CapabilityType), header.Version, + rdpdr->common.capabilityVersions[header.CapabilityType]); + } + + switch (header.CapabilityType) + { + case CAP_GENERAL_TYPE: + status = rdpdr_client_process_general_capset(rdpdr, s, &header); + break; + + case CAP_PRINTER_TYPE: + status = rdpdr_process_printer_capset(rdpdr, s, &header); + break; + + case CAP_PORT_TYPE: + status = rdpdr_process_port_capset(rdpdr, s, &header); + break; + + case CAP_DRIVE_TYPE: + status = rdpdr_process_drive_capset(rdpdr, s, &header); + break; + + case CAP_SMARTCARD_TYPE: + status = rdpdr_process_smartcard_capset(rdpdr, s, &header); + break; + + default: + WLog_Print(rdpdr->log, WLOG_WARN, + "unknown capability 0x%04" PRIx16 ", length %" PRIu16 + ", version %" PRIu32, + header.CapabilityType, header.CapabilityLength, header.Version); + Stream_Seek(s, header.CapabilityLength); + break; + } + + if (status != CHANNEL_RC_OK) + return status; + } + + return CHANNEL_RC_OK; +} + +static BOOL rdpdr_write_general_capset(wLog* log, pf_channel_common_context* rdpdr, wStream* s) +{ + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + const RDPDR_CAPABILITY_HEADER header = { CAP_GENERAL_TYPE, 44, + rdpdr->capabilityVersions[CAP_GENERAL_TYPE] }; + if (rdpdr_write_capset_header(log, s, &header) != CHANNEL_RC_OK) + return FALSE; + Stream_Write_UINT32(s, 0); /* osType, ignored on receipt */ + Stream_Write_UINT32(s, 0); /* osVersion, should be ignored */ + Stream_Write_UINT16(s, rdpdr->versionMajor); /* protocolMajorVersion, must be set to 1 */ + Stream_Write_UINT16(s, rdpdr->versionMinor); /* protocolMinorVersion */ + Stream_Write_UINT32(s, 0x0000FFFF); /* ioCode1 */ + Stream_Write_UINT32(s, 0); /* ioCode2, must be set to zero, reserved for future use */ + Stream_Write_UINT32(s, RDPDR_DEVICE_REMOVE_PDUS | RDPDR_CLIENT_DISPLAY_NAME_PDU | + RDPDR_USER_LOGGEDON_PDU); /* extendedPDU */ + Stream_Write_UINT32(s, ENABLE_ASYNCIO); /* extraFlags1 */ + Stream_Write_UINT32(s, 0); /* extraFlags2, must be set to zero, reserved for future use */ + Stream_Write_UINT32(s, rdpdr->SpecialDeviceCount); /* SpecialTypeDeviceCap, number of special + devices to be redirected before logon */ + return TRUE; +} + +static BOOL rdpdr_write_printer_capset(wLog* log, pf_channel_common_context* rdpdr, wStream* s) +{ + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + const RDPDR_CAPABILITY_HEADER header = { CAP_PRINTER_TYPE, 8, + rdpdr->capabilityVersions[CAP_PRINTER_TYPE] }; + if (rdpdr_write_capset_header(log, s, &header) != CHANNEL_RC_OK) + return FALSE; + return TRUE; +} + +static BOOL rdpdr_write_port_capset(wLog* log, pf_channel_common_context* rdpdr, wStream* s) +{ + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + const RDPDR_CAPABILITY_HEADER header = { CAP_PORT_TYPE, 8, + rdpdr->capabilityVersions[CAP_PORT_TYPE] }; + if (rdpdr_write_capset_header(log, s, &header) != CHANNEL_RC_OK) + return FALSE; + return TRUE; +} + +static BOOL rdpdr_write_drive_capset(wLog* log, pf_channel_common_context* rdpdr, wStream* s) +{ + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + const RDPDR_CAPABILITY_HEADER header = { CAP_DRIVE_TYPE, 8, + rdpdr->capabilityVersions[CAP_DRIVE_TYPE] }; + if (rdpdr_write_capset_header(log, s, &header) != CHANNEL_RC_OK) + return FALSE; + return TRUE; +} + +static BOOL rdpdr_write_smartcard_capset(wLog* log, pf_channel_common_context* rdpdr, wStream* s) +{ + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + const RDPDR_CAPABILITY_HEADER header = { CAP_SMARTCARD_TYPE, 8, + rdpdr->capabilityVersions[CAP_SMARTCARD_TYPE] }; + if (rdpdr_write_capset_header(log, s, &header) != CHANNEL_RC_OK) + return FALSE; + return TRUE; +} + +static UINT rdpdr_send_server_capability_request(pf_channel_server_context* rdpdr) +{ + wStream* s = + rdpdr_server_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_SERVER_CAPABILITY, 8); + if (!s) + return CHANNEL_RC_NO_MEMORY; + Stream_Write_UINT16(s, 5); /* numCapabilities */ + Stream_Write_UINT16(s, 0); /* pad */ + if (!rdpdr_write_general_capset(rdpdr->log, &rdpdr->common, s)) + return CHANNEL_RC_NO_MEMORY; + if (!rdpdr_write_printer_capset(rdpdr->log, &rdpdr->common, s)) + return CHANNEL_RC_NO_MEMORY; + if (!rdpdr_write_port_capset(rdpdr->log, &rdpdr->common, s)) + return CHANNEL_RC_NO_MEMORY; + if (!rdpdr_write_drive_capset(rdpdr->log, &rdpdr->common, s)) + return CHANNEL_RC_NO_MEMORY; + if (!rdpdr_write_smartcard_capset(rdpdr->log, &rdpdr->common, s)) + return CHANNEL_RC_NO_MEMORY; + return rdpdr_seal_send_free_request(rdpdr, s); +} + +static UINT rdpdr_process_client_capability_response(pf_channel_server_context* rdpdr, wStream* s) +{ + const UINT16 component = RDPDR_CTYP_CORE; + const UINT16 packetid = PAKID_CORE_CLIENT_CAPABILITY; + UINT status = CHANNEL_RC_OK; + UINT16 numCapabilities = 0; + WINPR_ASSERT(rdpdr); + + if (!rdpdr_process_server_header(TRUE, rdpdr->log, s, component, packetid, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, numCapabilities); + Stream_Seek_UINT16(s); /* padding */ + + for (UINT16 x = 0; x < numCapabilities; x++) + { + RDPDR_CAPABILITY_HEADER header = { 0 }; + UINT error = rdpdr_read_capset_header(rdpdr->log, s, &header); + if (error != CHANNEL_RC_OK) + return error; + if (header.CapabilityType < ARRAYSIZE(rdpdr->common.capabilityVersions)) + { + if (rdpdr->common.capabilityVersions[header.CapabilityType] > header.Version) + rdpdr->common.capabilityVersions[header.CapabilityType] = header.Version; + + WLog_Print(rdpdr->log, WLOG_TRACE, + "capability %s got version %" PRIu32 ", will use version %" PRIu32, + rdpdr_cap_type_string(header.CapabilityType), header.Version, + rdpdr->common.capabilityVersions[header.CapabilityType]); + } + + switch (header.CapabilityType) + { + case CAP_GENERAL_TYPE: + status = rdpdr_ignore_capset(TRUE, rdpdr->log, s, &header); + break; + + case CAP_PRINTER_TYPE: + status = rdpdr_ignore_capset(TRUE, rdpdr->log, s, &header); + break; + + case CAP_PORT_TYPE: + status = rdpdr_ignore_capset(TRUE, rdpdr->log, s, &header); + break; + + case CAP_DRIVE_TYPE: + status = rdpdr_ignore_capset(TRUE, rdpdr->log, s, &header); + break; + + case CAP_SMARTCARD_TYPE: + status = rdpdr_ignore_capset(TRUE, rdpdr->log, s, &header); + break; + + default: + SERVER_RX_LOG(rdpdr->log, WLOG_WARN, + "[%s | %s] invalid capability type 0x%04" PRIx16, + rdpdr_component_string(component), rdpdr_packetid_string(packetid), + header.CapabilityType); + status = ERROR_INVALID_DATA; + break; + } + + if (status != CHANNEL_RC_OK) + break; + } + + return status; +} + +static UINT rdpdr_send_client_capability_response(pClientContext* pc, + pf_channel_client_context* rdpdr) +{ + wStream* s = NULL; + + WINPR_ASSERT(rdpdr); + s = rdpdr_client_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_CLIENT_CAPABILITY, 4); + if (!s) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write_UINT16(s, 5); /* numCapabilities */ + Stream_Write_UINT16(s, 0); /* pad */ + if (!rdpdr_write_general_capset(rdpdr->log, &rdpdr->common, s)) + return CHANNEL_RC_NO_MEMORY; + if (!rdpdr_write_printer_capset(rdpdr->log, &rdpdr->common, s)) + return CHANNEL_RC_NO_MEMORY; + if (!rdpdr_write_port_capset(rdpdr->log, &rdpdr->common, s)) + return CHANNEL_RC_NO_MEMORY; + if (!rdpdr_write_drive_capset(rdpdr->log, &rdpdr->common, s)) + return CHANNEL_RC_NO_MEMORY; + if (!rdpdr_write_smartcard_capset(rdpdr->log, &rdpdr->common, s)) + return CHANNEL_RC_NO_MEMORY; + return rdpdr_client_send(rdpdr->log, pc, s); +} + +static UINT rdpdr_send_server_clientid_confirm(pf_channel_server_context* rdpdr) +{ + wStream* s = NULL; + + s = rdpdr_server_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_CLIENTID_CONFIRM, 8); + if (!s) + return CHANNEL_RC_NO_MEMORY; + Stream_Write_UINT16(s, rdpdr->common.versionMajor); + Stream_Write_UINT16(s, rdpdr->common.versionMinor); + Stream_Write_UINT32(s, rdpdr->common.clientID); + return rdpdr_seal_send_free_request(rdpdr, s); +} + +static UINT rdpdr_process_server_clientid_confirm(pf_channel_client_context* rdpdr, wStream* s) +{ + UINT16 versionMajor = 0; + UINT16 versionMinor = 0; + UINT32 clientID = 0; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + if (!rdpdr_process_server_header(FALSE, rdpdr->log, s, RDPDR_CTYP_CORE, + PAKID_CORE_CLIENTID_CONFIRM, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, versionMajor); + Stream_Read_UINT16(s, versionMinor); + if (!rdpdr_check_version(FALSE, rdpdr->log, versionMajor, versionMinor, RDPDR_CTYP_CORE, + PAKID_CORE_CLIENTID_CONFIRM)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, clientID); + + if ((versionMajor != rdpdr->common.versionMajor) || + (versionMinor != rdpdr->common.versionMinor)) + { + CLIENT_RX_LOG(rdpdr->log, WLOG_WARN, + "[%s | %s] Version mismatch, sent %" PRIu16 ".%" PRIu16 + ", downgraded to %" PRIu16 ".%" PRIu16, + rdpdr_component_string(RDPDR_CTYP_CORE), + rdpdr_packetid_string(PAKID_CORE_CLIENTID_CONFIRM), + rdpdr->common.versionMajor, rdpdr->common.versionMinor, versionMajor, + versionMinor); + rdpdr->common.versionMajor = versionMajor; + rdpdr->common.versionMinor = versionMinor; + } + + if (clientID != rdpdr->common.clientID) + { + CLIENT_RX_LOG(rdpdr->log, WLOG_WARN, + "[%s | %s] clientID mismatch, sent 0x%08" PRIx32 ", changed to 0x%08" PRIx32, + rdpdr_component_string(RDPDR_CTYP_CORE), + rdpdr_packetid_string(PAKID_CORE_CLIENTID_CONFIRM), rdpdr->common.clientID, + clientID); + rdpdr->common.clientID = clientID; + } + + return CHANNEL_RC_OK; +} + +static BOOL +rdpdr_process_server_capability_request_or_clientid_confirm(pf_channel_client_context* rdpdr, + wStream* s) +{ + const UINT32 mask = STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM | + STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST; + const UINT16 rcomponent = RDPDR_CTYP_CORE; + UINT16 component = 0; + UINT16 packetid = 0; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + if ((rdpdr->flags & mask) == mask) + { + CLIENT_RX_LOG(rdpdr->log, WLOG_WARN, "already past this state, abort!"); + return FALSE; + } + + if (!Stream_CheckAndLogRequiredLengthClient(rdpdr->log, s, 4)) + return FALSE; + + Stream_Read_UINT16(s, component); + if (rcomponent != component) + { + CLIENT_RX_LOG(rdpdr->log, WLOG_WARN, "got component %s, expected %s", + rdpdr_component_string(component), rdpdr_component_string(rcomponent)); + return FALSE; + } + Stream_Read_UINT16(s, packetid); + Stream_Rewind(s, 4); + + switch (packetid) + { + case PAKID_CORE_SERVER_CAPABILITY: + if (rdpdr->flags & STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST) + { + CLIENT_RX_LOG(rdpdr->log, WLOG_WARN, "got duplicate packetid %s", + rdpdr_packetid_string(packetid)); + return FALSE; + } + rdpdr->flags |= STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST; + return rdpdr_process_server_core_capability_request(rdpdr, s) == CHANNEL_RC_OK; + case PAKID_CORE_CLIENTID_CONFIRM: + default: + if (rdpdr->flags & STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM) + { + CLIENT_RX_LOG(rdpdr->log, WLOG_WARN, "got duplicate packetid %s", + rdpdr_packetid_string(packetid)); + return FALSE; + } + rdpdr->flags |= STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM; + return rdpdr_process_server_clientid_confirm(rdpdr, s) == CHANNEL_RC_OK; + } +} + +#if defined(WITH_PROXY_EMULATE_SMARTCARD) +static UINT rdpdr_send_emulated_scard_device_list_announce_request(pClientContext* pc, + pf_channel_client_context* rdpdr) +{ + wStream* s = NULL; + + s = rdpdr_client_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_DEVICELIST_ANNOUNCE, 24); + if (!s) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write_UINT32(s, 1); /* deviceCount -> our emulated smartcard only */ + Stream_Write_UINT32(s, RDPDR_DTYP_SMARTCARD); /* deviceType */ + Stream_Write_UINT32( + s, SCARD_DEVICE_ID); /* deviceID -> reserve highest value for the emulated smartcard */ + Stream_Write(s, "SCARD\0\0\0", 8); + Stream_Write_UINT32(s, 6); + Stream_Write(s, "SCARD\0", 6); + + return rdpdr_client_send(rdpdr->log, pc, s); +} + +static UINT rdpdr_send_emulated_scard_device_remove(pClientContext* pc, + pf_channel_client_context* rdpdr) +{ + wStream* s = NULL; + + s = rdpdr_client_get_send_buffer(rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_DEVICELIST_REMOVE, 24); + if (!s) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write_UINT32(s, 1); /* deviceCount -> our emulated smartcard only */ + Stream_Write_UINT32( + s, SCARD_DEVICE_ID); /* deviceID -> reserve highest value for the emulated smartcard */ + + return rdpdr_client_send(rdpdr->log, pc, s); +} + +static UINT rdpdr_process_server_device_announce_response(pf_channel_client_context* rdpdr, + wStream* s) +{ + const UINT16 component = RDPDR_CTYP_CORE; + const UINT16 packetid = PAKID_CORE_DEVICE_REPLY; + UINT32 deviceID = 0; + UINT32 resultCode = 0; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + if (!rdpdr_process_server_header(TRUE, rdpdr->log, s, component, packetid, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, deviceID); + Stream_Read_UINT32(s, resultCode); + + if (deviceID != SCARD_DEVICE_ID) + { + CLIENT_RX_LOG(rdpdr->log, WLOG_WARN, + "[%s | %s] deviceID mismatch, sent 0x%08" PRIx32 ", changed to 0x%08" PRIx32, + rdpdr_component_string(component), rdpdr_packetid_string(packetid), + SCARD_DEVICE_ID, deviceID); + } + else if (resultCode != 0) + { + CLIENT_RX_LOG(rdpdr->log, WLOG_WARN, + "[%s | %s] deviceID 0x%08" PRIx32 " resultCode=0x%08" PRIx32, + rdpdr_component_string(component), rdpdr_packetid_string(packetid), deviceID, + resultCode); + } + else + CLIENT_RX_LOG(rdpdr->log, WLOG_DEBUG, + "[%s | %s] deviceID 0x%08" PRIx32 " resultCode=0x%08" PRIx32 + " -> emulated smartcard redirected!", + rdpdr_component_string(component), rdpdr_packetid_string(packetid), deviceID, + resultCode); + + return CHANNEL_RC_OK; +} +#endif + +static BOOL pf_channel_rdpdr_rewrite_device_list_to(wStream* s, UINT32 fromVersion, + UINT32 toVersion) +{ + BOOL rc = FALSE; + if (fromVersion == toVersion) + return TRUE; + + const size_t cap = Stream_GetRemainingLength(s); + wStream* clone = Stream_New(NULL, cap); + if (!clone) + goto fail; + const size_t pos = Stream_GetPosition(s); + Stream_Copy(s, clone, cap); + Stream_SealLength(clone); + + Stream_SetPosition(clone, 0); + Stream_SetPosition(s, pos); + + /* Skip device count */ + if (!Stream_SafeSeek(s, 4)) + goto fail; + + UINT32 count = 0; + if (Stream_GetRemainingLength(clone) < 4) + goto fail; + Stream_Read_UINT32(clone, count); + + for (UINT32 x = 0; x < count; x++) + { + RdpdrDevice device = { 0 }; + const size_t charCount = ARRAYSIZE(device.PreferredDosName); + if (Stream_GetRemainingLength(clone) < 20) + goto fail; + + Stream_Read_UINT32(clone, device.DeviceType); /* DeviceType (4 bytes) */ + Stream_Read_UINT32(clone, device.DeviceId); /* DeviceId (4 bytes) */ + Stream_Read(clone, device.PreferredDosName, charCount); /* PreferredDosName (8 bytes) */ + Stream_Read_UINT32(clone, device.DeviceDataLength); /* DeviceDataLength (4 bytes) */ + device.DeviceData = Stream_Pointer(clone); + if (!Stream_SafeSeek(clone, device.DeviceDataLength)) + goto fail; + + if (!Stream_EnsureRemainingCapacity(s, 20)) + goto fail; + Stream_Write_UINT32(s, device.DeviceType); + Stream_Write_UINT32(s, device.DeviceId); + Stream_Write(s, device.PreferredDosName, charCount); + + if (device.DeviceType == RDPDR_DTYP_FILESYSTEM) + { + if (toVersion == DRIVE_CAPABILITY_VERSION_01) + Stream_Write_UINT32(s, 0); /* No unicode name */ + else + { + const size_t datalen = charCount * sizeof(WCHAR); + if (!Stream_EnsureRemainingCapacity(s, datalen + sizeof(UINT32))) + goto fail; + Stream_Write_UINT32(s, datalen); + + const SSIZE_T rcw = Stream_Write_UTF16_String_From_UTF8( + s, charCount, device.PreferredDosName, charCount - 1, TRUE); + if (rcw < 0) + goto fail; + } + } + else + { + Stream_Write_UINT32(s, device.DeviceDataLength); + if (!Stream_EnsureRemainingCapacity(s, device.DeviceDataLength)) + goto fail; + Stream_Write(s, device.DeviceData, device.DeviceDataLength); + } + } + + Stream_SealLength(s); + rc = TRUE; + +fail: + Stream_Free(clone, TRUE); + return rc; +} + +static BOOL pf_channel_rdpdr_rewrite_device_list(pf_channel_client_context* rdpdr, + pServerContext* ps, wStream* s, BOOL toServer) +{ + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(ps); + + const size_t pos = Stream_GetPosition(s); + UINT16 component = 0; + UINT16 packetid = 0; + Stream_SetPosition(s, 0); + + if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, 4)) + return FALSE; + + Stream_Read_UINT16(s, component); + Stream_Read_UINT16(s, packetid); + if ((component != RDPDR_CTYP_CORE) || (packetid != PAKID_CORE_DEVICELIST_ANNOUNCE)) + { + Stream_SetPosition(s, pos); + return TRUE; + } + + const pf_channel_server_context* srv = + HashTable_GetItemValue(ps->interceptContextMap, RDPDR_SVC_CHANNEL_NAME); + if (!srv) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "No channel %s in intercep map", RDPDR_SVC_CHANNEL_NAME); + return FALSE; + } + + UINT32 from = srv->common.capabilityVersions[CAP_DRIVE_TYPE]; + UINT32 to = rdpdr->common.capabilityVersions[CAP_DRIVE_TYPE]; + if (toServer) + { + from = rdpdr->common.capabilityVersions[CAP_DRIVE_TYPE]; + to = srv->common.capabilityVersions[CAP_DRIVE_TYPE]; + } + if (!pf_channel_rdpdr_rewrite_device_list_to(s, from, to)) + return FALSE; + + Stream_SetPosition(s, pos); + return TRUE; +} + +static BOOL pf_channel_rdpdr_client_send_to_server(pf_channel_client_context* rdpdr, + pServerContext* ps, wStream* s) +{ + WINPR_ASSERT(rdpdr); + if (ps) + { + UINT16 server_channel_id = WTSChannelGetId(ps->context.peer, RDPDR_SVC_CHANNEL_NAME); + + /* Ignore messages for channels that can not be mapped. + * The client might not have enabled support for this specific channel, + * so just drop the message. */ + if (server_channel_id == 0) + return TRUE; + + if (!pf_channel_rdpdr_rewrite_device_list(rdpdr, ps, s, TRUE)) + return FALSE; + size_t len = Stream_Length(s); + Stream_SetPosition(s, len); + rdpdr_dump_send_packet(rdpdr->log, WLOG_TRACE, s, proxy_client_tx); + WINPR_ASSERT(ps->context.peer); + WINPR_ASSERT(ps->context.peer->SendChannelData); + return ps->context.peer->SendChannelData(ps->context.peer, server_channel_id, + Stream_Buffer(s), len); + } + return TRUE; +} + +static BOOL pf_channel_send_client_queue(pClientContext* pc, pf_channel_client_context* rdpdr); + +#if defined(WITH_PROXY_EMULATE_SMARTCARD) +static BOOL rdpdr_process_server_loggedon_request(pServerContext* ps, pClientContext* pc, + pf_channel_client_context* rdpdr, wStream* s, + UINT16 component, UINT16 packetid) +{ + WINPR_ASSERT(rdpdr); + WLog_Print(rdpdr->log, WLOG_DEBUG, "[%s | %s]", rdpdr_component_string(component), + rdpdr_packetid_string(packetid)); + if (rdpdr_send_emulated_scard_device_remove(pc, rdpdr) != CHANNEL_RC_OK) + return FALSE; + if (rdpdr_send_emulated_scard_device_list_announce_request(pc, rdpdr) != CHANNEL_RC_OK) + return FALSE; + return pf_channel_rdpdr_client_send_to_server(rdpdr, ps, s); +} + +static BOOL filter_smartcard_io_requests(pf_channel_client_context* rdpdr, wStream* s, + UINT16* pPacketid) +{ + BOOL rc = FALSE; + UINT16 component = 0; + UINT16 packetid = 0; + UINT32 deviceID = 0; + size_t pos = 0; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(pPacketid); + + if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, 4)) + return FALSE; + + pos = Stream_GetPosition(s); + Stream_Read_UINT16(s, component); + Stream_Read_UINT16(s, packetid); + + if (Stream_GetRemainingLength(s) >= 4) + Stream_Read_UINT32(s, deviceID); + + WLog_Print(rdpdr->log, WLOG_DEBUG, "got: [%s | %s]: [0x%08" PRIx32 "]", + rdpdr_component_string(component), rdpdr_packetid_string(packetid), deviceID); + + if (component != RDPDR_CTYP_CORE) + goto fail; + + switch (packetid) + { + case PAKID_CORE_SERVER_ANNOUNCE: + case PAKID_CORE_CLIENTID_CONFIRM: + case PAKID_CORE_CLIENT_NAME: + case PAKID_CORE_DEVICELIST_ANNOUNCE: + case PAKID_CORE_DEVICELIST_REMOVE: + case PAKID_CORE_SERVER_CAPABILITY: + case PAKID_CORE_CLIENT_CAPABILITY: + WLog_Print(rdpdr->log, WLOG_WARN, "Filtering client -> server message [%s | %s]", + rdpdr_component_string(component), rdpdr_packetid_string(packetid)); + *pPacketid = packetid; + break; + case PAKID_CORE_USER_LOGGEDON: + *pPacketid = packetid; + break; + case PAKID_CORE_DEVICE_REPLY: + case PAKID_CORE_DEVICE_IOREQUEST: + if (deviceID != SCARD_DEVICE_ID) + goto fail; + *pPacketid = packetid; + break; + default: + if (deviceID != SCARD_DEVICE_ID) + goto fail; + WLog_Print(rdpdr->log, WLOG_WARN, + "Got [%s | %s] for deviceID 0x%08" PRIx32 ", TODO: Not handled!", + rdpdr_component_string(component), rdpdr_packetid_string(packetid), + deviceID); + goto fail; + } + + rc = TRUE; + +fail: + Stream_SetPosition(s, pos); + return rc; +} +#endif + +BOOL pf_channel_send_client_queue(pClientContext* pc, pf_channel_client_context* rdpdr) +{ + UINT16 channelId = 0; + + WINPR_ASSERT(pc); + WINPR_ASSERT(rdpdr); + + if (rdpdr->state != STATE_CLIENT_CHANNEL_RUNNING) + return FALSE; + channelId = freerdp_channels_get_id_by_name(pc->context.instance, RDPDR_SVC_CHANNEL_NAME); + if ((channelId == 0) || (channelId == UINT16_MAX)) + return TRUE; + + Queue_Lock(rdpdr->queue); + while (Queue_Count(rdpdr->queue) > 0) + { + wStream* s = Queue_Dequeue(rdpdr->queue); + if (!s) + continue; + + size_t len = Stream_Length(s); + Stream_SetPosition(s, len); + + rdpdr_dump_send_packet(rdpdr->log, WLOG_TRACE, s, proxy_server_tx " (queue) "); + WINPR_ASSERT(pc->context.instance->SendChannelData); + if (!pc->context.instance->SendChannelData(pc->context.instance, channelId, + Stream_Buffer(s), len)) + { + CLIENT_TX_LOG(rdpdr->log, WLOG_ERROR, "xxxxxx TODO: Failed to send data!"); + } + Stream_Free(s, TRUE); + } + Queue_Unlock(rdpdr->queue); + return TRUE; +} + +static BOOL rdpdr_handle_server_announce_request(pClientContext* pc, + pf_channel_client_context* rdpdr, wStream* s) +{ + WINPR_ASSERT(pc); + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + if (rdpdr_process_server_announce_request(rdpdr, s) != CHANNEL_RC_OK) + return FALSE; + if (rdpdr_send_client_announce_reply(pc, rdpdr) != CHANNEL_RC_OK) + return FALSE; + if (rdpdr_send_client_name_request(pc, rdpdr) != CHANNEL_RC_OK) + return FALSE; + rdpdr->state = STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST; + return TRUE; +} + +BOOL pf_channel_rdpdr_client_handle(pClientContext* pc, UINT16 channelId, const char* channel_name, + const BYTE* xdata, size_t xsize, UINT32 flags, size_t totalSize) +{ + pf_channel_client_context* rdpdr = NULL; + pServerContext* ps = NULL; + wStream* s = NULL; +#if defined(WITH_PROXY_EMULATE_SMARTCARD) + UINT16 packetid = 0; +#endif + + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->pdata); + WINPR_ASSERT(pc->interceptContextMap); + WINPR_ASSERT(channel_name); + WINPR_ASSERT(xdata); + + ps = pc->pdata->ps; + + rdpdr = HashTable_GetItemValue(pc->interceptContextMap, channel_name); + if (!rdpdr) + { + CLIENT_RX_LOG(WLog_Get(RTAG), WLOG_ERROR, + "Channel %s [0x%04" PRIx16 "] missing context in interceptContextMap", + channel_name, channelId); + return FALSE; + } + s = rdpdr->common.buffer; + if (flags & CHANNEL_FLAG_FIRST) + Stream_SetPosition(s, 0); + if (!Stream_EnsureRemainingCapacity(s, xsize)) + { + CLIENT_RX_LOG(rdpdr->log, WLOG_ERROR, + "Channel %s [0x%04" PRIx16 "] not enough memory [need %" PRIuz "]", + channel_name, channelId, xsize); + return FALSE; + } + Stream_Write(s, xdata, xsize); + if ((flags & CHANNEL_FLAG_LAST) == 0) + return TRUE; + + Stream_SealLength(s); + Stream_SetPosition(s, 0); + if (Stream_Length(s) != totalSize) + { + CLIENT_RX_LOG(rdpdr->log, WLOG_WARN, + "Received invalid %s channel data (server -> proxy), expected %" PRIuz + "bytes, got %" PRIuz, + channel_name, totalSize, Stream_Length(s)); + return FALSE; + } + + rdpdr_dump_received_packet(rdpdr->log, WLOG_TRACE, s, proxy_server_rx); + switch (rdpdr->state) + { + case STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST: + if (!rdpdr_handle_server_announce_request(pc, rdpdr, s)) + return FALSE; + break; + case STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST: + if (!rdpdr_process_server_capability_request_or_clientid_confirm(rdpdr, s)) + return FALSE; + rdpdr->state = STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM; + break; + case STATE_CLIENT_EXPECT_SERVER_CLIENT_ID_CONFIRM: + if (!rdpdr_process_server_capability_request_or_clientid_confirm(rdpdr, s)) + return FALSE; + if (rdpdr_send_client_capability_response(pc, rdpdr) != CHANNEL_RC_OK) + return FALSE; +#if defined(WITH_PROXY_EMULATE_SMARTCARD) + if (pf_channel_smartcard_client_emulate(pc)) + { + if (rdpdr_send_emulated_scard_device_list_announce_request(pc, rdpdr) != + CHANNEL_RC_OK) + return FALSE; + rdpdr->state = STATE_CLIENT_CHANNEL_RUNNING; + } + else +#endif + { + rdpdr->state = STATE_CLIENT_CHANNEL_RUNNING; + pf_channel_send_client_queue(pc, rdpdr); + } + + break; + case STATE_CLIENT_CHANNEL_RUNNING: +#if defined(WITH_PROXY_EMULATE_SMARTCARD) + if (!pf_channel_smartcard_client_emulate(pc) || + !filter_smartcard_io_requests(rdpdr, s, &packetid)) + return pf_channel_rdpdr_client_send_to_server(rdpdr, ps, s); + else + { + switch (packetid) + { + case PAKID_CORE_USER_LOGGEDON: + return rdpdr_process_server_loggedon_request(ps, pc, rdpdr, s, + RDPDR_CTYP_CORE, packetid); + case PAKID_CORE_DEVICE_IOREQUEST: + { + wStream* out = rdpdr_client_get_send_buffer( + rdpdr, RDPDR_CTYP_CORE, PAKID_CORE_DEVICE_IOCOMPLETION, 0); + WINPR_ASSERT(out); + + if (!rdpdr_process_server_header(FALSE, rdpdr->log, s, RDPDR_CTYP_CORE, + PAKID_CORE_DEVICE_IOREQUEST, 20)) + return FALSE; + + if (!pf_channel_smartcard_client_handle(rdpdr->log, pc, s, out, + rdpdr_client_send)) + return FALSE; + } + break; + case PAKID_CORE_SERVER_ANNOUNCE: + pf_channel_rdpdr_client_reset(pc); + if (!rdpdr_handle_server_announce_request(pc, rdpdr, s)) + return FALSE; + break; + case PAKID_CORE_SERVER_CAPABILITY: + rdpdr->state = STATE_CLIENT_EXPECT_SERVER_CORE_CAPABILITY_REQUEST; + rdpdr->flags = 0; + return pf_channel_rdpdr_client_handle(pc, channelId, channel_name, xdata, + xsize, flags, totalSize); + case PAKID_CORE_DEVICE_REPLY: + break; + default: + CLIENT_RX_LOG( + rdpdr->log, WLOG_ERROR, + "Channel %s [0x%04" PRIx16 + "] we´ve reached an impossible state %s! [%s] aliens invaded!", + channel_name, channelId, rdpdr_client_state_to_string(rdpdr->state), + rdpdr_packetid_string(packetid)); + return FALSE; + } + } + break; +#else + return pf_channel_rdpdr_client_send_to_server(rdpdr, ps, s); +#endif + default: + CLIENT_RX_LOG(rdpdr->log, WLOG_ERROR, + "Channel %s [0x%04" PRIx16 + "] we´ve reached an impossible state %s! aliens invaded!", + channel_name, channelId, rdpdr_client_state_to_string(rdpdr->state)); + return FALSE; + } + + return TRUE; +} + +static void pf_channel_rdpdr_common_context_free(pf_channel_common_context* common) +{ + if (!common) + return; + free(common->computerName.v); + Stream_Free(common->s, TRUE); + Stream_Free(common->buffer, TRUE); +} + +static void pf_channel_rdpdr_client_context_free(InterceptContextMapEntry* base) +{ + pf_channel_client_context* entry = (pf_channel_client_context*)base; + if (!entry) + return; + + pf_channel_rdpdr_common_context_free(&entry->common); + Queue_Free(entry->queue); + free(entry); +} + +static BOOL pf_channel_rdpdr_common_context_new(pf_channel_common_context* common, + void (*fkt)(InterceptContextMapEntry*)) +{ + if (!common) + return FALSE; + common->base.free = fkt; + common->s = Stream_New(NULL, 1024); + if (!common->s) + return FALSE; + common->buffer = Stream_New(NULL, 1024); + if (!common->buffer) + return FALSE; + common->computerNameUnicode = 1; + common->computerName.v = NULL; + common->versionMajor = RDPDR_VERSION_MAJOR; + common->versionMinor = RDPDR_VERSION_MINOR_RDP10X; + common->clientID = SCARD_DEVICE_ID; + + const UINT32 versions[] = { 0, + GENERAL_CAPABILITY_VERSION_02, + PRINT_CAPABILITY_VERSION_01, + PORT_CAPABILITY_VERSION_01, + DRIVE_CAPABILITY_VERSION_02, + SMARTCARD_CAPABILITY_VERSION_01 }; + + memcpy(common->capabilityVersions, versions, sizeof(common->capabilityVersions)); + return TRUE; +} + +static BOOL pf_channel_rdpdr_client_pass_message(pServerContext* ps, pClientContext* pc, + UINT16 channelId, const char* channel_name, + wStream* s) +{ + pf_channel_client_context* rdpdr = NULL; + + WINPR_ASSERT(ps); + WINPR_ASSERT(pc); + + rdpdr = HashTable_GetItemValue(pc->interceptContextMap, channel_name); + if (!rdpdr) + return TRUE; /* Ignore data for channels not available on proxy -> server connection */ + WINPR_ASSERT(rdpdr->queue); + + if (!pf_channel_rdpdr_rewrite_device_list(rdpdr, ps, s, FALSE)) + return FALSE; + if (!Queue_Enqueue(rdpdr->queue, s)) + return FALSE; + pf_channel_send_client_queue(pc, rdpdr); + return TRUE; +} + +#if defined(WITH_PROXY_EMULATE_SMARTCARD) +static BOOL filter_smartcard_device_list_remove(pf_channel_server_context* rdpdr, wStream* s) +{ + size_t pos = 0; + UINT32 count = 0; + + WINPR_ASSERT(rdpdr); + if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, sizeof(UINT32))) + return TRUE; + pos = Stream_GetPosition(s); + Stream_Read_UINT32(s, count); + + if (count == 0) + return TRUE; + + if (!Stream_CheckAndLogRequiredLengthOfSizeWLog(rdpdr->log, s, count, sizeof(UINT32))) + return TRUE; + + for (UINT32 x = 0; x < count; x++) + { + UINT32 deviceID = 0; + BYTE* dst = Stream_Pointer(s); + Stream_Read_UINT32(s, deviceID); + if (deviceID == SCARD_DEVICE_ID) + { + ArrayList_Remove(rdpdr->blockedDevices, (void*)(size_t)deviceID); + + /* This is the only device, filter it! */ + if (count == 1) + return TRUE; + + /* Remove this device from the list */ + memmove(dst, Stream_ConstPointer(s), (count - x - 1) * sizeof(UINT32)); + + count--; + Stream_SetPosition(s, pos); + Stream_Write_UINT32(s, count); + return FALSE; + } + } + + return FALSE; +} + +static BOOL filter_smartcard_device_io_request(pf_channel_server_context* rdpdr, wStream* s) +{ + UINT32 DeviceID = 0; + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + Stream_Read_UINT32(s, DeviceID); + return ArrayList_Contains(rdpdr->blockedDevices, (void*)(size_t)DeviceID); +} + +static BOOL filter_smartcard_device_list_announce(pf_channel_server_context* rdpdr, wStream* s) +{ + UINT32 count = 0; + + WINPR_ASSERT(rdpdr); + if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, sizeof(UINT32))) + return TRUE; + const size_t pos = Stream_GetPosition(s); + Stream_Read_UINT32(s, count); + + if (count == 0) + return TRUE; + + for (UINT32 x = 0; x < count; x++) + { + UINT32 DeviceType = 0; + UINT32 DeviceId = 0; + char PreferredDosName[8]; + UINT32 DeviceDataLength = 0; + BYTE* dst = Stream_Pointer(s); + if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, 20)) + return TRUE; + Stream_Read_UINT32(s, DeviceType); + Stream_Read_UINT32(s, DeviceId); + Stream_Read(s, PreferredDosName, ARRAYSIZE(PreferredDosName)); + Stream_Read_UINT32(s, DeviceDataLength); + if (!Stream_SafeSeek(s, DeviceDataLength)) + return TRUE; + if (DeviceType == RDPDR_DTYP_SMARTCARD) + { + ArrayList_Append(rdpdr->blockedDevices, (void*)(size_t)DeviceId); + if (count == 1) + return TRUE; + + WLog_Print(rdpdr->log, WLOG_INFO, "Filtering smartcard device 0x%08" PRIx32 "", + DeviceId); + + memmove(dst, Stream_ConstPointer(s), Stream_GetRemainingLength(s)); + Stream_SetPosition(s, pos); + Stream_Write_UINT32(s, count - 1); + return FALSE; + } + } + + return FALSE; +} + +static BOOL filter_smartcard_device_list_announce_request(pf_channel_server_context* rdpdr, + wStream* s) +{ + BOOL rc = TRUE; + size_t pos = 0; + UINT16 component = 0; + UINT16 packetid = 0; + + WINPR_ASSERT(rdpdr); + if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, 8)) + return FALSE; + + pos = Stream_GetPosition(s); + + Stream_Read_UINT16(s, component); + Stream_Read_UINT16(s, packetid); + + if (component != RDPDR_CTYP_CORE) + goto fail; + + switch (packetid) + { + case PAKID_CORE_DEVICELIST_ANNOUNCE: + if (filter_smartcard_device_list_announce(rdpdr, s)) + goto fail; + break; + case PAKID_CORE_DEVICELIST_REMOVE: + if (filter_smartcard_device_list_remove(rdpdr, s)) + goto fail; + break; + case PAKID_CORE_DEVICE_IOREQUEST: + if (filter_smartcard_device_io_request(rdpdr, s)) + goto fail; + break; + + case PAKID_CORE_SERVER_ANNOUNCE: + case PAKID_CORE_CLIENTID_CONFIRM: + case PAKID_CORE_CLIENT_NAME: + case PAKID_CORE_DEVICE_REPLY: + case PAKID_CORE_SERVER_CAPABILITY: + case PAKID_CORE_CLIENT_CAPABILITY: + case PAKID_CORE_USER_LOGGEDON: + WLog_Print(rdpdr->log, WLOG_WARN, "Filtering client -> server message [%s | %s]", + rdpdr_component_string(component), rdpdr_packetid_string(packetid)); + goto fail; + default: + break; + } + + rc = FALSE; +fail: + Stream_SetPosition(s, pos); + return rc; +} +#endif + +static void* stream_copy(const void* obj) +{ + const wStream* src = obj; + wStream* dst = Stream_New(NULL, Stream_Capacity(src)); + if (!dst) + return NULL; + memcpy(Stream_Buffer(dst), Stream_ConstBuffer(src), Stream_Capacity(dst)); + Stream_SetLength(dst, Stream_Length(src)); + Stream_SetPosition(dst, Stream_GetPosition(src)); + return dst; +} + +static void stream_free(void* obj) +{ + wStream* s = obj; + Stream_Free(s, TRUE); +} + +static const char* pf_channel_rdpdr_client_context(void* arg) +{ + pClientContext* pc = arg; + if (!pc) + return "pc=null"; + if (!pc->pdata) + return "pc->pdata=null"; + return pc->pdata->session_id; +} + +BOOL pf_channel_rdpdr_client_new(pClientContext* pc) +{ + wObject* obj = NULL; + pf_channel_client_context* rdpdr = NULL; + + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->interceptContextMap); + + rdpdr = calloc(1, sizeof(pf_channel_client_context)); + if (!rdpdr) + return FALSE; + rdpdr->log = WLog_Get(RTAG); + WINPR_ASSERT(rdpdr->log); + + WLog_SetContext(rdpdr->log, pf_channel_rdpdr_client_context, pc); + if (!pf_channel_rdpdr_common_context_new(&rdpdr->common, pf_channel_rdpdr_client_context_free)) + goto fail; + + rdpdr->maxMajorVersion = RDPDR_VERSION_MAJOR; + rdpdr->maxMinorVersion = RDPDR_VERSION_MINOR_RDP10X; + rdpdr->state = STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST; + + rdpdr->queue = Queue_New(TRUE, 0, 0); + if (!rdpdr->queue) + goto fail; + obj = Queue_Object(rdpdr->queue); + WINPR_ASSERT(obj); + obj->fnObjectNew = stream_copy; + obj->fnObjectFree = stream_free; + if (!HashTable_Insert(pc->interceptContextMap, RDPDR_SVC_CHANNEL_NAME, rdpdr)) + goto fail; + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert takes ownership of rdpdr + return TRUE; +fail: + pf_channel_rdpdr_client_context_free(&rdpdr->common.base); + return FALSE; +} + +void pf_channel_rdpdr_client_free(pClientContext* pc) +{ + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->interceptContextMap); + HashTable_Remove(pc->interceptContextMap, RDPDR_SVC_CHANNEL_NAME); +} + +static void pf_channel_rdpdr_server_context_free(InterceptContextMapEntry* base) +{ + pf_channel_server_context* entry = (pf_channel_server_context*)base; + if (!entry) + return; + + WTSVirtualChannelClose(entry->handle); + pf_channel_rdpdr_common_context_free(&entry->common); + ArrayList_Free(entry->blockedDevices); + free(entry); +} + +static const char* pf_channel_rdpdr_server_context(void* arg) +{ + pServerContext* ps = arg; + if (!ps) + return "ps=null"; + if (!ps->pdata) + return "ps->pdata=null"; + return ps->pdata->session_id; +} + +BOOL pf_channel_rdpdr_server_new(pServerContext* ps) +{ + pf_channel_server_context* rdpdr = NULL; + PULONG pSessionId = NULL; + DWORD BytesReturned = 0; + + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->interceptContextMap); + + rdpdr = calloc(1, sizeof(pf_channel_server_context)); + if (!rdpdr) + return FALSE; + rdpdr->log = WLog_Get(RTAG); + WINPR_ASSERT(rdpdr->log); + WLog_SetContext(rdpdr->log, pf_channel_rdpdr_server_context, ps); + + if (!pf_channel_rdpdr_common_context_new(&rdpdr->common, pf_channel_rdpdr_server_context_free)) + goto fail; + rdpdr->state = STATE_SERVER_INITIAL; + + rdpdr->blockedDevices = ArrayList_New(FALSE); + if (!rdpdr->blockedDevices) + goto fail; + + rdpdr->SessionId = WTS_CURRENT_SESSION; + if (WTSQuerySessionInformationA(ps->vcm, WTS_CURRENT_SESSION, WTSSessionId, (LPSTR*)&pSessionId, + &BytesReturned)) + { + rdpdr->SessionId = (DWORD)*pSessionId; + WTSFreeMemory(pSessionId); + } + + rdpdr->handle = WTSVirtualChannelOpenEx(rdpdr->SessionId, RDPDR_SVC_CHANNEL_NAME, 0); + if (rdpdr->handle == 0) + goto fail; + if (!HashTable_Insert(ps->interceptContextMap, RDPDR_SVC_CHANNEL_NAME, rdpdr)) + goto fail; + + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert takes ownership of rdpdr + return TRUE; +fail: + pf_channel_rdpdr_server_context_free(&rdpdr->common.base); + return FALSE; +} + +void pf_channel_rdpdr_server_free(pServerContext* ps) +{ + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->interceptContextMap); + HashTable_Remove(ps->interceptContextMap, RDPDR_SVC_CHANNEL_NAME); +} + +static pf_channel_server_context* get_channel(pServerContext* ps, BOOL send) +{ + pf_channel_server_context* rdpdr = NULL; + WINPR_ASSERT(ps); + WINPR_ASSERT(ps->interceptContextMap); + + rdpdr = HashTable_GetItemValue(ps->interceptContextMap, RDPDR_SVC_CHANNEL_NAME); + if (!rdpdr) + { + SERVER_RXTX_LOG(send, WLog_Get(RTAG), WLOG_ERROR, + "Channel %s missing context in interceptContextMap", + RDPDR_SVC_CHANNEL_NAME); + return NULL; + } + + return rdpdr; +} + +BOOL pf_channel_rdpdr_server_handle(pServerContext* ps, UINT16 channelId, const char* channel_name, + const BYTE* xdata, size_t xsize, UINT32 flags, size_t totalSize) +{ + wStream* s = NULL; + pClientContext* pc = NULL; + pf_channel_server_context* rdpdr = get_channel(ps, FALSE); + if (!rdpdr) + return FALSE; + + WINPR_ASSERT(ps->pdata); + pc = ps->pdata->pc; + + s = rdpdr->common.buffer; + + if (flags & CHANNEL_FLAG_FIRST) + Stream_SetPosition(s, 0); + + if (!Stream_EnsureRemainingCapacity(s, xsize)) + return FALSE; + Stream_Write(s, xdata, xsize); + + if ((flags & CHANNEL_FLAG_LAST) == 0) + return TRUE; + + Stream_SealLength(s); + Stream_SetPosition(s, 0); + + if (Stream_Length(s) != totalSize) + { + SERVER_RX_LOG(rdpdr->log, WLOG_WARN, + "Received invalid %s channel data (client -> proxy), expected %" PRIuz + "bytes, got %" PRIuz, + channel_name, totalSize, Stream_Length(s)); + return FALSE; + } + + rdpdr_dump_received_packet(rdpdr->log, WLOG_TRACE, s, proxy_client_rx); + switch (rdpdr->state) + { + case STATE_SERVER_EXPECT_CLIENT_ANNOUNCE_REPLY: + if (rdpdr_process_client_announce_reply(rdpdr, s) != CHANNEL_RC_OK) + return FALSE; + rdpdr->state = STATE_SERVER_EXPECT_CLIENT_NAME_REQUEST; + break; + case STATE_SERVER_EXPECT_CLIENT_NAME_REQUEST: + if (rdpdr_process_client_name_request(rdpdr, s, pc) != CHANNEL_RC_OK) + return FALSE; + if (rdpdr_send_server_capability_request(rdpdr) != CHANNEL_RC_OK) + return FALSE; + if (rdpdr_send_server_clientid_confirm(rdpdr) != CHANNEL_RC_OK) + return FALSE; + rdpdr->state = STATE_SERVER_EXPECT_EXPECT_CLIENT_CAPABILITY_RESPONE; + break; + case STATE_SERVER_EXPECT_EXPECT_CLIENT_CAPABILITY_RESPONE: + if (rdpdr_process_client_capability_response(rdpdr, s) != CHANNEL_RC_OK) + return FALSE; + rdpdr->state = STATE_SERVER_CHANNEL_RUNNING; + break; + case STATE_SERVER_CHANNEL_RUNNING: +#if defined(WITH_PROXY_EMULATE_SMARTCARD) + if (!pf_channel_smartcard_client_emulate(pc) || + !filter_smartcard_device_list_announce_request(rdpdr, s)) + { + if (!pf_channel_rdpdr_client_pass_message(ps, pc, channelId, channel_name, s)) + return FALSE; + } + else + return pf_channel_smartcard_server_handle(ps, s); +#else + if (!pf_channel_rdpdr_client_pass_message(ps, pc, channelId, channel_name, s)) + return FALSE; +#endif + break; + default: + case STATE_SERVER_INITIAL: + SERVER_RX_LOG(rdpdr->log, WLOG_WARN, "Invalid state %s", + rdpdr_server_state_to_string(rdpdr->state)); + return FALSE; + } + + return TRUE; +} + +BOOL pf_channel_rdpdr_server_announce(pServerContext* ps) +{ + pf_channel_server_context* rdpdr = get_channel(ps, TRUE); + if (!rdpdr) + return FALSE; + + WINPR_ASSERT(rdpdr->state == STATE_SERVER_INITIAL); + if (rdpdr_server_send_announce_request(rdpdr) != CHANNEL_RC_OK) + return FALSE; + rdpdr->state = STATE_SERVER_EXPECT_CLIENT_ANNOUNCE_REPLY; + return TRUE; +} + +BOOL pf_channel_rdpdr_client_reset(pClientContext* pc) +{ + pf_channel_client_context* rdpdr = NULL; + + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->pdata); + WINPR_ASSERT(pc->interceptContextMap); + + rdpdr = HashTable_GetItemValue(pc->interceptContextMap, RDPDR_SVC_CHANNEL_NAME); + if (!rdpdr) + return TRUE; + + Queue_Clear(rdpdr->queue); + rdpdr->flags = 0; + rdpdr->state = STATE_CLIENT_EXPECT_SERVER_ANNOUNCE_REQUEST; + + return TRUE; +} + +static PfChannelResult pf_rdpdr_back_data(proxyData* pdata, + const pServerStaticChannelContext* channel, + const BYTE* xdata, size_t xsize, UINT32 flags, + size_t totalSize) +{ + WINPR_ASSERT(pdata); + WINPR_ASSERT(channel); + + if (!pf_channel_rdpdr_client_handle(pdata->pc, channel->back_channel_id, channel->channel_name, + xdata, xsize, flags, totalSize)) + return PF_CHANNEL_RESULT_ERROR; + +#if defined(WITH_PROXY_EMULATE_SMARTCARD) + if (pf_channel_smartcard_client_emulate(pdata->pc)) + return PF_CHANNEL_RESULT_DROP; +#endif + return PF_CHANNEL_RESULT_DROP; +} + +static PfChannelResult pf_rdpdr_front_data(proxyData* pdata, + const pServerStaticChannelContext* channel, + const BYTE* xdata, size_t xsize, UINT32 flags, + size_t totalSize) +{ + WINPR_ASSERT(pdata); + WINPR_ASSERT(channel); + + if (!pf_channel_rdpdr_server_handle(pdata->ps, channel->front_channel_id, channel->channel_name, + xdata, xsize, flags, totalSize)) + return PF_CHANNEL_RESULT_ERROR; + +#if defined(WITH_PROXY_EMULATE_SMARTCARD) + if (pf_channel_smartcard_client_emulate(pdata->pc)) + return PF_CHANNEL_RESULT_DROP; +#endif + return PF_CHANNEL_RESULT_DROP; +} + +BOOL pf_channel_setup_rdpdr(pServerContext* ps, pServerStaticChannelContext* channel) +{ + channel->onBackData = pf_rdpdr_back_data; + channel->onFrontData = pf_rdpdr_front_data; + + if (!pf_channel_rdpdr_server_new(ps)) + return FALSE; + if (!pf_channel_rdpdr_server_announce(ps)) + return FALSE; + + return TRUE; +} |