/* 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]
*/
/** *TheformatsweadvertiseassupportedtotheRDPclient
*/ staticconstint
g_supported_formats[] =
{
CF_UNICODETEXT,
CF_LOCALE,
CF_TEXT, /* Don't advertise CF_OEMTEXT - anything other than ASCII will be broken */ /* CF_OEMTEXT, */ 0
};
/** *DataprivatetotheVNCclipboard * *Notethatthisdoesn'tincludetheclipchannelID,asvnc.cneeds
* 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) */
};
#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
*/ staticconstchar *
cf2text(int cf, char *buff, int bufflen)
{ constchar *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
*/ staticvoid
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()
*/ staticint
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);
/* 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);
}
} elseif ((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
*/ staticint
char_count_in(conststruct stream *s, char c)
{ int rv = 0; constchar *p = s->data; constchar *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, or0ifnot found
*/ staticint
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);
}
/******************************************************************************/ staticint
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
*/ staticvoid
compute_stream_characteristics(conststruct 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 != 0if characteristics are equal
*/ staticint
stream_characteristics_equal(conststruct stream_characteristics *a, conststruct stream_characteristics *b)
{ return (a->length == b->length &&
g_memcmp(a->digest, b->digest, sizeof(a->digest)) == 0);
}
/******************************************************************************/ staticint
handle_cb_format_data_request(struct vnc *v, struct stream *s)
{ int format = 0; struct stream *out_s = NULL; 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)));
/* 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 */ if ((make_stream(out_s)) != NULL)
{
init_stream(out_s, alloclen);
} if (out_s == NULL || 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: /* *Thisisundocumented. * *Reverseengineeringbyfiringthisrequestoffto *aMicrosoftclientsuggeststhisisacodefrom *[MS-LCID].0x409mapstoen-uswhichusescodepage *1252.ThisisacloseenoughmatchtoISO8859-1asused
* 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) mapsdirectlytoUTF-16LEbymovingoverthebottom8bits,
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);
}
/* The [MS-RDPECLIP] specification lets a new CB_FORMAT_LIST PDU turn *upbeforewe'vereceivedaresponsetoaCB_FORMAT_DATA_REQUEST. *Asaresult,theremightbemorethanoneCB_FORMAT_DATA_RESPONSE *PDUsin-flight.WehandlethisbyignoringallbutthelastPDU *we'reexpecting
*/ if (vc->active_data_requests > 0 && --vc->active_data_requests == 0)
{ struct stream *out_s; int length; char c; char lastc; int wc; unsignedint out_of_range = 0;
/* We've got a copy of the current VNC paste buffer in *vc->rfb_clip_s.Sincewe'reabouttochangetheVNCpaste *bufferanyway,we'llusethistoconstructtheISO8859-1 *text,andthensendittotheVNCserver * *Wesizethebufferasfollows:- *TEXTUsethesamesizebuffer. *UTF-16-Usehalfthesize * *Inallcasesthisisbigenough,oralittleoverwhenremoval
* 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);
/******************************************************************************/ staticint
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;
/* 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
*/ staticvoid
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; unsignedint i = 0; int format;
/******************************************************************************/ 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);
}
}
/******************************************************************************/ 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;
}
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;
}
/******************************************************************************/ /** *Processa[MS-RDPBCGR]2.2.6.1VirtualChannelPDUandre-assemblethe *datachunksasneeded-see3.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");
} elseif (first && vc->dechunker_s != NULL)
{ /* *Ifthispacketismarkedas'first',weshouldnotbe
* dechunking data already */
LOG(LOG_LEVEL_ERROR, "Packet chunking start state error");
free_stream(vc->dechunker_s);
vc->dechunker_s = NULL;
} elseif (!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");
} elseif (first && last)
{ /* this is a complete packet *Constructatempstreamforthecompletepacket,andpassit
* to the application */ struct stream packet_s = {0};
/* MS-RDPBCGR 3.1.5.2.2.1 states:- * *AreassemblybufferMUSTbecreatedbythevirtualchannel *endpointusingthesizespecifiedbytotalLengthwhen *thefirstchunkisreceived. * *The'total_size'canbeseveralGBinsize,sowereallyneed
* 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;
}
} elseif (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;
/* 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;
} elseif (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);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.