709 lines
22 KiB
C
709 lines
22 KiB
C
/**
|
|
* FreeRDP: A Remote Desktop Protocol Implementation
|
|
* pf_channel_drdynvc
|
|
*
|
|
* Copyright 2022 David Fort <contact@hardening-consulting.com>
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
#include <winpr/assert.h>
|
|
|
|
#include <freerdp/channels/drdynvc.h>
|
|
#include <freerdp/utils/drdynvc.h>
|
|
#include <freerdp/server/proxy/proxy_log.h>
|
|
|
|
#include "pf_channel_drdynvc.h"
|
|
#include "../pf_channel.h"
|
|
#include "../proxy_modules.h"
|
|
#include "../pf_utils.h"
|
|
|
|
#define DTAG PROXY_TAG("drdynvc")
|
|
|
|
/** @brief channel opened status */
|
|
typedef enum
|
|
{
|
|
CHANNEL_OPENSTATE_WAITING_OPEN_STATUS, /*!< dynamic channel waiting for create response */
|
|
CHANNEL_OPENSTATE_OPENED, /*!< opened */
|
|
CHANNEL_OPENSTATE_CLOSED /*!< dynamic channel has been opened then closed */
|
|
} PfDynChannelOpenStatus;
|
|
|
|
typedef struct p_server_dynamic_channel_context pServerDynamicChannelContext;
|
|
typedef struct DynChannelTrackerState DynChannelTrackerState;
|
|
|
|
typedef PfChannelResult (*dynamic_channel_on_data_fn)(pServerContext* ps,
|
|
pServerDynamicChannelContext* channel,
|
|
BOOL isBackData, ChannelStateTracker* tracker,
|
|
BOOL firstPacket, BOOL lastPacket);
|
|
|
|
/** @brief tracker state for a drdynvc stream */
|
|
struct DynChannelTrackerState
|
|
{
|
|
UINT32 currentDataLength;
|
|
UINT32 CurrentDataReceived;
|
|
UINT32 CurrentDataFragments;
|
|
wStream* currentPacket;
|
|
dynamic_channel_on_data_fn dataCallback;
|
|
};
|
|
|
|
typedef void (*channel_data_dtor_fn)(void** user_data);
|
|
|
|
struct p_server_dynamic_channel_context
|
|
{
|
|
char* channelName;
|
|
UINT32 channelId;
|
|
PfDynChannelOpenStatus openStatus;
|
|
pf_utils_channel_mode channelMode;
|
|
BOOL packetReassembly;
|
|
DynChannelTrackerState backTracker;
|
|
DynChannelTrackerState frontTracker;
|
|
|
|
void* channelData;
|
|
channel_data_dtor_fn channelDataDtor;
|
|
};
|
|
|
|
/** @brief context for the dynamic channel */
|
|
typedef struct
|
|
{
|
|
wHashTable* channels;
|
|
ChannelStateTracker* backTracker;
|
|
ChannelStateTracker* frontTracker;
|
|
wLog* log;
|
|
} DynChannelContext;
|
|
|
|
/** @brief result of dynamic channel packet treatment */
|
|
typedef enum
|
|
{
|
|
DYNCVC_READ_OK, /*!< read was OK */
|
|
DYNCVC_READ_ERROR, /*!< an error happened during read */
|
|
DYNCVC_READ_INCOMPLETE /*!< missing bytes to read the complete packet */
|
|
} DynvcReadResult;
|
|
|
|
static const char* openstatus2str(PfDynChannelOpenStatus status)
|
|
{
|
|
switch (status)
|
|
{
|
|
case CHANNEL_OPENSTATE_WAITING_OPEN_STATUS:
|
|
return "CHANNEL_OPENSTATE_WAITING_OPEN_STATUS";
|
|
case CHANNEL_OPENSTATE_CLOSED:
|
|
return "CHANNEL_OPENSTATE_CLOSED";
|
|
case CHANNEL_OPENSTATE_OPENED:
|
|
return "CHANNEL_OPENSTATE_OPENED";
|
|
default:
|
|
return "CHANNEL_OPENSTATE_UNKNOWN";
|
|
}
|
|
}
|
|
|
|
static PfChannelResult data_cb(pServerContext* ps, pServerDynamicChannelContext* channel,
|
|
BOOL isBackData, ChannelStateTracker* tracker, BOOL firstPacket,
|
|
BOOL lastPacket)
|
|
{
|
|
WINPR_ASSERT(ps);
|
|
WINPR_ASSERT(channel);
|
|
WINPR_ASSERT(tracker);
|
|
WINPR_ASSERT(ps->pdata);
|
|
|
|
wStream* currentPacket = channelTracker_getCurrentPacket(tracker);
|
|
proxyDynChannelInterceptData dyn = { .name = channel->channelName,
|
|
.channelId = channel->channelId,
|
|
.data = currentPacket,
|
|
.isBackData = isBackData,
|
|
.first = firstPacket,
|
|
.last = lastPacket,
|
|
.rewritten = FALSE,
|
|
.packetSize = channelTracker_getCurrentPacketSize(tracker),
|
|
.result = PF_CHANNEL_RESULT_ERROR };
|
|
Stream_SealLength(dyn.data);
|
|
if (!pf_modules_run_filter(ps->pdata->module, FILTER_TYPE_INTERCEPT_CHANNEL, ps->pdata, &dyn))
|
|
return PF_CHANNEL_RESULT_ERROR;
|
|
|
|
channelTracker_setCurrentPacketSize(tracker, dyn.packetSize);
|
|
if (dyn.rewritten)
|
|
return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, !isBackData);
|
|
return dyn.result;
|
|
}
|
|
|
|
static pServerDynamicChannelContext* DynamicChannelContext_new(wLog* log, pServerContext* ps,
|
|
const char* name, UINT32 id)
|
|
{
|
|
WINPR_ASSERT(log);
|
|
|
|
pServerDynamicChannelContext* ret = calloc(1, sizeof(*ret));
|
|
if (!ret)
|
|
{
|
|
WLog_Print(log, WLOG_ERROR, "error allocating dynamic channel context '%s'", name);
|
|
return NULL;
|
|
}
|
|
|
|
ret->channelId = id;
|
|
ret->channelName = _strdup(name);
|
|
if (!ret->channelName)
|
|
{
|
|
WLog_Print(log, WLOG_ERROR, "error allocating name in dynamic channel context '%s'", name);
|
|
free(ret);
|
|
return NULL;
|
|
}
|
|
|
|
ret->frontTracker.dataCallback = data_cb;
|
|
ret->backTracker.dataCallback = data_cb;
|
|
|
|
proxyChannelToInterceptData dyn = { .name = name, .channelId = id, .intercept = FALSE };
|
|
if (pf_modules_run_filter(ps->pdata->module, FILTER_TYPE_DYN_INTERCEPT_LIST, ps->pdata, &dyn) &&
|
|
dyn.intercept)
|
|
ret->channelMode = PF_UTILS_CHANNEL_INTERCEPT;
|
|
else
|
|
ret->channelMode = pf_utils_get_channel_mode(ps->pdata->config, name);
|
|
ret->openStatus = CHANNEL_OPENSTATE_OPENED;
|
|
ret->packetReassembly = (ret->channelMode == PF_UTILS_CHANNEL_INTERCEPT);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void DynamicChannelContext_free(void* ptr)
|
|
{
|
|
pServerDynamicChannelContext* c = (pServerDynamicChannelContext*)ptr;
|
|
if (!c)
|
|
return;
|
|
|
|
if (c->backTracker.currentPacket)
|
|
Stream_Free(c->backTracker.currentPacket, TRUE);
|
|
|
|
if (c->frontTracker.currentPacket)
|
|
Stream_Free(c->frontTracker.currentPacket, TRUE);
|
|
|
|
if (c->channelDataDtor)
|
|
c->channelDataDtor(&c->channelData);
|
|
|
|
free(c->channelName);
|
|
free(c);
|
|
}
|
|
|
|
static UINT32 ChannelId_Hash(const void* key)
|
|
{
|
|
const UINT32* v = (const UINT32*)key;
|
|
return *v;
|
|
}
|
|
|
|
static BOOL ChannelId_Compare(const void* objA, const void* objB)
|
|
{
|
|
const UINT32* v1 = objA;
|
|
const UINT32* v2 = objB;
|
|
return (*v1 == *v2);
|
|
}
|
|
|
|
static DynvcReadResult dynvc_read_varInt(wLog* log, wStream* s, size_t len, UINT64* varInt,
|
|
BOOL last)
|
|
{
|
|
WINPR_ASSERT(varInt);
|
|
switch (len)
|
|
{
|
|
case 0x00:
|
|
if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 1))
|
|
return last ? DYNCVC_READ_ERROR : DYNCVC_READ_INCOMPLETE;
|
|
Stream_Read_UINT8(s, *varInt);
|
|
break;
|
|
case 0x01:
|
|
if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 2))
|
|
return last ? DYNCVC_READ_ERROR : DYNCVC_READ_INCOMPLETE;
|
|
Stream_Read_UINT16(s, *varInt);
|
|
break;
|
|
case 0x02:
|
|
if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 4))
|
|
return last ? DYNCVC_READ_ERROR : DYNCVC_READ_INCOMPLETE;
|
|
Stream_Read_UINT32(s, *varInt);
|
|
break;
|
|
case 0x03:
|
|
default:
|
|
WLog_Print(log, WLOG_ERROR, "Unknown int len %" PRIuz, len);
|
|
return DYNCVC_READ_ERROR;
|
|
}
|
|
return DYNCVC_READ_OK;
|
|
}
|
|
|
|
static PfChannelResult DynvcTrackerPeekFn(ChannelStateTracker* tracker, BOOL firstPacket,
|
|
BOOL lastPacket)
|
|
{
|
|
BYTE cmd, byte0;
|
|
wStream *s, sbuffer;
|
|
BOOL haveChannelId;
|
|
BOOL haveLength;
|
|
UINT64 dynChannelId = 0;
|
|
UINT64 Length = 0;
|
|
pServerDynamicChannelContext* dynChannel = NULL;
|
|
|
|
WINPR_ASSERT(tracker);
|
|
|
|
DynChannelContext* dynChannelContext =
|
|
(DynChannelContext*)channelTracker_getCustomData(tracker);
|
|
WINPR_ASSERT(dynChannelContext);
|
|
|
|
BOOL isBackData = (tracker == dynChannelContext->backTracker);
|
|
DynChannelTrackerState* trackerState = NULL;
|
|
|
|
UINT32 flags = lastPacket ? CHANNEL_FLAG_LAST : 0;
|
|
proxyData* pdata = channelTracker_getPData(tracker);
|
|
WINPR_ASSERT(pdata);
|
|
|
|
const char* direction = isBackData ? "B->F" : "F->B";
|
|
|
|
{
|
|
wStream* currentPacket = channelTracker_getCurrentPacket(tracker);
|
|
s = Stream_StaticConstInit(&sbuffer, Stream_Buffer(currentPacket),
|
|
Stream_GetPosition(currentPacket));
|
|
}
|
|
|
|
if (!Stream_CheckAndLogRequiredLengthWLog(dynChannelContext->log, s, 1))
|
|
return PF_CHANNEL_RESULT_ERROR;
|
|
|
|
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)
|
|
{
|
|
BYTE cbId = byte0 & 0x03;
|
|
|
|
switch (dynvc_read_varInt(dynChannelContext->log, s, cbId, &dynChannelId, lastPacket))
|
|
{
|
|
case DYNCVC_READ_OK:
|
|
break;
|
|
case DYNCVC_READ_INCOMPLETE:
|
|
return PF_CHANNEL_RESULT_DROP;
|
|
case DYNCVC_READ_ERROR:
|
|
default:
|
|
WLog_Print(dynChannelContext->log, WLOG_ERROR,
|
|
"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
|
|
*/
|
|
dynChannel = (pServerDynamicChannelContext*)HashTable_GetItemValue(
|
|
dynChannelContext->channels, &dynChannelId);
|
|
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 */
|
|
channelTracker_setMode(tracker, CHANNEL_TRACKER_DROP);
|
|
return PF_CHANNEL_RESULT_DROP;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (haveLength)
|
|
{
|
|
BYTE lenLen = (byte0 >> 2) & 0x03;
|
|
switch (dynvc_read_varInt(dynChannelContext->log, s, lenLen, &Length, lastPacket))
|
|
{
|
|
case DYNCVC_READ_OK:
|
|
break;
|
|
case DYNCVC_READ_INCOMPLETE:
|
|
return PF_CHANNEL_RESULT_DROP;
|
|
case DYNCVC_READ_ERROR:
|
|
default:
|
|
WLog_Print(dynChannelContext->log, WLOG_ERROR,
|
|
"DynvcTrackerPeekFn: invalid length field");
|
|
return PF_CHANNEL_RESULT_ERROR;
|
|
}
|
|
}
|
|
|
|
switch (cmd)
|
|
{
|
|
case CAPABILITY_REQUEST_PDU:
|
|
WLog_Print(dynChannelContext->log, WLOG_DEBUG, "DynvcTracker: %s CAPABILITY_%s",
|
|
direction, isBackData ? "REQUEST" : "RESPONSE");
|
|
channelTracker_setMode(tracker, 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 = { 0 };
|
|
const char* name = Stream_ConstPointer(s);
|
|
const size_t nameLen = Stream_GetRemainingLength(s);
|
|
|
|
const size_t len = strnlen(name, nameLen);
|
|
if ((len == 0) || (len == nameLen))
|
|
return PF_CHANNEL_RESULT_ERROR;
|
|
|
|
wStream* currentPacket = channelTracker_getCurrentPacket(tracker);
|
|
dev.channel_id = dynChannelId;
|
|
dev.channel_name = name;
|
|
dev.data = Stream_Buffer(s);
|
|
dev.data_len = Stream_GetPosition(currentPacket);
|
|
dev.flags = flags;
|
|
dev.total_size = Stream_GetPosition(currentPacket);
|
|
|
|
if (dynChannel)
|
|
{
|
|
WLog_Print(
|
|
dynChannelContext->log, WLOG_WARN,
|
|
"Reusing channel id %" PRIu32 ", previously %s [state %s, mode %s], now %s",
|
|
dynChannel->channelId, dynChannel->channelName,
|
|
openstatus2str(dynChannel->openStatus),
|
|
pf_utils_channel_mode_string(dynChannel->channelMode), dev.channel_name);
|
|
|
|
HashTable_Remove(dynChannelContext->channels, &dynChannel->channelId);
|
|
}
|
|
|
|
if (!pf_modules_run_filter(pdata->module,
|
|
FILTER_TYPE_CLIENT_PASSTHROUGH_DYN_CHANNEL_CREATE, pdata,
|
|
&dev))
|
|
return PF_CHANNEL_RESULT_DROP; /* Silently drop */
|
|
|
|
dynChannel = DynamicChannelContext_new(dynChannelContext->log, pdata->ps, name,
|
|
dynChannelId);
|
|
if (!dynChannel)
|
|
{
|
|
WLog_Print(dynChannelContext->log, WLOG_ERROR,
|
|
"unable to create dynamic channel context data");
|
|
return PF_CHANNEL_RESULT_ERROR;
|
|
}
|
|
|
|
WLog_Print(dynChannelContext->log, WLOG_DEBUG, "Adding channel '%s'[%d]",
|
|
dynChannel->channelName, dynChannel->channelId);
|
|
if (!HashTable_Insert(dynChannelContext->channels, &dynChannel->channelId,
|
|
dynChannel))
|
|
{
|
|
WLog_Print(dynChannelContext->log, WLOG_ERROR,
|
|
"unable register dynamic channel context data");
|
|
DynamicChannelContext_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_CheckAndLogRequiredLengthWLog(dynChannelContext->log, s, 4))
|
|
return PF_CHANNEL_RESULT_ERROR;
|
|
|
|
Stream_Read_UINT32(s, creationStatus);
|
|
WLog_Print(dynChannelContext->log, WLOG_DEBUG,
|
|
"DynvcTracker(%" PRIu64 ",%s): %s CREATE_RESPONSE openStatus=%" PRIu32,
|
|
dynChannelId, dynChannel->channelName, direction, creationStatus);
|
|
|
|
if (creationStatus == 0)
|
|
dynChannel->openStatus = CHANNEL_OPENSTATE_OPENED;
|
|
|
|
return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, TRUE);
|
|
}
|
|
|
|
case CLOSE_REQUEST_PDU:
|
|
if (!lastPacket)
|
|
return PF_CHANNEL_RESULT_DROP;
|
|
|
|
WLog_Print(dynChannelContext->log, WLOG_DEBUG,
|
|
"DynvcTracker(%s): %s Close request on channel", dynChannel->channelName,
|
|
direction);
|
|
channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS);
|
|
if (dynChannel->openStatus != CHANNEL_OPENSTATE_OPENED)
|
|
{
|
|
WLog_Print(dynChannelContext->log, WLOG_WARN,
|
|
"DynvcTracker(%s): is in state %s, expected %s", dynChannel->channelName,
|
|
openstatus2str(dynChannel->openStatus),
|
|
openstatus2str(CHANNEL_OPENSTATE_OPENED));
|
|
}
|
|
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_Print(dynChannelContext->log, WLOG_DEBUG, "SOFT_SYNC_REQUEST_PDU");
|
|
channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS);
|
|
/*TODO: return pf_treat_softsync_req(pdata, s);*/
|
|
return PF_CHANNEL_RESULT_PASS;
|
|
|
|
case SOFT_SYNC_RESPONSE_PDU:
|
|
/* just pass then as is for now */
|
|
WLog_Print(dynChannelContext->log, WLOG_DEBUG, "SOFT_SYNC_RESPONSE_PDU");
|
|
channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS);
|
|
return PF_CHANNEL_RESULT_PASS;
|
|
|
|
case DATA_FIRST_PDU:
|
|
case DATA_PDU:
|
|
/* treat these below */
|
|
trackerState = isBackData ? &dynChannel->backTracker : &dynChannel->frontTracker;
|
|
break;
|
|
|
|
case DATA_FIRST_COMPRESSED_PDU:
|
|
case DATA_COMPRESSED_PDU:
|
|
WLog_Print(dynChannelContext->log, WLOG_DEBUG,
|
|
"TODO: compressed data packets, pass them as is for now");
|
|
channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS);
|
|
return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, !isBackData);
|
|
|
|
default:
|
|
return PF_CHANNEL_RESULT_ERROR;
|
|
}
|
|
|
|
if (dynChannel->openStatus != CHANNEL_OPENSTATE_OPENED)
|
|
{
|
|
WLog_Print(dynChannelContext->log, WLOG_ERROR,
|
|
"DynvcTracker(%s [%s]): channel is not opened", dynChannel->channelName,
|
|
drdynvc_get_packet_type(cmd));
|
|
return PF_CHANNEL_RESULT_ERROR;
|
|
}
|
|
|
|
if ((cmd == DATA_FIRST_PDU) || (cmd == DATA_FIRST_COMPRESSED_PDU))
|
|
{
|
|
WLog_Print(dynChannelContext->log, WLOG_DEBUG,
|
|
"DynvcTracker(%s [%s]): %s DATA_FIRST currentPacketLength=%" PRIu64 "",
|
|
dynChannel->channelName, drdynvc_get_packet_type(cmd), direction, Length);
|
|
trackerState->currentDataLength = Length;
|
|
trackerState->CurrentDataReceived = 0;
|
|
trackerState->CurrentDataFragments = 0;
|
|
|
|
if (dynChannel->packetReassembly)
|
|
{
|
|
if (trackerState->currentPacket)
|
|
Stream_SetPosition(trackerState->currentPacket, 0);
|
|
}
|
|
}
|
|
|
|
if (cmd == DATA_PDU || cmd == DATA_FIRST_PDU)
|
|
{
|
|
size_t extraSize = Stream_GetRemainingLength(s);
|
|
|
|
trackerState->CurrentDataFragments++;
|
|
trackerState->CurrentDataReceived += extraSize;
|
|
|
|
if (dynChannel->packetReassembly)
|
|
{
|
|
if (!trackerState->currentPacket)
|
|
{
|
|
trackerState->currentPacket = Stream_New(NULL, 1024);
|
|
if (!trackerState->currentPacket)
|
|
{
|
|
WLog_Print(dynChannelContext->log, WLOG_ERROR,
|
|
"unable to create current packet");
|
|
return PF_CHANNEL_RESULT_ERROR;
|
|
}
|
|
}
|
|
|
|
if (!Stream_EnsureRemainingCapacity(trackerState->currentPacket, extraSize))
|
|
{
|
|
WLog_Print(dynChannelContext->log, WLOG_ERROR, "unable to grow current packet");
|
|
return PF_CHANNEL_RESULT_ERROR;
|
|
}
|
|
|
|
Stream_Write(trackerState->currentPacket, Stream_ConstPointer(s), extraSize);
|
|
}
|
|
WLog_Print(dynChannelContext->log, WLOG_DEBUG,
|
|
"DynvcTracker(%s [%s]): %s frags=%" PRIu32 " received=%" PRIu32 "(%" PRIu32 ")",
|
|
dynChannel->channelName, drdynvc_get_packet_type(cmd), direction,
|
|
trackerState->CurrentDataFragments, trackerState->CurrentDataReceived,
|
|
trackerState->currentDataLength);
|
|
}
|
|
|
|
if (cmd == DATA_PDU)
|
|
{
|
|
if (trackerState->currentDataLength)
|
|
{
|
|
if (trackerState->CurrentDataReceived > trackerState->currentDataLength)
|
|
{
|
|
WLog_Print(dynChannelContext->log, WLOG_ERROR,
|
|
"DynvcTracker (%s [%s]): reassembled packet (%" PRIu32
|
|
") is bigger than announced length (%" PRIu32 ")",
|
|
dynChannel->channelName, drdynvc_get_packet_type(cmd),
|
|
trackerState->CurrentDataReceived, trackerState->currentDataLength);
|
|
return PF_CHANNEL_RESULT_ERROR;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
trackerState->CurrentDataFragments = 0;
|
|
trackerState->CurrentDataReceived = 0;
|
|
}
|
|
}
|
|
|
|
PfChannelResult result;
|
|
switch (dynChannel->channelMode)
|
|
{
|
|
case PF_UTILS_CHANNEL_PASSTHROUGH:
|
|
result = channelTracker_flushCurrent(tracker, firstPacket, lastPacket, !isBackData);
|
|
break;
|
|
case PF_UTILS_CHANNEL_BLOCK:
|
|
channelTracker_setMode(tracker, CHANNEL_TRACKER_DROP);
|
|
result = PF_CHANNEL_RESULT_DROP;
|
|
break;
|
|
case PF_UTILS_CHANNEL_INTERCEPT:
|
|
if (trackerState->dataCallback)
|
|
{
|
|
result = trackerState->dataCallback(pdata->ps, dynChannel, isBackData, tracker,
|
|
firstPacket, lastPacket);
|
|
}
|
|
else
|
|
{
|
|
WLog_Print(dynChannelContext->log, WLOG_ERROR,
|
|
"no intercept callback for channel %s(fromBack=%d), dropping packet",
|
|
dynChannel->channelName, isBackData);
|
|
result = PF_CHANNEL_RESULT_DROP;
|
|
}
|
|
break;
|
|
default:
|
|
WLog_Print(dynChannelContext->log, WLOG_ERROR, "unknown channel mode %d",
|
|
dynChannel->channelMode);
|
|
result = PF_CHANNEL_RESULT_ERROR;
|
|
break;
|
|
}
|
|
|
|
if (!trackerState->currentDataLength ||
|
|
(trackerState->CurrentDataReceived == trackerState->currentDataLength))
|
|
{
|
|
trackerState->currentDataLength = 0;
|
|
trackerState->CurrentDataFragments = 0;
|
|
trackerState->CurrentDataReceived = 0;
|
|
|
|
if (dynChannel->packetReassembly && trackerState->currentPacket)
|
|
Stream_SetPosition(trackerState->currentPacket, 0);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static void DynChannelContext_free(void* context)
|
|
{
|
|
DynChannelContext* c = context;
|
|
if (!c)
|
|
return;
|
|
channelTracker_free(c->backTracker);
|
|
channelTracker_free(c->frontTracker);
|
|
HashTable_Free(c->channels);
|
|
free(c);
|
|
}
|
|
|
|
static const char* dynamic_context(void* arg)
|
|
{
|
|
proxyData* pdata = arg;
|
|
if (!pdata)
|
|
return "pdata=null";
|
|
return pdata->session_id;
|
|
}
|
|
|
|
static DynChannelContext* DynChannelContext_new(proxyData* pdata,
|
|
pServerStaticChannelContext* channel)
|
|
{
|
|
DynChannelContext* dyn = calloc(1, sizeof(DynChannelContext));
|
|
if (!dyn)
|
|
return FALSE;
|
|
|
|
dyn->log = WLog_Get(DTAG);
|
|
WINPR_ASSERT(dyn->log);
|
|
WLog_SetContext(dyn->log, dynamic_context, pdata);
|
|
|
|
dyn->backTracker = channelTracker_new(channel, DynvcTrackerPeekFn, dyn);
|
|
if (!dyn->backTracker)
|
|
goto fail;
|
|
if (!channelTracker_setPData(dyn->backTracker, pdata))
|
|
goto fail;
|
|
|
|
dyn->frontTracker = channelTracker_new(channel, DynvcTrackerPeekFn, dyn);
|
|
if (!dyn->frontTracker)
|
|
goto fail;
|
|
if (!channelTracker_setPData(dyn->frontTracker, pdata))
|
|
goto fail;
|
|
|
|
dyn->channels = HashTable_New(FALSE);
|
|
if (!dyn->channels)
|
|
goto fail;
|
|
|
|
if (!HashTable_SetHashFunction(dyn->channels, ChannelId_Hash))
|
|
goto fail;
|
|
|
|
wObject* kobj = HashTable_KeyObject(dyn->channels);
|
|
WINPR_ASSERT(kobj);
|
|
kobj->fnObjectEquals = ChannelId_Compare;
|
|
|
|
wObject* vobj = HashTable_ValueObject(dyn->channels);
|
|
WINPR_ASSERT(vobj);
|
|
vobj->fnObjectFree = DynamicChannelContext_free;
|
|
|
|
return dyn;
|
|
|
|
fail:
|
|
DynChannelContext_free(dyn);
|
|
return NULL;
|
|
}
|
|
|
|
static PfChannelResult pf_dynvc_back_data(proxyData* pdata,
|
|
const pServerStaticChannelContext* channel,
|
|
const BYTE* xdata, size_t xsize, UINT32 flags,
|
|
size_t totalSize)
|
|
{
|
|
WINPR_ASSERT(channel);
|
|
|
|
DynChannelContext* dyn = (DynChannelContext*)channel->context;
|
|
WINPR_UNUSED(pdata);
|
|
WINPR_ASSERT(dyn);
|
|
|
|
return channelTracker_update(dyn->backTracker, xdata, xsize, flags, totalSize);
|
|
}
|
|
|
|
static PfChannelResult pf_dynvc_front_data(proxyData* pdata,
|
|
const pServerStaticChannelContext* channel,
|
|
const BYTE* xdata, size_t xsize, UINT32 flags,
|
|
size_t totalSize)
|
|
{
|
|
WINPR_ASSERT(channel);
|
|
|
|
DynChannelContext* dyn = (DynChannelContext*)channel->context;
|
|
WINPR_UNUSED(pdata);
|
|
WINPR_ASSERT(dyn);
|
|
|
|
return channelTracker_update(dyn->frontTracker, xdata, xsize, flags, totalSize);
|
|
}
|
|
|
|
BOOL pf_channel_setup_drdynvc(proxyData* pdata, pServerStaticChannelContext* channel)
|
|
{
|
|
DynChannelContext* ret = DynChannelContext_new(pdata, channel);
|
|
if (!ret)
|
|
return FALSE;
|
|
|
|
channel->onBackData = pf_dynvc_back_data;
|
|
channel->onFrontData = pf_dynvc_front_data;
|
|
channel->contextDtor = DynChannelContext_free;
|
|
channel->context = ret;
|
|
return TRUE;
|
|
}
|