Milestone 5: deliver embedded RDP sessions and lifecycle hardening
This commit is contained in:
8
third_party/FreeRDP/server/proxy/channels/CMakeLists.txt
vendored
Normal file
8
third_party/FreeRDP/server/proxy/channels/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,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)
|
||||
list(APPEND SOURCES pf_channel_smartcard.c pf_channel_smartcard.h)
|
||||
endif()
|
||||
|
||||
add_library(${MODULE_NAME} OBJECT ${SOURCES})
|
||||
924
third_party/FreeRDP/server/proxy/channels/pf_channel_drdynvc.c
vendored
Normal file
924
third_party/FreeRDP/server/proxy/channels/pf_channel_drdynvc.c
vendored
Normal file
@@ -0,0 +1,924 @@
|
||||
/**
|
||||
* 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")
|
||||
|
||||
#define Stream_CheckAndLogRequiredLengthWLogWithBackend(log, s, nmemb, backdata) \
|
||||
Stream_CheckAndLogRequiredLengthWLogEx(log, WLOG_WARN, s, nmemb, 1, "%s(%s:%" PRIuz ")[%s]", \
|
||||
__func__, __FILE__, (size_t)__LINE__, \
|
||||
getDirection(backdata))
|
||||
|
||||
/** @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;
|
||||
WINPR_ATTR_NODISCARD 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";
|
||||
}
|
||||
}
|
||||
|
||||
#define DynvcTrackerLog(log, level, dynChannel, cmd, isBackData, ...) \
|
||||
dyn_log_((log), (level), (dynChannel), (cmd), (isBackData), __func__, __FILE__, __LINE__, \
|
||||
__VA_ARGS__)
|
||||
|
||||
WINPR_ATTR_NODISCARD
|
||||
static const char* getDirection(BOOL isBackData)
|
||||
{
|
||||
return isBackData ? "B->F" : "F->B";
|
||||
}
|
||||
|
||||
static void dyn_log_(wLog* log, DWORD level, const pServerDynamicChannelContext* dynChannel,
|
||||
BYTE cmd, BOOL isBackData, const char* fkt, const char* file, size_t line,
|
||||
const char* fmt, ...)
|
||||
{
|
||||
if (!WLog_IsLevelActive(log, level))
|
||||
return;
|
||||
|
||||
char* prefix = nullptr;
|
||||
char* msg = nullptr;
|
||||
size_t prefixlen = 0;
|
||||
size_t msglen = 0;
|
||||
|
||||
uint32_t channelId = dynChannel ? dynChannel->channelId : UINT32_MAX;
|
||||
const char* channelName = dynChannel ? dynChannel->channelName : "<nullptr>";
|
||||
(void)winpr_asprintf(&prefix, &prefixlen, "DynvcTracker[%s](%s [%s:%" PRIu32 "])",
|
||||
getDirection(isBackData), channelName, drdynvc_get_packet_type(cmd),
|
||||
channelId);
|
||||
|
||||
va_list ap = WINPR_C_ARRAY_INIT;
|
||||
va_start(ap, fmt);
|
||||
(void)winpr_vasprintf(&msg, &msglen, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
WLog_PrintTextMessage(log, level, line, file, fkt, "%s: %s", prefix, msg);
|
||||
free(prefix);
|
||||
free(msg);
|
||||
}
|
||||
|
||||
WINPR_ATTR_NODISCARD
|
||||
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 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);
|
||||
}
|
||||
|
||||
WINPR_ATTR_MALLOC(DynamicChannelContext_free, 1)
|
||||
WINPR_ATTR_NODISCARD
|
||||
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 nullptr;
|
||||
}
|
||||
|
||||
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 nullptr;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
WINPR_ATTR_NODISCARD
|
||||
static UINT32 ChannelId_Hash(const void* key)
|
||||
{
|
||||
const UINT32* v = (const UINT32*)key;
|
||||
return *v;
|
||||
}
|
||||
|
||||
WINPR_ATTR_NODISCARD
|
||||
static BOOL ChannelId_Compare(const void* objA, const void* objB)
|
||||
{
|
||||
const UINT32* v1 = objA;
|
||||
const UINT32* v2 = objB;
|
||||
return (*v1 == *v2);
|
||||
}
|
||||
|
||||
WINPR_ATTR_NODISCARD
|
||||
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;
|
||||
}
|
||||
|
||||
WINPR_ATTR_NODISCARD
|
||||
static PfChannelResult DynvcTrackerPeekHandleByMode(ChannelStateTracker* tracker,
|
||||
DynChannelTrackerState* trackerState,
|
||||
pServerDynamicChannelContext* dynChannel,
|
||||
BYTE cmd, BOOL firstPacket, BOOL lastPacket)
|
||||
{
|
||||
WINPR_ASSERT(tracker);
|
||||
WINPR_ASSERT(trackerState);
|
||||
WINPR_ASSERT(dynChannel);
|
||||
PfChannelResult result = PF_CHANNEL_RESULT_ERROR;
|
||||
|
||||
DynChannelContext* dynChannelContext =
|
||||
(DynChannelContext*)channelTracker_getCustomData(tracker);
|
||||
WINPR_ASSERT(dynChannelContext);
|
||||
|
||||
proxyData* pdata = channelTracker_getPData(tracker);
|
||||
WINPR_ASSERT(pdata);
|
||||
|
||||
const BOOL isBackData = (tracker == dynChannelContext->backTracker);
|
||||
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
|
||||
{
|
||||
DynvcTrackerLog(dynChannelContext->log, WLOG_ERROR, dynChannel, cmd, isBackData,
|
||||
"no intercept callback for channel, dropping packet");
|
||||
result = PF_CHANNEL_RESULT_DROP;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
DynvcTrackerLog(dynChannelContext->log, WLOG_ERROR, dynChannel, cmd, isBackData,
|
||||
"unknown channel mode %u", 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_ResetPosition(trackerState->currentPacket);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
WINPR_ATTR_NODISCARD
|
||||
static PfChannelResult DynvcTrackerHandleClose(ChannelStateTracker* tracker,
|
||||
pServerDynamicChannelContext* dynChannel,
|
||||
DynChannelContext* dynChannelContext,
|
||||
BOOL firstPacket, BOOL lastPacket)
|
||||
{
|
||||
WINPR_ASSERT(dynChannelContext);
|
||||
|
||||
const BOOL isBackData = (tracker == dynChannelContext->backTracker);
|
||||
|
||||
if (!lastPacket || !dynChannel)
|
||||
return PF_CHANNEL_RESULT_DROP;
|
||||
|
||||
DynvcTrackerLog(dynChannelContext->log, WLOG_DEBUG, dynChannel, CLOSE_REQUEST_PDU, isBackData,
|
||||
"Close request");
|
||||
channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS);
|
||||
if (dynChannel->openStatus != CHANNEL_OPENSTATE_OPENED)
|
||||
{
|
||||
DynvcTrackerLog(dynChannelContext->log, WLOG_DEBUG, dynChannel, CLOSE_REQUEST_PDU,
|
||||
isBackData, "is in state %s, expected %s",
|
||||
openstatus2str(dynChannel->openStatus),
|
||||
openstatus2str(CHANNEL_OPENSTATE_OPENED));
|
||||
}
|
||||
dynChannel->openStatus = CHANNEL_OPENSTATE_CLOSED;
|
||||
return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, !isBackData);
|
||||
}
|
||||
|
||||
WINPR_ATTR_NODISCARD
|
||||
static PfChannelResult DynvcTrackerHandleCreateBack(ChannelStateTracker* tracker, wStream* s,
|
||||
DWORD flags, proxyData* pdata,
|
||||
pServerDynamicChannelContext* dynChannel,
|
||||
DynChannelContext* dynChannelContext,
|
||||
UINT64 dynChannelId)
|
||||
{
|
||||
proxyChannelDataEventInfo dev = WINPR_C_ARRAY_INIT;
|
||||
const char* name = Stream_ConstPointer(s);
|
||||
const size_t nameLen = Stream_GetRemainingLength(s);
|
||||
const size_t len = strnlen(name, nameLen);
|
||||
const BOOL isBackData = (tracker == dynChannelContext->backTracker);
|
||||
const BYTE cmd = CREATE_REQUEST_PDU;
|
||||
|
||||
if ((len == 0) || (len == nameLen) || (dynChannelId > UINT16_MAX))
|
||||
{
|
||||
char namebuffer[64] = WINPR_C_ARRAY_INIT;
|
||||
(void)_snprintf(namebuffer, sizeof(namebuffer) - 1, "%s", name);
|
||||
|
||||
DynvcTrackerLog(dynChannelContext->log, WLOG_ERROR, dynChannel, cmd, isBackData,
|
||||
"channel id %" PRIu64 ", name=%s [%" PRIuz "|%" PRIuz "], status=%s",
|
||||
dynChannelId, namebuffer, len, nameLen,
|
||||
dynChannel ? openstatus2str(dynChannel->openStatus) : "nullptr");
|
||||
return PF_CHANNEL_RESULT_ERROR;
|
||||
}
|
||||
|
||||
wStream* currentPacket = channelTracker_getCurrentPacket(tracker);
|
||||
dev.channel_id = (UINT16)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)
|
||||
{
|
||||
DynvcTrackerLog(dynChannelContext->log, WLOG_WARN, dynChannel, cmd, isBackData,
|
||||
"Reusing channel id, now %s", 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, (UINT32)dynChannelId);
|
||||
if (!dynChannel)
|
||||
{
|
||||
DynvcTrackerLog(dynChannelContext->log, WLOG_ERROR, dynChannel, cmd, isBackData,
|
||||
"unable to create dynamic channel context data");
|
||||
return PF_CHANNEL_RESULT_ERROR;
|
||||
}
|
||||
|
||||
DynvcTrackerLog(dynChannelContext->log, WLOG_DEBUG, dynChannel, cmd, isBackData,
|
||||
"Adding channel");
|
||||
if (!HashTable_Insert(dynChannelContext->channels, &dynChannel->channelId, dynChannel))
|
||||
{
|
||||
DynvcTrackerLog(dynChannelContext->log, WLOG_ERROR, dynChannel, cmd, isBackData,
|
||||
"unable register dynamic channel context data");
|
||||
DynamicChannelContext_free(dynChannel);
|
||||
return PF_CHANNEL_RESULT_ERROR;
|
||||
}
|
||||
|
||||
dynChannel->openStatus = CHANNEL_OPENSTATE_WAITING_OPEN_STATUS;
|
||||
|
||||
const BOOL firstPacket = (flags & CHANNEL_FLAG_FIRST) != 0;
|
||||
const BOOL lastPacket = (flags & CHANNEL_FLAG_LAST) != 0;
|
||||
|
||||
// NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert owns dynChannel
|
||||
return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, FALSE);
|
||||
}
|
||||
|
||||
WINPR_ATTR_NODISCARD
|
||||
static PfChannelResult DynvcTrackerHandleCreateFront(ChannelStateTracker* tracker, wStream* s,
|
||||
DWORD flags,
|
||||
WINPR_ATTR_UNUSED proxyData* pdata,
|
||||
pServerDynamicChannelContext* dynChannel,
|
||||
DynChannelContext* dynChannelContext,
|
||||
WINPR_ATTR_UNUSED UINT64 dynChannelId)
|
||||
{
|
||||
const BOOL isBackData = (tracker == dynChannelContext->backTracker);
|
||||
const BYTE cmd = CREATE_REQUEST_PDU;
|
||||
|
||||
/* CREATE_REQUEST_PDU response */
|
||||
if (!Stream_CheckAndLogRequiredLengthWLogWithBackend(dynChannelContext->log, s, 4, FALSE))
|
||||
return PF_CHANNEL_RESULT_ERROR;
|
||||
|
||||
const UINT32 creationStatus = Stream_Get_UINT32(s);
|
||||
DynvcTrackerLog(dynChannelContext->log, WLOG_DEBUG, dynChannel, cmd, isBackData,
|
||||
"CREATE_RESPONSE openStatus=%" PRIu32, creationStatus);
|
||||
|
||||
if (dynChannel && (creationStatus == 0))
|
||||
dynChannel->openStatus = CHANNEL_OPENSTATE_OPENED;
|
||||
|
||||
const BOOL firstPacket = (flags & CHANNEL_FLAG_FIRST) != 0;
|
||||
const BOOL lastPacket = (flags & CHANNEL_FLAG_LAST) != 0;
|
||||
|
||||
return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, TRUE);
|
||||
}
|
||||
|
||||
WINPR_ATTR_NODISCARD
|
||||
static PfChannelResult DynvcTrackerHandleCreate(ChannelStateTracker* tracker, wStream* s,
|
||||
DWORD flags,
|
||||
pServerDynamicChannelContext* dynChannel,
|
||||
UINT64 dynChannelId)
|
||||
{
|
||||
WINPR_ASSERT(tracker);
|
||||
WINPR_ASSERT(s);
|
||||
|
||||
DynChannelContext* dynChannelContext =
|
||||
(DynChannelContext*)channelTracker_getCustomData(tracker);
|
||||
WINPR_ASSERT(dynChannelContext);
|
||||
|
||||
const BOOL lastPacket = (flags & CHANNEL_FLAG_LAST) != 0;
|
||||
const BOOL isBackData = (tracker == dynChannelContext->backTracker);
|
||||
|
||||
proxyData* pdata = channelTracker_getPData(tracker);
|
||||
WINPR_ASSERT(pdata);
|
||||
|
||||
/* we only want the full packet */
|
||||
if (!lastPacket)
|
||||
return PF_CHANNEL_RESULT_DROP;
|
||||
|
||||
if (isBackData)
|
||||
return DynvcTrackerHandleCreateBack(tracker, s, flags, pdata, dynChannel, dynChannelContext,
|
||||
dynChannelId);
|
||||
|
||||
return DynvcTrackerHandleCreateFront(tracker, s, flags, pdata, dynChannel, dynChannelContext,
|
||||
dynChannelId);
|
||||
}
|
||||
|
||||
WINPR_ATTR_NODISCARD
|
||||
static PfChannelResult DynvcTrackerHandleCmdDATA(ChannelStateTracker* tracker,
|
||||
pServerDynamicChannelContext* dynChannel,
|
||||
wStream* s, BYTE cmd, UINT64 Length,
|
||||
BOOL firstPacket, BOOL lastPacket)
|
||||
{
|
||||
WINPR_ASSERT(tracker);
|
||||
WINPR_ASSERT(s);
|
||||
|
||||
DynChannelContext* dynChannelContext =
|
||||
(DynChannelContext*)channelTracker_getCustomData(tracker);
|
||||
WINPR_ASSERT(dynChannelContext);
|
||||
|
||||
const BOOL isBackData = (tracker == dynChannelContext->backTracker);
|
||||
|
||||
if (!dynChannel)
|
||||
{
|
||||
DynvcTrackerLog(dynChannelContext->log, WLOG_WARN, dynChannel, cmd, isBackData,
|
||||
"channel is nullptr, dropping packet");
|
||||
return PF_CHANNEL_RESULT_DROP;
|
||||
}
|
||||
|
||||
DynChannelTrackerState* trackerState =
|
||||
isBackData ? &dynChannel->backTracker : &dynChannel->frontTracker;
|
||||
if (dynChannel->openStatus != CHANNEL_OPENSTATE_OPENED)
|
||||
{
|
||||
DynvcTrackerLog(dynChannelContext->log, WLOG_WARN, dynChannel, cmd, isBackData,
|
||||
"channel is not opened, dropping packet");
|
||||
return PF_CHANNEL_RESULT_DROP;
|
||||
}
|
||||
|
||||
switch (cmd)
|
||||
{
|
||||
case DATA_FIRST_PDU:
|
||||
case DATA_FIRST_COMPRESSED_PDU:
|
||||
{
|
||||
DynvcTrackerLog(dynChannelContext->log, WLOG_DEBUG, dynChannel, cmd, isBackData,
|
||||
"DATA_FIRST currentPacketLength=%" PRIu64 "", Length);
|
||||
if (Length > UINT32_MAX)
|
||||
{
|
||||
DynvcTrackerLog(dynChannelContext->log, WLOG_ERROR, dynChannel, cmd, isBackData,
|
||||
"Length out of bounds: %" PRIu64, Length);
|
||||
return PF_CHANNEL_RESULT_ERROR;
|
||||
}
|
||||
trackerState->currentDataLength = (UINT32)Length;
|
||||
trackerState->CurrentDataReceived = 0;
|
||||
trackerState->CurrentDataFragments = 0;
|
||||
|
||||
if (dynChannel->packetReassembly)
|
||||
{
|
||||
if (trackerState->currentPacket)
|
||||
Stream_ResetPosition(trackerState->currentPacket);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (cmd)
|
||||
{
|
||||
case DATA_PDU:
|
||||
case DATA_FIRST_PDU:
|
||||
{
|
||||
size_t extraSize = Stream_GetRemainingLength(s);
|
||||
|
||||
trackerState->CurrentDataFragments++;
|
||||
trackerState->CurrentDataReceived += WINPR_ASSERTING_INT_CAST(uint32_t, extraSize);
|
||||
|
||||
if (dynChannel->packetReassembly)
|
||||
{
|
||||
if (!trackerState->currentPacket)
|
||||
{
|
||||
trackerState->currentPacket = Stream_New(nullptr, 1024);
|
||||
if (!trackerState->currentPacket)
|
||||
{
|
||||
DynvcTrackerLog(dynChannelContext->log, WLOG_ERROR, dynChannel, cmd,
|
||||
isBackData, "unable to create current packet",
|
||||
getDirection(isBackData), dynChannel->channelName,
|
||||
drdynvc_get_packet_type(cmd));
|
||||
return PF_CHANNEL_RESULT_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Stream_EnsureRemainingCapacity(trackerState->currentPacket, extraSize))
|
||||
{
|
||||
DynvcTrackerLog(dynChannelContext->log, WLOG_ERROR, dynChannel, cmd, isBackData,
|
||||
"unable to grow current packet", getDirection(isBackData),
|
||||
dynChannel->channelName, drdynvc_get_packet_type(cmd));
|
||||
return PF_CHANNEL_RESULT_ERROR;
|
||||
}
|
||||
|
||||
Stream_Write(trackerState->currentPacket, Stream_ConstPointer(s), extraSize);
|
||||
}
|
||||
DynvcTrackerLog(dynChannelContext->log, WLOG_DEBUG, dynChannel, cmd, isBackData,
|
||||
"frags=%" PRIu32 " received=%" PRIu32 "(%" PRIu32 ")",
|
||||
trackerState->CurrentDataFragments, trackerState->CurrentDataReceived,
|
||||
trackerState->currentDataLength);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (cmd)
|
||||
{
|
||||
case DATA_PDU:
|
||||
{
|
||||
if (trackerState->currentDataLength)
|
||||
{
|
||||
if (trackerState->CurrentDataReceived > trackerState->currentDataLength)
|
||||
{
|
||||
DynvcTrackerLog(dynChannelContext->log, WLOG_ERROR, dynChannel, cmd, isBackData,
|
||||
"reassembled packet (%" PRIu32
|
||||
") is bigger than announced length (%" PRIu32 ")",
|
||||
trackerState->CurrentDataReceived,
|
||||
trackerState->currentDataLength);
|
||||
return PF_CHANNEL_RESULT_ERROR;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
trackerState->CurrentDataFragments = 0;
|
||||
trackerState->CurrentDataReceived = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return DynvcTrackerPeekHandleByMode(tracker, trackerState, dynChannel, cmd, firstPacket,
|
||||
lastPacket);
|
||||
}
|
||||
|
||||
WINPR_ATTR_NODISCARD
|
||||
static PfChannelResult DynvcTrackerHandleCmd(ChannelStateTracker* tracker,
|
||||
pServerDynamicChannelContext* dynChannel, wStream* s,
|
||||
BYTE cmd, UINT32 flags, UINT64 Length,
|
||||
UINT64 dynChannelId, BOOL firstPacket, BOOL lastPacket)
|
||||
{
|
||||
WINPR_ASSERT(tracker);
|
||||
WINPR_ASSERT(s);
|
||||
|
||||
DynChannelContext* dynChannelContext =
|
||||
(DynChannelContext*)channelTracker_getCustomData(tracker);
|
||||
WINPR_ASSERT(dynChannelContext);
|
||||
|
||||
const BOOL isBackData = (tracker == dynChannelContext->backTracker);
|
||||
switch (cmd)
|
||||
{
|
||||
case CAPABILITY_REQUEST_PDU:
|
||||
DynvcTrackerLog(dynChannelContext->log, WLOG_DEBUG, dynChannel, cmd, isBackData,
|
||||
"CAPABILITY_%s", isBackData ? "REQUEST" : "RESPONSE");
|
||||
channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS);
|
||||
return PF_CHANNEL_RESULT_PASS;
|
||||
|
||||
case CREATE_REQUEST_PDU:
|
||||
return DynvcTrackerHandleCreate(tracker, s, flags, dynChannel, dynChannelId);
|
||||
|
||||
case CLOSE_REQUEST_PDU:
|
||||
return DynvcTrackerHandleClose(tracker, dynChannel, dynChannelContext, firstPacket,
|
||||
lastPacket);
|
||||
|
||||
case SOFT_SYNC_REQUEST_PDU:
|
||||
/* just pass then as is for now */
|
||||
DynvcTrackerLog(dynChannelContext->log, WLOG_DEBUG, dynChannel, cmd, isBackData,
|
||||
"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 */
|
||||
DynvcTrackerLog(dynChannelContext->log, WLOG_DEBUG, dynChannel, cmd, isBackData,
|
||||
"SOFT_SYNC_RESPONSE_PDU");
|
||||
channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS);
|
||||
return PF_CHANNEL_RESULT_PASS;
|
||||
|
||||
case DATA_FIRST_PDU:
|
||||
case DATA_PDU:
|
||||
return DynvcTrackerHandleCmdDATA(tracker, dynChannel, s, cmd, Length, firstPacket,
|
||||
lastPacket);
|
||||
|
||||
case DATA_FIRST_COMPRESSED_PDU:
|
||||
case DATA_COMPRESSED_PDU:
|
||||
DynvcTrackerLog(dynChannelContext->log, WLOG_DEBUG, dynChannel, cmd, isBackData,
|
||||
"TODO: compressed data packets, pass them as is for now");
|
||||
channelTracker_setMode(tracker, CHANNEL_TRACKER_PASS);
|
||||
return channelTracker_flushCurrent(tracker, firstPacket, lastPacket, !isBackData);
|
||||
|
||||
default:
|
||||
DynvcTrackerLog(dynChannelContext->log, WLOG_ERROR, dynChannel, cmd, isBackData,
|
||||
"Invalid command ID");
|
||||
return PF_CHANNEL_RESULT_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
WINPR_ATTR_NODISCARD
|
||||
static PfChannelResult DynvcTrackerPeekFn(ChannelStateTracker* tracker, BOOL firstPacket,
|
||||
BOOL lastPacket)
|
||||
{
|
||||
wStream* s = nullptr;
|
||||
wStream sbuffer;
|
||||
BOOL haveChannelId = 0;
|
||||
BOOL haveLength = 0;
|
||||
UINT64 dynChannelId = 0;
|
||||
UINT64 Length = 0;
|
||||
pServerDynamicChannelContext* dynChannel = nullptr;
|
||||
|
||||
WINPR_ASSERT(tracker);
|
||||
|
||||
DynChannelContext* dynChannelContext =
|
||||
(DynChannelContext*)channelTracker_getCustomData(tracker);
|
||||
WINPR_ASSERT(dynChannelContext);
|
||||
|
||||
const BOOL isBackData = (tracker == dynChannelContext->backTracker);
|
||||
|
||||
UINT32 flags = lastPacket ? CHANNEL_FLAG_LAST : 0;
|
||||
if (firstPacket)
|
||||
flags |= CHANNEL_FLAG_FIRST;
|
||||
|
||||
{
|
||||
wStream* currentPacket = channelTracker_getCurrentPacket(tracker);
|
||||
s = Stream_StaticConstInit(&sbuffer, Stream_Buffer(currentPacket),
|
||||
Stream_GetPosition(currentPacket));
|
||||
}
|
||||
|
||||
if (!Stream_CheckAndLogRequiredLengthWLogWithBackend(dynChannelContext->log, s, 1, isBackData))
|
||||
return PF_CHANNEL_RESULT_ERROR;
|
||||
|
||||
const BYTE byte0 = Stream_Get_UINT8(s);
|
||||
const BYTE 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:
|
||||
DynvcTrackerLog(dynChannelContext->log, WLOG_ERROR, dynChannel, cmd, isBackData,
|
||||
"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 || (dynChannel->openStatus == CHANNEL_OPENSTATE_CLOSED))
|
||||
{
|
||||
/* 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:
|
||||
DynvcTrackerLog(dynChannelContext->log, WLOG_ERROR, dynChannel, cmd, isBackData,
|
||||
"invalid length field");
|
||||
return PF_CHANNEL_RESULT_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return DynvcTrackerHandleCmd(tracker, dynChannel, s, cmd, flags, Length, dynChannelId,
|
||||
firstPacket, lastPacket);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
WINPR_ATTR_NODISCARD
|
||||
static const char* dynamic_context(void* arg)
|
||||
{
|
||||
proxyData* pdata = arg;
|
||||
if (!pdata)
|
||||
return "pdata=null";
|
||||
return pdata->session_id;
|
||||
}
|
||||
|
||||
WINPR_ATTR_MALLOC(DynChannelContext_free, 1)
|
||||
WINPR_ATTR_NODISCARD
|
||||
static DynChannelContext* DynChannelContext_new(proxyData* pdata,
|
||||
pServerStaticChannelContext* channel)
|
||||
{
|
||||
DynChannelContext* dyn = calloc(1, sizeof(DynChannelContext));
|
||||
if (!dyn)
|
||||
return nullptr;
|
||||
|
||||
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 nullptr;
|
||||
}
|
||||
|
||||
WINPR_ATTR_NODISCARD
|
||||
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);
|
||||
}
|
||||
|
||||
WINPR_ATTR_NODISCARD
|
||||
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;
|
||||
}
|
||||
27
third_party/FreeRDP/server/proxy/channels/pf_channel_drdynvc.h
vendored
Normal file
27
third_party/FreeRDP/server/proxy/channels/pf_channel_drdynvc.h
vendored
Normal 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>
|
||||
|
||||
WINPR_ATTR_NODISCARD BOOL pf_channel_setup_drdynvc(proxyData* pdata,
|
||||
pServerStaticChannelContext* channel);
|
||||
|
||||
#endif /* SERVER_PROXY_CHANNELS_PF_CHANNEL_DRDYNVC_H_ */
|
||||
2075
third_party/FreeRDP/server/proxy/channels/pf_channel_rdpdr.c
vendored
Normal file
2075
third_party/FreeRDP/server/proxy/channels/pf_channel_rdpdr.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
50
third_party/FreeRDP/server/proxy/channels/pf_channel_rdpdr.h
vendored
Normal file
50
third_party/FreeRDP/server/proxy/channels/pf_channel_rdpdr.h
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef FREERDP_SERVER_PROXY_RDPDR_H
|
||||
#define FREERDP_SERVER_PROXY_RDPDR_H
|
||||
|
||||
#include <freerdp/server/proxy/proxy_context.h>
|
||||
|
||||
WINPR_ATTR_NODISCARD BOOL pf_channel_setup_rdpdr(pServerContext* ps,
|
||||
pServerStaticChannelContext* channel);
|
||||
|
||||
void pf_channel_rdpdr_client_free(pClientContext* pc);
|
||||
|
||||
WINPR_ATTR_NODISCARD BOOL pf_channel_rdpdr_client_new(pClientContext* pc);
|
||||
|
||||
BOOL pf_channel_rdpdr_client_reset(pClientContext* pc);
|
||||
|
||||
WINPR_ATTR_NODISCARD 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);
|
||||
|
||||
void pf_channel_rdpdr_server_free(pServerContext* ps);
|
||||
|
||||
WINPR_ATTR_NODISCARD BOOL pf_channel_rdpdr_server_new(pServerContext* ps);
|
||||
|
||||
WINPR_ATTR_NODISCARD BOOL pf_channel_rdpdr_server_announce(pServerContext* ps);
|
||||
WINPR_ATTR_NODISCARD BOOL pf_channel_rdpdr_server_handle(pServerContext* ps, UINT16 channelId,
|
||||
const char* channel_name,
|
||||
const BYTE* xdata, size_t xsize,
|
||||
UINT32 flags, size_t totalSize);
|
||||
|
||||
#endif /* FREERDP_SERVER_PROXY_RDPDR_H */
|
||||
394
third_party/FreeRDP/server/proxy/channels/pf_channel_smartcard.c
vendored
Normal file
394
third_party/FreeRDP/server/proxy/channels/pf_channel_smartcard.c
vendored
Normal file
@@ -0,0 +1,394 @@
|
||||
/**
|
||||
* 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 <winpr/assert.h>
|
||||
#include <winpr/string.h>
|
||||
|
||||
#include <winpr/smartcard.h>
|
||||
#include <winpr/pool.h>
|
||||
|
||||
#include <freerdp/server/proxy/proxy_log.h>
|
||||
#include <freerdp/emulate/scard/smartcard_emulate.h>
|
||||
#include <freerdp/channels/scard.h>
|
||||
#include <freerdp/channels/rdpdr.h>
|
||||
#include <freerdp/utils/rdpdr_utils.h>
|
||||
|
||||
#include <freerdp/utils/smartcard_operations.h>
|
||||
#include <freerdp/utils/smartcard_call.h>
|
||||
|
||||
#include "pf_channel_smartcard.h"
|
||||
#include "pf_channel_rdpdr.h"
|
||||
|
||||
#define TAG PROXY_TAG("channel.scard")
|
||||
|
||||
#define SCARD_SVC_CHANNEL_NAME "SCARD"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
InterceptContextMapEntry base;
|
||||
scard_call_context* callctx;
|
||||
wArrayList* workObjects;
|
||||
} pf_channel_client_context;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SMARTCARD_OPERATION op;
|
||||
wStream* out;
|
||||
pClientContext* pc;
|
||||
wLog* log;
|
||||
pf_scard_send_fkt_t send_fkt;
|
||||
} pf_channel_client_queue_element;
|
||||
|
||||
WINPR_ATTR_NODISCARD
|
||||
static pf_channel_client_context* scard_get_client_context(pClientContext* pc)
|
||||
{
|
||||
pf_channel_client_context* scard = nullptr;
|
||||
|
||||
WINPR_ASSERT(pc);
|
||||
WINPR_ASSERT(pc->interceptContextMap);
|
||||
|
||||
scard = HashTable_GetItemValue(pc->interceptContextMap, SCARD_SVC_CHANNEL_NAME);
|
||||
if (!scard)
|
||||
WLog_WARN(TAG, "[%s] missing in pc->interceptContextMap", SCARD_SVC_CHANNEL_NAME);
|
||||
return scard;
|
||||
}
|
||||
|
||||
WINPR_ATTR_NODISCARD
|
||||
static BOOL pf_channel_client_write_iostatus(wStream* out, const SMARTCARD_OPERATION* op,
|
||||
NTSTATUS ioStatus)
|
||||
{
|
||||
UINT16 component = 0;
|
||||
UINT16 packetid = 0;
|
||||
UINT32 dID = 0;
|
||||
UINT32 cID = 0;
|
||||
size_t pos = 0;
|
||||
|
||||
WINPR_ASSERT(op);
|
||||
WINPR_ASSERT(out);
|
||||
|
||||
pos = Stream_GetPosition(out);
|
||||
Stream_ResetPosition(out);
|
||||
if (!Stream_CheckAndLogRequiredLength(TAG, out, 16))
|
||||
return FALSE;
|
||||
|
||||
Stream_Read_UINT16(out, component);
|
||||
Stream_Read_UINT16(out, packetid);
|
||||
|
||||
Stream_Read_UINT32(out, dID);
|
||||
Stream_Read_UINT32(out, cID);
|
||||
|
||||
WINPR_ASSERT(component == RDPDR_CTYP_CORE);
|
||||
WINPR_ASSERT(packetid == PAKID_CORE_DEVICE_IOCOMPLETION);
|
||||
WINPR_ASSERT(dID == op->deviceID);
|
||||
WINPR_ASSERT(cID == op->completionID);
|
||||
|
||||
Stream_Write_INT32(out, ioStatus);
|
||||
Stream_SetPosition(out, pos);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
struct thread_arg
|
||||
{
|
||||
pf_channel_client_context* scard;
|
||||
pf_channel_client_queue_element* e;
|
||||
};
|
||||
|
||||
static void queue_free(void* obj);
|
||||
|
||||
WINPR_ATTR_MALLOC(queue_free, 1)
|
||||
WINPR_ATTR_NODISCARD
|
||||
static void* queue_copy(const void* obj);
|
||||
|
||||
static VOID irp_thread(WINPR_ATTR_UNUSED PTP_CALLBACK_INSTANCE Instance, PVOID Context,
|
||||
PTP_WORK Work)
|
||||
{
|
||||
struct thread_arg* arg = Context;
|
||||
pf_channel_client_context* scard = arg->scard;
|
||||
{
|
||||
NTSTATUS ioStatus = 0;
|
||||
LONG rc = smartcard_irp_device_control_call(arg->scard->callctx, arg->e->out, &ioStatus,
|
||||
&arg->e->op);
|
||||
if (rc == CHANNEL_RC_OK)
|
||||
{
|
||||
if (pf_channel_client_write_iostatus(arg->e->out, &arg->e->op, ioStatus))
|
||||
arg->e->send_fkt(arg->e->log, arg->e->pc, arg->e->out);
|
||||
}
|
||||
}
|
||||
queue_free(arg->e);
|
||||
free(arg);
|
||||
ArrayList_Remove(scard->workObjects, Work);
|
||||
}
|
||||
|
||||
WINPR_ATTR_NODISCARD
|
||||
static BOOL start_irp_thread(pf_channel_client_context* scard,
|
||||
const pf_channel_client_queue_element* e)
|
||||
{
|
||||
PTP_WORK work = nullptr;
|
||||
struct thread_arg* arg = calloc(1, sizeof(struct thread_arg));
|
||||
if (!arg)
|
||||
return FALSE;
|
||||
arg->scard = scard;
|
||||
arg->e = queue_copy(e);
|
||||
if (!arg->e)
|
||||
goto fail;
|
||||
|
||||
work = CreateThreadpoolWork(irp_thread, arg, nullptr);
|
||||
if (!work)
|
||||
goto fail;
|
||||
ArrayList_Append(scard->workObjects, work);
|
||||
SubmitThreadpoolWork(work);
|
||||
|
||||
return TRUE;
|
||||
|
||||
fail:
|
||||
if (arg)
|
||||
queue_free(arg->e);
|
||||
free(arg);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
BOOL pf_channel_smartcard_client_handle(wLog* log, pClientContext* pc, wStream* s, wStream* out,
|
||||
pf_scard_send_fkt_t send_fkt)
|
||||
{
|
||||
BOOL rc = FALSE;
|
||||
LONG status = 0;
|
||||
UINT32 FileId = 0;
|
||||
UINT32 CompletionId = 0;
|
||||
NTSTATUS ioStatus = 0;
|
||||
pf_channel_client_queue_element e = WINPR_C_ARRAY_INIT;
|
||||
pf_channel_client_context* scard = scard_get_client_context(pc);
|
||||
|
||||
WINPR_ASSERT(log);
|
||||
WINPR_ASSERT(send_fkt);
|
||||
WINPR_ASSERT(s);
|
||||
|
||||
if (!scard)
|
||||
return FALSE;
|
||||
|
||||
e.log = log;
|
||||
e.pc = pc;
|
||||
e.out = out;
|
||||
e.send_fkt = send_fkt;
|
||||
|
||||
/* Skip IRP header */
|
||||
if (!Stream_CheckAndLogRequiredLength(TAG, s, 20))
|
||||
return FALSE;
|
||||
else
|
||||
{
|
||||
const uint32_t DeviceId = Stream_Get_UINT32(s); /* DeviceId (4 bytes) */
|
||||
FileId = Stream_Get_UINT32(s); /* FileId (4 bytes) */
|
||||
CompletionId = Stream_Get_UINT32(s); /* CompletionId (4 bytes) */
|
||||
const uint32_t MajorFunction = Stream_Get_UINT32(s); /* MajorFunction (4 bytes) */
|
||||
const uint32_t MinorFunction = Stream_Get_UINT32(s); /* MinorFunction (4 bytes) */
|
||||
|
||||
if (MajorFunction != IRP_MJ_DEVICE_CONTROL)
|
||||
{
|
||||
WLog_WARN(TAG, "[%s] Invalid IRP received, expected %s, got %s [0x%08" PRIx32 "]",
|
||||
SCARD_SVC_CHANNEL_NAME, rdpdr_irp_string(IRP_MJ_DEVICE_CONTROL),
|
||||
rdpdr_irp_string(MajorFunction), MinorFunction);
|
||||
return FALSE;
|
||||
}
|
||||
e.op.completionID = CompletionId;
|
||||
e.op.deviceID = DeviceId;
|
||||
|
||||
if (!rdpdr_write_iocompletion_header(out, DeviceId, CompletionId, 0))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
status = smartcard_irp_device_control_decode(s, CompletionId, FileId, &e.op);
|
||||
if (status != 0)
|
||||
goto fail;
|
||||
|
||||
switch (e.op.ioControlCode)
|
||||
{
|
||||
case SCARD_IOCTL_LISTREADERGROUPSA:
|
||||
case SCARD_IOCTL_LISTREADERGROUPSW:
|
||||
case SCARD_IOCTL_LISTREADERSA:
|
||||
case SCARD_IOCTL_LISTREADERSW:
|
||||
case SCARD_IOCTL_LOCATECARDSA:
|
||||
case SCARD_IOCTL_LOCATECARDSW:
|
||||
case SCARD_IOCTL_LOCATECARDSBYATRA:
|
||||
case SCARD_IOCTL_LOCATECARDSBYATRW:
|
||||
case SCARD_IOCTL_GETSTATUSCHANGEA:
|
||||
case SCARD_IOCTL_GETSTATUSCHANGEW:
|
||||
case SCARD_IOCTL_CONNECTA:
|
||||
case SCARD_IOCTL_CONNECTW:
|
||||
case SCARD_IOCTL_RECONNECT:
|
||||
case SCARD_IOCTL_DISCONNECT:
|
||||
case SCARD_IOCTL_BEGINTRANSACTION:
|
||||
case SCARD_IOCTL_ENDTRANSACTION:
|
||||
case SCARD_IOCTL_STATE:
|
||||
case SCARD_IOCTL_STATUSA:
|
||||
case SCARD_IOCTL_STATUSW:
|
||||
case SCARD_IOCTL_TRANSMIT:
|
||||
case SCARD_IOCTL_CONTROL:
|
||||
case SCARD_IOCTL_GETATTRIB:
|
||||
case SCARD_IOCTL_SETATTRIB:
|
||||
if (!start_irp_thread(scard, &e))
|
||||
goto fail;
|
||||
return TRUE;
|
||||
|
||||
default:
|
||||
status = smartcard_irp_device_control_call(scard->callctx, out, &ioStatus, &e.op);
|
||||
if (status != 0)
|
||||
goto fail;
|
||||
if (!pf_channel_client_write_iostatus(out, &e.op, ioStatus))
|
||||
goto fail;
|
||||
break;
|
||||
}
|
||||
|
||||
rc = send_fkt(log, pc, out) == CHANNEL_RC_OK;
|
||||
|
||||
fail:
|
||||
smartcard_operation_free(&e.op, FALSE);
|
||||
return rc;
|
||||
}
|
||||
|
||||
BOOL pf_channel_smartcard_server_handle(WINPR_ATTR_UNUSED pServerContext* ps,
|
||||
WINPR_ATTR_UNUSED wStream* s)
|
||||
{
|
||||
WLog_ERR(TAG, "TODO: unimplemented");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void channel_stop_and_wait(pf_channel_client_context* scard, BOOL reset)
|
||||
{
|
||||
WINPR_ASSERT(scard);
|
||||
smartcard_call_context_signal_stop(scard->callctx, FALSE);
|
||||
|
||||
while (ArrayList_Count(scard->workObjects) > 0)
|
||||
{
|
||||
PTP_WORK work = ArrayList_GetItem(scard->workObjects, 0);
|
||||
if (!work)
|
||||
continue;
|
||||
WaitForThreadpoolWorkCallbacks(work, TRUE);
|
||||
}
|
||||
|
||||
smartcard_call_context_signal_stop(scard->callctx, reset);
|
||||
}
|
||||
|
||||
static void pf_channel_scard_client_context_free(InterceptContextMapEntry* base)
|
||||
{
|
||||
pf_channel_client_context* entry = (pf_channel_client_context*)base;
|
||||
if (!entry)
|
||||
return;
|
||||
|
||||
/* Set the stop event.
|
||||
* All threads waiting in blocking operations will abort at the next
|
||||
* available polling slot */
|
||||
channel_stop_and_wait(entry, FALSE);
|
||||
ArrayList_Free(entry->workObjects);
|
||||
|
||||
smartcard_call_context_free(entry->callctx);
|
||||
free(entry);
|
||||
}
|
||||
|
||||
static void queue_free(void* obj)
|
||||
{
|
||||
pf_channel_client_queue_element* element = obj;
|
||||
if (!element)
|
||||
return;
|
||||
smartcard_operation_free(&element->op, FALSE);
|
||||
Stream_Free(element->out, TRUE);
|
||||
free(element);
|
||||
}
|
||||
|
||||
WINPR_ATTR_MALLOC(queue_free, 1)
|
||||
WINPR_ATTR_NODISCARD
|
||||
static void* queue_copy(const void* obj)
|
||||
{
|
||||
const pf_channel_client_queue_element* other = obj;
|
||||
pf_channel_client_queue_element* copy = nullptr;
|
||||
if (!other)
|
||||
return nullptr;
|
||||
copy = calloc(1, sizeof(pf_channel_client_queue_element));
|
||||
if (!copy)
|
||||
return nullptr;
|
||||
|
||||
*copy = *other;
|
||||
copy->out = Stream_New(nullptr, Stream_Capacity(other->out));
|
||||
if (!copy->out)
|
||||
goto fail;
|
||||
Stream_Write(copy->out, Stream_Buffer(other->out), Stream_GetPosition(other->out));
|
||||
return copy;
|
||||
fail:
|
||||
queue_free(copy);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void work_object_free(void* arg)
|
||||
{
|
||||
PTP_WORK work = arg;
|
||||
CloseThreadpoolWork(work);
|
||||
}
|
||||
|
||||
BOOL pf_channel_smartcard_client_new(pClientContext* pc)
|
||||
{
|
||||
pf_channel_client_context* scard = nullptr;
|
||||
wObject* obj = nullptr;
|
||||
|
||||
WINPR_ASSERT(pc);
|
||||
WINPR_ASSERT(pc->interceptContextMap);
|
||||
|
||||
scard = calloc(1, sizeof(pf_channel_client_context));
|
||||
if (!scard)
|
||||
return FALSE;
|
||||
scard->base.free = pf_channel_scard_client_context_free;
|
||||
scard->callctx = smartcard_call_context_new(pc->context.settings);
|
||||
if (!scard->callctx)
|
||||
goto fail;
|
||||
|
||||
scard->workObjects = ArrayList_New(TRUE);
|
||||
if (!scard->workObjects)
|
||||
goto fail;
|
||||
obj = ArrayList_Object(scard->workObjects);
|
||||
WINPR_ASSERT(obj);
|
||||
obj->fnObjectFree = work_object_free;
|
||||
|
||||
return HashTable_Insert(pc->interceptContextMap, SCARD_SVC_CHANNEL_NAME, scard);
|
||||
fail:
|
||||
pf_channel_scard_client_context_free(&scard->base);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void pf_channel_smartcard_client_free(pClientContext* pc)
|
||||
{
|
||||
WINPR_ASSERT(pc);
|
||||
WINPR_ASSERT(pc->interceptContextMap);
|
||||
HashTable_Remove(pc->interceptContextMap, SCARD_SVC_CHANNEL_NAME);
|
||||
}
|
||||
|
||||
BOOL pf_channel_smartcard_client_emulate(pClientContext* pc)
|
||||
{
|
||||
pf_channel_client_context* scard = scard_get_client_context(pc);
|
||||
if (!scard)
|
||||
return FALSE;
|
||||
return smartcard_call_is_configured(scard->callctx);
|
||||
}
|
||||
|
||||
BOOL pf_channel_smartcard_client_reset(pClientContext* pc)
|
||||
{
|
||||
pf_channel_client_context* scard = scard_get_client_context(pc);
|
||||
if (!scard)
|
||||
return TRUE;
|
||||
|
||||
channel_stop_and_wait(scard, TRUE);
|
||||
return TRUE;
|
||||
}
|
||||
40
third_party/FreeRDP/server/proxy/channels/pf_channel_smartcard.h
vendored
Normal file
40
third_party/FreeRDP/server/proxy/channels/pf_channel_smartcard.h
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef FREERDP_SERVER_PROXY_SCARD_H
|
||||
#define FREERDP_SERVER_PROXY_SCARD_H
|
||||
|
||||
#include <winpr/wlog.h>
|
||||
#include <freerdp/server/proxy/proxy_context.h>
|
||||
|
||||
typedef UINT (*pf_scard_send_fkt_t)(wLog* log, pClientContext*, wStream*);
|
||||
|
||||
WINPR_ATTR_NODISCARD BOOL pf_channel_smartcard_client_new(pClientContext* pc);
|
||||
void pf_channel_smartcard_client_free(pClientContext* pc);
|
||||
|
||||
BOOL pf_channel_smartcard_client_reset(pClientContext* pc);
|
||||
WINPR_ATTR_NODISCARD BOOL pf_channel_smartcard_client_emulate(pClientContext* pc);
|
||||
|
||||
WINPR_ATTR_NODISCARD BOOL pf_channel_smartcard_client_handle(wLog* log, pClientContext* pc,
|
||||
wStream* s, wStream* out,
|
||||
pf_scard_send_fkt_t fkt);
|
||||
WINPR_ATTR_NODISCARD BOOL pf_channel_smartcard_server_handle(pServerContext* ps, wStream* s);
|
||||
|
||||
#endif /* FREERDP_SERVER_PROXY_SCARD_H */
|
||||
Reference in New Issue
Block a user