proxy: rework proxy channel treatments for statefull drdynvc

The rework introduce a stateful dynamic channel treatment, so that we can take early decisions
for data packet (dropping all the current packet or pass it), but also reassemble important
packets like channel creation.
This commit is contained in:
David Fort 2022-03-07 13:46:49 +01:00 committed by akallabeth
parent f232562d8b
commit 63d0d3e4cf
13 changed files with 902 additions and 208 deletions

View File

@ -36,6 +36,7 @@ extern "C"
typedef struct proxy_data proxyData;
typedef struct proxy_module proxyModule;
typedef struct p_server_channel_context pServerChannelContext;
typedef struct s_InterceptContextMapEntry
{
@ -49,21 +50,48 @@ extern "C"
/** @brief how is handled a channel */
typedef enum
{
PF_UTILS_CHANNEL_NOT_HANDLED,
PF_UTILS_CHANNEL_BLOCK,
PF_UTILS_CHANNEL_PASSTHROUGH,
PF_UTILS_CHANNEL_INTERCEPT,
PF_UTILS_CHANNEL_NOT_HANDLED, /*!< channel not handled */
PF_UTILS_CHANNEL_BLOCK, /*!< block and drop traffic on this channel */
PF_UTILS_CHANNEL_PASSTHROUGH, /*!< pass traffic from this channel */
PF_UTILS_CHANNEL_INTERCEPT, /*!< inspect traffic from this channel */
} pf_utils_channel_mode;
/** @brief channel opened status */
typedef enum
{
CHANNEL_OPENSTATE_WAITING_OPEN_STATUS, /*!< dynamic channel waiting for create response */
CHANNEL_OPENSTATE_OPENED, /*!< opened */
CHANNEL_OPENSTATE_CLOSED /*!< dynamic channel has been opened then closed */
} PfChannelOpenStatus;
#define PF_DYNAMIC_CHANNEL_MASK 0xFFFF000000000000
/** @brief result of a channel treatment */
typedef enum
{
PF_CHANNEL_RESULT_PASS, /*!< pass the packet as is */
PF_CHANNEL_RESULT_DROP, /*!< drop the packet */
PF_CHANNEL_RESULT_ERROR /*!< error during packet treatment */
} PfChannelResult;
typedef PfChannelResult (*proxyChannelDataFn)(proxyData* pdata, const pServerChannelContext* channel,
const BYTE* xdata, size_t xsize, UINT32 flags,
size_t totalSizepServer);
typedef void (*proxyChannelContextDtor)(void *context);
/** @brief per channel configuration */
struct p_server_channel_context
{
char* channel_name;
UINT32 channel_id;
UINT64 channel_id;
PfChannelOpenStatus openStatus;
BOOL isDynamic;
pf_utils_channel_mode channelMode;
proxyChannelDataFn onFrontData;
proxyChannelDataFn onBackData;
proxyChannelContextDtor contextDtor;
void *context;
};
typedef struct p_server_channel_context pServerChannelContext;
void ChannelContext_free(pServerChannelContext* ctx);
@ -84,7 +112,7 @@ extern "C"
};
typedef struct p_server_context pServerContext;
pServerChannelContext* ChannelContext_new(pServerContext* ps, const char* name, UINT32 id);
pServerChannelContext* ChannelContext_new(pServerContext* ps, const char* name, UINT64 id);
/**
* Wraps rdpContext and holds the state for the proxy's client.

View File

@ -141,7 +141,7 @@ static BOOL autodetect_send_rtt_measure_response(rdpRdp* rdp, UINT16 sequenceNum
if (!s)
return FALSE;
WLog_VRB(AUTODETECT_TAG, "sending RTT Measure Response PDU");
WLog_VRB(AUTODETECT_TAG, "sending RTT Measure Response PDU (seqNumber=0x%"PRIx16")", sequenceNumber);
Stream_Write_UINT8(s, 0x06); /* headerLength (1 byte) */
Stream_Write_UINT8(s, TYPE_ID_AUTODETECT_RESPONSE); /* headerTypeId (1 byte) */
Stream_Write_UINT16(s, sequenceNumber); /* sequenceNumber (2 bytes) */
@ -158,7 +158,7 @@ static BOOL autodetect_send_bandwidth_measure_start(rdpContext* context, UINT16
if (!s)
return FALSE;
WLog_VRB(AUTODETECT_TAG, "sending Bandwidth Measure Start PDU");
WLog_VRB(AUTODETECT_TAG, "sending Bandwidth Measure Start PDU(seqNumber=%"PRIu16")", sequenceNumber);
Stream_Write_UINT8(s, 0x06); /* headerLength (1 byte) */
Stream_Write_UINT8(s, TYPE_ID_AUTODETECT_REQUEST); /* headerTypeId (1 byte) */
Stream_Write_UINT16(s, sequenceNumber); /* sequenceNumber (2 bytes) */

View File

@ -25,6 +25,8 @@ set(MODULE_PREFIX "FREERDP_SERVER_PROXY")
set(${MODULE_PREFIX}_SRCS
pf_context.c
pf_channel.c
pf_channel.h
pf_client.c
pf_client.h
pf_input.c

View File

@ -3,6 +3,8 @@ set(MODULE_NAME pf_channels)
set(SOURCES
pf_channel_rdpdr.c
pf_channel_rdpdr.h
pf_channel_drdynvc.c
pf_channel_drdynvc.h
)
if (WITH_PROXY_EMULATE_SMARTCARD)

View File

@ -0,0 +1,418 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* pf_channel_drdynvc
*
* Copyright 2022 David Fort <contact@hardening-consulting.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <freerdp/channels/drdynvc.h>
#include <freerdp/server/proxy/proxy_log.h>
#include "pf_channel_drdynvc.h"
#include "../pf_channel.h"
#include "../proxy_modules.h"
#define TAG PROXY_TAG("drdynvc")
/** @brief tracker state for a drdynvc stream */
typedef struct
{
ChannelStateTracker* tracker;
UINT32 currentDataLength;
UINT32 CurrentDataReceived;
UINT32 CurrentDataFragments;
} DynChannelTrackerState;
/** @brief context for the dynamic channel */
typedef struct
{
DynChannelTrackerState backTracker;
DynChannelTrackerState frontTracker;
} DynChannelContext;
/** @brief result of dynamic channel packet treatment */
typedef enum {
DYNCVC_READ_OK, /*!< read was OK */
DYNCVC_READ_ERROR, /*!< an error happened during read */
DYNCVC_READ_INCOMPLETE /*!< missing bytes to read the complete packet */
} DynvcReadResult;
static DynvcReadResult dynvc_read_varInt(wStream* s, size_t len, UINT64* varInt, BOOL last)
{
switch (len)
{
case 0x00:
if (Stream_GetRemainingLength(s) < 1)
return last ? DYNCVC_READ_ERROR : DYNCVC_READ_INCOMPLETE;
Stream_Read_UINT8(s, *varInt);
break;
case 0x01:
if (Stream_GetRemainingLength(s) < 2)
return last ? DYNCVC_READ_ERROR : DYNCVC_READ_INCOMPLETE;
Stream_Read_UINT16(s, *varInt);
break;
case 0x02:
if (Stream_GetRemainingLength(s) < 4)
return last ? DYNCVC_READ_ERROR : DYNCVC_READ_INCOMPLETE;
Stream_Read_UINT32(s, *varInt);
break;
case 0x03:
default:
WLog_ERR(TAG, "Unknown int len %d", len);
return DYNCVC_READ_ERROR;
}
return DYNCVC_READ_OK;
}
PfChannelResult DynvcTrackerPeekFn(ChannelStateTracker* tracker, BOOL firstPacket, BOOL lastPacket)
{
BYTE cmd, byte0;
wStream *s, sbuffer;
BOOL haveChannelId;
BOOL haveLength;
UINT64 dynChannelId = 0;
UINT64 Length = 0;
pServerChannelContext* dynChannel;
DynChannelContext* dynChannelContext = (DynChannelContext*)tracker->trackerData;
BOOL isBackData = (tracker == dynChannelContext->backTracker.tracker);
DynChannelTrackerState* trackerState = isBackData ? &dynChannelContext->backTracker : &dynChannelContext->frontTracker;
UINT32 flags = lastPacket ? CHANNEL_FLAG_LAST : 0;
proxyData* pdata = tracker->pdata;
const char* direction = isBackData ? "B->F" : "F->B";
s = Stream_StaticConstInit(&sbuffer, Stream_Buffer(tracker->currentPacket), Stream_GetPosition(tracker->currentPacket));
if (Stream_Length(s) < 1)
return DYNCVC_READ_INCOMPLETE;
Stream_Read_UINT8(s, byte0);
cmd = byte0 >> 4;
switch (cmd)
{
case CREATE_REQUEST_PDU:
case CLOSE_REQUEST_PDU:
case DATA_PDU:
case DATA_COMPRESSED_PDU:
haveChannelId = TRUE;
haveLength = FALSE;
break;
case DATA_FIRST_PDU:
case DATA_FIRST_COMPRESSED_PDU:
haveLength = TRUE;
haveChannelId = TRUE;
break;
default:
haveChannelId = FALSE;
haveLength = FALSE;
break;
}
if (haveChannelId)
{
UINT64 maskedDynChannelId;
BYTE cbId = byte0 & 0x03;
switch (dynvc_read_varInt(s, cbId, &dynChannelId, lastPacket))
{
case DYNCVC_READ_OK:
break;
case DYNCVC_READ_INCOMPLETE:
return PF_CHANNEL_RESULT_DROP;
case DYNCVC_READ_ERROR:
default:
WLog_ERR(TAG, "DynvcTrackerPeekFn: invalid channelId field");
return PF_CHANNEL_RESULT_ERROR;
}
/* we always try to retrieve the dynamic channel in case it would have been opened
* and closed
*/
maskedDynChannelId = dynChannelId | PF_DYNAMIC_CHANNEL_MASK;
dynChannel = (pServerChannelContext*)HashTable_GetItemValue(pdata->ps->channelsById, &maskedDynChannelId);
if (cmd != CREATE_REQUEST_PDU || !isBackData)
{
if (!dynChannel)
{
/* we've not found the target channel, so we drop this chunk, plus all the rest of the packet */
tracker->mode = CHANNEL_TRACKER_DROP;
return PF_CHANNEL_RESULT_DROP;
}
}
}
if (haveLength)
{
BYTE lenLen = (byte0 >> 2) & 0x03;
switch (dynvc_read_varInt(s, lenLen, &Length, lastPacket))
{
case DYNCVC_READ_OK:
break;
case DYNCVC_READ_INCOMPLETE:
return PF_CHANNEL_RESULT_DROP;
case DYNCVC_READ_ERROR:
default:
WLog_ERR(TAG, "DynvcTrackerPeekFn: invalid length field");
return PF_CHANNEL_RESULT_ERROR;
}
}
switch (cmd)
{
case CAPABILITY_REQUEST_PDU:
WLog_DBG(TAG, "DynvcTracker: %s CAPABILITY_%s", direction, isBackData ? "REQUEST" : "RESPONSE");
tracker->mode = CHANNEL_TRACKER_PASS;
return PF_CHANNEL_RESULT_PASS;
case CREATE_REQUEST_PDU:
{
UINT32 creationStatus;
/* we only want the full packet */
if (!lastPacket)
return PF_CHANNEL_RESULT_DROP;
if (isBackData)
{
proxyChannelDataEventInfo dev;
size_t len;
const char* name = (const char*)Stream_Pointer(s);
size_t nameLen = Stream_GetRemainingLength(s);
len = strnlen(name, nameLen);
if ((len == 0) || (len == nameLen))
return PF_CHANNEL_RESULT_ERROR;
dev.channel_id = dynChannelId;
dev.channel_name = name;
dev.data = Stream_Buffer(s);
dev.data_len = Stream_GetPosition(tracker->currentPacket);
dev.flags = flags;
dev.total_size = Stream_GetPosition(tracker->currentPacket);
if (!pf_modules_run_filter(
pdata->module, FILTER_TYPE_CLIENT_PASSTHROUGH_DYN_CHANNEL_CREATE, pdata, &dev))
return PF_CHANNEL_RESULT_DROP; /* Silently drop */
if (!dynChannel)
{
dynChannel = ChannelContext_new(pdata->ps, name, dynChannelId | PF_DYNAMIC_CHANNEL_MASK);
if (!dynChannel)
{
WLog_ERR(TAG, "unable to create dynamic channel context data");
return PF_CHANNEL_RESULT_ERROR;
}
dynChannel->isDynamic = TRUE;
if (!HashTable_Insert(pdata->ps->channelsById, &dynChannel->channel_id, dynChannel))
{
WLog_ERR(TAG, "unable register dynamic channel context data");
ChannelContext_free(dynChannel);
return PF_CHANNEL_RESULT_ERROR;
}
}
dynChannel->openStatus = CHANNEL_OPENSTATE_WAITING_OPEN_STATUS;
return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, FALSE);
}
/* CREATE_REQUEST_PDU response */
if (Stream_GetRemainingLength(s) < 4)
return PF_CHANNEL_RESULT_ERROR;
Stream_Read_INT32(s, creationStatus);
WLog_DBG(TAG, "DynvcTracker(%"PRIu16",%s): %s CREATE_RESPONSE openStatus=%"PRIu32, dynChannelId,
dynChannel->channel_name, direction, creationStatus);
if (creationStatus != 0)
{
/* we remove it from the channels map, as it happens that server reused channel ids when
* the channel can't be opened
*/
HashTable_Remove(pdata->ps->channelsById, &dynChannel->channel_id);
}
else
{
dynChannel->openStatus = CHANNEL_OPENSTATE_OPENED;
}
return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, TRUE);
}
case CLOSE_REQUEST_PDU:
if (!lastPacket)
return PF_CHANNEL_RESULT_DROP;
WLog_DBG(TAG, "DynvcTracker(%s): %s Close request on channel", dynChannel->channel_name, direction);
tracker->mode = CHANNEL_TRACKER_PASS;
dynChannel->openStatus = CHANNEL_OPENSTATE_CLOSED;
return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, !isBackData);
case SOFT_SYNC_REQUEST_PDU:
/* just pass then as is for now */
WLog_DBG(TAG, "SOFT_SYNC_REQUEST_PDU");
tracker->mode = CHANNEL_TRACKER_PASS;
/*TODO: return pf_treat_softsync_req(pdata, s);*/
return PF_CHANNEL_RESULT_PASS;
case SOFT_SYNC_RESPONSE_PDU:
/* just pass then as is for now */
WLog_DBG(TAG, "SOFT_SYNC_RESPONSE_PDU");
tracker->mode = CHANNEL_TRACKER_PASS;
return PF_CHANNEL_RESULT_PASS;
case DATA_FIRST_PDU:
case DATA_PDU:
/* treat these below */
break;
case DATA_FIRST_COMPRESSED_PDU:
case DATA_COMPRESSED_PDU:
WLog_DBG(TAG, "TODO: compressed data packets, pass them as is for now");
tracker->mode = CHANNEL_TRACKER_PASS;
return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, !isBackData);
default:
return PF_CHANNEL_RESULT_ERROR;
}
if ((cmd == DATA_FIRST_PDU) || (cmd == DATA_FIRST_COMPRESSED_PDU))
{
WLog_DBG(TAG, "DynvcTracker(%s): %s DATA_FIRST currentPacketLength=%d", dynChannel->channel_name, direction, Length);
trackerState->currentDataLength = Length;
trackerState->CurrentDataReceived = 0;
trackerState->CurrentDataFragments = 0;
}
if (cmd == DATA_PDU || cmd == DATA_FIRST_PDU)
{
trackerState->CurrentDataFragments++;
trackerState->CurrentDataReceived += Stream_GetRemainingLength(s);
WLog_DBG(TAG, "DynvcTracker(%s): %s %s frags=%d received=%d(%d)", dynChannel->channel_name, direction,
cmd == DATA_PDU ? "DATA" : "DATA_FIRST",
trackerState->CurrentDataFragments, trackerState->CurrentDataReceived,
trackerState->currentDataLength);
}
if (cmd == DATA_PDU)
{
if (trackerState->currentDataLength)
{
if (trackerState->CurrentDataReceived > trackerState->currentDataLength)
{
WLog_ERR(TAG, "DynvcTracker: reassembled packet (%d) is bigger than announced length (%d)", trackerState->CurrentDataReceived, trackerState->currentDataLength);
return PF_CHANNEL_RESULT_ERROR;
}
if (trackerState->CurrentDataReceived == trackerState->currentDataLength)
{
trackerState->currentDataLength = 0;
trackerState->CurrentDataFragments = 0;
trackerState->CurrentDataReceived = 0;
}
}
else
{
trackerState->CurrentDataFragments = 0;
trackerState->CurrentDataReceived = 0;
}
}
if (dynChannel->openStatus != CHANNEL_OPENSTATE_OPENED)
{
WLog_ERR(TAG, "DynvcTracker(%s): channel is not opened", dynChannel->channel_name);
return PF_CHANNEL_RESULT_ERROR;
}
switch(dynChannel->channelMode)
{
case PF_UTILS_CHANNEL_PASSTHROUGH:
return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, !isBackData);
case PF_UTILS_CHANNEL_BLOCK:
tracker->mode = CHANNEL_TRACKER_DROP;
return PF_CHANNEL_RESULT_DROP;
case PF_UTILS_CHANNEL_INTERCEPT:
WLog_DBG(TAG, "TODO: implement intercepted dynamic channel");
return PF_CHANNEL_RESULT_DROP;
default:
WLog_ERR(TAG, "unknown channel mode");
return PF_CHANNEL_RESULT_ERROR;
}
}
static DynChannelContext* DynChannelContext_new(proxyData* pdata, pServerChannelContext* channel)
{
DynChannelContext* dyn = calloc(1, sizeof(DynChannelContext));
if (!dyn)
return FALSE;
dyn->backTracker.tracker = channelTracker_new(channel, DynvcTrackerPeekFn, dyn);
if (!dyn->backTracker.tracker)
goto out_fromClientTracker;
dyn->backTracker.tracker->pdata = pdata;
dyn->frontTracker.tracker = channelTracker_new(channel, DynvcTrackerPeekFn, dyn);
if (!dyn->frontTracker.tracker)
goto out_fromServerTracker;
dyn->frontTracker.tracker->pdata = pdata;
return dyn;
out_fromServerTracker:
channelTracker_free(dyn->backTracker.tracker);
out_fromClientTracker:
free(dyn);
return NULL;
}
static void DynChannelContext_free(DynChannelContext* c)
{
channelTracker_free(c->backTracker.tracker);
channelTracker_free(c->frontTracker.tracker);
free(c);
}
static PfChannelResult pf_dynvc_back_data(proxyData* pdata, const pServerChannelContext* channel,
const BYTE* xdata, size_t xsize, UINT32 flags,
size_t totalSize)
{
DynChannelContext* dyn = (DynChannelContext*)channel->context;
return channelTracker_update(dyn->backTracker.tracker, xdata, xsize, flags, totalSize);
}
static PfChannelResult pf_dynvc_front_data(proxyData* pdata, const pServerChannelContext* channel,
const BYTE* xdata, size_t xsize, UINT32 flags,
size_t totalSize)
{
DynChannelContext* dyn = (DynChannelContext*)channel->context;
return channelTracker_update(dyn->frontTracker.tracker, xdata, xsize, flags, totalSize);
}
BOOL pf_channel_setup_drdynvc(proxyData* pdata, pServerChannelContext* channel)
{
DynChannelContext* ret = DynChannelContext_new(pdata, channel);
if (!ret)
return FALSE;
channel->onBackData = pf_dynvc_back_data;
channel->onFrontData = pf_dynvc_front_data;
channel->contextDtor = (proxyChannelContextDtor)DynChannelContext_free;
channel->context = ret;
return TRUE;
}

