fb9c175b11
Co-authored-by: matt335672 <30179339+matt335672@users.noreply.github.com>
1117 lines
33 KiB
C
1117 lines
33 KiB
C
|
|
/* startup_complete is only ever set if we're using the VNC clip facility *//**
|
|
* xrdp: A Remote Desktop Protocol server.
|
|
*
|
|
* Copyright (C) Jay Sorg 2004-2015
|
|
*
|
|
* 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.
|
|
*
|
|
* libvnc
|
|
*
|
|
* The message definitions used in this source file can be found mostly
|
|
* in RFC6143 - "The Remote Framebuffer Protocol".
|
|
*
|
|
* The clipboard messages from the RDP client side are mostly
|
|
* described in [MS-RDPECLIP]
|
|
*/
|
|
|
|
#if defined(HAVE_CONFIG_H)
|
|
#include <config_ac.h>
|
|
#endif
|
|
|
|
#include "arch.h"
|
|
#include "vnc.h"
|
|
#include "vnc_clip.h"
|
|
#include "string_calls.h"
|
|
#include "ssl_calls.h"
|
|
#include "rfb.h"
|
|
#include "log.h"
|
|
#include "trans.h"
|
|
#include "ms-rdpbcgr.h"
|
|
#include "ms-rdpeclip.h"
|
|
#include "xrdp_constants.h"
|
|
|
|
/**
|
|
* The formats we advertise as supported to the RDP client
|
|
*/
|
|
static const int
|
|
g_supported_formats[] =
|
|
{
|
|
CF_UNICODETEXT,
|
|
CF_LOCALE,
|
|
CF_TEXT,
|
|
/* Don't advertise CF_OEMTEXT - anything other than ASCII will be broken */
|
|
/* CF_OEMTEXT, */
|
|
0
|
|
};
|
|
|
|
/**
|
|
* Data private to the VNC clipboard
|
|
*
|
|
* Note that this doesn't include the clip channel ID, as vnc.c needs
|
|
* this to redirect virtual channel PDUs to this module */
|
|
struct vnc_clipboard_data
|
|
{
|
|
struct stream *rfb_clip_s;
|
|
int requested_clip_format; /* Last requested text format */
|
|
int active_data_requests; /* Number of outstanding FORMAT_DATA_REQUESTs */
|
|
struct stream *dechunker_s; /* Dechunker for the RDP clip channel */
|
|
int capability_version; /* Clipboard virt channel extension version */
|
|
int capability_flags; /* Channel capability flags */
|
|
bool_t startup_complete; /* is the startup sequence done (1.3.2.1) */
|
|
};
|
|
|
|
/**
|
|
* Summarise a stream contents in a way which allows two streams to
|
|
* be easily compared
|
|
*/
|
|
struct stream_characteristics
|
|
{
|
|
char digest[16];
|
|
int length;
|
|
};
|
|
|
|
#ifdef USE_DEVEL_LOGGING
|
|
/***************************************************************************//**
|
|
* Convert a CF_ constant to text
|
|
*
|
|
* @param CF_xxx constant
|
|
* @param buff Scratchpad for storing a temporary string in
|
|
* @param bufflen Length of the above
|
|
*
|
|
* @return string representation
|
|
*/
|
|
static const char *
|
|
cf2text(int cf, char *buff, int bufflen)
|
|
{
|
|
const char *result;
|
|
|
|
switch (cf)
|
|
{
|
|
case CF_UNICODETEXT:
|
|
result = "CF_UNICODETEXT";
|
|
break;
|
|
|
|
case CF_LOCALE:
|
|
result = "CF_LOCALE";
|
|
break;
|
|
|
|
case CF_TEXT:
|
|
result = "CF_TEXT";
|
|
break;
|
|
|
|
case CF_OEMTEXT:
|
|
result = "CF_OEMTEXT";
|
|
break;
|
|
|
|
default:
|
|
g_snprintf(buff, bufflen, "CF_<0x%08x>", cf);
|
|
result = buff;
|
|
};
|
|
|
|
return result;
|
|
}
|
|
#endif /* USE_DEVEL_LOGGING */
|
|
|
|
/***************************************************************************//**
|
|
* Adds a CLIPRDR_HEADER ([MS-RDPECLIP] 2.2.1) to the data stream
|
|
*
|
|
* The location of the length is stored in the unused 'channel_hdr' field
|
|
* of the data stream. When send_stream_to_clip_channel() is called,
|
|
* we can use update the data length.
|
|
*
|
|
* @param s Output stream
|
|
* @param msg_type Message Type
|
|
* @param msg_flags Message flags
|
|
*/
|
|
static void
|
|
out_cliprdr_header(struct stream *s, int msg_type, int msg_flags)
|
|
{
|
|
out_uint16_le(s, msg_type);
|
|
out_uint16_le(s, msg_flags);
|
|
/* Save the datalen location so we can update it later */
|
|
s_push_layer(s, channel_hdr, 4);
|
|
}
|
|
|
|
/***************************************************************************//**
|
|
* Sends the contents of a stream buffer to the clipboard channel
|
|
*
|
|
* Stream contents are chunked appropriately if they are too big to
|
|
* fit in a single PDU
|
|
*
|
|
* The stream object cliprdr datalen header field is updated by this call.
|
|
*
|
|
* @param v VNC object
|
|
* @param s stream buffer
|
|
*
|
|
* @pre stream buffer must have been initialised with a call to
|
|
* out_cliprdr_header()
|
|
* @pre Stream is terminated with s_mark_end()
|
|
*/
|
|
static int
|
|
send_stream_to_clip_channel(struct vnc *v, struct stream *s)
|
|
{
|
|
int rv = 0;
|
|
int datalen = 0; /* cliprdr PDU datalen field */
|
|
int pos = 0;
|
|
int pdu_len = 0; /* Length of channel PDU */
|
|
int total_data_len = (int)(s->end - s->data);
|
|
int flags;
|
|
int msg_type;
|
|
int msg_flags;
|
|
|
|
/* Use the pointer saved by out_cliprdr_header() to
|
|
* write the cliprdr PDU length */
|
|
s_pop_layer(s, channel_hdr);
|
|
datalen = (int)(s->end - s->p) - 4;
|
|
out_uint32_le(s, datalen);
|
|
|
|
/* Read the other fields of the cliprdr header for logging */
|
|
s->p = s->data;
|
|
in_uint16_le(s, msg_type);
|
|
in_uint16_le(s, msg_flags);
|
|
LOG(LOG_LEVEL_DEBUG, "Sending cliprdr PDU type:%s flags:%d datalen:%d",
|
|
CB_PDUTYPE_TO_STR(msg_type), msg_flags, datalen);
|
|
|
|
|
|
for ( ; rv == 0 && pos < total_data_len ; pos += pdu_len)
|
|
{
|
|
pdu_len = MIN(CHANNEL_CHUNK_LENGTH, (total_data_len - pos));
|
|
|
|
/* Determine chunking flags for this PDU (MS-RDPBCGR 3.1.5.2.1) */
|
|
if (pos == 0)
|
|
{
|
|
if ((pos + pdu_len) == total_data_len)
|
|
{
|
|
/* Only one chunk */
|
|
flags = (XR_CHANNEL_FLAG_FIRST | XR_CHANNEL_FLAG_LAST);
|
|
}
|
|
else
|
|
{
|
|
/* First chunk of several */
|
|
flags = (XR_CHANNEL_FLAG_FIRST | XR_CHANNEL_FLAG_SHOW_PROTOCOL);
|
|
}
|
|
}
|
|
else if ((pos + pdu_len) == total_data_len)
|
|
{
|
|
/* Last chunk of several */
|
|
flags = (XR_CHANNEL_FLAG_LAST | XR_CHANNEL_FLAG_SHOW_PROTOCOL);
|
|
}
|
|
else
|
|
{
|
|
/* Intermediate chunk of several */
|
|
flags = XR_CHANNEL_FLAG_SHOW_PROTOCOL;
|
|
}
|
|
rv = v->server_send_to_channel(v, v->clip_chanid,
|
|
s->data + pos, pdu_len,
|
|
total_data_len, flags);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/***************************************************************************//**
|
|
* Counts the occurrences of a character in a stream
|
|
* @param s stream
|
|
* @param c character
|
|
* @return occurrence count
|
|
*/
|
|
static int
|
|
char_count_in(const struct stream *s, char c)
|
|
{
|
|
int rv = 0;
|
|
const char *p = s->data;
|
|
const char *end = s->end;
|
|
|
|
while ((p = g_strnchr(p, c, end - p)) != NULL)
|
|
{
|
|
++rv;
|
|
++p; /* Skip counted character */
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/***************************************************************************//**
|
|
* Searches a Format List PDU for a preferred text format
|
|
*
|
|
* On entry, the stream contains a formatListData object
|
|
*
|
|
* @param v VNC module
|
|
* @param msg_flags clipHeader msgFlags field
|
|
* @params s formatListData object.
|
|
* @return Preferred text format, or 0 if not found
|
|
*/
|
|
static int
|
|
find_preferred_text_format(struct vnc *v, int msg_flags, struct stream *s)
|
|
{
|
|
int seen_cf_unicodetext = 0;
|
|
int seen_cf_text = 0;
|
|
int format_id;
|
|
#ifdef USE_DEVEL_LOGGING
|
|
char scratch[64];
|
|
#endif
|
|
|
|
while (s_check_rem(s, 4))
|
|
{
|
|
in_uint32_le(s, format_id);
|
|
|
|
if ((v->vc->capability_flags & CB_USE_LONG_FORMAT_NAMES) == 0)
|
|
{
|
|
/* Short format name */
|
|
int skip = MIN(s_rem(s), 32);
|
|
in_uint8s(s, skip);
|
|
}
|
|
else
|
|
{
|
|
/* Skip a wsz string */
|
|
int wc = 1;
|
|
while (s_check_rem(s, 2) && wc != 0)
|
|
{
|
|
in_uint16_le(s, wc);
|
|
}
|
|
}
|
|
|
|
LOG_DEVEL(LOG_LEVEL_INFO, "VNC: Format id %s available"
|
|
" from RDP client",
|
|
cf2text(format_id, scratch, sizeof(scratch)));
|
|
|
|
switch (format_id)
|
|
{
|
|
case CF_UNICODETEXT:
|
|
seen_cf_unicodetext = 1;
|
|
break;
|
|
|
|
case CF_TEXT:
|
|
seen_cf_text = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Prefer Unicode (UTF-16), as it's most easily converted to
|
|
* the ISO-8859-1 supported by the VNC clipboard */
|
|
return
|
|
(seen_cf_unicodetext != 0 ? CF_UNICODETEXT :
|
|
seen_cf_text != 0 ? CF_TEXT :
|
|
/* Default */ 0);
|
|
}
|
|
|
|
/******************************************************************************/
|
|
static int
|
|
handle_cb_format_list(struct vnc *v, int msg_flags, struct stream *s)
|
|
{
|
|
struct stream *out_s;
|
|
int format;
|
|
int rv = 0;
|
|
#ifdef USE_DEVEL_LOGGING
|
|
char scratch[64];
|
|
#endif
|
|
|
|
/* This is the last stage of the startup sequence in MS-RDPECLIP 1.3.2.1,
|
|
* although it does occur at other times */
|
|
v->vc->startup_complete = 1;
|
|
|
|
make_stream(out_s);
|
|
|
|
/* Reply to the caller */
|
|
init_stream(out_s, 64);
|
|
out_cliprdr_header(out_s, CB_FORMAT_LIST_RESPONSE, CB_RESPONSE_OK);
|
|
s_mark_end(out_s);
|
|
send_stream_to_clip_channel(v, out_s);
|
|
|
|
/* Send a CB_DATA_REQUEST message to the cliprdr channel,
|
|
* if a suitable text format is available */
|
|
if ((format = find_preferred_text_format(v, msg_flags, s)) != 0)
|
|
{
|
|
LOG_DEVEL(LOG_LEVEL_INFO,
|
|
"Asking RDP client for clip data format=%s",
|
|
cf2text(format, scratch, sizeof(scratch)));
|
|
v->vc->requested_clip_format = format;
|
|
++v->vc->active_data_requests;
|
|
init_stream(out_s, 64);
|
|
out_cliprdr_header(out_s, CB_FORMAT_DATA_REQUEST, 0);
|
|
out_uint32_le(out_s, format);
|
|
s_mark_end(out_s);
|
|
send_stream_to_clip_channel(v, out_s);
|
|
}
|
|
free_stream(out_s);
|
|
|
|
return rv;
|
|
}
|
|
|
|
/***************************************************************************//**
|
|
* Computes the characteristics of a stream.
|
|
*
|
|
* This can be used to tell if a stream has changed or two streams are
|
|
* the same
|
|
*
|
|
* @param s Stream
|
|
* @param[out] chars Resulting characteristics of stream
|
|
*/
|
|
static void
|
|
compute_stream_characteristics(const struct stream *s,
|
|
struct stream_characteristics *chars)
|
|
{
|
|
void *info = ssl_md5_info_create();
|
|
ssl_md5_clear(info);
|
|
if (s->data != NULL && s->end != NULL)
|
|
{
|
|
chars->length = (int)(s->end - s->data);
|
|
ssl_md5_transform(info, s->data, chars->length);
|
|
}
|
|
else
|
|
{
|
|
chars->length = 0;
|
|
}
|
|
ssl_md5_complete(info, chars->digest);
|
|
ssl_md5_info_delete(info);
|
|
}
|
|
|
|
/***************************************************************************//**
|
|
* Compare two stream characteristics structs for equality
|
|
*
|
|
* @param a characteristics of first stream
|
|
* @param b characteristics of second stream
|
|
* @result != 0 if characteristics are equal
|
|
*/
|
|
static int
|
|
stream_characteristics_equal(const struct stream_characteristics *a,
|
|
const struct stream_characteristics *b)
|
|
{
|
|
return (a->length == b->length &&
|
|
g_memcmp(a->digest, b->digest, sizeof(a->digest)) == 0);
|
|
}
|
|
|
|
/******************************************************************************/
|
|
static int
|
|
handle_cb_format_data_request(struct vnc *v, struct stream *s)
|
|
{
|
|
int format = 0;
|
|
struct stream *out_s;
|
|
int i;
|
|
struct vnc_clipboard_data *vc = v->vc;
|
|
int rv = 0;
|
|
int msg_flags = CB_RESPONSE_OK;
|
|
int lf_count;
|
|
int alloclen = 64;
|
|
#ifdef USE_DEVEL_LOGGING
|
|
char scratch[64];
|
|
#endif
|
|
|
|
if (s_check_rem(s, 4))
|
|
{
|
|
in_uint32_le(s, format);
|
|
}
|
|
|
|
LOG_DEVEL(LOG_LEVEL_INFO, "RDP client requested data format=%s",
|
|
cf2text(format, scratch, sizeof(scratch)));
|
|
|
|
make_stream(out_s);
|
|
|
|
/* For all formats, we need to convert to Windows carriage control,
|
|
* so we need to know how many '\n' characters become '\r\n' */
|
|
lf_count = char_count_in(vc->rfb_clip_s, '\n');
|
|
|
|
/* If we're writing a big string, we need to increase the alloclen
|
|
* for the return PDU. We can also vet the requested format here */
|
|
switch (format)
|
|
{
|
|
case CF_TEXT:
|
|
/* We need to allocate enough characters to hold the string
|
|
* with '\n' becoming '\r\n' and also for a terminator. */
|
|
alloclen += vc->rfb_clip_s->size + lf_count + 1;
|
|
break;
|
|
|
|
case CF_UNICODETEXT:
|
|
/* As CF_TEXT, but twice as much, as each ANSI character maps to
|
|
* two octets */
|
|
alloclen += (vc->rfb_clip_s->size + lf_count + 1) * 2;
|
|
break;
|
|
|
|
case CF_LOCALE:
|
|
break;
|
|
|
|
default:
|
|
/* No idea what this is */
|
|
msg_flags = CB_RESPONSE_FAIL;
|
|
}
|
|
|
|
/* Allocate the stream and check for failure as the string could be
|
|
* essentially unlimited in length */
|
|
init_stream(out_s, alloclen);
|
|
if (out_s->data == NULL)
|
|
{
|
|
LOG(LOG_LEVEL_ERROR,
|
|
"Memory exhausted allocating %d bytes for clip data response",
|
|
alloclen);
|
|
rv = 1;
|
|
}
|
|
else
|
|
{
|
|
/* Write the packet header.... */
|
|
out_cliprdr_header(out_s, CB_FORMAT_DATA_RESPONSE, msg_flags);
|
|
|
|
/* ...and any data */
|
|
switch (format)
|
|
{
|
|
case CF_LOCALE:
|
|
/*
|
|
* This is undocumented.
|
|
*
|
|
* Reverse engineering by firing this request off to
|
|
* a Microsoft client suggests this is a code from
|
|
* [MS-LCID]. 0x409 maps to en-us which uses codepage
|
|
* 1252. This is a close enough match to ISO8859-1 as used
|
|
* by RFB */
|
|
out_uint32_le(out_s, 0x409);
|
|
break;
|
|
|
|
case CF_TEXT:
|
|
for (i = 0; i < vc->rfb_clip_s->size; ++i)
|
|
{
|
|
char c = vc->rfb_clip_s->data[i];
|
|
if (c == '\n')
|
|
{
|
|
out_uint8(out_s, '\r');
|
|
}
|
|
out_uint8(out_s, c);
|
|
}
|
|
|
|
out_uint8s(out_s, 1);
|
|
break;
|
|
|
|
case CF_UNICODETEXT:
|
|
/* The VNC clipboard format (ISO 8859-1)
|
|
maps directly to UTF-16LE by moving over the bottom 8 bits,
|
|
and setting the top 8 bits to zero */
|
|
for (i = 0; i < vc->rfb_clip_s->size; ++i)
|
|
{
|
|
char c = vc->rfb_clip_s->data[i];
|
|
if (c == '\n')
|
|
{
|
|
out_uint8(out_s, '\r');
|
|
out_uint8(out_s, 0);
|
|
}
|
|
out_uint8(out_s, c);
|
|
out_uint8(out_s, 0);
|
|
}
|
|
|
|
out_uint8s(out_s, 2);
|
|
break;
|
|
}
|
|
|
|
s_mark_end(out_s);
|
|
send_stream_to_clip_channel(v, out_s);
|
|
free_stream(out_s);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
static int
|
|
handle_cb_format_data_response(struct vnc *v, struct stream *s)
|
|
{
|
|
int rv = 0;
|
|
|
|
struct vnc_clipboard_data *vc = v->vc;
|
|
|
|
/* The [MS-RDPECLIP] specification lets a new CB_FORMAT_LIST PDU turn
|
|
* up before we've received a response to a CB_FORMAT_DATA_REQUEST.
|
|
* As a result, there might be more than one CB_FORMAT_DATA_RESPONSE
|
|
* PDUs in-flight. We handle this by ignoring all but the last PDU
|
|
* we're expecting
|
|
*/
|
|
if (vc->active_data_requests > 0 && --vc->active_data_requests == 0)
|
|
{
|
|
struct stream *out_s;
|
|
int length;
|
|
char c;
|
|
char lastc;
|
|
int wc;
|
|
unsigned int out_of_range = 0;
|
|
|
|
/* We've got a copy of the current VNC paste buffer in
|
|
* vc->rfb_clip_s. Since we're about to change the VNC paste
|
|
* buffer anyway, we'll use this to construct the ISO8859-1
|
|
* text, and then send it to the VNC server
|
|
*
|
|
* We size the buffer as follows:-
|
|
* TEXT Use the same size buffer.
|
|
* UTF-16 - Use half the size
|
|
*
|
|
* In all cases this is big enough, or a little over when removal
|
|
* of `\r` characters is taken into account */
|
|
if (vc->requested_clip_format == CF_UNICODETEXT)
|
|
{
|
|
length = s_rem(s) / 2;
|
|
}
|
|
else
|
|
{
|
|
length = s_rem(s);
|
|
}
|
|
|
|
init_stream(vc->rfb_clip_s, length);
|
|
if (vc->rfb_clip_s->data == NULL)
|
|
{
|
|
LOG(LOG_LEVEL_ERROR,
|
|
"Memory exhausted allocating %d bytes for clip buffer",
|
|
length);
|
|
rv = 1;
|
|
}
|
|
else
|
|
{
|
|
switch (vc->requested_clip_format)
|
|
{
|
|
case CF_TEXT:
|
|
lastc = '\0';
|
|
while (s_check_rem(s, 1))
|
|
{
|
|
in_uint8(s, c);
|
|
if (c == '\n' && lastc == '\r')
|
|
{
|
|
/* Overwrite the `\r' */
|
|
--vc->rfb_clip_s->p;
|
|
}
|
|
out_uint8(vc->rfb_clip_s, c);
|
|
lastc = c;
|
|
}
|
|
break;
|
|
|
|
case CF_UNICODETEXT:
|
|
lastc = '\0';
|
|
while (s_check_rem(s, 2))
|
|
{
|
|
in_uint16_le(s, wc);
|
|
if (wc / 0x100 == 0)
|
|
{
|
|
/* Valid ISO8859-1 character in bottom 8 bits */
|
|
c = wc % 0x100;
|
|
if (c == '\n' && lastc == '\r')
|
|
{
|
|
/* Overwrite the `\r' */
|
|
--vc->rfb_clip_s->p;
|
|
}
|
|
out_uint8(vc->rfb_clip_s, c);
|
|
lastc = c;
|
|
}
|
|
else
|
|
{
|
|
/* Character can't be represented in ISO8859-1 */
|
|
++out_of_range;
|
|
if (wc & 0xdc00 && wc <= 0xdfff)
|
|
{
|
|
/* Character is start of a surrogate pair */
|
|
if (s_check_rem(s, 2))
|
|
{
|
|
in_uint16_le(s, wc);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (out_of_range > 0)
|
|
{
|
|
LOG(LOG_LEVEL_WARNING,
|
|
"VNC: %u out-of-range Unicode characters"
|
|
" could not be moved to the VNC clipboard",
|
|
out_of_range);
|
|
}
|
|
break;
|
|
|
|
}
|
|
|
|
/* Remove a terminator at the end, as RFB doesn't need it */
|
|
if (vc->rfb_clip_s->p != vc->rfb_clip_s->data &&
|
|
*(vc->rfb_clip_s->p - 1) == '\0')
|
|
{
|
|
--vc->rfb_clip_s->p;
|
|
}
|
|
s_mark_end(vc->rfb_clip_s);
|
|
|
|
/* Update the VNC server */
|
|
make_stream(out_s);
|
|
length = (unsigned int)(vc->rfb_clip_s->end -
|
|
vc->rfb_clip_s->data);
|
|
init_stream(out_s, 1 + 3 + 4 + length);
|
|
out_uint8(out_s, RFB_C2S_CLIENT_CUT_TEXT);
|
|
out_uint8s(out_s, 3); /* padding */
|
|
out_uint32_be(out_s, length);
|
|
out_uint8p(out_s, vc->rfb_clip_s->data, length);
|
|
s_mark_end(out_s);
|
|
lib_send_copy(v, out_s);
|
|
free_stream(out_s);
|
|
}
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
static int
|
|
handle_cb_caps(struct vnc *v, struct stream *s)
|
|
{
|
|
int rv = 0;
|
|
int i;
|
|
int capset_count;
|
|
int capset_type;
|
|
int capset_length;
|
|
int version;
|
|
int flags;
|
|
|
|
if (!s_check_rem_and_log(s, 4, "Reading clip capabilities"))
|
|
{
|
|
rv = 1;
|
|
}
|
|
else
|
|
{
|
|
in_uint16_le(s, capset_count);
|
|
in_uint8s(s, 2); /* pad */
|
|
|
|
for (i = 0; i < capset_count && rv == 0; ++i)
|
|
{
|
|
if (!s_check_rem_and_log(s, 4, "Reading capability set"))
|
|
{
|
|
rv = 1;
|
|
break;
|
|
}
|
|
|
|
in_uint16_le(s, capset_type); /* Length includes these two fields */
|
|
in_uint16_le(s, capset_length);
|
|
capset_length -= 4; /* Account for last two fields */
|
|
|
|
switch (capset_type)
|
|
{
|
|
case CB_CAPSTYPE_GENERAL:
|
|
if (!s_check_rem_and_log(s, 8, "Reading general cap set"))
|
|
{
|
|
rv = 1;
|
|
}
|
|
else
|
|
{
|
|
in_uint32_le(s, version); /* version */
|
|
in_uint32_le(s, flags); /* generalFlags */
|
|
capset_length -= 8;
|
|
|
|
/* Update our own capability fields */
|
|
if (version > 0 && version < v->vc->capability_version)
|
|
{
|
|
v->vc->capability_version = version;
|
|
}
|
|
v->vc->capability_flags &= flags;
|
|
|
|
LOG(LOG_LEVEL_DEBUG,
|
|
"Agreed MS-RDPECLIP capability"
|
|
"version=%d flags=%08x with RDP client",
|
|
v->vc->capability_version,
|
|
v->vc->capability_flags);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
LOG(LOG_LEVEL_WARNING, "clipboard_process_clip_caps: "
|
|
"unknown capabilitySetType %d", capset_type);
|
|
break;
|
|
}
|
|
|
|
/* Check for padding at the end of the set */
|
|
if (capset_length > 0)
|
|
{
|
|
if (!s_check_rem_and_log(s, capset_length, "cap set padding"))
|
|
{
|
|
rv = 1;
|
|
}
|
|
else
|
|
{
|
|
in_uint8s(s, capset_length);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/***************************************************************************//**
|
|
* Send a format list PDU to the RDP client
|
|
*
|
|
* Described in [MS-RDPECLIP] 2.2.3.1
|
|
*
|
|
* @param v VNC structure
|
|
*/
|
|
static void
|
|
send_format_list(struct vnc *v)
|
|
{
|
|
struct vnc_clipboard_data *vc = v->vc;
|
|
int use_long_names = vc->capability_flags & CB_USE_LONG_FORMAT_NAMES;
|
|
struct stream *out_s;
|
|
unsigned int i = 0;
|
|
int format;
|
|
|
|
make_stream(out_s);
|
|
init_stream(out_s, 8192);
|
|
out_cliprdr_header(out_s, CB_FORMAT_LIST, use_long_names);
|
|
|
|
while ((format = g_supported_formats[i++]) != 0)
|
|
{
|
|
if (use_long_names)
|
|
{
|
|
/* Long format name [MS-RDPECLIP] 2.2.3.1.2.1 */
|
|
out_uint32_le(out_s, format);
|
|
out_uint8s(out_s, 2); /* wsz terminator */
|
|
}
|
|
else
|
|
{
|
|
/* Short format name [MS-RDPECLIP] 2.2.3.1.1.1 */
|
|
out_uint32_le(out_s, format);
|
|
out_uint8s(out_s, 32);
|
|
}
|
|
}
|
|
s_mark_end(out_s);
|
|
send_stream_to_clip_channel(v, out_s);
|
|
free_stream(out_s);
|
|
}
|
|
|
|
/******************************************************************************/
|
|
void
|
|
vnc_clip_init(struct vnc *v)
|
|
{
|
|
v->vc = (struct vnc_clipboard_data *)g_malloc(sizeof(*v->vc), 1);
|
|
make_stream(v->vc->rfb_clip_s);
|
|
}
|
|
|
|
/******************************************************************************/
|
|
void
|
|
vnc_clip_exit(struct vnc *v)
|
|
{
|
|
if (v != NULL && v->vc != NULL)
|
|
{
|
|
free_stream(v->vc->rfb_clip_s);
|
|
free_stream(v->vc->dechunker_s);
|
|
g_free(v->vc);
|
|
}
|
|
}
|
|
|
|
|
|
/******************************************************************************/
|
|
static int
|
|
vnc_clip_process_eclip_pdu(struct vnc *v, struct stream *s)
|
|
{
|
|
int type;
|
|
int msg_flags;
|
|
int datalen;
|
|
int rv = 0;
|
|
|
|
/* Ignore PDUs with no complete header */
|
|
if (s_check_rem_and_log(s, 8, "MS-RDPECLIP PDU Header"))
|
|
{
|
|
in_uint16_le(s, type);
|
|
in_uint16_le(s, msg_flags);
|
|
in_uint32_le(s, datalen);
|
|
|
|
LOG(LOG_LEVEL_DEBUG, "got clip data type %s msg_flags %d length %d",
|
|
CB_PDUTYPE_TO_STR(type), msg_flags, datalen);
|
|
LOG_DEVEL_HEXDUMP(LOG_LEVEL_TRACE, "clipboard data",
|
|
s->p, s->end - s->p);
|
|
|
|
/* Check the PDU is contained in the stream */
|
|
if (!s_check_rem_and_log(s, datalen, "MS-RDPECLIP PDU"))
|
|
{
|
|
datalen = s_rem(s);
|
|
}
|
|
else
|
|
{
|
|
/* Truncate the PDU to the data length so we can use the
|
|
* normal functions to parse the PDU */
|
|
s->end = s->p + datalen;
|
|
}
|
|
|
|
switch (type)
|
|
{
|
|
case CB_FORMAT_LIST:
|
|
rv = handle_cb_format_list(v, msg_flags, s);
|
|
break;
|
|
|
|
case CB_FORMAT_LIST_RESPONSE:
|
|
/* We don't need to do anything with this */
|
|
break;
|
|
|
|
case CB_FORMAT_DATA_REQUEST:
|
|
rv = handle_cb_format_data_request(v, s);
|
|
break;
|
|
|
|
case CB_FORMAT_DATA_RESPONSE:
|
|
if (msg_flags == CB_RESPONSE_OK)
|
|
{
|
|
rv = handle_cb_format_data_response(v, s);
|
|
}
|
|
break;
|
|
|
|
case CB_CLIP_CAPS:
|
|
rv = handle_cb_caps(v, s);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
|
|
/******************************************************************************/
|
|
/**
|
|
* Process a [MS-RDPBCGR] 2.2.6.1 Virtual Channel PDU and re-assemble the
|
|
* data chunks as needed - see 3.1.5.2.2.1
|
|
*/
|
|
int
|
|
vnc_clip_process_channel_data(struct vnc *v, char *data, int size,
|
|
int total_size, int flags)
|
|
{
|
|
int rv = 1;
|
|
struct vnc_clipboard_data *vc = v->vc;
|
|
bool_t first = ((flags & XR_CHANNEL_FLAG_FIRST) != 0);
|
|
bool_t last = ((flags & XR_CHANNEL_FLAG_LAST) != 0);
|
|
|
|
if (size > total_size)
|
|
{
|
|
/* This should never happen */
|
|
LOG(LOG_LEVEL_ERROR,
|
|
"Ignoring bad PDU chunk data on clip channel");
|
|
}
|
|
else if (first && vc->dechunker_s != NULL)
|
|
{
|
|
/*
|
|
* If this packet is marked as 'first', we should not be
|
|
* dechunking data already */
|
|
LOG(LOG_LEVEL_ERROR, "Packet chunking start state error");
|
|
free_stream(vc->dechunker_s);
|
|
vc->dechunker_s = NULL;
|
|
}
|
|
else if (!first && vc->dechunker_s == NULL)
|
|
{
|
|
/*
|
|
* This is not the first packet, but the dechunker is not active */
|
|
LOG(LOG_LEVEL_ERROR, "Packet chunking end state error");
|
|
}
|
|
else if (first && last)
|
|
{
|
|
/* this is a complete packet
|
|
* Construct a temp stream for the complete packet, and pass it
|
|
* to the application */
|
|
struct stream packet_s = {0};
|
|
|
|
packet_s.data = data;
|
|
packet_s.size = size;
|
|
packet_s.end = packet_s.data + size;
|
|
packet_s.p = packet_s.data;
|
|
rv = vnc_clip_process_eclip_pdu(v, &packet_s);
|
|
}
|
|
else if (first)
|
|
{
|
|
/* Start de-chunking the data */
|
|
make_stream(vc->dechunker_s);
|
|
init_stream(vc->dechunker_s, (int)total_size);
|
|
|
|
/* MS-RDPBCGR 3.1.5.2.2.1 states:-
|
|
*
|
|
* A reassembly buffer MUST be created by the virtual channel
|
|
* endpoint using the size specified by totalLength when
|
|
* the first chunk is received.
|
|
*
|
|
* The 'total_size' can be several GB in size, so we really need
|
|
* to check for an allocation failure here */
|
|
if (vc->dechunker_s->data == NULL)
|
|
{
|
|
LOG(LOG_LEVEL_ERROR,
|
|
"Memory exhausted dechunking a %u byte virtual channel PDU",
|
|
total_size);
|
|
}
|
|
else
|
|
{
|
|
out_uint8a(vc->dechunker_s, data, size);
|
|
rv = 0;
|
|
}
|
|
}
|
|
else if (s_check_rem_out_and_log(vc->dechunker_s,
|
|
size, "VNC dechunker:"))
|
|
{
|
|
out_uint8a(vc->dechunker_s, data, size);
|
|
/* At the end? */
|
|
if (last)
|
|
{
|
|
s_mark_end(vc->dechunker_s);
|
|
vc->dechunker_s->p = vc->dechunker_s->data;
|
|
|
|
/* Call the app and reset the dechunker */
|
|
rv = vnc_clip_process_eclip_pdu(v, vc->dechunker_s);
|
|
free_stream(vc->dechunker_s);
|
|
vc->dechunker_s = NULL;
|
|
}
|
|
else
|
|
{
|
|
rv = 0;
|
|
}
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/* clip data from the vnc server */
|
|
int
|
|
vnc_clip_process_rfb_data(struct vnc *v)
|
|
{
|
|
struct vnc_clipboard_data *vc = v->vc;
|
|
struct stream *s;
|
|
int size;
|
|
int rv;
|
|
|
|
make_stream(s);
|
|
init_stream(s, 8192);
|
|
rv = trans_force_read_s(v->trans, s, 7);
|
|
|
|
if (rv == 0)
|
|
{
|
|
in_uint8s(s, 3);
|
|
in_uint32_be(s, size);
|
|
|
|
if (v->clip_chanid < 0 || v->server_chansrv_in_use(v))
|
|
{
|
|
/* Skip this message */
|
|
LOG(LOG_LEVEL_DEBUG, "Skipping %d clip bytes from RFB", size);
|
|
rv = skip_trans_bytes(v->trans, size);
|
|
}
|
|
else
|
|
{
|
|
struct stream_characteristics old_chars;
|
|
struct stream_characteristics new_chars;
|
|
|
|
/* Compute the characteristics of the existing data */
|
|
compute_stream_characteristics(vc->rfb_clip_s, &old_chars);
|
|
|
|
/* Lose any existing RFB clip data */
|
|
free_stream(vc->rfb_clip_s);
|
|
vc->rfb_clip_s = 0;
|
|
|
|
make_stream(vc->rfb_clip_s);
|
|
if (size < 0)
|
|
{
|
|
/* This shouldn't happen - see Extended Clipboard
|
|
* Pseudo-Encoding */
|
|
LOG(LOG_LEVEL_ERROR, "Unexpected size %d for RFB data", size);
|
|
rv = 1;
|
|
}
|
|
else if (size == 0)
|
|
{
|
|
LOG(LOG_LEVEL_DEBUG, "RFB clip data cleared by VNC server");
|
|
}
|
|
else
|
|
{
|
|
init_stream(vc->rfb_clip_s, size);
|
|
if (vc->rfb_clip_s->data == NULL)
|
|
{
|
|
LOG(LOG_LEVEL_ERROR,
|
|
"Memory exhausted allocating %d bytes"
|
|
" for RFB clip data",
|
|
size);
|
|
rv = 1;
|
|
}
|
|
else
|
|
{
|
|
LOG(LOG_LEVEL_DEBUG, "Reading %d clip bytes from RFB",
|
|
size);
|
|
rv = trans_force_read_s(v->trans, vc->rfb_clip_s, size);
|
|
}
|
|
}
|
|
|
|
/* Consider telling the RDP client about the update only if we've
|
|
* completed the startup handshake */
|
|
if (rv == 0 && vc->startup_complete)
|
|
{
|
|
/* Has the data actually changed ? */
|
|
compute_stream_characteristics(vc->rfb_clip_s, &new_chars);
|
|
if (stream_characteristics_equal(&old_chars, &new_chars))
|
|
{
|
|
LOG_DEVEL(LOG_LEVEL_INFO, "RFB Clip data is unchanged");
|
|
}
|
|
else
|
|
{
|
|
LOG_DEVEL(LOG_LEVEL_INFO, "RFB Clip data is updated");
|
|
send_format_list(v);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
free_stream(s);
|
|
return rv;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
int
|
|
vnc_clip_open_clip_channel(struct vnc *v)
|
|
{
|
|
v->clip_chanid = v->server_get_channel_id(v, CLIPRDR_SVC_CHANNEL_NAME);
|
|
|
|
if (v->server_chansrv_in_use(v))
|
|
{
|
|
/*
|
|
* The clipboard is provided by chansrv, if at all - it may of
|
|
* course be disabled there.
|
|
*/
|
|
LOG(LOG_LEVEL_INFO, "VNC: Clipboard (if available) is provided "
|
|
"by chansrv facility");
|
|
}
|
|
else if (v->clip_chanid < 0)
|
|
{
|
|
LOG(LOG_LEVEL_INFO, "VNC: Clipboard is unavailable");
|
|
}
|
|
else
|
|
{
|
|
struct stream *s;
|
|
|
|
LOG(LOG_LEVEL_INFO, "VNC: Clipboard supports ISO-8859-1 text only");
|
|
|
|
make_stream(s);
|
|
init_stream(s, 8192);
|
|
|
|
v->vc->capability_version = CB_CAPS_VERSION_2;
|
|
v->vc->capability_flags = CB_USE_LONG_FORMAT_NAMES;
|
|
/**
|
|
* Send two PDUs to initialise the channel. The client should
|
|
* respond with a CB_CLIP_CAPS PDU of its own. See [MS-RDPECLIP]
|
|
* 1.3.2.1 */
|
|
out_cliprdr_header(s, CB_CLIP_CAPS, 0);
|
|
out_uint16_le(s, 1); /* #cCapabilitiesSets */
|
|
out_uint16_le(s, 0); /* pad1 */
|
|
/* CLIPRDR_GENERAL_CAPABILITY */
|
|
out_uint16_le(s, CB_CAPSTYPE_GENERAL); /* capabilitySetType */
|
|
out_uint16_le(s, 12); /* lengthCapability */
|
|
out_uint32_le(s, v->vc->capability_version);
|
|
out_uint32_le(s, v->vc->capability_flags);
|
|
s_mark_end(s);
|
|
send_stream_to_clip_channel(v, s);
|
|
|
|
/* Send the monitor ready PDU */
|
|
init_stream(s, 0);
|
|
out_cliprdr_header(s, CB_MONITOR_READY, 0);
|
|
s_mark_end(s);
|
|
send_stream_to_clip_channel(v, s);
|
|
|
|
free_stream(s);
|
|
/* Need to complete the startup handshake before we send formats */
|
|
v->vc->startup_complete = 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|