summaryrefslogtreecommitdiffstats
path: root/server/proxy/channels/pf_channel_rdpdr.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:24:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:24:41 +0000
commita9bcc81f821d7c66f623779fa5147e728eb3c388 (patch)
tree98676963bcdd537ae5908a067a8eb110b93486a6 /server/proxy/channels/pf_channel_rdpdr.c
parentInitial commit. (diff)
downloadfreerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.tar.xz
freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.zip
Adding upstream version 3.3.0+dfsg1.upstream/3.3.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'server/proxy/channels/pf_channel_rdpdr.c')
-rw-r--r--server/proxy/channels/pf_channel_rdpdr.c2017
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;
+}