View File

@ -0,0 +1,27 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* pf_channel_drdynvc
*
* Copyright 2022 David Fort <contact@hardening-consulting.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef SERVER_PROXY_CHANNELS_PF_CHANNEL_DRDYNVC_H_
#define SERVER_PROXY_CHANNELS_PF_CHANNEL_DRDYNVC_H_
#include <freerdp/server/proxy/proxy_context.h>
BOOL pf_channel_setup_drdynvc(proxyData* pdata, pServerChannelContext* channel);
#endif /* SERVER_PROXY_CHANNELS_PF_CHANNEL_DRDYNVC_H_ */

View File

@ -1715,3 +1715,42 @@ BOOL pf_channel_rdpdr_client_reset(pClientContext* pc)
return TRUE;
}
static PfChannelResult pf_rdpdr_back_data(proxyData* pdata, const pServerChannelContext* channel,
const BYTE* xdata, size_t xsize, UINT32 flags,
size_t totalSize)
{
if (!pf_channel_rdpdr_client_handle(pdata->pc, channel->channel_id, channel->channel_name, xdata, xsize, flags, totalSize))
{
WLog_ERR(TAG, "error treating client back data");
return PF_CHANNEL_RESULT_ERROR;
}
return PF_CHANNEL_RESULT_PASS;
}
static PfChannelResult pf_rdpdr_front_data(proxyData* pdata, const pServerChannelContext* channel,
const BYTE* xdata, size_t xsize, UINT32 flags,
size_t totalSize)
{
if (!pf_channel_rdpdr_server_handle(pdata->ps, channel->channel_id, channel->channel_name, xdata, xsize, flags, totalSize))
{
WLog_ERR(TAG, "error treating front data");
return PF_CHANNEL_RESULT_ERROR;
}
return PF_CHANNEL_RESULT_PASS;
}
BOOL pf_channel_setup_rdpdr(pServerContext* ps, pServerChannelContext* 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;
}

