528 lines
13 KiB
C
528 lines
13 KiB
C
/**
|
|
* xrdp: A Remote Desktop Protocol server.
|
|
*
|
|
* Copyright (C) Laxmikant Rashinkar 2012 LK.Rashinkar@gmail.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 "drdynvc.h"
|
|
|
|
int g_drdynvc_chan_id;
|
|
int g_drdynvc_inited = 0;
|
|
|
|
static int APP_CC drdynvc_send_capability_request(uint16_t version);
|
|
static int APP_CC drdynvc_process_capability_response(struct stream* s, unsigned char cmd);
|
|
static int APP_CC drdynvc_process_open_channel_response(struct stream *s, unsigned char cmd);
|
|
static int APP_CC drdynvc_process_close_channel_response(struct stream *s, unsigned char cmd);
|
|
static int APP_CC drdynvc_process_data_first(struct stream* s, unsigned char cmd);
|
|
static int APP_CC drdynvc_process_data(struct stream* s, unsigned char cmd);
|
|
static int APP_CC drdynvc_insert_uint_124(struct stream *s, uint32_t val);
|
|
static int APP_CC drdynvc_get_chan_id(struct stream *s, char cmd, uint32_t *chan_id_p);
|
|
|
|
/**
|
|
* bring up dynamic virtual channel
|
|
*
|
|
* @return 0 on success, -1 on response
|
|
******************************************************************************/
|
|
int APP_CC
|
|
drdynvc_init(void)
|
|
{
|
|
/* bring up X11 */
|
|
xcommon_init();
|
|
|
|
/* let client know what version of DVC we support */
|
|
drdynvc_send_capability_request(2);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* let DVC Manager on client end know what version of protocol we support
|
|
* client will respond with version that it supports
|
|
*
|
|
* @return 0 on success, -1 on response
|
|
******************************************************************************/
|
|
static int APP_CC
|
|
drdynvc_send_capability_request(uint16_t version)
|
|
{
|
|
struct stream *s;
|
|
int bytes_in_stream;
|
|
|
|
/* setup stream */
|
|
make_stream(s);
|
|
init_stream(s, MAX_PDU_SIZE);
|
|
|
|
out_uint8(s, 0x50); /* insert cmd */
|
|
out_uint8(s, 0x00); /* insert padding */
|
|
out_uint16_le(s, version); /* 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 */
|
|
|
|
/* send command to client */
|
|
bytes_in_stream = stream_length_before_p(s);
|
|
send_channel_data(g_drdynvc_chan_id, s->data, bytes_in_stream);
|
|
free_stream(s);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* process capability response received from DVC manager at client end
|
|
*
|
|
* @param s stream containing client response
|
|
*
|
|
* @return 0 on success, -1 on failure
|
|
******************************************************************************/
|
|
static int APP_CC
|
|
drdynvc_process_capability_response(struct stream *s, unsigned char cmd)
|
|
{
|
|
int cap_version;
|
|
|
|
/* skip padding */
|
|
in_uint8s(s, 1);
|
|
|
|
/* read client's version */
|
|
in_uint16_le(s, cap_version);
|
|
|
|
if ((cap_version != 2) && (cap_version != 3))
|
|
{
|
|
LOG(0, ("drdynvc_process_capability_response: incompatible DVC "
|
|
"version %d detected", cap_version));
|
|
return -1;
|
|
}
|
|
|
|
LOG(0, ("drdynvc_process_capability_response: DVC version %d selected",
|
|
cap_version));
|
|
g_drdynvc_inited = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* create a new dynamic virtual channel
|
|
*
|
|
* @pram channel_id channel id number
|
|
* @pram channel_name name of channel
|
|
*
|
|
* @return 0 on success, -1 on failure
|
|
******************************************************************************/
|
|
int APP_CC
|
|
drdynvc_send_open_channel_request(int chan_pri, unsigned int chan_id,
|
|
char *chan_name)
|
|
{
|
|
struct stream *s;
|
|
int bytes_in_stream;
|
|
int cbChId;
|
|
int name_length;
|
|
|
|
if ((chan_name == NULL) || (strlen(chan_name) == 0))
|
|
{
|
|
LOG(0, ("drdynvc_send_open_channel_request: bad channel name specified"));
|
|
return -1;
|
|
}
|
|
|
|
make_stream(s);
|
|
init_stream(s, MAX_PDU_SIZE);
|
|
|
|
name_length = strlen(chan_name);
|
|
|
|
/* dummy command for now */
|
|
out_uint8(s, 0);
|
|
|
|
/* insert channel id */
|
|
cbChId = drdynvc_insert_uint_124(s, chan_id);
|
|
|
|
/* insert channel name */
|
|
out_uint8a(s, chan_name, name_length + 1);
|
|
|
|
/* insert command */
|
|
s->data[0] = CMD_DVC_OPEN_CHANNEL | ((chan_pri << 2) & 0x0c) | cbChId;
|
|
|
|
/* send command */
|
|
bytes_in_stream = stream_length_before_p(s);
|
|
send_channel_data(g_drdynvc_chan_id, s->data, bytes_in_stream);
|
|
free_stream(s);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int APP_CC
|
|
drdynvc_process_open_channel_response(struct stream *s, unsigned char cmd)
|
|
{
|
|
struct xrdp_api_data *adp;
|
|
|
|
uint32_t chan_id;
|
|
int creation_status;
|
|
|
|
drdynvc_get_chan_id(s, cmd, &chan_id);
|
|
in_uint32_le(s, creation_status);
|
|
|
|
/* LK_TODO now do something using useful! */
|
|
|
|
if (creation_status < 0)
|
|
{
|
|
// TODO
|
|
}
|
|
else
|
|
{
|
|
/* get struct xrdp_api_data containing this channel id */
|
|
if ((adp = struct_from_dvc_chan_id(chan_id)) == NULL)
|
|
{
|
|
LOG(0, ("drdynvc_process_open_channel_response: error : "
|
|
"could not find xrdp_api_data containing chan_id %d", chan_id));
|
|
|
|
return -1;
|
|
}
|
|
|
|
adp->is_connected = 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int APP_CC
|
|
drdynvc_send_close_channel_request(unsigned int chan_id)
|
|
{
|
|
struct stream *s;
|
|
int bytes_in_stream;
|
|
int cbChId;
|
|
|
|
make_stream(s);
|
|
init_stream(s, MAX_PDU_SIZE);
|
|
|
|
/* insert dummy cmd for now */
|
|
out_uint8(s, 0);
|
|
|
|
/* insert channel id */
|
|
cbChId = drdynvc_insert_uint_124(s, chan_id);
|
|
|
|
/* insert command */
|
|
s->data[0] = CMD_DVC_CLOSE_CHANNEL | cbChId;
|
|
|
|
/* send command */
|
|
bytes_in_stream = stream_length_before_p(s);
|
|
send_channel_data(g_drdynvc_chan_id, s->data, bytes_in_stream);
|
|
|
|
free_stream(s);
|
|
return 0;
|
|
}
|
|
|
|
static int APP_CC
|
|
drdynvc_process_close_channel_response(struct stream *s, unsigned char cmd)
|
|
{
|
|
uint32_t chan_id;
|
|
|
|
drdynvc_get_chan_id(s, cmd, &chan_id);
|
|
|
|
/* LK_TODO now do something using useful! */
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* send data to client
|
|
*
|
|
* @param chan_id the virtual channel to write to
|
|
* @param data data to write
|
|
* @param data_size number of bytes to write
|
|
*
|
|
* @return 0 on success, -1 on failure
|
|
******************************************************************************/
|
|
int APP_CC drdynvc_write_data(uint32_t chan_id, char *data, int data_size)
|
|
{
|
|
struct stream *s;
|
|
char *saved_ptr;
|
|
int cbChId;
|
|
int Len;
|
|
int bytes_in_stream;
|
|
int frag_size;
|
|
|
|
if (data == NULL)
|
|
{
|
|
LOG(0, ("drdynvc_write_data: data is NULL\n"));
|
|
return -1;
|
|
}
|
|
|
|
if (data_size <= 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
make_stream(s);
|
|
init_stream(s, MAX_PDU_SIZE);
|
|
|
|
/* this is a dummy write */
|
|
out_uint8(s, 0);
|
|
|
|
/* insert channel id */
|
|
cbChId = drdynvc_insert_uint_124(s, chan_id);
|
|
|
|
/* will data fit into one pkt? */
|
|
bytes_in_stream = stream_length_before_p(s);
|
|
|
|
if ((bytes_in_stream + data_size) <= MAX_PDU_SIZE)
|
|
{
|
|
/* yes it will - insert data */
|
|
out_uint8p(s, data, data_size);
|
|
|
|
/* insert command */
|
|
s->data[0] = CMD_DVC_DATA | cbChId;
|
|
|
|
/* write data to client */
|
|
bytes_in_stream = stream_length_before_p(s);
|
|
send_channel_data(g_drdynvc_chan_id, s->data, bytes_in_stream);
|
|
free_stream(s);
|
|
return 0;
|
|
}
|
|
|
|
/* no it won't - fragment it */
|
|
|
|
saved_ptr = s->p;
|
|
|
|
/* let client know how much data to expect */
|
|
Len = drdynvc_insert_uint_124(s, data_size);
|
|
|
|
/* insert data into first fragment */
|
|
frag_size = MAX_PDU_SIZE - stream_length_before_p(s);
|
|
out_uint8p(s, data, frag_size);
|
|
|
|
/* insert command */
|
|
s->data[0] = CMD_DVC_DATA_FIRST | Len << 2 | cbChId;
|
|
|
|
/* write first fragment to client */
|
|
bytes_in_stream = stream_length_before_p(s);
|
|
send_channel_data(g_drdynvc_chan_id, s->data, bytes_in_stream);
|
|
data_size -= frag_size;
|
|
data += frag_size;
|
|
s->data[0] = CMD_DVC_DATA | cbChId;
|
|
s->p = saved_ptr;
|
|
|
|
/* now send rest of the data using CMD_DVC_DATA */
|
|
while (data_size > 0)
|
|
{
|
|
frag_size = MAX_PDU_SIZE - stream_length_before_p(s);
|
|
|
|
if (frag_size > data_size)
|
|
{
|
|
frag_size = data_size;
|
|
}
|
|
|
|
out_uint8p(s, data, frag_size);
|
|
bytes_in_stream = stream_length_before_p(s);
|
|
send_channel_data(g_drdynvc_chan_id, s->data, bytes_in_stream);
|
|
data_size -= frag_size;
|
|
data += frag_size;
|
|
s->p = saved_ptr;
|
|
}
|
|
|
|
free_stream(s);
|
|
return 0;
|
|
}
|
|
|
|
static int APP_CC
|
|
drdynvc_process_data_first(struct stream *s, unsigned char cmd)
|
|
{
|
|
struct xrdp_api_data *adp;
|
|
struct stream *ls;
|
|
|
|
uint32_t chan_id;
|
|
int bytes_in_stream;
|
|
int Len;
|
|
|
|
drdynvc_get_chan_id(s, cmd, &chan_id);
|
|
|
|
Len = (cmd >> 2) & 0x03;
|
|
|
|
/* skip data_len */
|
|
if (Len == 0)
|
|
{
|
|
in_uint8s(s, 1);
|
|
}
|
|
else if (Len == 1)
|
|
{
|
|
in_uint8s(s, 2);
|
|
}
|
|
else
|
|
{
|
|
in_uint8s(s, 4);
|
|
}
|
|
|
|
bytes_in_stream = stream_length_after_p(s);
|
|
|
|
/* get struct xrdp_api_data containing this channel id */
|
|
if ((adp = struct_from_dvc_chan_id(chan_id)) == NULL)
|
|
{
|
|
LOG(0, ("drdynvc_process_data_first: error : "
|
|
"could not find xrdp_api_data containing chan_id %d", chan_id));
|
|
|
|
return -1;
|
|
}
|
|
|
|
ls = trans_get_out_s(adp->transp, MAX_PDU_SIZE);
|
|
out_uint8p(ls, s->p, bytes_in_stream);
|
|
s_mark_end(ls);
|
|
trans_force_write(adp->transp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int APP_CC
|
|
drdynvc_process_data(struct stream *s, unsigned char cmd)
|
|
{
|
|
struct xrdp_api_data *adp;
|
|
struct stream *ls;
|
|
|
|
uint32_t chan_id;
|
|
int bytes_in_stream;
|
|
|
|
drdynvc_get_chan_id(s, cmd, &chan_id);
|
|
bytes_in_stream = stream_length_after_p(s);
|
|
|
|
/* get struct xrdp_api_data containing this channel id */
|
|
if ((adp = struct_from_dvc_chan_id(chan_id)) == NULL)
|
|
{
|
|
LOG(0, ("drdynvc_process_data: error : "
|
|
"could not find xrdp_api_data containing chan_id %d", chan_id));
|
|
|
|
return -1;
|
|
}
|
|
|
|
ls = trans_get_out_s(adp->transp, MAX_PDU_SIZE);
|
|
out_uint8p(ls, s->p, bytes_in_stream);
|
|
s_mark_end(ls);
|
|
trans_force_write(adp->transp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* process incoming data on a dynamic virtual channel
|
|
*
|
|
* @pram s stream containing the incoming data
|
|
* @pram chan_id LK_TODO
|
|
* @pram chan_flags LK_TODO
|
|
* @pram length LK_TODO
|
|
* @pram total_length LK_TODO
|
|
*
|
|
* @return 0 on success, -1 on failure
|
|
******************************************************************************/
|
|
int APP_CC
|
|
drdynvc_data_in(struct stream *s, int chan_id, int chan_flags, int length,
|
|
int total_length)
|
|
{
|
|
unsigned char cmd;
|
|
|
|
in_uint8(s, cmd); /* read command */
|
|
|
|
switch (cmd & 0xf0)
|
|
{
|
|
case CMD_DVC_CAPABILITY:
|
|
drdynvc_process_capability_response(s, cmd);
|
|
break;
|
|
|
|
case CMD_DVC_OPEN_CHANNEL:
|
|
drdynvc_process_open_channel_response(s, cmd);
|
|
break;
|
|
|
|
case CMD_DVC_CLOSE_CHANNEL:
|
|
drdynvc_process_close_channel_response(s, cmd);
|
|
break;
|
|
|
|
case CMD_DVC_DATA_FIRST:
|
|
drdynvc_process_data_first(s, cmd);
|
|
break;
|
|
|
|
case CMD_DVC_DATA:
|
|
drdynvc_process_data(s, cmd);
|
|
break;
|
|
|
|
default:
|
|
LOG(0, ("drdynvc_data_in: got unknown command 0x%x", cmd));
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* insert a byte, short or 32bit value into specified stream
|
|
*
|
|
* @param s stream used for insertion
|
|
* @param val value to insert
|
|
*
|
|
* @return 0 for byte insertions
|
|
* @return 1 for short insertion
|
|
* @return 2 for uint32_t insertions
|
|
******************************************************************************/
|
|
static int APP_CC
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* extract channel id from stream
|
|
*
|
|
* @param s stream containing channel id
|
|
* @param cmd first byte in stream
|
|
* @param chan_id return channel id here
|
|
******************************************************************************/
|
|
static int APP_CC
|
|
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)
|
|
{
|
|
in_uint8(s, chan_id);
|
|
}
|
|
else if (cbChId == 1)
|
|
{
|
|
in_uint16_le(s, chan_id);
|
|
}
|
|
else
|
|
{
|
|
in_uint32_le(s, chan_id);
|
|
}
|
|
|
|
*chan_id_p = chan_id;
|
|
|
|
return 0;
|
|
}
|