Merge pull request #2082 from kenhys/extend-inbound-outbound-restriction

Extend inbound/outbound clipboard restriction
This commit is contained in:
matt335672 2022-01-14 11:17:49 +00:00 committed by GitHub
commit dfd64e2147
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 941 additions and 95 deletions

View File

@ -986,3 +986,72 @@ g_bitmask_to_str(int bitmask, const struct bitmask_string bitdefs[],
return rlen;
}
int
g_str_to_bitmask(const char *str, const struct bitmask_string bitdefs[],
const char *delim, char *unrecognised, int unrecognised_len)
{
char *properties = NULL;
char *p = NULL;
int mask = 0;
if (unrecognised_len < 1)
{
/* No space left to tell unrecognised tokens */
return 0;
}
if (!unrecognised)
{
return 0;
}
/* ensure not to return with uninitialized buffer */
unrecognised[0] = '\0';
if (!str || !bitdefs || !delim)
{
return 0;
}
properties = g_strdup(str);
if (!properties)
{
return 0;
}
p = strtok(properties, delim);
while (p != NULL)
{
g_strtrim(p, 3);
const struct bitmask_string *b;
int found = 0;
for (b = &bitdefs[0] ; b->str != NULL; ++b)
{
if (0 == g_strcasecmp(p, b->str))
{
mask |= b->mask;
found = 1;
break;
}
}
if (found == 0)
{
int length = g_strlen(unrecognised);
if (length > 0)
{
/* adding ",property" */
if (length + g_strlen(p) + 1 < unrecognised_len)
{
unrecognised[length] = delim[0];
length += 1;
g_strcpy(unrecognised + length, p);
}
}
else if (g_strlen(p) < unrecognised_len)
{
g_strcpy(unrecognised, p);
}
}
p = strtok(NULL, delim);
}
g_free(properties);
return mask;
}

View File

@ -174,6 +174,20 @@ int
g_bitmask_to_str(int bitmask, const struct bitmask_string[],
char delim, char *buff, int bufflen);
/***
* Converts a string containing a series of tokens to a bitmask.
* @param str Input string
* @param bitmask_string Array mapping tokens to bitmask values
* @param delim Delimiter for tokens in str
* @param[out] unrecognised Buffer for any unrecognised tokens
* @param unrecognised_len Length of unrecognised including '\0';
* @return bitmask value for recognised tokens
*/
int
g_str_to_bitmask(const char *str, const struct bitmask_string[],
const char *delim, char *unrecognised,
int unrecognised_len);
int g_strlen(const char *text);
char *g_strchr(const char *text, int c);
char *g_strrchr(const char *text, int c);

View File

@ -221,10 +221,62 @@ login for all users is enabled.
have session management rights.
.TP
\fBRestrictOutboundClipboard\fR=\fI[true|false]\fR
If set to \fB1\fR, \fBtrue\fR or \fByes\fR, will restrict the clipboard
\fBRestrictOutboundClipboard\fR=\fI[all|none|text|file|image]\fR
If set to \fBall\fR, will restrict the clipboard
outbound from the server, to prevent data copied inside the xrdp session
to be be pasted in the client host. Default value is \fBfalse\fR.
to be pasted in the client. Default value is \fBnone\fR.
In addition, you can control text/file/image transfer restrictions
respectively. It also accepts comma separated list such as text,file,image.
.br
.br
\fBnone\fR - No restriction about copying inbound clipboard data.
.br
\fBall\fR - Restrict to copy inbound clipboard data.
.br
\fBtext\fR - Restrict to copy only inbound text clipboard data.
.br
\fBfile\fR - Restrict to copy only inbound file clipboard data.
.br
\fBimage\fR - Restrict to copy only inbound image clipboard data.
.br
To keep compatibility, the following aliases are also available.
.br
\fBtrue\fR - an alias of \fBall\fR.
.br
\fBfalse\fR - an alias of \fBnone\fR.
.br
\fByes\fR - an alias of \fBall\fR.
.TP
\fBRestrictInboundClipboard\fR=\fI[none|all|text|file|image]\fR
If set to \fBall\fR, will restrict the clipboard
inbound from the client, to prevent data copied inside the client
to be pasted in the xrdp session. Default value is \fBnone\fR.
In addition, you can control text/file/image transfer restrictions
respectively. It also accepts comma separated list such as text,file,image.
.br
.br
\fBnone\fR - No restriction about copying inbound clipboard data.
.br
\fBall\fR - Restrict to copy inbound clipboard data.
.br
\fBtext\fR - Restrict to copy only inbound text clipboard data.
.br
\fBfile\fR - Restrict to copy only inbound file clipboard data.
.br
\fBimage\fR - Restrict to copy only inbound image clipboard data.
.br
To keep compatibility, the following aliases are also available.
.br
\fBtrue\fR - an alias of \fBall\fR.
.br
\fBfalse\fR - an alias of \fBnone\fR.
.br
\fByes\fR - an alias of \fBall\fR.
.TP
\fBAlwaysGroupCheck\fR=\fI[true|false]\fR

View File

@ -22,6 +22,13 @@
#include "parse.h"
#include "os_calls.h"
/* Define bitmask values for restricting the clipboard */
#define CLIP_RESTRICT_NONE 0
#define CLIP_RESTRICT_TEXT (1<<0)
#define CLIP_RESTRICT_FILE (1<<1)
#define CLIP_RESTRICT_IMAGE (1<<2)
#define CLIP_RESTRICT_ALL 0x7fffffff
int read_entire_packet(struct stream *src, struct stream **dest, int chan_flags, int length, int total_length);
#endif

View File