234
server/proxy/pf_channel.c Normal file
View File

@ -0,0 +1,234 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
*
* Copyright 2022 David Fort <contact@hardening-consulting.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <freerdp/freerdp.h>
#include <freerdp/server/proxy/proxy_log.h>
#include "proxy_modules.h"
#include "pf_channel.h"
#define TAG PROXY_TAG("channel")
ChannelStateTracker* channelTracker_new(pServerChannelContext* channel, ChannelTrackerPeekFn fn, void* data)
{
ChannelStateTracker* ret = calloc(1, sizeof(*ret));
if (!ret)
return ret;
ret->channel = channel;
ret->peekFn = fn;
ret->trackerData = data;
ret->currentPacket = Stream_New(NULL, 10 * 1024);
if (!ret->currentPacket)
{
free(ret);
return NULL;
}
return ret;
}
PfChannelResult channelTracker_update(ChannelStateTracker* tracker, const BYTE* xdata, size_t xsize, UINT32 flags,
size_t totalSize)
{
PfChannelResult result;
BOOL firstPacket = !!(flags & CHANNEL_FLAG_FIRST);
BOOL lastPacket = !!(flags & CHANNEL_FLAG_LAST);
WLog_VRB(TAG, "channelTracker_update(%s): sz=%d first=%d last=%d", tracker->channel->channel_name, xsize, firstPacket, lastPacket);
if (flags & CHANNEL_FLAG_FIRST)
{
/* don't keep a too big currentPacket */
if (Stream_Capacity(tracker->currentPacket) > 1 * 1000 * 1000)
{
Stream_Free(tracker->currentPacket, TRUE);
tracker->currentPacket = Stream_New(NULL, 10 * 1024);
if (!tracker->currentPacket)
{
return PF_CHANNEL_RESULT_ERROR;
}
}
else
{
Stream_SetPosition(tracker->currentPacket, 0);
}
tracker->currentPacketSize = totalSize;
tracker->currentPacketReceived = 0;
tracker->currentPacketFragments = 0;
}
if (tracker->currentPacketReceived + xsize > tracker->currentPacketSize)
WLog_INFO(TAG, "cumulated size is bigger (%d) than total size (%d)", tracker->currentPacketReceived + xsize,
tracker->currentPacketSize);
tracker->currentPacketReceived += xsize;
tracker->currentPacketFragments++;
switch (tracker->mode)
{
case CHANNEL_TRACKER_PEEK:
if (!Stream_EnsureRemainingCapacity(tracker->currentPacket, xsize))
return PF_CHANNEL_RESULT_ERROR;
Stream_Write(tracker->currentPacket, xdata, xsize);
result = tracker->peekFn(tracker, firstPacket, lastPacket);
break;
case CHANNEL_TRACKER_PASS:
result = PF_CHANNEL_RESULT_PASS;
break;
case CHANNEL_TRACKER_DROP:
result = PF_CHANNEL_RESULT_DROP;
break;
}
if (lastPacket)
{
tracker->mode = CHANNEL_TRACKER_PEEK;
if (tracker->currentPacketReceived != tracker->currentPacketSize)
WLog_INFO(TAG, "cumulated size(%d) does not match total size (%d)", tracker->currentPacketReceived, tracker->currentPacketSize);
}
return result;
}
void channelTracker_free(ChannelStateTracker* t)
{
if (!t)
return;
Stream_Free(t->currentPacket, TRUE);
free(t);
}
/**
* Flushes the current accumulated tracker content, if it's the first packet, then
* when can just return that the packet shall be passed, otherwise to have to refragment
* the accumulated current packet.
*/
PfChannelResult channelTracker_flushCurrent(ChannelStateTracker* t, BOOL first, BOOL last, BOOL toBack)
{
proxyData* pdata;
pServerContext* ps;
pServerChannelContext* channel;
UINT32 flags = CHANNEL_FLAG_FIRST;
BOOL r;
const char* direction = toBack ? "F->B" : "B->F";
WLog_VRB(TAG, "channelTracker_flushCurrent(%s): %s sz=%d first=%d last=%d", t->channel->channel_name,
direction, Stream_GetPosition(t->currentPacket), first, last);
if (first)
return PF_CHANNEL_RESULT_PASS;
pdata = t->pdata;
channel = t->channel;
if (last)
flags |= CHANNEL_FLAG_LAST;
if (toBack)
{
proxyChannelDataEventInfo ev;
ev.channel_id = channel->channel_id;
ev.channel_name = channel->channel_name;
ev.data = Stream_Buffer(t->currentPacket);
ev.data_len = Stream_GetPosition(t->currentPacket);
ev.flags = flags;
ev.total_size = t->currentPacketSize;
if (!pdata->pc->sendChannelData)
return PF_CHANNEL_RESULT_ERROR;
return pdata->pc->sendChannelData(pdata->pc, &ev) ? PF_CHANNEL_RESULT_DROP : PF_CHANNEL_RESULT_ERROR;
}
ps = pdata->ps;
r = ps->context.peer->SendChannelPacket(ps->context.peer, channel->channel_id, t->currentPacketSize,
flags, Stream_Buffer(t->currentPacket), Stream_GetPosition(t->currentPacket));
return r ? PF_CHANNEL_RESULT_DROP : PF_CHANNEL_RESULT_ERROR;
}
static PfChannelResult pf_channel_generic_back_data(proxyData* pdata, const pServerChannelContext* channel,
const BYTE* xdata, size_t xsize, UINT32 flags,
size_t totalSize)
{
proxyChannelDataEventInfo ev;
switch(channel->channelMode) {
case PF_UTILS_CHANNEL_PASSTHROUGH:
ev.channel_id = channel->channel_id;
ev.channel_name = channel->channel_name;
ev.data = xdata;
ev.data_len = xsize;
ev.flags = flags;
ev.total_size = totalSize;
if (!pf_modules_run_filter(pdata->module, FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA, pdata, &ev))
return PF_CHANNEL_RESULT_DROP; /* Silently drop */
return PF_CHANNEL_RESULT_PASS;
case PF_UTILS_CHANNEL_INTERCEPT:
/* TODO */
case PF_UTILS_CHANNEL_BLOCK:
default:
return PF_CHANNEL_RESULT_DROP;
}
}
static PfChannelResult pf_channel_generic_front_data(proxyData* pdata, const pServerChannelContext* channel,
const BYTE* xdata, size_t xsize, UINT32 flags,
size_t totalSize)
{
proxyChannelDataEventInfo ev;
switch(channel->channelMode) {
case PF_UTILS_CHANNEL_PASSTHROUGH:
ev.channel_id = channel->channel_id;
ev.channel_name = channel->channel_name;
ev.data = xdata;
ev.data_len = xsize;
ev.flags = flags;
ev.total_size = totalSize;
if (!pf_modules_run_filter(pdata->module, FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA, pdata, &ev))
return PF_CHANNEL_RESULT_DROP; /* Silently drop */
return PF_CHANNEL_RESULT_PASS;
case PF_UTILS_CHANNEL_INTERCEPT:
/* TODO */
case PF_UTILS_CHANNEL_BLOCK:
default:
return PF_CHANNEL_RESULT_DROP;
}
}
BOOL pf_channel_setup_generic(pServerChannelContext* channel)
{
channel->onBackData = pf_channel_generic_back_data;
channel->onFrontData = pf_channel_generic_front_data;
return TRUE;
}

