Files
xrdp/libxrdp/xrdp_channel.c
matt335672 63afb676e6 drdynvc: Improve dynamic channel support
The dynamic channel handler in xrdp_channel.c is updated to allow
the procs `data_first` pointer to be NULL. If this is done, the
channel handler performs all the dechunking necessary for the channel,
and only complete data PDUs are passed to procs 'data' callback.

This facility is applied to the dynamic channels supported by xrdp_mm.c.
The incoming callbacks for these channels now provide complete support
for the specification in [MS-RDPEDYC]. The existing channels were
incomplete in these respects:
1) The "Microsoft::Windows::RDS::Graphics" channel handler did not
   support incoming PDUs between 1591 and 1600 bytes. The specification
   calls for these to be sent as a single DATA_FIRST PDU.
2) The "Microsoft::Windows::RDS::DisplayControl" channel handler did
   not support incoming PDUs over 1590 bytes.
2026-08-12 11:31:47 +01:00

1220 lines
37 KiB
C

/**
* xrdp: A Remote Desktop Protocol server.
*
* Copyright (C) Jay Sorg 2006-2013
*
* 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.
*
* channel layer
*/
#if defined(HAVE_CONFIG_H)
#include <config_ac.h>
#endif
#include "libxrdp.h"
#include "dechunker.h"
#include "string_calls.h"
#include "xrdp_channel.h"
#define CMD_DVC_OPEN_CHANNEL 0x10
#define CMD_DVC_DATA_FIRST 0x20
#define CMD_DVC_DATA 0x30
#define CMD_DVC_CLOSE_CHANNEL 0x40
#define CMD_DVC_CAPABILITY 0x50
#define XRDP_DRDYNVC_CHANNEL_ID_TO_NAME(self, chan_id) \
(xrdp_channel_get_item((self), (chan_id)) != NULL \
? xrdp_channel_get_item((self), (chan_id))->name \
: "unknown")
/*****************************************************************************/
/* returns pointer or nil on error */
static struct mcs_channel_item *
xrdp_channel_get_item(struct xrdp_channel *self, int channel_id)
{
struct mcs_channel_item *channel;
if (self->mcs_layer->channel_list == NULL)
{
LOG(LOG_LEVEL_WARNING, "Channel list is NULL, returning NULL");
return NULL ;
}
channel = (struct mcs_channel_item *)
list_get_item(self->mcs_layer->channel_list, channel_id);
return channel;
}
/*****************************************************************************/
struct xrdp_channel *
xrdp_channel_create(struct xrdp_sec *owner, struct xrdp_mcs *mcs_layer)
{
struct xrdp_channel *self;
self = (struct xrdp_channel *)g_malloc(sizeof(struct xrdp_channel), 1);
self->sec_layer = owner;
self->mcs_layer = mcs_layer;
self->drdynvc_channel_id = -1;
return self;
}
/*****************************************************************************/
/* returns error */
void
xrdp_channel_delete(struct xrdp_channel *self)
{
int i;
if (self == 0)
{
return;
}
vc_dechunker_free(self->drdynvc_dc);
for (i = 0 ; i < DRDYNVC_CHANNEL_COUNT ; ++i)
{
dyn_dechunker_free(self->drdynvcs[i].dc);
}
g_memset(self, 0, sizeof(struct xrdp_channel));
g_free(self);
}
/*****************************************************************************/
/* returns error */
int
xrdp_channel_init(struct xrdp_channel *self, struct stream *s)
{
if (xrdp_sec_init(self->sec_layer, s) != 0)
{
LOG(LOG_LEVEL_ERROR, "xrdp_channel_init: xrdp_sec_init failed");
return 1;
}
s_push_layer(s, channel_hdr, 8);
return 0;
}
/*****************************************************************************/
/* returns error */
/* This sends data out to the secure layer. */
int
xrdp_channel_send(struct xrdp_channel *self, struct stream *s, int channel_id,
int total_data_len, int flags)
{
struct mcs_channel_item *channel;
channel = xrdp_channel_get_item(self, channel_id);
if (channel == NULL)
{
LOG(LOG_LEVEL_ERROR,
"Request to send a message to non-existent channel_id %d",
channel_id);
return 1;
}
if (channel->disabled)
{
LOG(LOG_LEVEL_DEBUG,
"Request to send a message to the disabled channel %s (%d)",
channel->name, channel_id);
return 0; /* not an error */
}
s_pop_layer(s, channel_hdr);
out_uint32_le(s, total_data_len);
/*
* According to 2.2.1.3.4.1 Channel Definition Structure (CHANNEL_DEF):
* CHANNEL_OPTION_SHOW_PROTOCOL 0x00200000
* The value of this flag MUST be ignored by the server. The
* visibility of the Channel PDU Header (section 2.2.6.1.1) is
* determined by the CHANNEL_FLAG_SHOW_PROTOCOL
* (0x00000010) flag as defined in the flags field (section
* 2.2.6.1.1).
*
* That's flag makes MSTSC crash when using RAIL channel.
*/
// if (channel->flags & XR_CHANNEL_OPTION_SHOW_PROTOCOL)
// {
// flags |= CHANNEL_FLAG_SHOW_PROTOCOL;
// }
out_uint32_le(s, flags);
LOG_DEVEL(LOG_LEVEL_TRACE, "Adding header [MS-RDPBCGR] CHANNEL_PDU_HEADER "
"length %d, flags 0x%8.8x", total_data_len, flags);
if (xrdp_sec_send(self->sec_layer, s, channel->chanid) != 0)
{
LOG(LOG_LEVEL_ERROR, "xrdp_channel_send: xrdp_sec_send failed");
return 1;
}
return 0;
}
/*****************************************************************************/
/* returns error */
/* this will inform the callback, whatever it is that some channel data is
ready. the default for this is a call to xrdp_wm.c. */
static int
xrdp_channel_call_callback(struct xrdp_channel *self, struct stream *s,
int channel_id,
int total_data_len, int flags)
{
struct xrdp_session *session;
int rv;
int size;
rv = 0;
session = self->sec_layer->rdp_layer->session;
if (session != 0)
{
if (session->callback != 0)
{
size = (int)(s->end - s->p);
/* in xrdp_wm.c */
rv = session->callback(session->id, 0x5555,
MAKELONG(channel_id, flags),
size, (tbus)(s->p), total_data_len);
}
else
{
LOG_DEVEL(LOG_LEVEL_WARNING, "session->callback is NULL");
}
}
else
{
LOG_DEVEL(LOG_LEVEL_WARNING, "session is NULL");
}
return rv;
}
/*****************************************************************************/
/**
* Write a variable length unsigned int (1, 2, or 4 bytes) to the stream.
*
* The number of bytes written is the minimum number of bytes needed to
* represent the value.
*
* @param s the stream to write to
* @param val the value to write
* @return the DYNVC cbId length code for the number of bytes written (see [MS-RDPEDYC] 2.2.2.1)
*/
static int
drdynvc_insert_uint_124(struct stream *s, uint32_t val)
{
int ret_val;
if (val <= 0xff)
{
out_uint8(s, val);
ret_val = 0;
}
else if (val <= 0xffff)
{
out_uint16_le(s, val);
ret_val = 1;
}
else
{
out_uint32_le(s, val);
ret_val = 2;
}
return ret_val;
}
/*****************************************************************************/
/**
* Read a variable length unsigned int (1, 2, or 4 bytes) from the stream.
*
* The number of bytes read is determined by the cbId bit field flag in the
* cmd argument (see [MS-RDPEDYC] 2.2.2.1).
*
* @param s [in] the stream to read from
* @param cmd [in] the cmd byte which contains the cbId bit field flag
* @param chan_id_p [out] a pointer to the value read from the stream
* @return error code
*/
static int
drdynvc_get_chan_id(struct stream *s, char cmd, uint32_t *chan_id_p)
{
int cbChId;
int chan_id;
cbChId = cmd & 0x03;
if (cbChId == 0)
{
if (!s_check_rem_and_log(s, 1, "Parsing [MS-RDPEDYC] channel id"))
{
return 1;
}
in_uint8(s, chan_id);
}
else if (cbChId == 1)
{
if (!s_check_rem_and_log(s, 2, "Parsing [MS-RDPEDYC] channel id"))
{
return 1;
}
in_uint16_le(s, chan_id);
}
else
{
if (!s_check_rem_and_log(s, 4, "Parsing [MS-RDPEDYC] channel id"))
{
return 1;
}
in_uint32_le(s, chan_id);
}
*chan_id_p = chan_id;
return 0;
}
/*****************************************************************************/
/*
* Process a [MS-RDPEDYC] DYNVC_CAPS_RSP message.
*/
static int
drdynvc_process_capability_response(struct xrdp_channel *self,
int cmd, struct stream *s)
{
struct xrdp_session *session;
int cap_version;
int rv;
if (!s_check_rem_and_log(s, 3, "Parsing [MS-RDPEDYC] DYNVC_CAPS_RSP"))
{
return 1;
}
in_uint8s(s, 1); /* skip padding */
in_uint16_le(s, cap_version); /* Version */
LOG_DEVEL(LOG_LEVEL_TRACE, "Received [MS-RDPEDYC] DYNVC_CAPS_RSP "
"version %d", cap_version);
if ((cap_version != 2) && (cap_version != 3))
{
LOG(LOG_LEVEL_ERROR,
"Dynamic Virtual Channel version %d is not supported",
cap_version);
return 1;
}
self->drdynvc_state = 1;
session = self->sec_layer->rdp_layer->session;
rv = session->callback(session->id, 0x5558, 0, 0, 0, 0);
return rv;
}
/*****************************************************************************/
/*
* Process a [MS-RDPEDYC] DYNVC_CREATE_RSP message.
*/
static int
drdynvc_process_open_channel_response(struct xrdp_channel *self,
int cmd, struct stream *s)
{
struct xrdp_session *session;
int creation_status;
uint32_t chan_id;
struct xrdp_drdynvc *drdynvc;
if (drdynvc_get_chan_id(s, cmd, &chan_id) != 0) /* ChannelId */
{
LOG(LOG_LEVEL_ERROR,
"Parsing [MS-RDPEDYC] DYNVC_CREATE_RSP failed");
return 1;
}
if (!s_check_rem_and_log(s, 4, "Parsing [MS-RDPEDYC] DYNVC_CREATE_RSP"))
{
return 1;
}
in_uint32_le(s, creation_status); /* CreationStatus */
LOG_DEVEL(LOG_LEVEL_TRACE, "Received [MS-RDPEDYC] DYNVC_CREATE_RSP "
"ChannelId %d, CreationStatus %d", chan_id, creation_status);
if (chan_id >= DRDYNVC_CHANNEL_COUNT)
{
LOG(LOG_LEVEL_ERROR, "Received [MS-RDPEDYC] DYNVC_CREATE_RSP "
"for an invalid channel id %d", chan_id);
return 1;
}
session = self->sec_layer->rdp_layer->session;
drdynvc = self->drdynvcs + chan_id;
if (creation_status == 0)
{
drdynvc->status = XRDP_DRDYNVC_STATUS_OPEN;
}
else
{
drdynvc->status = XRDP_DRDYNVC_STATUS_CLOSED;
dyn_dechunker_free(drdynvc->dc);
drdynvc->dc = NULL;
}
LOG_DEVEL(LOG_LEVEL_DEBUG,
"Dynamic Virtual Channel %s (%d) updated: status = %s",
XRDP_DRDYNVC_CHANNEL_ID_TO_NAME(self, chan_id),
chan_id,
XRDP_DRDYNVC_STATUS_TO_STR(drdynvc->status));
if (drdynvc->open_response != NULL)
{
return drdynvc->open_response(session->id, chan_id, creation_status);
}
LOG_DEVEL(LOG_LEVEL_WARNING, "Dynamic Virtual Channel %s (%d): "
"callback 'open_response' is NULL",
XRDP_DRDYNVC_CHANNEL_ID_TO_NAME(self, chan_id),
chan_id);
return 0;
}
/*****************************************************************************/
/*
* Process a [MS-RDPEDYC] DYNVC_CLOSE message.
*/
static int
drdynvc_process_close_channel_response(struct xrdp_channel *self,
int cmd, struct stream *s)
{
struct xrdp_session *session;
uint32_t chan_id;
struct xrdp_drdynvc *drdynvc;
if (drdynvc_get_chan_id(s, cmd, &chan_id) != 0) /* ChannelId */
{
LOG(LOG_LEVEL_ERROR,
"drdynvc_process_close_channel_response: drdynvc_get_chan_id failed");
return 1;
}
LOG_DEVEL(LOG_LEVEL_TRACE, "Received [MS-RDPEDYC] DYNVC_CLOSE "
"ChannelId %d", chan_id);
session = self->sec_layer->rdp_layer->session;
if (chan_id >= DRDYNVC_CHANNEL_COUNT)
{
LOG(LOG_LEVEL_ERROR, "Received message for an invalid "
"channel id %d", chan_id);
return 1;
}
drdynvc = self->drdynvcs + chan_id;
if (dyn_dechunker_pending(drdynvc->dc))
{
// The last PDU wasn't completed
LOG(LOG_LEVEL_WARNING,
"Dynamic Virtual Channel %s (%d) closing with outstanding data",
XRDP_DRDYNVC_CHANNEL_ID_TO_NAME(self, chan_id),
chan_id);
}
drdynvc->status = XRDP_DRDYNVC_STATUS_CLOSED;
dyn_dechunker_free(drdynvc->dc);
drdynvc->dc = NULL;
LOG_DEVEL(LOG_LEVEL_DEBUG,
"Dynamic Virtual Channel %s (%d) updated: status = %s",
XRDP_DRDYNVC_CHANNEL_ID_TO_NAME(self, chan_id),
chan_id,
XRDP_DRDYNVC_STATUS_TO_STR(drdynvc->status));
if (drdynvc->close_response != NULL)
{
return drdynvc->close_response(session->id, chan_id);
}
LOG_DEVEL(LOG_LEVEL_WARNING, "Dynamic Virtual Channel %s (%d): "
"callback 'close_response' is NULL",
XRDP_DRDYNVC_CHANNEL_ID_TO_NAME(self, chan_id),
chan_id);
return 0;
}
/*****************************************************************************/
/*
* Process a [MS-RDPEDYC] DYNVC_DATA_FIRST message.
*/
static int
drdynvc_process_data_first(struct xrdp_channel *self,
int cmd, struct stream *s)
{
uint32_t chan_id;
int rv = 0;
if (drdynvc_get_chan_id(s, cmd, &chan_id) != 0) /* ChannelId */
{
LOG(LOG_LEVEL_ERROR,
"Parsing [MS-RDPEDYC] DYNVC_DATA_FIRST channel ID failed");
rv = 1;
}
else
{
int len = (cmd >> 2) & 0x03;
int total_bytes;
switch (len)
{
case 0:
// Technically this can't happen as DATA_FIRST is only used for
// PDUs over 1590 bytes ([MS-RDPEDYC] 2.2.3)
if (!s_check_rem(s, 1))
{
goto short_pdu;
}
in_uint8(s, total_bytes); /* Length */
break;
case 1:
if (!s_check_rem(s, 2))
{
goto short_pdu;
}
in_uint16_le(s, total_bytes); /* Length */
break;
case 2:
if (!s_check_rem(s, 4))
{
goto short_pdu;
}
in_uint32_le(s, total_bytes); /* Length */
break;
default:
LOG(LOG_LEVEL_ERROR,
"[MS-RDPEDYC] DYNVC_DATA_FIRST has bad Len field");
return 1;
}
LOG_DEVEL(LOG_LEVEL_TRACE, "Received [MS-RDPEDYC] DYNVC_DATA_FIRST "
"ChannelId %d, Length %d, Data (omitted from the log)",
chan_id, total_bytes);
if (chan_id >= DRDYNVC_CHANNEL_COUNT)
{
LOG(LOG_LEVEL_ERROR, "Received [MS-RDPEDYC] DYNVC_DATA_FIRST "
"for an invalid channel id %d", chan_id);
rv = 1;
}
else
{
struct xrdp_drdynvc *drdynvc = self->drdynvcs + chan_id;
if (drdynvc->data_first != NULL)
{
// Caller has requested to defragment PDUs themself
struct xrdp_session *session =
self->sec_layer->rdp_layer->session;
rv = drdynvc->data_first(session->id, chan_id, s, total_bytes);
}
else
{
// Get the dechunker working on the stream
enum dyn_dechunker_status status;
status = dyn_dechunker_process_first_chunk(drdynvc->dc,
s, total_bytes);
switch (status)
{
case E_DYN_INLINE_CHUNK:
// Pass through as data PDU
if (drdynvc->data != NULL)
{
struct xrdp_session *session =
self->sec_layer->rdp_layer->session;
rv = drdynvc->data(session->id, chan_id, s);
}
break;
case E_DYN_IN_PROGRESS:
break;
case E_DYN_ERROR:
rv = 1;
break;
default:
LOG(LOG_LEVEL_ERROR,
"Dechunker returned unknown error %d",
(int)status);
rv = 1;
}
}
}
}
return rv;
short_pdu:
LOG(LOG_LEVEL_ERROR, "[MS-RDPEDYC] DYNVC_DATA_FIRST is too short");
return 1;
}
/*****************************************************************************/
/*
* Process a [MS-RDPEDYC] DYNVC_DATA message.
*/
static int
drdynvc_process_data(struct xrdp_channel *self,
int cmd, struct stream *s)
{
int rv = 0;
uint32_t chan_id;
if (drdynvc_get_chan_id(s, cmd, &chan_id) != 0) /* ChannelId */
{
LOG(LOG_LEVEL_ERROR,
"Parsing [MS-RDPEDYC] DYNVC_DATA channel ID failed");
rv = 1;
}
else
{
LOG_DEVEL(LOG_LEVEL_TRACE, "Received [MS-RDPEDYC] DYNVC_DATA "
"ChannelId %d, Length %d, Data (omitted from the log)",
chan_id, s_rem(s));
if (chan_id >= DRDYNVC_CHANNEL_COUNT)
{
LOG(LOG_LEVEL_ERROR, "Received DYNVC_DATA PDU for an invalid "
"channel id %d", chan_id);
rv = 1;
}
else
{
struct stream *ls = NULL; // Set if the application to be called
int free_ls = 0; // Set if we need to clear ls when we're done
struct xrdp_drdynvc *drdynvc = self->drdynvcs + chan_id;
if (drdynvc->data_first != NULL)
{
// Caller is processing all PDUs directly
ls = s;
}
else
{
// Pass the PDU to the dechunker
enum dyn_dechunker_status dechunker_status =
dyn_dechunker_process_data_chunk(drdynvc->dc, s);
switch (dechunker_status)
{
case E_DYN_INLINE_CHUNK:
ls = s;
break;
case E_DYN_IN_PROGRESS:
break;
case E_DYN_READY:
ls = dyn_dechunker_get_stream(drdynvc->dc);
// We now own the stream, so must delete it
free_ls = 1;
break;
case E_DYN_ERROR:
// Error has been logged
rv = 1;
break;
default:
LOG(LOG_LEVEL_ERROR,
"Dechunker returned unknown error %d",
(int)dechunker_status);
rv = 1;
}
}
if (ls != NULL)
{
if (drdynvc->data != NULL)
{
struct xrdp_session *session =
self->sec_layer->rdp_layer->session;
rv = drdynvc->data(session->id, chan_id, ls);
}
if (free_ls)
{
free_stream(ls);
}
}
}
}
return rv;
}
/*****************************************************************************/
/**
* Process a [MS-RDPBCGR] 2.2.6.1 Virtual Channel PDU and re-assemble the
* data chunks as needed.
*/
static int
xrdp_channel_process_drdynvc(struct xrdp_channel *self,
struct mcs_channel_item *channel,
struct stream *s)
{
int total_length;
int flags;
int cmd;
int rv;
struct stream *ls = NULL;
int free_ls = 0;
enum vc_dechunker_status dechunker_status;
if (!s_check_rem_and_log(s, 8, "Parsing [MS-RDPBCGR] CHANNEL_PDU_HEADER"))
{
return 1;
}
in_uint32_le(s, total_length); /* length */
in_uint32_le(s, flags); /* flags */
LOG_DEVEL(LOG_LEVEL_TRACE, "Received header [MS-RDPBCGR] CHANNEL_PDU_HEADER "
"length %d, flags 0x%8.8x", total_length, flags);
dechunker_status = vc_dechunker_process_chunk(
self->drdynvc_dc,
s, flags, total_length);
switch (dechunker_status)
{
case E_VC_INLINE_CHUNK:
ls = s;
break;
case E_VC_IN_PROGRESS:
return 0;
break;
case E_VC_READY:
ls = vc_dechunker_get_stream(self->drdynvc_dc);
// We now own the stream, so must delete it
free_ls = 1;
break;
case E_VC_ERROR:
// Error has been logged
return 1;
default:
LOG(LOG_LEVEL_ERROR, "Dechunker returned unknown error %d",
(int)dechunker_status);
return 1;
}
if (ls == NULL)
{
LOG(LOG_LEVEL_ERROR, "BUG: ls must not be NULL");
return 1;
}
in_uint8(ls, cmd); /* cbId (low 2 bits), Sp (2 bits), Cmd (hi 4 bits) */
LOG_DEVEL(LOG_LEVEL_TRACE, "Received header [MS-RDPEDYC] "
"cbId %d, Sp %d, Cmd 0x%2.2x",
(cmd & 0x03), (cmd & 0x0c) >> 2, (cmd & 0xf0) >> 4);
rv = 1;
switch (cmd & 0xf0)
{
case CMD_DVC_CAPABILITY:
rv = drdynvc_process_capability_response(self, cmd, ls);
break;
case CMD_DVC_OPEN_CHANNEL:
rv = drdynvc_process_open_channel_response(self, cmd, ls);
break;
case CMD_DVC_CLOSE_CHANNEL:
rv = drdynvc_process_close_channel_response(self, cmd, ls);
break;
case CMD_DVC_DATA_FIRST:
rv = drdynvc_process_data_first(self, cmd, ls);
break;
case CMD_DVC_DATA:
rv = drdynvc_process_data(self, cmd, ls);
break;
default:
LOG(LOG_LEVEL_ERROR, "Received header [MS-RDPEDYC] with "
"unknown command 0x%2.2x", cmd);
break;
}
if (free_ls)
{
free_stream(ls);
}
return rv;
}
/*****************************************************************************/
/* Process a static ([MS-RDPBCGR] 2.2.6) or dynamic (MS-RDPEDYC 2.2.3)
* virtual channel message.
* returns error */
/* This is called from the secure layer to process an incoming non global
channel packet.
'chanid' passed in here is the mcs channel id so it MCS_GLOBAL_CHANNEL
plus something. */
int
xrdp_channel_process(struct xrdp_channel *self, struct stream *s,
int chanid)
{
int length;
int flags;
int rv;
int channel_id;
struct mcs_channel_item *channel;
/* this assumes that the channels are in order of chanid(mcs channel id)
but they should be, see xrdp_sec_process_mcs_data_channels
the first channel should be MCS_GLOBAL_CHANNEL + 1, second
one should be MCS_GLOBAL_CHANNEL + 2, and so on */
channel_id = (chanid - MCS_GLOBAL_CHANNEL) - 1;
channel = xrdp_channel_get_item(self, channel_id);
if (channel == NULL)
{
LOG(LOG_LEVEL_ERROR,
"Received a message for an unknown channel id. channel id %d",
chanid);
return 1;
}
if (channel->disabled)
{
LOG(LOG_LEVEL_WARNING,
"Received a message for the disabled channel %s (%d)",
channel->name, chanid);
return 0; /* not an error */
}
if (channel_id == self->drdynvc_channel_id)
{
return xrdp_channel_process_drdynvc(self, channel, s);
}
rv = 0;
in_uint32_le(s, length); /* length */
in_uint32_le(s, flags); /* flags */
LOG_DEVEL(LOG_LEVEL_TRACE, "Received header [MS-RDPBCGR] CHANNEL_PDU_HEADER "
"length %d, flags 0x%8.8x", length, flags);
rv = xrdp_channel_call_callback(self, s, channel_id, length, flags);
return rv;
}
/*****************************************************************************/
/* Send a [MS-RDPEDYC] DYNVC_CAPS_VERSION2 message */
static int
xrdp_channel_drdynvc_send_capability_request(struct xrdp_channel *self)
{
struct stream *s;
int flags;
int total_data_len;
int channel_id;
char *phold;
/* setup stream */
make_stream(s);
init_stream(s, 8192);
if (xrdp_channel_init(self, s) != 0)
{
LOG(LOG_LEVEL_ERROR,
"xrdp_channel_drdynvc_send_capability_request: xrdp_channel_init failed");
free_stream(s);
return 1;
}
phold = s->p;
out_uint8(s, 0x50); /* insert cbId (2 bits), Sp (2 bits), cmd (4 bits) */
out_uint8(s, 0x00); /* insert padding */
out_uint16_le(s, 2); /* insert version */
/* channel priority unused for now */
out_uint16_le(s, 0x0000); /* priority charge 0 */
out_uint16_le(s, 0x0000); /* priority charge 1 */
out_uint16_le(s, 0x0000); /* priority charge 2 */
out_uint16_le(s, 0x0000); /* priority charge 3 */
s_mark_end(s);
/* send command to client */
total_data_len = (int) (s->end - phold);
flags = XR_CHANNEL_FLAG_FIRST | XR_CHANNEL_FLAG_LAST;
channel_id = self->drdynvc_channel_id;
LOG_DEVEL(LOG_LEVEL_TRACE, "Sending [MS-RDPEDYC] DYNVC_CAPS_VERSION2 "
"cbId 0, Sp 0, Cmd 0x05, Version 2, PriorityCharge0 0, "
"PriorityCharge1 0, PriorityCharge2 0, PriorityCharge3 0");
if (xrdp_channel_send(self, s, channel_id, total_data_len, flags) != 0)
{
LOG(LOG_LEVEL_ERROR,
"xrdp_channel_drdynvc_send_capability_request: xrdp_channel_send failed");
free_stream(s);
return 1;
}
free_stream(s);
return 0;
}
/*****************************************************************************/
int
xrdp_channel_drdynvc_start(struct xrdp_channel *self)
{
int rv = 0;
LOG_DEVEL(LOG_LEVEL_INFO,
"xrdp_channel_drdynvc_start: drdynvc_channel_id %d",
self->drdynvc_channel_id);
if (self->drdynvc_channel_id != -1)
{
LOG_DEVEL(LOG_LEVEL_INFO,
"xrdp_channel_drdynvc_start: already started");
}
else
{
int index;
int count;
struct mcs_channel_item *ci;
struct mcs_channel_item *dci;
dci = NULL;
count = self->mcs_layer->channel_list->count;
for (index = 0; index < count; index++)
{
ci = (struct mcs_channel_item *)
list_get_item(self->mcs_layer->channel_list, index);
if (ci != NULL)
{
if (g_strcasecmp(ci->name, DRDYNVC_SVC_CHANNEL_NAME) == 0)
{
dci = ci;
break;
}
}
}
if (dci == NULL)
{
LOG(LOG_LEVEL_WARNING, "Static channel '%s' not found.",
DRDYNVC_SVC_CHANNEL_NAME);
rv = -1;
}
else if (dci->disabled)
{
LOG(LOG_LEVEL_WARNING, "Static channel '%s' is disabled.",
DRDYNVC_SVC_CHANNEL_NAME);
rv = -1;
}
else if ((self->drdynvc_dc =
vc_dechunker_init(DRDYNVC_SVC_CHANNEL_NAME,
CHANNEL_CHUNK_LENGTH)) == NULL)
{
LOG(LOG_LEVEL_ERROR, "No memory");
rv = -1;
}
else
{
self->drdynvc_channel_id = (dci->chanid - MCS_GLOBAL_CHANNEL) - 1;
LOG_DEVEL(LOG_LEVEL_DEBUG, DRDYNVC_SVC_CHANNEL_NAME
"Initializing Dynamic Virtual Channel with channel id %d",
self->drdynvc_channel_id);
xrdp_channel_drdynvc_send_capability_request(self);
}
}
if (rv != 0)
{
LOG(LOG_LEVEL_WARNING, "Dynamic channels will not be available");
}
return rv;
}
/*****************************************************************************/
/*
* Send a [MS-RDPEDYC] DYNVC_CREATE_REQ message to request the creation of a channel.
*/
int
xrdp_channel_drdynvc_open(struct xrdp_channel *self, const char *name,
int flags, struct xrdp_drdynvc_procs *procs,
int *chan_id)
{
struct stream *s;
int ChId;
int cbChId;
int chan_pri;
int static_channel_id;
int name_length;
int total_data_len;
int static_flags;
char *cmd_ptr;
struct dyn_dechunker *dc = NULL;
make_stream(s);
init_stream(s, 8192);
if (xrdp_channel_init(self, s) != 0)
{
LOG(LOG_LEVEL_ERROR,
"xrdp_channel_drdynvc_open: xrdp_channel_init failed");
goto cleanup;
}
// If there's no 'data_first' proc, we need a dechunker for the
// channel
if (procs->data_first == NULL)
{
if ((dc = dyn_dechunker_init(name)) == NULL)
{
// Error logged
goto cleanup;
}
}
cmd_ptr = s->p;
out_uint8(s, 0); /* set later */
ChId = 1;
while (self->drdynvcs[ChId].status != XRDP_DRDYNVC_STATUS_CLOSED)
{
ChId++;
if (ChId >= DRDYNVC_CHANNEL_COUNT)
{
LOG(LOG_LEVEL_ERROR,
"Attempting to create a new channel when the maximum "
"number of channels have already been created.");
goto cleanup;
}
}
cbChId = drdynvc_insert_uint_124(s, ChId); /* ChannelId */
name_length = g_strlen(name);
out_uint8a(s, name, name_length + 1); /* ChannelName */
chan_pri = 0;
/* cbId (low 2 bits), Pri (2 bits), Cmd (hi 4 bits) */
cmd_ptr[0] = CMD_DVC_OPEN_CHANNEL | ((chan_pri << 2) & 0x0c) | cbChId;
static_channel_id = self->drdynvc_channel_id;
static_flags = XR_CHANNEL_FLAG_FIRST | XR_CHANNEL_FLAG_LAST;
s_mark_end(s);
total_data_len = (int) (s->end - cmd_ptr);
LOG_DEVEL(LOG_LEVEL_TRACE, "Sending [MS-RDPEDYC] DYNVC_CREATE_REQ "
"cbId %d, Pri %d, Cmd 0x%2.2x, ChannelId %d, ChannelName [%s]",
cbChId, chan_pri, CMD_DVC_OPEN_CHANNEL, ChId, name);
if (xrdp_channel_send(self, s, static_channel_id, total_data_len,
static_flags) != 0)
{
LOG(LOG_LEVEL_ERROR,
"Sending [MS-RDPEDYC] DYNVC_CREATE_REQ failed");
goto cleanup;
}
if (procs->data == NULL)
{
// Not expected. Log this once.
LOG(LOG_LEVEL_WARNING, "Dynamic Virtual Channel %s (%d): "
"callback 'data' is NULL", name, ChId);
}
*chan_id = ChId;
self->drdynvcs[ChId].open_response = procs->open_response;
self->drdynvcs[ChId].close_response = procs->close_response;
self->drdynvcs[ChId].data_first = procs->data_first;
self->drdynvcs[ChId].data = procs->data;
self->drdynvcs[ChId].status = XRDP_DRDYNVC_STATUS_OPEN_SENT;
self->drdynvcs[ChId].dc = dc;
free_stream(s);
return 0;
cleanup:
free_stream(s);
dyn_dechunker_free(dc);
return 1;
}
/*****************************************************************************/
/*
* Send a [MS-RDPEDYC] DYNVC_CLOSE message to request the closing of a channel.
*/
int
xrdp_channel_drdynvc_close(struct xrdp_channel *self, int chan_id)
{
struct stream *s;
int ChId;
int cbChId;
int static_channel_id;
int total_data_len;
int static_flags;
char *cmd_ptr;
if ((chan_id < 0) || (chan_id >= DRDYNVC_CHANNEL_COUNT))
{
LOG(LOG_LEVEL_ERROR, "Attempting to close an invalid "
"channel id %d", chan_id);
return 1;
}
if ((self->drdynvcs[chan_id].status != XRDP_DRDYNVC_STATUS_OPEN) &&
(self->drdynvcs[chan_id].status != XRDP_DRDYNVC_STATUS_OPEN_SENT))
{
/* not open */
LOG(LOG_LEVEL_ERROR, "Attempting to close a channel that is not open. "
"channel id %d, channel status %s",
chan_id,
XRDP_DRDYNVC_STATUS_TO_STR(self->drdynvcs[chan_id].status));
return 1;
}
make_stream(s);
init_stream(s, 8192);
if (xrdp_channel_init(self, s) != 0)
{
LOG(LOG_LEVEL_ERROR,
"xrdp_channel_drdynvc_close: xrdp_channel_init failed");
free_stream(s);
return 1;
}
cmd_ptr = s->p;
out_uint8(s, 0); /* set later */
ChId = chan_id;
cbChId = drdynvc_insert_uint_124(s, ChId); /* ChannelId */
/* cbId (low 2 bits), Sp (2 bits), Cmd (hi 4 bits) */
cmd_ptr[0] = CMD_DVC_CLOSE_CHANNEL | cbChId;
static_channel_id = self->drdynvc_channel_id;
static_flags = XR_CHANNEL_FLAG_FIRST | XR_CHANNEL_FLAG_LAST;
s_mark_end(s);
total_data_len = (int) (s->end - cmd_ptr);
LOG_DEVEL(LOG_LEVEL_TRACE, "Sending [MS-RDPEDYC] DYNVC_CLOSE "
"cbId %d, Sp 0, Cmd 0x%2.2x, ChannelId %d",
cbChId, CMD_DVC_CLOSE_CHANNEL, ChId);
if (xrdp_channel_send(self, s, static_channel_id, total_data_len,
static_flags) != 0)
{
LOG(LOG_LEVEL_ERROR,
"xrdp_channel_drdynvc_open: xrdp_channel_send failed");
free_stream(s);
return 1;
}
free_stream(s);
self->drdynvcs[ChId].status = XRDP_DRDYNVC_STATUS_CLOSE_SENT;
return 0;
}
/*****************************************************************************/
/*
* Send a [MS-RDPEDYC] DYNVC_DATA_FIRST message.
*/
int
xrdp_channel_drdynvc_data_first(struct xrdp_channel *self, int chan_id,
const char *data, int data_bytes,
int total_data_bytes)
{
struct stream *s;
int ChId;
int cbChId;
int cbTotalDataSize;
int static_channel_id;
int total_data_len;
int static_flags;
char *cmd_ptr;
if ((chan_id < 0) || (chan_id >= DRDYNVC_CHANNEL_COUNT))
{
LOG(LOG_LEVEL_ERROR, "Attempting to send data to an invalid "
"channel id %d", chan_id);
return 1;
}
if (self->drdynvcs[chan_id].status != XRDP_DRDYNVC_STATUS_OPEN)
{
LOG(LOG_LEVEL_ERROR, "Attempting to send data to a channel that "
"is not open. channel id %d, channel status %s",
chan_id,
XRDP_DRDYNVC_STATUS_TO_STR(self->drdynvcs[chan_id].status));
return 1;
}
if (data_bytes > 1590)
{
LOG(LOG_LEVEL_ERROR, "Payload for channel id %d is is too big. "
"data_bytes %d", chan_id, data_bytes);
return 1;
}
make_stream(s);
init_stream(s, 8192);
if (xrdp_channel_init(self, s) != 0)
{
LOG(LOG_LEVEL_ERROR,
"xrdp_channel_drdynvc_data_first: xrdp_channel_init failed");
free_stream(s);
return 1;
}
cmd_ptr = s->p;
out_uint8(s, 0); /* set later */
ChId = chan_id;
cbChId = drdynvc_insert_uint_124(s, ChId); /* ChannelId */
cbTotalDataSize = drdynvc_insert_uint_124(s, total_data_bytes); /* Length */
out_uint8p(s, data, data_bytes); /* Data */
/* cbId (low 2 bits), Len (2 bits), Cmd (hi 4 bits) */
cmd_ptr[0] = CMD_DVC_DATA_FIRST | (cbTotalDataSize << 2) | cbChId;
static_channel_id = self->drdynvc_channel_id;
static_flags = XR_CHANNEL_FLAG_FIRST | XR_CHANNEL_FLAG_LAST;
s_mark_end(s);
LOG_DEVEL(LOG_LEVEL_TRACE, "Sending [MS-RDPEDYC] DYNVC_DATA_FIRST "
"cbId %d, Len %d, Cmd 0x%2.2x, ChannelId %d, Length %d",
cbChId, cbTotalDataSize, CMD_DVC_DATA_FIRST, ChId, total_data_bytes);
total_data_len = (int) (s->end - cmd_ptr);
if (xrdp_channel_send(self, s, static_channel_id, total_data_len,
static_flags) != 0)
{
LOG(LOG_LEVEL_ERROR,
"xrdp_channel_drdynvc_data_first: xrdp_channel_send failed");
free_stream(s);
return 1;
}
free_stream(s);
return 0;
}
/*****************************************************************************/
/*
* Send a [MS-RDPEDYC] DYNVC_DATA message.
*/
int
xrdp_channel_drdynvc_data(struct xrdp_channel *self, int chan_id,
const char *data, int data_bytes)
{
struct stream *s;
int ChId;
int cbChId;
int static_channel_id;
int total_data_len;
int static_flags;
char *cmd_ptr;
if ((chan_id < 0) || (chan_id >= DRDYNVC_CHANNEL_COUNT))
{
LOG(LOG_LEVEL_ERROR, "Attempting to send data to an invalid "
"channel id %d", chan_id);
return 1;
}
if (self->drdynvcs[chan_id].status != XRDP_DRDYNVC_STATUS_OPEN)
{
LOG(LOG_LEVEL_ERROR, "Attempting to send data to a channel that "
"is not open. channel id %d, channel status %s",
chan_id,
XRDP_DRDYNVC_STATUS_TO_STR(self->drdynvcs[chan_id].status));
return 1;
}
if (data_bytes > 1590)
{
LOG(LOG_LEVEL_ERROR, "Payload for channel id %d is is too big. "
"data_bytes %d", chan_id, data_bytes);
return 1;
}
make_stream(s);
init_stream(s, 8192);
if (xrdp_channel_init(self, s) != 0)
{
LOG(LOG_LEVEL_ERROR,
"xrdp_channel_drdynvc_data: xrdp_channel_init failed");
free_stream(s);
return 1;
}
cmd_ptr = s->p;
out_uint8(s, 0); /* set later */
ChId = chan_id;
cbChId = drdynvc_insert_uint_124(s, ChId); /* ChannelId */
out_uint8p(s, data, data_bytes); /* Data */
/* cbId (low 2 bits), Sp (2 bits), Cmd (hi 4 bits) */
cmd_ptr[0] = CMD_DVC_DATA | cbChId;
static_channel_id = self->drdynvc_channel_id;
static_flags = XR_CHANNEL_FLAG_FIRST | XR_CHANNEL_FLAG_LAST;
s_mark_end(s);
total_data_len = (int) (s->end - cmd_ptr);
LOG_DEVEL(LOG_LEVEL_TRACE, "Sending [MS-RDPEDYC] DYNVC_DATA "
"cbId %d, Sp 0, Cmd 0x%2.2x, ChannelId %d",
cbChId, CMD_DVC_DATA_FIRST, ChId);
if (xrdp_channel_send(self, s, static_channel_id, total_data_len,
static_flags) != 0)
{
LOG(LOG_LEVEL_ERROR,
"xrdp_channel_drdynvc_data: xrdp_channel_send failed");
free_stream(s);
return 1;
}
free_stream(s);
return 0;
}