@ -30,12 +30,14 @@
#include "file.h"
#include "os_calls.h"
#include "chansrv_common.h"
#include "chansrv_config.h"
#include "string_calls.h"
/* Default settings */
#define DEFAULT_USE_UNIX_SOCKET 0
#define DEFAULT_RESTRICT_OUTBOUND_CLIPBOARD 0
#define DEFAULT_RESTRICT_INBOUND_CLIPBOARD 0
#define DEFAULT_ENABLE_FUSE_MOUNT 1
#define DEFAULT_FUSE_MOUNT_NAME "xrdp-client"
#define DEFAULT_FILE_UMASK 077
@ -48,6 +50,21 @@ printflike(2, 3)
enum logReturns (*log_func_t)(const enum logLevels lvl,
const char *msg, ...);
/* Map clipboard strings into bitmask values */
static const struct bitmask_string clip_restrict_map[] =
{
{ CLIP_RESTRICT_TEXT, "text"},
{ CLIP_RESTRICT_FILE, "file"},
{ CLIP_RESTRICT_IMAGE, "image"},
{ CLIP_RESTRICT_ALL, "all"},
{ CLIP_RESTRICT_NONE, "none"},
/* Compatibility values */
{ CLIP_RESTRICT_ALL, "true"},
{ CLIP_RESTRICT_ALL, "yes"},
{ CLIP_RESTRICT_NONE, "false"},
BITMASK_STRING_END_OF_LIST
};
/***************************************************************************//**
* @brief Error logging function to use to log to stdout
*
@ -125,9 +142,30 @@ read_config_security(log_func_t logmsg,
const char *name = (const char *)list_get_item(names, index);
const char *value = (const char *)list_get_item(values, index);
char unrecognised[256];
if (g_strcasecmp(name, "RestrictOutboundClipboard") == 0)
{
cfg->restrict_outbound_clipboard = g_text2bool(value);
cfg->restrict_outbound_clipboard =
g_str_to_bitmask(value, clip_restrict_map, ",",
unrecognised, sizeof(unrecognised));
if (unrecognised[0] != '\0')
{
LOG(LOG_LEVEL_WARNING,
"Unrecognised tokens parsing 'RestrictOutboundClipboard' %s",
unrecognised);
}
}
if (g_strcasecmp(name, "RestrictInboundClipboard") == 0)
{
cfg->restrict_inbound_clipboard =
g_str_to_bitmask(value, clip_restrict_map, ",",
unrecognised, sizeof(unrecognised));
if (unrecognised[0] != '\0')
{
LOG(LOG_LEVEL_WARNING,
"Unrecognised tokens parsing 'RestrictInboundClipboard' %s",
unrecognised);
}
}
}
@ -208,6 +246,7 @@ new_config(void)
cfg->use_unix_socket = DEFAULT_USE_UNIX_SOCKET;
cfg->enable_fuse_mount = DEFAULT_ENABLE_FUSE_MOUNT;
cfg->restrict_outbound_clipboard = DEFAULT_RESTRICT_OUTBOUND_CLIPBOARD;
cfg->restrict_inbound_clipboard = DEFAULT_RESTRICT_INBOUND_CLIPBOARD;
cfg->fuse_mount_name = fuse_mount_name;
cfg->file_umask = DEFAULT_FILE_UMASK;
cfg->use_nautilus3_flist_format = DEFAULT_USE_NAUTILUS3_FLIST_FORMAT;
@ -286,10 +325,37 @@ config_dump(struct config_chansrv *config)
g_writeln(" UseUnixSocket (derived): %s",
g_bool2text(config->use_unix_socket));
char buf[256];
g_writeln("\nSecurity configuration:");
g_writeln(" RestrictOutboundClipboard: %s",
g_bool2text(config->restrict_outbound_clipboard));
if (config->restrict_outbound_clipboard == CLIP_RESTRICT_NONE)
{
g_writeln(" RestrictOutboundClipboard: %s", "none");
}
else if (config->restrict_outbound_clipboard == CLIP_RESTRICT_ALL)
{
g_writeln(" RestrictOutboundClipboard: %s", "all");
}
else
{
g_bitmask_to_str(config->restrict_outbound_clipboard,
clip_restrict_map, ',', buf, sizeof(buf));
g_writeln(" RestrictOutboundClipboard: %s", buf);
}
g_writeln(" RestrictInboundClipboard: %s", buf);
if (config->restrict_inbound_clipboard == CLIP_RESTRICT_NONE)
{
g_writeln(" RestrictInboundClipboard: %s", "none");
}
else if (config->restrict_inbound_clipboard == CLIP_RESTRICT_ALL)
{
g_writeln(" RestrictInboundClipboard: %s", "all");
}
else
{
g_bitmask_to_str(config->restrict_inbound_clipboard,
clip_restrict_map, ',', buf, sizeof(buf));
g_writeln(" RestrictInboundClipboard: %s", buf);
}
g_writeln("\nChansrv configuration:");
g_writeln(" EnableFuseMount %s",
g_bool2text(config->enable_fuse_mount));

View File

@ -31,6 +31,8 @@ struct config_chansrv
/** RestrictOutboundClipboard setting from sesman.ini */
int restrict_outbound_clipboard;
/** RestrictInboundClipboard setting from sesman.ini */
int restrict_inbound_clipboard;
/** * FuseMountName from sesman.ini */
char *fuse_mount_name;

View File