64
server/proxy/pf_channel.h Normal file
View File

@ -0,0 +1,64 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
*
*
* Copyright 2022 David Fort <contact@hardening-consulting.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef SERVER_PROXY_PF_CHANNEL_H_
#define SERVER_PROXY_PF_CHANNEL_H_
#include <freerdp/server/proxy/proxy_context.h>
/** @brief operating mode of a channel tracker */
typedef enum
{
CHANNEL_TRACKER_PEEK, /*!< inquiring content, accumulating packet fragments */
CHANNEL_TRACKER_PASS, /*!< pass all the fragments of the current packet */
CHANNEL_TRACKER_DROP /*!< drop all the fragments of the current packet */
} ChannelTrackerMode;
typedef struct _ChannelStateTracker ChannelStateTracker;
typedef PfChannelResult (*ChannelTrackerPeekFn)(ChannelStateTracker* tracker, BOOL first, BOOL lastPacket);
/** @brief a tracker for channel packets */
struct _ChannelStateTracker {
pServerChannelContext* channel;
ChannelTrackerMode mode;
wStream* currentPacket;
size_t currentPacketReceived;
size_t currentPacketSize;
size_t currentPacketFragments;
ChannelTrackerPeekFn peekFn;
void *trackerData;
proxyData* pdata;
};
ChannelStateTracker* channelTracker_new(pServerChannelContext* channel, ChannelTrackerPeekFn fn, void* data);
void channelTracker_free(ChannelStateTracker* t);
PfChannelResult channelTracker_update(ChannelStateTracker* tracker, const BYTE* xdata, size_t xsize, UINT32 flags,
size_t totalSize);
PfChannelResult channelTracker_flushCurrent(ChannelStateTracker* t, BOOL first, BOOL last, BOOL toFront);
BOOL pf_channel_setup_rdpdr(pServerContext* ps, pServerChannelContext* channel);
BOOL pf_channel_setup_generic(pServerChannelContext* channel);
#endif /* SERVER_PROXY_PF_CHANNEL_H_ */

