/* -*- c-basic-offset: 8 -*- rdesktop: A Remote Desktop Protocol client. Dynamic Channel Virtual Channel Extension. Copyright 2017 Henrik Andersson for Cendio AB Copyright 2017 Karl Mikaelsson for Cendio AB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "rdesktop.h" #include #include #include #define MAX_DVC_CHANNELS 20 #define INVALID_CHANNEL ((uint32_t)-1) #define DYNVC_CREATE_REQ 0x01 #define DYNVC_DATA_FIRST 0x02 #define DYNVC_DATA 0x03 #define DYNVC_CLOSE 0x04 #define DYNVC_CAPABILITIES 0x05 #define DYNVC_DATA_FIRST_COMPRESSED 0x06 #define DYNVC_DATA_COMPRESSED 0x07 #define DYNVC_SOFT_SYNC_REQUEST 0x08 #define DYNVC_SOFT_SYNC_RESPONSE 0x09 typedef union { uint8_t data; struct { uint8_t cbid : 2; uint8_t sp : 2; uint8_t cmd : 4; } hdr; } dvc_hdr_t; typedef struct { uint32_t hash; uint32_t channel_id; dvc_channel_process_fn handler; } dvc_channel_t; static VCHANNEL *dvc_channel; static dvc_channel_t channels[MAX_DVC_CHANNELS]; static uint32_t dvc_in_channelid(STREAM s, dvc_hdr_t hdr); static bool dvc_channels_exists(const char *name) { uint32_t hash = utils_djb2_hash(name); for (int i = 0; i < MAX_DVC_CHANNELS; i++) { if (channels[i].hash == hash) return true; } return false; } static const dvc_channel_t *dvc_channels_get_by_id(uint32_t id) { for (int i = 0; i < MAX_DVC_CHANNELS; i++) { if (channels[i].channel_id == id) { return &channels[i]; } } return NULL; } static uint32_t dvc_channels_get_id(const char *name) { uint32_t hash = utils_djb2_hash(name); for (int i = 0; i < MAX_DVC_CHANNELS; i++) { if (channels[i].hash == hash) { return channels[i].channel_id; } } return INVALID_CHANNEL; } static bool dvc_channels_remove_by_id(uint32_t channelid) { for (int i = 0; i < MAX_DVC_CHANNELS; i++) { if (channels[i].channel_id == channelid) { memset(&channels[i], 0, sizeof(dvc_channel_t)); return true; } } return false; } static bool dvc_channels_add(const char *name, dvc_channel_process_fn handler, uint32_t channel_id) { if (dvc_channels_exists(name)) { logger(Core, Warning, "dvc_channels_add(), channel with name '%s' already exists", name); return false; } for (int i = 0; i < MAX_DVC_CHANNELS; i++) { if (channels[i].hash == 0) { uint32_t hash = utils_djb2_hash(name); channels[i].hash = hash; channels[i].handler = handler; channels[i].channel_id = channel_id; logger(Core, Debug, "dvc_channels_add(), Added hash=%x, channel_id=%d, name=%s, handler=%p", hash, channel_id, name, handler); return true; } } logger(Core, Warning, "dvc_channels_add(), Failed to add channel, maximum number of channels are being used"); return false; } static int dvc_channels_set_id(const char *name, uint32_t channel_id) { uint32_t hash = utils_djb2_hash(name); for (int i = 0; i < MAX_DVC_CHANNELS; i++) { if (channels[i].hash == hash) { logger(Core, Debug, "dvc_channels_set_id(), name = '%s', channel_id = %d", name, channel_id); channels[i].channel_id = channel_id; return 0; } } return -1; } bool dvc_channels_is_available(const char *name) { uint32_t hash = utils_djb2_hash(name); for (int i = 0; i < MAX_DVC_CHANNELS; i++) { if (channels[i].hash == hash) { return (channels[i].channel_id != INVALID_CHANNEL); } } return false; } bool dvc_channels_register(const char *name, dvc_channel_process_fn handler) { return dvc_channels_add(name, handler, INVALID_CHANNEL); } static STREAM dvc_init_packet(dvc_hdr_t hdr, uint32_t channelid, size_t length) { STREAM s; length += 1; // add 1 byte hdr if (channelid != INVALID_CHANNEL) { if (hdr.hdr.cbid == 0) length += 1; else if (hdr.hdr.cbid == 1) length += 2; else if (hdr.hdr.cbid == 2) length += 4; } s = channel_init(dvc_channel, length); out_uint8(s, hdr.data); // DVC header if (channelid != INVALID_CHANNEL) { if (hdr.hdr.cbid == 0) { out_uint8(s, channelid); } else if (hdr.hdr.cbid == 1) { out_uint16_le(s, channelid); } else if (hdr.hdr.cbid == 2) { out_uint32_le(s, channelid); } } return s; } void dvc_send(const char *name, STREAM s) { STREAM ls; dvc_hdr_t hdr; uint32_t channel_id = dvc_channels_get_id(name); if (channel_id == INVALID_CHANNEL) { logger(Core, Error, "dvc_send(), Trying to send data on invalid channel '%s'", name); return; } // FIXME: we assume length is less than 1600 hdr.hdr.cmd = DYNVC_DATA; hdr.hdr.cbid = 2; hdr.hdr.sp = 0; ls = dvc_init_packet(hdr, channel_id, s_length(s)); out_stream(ls, s); s_mark_end(ls); channel_send(ls, dvc_channel); s_free(ls); } static void dvc_send_capabilities_response() { STREAM s; dvc_hdr_t hdr; uint16_t supportedversion = 0x01; hdr.hdr.cbid = 0x00; hdr.hdr.sp = 0x00; hdr.hdr.cmd = DYNVC_CAPABILITIES; logger(Protocol, Debug, "dvc_send_capabilities_response(), offering support for dvc %d", supportedversion); s = dvc_init_packet(hdr, (uint32_t)-1, 3); out_uint8(s, 0x00); // pad out_uint16_le(s, supportedversion); // version s_mark_end(s); channel_send(s, dvc_channel); s_free(s); } static void dvc_process_caps_pdu(STREAM s) { uint16_t version; in_uint8s(s, 1); // pad in_uint16_le(s, version); // version logger(Protocol, Debug, "dvc_process_caps(), server supports dvc %d", version); dvc_send_capabilities_response(); } static void dvc_send_create_response(bool success, dvc_hdr_t hdr, uint32_t channelid) { STREAM s; logger(Protocol, Debug, "dvc_send_create_response(), %s request to create channelid %d", (success ? "granted" : "denied"), channelid); s = dvc_init_packet(hdr, channelid, 4); out_uint32_le(s, success ? 0 : (uint32_t)-1); s_mark_end(s); channel_send(s, dvc_channel); s_free(s); } static void dvc_process_create_pdu(STREAM s, dvc_hdr_t hdr) { char name[512]; uint32_t channelid; channelid = dvc_in_channelid(s, hdr); in_ansi_string(s, name, sizeof(name)); logger(Protocol, Debug, "dvc_process_create(), server requests channelid = %d, name = '%s'", channelid, name); if (dvc_channels_exists(name)) { logger(Core, Verbose, "Established dynamic virtual channel '%s'", name); dvc_channels_set_id(name, channelid); dvc_send_create_response(true, hdr, channelid); } else { dvc_send_create_response(false, hdr, channelid); } } static uint32_t dvc_in_channelid(STREAM s, dvc_hdr_t hdr) { uint32_t id = (uint32_t)-1; switch (hdr.hdr.cbid) { case 0: in_uint8(s, id); break; case 1: in_uint16_le(s, id); break; case 2: in_uint32_le(s, id); break; } return id; } static void dvc_process_data_pdu(STREAM s, dvc_hdr_t hdr) { const dvc_channel_t *ch; uint32_t channelid = dvc_in_channelid(s, hdr); ch = dvc_channels_get_by_id(channelid); if (ch == NULL) { logger(Protocol, Warning, "dvc_process_data(), Received data on unregistered channel %d", channelid); return; } // Dispatch packet to channel handler ch->handler(s); } static void dvc_process_close_pdu(STREAM s, dvc_hdr_t hdr) { uint32_t channelid = dvc_in_channelid(s, hdr); logger(Protocol, Debug, "dvc_process_close_pdu(), close channel %d", channelid); if (!dvc_channels_remove_by_id(channelid)) { logger(Protocol, Warning, "dvc_process_close_pdu(), Received close request for unregistered channel %d", channelid); } } static void dvc_process_pdu(STREAM s) { dvc_hdr_t hdr; in_uint8(s, hdr.data); switch (hdr.hdr.cmd) { case DYNVC_CAPABILITIES: dvc_process_caps_pdu(s); break; case DYNVC_CREATE_REQ: dvc_process_create_pdu(s, hdr); break; case DYNVC_DATA: dvc_process_data_pdu(s, hdr); break; case DYNVC_CLOSE: dvc_process_close_pdu(s, hdr); break; #if 0 // Unimplemented case DYNVC_DATA_FIRST: break; case DYNVC_DATA_FIRST_COMPRESSED: break; case DYNVC_DATA_COMPRESSED: break; case DYNVC_SOFT_SYNC_REQUEST: break; case DYNVC_SOFT_SYNC_RESPONSE: break; #endif default: logger(Protocol, Warning, "dvc_process_pdu(), Unhandled command type 0x%x", hdr.hdr.cmd); break; } } bool dvc_init() { memset(channels, 0, sizeof(channels)); dvc_channel = channel_register("drdynvc", CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP, dvc_process_pdu); return (dvc_channel != NULL); }