@ -171,6 +171,7 @@ x-special/gnome-copied-files
#include "os_calls.h"
#include "string_calls.h"
#include "chansrv.h"
#include "chansrv_common.h"
#include "chansrv_config.h"
#include "clipboard.h"
#include "clipboard_file.h"
@ -2005,14 +2006,33 @@ clipboard_event_selection_notify(XEvent *xevent)
g_clip_s2c.data[g_clip_s2c.total_bytes] = 0;
if (g_clip_s2c.xrdp_clip_type == XRDP_CB_FILE)
{
clipboard_send_data_response_for_file(g_clip_s2c.data,
g_clip_s2c.total_bytes);
if (g_cfg->restrict_outbound_clipboard & CLIP_RESTRICT_FILE)
{
LOG(LOG_LEVEL_DEBUG,
"outbound clipboard(file) UTF8_STRING(%s) is restricted because of config",
g_clip_s2c.data);
}
else
{
clipboard_send_data_response_for_file(g_clip_s2c.data,
g_clip_s2c.total_bytes);
}
}
else
{
clipboard_send_data_response_for_text(g_clip_s2c.data,
g_clip_s2c.total_bytes);
if (g_cfg->restrict_outbound_clipboard & CLIP_RESTRICT_TEXT)
{
LOG(LOG_LEVEL_DEBUG,
"outbound clipboard(text) UTF8_STRING(%s) is restricted because of config",
g_clip_s2c.data);
}
else
{
clipboard_send_data_response_for_text(g_clip_s2c.data,
g_clip_s2c.total_bytes);
}
}
}
}
else if (lxevent->target == XA_STRING)
@ -2026,8 +2046,18 @@ clipboard_event_selection_notify(XEvent *xevent)
g_clip_s2c.data = (char *) g_malloc(g_clip_s2c.total_bytes + 1, 0);
g_memcpy(g_clip_s2c.data, data, g_clip_s2c.total_bytes);
g_clip_s2c.data[g_clip_s2c.total_bytes] = 0;
clipboard_send_data_response_for_text(g_clip_s2c.data,
g_clip_s2c.total_bytes);
if (g_cfg->restrict_outbound_clipboard & CLIP_RESTRICT_TEXT)
{
LOG(LOG_LEVEL_DEBUG,
"outbound clipboard(text) XA_STRING(%s) is restricted because of config",
g_clip_s2c.data);
}
else
{
clipboard_send_data_response_for_text(g_clip_s2c.data,
g_clip_s2c.total_bytes);
}
}
}
else if (lxevent->target == g_image_bmp_atom)
@ -2037,11 +2067,21 @@ clipboard_event_selection_notify(XEvent *xevent)
if ((g_clip_s2c.incr_in_progress == 0) && (data_size > 14))
{
g_free(g_clip_s2c.data);
g_clip_s2c.total_bytes = data_size;
g_clip_s2c.data = (char *) g_malloc(data_size, 0);
g_memcpy(g_clip_s2c.data, data, data_size);
clipboard_send_data_response_for_image(g_clip_s2c.data + 14,
data_size - 14);
if (g_cfg->restrict_outbound_clipboard & CLIP_RESTRICT_IMAGE)
{
LOG(LOG_LEVEL_DEBUG,
"outbound clipboard(image) image/bmp is restricted because of config");
}
else
{
g_clip_s2c.total_bytes = data_size;
g_clip_s2c.data = (char *) g_malloc(data_size, 0);
g_memcpy(g_clip_s2c.data, data, data_size);
clipboard_send_data_response_for_image(g_clip_s2c.data + 14,
data_size - 14);
}
}
}
else if (lxevent->target == g_file_atom1)
@ -2055,8 +2095,19 @@ clipboard_event_selection_notify(XEvent *xevent)
g_clip_s2c.data = (char *) g_malloc(g_clip_s2c.total_bytes + 1, 0);
g_memcpy(g_clip_s2c.data, data, g_clip_s2c.total_bytes);
g_clip_s2c.data[g_clip_s2c.total_bytes] = 0;
clipboard_send_data_response_for_file(g_clip_s2c.data,
g_clip_s2c.total_bytes);
if (g_cfg->restrict_outbound_clipboard & CLIP_RESTRICT_FILE)
{
LOG(LOG_LEVEL_DEBUG,
"outbound clipboard(file) text/uri-list(%s) is restricted because of config",
g_clip_s2c.data);
}
else
{
clipboard_send_data_response_for_file(g_clip_s2c.data,
g_clip_s2c.total_bytes);
}
}
}
else if (lxevent->target == g_file_atom2)
@ -2070,8 +2121,18 @@ clipboard_event_selection_notify(XEvent *xevent)
g_clip_s2c.data = (char *) g_malloc(g_clip_s2c.total_bytes + 1, 0);
g_memcpy(g_clip_s2c.data, data, g_clip_s2c.total_bytes);
g_clip_s2c.data[g_clip_s2c.total_bytes] = 0;
clipboard_send_data_response_for_file(g_clip_s2c.data,
g_clip_s2c.total_bytes);
if (g_cfg->restrict_outbound_clipboard & CLIP_RESTRICT_FILE)
{
LOG(LOG_LEVEL_DEBUG,
"outbound clipboard(file) x-special/gnome-copied-files(%s) is restricted because of config",
g_clip_s2c.data);
}
else
{
clipboard_send_data_response_for_file(g_clip_s2c.data,
g_clip_s2c.total_bytes);
}
}
}
else
@ -2090,35 +2151,81 @@ clipboard_event_selection_notify(XEvent *xevent)
if (got_file_atom != 0)
{
/* text/uri-list or x-special/gnome-copied-files */
g_clip_s2c.type = got_file_atom;
g_clip_s2c.xrdp_clip_type = XRDP_CB_FILE;
g_clip_s2c.converted = 0;
g_clip_s2c.clip_time = lxevent->time;
send_format_announce = 1;
if (g_cfg->restrict_outbound_clipboard & CLIP_RESTRICT_FILE)
{
LOG(LOG_LEVEL_DEBUG,
"outbound clipboard(file) is restricted because of config");
}
else
{
g_clip_s2c.type = got_file_atom;
g_clip_s2c.xrdp_clip_type = XRDP_CB_FILE;
g_clip_s2c.converted = 0;
g_clip_s2c.clip_time = lxevent->time;
send_format_announce = 1;
}
}
else if (got_utf8)
{
g_clip_s2c.type = g_utf8_atom;
g_clip_s2c.xrdp_clip_type = XRDP_CB_TEXT;
g_clip_s2c.converted = 0;
g_clip_s2c.clip_time = lxevent->time;
send_format_announce = 1;
if (g_cfg->restrict_outbound_clipboard & CLIP_RESTRICT_TEXT)
{
LOG(LOG_LEVEL_DEBUG,
"outbound clipboard(text) is restricted because of config");
}
else
{
g_clip_s2c.type = g_utf8_atom;
g_clip_s2c.xrdp_clip_type = XRDP_CB_TEXT;
g_clip_s2c.converted = 0;
g_clip_s2c.clip_time = lxevent->time;
send_format_announce = 1;
}
}
else if (got_string)
{
g_clip_s2c.type = XA_STRING;
g_clip_s2c.xrdp_clip_type = XRDP_CB_TEXT;
g_clip_s2c.converted = 0;
g_clip_s2c.clip_time = lxevent->time;
send_format_announce = 1;
/*
* In most cases, when copying text, TARGETS atom and UTF8_STRING atom exists,
* it means that this code block which checks STRING atom might not be never executed
* in recent platforms.
* Use echo foo | xclip -selection clipboard -noutf8 to reproduce it.
*/
if (g_cfg->restrict_outbound_clipboard & CLIP_RESTRICT_TEXT)
{
LOG(LOG_LEVEL_DEBUG,
"outbound clipboard(text) is restricted because of config");
}
else
{
g_clip_s2c.type = XA_STRING;
g_clip_s2c.xrdp_clip_type = XRDP_CB_TEXT;
g_clip_s2c.converted = 0;
g_clip_s2c.clip_time = lxevent->time;
send_format_announce = 1;
}
}
else if (got_bmp_image)
{
g_clip_s2c.type = g_image_bmp_atom;
g_clip_s2c.xrdp_clip_type = XRDP_CB_BITMAP;
g_clip_s2c.converted = 0;
g_clip_s2c.clip_time = lxevent->time;
send_format_announce = 1;
if (g_cfg->restrict_outbound_clipboard & CLIP_RESTRICT_IMAGE)
{
LOG(LOG_LEVEL_DEBUG,
"outbound clipboard(image) is restricted because of config");
}
else
{
g_clip_s2c.type = g_image_bmp_atom;
g_clip_s2c.xrdp_clip_type = XRDP_CB_BITMAP;
g_clip_s2c.converted = 0;
g_clip_s2c.clip_time = lxevent->time;
send_format_announce = 1;
}
}
if (send_format_announce)
@ -2187,16 +2294,23 @@ clipboard_event_selection_request(XEvent *xevent)
atom_buf[0] = g_targets_atom;
atom_buf[1] = g_timestamp_atom;
atom_buf[2] = g_multiple_atom;
atom_buf[3] = XA_STRING;
atom_buf[4] = g_utf8_atom;
atom_count = 5;
if (clipboard_find_format_id(CB_FORMAT_DIB) >= 0)
atom_count = 3;
if ((g_cfg->restrict_inbound_clipboard & CLIP_RESTRICT_TEXT) == 0)
{
atom_buf[atom_count] = XA_STRING;
atom_count++;
atom_buf[atom_count] = g_utf8_atom;
atom_count++;
}
if (clipboard_find_format_id(CB_FORMAT_DIB) >= 0 &&
(g_cfg->restrict_inbound_clipboard & CLIP_RESTRICT_IMAGE) == 0)
{
LOG_DEVEL(LOG_LEVEL_DEBUG, " reporting image/bmp");
atom_buf[atom_count] = g_image_bmp_atom;
atom_count++;
}
if (clipboard_find_format_id(g_file_format_id) >= 0)
if (clipboard_find_format_id(g_file_format_id) >= 0 &&
(g_cfg->restrict_inbound_clipboard & CLIP_RESTRICT_FILE) == 0)
{
LOG_DEVEL(LOG_LEVEL_DEBUG, " reporting text/uri-list");
atom_buf[atom_count] = g_file_atom1;
@ -2243,19 +2357,43 @@ clipboard_event_selection_request(XEvent *xevent)
{
LOG_DEVEL(LOG_LEVEL_DEBUG, "clipboard_event_selection_request: "
"text requested when files available");
g_memcpy(&g_saved_selection_req_event, lxev,
sizeof(g_saved_selection_req_event));
g_clip_c2s.type = lxev->target;
g_clip_c2s.xrdp_clip_type = XRDP_CB_FILE;
clipboard_send_data_request(g_file_format_id);
if (g_cfg->restrict_inbound_clipboard & CLIP_RESTRICT_FILE)
{
LOG(LOG_LEVEL_DEBUG,
"inbound clipboard %s is restricted because of config",
lxev->target == XA_STRING ? "XA_STRING" : "UTF8_STRING");
clipboard_refuse_selection(lxev);
}
else
{
g_memcpy(&g_saved_selection_req_event, lxev,
sizeof(g_saved_selection_req_event));
g_clip_c2s.type = lxev->target;
g_clip_c2s.xrdp_clip_type = XRDP_CB_FILE;
clipboard_send_data_request(g_file_format_id);
}
}
else
{
g_memcpy(&g_saved_selection_req_event, lxev,
sizeof(g_saved_selection_req_event));
g_clip_c2s.type = lxev->target;
g_clip_c2s.xrdp_clip_type = XRDP_CB_TEXT;
clipboard_send_data_request(CB_FORMAT_UNICODETEXT);
if (g_cfg->restrict_inbound_clipboard & CLIP_RESTRICT_TEXT)
{
LOG(LOG_LEVEL_DEBUG,
"inbound clipboard %s is restricted because of config",
lxev->target == XA_STRING ? "XA_STRING" : "UTF8_STRING");
clipboard_refuse_selection(lxev);
}
else
{
g_memcpy(&g_saved_selection_req_event, lxev,
sizeof(g_saved_selection_req_event));
g_clip_c2s.type = lxev->target;
g_clip_c2s.xrdp_clip_type = XRDP_CB_TEXT;
clipboard_send_data_request(CB_FORMAT_UNICODETEXT);
}
}
return 0;
}
@ -2265,15 +2403,37 @@ clipboard_event_selection_request(XEvent *xevent)
if ((g_clip_c2s.type == lxev->target) && g_clip_c2s.converted)
{
LOG_DEVEL(LOG_LEVEL_DEBUG, "clipboard_event_selection_request: -------------------------------------------");
clipboard_provide_selection_c2s(lxev, lxev->target);
if (g_cfg->restrict_inbound_clipboard & CLIP_RESTRICT_IMAGE)
{
LOG(LOG_LEVEL_DEBUG,
"inbound clipboard image/bmp converted is restricted because of config");
clipboard_refuse_selection(lxev);
}
else
{
clipboard_provide_selection_c2s(lxev, lxev->target);
}
return 0;
}
if (g_cfg->restrict_inbound_clipboard & CLIP_RESTRICT_IMAGE)
{
LOG(LOG_LEVEL_DEBUG,
"inbound clipboard image/bmp is restricted because of config");
clipboard_refuse_selection(lxev);
}
else
{
g_memcpy(&g_saved_selection_req_event, lxev,
sizeof(g_saved_selection_req_event));
g_clip_c2s.type = g_image_bmp_atom;
g_clip_c2s.xrdp_clip_type = XRDP_CB_BITMAP;
clipboard_send_data_request(CB_FORMAT_DIB);
}
g_memcpy(&g_saved_selection_req_event, lxev,
sizeof(g_saved_selection_req_event));
g_clip_c2s.type = g_image_bmp_atom;
g_clip_c2s.xrdp_clip_type = XRDP_CB_BITMAP;
clipboard_send_data_request(CB_FORMAT_DIB);
return 0;
}
else if (lxev->target == g_file_atom1)
{
@ -2281,31 +2441,73 @@ clipboard_event_selection_request(XEvent *xevent)
if ((g_clip_c2s.type == lxev->target) && g_clip_c2s.converted)
{
LOG_DEVEL(LOG_LEVEL_DEBUG, "clipboard_event_selection_request: -------------------------------------------");
clipboard_provide_selection_c2s(lxev, lxev->target);
if (g_cfg->restrict_inbound_clipboard & CLIP_RESTRICT_FILE)
{
LOG(LOG_LEVEL_DEBUG,
"inbound clipboard text/uri-list is restricted because of config");
clipboard_refuse_selection(lxev);
return 0;
}
else
{
clipboard_provide_selection_c2s(lxev, lxev->target);
return 0;
}
}
if (g_cfg->restrict_inbound_clipboard & CLIP_RESTRICT_FILE)
{
LOG(LOG_LEVEL_DEBUG,
"inbound clipboard text/uri-list is restricted because of config");
clipboard_refuse_selection(lxev);
return 0;
}
g_memcpy(&g_saved_selection_req_event, lxev,
sizeof(g_saved_selection_req_event));
g_clip_c2s.type = g_file_atom1;
g_clip_c2s.xrdp_clip_type = XRDP_CB_FILE;
clipboard_send_data_request(g_file_format_id);
return 0;
else
{
g_memcpy(&g_saved_selection_req_event, lxev,
sizeof(g_saved_selection_req_event));
g_clip_c2s.type = g_file_atom1;
g_clip_c2s.xrdp_clip_type = XRDP_CB_FILE;
clipboard_send_data_request(g_file_format_id);
return 0;
}
}
else if (lxev->target == g_file_atom2)
{
LOG_DEVEL(LOG_LEVEL_DEBUG, "clipboard_event_selection_request: g_file_atom2");
if ((g_clip_c2s.type == lxev->target) && g_clip_c2s.converted)
{
LOG_DEVEL(LOG_LEVEL_DEBUG, "clipboard_event_selection_request: -------------------------------------------");
clipboard_provide_selection_c2s(lxev, lxev->target);
if (g_cfg->restrict_inbound_clipboard & CLIP_RESTRICT_FILE)
{
LOG(LOG_LEVEL_DEBUG,
"inbound clipboard x-special/gnome-copied-files converted is restricted because of config");
clipboard_refuse_selection(lxev);
return 0;
}
else
{
clipboard_provide_selection_c2s(lxev, lxev->target);
return 0;
}
}
if (g_cfg->restrict_inbound_clipboard & CLIP_RESTRICT_FILE)
{
LOG(LOG_LEVEL_DEBUG,
"inbound clipboard x-special/gnome-copied-files is restricted because of config");
clipboard_refuse_selection(lxev);
return 0;
}
else
{
g_memcpy(&g_saved_selection_req_event, lxev,
sizeof(g_saved_selection_req_event));
g_clip_c2s.type = g_file_atom2;
g_clip_c2s.xrdp_clip_type = XRDP_CB_FILE;
clipboard_send_data_request(g_file_format_id);
return 0;
}
g_memcpy(&g_saved_selection_req_event, lxev,
sizeof(g_saved_selection_req_event));
g_clip_c2s.type = g_file_atom2;
g_clip_c2s.xrdp_clip_type = XRDP_CB_FILE;
clipboard_send_data_request(g_file_format_id);
return 0;
}
else
{
@ -2527,15 +2729,7 @@ clipboard_xevent(void *xevent)
switch (lxevent->type)
{
case SelectionNotify:
if (g_cfg->restrict_outbound_clipboard == 0)
{
clipboard_event_selection_notify(lxevent);
}
else
{
LOG(LOG_LEVEL_INFO, "outbound clipboard is restricted because of config");
return 1;
}
clipboard_event_selection_notify(lxevent);
break;
case SelectionRequest:
clipboard_event_selection_request(lxevent);

View File

@ -34,6 +34,7 @@
#include "sesman.h"
#include "log.h"
#include "string_calls.h"
#include "chansrv/chansrv_common.h"
/***************************************************************************//**
*
@ -162,6 +163,26 @@ config_read_globals(int file, struct config_sesman *cf, struct list *param_n,
return 0;
}
/*
Map clipboard strings into bitmask values.
Duplicated definition exists in chansrv_config,
because it avoids build failure for xrdp-sesman and xrdp-sesrun.
It should be unified in the future.
*/
static const struct bitmask_string clip_restrict_map[] =
{
{ CLIP_RESTRICT_TEXT, "text"},
{ CLIP_RESTRICT_FILE, "file"},
{ CLIP_RESTRICT_IMAGE, "image"},
{ CLIP_RESTRICT_ALL, "all"},
{ CLIP_RESTRICT_NONE, "none"},
/* Compatibility values */
{ CLIP_RESTRICT_ALL, "true"},
{ CLIP_RESTRICT_ALL, "yes"},
{ CLIP_RESTRICT_NONE, "false"},
BITMASK_STRING_END_OF_LIST
};
/***************************************************************************//**
*
* @brief Reads sesman [Security] configuration section
@ -190,6 +211,7 @@ config_read_security(int file, struct config_security *sc,
sc->ts_users_enable = 0;
sc->ts_admins_enable = 0;
sc->restrict_outbound_clipboard = 0;
sc->restrict_inbound_clipboard = 0;
file_read_section(file, SESMAN_CFG_SECURITY, param_n, param_v);
@ -231,7 +253,31 @@ config_read_security(int file, struct config_security *sc,
if (0 == g_strcasecmp(buf, SESMAN_CFG_SEC_RESTRICT_OUTBOUND_CLIPBOARD))
{
sc->restrict_outbound_clipboard = g_text2bool((char *)list_get_item(param_v, i));
char unrecognised[256];
sc->restrict_outbound_clipboard =
g_str_to_bitmask((const char *)list_get_item(param_v, i),
clip_restrict_map, ",",
unrecognised, sizeof(unrecognised));
if (unrecognised[0] != '\0')
{
LOG(LOG_LEVEL_WARNING,
"Unrecognised tokens parsing 'RestrictOutboundClipboard' %s",
unrecognised);
}
}
if (0 == g_strcasecmp(buf, SESMAN_CFG_SEC_RESTRICT_INBOUND_CLIPBOARD))
{
char unrecognised[256];
sc->restrict_inbound_clipboard =
g_str_to_bitmask((const char *)list_get_item(param_v, i),
clip_restrict_map, ",",
unrecognised, sizeof(unrecognised));
if (unrecognised[0] != '\0')
{
LOG(LOG_LEVEL_WARNING,
"Unrecognised tokens parsing 'RestrictInboundClipboard' %s",
unrecognised);
}
}
}
@ -553,12 +599,41 @@ config_dump(struct config_sesman *config)
/* Security configuration */
g_writeln("Security configuration:");
g_writeln(" AllowRootLogin: %d", sc->allow_root);
g_writeln(" MaxLoginRetry: %d", sc->login_retry);
g_writeln(" AlwaysGroupCheck: %d", sc->ts_always_group_check);
g_writeln(" RestrictOutboundClipboard: %d", sc->restrict_outbound_clipboard);
g_writeln(" AllowRootLogin: %d", sc->allow_root);
g_writeln(" MaxLoginRetry: %d", sc->login_retry);
g_writeln(" AlwaysGroupCheck: %d", sc->ts_always_group_check);
if (sc->restrict_outbound_clipboard == CLIP_RESTRICT_NONE)
{
g_writeln(" RestrictOutboundClipboard: %s", "none");
}
else if (sc->restrict_outbound_clipboard == CLIP_RESTRICT_ALL)
{
g_writeln(" RestrictOutboundClipboard: %s", "all");
}
else
{
char buf[256];
g_bitmask_to_str(sc->restrict_outbound_clipboard,
clip_restrict_map, ',', buf, sizeof(buf));
g_writeln(" RestrictOutboundClipboard: %s", buf);
}
if (sc->restrict_inbound_clipboard == CLIP_RESTRICT_NONE)
{
g_writeln(" RestrictInboundClipboard: %s", "none");
}
else if (sc->restrict_inbound_clipboard == CLIP_RESTRICT_ALL)
{
g_writeln(" RestrictInboundClipboard: %s", "all");
}
else
{
char buf[256];
g_bitmask_to_str(sc->restrict_inbound_clipboard,
clip_restrict_map, ',', buf, sizeof(buf));
g_writeln(" RestrictInboundClipboard: %s", buf);
}
g_printf( " TSUsersGroup: ");
g_printf( " TSUsersGroup: ");
if (sc->ts_users_enable)
{
g_printf("%d", sc->ts_users);
@ -569,7 +644,7 @@ config_dump(struct config_sesman *config)
}
g_writeln("%s", "");
g_printf( " TSAdminsGroup: ");
g_printf( " TSAdminsGroup: ");
if (sc->ts_admins_enable)
{
g_printf("%d", sc->ts_admins);

View File

@ -61,6 +61,7 @@
#define SESMAN_CFG_SEC_ADM_GROUP "TerminalServerAdmins"
#define SESMAN_CFG_SEC_ALWAYSGROUPCHECK "AlwaysGroupCheck"
#define SESMAN_CFG_SEC_RESTRICT_OUTBOUND_CLIPBOARD "RestrictOutboundClipboard"
#define SESMAN_CFG_SEC_RESTRICT_INBOUND_CLIPBOARD "RestrictInboundClipboard"
#define SESMAN_CFG_SESSIONS "Sessions"
#define SESMAN_CFG_SESS_MAX "MaxSessions"
@ -134,6 +135,12 @@ struct config_security
* @brief if the clipboard should be enforced restricted. If true only allow client -> server, not vice versa.
*/
int restrict_outbound_clipboard;
/**
* @var restrict_inbound_clipboard
* @brief if the clipboard should be enforced restricted. If true only allow server -> client, not vice versa.
*/
int restrict_inbound_clipboard;
};
/**

View File

@ -19,9 +19,24 @@ TerminalServerAdmins=tsadmins
; When AlwaysGroupCheck=false access will be permitted
; if the group TerminalServerUsers is not defined.
AlwaysGroupCheck=false
; When RestrictOutboundClipboard=true clipboard from the
; When RestrictOutboundClipboard=all clipboard from the
; server is not pushed to the client.
RestrictOutboundClipboard=false
; In addition, you can control text/file/image transfer restrictions
; respectively. It also accepts comma separated list such as text,file,image.
; To keep compatibility, some aliases are also available:
; true: an alias of all
; false: an alias of none
; yes: an alias of all
RestrictOutboundClipboard=none
; When RestrictInboundClipboard=all clipboard from the
; client is not pushed to the server.
; In addition, you can control text/file/image transfer restrictions
; respectively. It also accepts comma separated list such as text,file,image.
; To keep compatibility, some aliases are also available:
; true: an alias of all
; false: an alias of none
; yes: an alias of all
RestrictInboundClipboard=none
[Sessions]
;; X11DisplayOffset - x11 display number offset

View File

@ -323,6 +323,331 @@ START_TEST(test_bm2str__overflow_some_bits_undefined)
}
END_TEST
START_TEST(test_str2bm__null_string)
{
int rv;
char buff[16] = { 'd', 'u', 'm', 'm', 'y' };
static const struct bitmask_string bits[] =
{
{1 << 0, "BIT_0"},
BITMASK_STRING_END_OF_LIST
};
rv = g_str_to_bitmask(NULL, bits, ",", buff, sizeof(buff));
ck_assert_str_eq(buff, "");
ck_assert_int_eq(rv, 0);
}
END_TEST
START_TEST(test_str2bm__empty_string)
{
int rv;
char buff[16] = { 'd', 'u', 'm', 'm', 'y' };
static const struct bitmask_string bits[] =
{
{1 << 0, "BIT_0"},
BITMASK_STRING_END_OF_LIST
};
rv = g_str_to_bitmask("", bits, ",", buff, sizeof(buff));
ck_assert_str_eq(buff, "");
ck_assert_int_eq(rv, 0);
}
END_TEST
START_TEST(test_str2bm__null_bitdefs)
{
int rv;
char buff[16] = { 'd', 'u', 'm', 'm', 'y' };
rv = g_str_to_bitmask("BIT_0", NULL, ",", buff, sizeof(buff));
ck_assert_str_eq(buff, "");
ck_assert_int_eq(rv, 0);
}
END_TEST
START_TEST(test_str2bm__null_delim)
{
int rv;
char buff[16] = { 'd', 'u', 'm', 'm', 'y' };
static const struct bitmask_string bits[] =
{
{1 << 0, "BIT_0"},
BITMASK_STRING_END_OF_LIST
};
rv = g_str_to_bitmask("BIT_0", bits, NULL, buff, sizeof(buff));
ck_assert_str_eq(buff, "");
ck_assert_int_eq(rv, 0);
}
END_TEST
START_TEST(test_str2bm__null_buffer)
{
int rv;
char buff[16] = { 'd', 'u', 'm', 'm', 'y' };
static const struct bitmask_string bits[] =
{
{1 << 0, "BIT_0"},
BITMASK_STRING_END_OF_LIST
};
rv = g_str_to_bitmask("BIT_0", bits, ",", NULL, sizeof(buff));
ck_assert_str_eq(buff, "dummy");
ck_assert_int_eq(rv, 0);
}
END_TEST
START_TEST(test_str2bm__zero_buffer)
{
int rv;
char buff[1];
static const struct bitmask_string bits[] =
{
{1 << 0, "BIT_0"},
BITMASK_STRING_END_OF_LIST
};
rv = g_str_to_bitmask("BIT_0", bits, ",", buff, 0);
ck_assert_int_eq(rv, 0);
}
END_TEST
START_TEST(test_str2bm__zero_mask)
{
int rv;
char buff[16] = { 'd', 'u', 'm', 'm', 'y' };
static const struct bitmask_string bits[] =
{
{0, "ZERO MASK"}, /* mask 0 should not be detected as end of list */
{1 << 0, "BIT_0"},
BITMASK_STRING_END_OF_LIST
};
int bitmask = 1 << 0;
rv = g_str_to_bitmask("BIT_0", bits, ",", buff, sizeof(buff));
ck_assert_str_eq(buff, "");
ck_assert_int_eq(rv, bitmask);
}
END_TEST
START_TEST(test_str2bm__all_defined)
{
int rv;
char buff[16] = { 'd', 'u', 'm', 'm', 'y' };
static const struct bitmask_string bits[] =
{
{1 << 0, "BIT_0"},
{1 << 1, "BIT_1"},
BITMASK_STRING_END_OF_LIST
};
int bitmask = 1 << 0 | 1 << 1;
rv = g_str_to_bitmask("BIT_0,BIT_1", bits, ",", buff, sizeof(buff));
ck_assert_str_eq(buff, "");
ck_assert_int_eq(rv, bitmask);
}
END_TEST
START_TEST(test_str2bm__no_defined)
{
int rv;
char buff[16] = { 'd', 'u', 'm', 'm', 'y' };
static const struct bitmask_string bits[] =
{
{1 << 0, "BIT_0"},
{1 << 1, "BIT_1"},
BITMASK_STRING_END_OF_LIST
};
int bitmask = 0;
rv = g_str_to_bitmask("BIT_2,BIT_3", bits, ",", buff, sizeof(buff));
ck_assert_str_eq(buff, "BIT_2,BIT_3");
ck_assert_int_eq(rv, bitmask);
}
END_TEST
START_TEST(test_str2bm__some_defined)
{
int rv;
char buff[16] = { 'd', 'u', 'm', 'm', 'y' };
static const struct bitmask_string bits[] =
{
{1 << 0, "BIT_0"},
{1 << 1, "BIT_1"},
{1 << 2, "BIT_2"},
BITMASK_STRING_END_OF_LIST
};
int bitmask = 1 << 1;
rv = g_str_to_bitmask("a,BIT_1,b", bits, ",", buff, sizeof(buff));
ck_assert_str_eq(buff, "a,b");
ck_assert_int_eq(rv, bitmask);
}
END_TEST
START_TEST(test_str2bm__trim_space)
{
int rv;
char buff[16] = { 'd', 'u', 'm', 'm', 'y' };
static const struct bitmask_string bits[] =
{
{1 << 0, "BIT_0"},
{1 << 1, "BIT_1"},
{1 << 2, "BIT_2"},
BITMASK_STRING_END_OF_LIST
};
int bitmask = 1 << 0 | 1 << 1 | 1 << 2;
rv = g_str_to_bitmask("BIT_0 , BIT_1 , BIT_2", bits, ",", buff, sizeof(buff));
ck_assert_str_eq(buff, "");
ck_assert_int_eq(rv, bitmask);
}
END_TEST
START_TEST(test_str2bm__overflow_undefined)
{
int rv;
char buff[16] = { 'd', 'u', 'm', 'm', 'y' };
static const struct bitmask_string bits[] =
{
{1 << 0, "BIT_0"},
{1 << 1, "BIT_1"},
{1 << 2, "BIT_2"},
BITMASK_STRING_END_OF_LIST
};
int bitmask = 1 << 1;
rv = g_str_to_bitmask("123456789,BIT_1,abcdef", bits, ",", buff, sizeof(buff));
/* abcdef is not filled */
ck_assert_str_eq(buff, "123456789");
ck_assert_int_eq(rv, bitmask);
}
END_TEST
START_TEST(test_str2bm__delim_slash)
{
int rv;
char buff[16] = { 'd', 'u', 'm', 'm', 'y' };
static const struct bitmask_string bits[] =
{
{1 << 0, "BIT_0"},
{1 << 1, "BIT_1"},
{1 << 2, "BIT_2"},
BITMASK_STRING_END_OF_LIST
};
int bitmask = 1 << 0 | 1 << 1 | 1 << 2;
rv = g_str_to_bitmask("BIT_0/BIT_1/BIT_2", bits, "/", buff, sizeof(buff));
ck_assert_str_eq(buff, "");
ck_assert_int_eq(rv, bitmask);
}
END_TEST
START_TEST(test_str2bm__no_delim)
{
int rv;
char buff[16] = { 'd', 'u', 'm', 'm', 'y' };
static const struct bitmask_string bits[] =
{
{1 << 0, "BIT_0"},
{1 << 1, "BIT_1"},
BITMASK_STRING_END_OF_LIST
};
rv = g_str_to_bitmask("BIT_0,BIT_1", bits, "", buff, sizeof(buff));
ck_assert_str_eq(buff, "BIT_0,BIT_1");
ck_assert_int_eq(rv, 0);
}
END_TEST
START_TEST(test_str2bm__multiple_delim)
{
int rv;
char buff[16] = { 'd', 'u', 'm', 'm', 'y' };
static const struct bitmask_string bits[] =
{
{1 << 0, "BIT_0"},
{1 << 1, "BIT_1"},
{1 << 2, "BIT_2"},
BITMASK_STRING_END_OF_LIST
};
int bitmask = 1 << 0 | 1 << 1 | 1 << 2;
rv = g_str_to_bitmask("BIT_0/BIT_1,BIT_2", bits, ",/", buff, sizeof(buff));
ck_assert_str_eq(buff, "");
ck_assert_int_eq(rv, bitmask);
}
END_TEST
START_TEST(test_str2bm__first_delim_is_semicolon)
{
int rv;
char buff[16] = { 'd', 'u', 'm', 'm', 'y' };
static const struct bitmask_string bits[] =
{
{1 << 0, "BIT_0"},
{1 << 1, "BIT_1"},
BITMASK_STRING_END_OF_LIST
};
int bitmask = 1 << 1;
rv = g_str_to_bitmask("a;b;BIT_1;c", bits, ";,", buff, sizeof(buff));
ck_assert_str_eq(buff, "a;b;c");
ck_assert_int_eq(rv, bitmask);
}
END_TEST
START_TEST(test_str2bm__empty_token)
{
int rv;
char buff[16] = { 'd', 'u', 'm', 'm', 'y' };
static const struct bitmask_string bits[] =
{
{1 << 0, "BIT_0"},
BITMASK_STRING_END_OF_LIST
};
int bitmask = 1 << 0;
rv = g_str_to_bitmask(",BIT_0, ,", bits, ",", buff, sizeof(buff));
ck_assert_str_eq(buff, "");
ck_assert_int_eq(rv, bitmask);
}
END_TEST
/******************************************************************************/
START_TEST(test_strtrim__trim_left)
@ -385,6 +710,7 @@ make_suite_test_string(void)
Suite *s;
TCase *tc_strnjoin;
TCase *tc_bm2str;
TCase *tc_str2bm;
TCase *tc_strtrim;
s = suite_create("String");
@ -410,6 +736,25 @@ make_suite_test_string(void)
tcase_add_test(tc_bm2str, test_bm2str__some_bits_undefined);
tcase_add_test(tc_bm2str, test_bm2str__overflow_all_bits_defined);
tcase_add_test(tc_bm2str, test_bm2str__overflow_some_bits_undefined);
tc_str2bm = tcase_create("str2bm");
suite_add_tcase(s, tc_str2bm);
tcase_add_test(tc_str2bm, test_str2bm__null_string);
tcase_add_test(tc_str2bm, test_str2bm__empty_string);
tcase_add_test(tc_str2bm, test_str2bm__null_bitdefs);
tcase_add_test(tc_str2bm, test_str2bm__null_delim);
tcase_add_test(tc_str2bm, test_str2bm__null_buffer);
tcase_add_test(tc_str2bm, test_str2bm__zero_buffer);
tcase_add_test(tc_str2bm, test_str2bm__zero_mask);
tcase_add_test(tc_str2bm, test_str2bm__all_defined);
tcase_add_test(tc_str2bm, test_str2bm__no_defined);
tcase_add_test(tc_str2bm, test_str2bm__some_defined);
tcase_add_test(tc_str2bm, test_str2bm__trim_space);
tcase_add_test(tc_str2bm, test_str2bm__overflow_undefined);
tcase_add_test(tc_str2bm, test_str2bm__no_delim);
tcase_add_test(tc_str2bm, test_str2bm__delim_slash);
tcase_add_test(tc_str2bm, test_str2bm__multiple_delim);
tcase_add_test(tc_str2bm, test_str2bm__first_delim_is_semicolon);
tcase_add_test(tc_str2bm, test_str2bm__empty_token);
tc_strtrim = tcase_create("strtrim");
suite_add_tcase(s, tc_strtrim);