View File

@ -36,6 +36,7 @@
#include <freerdp/channels/channels.h>
#include "pf_client.h"
#include "pf_channel.h"
#include <freerdp/server/proxy/proxy_context.h>
#include "pf_update.h"
#include "pf_input.h"
@ -333,7 +334,6 @@ static BOOL pf_client_pre_connect(freerdp* instance)
if (!pf_channel_smartcard_client_new(pc))
return FALSE;
#endif
/* Copy the current channel settings from the peer connection to the client. */
if (!freerdp_channels_from_mcs(settings, &ps->context))
return FALSE;
@ -374,126 +374,6 @@ static BOOL pf_client_pre_connect(freerdp* instance)
return pf_modules_run_hook(pc->pdata->module, HOOK_TYPE_CLIENT_PRE_CONNECT, pc->pdata, pc);
}
static BOOL pf_client_receive_channel_passthrough(proxyData* pdata,
const pServerChannelContext* channel,
const BYTE* xdata, size_t xsize, UINT32 flags,
size_t totalSize)
{
proxyChannelDataEventInfo ev;
UINT16 server_channel_id;
pServerContext* ps;
WINPR_ASSERT(pdata);
ps = pdata->ps;
WINPR_ASSERT(ps);
ev.channel_id = channel->channel_id;
ev.channel_name = channel->channel_name;
ev.data = xdata;
ev.data_len = xsize;
ev.flags = flags;
ev.total_size = totalSize;
if (!pf_modules_run_filter(pdata->module, FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA, pdata,
&ev))
return TRUE; /* Silently drop */
/* Dynamic channels need special treatment
*
* We need to check every message with CHANNEL_FLAG_FIRST set if it
* is a CREATE_REQUEST_PDU (0x01) and extract channelId and name
* from it.
*
* To avoid issues with (misbehaving) clients assume all packets
* that do not have at least a length of 1 byte and all incomplete
* CREATE_REQUEST_PDU (0x01) packets as invalid.
*/
if ((flags & CHANNEL_FLAG_FIRST) &&
(strncmp(channel->channel_name, DRDYNVC_SVC_CHANNEL_NAME, CHANNEL_NAME_LEN + 1) == 0))
{
BYTE cmd, first;
wStream *s, sbuffer;
s = Stream_StaticConstInit(&sbuffer, xdata, xsize);
if (Stream_Length(s) < 1)
return FALSE;
Stream_Read_UINT8(s, first);
cmd = first >> 4;
if (cmd == CREATE_REQUEST_PDU)
{
proxyChannelDataEventInfo dev;
size_t len, nameLen;
const char* name;
UINT32 dynChannelId;
BYTE cbId = first & 0x03;
switch (cbId)
{
case 0x00:
if (Stream_GetRemainingLength(s) < 1)
return FALSE;
Stream_Read_UINT8(s, dynChannelId);
break;
case 0x01:
if (Stream_GetRemainingLength(s) < 2)
return FALSE;
Stream_Read_UINT16(s, dynChannelId);
break;
case 0x02:
if (Stream_GetRemainingLength(s) < 4)
return FALSE;
Stream_Read_UINT32(s, dynChannelId);
break;
default:
return FALSE;
}
name = (const char*)Stream_Pointer(s);
nameLen = Stream_GetRemainingLength(s);
len = strnlen(name, nameLen);
if ((len == 0) || (len == nameLen))
return FALSE;
dev.channel_id = dynChannelId;
dev.channel_name = name;
dev.data = xdata;
dev.data_len = xsize;
dev.flags = flags;
dev.total_size = totalSize;
if (!pf_modules_run_filter(
pdata->module, FILTER_TYPE_CLIENT_PASSTHROUGH_DYN_CHANNEL_CREATE, pdata, &dev))
return TRUE; /* Silently drop */
}
}
server_channel_id = WTSChannelGetId(ps->context.peer, channel->channel_name);
/* Ignore messages for channels that can not be mapped.
* The client might not have enabled support for this specific channel,
* so just drop the message. */
if (server_channel_id == 0)
return TRUE;
return ps->context.peer->SendChannelPacket(ps->context.peer, server_channel_id, totalSize,
flags, xdata, xsize);
}
static BOOL pf_client_receive_channel_intercept(proxyData* pdata, UINT16 channelId,
const char* channel_name, const BYTE* xdata,
size_t xsize, UINT32 flags, size_t totalSize)
{
WINPR_ASSERT(pdata);
WINPR_ASSERT(channel_name);
if (strcmp(channel_name, RDPDR_SVC_CHANNEL_NAME) == 0)
return pf_channel_rdpdr_client_handle(pdata->pc, channelId, channel_name, xdata, xsize,
flags, totalSize);
WLog_ERR(TAG, "[%s]: Channel %s [0x%04" PRIx16 "] intercept mode not implemented, aborting",
__FUNCTION__, channel_name, channelId);
return FALSE;
}
static BOOL pf_client_receive_channel_data_hook(freerdp* instance, UINT16 channelId,
const BYTE* xdata, size_t xsize, UINT32 flags,
size_t totalSize)
@ -502,7 +382,8 @@ static BOOL pf_client_receive_channel_data_hook(freerdp* instance, UINT16 channe
pServerContext* ps;
proxyData* pdata;
pServerChannelContext* channel;
UINT32 channelId32 = channelId;
UINT16 server_channel_id;
UINT64 channelId64 = channelId;
WINPR_ASSERT(instance);
WINPR_ASSERT(xdata || (xsize == 0));
@ -517,26 +398,29 @@ static BOOL pf_client_receive_channel_data_hook(freerdp* instance, UINT16 channe
pdata = ps->pdata;
WINPR_ASSERT(pdata);
channel = HashTable_GetItemValue(ps->channelsById, &channelId32);
channel = HashTable_GetItemValue(ps->channelsById, &channelId64);
if (!channel)
return TRUE;
switch (channel->channelMode)
switch (channel->onBackData(pdata, channel, xdata, xsize, flags, totalSize))
{
case PF_UTILS_CHANNEL_BLOCK:
return TRUE; /* Silently drop */
case PF_UTILS_CHANNEL_PASSTHROUGH:
return pf_client_receive_channel_passthrough(pdata, channel, xdata, xsize, flags,
totalSize);
case PF_UTILS_CHANNEL_INTERCEPT:
return pf_client_receive_channel_intercept(pdata, channelId, channel->channel_name,
xdata, xsize, flags, totalSize);
case PF_UTILS_CHANNEL_NOT_HANDLED:
default:
WINPR_ASSERT(pc->client_receive_channel_data_original);
return pc->client_receive_channel_data_original(instance, channelId, xdata, xsize,
flags, totalSize);
case PF_CHANNEL_RESULT_PASS:
break;
case PF_CHANNEL_RESULT_DROP:
return TRUE;
case PF_CHANNEL_RESULT_ERROR:
return FALSE;
}
server_channel_id = WTSChannelGetId(ps->context.peer, channel->channel_name);
/* Ignore messages for channels that can not be mapped.
* The client might not have enabled support for this specific channel,
* so just drop the message. */
if (server_channel_id == 0)
return TRUE;
return ps->context.peer->SendChannelPacket(ps->context.peer, server_channel_id, totalSize, flags, xdata, xsize);
}
static BOOL pf_client_on_server_heartbeat(freerdp* instance, BYTE period, BYTE count1, BYTE count2)

View File

@ -960,7 +960,6 @@ static BOOL config_plugin_dynamic_channel_create(proxyPlugin* plugin, proxyData*
accept = TRUE;
break;
case PF_UTILS_CHANNEL_BLOCK:
case PF_UTILS_CHANNEL_NOT_HANDLED:
default:
accept = FALSE;
break;
@ -1028,7 +1027,6 @@ static BOOL config_plugin_channel_create(proxyPlugin* plugin, proxyData* pdata,
accept = TRUE;
break;
case PF_UTILS_CHANNEL_BLOCK:
case PF_UTILS_CHANNEL_NOT_HANDLED:
default:
accept = FALSE;
break;

View File

@ -37,16 +37,16 @@
static UINT32 ChannelId_Hash(const void* key)
{
const UINT32* v = (const UINT32*)key;
return *v;
const UINT64* v = (const UINT64*)key;
return (*v & 0xFFFFFFFF) + (*v >> 32);
}
static BOOL ChannelId_Compare(const UINT32* v1, const UINT32* v2)
static BOOL ChannelId_Compare(const UINT64* v1, const UINT64* v2)
{
return (*v1 == *v2);
}
pServerChannelContext* ChannelContext_new(pServerContext* ps, const char* name, UINT32 id)
pServerChannelContext* ChannelContext_new(pServerContext* ps, const char* name, UINT64 id)
{
pServerChannelContext* ret = calloc(1, sizeof(*ret));
if (!ret)
@ -55,6 +55,7 @@ pServerChannelContext* ChannelContext_new(pServerContext* ps, const char* name,
return NULL;
}
ret->openStatus = CHANNEL_OPENSTATE_OPENED;
ret->channel_id = id;
ret->channel_name = _strdup(name);
if (!ret->channel_name)
@ -73,6 +74,8 @@ void ChannelContext_free(pServerChannelContext* ctx)
if (!ctx)
return;
IFCALL(ctx->contextDtor,ctx->context);
free(ctx->channel_name);
free(ctx);
}

View File

@ -36,6 +36,7 @@
#include <freerdp/streamdump.h>
#include <freerdp/channels/wtsvc.h>
#include <freerdp/channels/channels.h>
#include <freerdp/channels/drdynvc.h>
#include <freerdp/build-config.h>
#include <freerdp/channels/rdpdr.h>
@ -44,12 +45,14 @@
#include <freerdp/server/proxy/proxy_log.h>
#include "pf_server.h"
#include "pf_channel.h"
#include <freerdp/server/proxy/proxy_config.h>
#include "pf_client.h"
#include <freerdp/server/proxy/proxy_context.h>
#include "pf_update.h"
#include "proxy_modules.h"
#include "pf_utils.h"
#include "channels/pf_channel_drdynvc.h"
#include "channels/pf_channel_rdpdr.h"
#define TAG PROXY_TAG("server")
@ -194,7 +197,7 @@ static BOOL pf_server_setup_channels(freerdp_peer* peer)
const char* cname = accepted_channels[i];
UINT16 channelId = WTSChannelGetId(peer, cname);
PROXY_LOG_INFO(TAG, ps, "Accepted channel: %s", cname);
PROXY_LOG_INFO(TAG, ps, "Accepted channel: %s (%d)", cname, channelId);
channelContext = ChannelContext_new(ps, cname, channelId);
if (!channelContext)
{
@ -202,6 +205,34 @@ static BOOL pf_server_setup_channels(freerdp_peer* peer)
return FALSE;
}
if (strcmp(cname, DRDYNVC_SVC_CHANNEL_NAME) == 0)
{
if (!pf_channel_setup_drdynvc(ps->pdata, channelContext))
{
PROXY_LOG_ERR(TAG, ps, "error while setting up dynamic channel");
ChannelContext_free(channelContext);
return FALSE;
}
}
else if (strcmp(cname, RDPDR_SVC_CHANNEL_NAME) == 0 && (channelContext->channelMode == PF_UTILS_CHANNEL_INTERCEPT))
{
if (!pf_channel_setup_rdpdr(ps, channelContext))
{
PROXY_LOG_ERR(TAG, ps, "error while setting up redirection channel");
ChannelContext_free(channelContext);
return FALSE;
}
}
else
{
if (!pf_channel_setup_generic(channelContext))
{
PROXY_LOG_ERR(TAG, ps, "error while setting up generic channel");
ChannelContext_free(channelContext);
return FALSE;
}
}
if (!HashTable_Insert(byId, &channelContext->channel_id, channelContext))
{
ChannelContext_free(channelContext);
@ -273,18 +304,6 @@ static BOOL pf_server_post_connect(freerdp_peer* peer)
if (!pf_modules_run_hook(pdata->module, HOOK_TYPE_SERVER_POST_CONNECT, pdata, peer))
return FALSE;
switch (pf_utils_get_channel_mode(ps->pdata->config, RDPDR_SVC_CHANNEL_NAME))
{
case PF_UTILS_CHANNEL_INTERCEPT:
if (!pf_channel_rdpdr_server_new(ps))
return FALSE;
if (!pf_channel_rdpdr_server_announce(ps))
return FALSE;
break;
default:
break;
}
/* Start a proxy's client in it's own thread */
if (!(pdata->client_thread = CreateThread(NULL, 0, pf_client_start, pc, 0, NULL)))
{
@ -349,22 +368,6 @@ static BOOL pf_server_adjust_monitor_layout(freerdp_peer* peer)
return TRUE;
}
static BOOL pf_server_receive_channel_intercept(proxyData* pdata, UINT16 channelId,
const char* channel_name, const BYTE* data,
size_t size, UINT32 flags, size_t totalSize)
{
WINPR_ASSERT(pdata);
WINPR_ASSERT(channel_name);
if (strcmp(channel_name, RDPDR_SVC_CHANNEL_NAME) == 0)
return pf_channel_rdpdr_server_handle(pdata->ps, channelId, channel_name, data, size, flags,
totalSize);
WLog_ERR(TAG, "[%s]: Channel %s [0x%04" PRIx16 "] intercept mode not implemented, aborting",
__FUNCTION__, channel_name, channelId);
return FALSE;
}
static BOOL pf_server_receive_channel_data_hook(freerdp_peer* peer, UINT16 channelId,
const BYTE* data, size_t size, UINT32 flags,
size_t totalSize)
@ -374,7 +377,7 @@ static BOOL pf_server_receive_channel_data_hook(freerdp_peer* peer, UINT16 chann
proxyData* pdata;
const proxyConfig* config;
const pServerChannelContext* channel;
UINT32 channelId32 = channelId;
UINT64 channelId64 = channelId;
WINPR_ASSERT(peer);
@ -395,40 +398,32 @@ static BOOL pf_server_receive_channel_data_hook(freerdp_peer* peer, UINT16 chann
if (!pc)
goto original_cb;
channel = HashTable_GetItemValue(ps->channelsById, &channelId32);
channel = HashTable_GetItemValue(ps->channelsById, &channelId64);
if (!channel)
{
PROXY_LOG_ERR(TAG, ps, "channel id=%d not registered here, dropping", channelId32);
PROXY_LOG_ERR(TAG, ps, "channel id=%d not registered here, dropping", channelId64);
return TRUE;
}
switch (channel->channelMode)
switch (channel->onFrontData(pdata, channel, data, size, flags, totalSize))
{
case PF_UTILS_CHANNEL_BLOCK:
return TRUE;
case PF_UTILS_CHANNEL_PASSTHROUGH:
{
proxyChannelDataEventInfo ev;
case PF_CHANNEL_RESULT_PASS: {
proxyChannelDataEventInfo ev;
ev.channel_id = channelId;
ev.channel_name = channel->channel_name;
ev.data = data;
ev.data_len = size;
ev.flags = flags;
ev.total_size = totalSize;
if (!pf_modules_run_filter(pdata->module, FILTER_TYPE_SERVER_PASSTHROUGH_CHANNEL_DATA,
pdata, &ev))
return TRUE; /* Silently ignore */
return IFCALLRESULT(TRUE, pc->sendChannelData, pc, &ev);
}
case PF_UTILS_CHANNEL_INTERCEPT:
return pf_server_receive_channel_intercept(pdata, channelId, channel->channel_name,
data, size, flags, totalSize);
default:
break;
ev.channel_id = channelId;
ev.channel_name = channel->channel_name;
ev.data = data;
ev.data_len = size;
ev.flags = flags;
ev.total_size = totalSize;
return IFCALLRESULT(TRUE, pc->sendChannelData, pc, &ev);
}
case PF_CHANNEL_RESULT_DROP:
return TRUE;
case PF_CHANNEL_RESULT_ERROR:
return FALSE;
}
original_cb:
WINPR_ASSERT(pdata->server_receive_channel_data_original);