diff --git a/common/Makefile.am b/common/Makefile.am index 7563b82b..d8bdc615 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -54,11 +54,12 @@ libcommon_la_SOURCES = \ log.h \ os_calls.c \ os_calls.h \ - os_calls.h \ parse.h \ rail.h \ ssl_calls.c \ ssl_calls.h \ + string_calls.c \ + string_calls.h \ thread_calls.c \ thread_calls.h \ trans.c \ diff --git a/common/log.c b/common/log.c index a045adfc..ef462f84 100644 --- a/common/log.c +++ b/common/log.c @@ -31,6 +31,7 @@ #include "file.h" #include "os_calls.h" #include "thread_calls.h" +#include "string_calls.h" /* Add a define here so that the log.h will hold more information * when compiled from this C file. diff --git a/common/os_calls.c b/common/os_calls.c index 5ed7db12..6f3bd219 100644 --- a/common/os_calls.c +++ b/common/os_calls.c @@ -3307,6 +3307,17 @@ g_setsid(void) #endif } +/*****************************************************************************/ +int +g_getlogin(char *name, unsigned int len) +{ +#if defined(_WIN32) + return -1; +#else + return getlogin_r(name, len); +#endif +} + /*****************************************************************************/ int g_setlogin(const char *name) @@ -3760,21 +3771,6 @@ g_save_to_bmp(const char* filename, char* data, int stride_bytes, return 0; } -/*****************************************************************************/ -/* returns boolean */ -int -g_text2bool(const char *s) -{ - if ( (g_atoi(s) != 0) || - (0 == g_strcasecmp(s, "true")) || - (0 == g_strcasecmp(s, "on")) || - (0 == g_strcasecmp(s, "yes"))) - { - return 1; - } - return 0; -} - /*****************************************************************************/ /* returns pointer or nil on error */ void * diff --git a/common/os_calls.h b/common/os_calls.h index 0245f0b9..d21248fa 100644 --- a/common/os_calls.h +++ b/common/os_calls.h @@ -162,6 +162,7 @@ int g_getuid(void); int g_getgid(void); int g_setuid(int pid); int g_setsid(void); +int g_getlogin(char *name, unsigned int len); int g_setlogin(const char *name); int g_waitchild(void); int g_waitpid(int pid); @@ -180,7 +181,6 @@ int g_time2(void); int g_time3(void); int g_save_to_bmp(const char* filename, char* data, int stride_bytes, int width, int height, int depth, int bits_per_pixel); -int g_text2bool(const char *s); void * g_shmat(int shmid); int g_shmdt(const void *shmaddr); int g_gethostname(char *name, int len); diff --git a/common/string_calls.c b/common/string_calls.c new file mode 100644 index 00000000..cb6468bc --- /dev/null +++ b/common/string_calls.c @@ -0,0 +1,136 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Jay Sorg 2004-2020 + * + * 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. + * + * generic string handling calls + */ + +#include +#include +#include + +#include "string_calls.h" + +unsigned int +g_format_info_string(char* dest, unsigned int len, + const char* format, + const struct info_string_tag map[]) +{ + unsigned int result = 0; + const char *copy_from; /* Data to add to output */ + unsigned int copy_len; /* Length of above */ + unsigned int skip; /* Date to skip over in format string */ + const char *p; + const struct info_string_tag *m; + + for ( ; *format != '\0'; format += skip) + { + if (*format == '%') + { + char ch= *(format + 1); + if (ch== '%') + { + /* '%%' in format - replace with single '%' */ + copy_from = format; + copy_len= 1; + skip = 2; + } + else if (ch== '\0') + { + /* Percent at end of string - ignore */ + copy_from = NULL; + copy_len= 0; + skip = 1; + } + else + { + /* Look up the character in the map, assuming failure */ + copy_from = NULL; + copy_len= 0; + skip = 2; + + for (m = map ; m->ch != '\0' ; ++m) + { + if (ch == m->ch) + { + copy_from = m->val; + copy_len = strlen(copy_from); + break; + } + } + } + } + else if ((p = strchr(format, '%')) != NULL) + { + /* Copy up to the next '%' */ + copy_from = format; + copy_len = p - format; + skip = copy_len; + } + else + { + /* Copy the rest of the format string */ + copy_from = format; + copy_len = strlen(format); + skip = copy_len; + } + + /* Update the result before any truncation */ + result += copy_len; + + /* Do we have room in the output buffer for any more data? We + * must always write a terminator if possible */ + if (len > 1) + { + if (copy_len > (len - 1)) + { + copy_len = len - 1; + } + memcpy(dest, copy_from, copy_len); + dest += copy_len; + len -= copy_len; + } + } + + /* Room for a terminator? */ + if (len > 0) + { + *dest = '\0'; + } + + return result; +} + +/******************************************************************************/ +const char * +g_bool2text(int value) +{ + return value ? "true" : "false"; +} + +/*****************************************************************************/ +int +g_text2bool(const char *s) +{ + if ( (atoi(s) != 0) || + (0 == strcasecmp(s, "true")) || + (0 == strcasecmp(s, "on")) || + (0 == strcasecmp(s, "yes"))) + { + return 1; + } + return 0; +} diff --git a/common/string_calls.h b/common/string_calls.h new file mode 100644 index 00000000..32aa62fd --- /dev/null +++ b/common/string_calls.h @@ -0,0 +1,81 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Jay Sorg 2004-2020 + * + * 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. + * + * generic string handling calls + */ + +#if !defined(STRING_CALLS_H) +#define STRING_CALLS_H + +/** + * Map a character to a string value + * + * This structure is used by g_format_info_string() to specify the + * string which chould be output for %'ch', where ch is a character + */ +struct info_string_tag +{ + char ch; + const char *val; +}; + +#define INFO_STRING_END_OF_LIST { '\0', NULL } + +/** + * Processes a format string for general info + * + * @param[out] dest Destination buffer + * @param[in] len Length of buffer, including space for a terminator + * @param[in] format Format string to process + * @param[in] map Array of struct info_string_tag. + * + * Where a '%' is encountered in the format string, the map is scanned + * and the corresponding string is copied instead of '%'. + * + * '%%' is always replaced with a single '%' in the output. % strings + * not present in the map are ignored. + * + * The map is terminated with INFO_STRING_END_OF_LIST + * + * Caller can check for buffer truncation by comparing the result with + * the buffer length (as in snprintf()) + */ +unsigned int +g_format_info_string(char* dest, unsigned int len, + const char* format, + const struct info_string_tag map[]); + + +/** + * Converts a boolean to a string for output + * + * @param[in] value Value to convert + * @return String representation + */ +const char * +g_bool2text(int value); + +/** + * Converts a string to a boolean value + * + * @param[in] s String to convert + * @return machine representation + */ +int +g_text2bool(const char *s); + +#endif diff --git a/docs/man/sesman.ini.5.in b/docs/man/sesman.ini.5.in index 5b6aac92..a60d7fef 100644 --- a/docs/man/sesman.ini.5.in +++ b/docs/man/sesman.ini.5.in @@ -246,8 +246,27 @@ Following parameters can be used in the \fB[Chansrv]\fR section. .TP \fBFuseMountName\fR=\fIstring\fR -Directory for drive redirection, relative to the user home directory. +Directory for drive redirection. Created if it doesn't exist. If not specified, defaults to \fIxrdp_client\fR. +If first character is not a '/', this is relative to $HOME. +.P +.RS +If first character is a '/' this is an absolute path. The following +substitutions are made in this string:- + %U - Username + %u - Numeric UID + %% - Percent character +.P +If this format is used:- +.HP 3 +1) The directory path permissions MUST be configured correctly by +the system administrator or the system itself - xrdp-chansrv will not +do this for you (although it will create the final directories owned by +the user). +.HP 3 +2) The desktop may not automatically display a link for the redirected +drive. To fix this, consult the docs for your chosen desktop. +.RE .TP \fBFileUmask\fR=\fImode\fR @@ -257,6 +276,21 @@ files on your redirected drives. This may not be approprate for all environents, and so you can change this value to allow other users to access your remote files if required. +.TP +\fBEnableFuseMount\fR=\fI[true|false]\fR +Defaults to \fItrue\fR. +Set to \fIfalse\fR to disable xrdp-chansrv's use of the FUSE system +feature, even if it has been built with this feature enabled. +.P +.RS +Setting this value to \fIfalse\fR will disable the following application +features:- +.P +- drive redirection +.P +- copying-and-pasting of files +.RE + .SH "SESSIONS VARIABLES" All entries in the \fB[SessionVariables]\fR section are set as environment variables in the user's session. diff --git a/libxrdp/xrdp_rdp.c b/libxrdp/xrdp_rdp.c index f5616991..da36b3a6 100644 --- a/libxrdp/xrdp_rdp.c +++ b/libxrdp/xrdp_rdp.c @@ -26,6 +26,7 @@ #include "ms-rdpbcgr.h" #include "log.h" #include "ssl_calls.h" +#include "string_calls.h" #if defined(XRDP_NEUTRINORDP) #include diff --git a/neutrinordp/xrdp-neutrinordp.c b/neutrinordp/xrdp-neutrinordp.c index 651b5096..50ae1bf8 100644 --- a/neutrinordp/xrdp-neutrinordp.c +++ b/neutrinordp/xrdp-neutrinordp.c @@ -26,6 +26,7 @@ #include "xrdp_rail.h" #include "trans.h" #include "log.h" +#include "string_calls.h" #include #if defined(VERSION_STRUCT_RDP_FREERDP) diff --git a/sesman/chansrv/chansrv_config.c b/sesman/chansrv/chansrv_config.c index c265ff02..ad8789ec 100644 --- a/sesman/chansrv/chansrv_config.c +++ b/sesman/chansrv/chansrv_config.c @@ -31,10 +31,12 @@ #include "os_calls.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_ENABLE_FUSE_MOUNT 1 #define DEFAULT_FUSE_MOUNT_NAME "xrdp-client" #define DEFAULT_FILE_UMASK 077 @@ -155,6 +157,10 @@ read_config_chansrv(log_func_t logmsg, const char *name = (const char *)list_get_item(names, index); const char *value = (const char *)list_get_item(values, index); + if (g_strcasecmp(name, "EnableFuseMount") == 0) + { + cfg->enable_fuse_mount = g_text2bool(value); + } if (g_strcasecmp(name, "FuseMountName") == 0) { g_free(cfg->fuse_mount_name); @@ -196,6 +202,7 @@ new_config(void) else { 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->fuse_mount_name = fuse_mount_name; cfg->file_umask = DEFAULT_FILE_UMASK; @@ -271,13 +278,16 @@ void config_dump(struct config_chansrv *config) { g_writeln("Global configuration:"); - g_writeln(" UseUnixSocket (derived): %d", config->use_unix_socket); + g_writeln(" UseUnixSocket (derived): %s", + g_bool2text(config->use_unix_socket)); g_writeln("\nSecurity configuration:"); - g_writeln(" RestrictOutboundClipboard: %d", - config->restrict_outbound_clipboard); + g_writeln(" RestrictOutboundClipboard: %s", + g_bool2text(config->restrict_outbound_clipboard)); g_writeln("\nChansrv configuration:"); + g_writeln(" EnableFuseMount %s", + g_bool2text(config->enable_fuse_mount)); g_writeln(" FuseMountName: %s", config->fuse_mount_name); g_writeln(" FileMask: 0%o", config->file_umask); } diff --git a/sesman/chansrv/chansrv_config.h b/sesman/chansrv/chansrv_config.h index 72d301cd..3d0ff379 100644 --- a/sesman/chansrv/chansrv_config.h +++ b/sesman/chansrv/chansrv_config.h @@ -26,6 +26,9 @@ struct config_chansrv /** Whether to use a UNIX socket for chansrv */ int use_unix_socket; + /** Whether the FUSE mount is enabled or not */ + int enable_fuse_mount; + /** RestrictOutboundClipboard setting from sesman.ini */ int restrict_outbound_clipboard; diff --git a/sesman/chansrv/chansrv_fuse.c b/sesman/chansrv/chansrv_fuse.c index b4411291..d5a53a9a 100644 --- a/sesman/chansrv/chansrv_fuse.c +++ b/sesman/chansrv/chansrv_fuse.c @@ -144,6 +144,7 @@ void xfuse_devredir_cb_file_close(struct state_close *fip) #include "arch.h" #include "os_calls.h" +#include "string_calls.h" #include "clipboard_file.h" #include "chansrv_fuse.h" #include "chansrv_xfs.h" @@ -392,6 +393,8 @@ static const char *filename_on_device(const char *full_path); static void update_inode_file_attributes(const struct file_attr *fattr, tui32 change_mask, XFS_INODE *xinode); static char *get_name_for_entry_in_parent(fuse_ino_t parent, const char *name); +static unsigned int format_user_info(char *dest, unsigned int len, + const char *format); /*****************************************************************************/ int @@ -446,7 +449,7 @@ xfuse_handle_from_fuse_handle(uint64_t handle) /** * Initialize FUSE subsystem * - * @return 0 on success, -1 on failure + * @return 0 on success, -1 on failure, 1 for feature disabled *****************************************************************************/ int @@ -458,6 +461,21 @@ xfuse_init(void) if (g_xfuse_inited) { LOG_DEVEL(LOG_LEVEL_DEBUG, "already inited"); + return 0; + } + + /* This feature may be disabled */ + if (!g_cfg->enable_fuse_mount) + { + /* + * Only log the 'disabled mounts' message one time + */ + static int disabled_mounts_msg_shown = 0; + if (!disabled_mounts_msg_shown) + { + LOG(LOG_LEVEL_INFO, "FUSE mounts are disabled by config"); + disabled_mounts_msg_shown = 1; + } return 1; } @@ -469,13 +487,27 @@ xfuse_init(void) load_fuse_config(); - /* define FUSE mount point to ~/xrdp_client, ~/thinclient_drives */ - g_snprintf(g_fuse_root_path, 255, "%s/%s", g_getenv("HOME"), g_cfg->fuse_mount_name); + /* define FUSE mount point */ + if (g_cfg->fuse_mount_name[0] == '/') + { + /* String is an absolute path to the mount point containing + * %u or %U characters */ + format_user_info(g_fuse_root_path, sizeof(g_fuse_root_path), + g_cfg->fuse_mount_name); + } + else + { + /* mount_name is relative to $HOME, e.g. ~/xrdp_client, + * or ~/thinclient_drives */ + g_snprintf(g_fuse_root_path, sizeof(g_fuse_root_path), "%s/%s", + g_getenv("HOME"), g_cfg->fuse_mount_name); + } g_snprintf(g_fuse_clipboard_path, 255, "%s/.clipboard", g_fuse_root_path); /* if FUSE mount point does not exist, create it */ if (!g_directory_exist(g_fuse_root_path)) { + (void)g_create_path(g_fuse_root_path); if (!g_create_dir(g_fuse_root_path)) { LOG_DEVEL(LOG_LEVEL_ERROR, "mkdir %s failed. If %s is already mounted, you must " @@ -748,23 +780,38 @@ xfuse_file_contents_range(int stream_id, const char *data, int data_bytes) int xfuse_add_clip_dir_item(const char *filename, int flags, int size, int lindex) { - LOG_DEVEL(LOG_LEVEL_DEBUG, "entered: filename=%s flags=%d size=%d lindex=%d", + LOG_DEVEL(LOG_LEVEL_DEBUG, + "entered: filename=%s flags=%d size=%d lindex=%d", filename, flags, size, lindex); - /* add entry to xrdp_fs */ - XFS_INODE *xinode = xfs_add_entry( g_xfs, - g_clipboard_inum, /* parent inode */ - filename, - (0666 | S_IFREG)); - if (xinode == NULL) - { - LOG_DEVEL(LOG_LEVEL_DEBUG, "failed to create file in xrdp filesystem"); - return -1; - } - xinode->size = size; - xinode->lindex = lindex; + int result = -1; - return 0; + if (g_xfs == NULL) + { + LOG_DEVEL(LOG_LEVEL_ERROR, + "xfuse_add_clip_dir_item() called with no filesystem") + } + else + { + /* add entry to xrdp_fs */ + XFS_INODE *xinode = xfs_add_entry( g_xfs, + g_clipboard_inum, /* parent inode */ + filename, + (0666 | S_IFREG)); + if (xinode == NULL) + { + LOG(LOG_LEVEL_INFO, + "failed to create file %s in xrdp filesystem", filename); + } + else + { + xinode->size = size; + xinode->lindex = lindex; + result = 0; + } + } + + return result; } /** @@ -2609,4 +2656,30 @@ static char *get_name_for_entry_in_parent(fuse_ino_t parent, const char *name) return result; } +/* + * Scans a user-provided string substituting %u/%U for UID/username + */ +static unsigned int format_user_info(char *dest, unsigned int len, + const char *format) +{ + char uidstr[64]; + char username[64]; + const struct info_string_tag map[] = + { + {'u', uidstr}, + {'U', username}, + INFO_STRING_END_OF_LIST + }; + + int uid = g_getuid(); + g_snprintf(uidstr, sizeof(uidstr), "%d", uid); + if (g_getlogin(username, sizeof(username)) != 0) + { + /* Fall back to UID */ + g_strncpy(username, uidstr, sizeof(username) - 1); + } + + return g_format_info_string(dest, len, format, map); +} + #endif /* end else #ifndef XRDP_FUSE */ diff --git a/sesman/config.c b/sesman/config.c index 142f3ced..481ae760 100644 --- a/sesman/config.c +++ b/sesman/config.c @@ -33,6 +33,7 @@ #include "file.h" #include "sesman.h" #include "log.h" +#include "string_calls.h" /***************************************************************************//** * diff --git a/sesman/sesman.ini.in b/sesman/sesman.ini.in index da4aaa0c..b709650d 100644 --- a/sesman/sesman.ini.in +++ b/sesman/sesman.ini.in @@ -114,11 +114,16 @@ param=-dpi param=96 [Chansrv] -; drive redirection, defaults to xrdp_client if not set +; drive redirection +; See sesman.ini(5) for the format of this parameter +#FuseMountName=/run/user/%u/thinclient_drives +#FuseMountName=/media/thinclient_drives/%U/thinclient_drives FuseMountName=thinclient_drives ; this value allows only the user to acess their own mapped drives. ; Make this more permissive (e.g. 022) if required. FileUmask=077 +; Can be used to disable FUSE functionality - see sesman.ini(5) +#EnableFuseMount=false [ChansrvLogging] ; Note: one log file is created per display and the LogFile config value diff --git a/xrdp/xrdp_listen.c b/xrdp/xrdp_listen.c index a8bb205a..dc327cd8 100644 --- a/xrdp/xrdp_listen.c +++ b/xrdp/xrdp_listen.c @@ -24,6 +24,7 @@ #include "xrdp.h" #include "log.h" +#include "string_calls.h" /* 'g_process' is protected by the semaphore 'g_process_sem'. One thread sets g_process and waits for the other to process it */ diff --git a/xrdp/xrdp_login_wnd.c b/xrdp/xrdp_login_wnd.c index 8fb72e62..530c0ca2 100644 --- a/xrdp/xrdp_login_wnd.c +++ b/xrdp/xrdp_login_wnd.c @@ -25,6 +25,7 @@ #include "base64.h" #include "xrdp.h" #include "log.h" +#include "string_calls.h" #define ASK "ask" #define ASK_LEN g_strlen(ASK) diff --git a/xrdp/xrdp_mm.c b/xrdp/xrdp_mm.c index 0e225b33..7d4bb86d 100644 --- a/xrdp/xrdp_mm.c +++ b/xrdp/xrdp_mm.c @@ -23,6 +23,7 @@ #endif #include "xrdp.h" #include "log.h" +#include "string_calls.h" #ifndef USE_NOPAM #if defined(HAVE__PAM_TYPES_H) diff --git a/xrdp/xrdp_wm.c b/xrdp/xrdp_wm.c index 0350c856..558be618 100644 --- a/xrdp/xrdp_wm.c +++ b/xrdp/xrdp_wm.c @@ -27,6 +27,7 @@ #include "xrdp.h" #include "ms-rdpbcgr.h" #include "log.h" +#include "string_calls.h" #define LLOG_LEVEL 1 #define LLOGLN(_level, _args) \