diff --git a/src/man/xpra.1 b/src/man/xpra.1 index 32d948e399..bf35ca9a47 100644 --- a/src/man/xpra.1 +++ b/src/man/xpra.1 @@ -163,6 +163,8 @@ xpra \- viewer for remote, persistent X applications \fBxpra\fP \fBshadow\fP [\fI:DISPLAY\fP] | \fIssh:[USER@]HOST[:DISPLAY]\fP [\fB\-\-env\fP=\fIKEY=VALUE\fP]\fB .\|.\|.\fP [\fB\-\-exit\-with\-children\fP] +[\fB\-\-uid\fP=\fIUID\fP] +[\fB\-\-gid\fP=\fIGID\fP] [\fB\-\-daemon\fP=\fIyes\fP|\fIno\fP] [\fB\-\-pidfile\fP=\fIFILENAME\fP] [\fB\-\-readonly\fP=\fIyes\fP|\fIno\fP] @@ -672,6 +674,11 @@ i.e. 'daemonizes', and redirects its output to a log file. This prevents that behavior (useful mostly for debugging). .TP +\fB\-\-uid\fP=\fIUID\fP and \fB\-\-gid\fP=\fIGID\fP +When launching the server as root, these options can be used +to drop privileges to the given UID / GID. +.TP + \fB\-\-pidfile\fP=\fIFILENAME\fP Writes the server process ID to this file on startup. If the file has not been replaced, diff --git a/src/xpra/os_util.py b/src/xpra/os_util.py index 7300a474c8..deabab4cfe 100644 --- a/src/xpra/os_util.py +++ b/src/xpra/os_util.py @@ -110,6 +110,15 @@ def get_username_for_uid(uid): pass return "" +def get_home_for_uid(uid): + if os.name=="posix": + from pwd import getpwuid + try: + return getpwuid(uid).pw_dir + except KeyError: + pass + return "" + def get_groups(username): if os.name=="posix": import grp #@UnresolvedImport @@ -506,6 +515,49 @@ def get_ssh_port(): return 22 +def setuidgid(uid, gid): + if os.name!="posix": + return + from xpra.log import Logger + log = Logger("server") + if os.getuid()!=uid or os.getgid()!=gid: + #find the username for the given uid: + from pwd import getpwuid + try: + username = getpwuid(uid).pw_name + except KeyError: + raise Exception("uid %i not found" % uid) + #set the groups: + if hasattr(os, "initgroups"): # python >= 2.7 + os.initgroups(username, gid) + else: + import grp #@UnresolvedImport + groups = [gr.gr_gid for gr in grp.getgrall() if (username in gr.gr_mem)] + os.setgroups(groups) + #change uid and gid: + try: + if os.getgid()!=gid: + os.setgid(gid) + except OSError as e: + log.error("Error: cannot change gid to %i:", gid) + if os.getgid()==0: + #don't run as root! + raise + log.error(" %s", e) + log.error(" continuing with gid=%i", os.getgid()) + try: + if os.getuid()!=uid: + os.setuid(uid) + except OSError as e: + log.error("Error: cannot change uid to %i:", uid) + if os.getuid()==0: + #don't run as root! + raise + log.error(" %s", e) + log.error(" continuing with gid=%i", os.getuid()) + log("new uid=%s, gid=%s", os.getuid(), os.getgid()) + + def main(): from xpra.log import Logger log = Logger("util") diff --git a/src/xpra/scripts/config.py b/src/xpra/scripts/config.py index ff3756faf2..124bca2d6c 100755 --- a/src/xpra/scripts/config.py +++ b/src/xpra/scripts/config.py @@ -518,6 +518,8 @@ def may_create_user_config(xpra_conf_filename=DEFAULT_XPRA_CONF_FILENAME): "server-idle-timeout" : int, "sync-xvfb" : int, "pixel-depth" : int, + "uid" : int, + "gid" : int, #float options: "auto-refresh-delay": float, #boolean options: @@ -599,7 +601,7 @@ def may_create_user_config(xpra_conf_filename=DEFAULT_XPRA_CONF_FILENAME): #keep track of the options added since v1, #so we can generate command lines that work with older supported versions: -OPTIONS_ADDED_SINCE_V1 = ["attach", "open-files", "pixel-depth", ""] +OPTIONS_ADDED_SINCE_V1 = ["attach", "open-files", "pixel-depth", "uid", "gid"] CLIENT_OPTIONS = ["title", "username", "password", "session-name", "dock-icon", "tray-icon", "window-icon", @@ -750,6 +752,8 @@ def addtrailingslash(v): if sys.version_info<(2, 7, 9): ssl_protocol = "SSLv23" + from xpra.os_util import getuid, getgid + GLOBAL_DEFAULTS = { "encoding" : "", "title" : "@title@ on @client-machine@", @@ -839,6 +843,8 @@ def addtrailingslash(v): "server-idle-timeout" : 0, "sync-xvfb" : 0, "pixel-depth" : 24, + "uid" : getuid(), + "gid" : getgid(), "auto-refresh-delay": 0.15, "daemon" : CAN_DAEMONIZE, "attach" : None, diff --git a/src/xpra/scripts/main.py b/src/xpra/scripts/main.py index 4935fcc1a7..cb7b8cd74e 100755 --- a/src/xpra/scripts/main.py +++ b/src/xpra/scripts/main.py @@ -23,7 +23,7 @@ from xpra.platform.features import LOCAL_SERVERS_SUPPORTED, SHADOW_SUPPORTED, CAN_DAEMONIZE from xpra.util import csv, envbool, envint, DEFAULT_PORT from xpra.exit_codes import EXIT_SSL_FAILURE, EXIT_SSH_FAILURE -from xpra.os_util import getuid, getgid, monotonic_time, setsid, WIN32, OSX +from xpra.os_util import getuid, getgid, monotonic_time, setsid, get_username_for_uid, WIN32, OSX from xpra.scripts.config import OPTION_TYPES, CLIENT_OPTIONS, NON_COMMAND_LINE_OPTIONS, START_COMMAND_OPTIONS, BIND_OPTIONS, OPTIONS_ADDED_SINCE_V1, \ InitException, InitInfo, InitExit, \ fixup_debug_option, fixup_options, dict_to_validated_config, \ @@ -358,6 +358,18 @@ def ignore(defaults): "html" : ""}) legacy_bool_parse("daemon") legacy_bool_parse("attach") + if os.name=="posix" and os.getuid()==0: + group.add_option("--uid", action="store", + dest="uid", default=defaults.uid, + help="The user id to change to when the server is started by root. Default: %s." % defaults.uid) + group.add_option("--gid", action="store", + dest="gid", default=defaults.gid, + help="The group id to change to when the server is started by root. Default: %s." % defaults.gid) + else: + ignore({ + "uid" : defaults.uid, + "gid" : defaults.gid, + }) if (supports_server or supports_shadow) and CAN_DAEMONIZE: group.add_option("--daemon", action="store", metavar="yes|no", dest="daemon", default=defaults.daemon, @@ -2348,57 +2360,8 @@ def run_glcheck(opts): return 0 -def setuidgid(uid, gid): - if os.name!="posix": - return - from xpra.log import Logger - log = Logger("server") - if os.getuid()!=uid or os.getgid()!=gid: - #find the username for the given uid: - from pwd import getpwuid - try: - username = getpwuid(uid).pw_name - except KeyError: - raise Exception("uid %i not found" % uid) - #set the groups: - if hasattr(os, "initgroups"): # python >= 2.7 - os.initgroups(username, gid) - else: - import grp #@UnresolvedImport - groups = [gr.gr_gid for gr in grp.getgrall() if (username in gr.gr_mem)] - os.setgroups(groups) - #change uid and gid: - try: - if os.getgid()!=gid: - os.setgid(gid) - except OSError as e: - log.error("Error: cannot change gid to %i:", gid) - if os.getgid()==0: - #don't run as root! - raise - log.error(" %s", e) - log.error(" continuing with gid=%i", os.getgid()) - try: - if os.getuid()!=uid: - os.setuid(uid) - except OSError as e: - log.error("Error: cannot change uid to %i:", uid) - if os.getuid()==0: - #don't run as root! - raise - log.error(" %s", e) - log.error(" continuing with gid=%i", os.getuid()) - log("new uid=%s, gid=%s", os.getuid(), os.getgid()) - - def start_server_subprocess(script_file, args, mode, opts, uid=getuid(), gid=getgid()): - username = "" - home = "" - if os.name=="posix": - import pwd - e = pwd.getpwuid(uid) - username = e.pw_name - home = e.pw_dir + username = get_username_for_uid(uid) dotxpra = DotXpra(opts.socket_dir, opts.socket_dirs, username, uid=uid, gid=gid) #we must use a subprocess to avoid messing things up - yuk assert mode in ("start", "start-desktop", "shadow") @@ -2469,17 +2432,11 @@ def start_server_subprocess(script_file, args, mode, opts, uid=getuid(), gid=get else: def preexec(): setsid() - if uid!=0 or gid!=0: - setuidgid(uid, gid) cmd.append("--systemd-run=no") - server_env = os.environ.copy() - if username: - server_env.update({ - "USER" : username, - "USERNAME" : username, - "HOME" : home or os.path.join("/home", username), - }) - proc = Popen(cmd, shell=False, close_fds=True, env=server_env, preexec_fn=preexec) + if os.name=="posix" and getuid()==0 and (uid!=0 or gid!=0): + cmd.append("--uid=%i" % uid) + cmd.append("--gid=%i" % gid) + proc = Popen(cmd, shell=False, close_fds=True, preexec_fn=setsid) socket_path = identify_new_socket(proc, dotxpra, existing_sockets, matching_display, new_server_uuid, display_name, uid) return proc, socket_path diff --git a/src/xpra/scripts/server.py b/src/xpra/scripts/server.py index 4f524e798c..29e92764e8 100644 --- a/src/xpra/scripts/server.py +++ b/src/xpra/scripts/server.py @@ -18,15 +18,11 @@ from xpra.scripts.main import warn, no_gtk, validate_encryption from xpra.scripts.config import InitException, TRUE_OPTIONS, FALSE_OPTIONS -from xpra.os_util import SIGNAMES, close_fds, get_ssh_port, WIN32, OSX -from xpra.util import envint, envbool +from xpra.os_util import SIGNAMES, close_fds, get_ssh_port, get_username_for_uid, get_home_for_uid, getuid, getgid, setuidgid, WIN32, OSX +from xpra.util import envbool from xpra.platform.dotxpra import DotXpra -#what timeout value to use on the socket probe attempt: -WAIT_PROBE_TIMEOUT = envint("XPRA_WAIT_PROBE_TIMEOUT", 6) - - _cleanups = [] def run_cleanups(): global _cleanups @@ -161,48 +157,6 @@ def display_name_check(display_name): pass -#warn just once: -MDNS_WARNING = False -def mdns_publish(display_name, mode, listen_on, text_dict={}): - global MDNS_WARNING - if MDNS_WARNING is True: - return - PREFER_PYBONJOUR = envbool("XPRA_PREFER_PYBONJOUR", False) or WIN32 or OSX - try: - from xpra.net import mdns - assert mdns - if PREFER_PYBONJOUR: - from xpra.net.mdns.pybonjour_publisher import BonjourPublishers as MDNSPublishers, get_interface_index - else: - from xpra.net.mdns.avahi_publisher import AvahiPublishers as MDNSPublishers, get_interface_index - except ImportError as e: - MDNS_WARNING = True - from xpra.log import Logger - log = Logger("mdns") - log("mdns import failure", exc_info=True) - log.warn("Warning: failed to load the mdns %s publisher:", ["avahi", "pybonjour"][PREFER_PYBONJOUR]) - log.warn(" %s", e) - log.warn(" either fix your installation or use the 'mdns=no' option") - return - d = text_dict.copy() - d["mode"] = mode - #ensure we don't have duplicate interfaces: - f_listen_on = {} - for host, port in listen_on: - f_listen_on[get_interface_index(host)] = (host, port) - try: - name = socket.gethostname() - except: - name = "Xpra" - if display_name and not (OSX or WIN32): - name += " %s" % display_name - if mode!="tcp": - name += " (%s)" % mode - ap = MDNSPublishers(f_listen_on.values(), name, text_dict=d) - _when_ready.append(ap.start) - _cleanups.append(ap.stop) - - def sanitize_env(): def unsetenv(*varnames): for x in varnames: @@ -415,12 +369,23 @@ def run_server(error_cb, opts, mode, xpra_file, extra_args, desktop_display=None from xpra.server.server_util import xpra_runner_shell_script, write_runner_shell_scripts, pam_open, write_pidfile, find_log_dir script = xpra_runner_shell_script(xpra_file, cwd, opts.socket_dir) + uid = int(opts.uid) + gid = int(opts.gid) + username = get_username_for_uid(uid) + home = get_home_for_uid(uid) + def fchown(fd): + if os.name=="posix" and uid!=getuid() or gid!=getgid(): + try: + os.fchown(fd, uid, gid) + except: + pass + if start_vfb or opts.daemon: #we will probably need a log dir #either for the vfb, or for our own log file log_dir = opts.log_dir or "" if not log_dir or log_dir.lower()=="auto": - log_dir = find_log_dir() + log_dir = find_log_dir(username, uid=uid, gid=gid) if not log_dir: raise InitException("cannot find or create a logging directory") #expose the log-dir as "XPRA_LOG_DIR", @@ -439,6 +404,7 @@ def run_server(error_cb, opts, mode, xpra_file, extra_args, desktop_display=None # so log_filename0 may point to a temporary file which we will rename later log_filename0 = select_log_file(log_dir, opts.log_file, display_name) logfd = open_log_file(log_filename0) + fchown(logfd) assert logfd > 2 stdout, stderr = daemonize(logfd) try: @@ -450,12 +416,12 @@ def run_server(error_cb, opts, mode, xpra_file, extra_args, desktop_display=None pass if opts.pidfile: - write_pidfile(opts.pidfile) + write_pidfile(opts.pidfile, withfd=fchown) if os.name=="posix": # Write out a shell-script so that we can start our proxy in a clean # environment: - write_runner_shell_scripts(script) + write_runner_shell_scripts(script, withfd=fchown) from xpra.log import Logger log = Logger("server") @@ -559,7 +525,7 @@ def run_server(error_cb, opts, mode, xpra_file, extra_args, desktop_display=None assert not proxying pixel_depth = validate_pixel_depth(opts.pixel_depth) try: - xvfb, display_name, xauth_data = start_Xvfb(opts.xvfb, pixel_depth, display_name, cwd) + xvfb, display_name, xauth_data = start_Xvfb(opts.xvfb, pixel_depth, display_name, cwd, uid, gid) except OSError as e: log.error("Error starting Xvfb:") log.error(" %s", e) @@ -614,6 +580,14 @@ def noerr(fn, *args): #xvfb problem: exit now return 1 + if os.name=="posix" and getuid()==0 and uid!=0: + setuidgid(uid, gid) + os.environ.update({ + "HOME" : home, + "USER" : username, + "LOGNAME" : username, + }) + display = None if not proxying: no_gtk() @@ -635,7 +609,7 @@ def noerr(fn, *args): #we always need at least one valid socket dir from xpra.platform.paths import get_socket_dirs opts.socket_dirs = get_socket_dirs() - local_sockets = setup_local_sockets(opts.bind, opts.socket_dir, opts.socket_dirs, display_name, clobber, opts.mmap_group, opts.socket_permissions) + local_sockets = setup_local_sockets(opts.bind, opts.socket_dir, opts.socket_dirs, display_name, clobber, opts.mmap_group, opts.socket_permissions, username, uid, gid) for socket, cleanup_socket in local_sockets: #ie: ("unix-domain", sock, sockpath), cleanup_socket sockets.append(socket) @@ -727,6 +701,7 @@ def kill_dbus(): if opts.mdns: from xpra.os_util import strtobytes from xpra.platform.info import get_username + from xpra.server.socket_util import mdns_publish mdns_info = { "display" : display_name, "username" : get_username(), @@ -736,7 +711,6 @@ def kill_dbus(): } if opts.session_name: mdns_info["session"] = opts.session_name - #reduce for mode, listen_on in mdns_recs: mdns_publish(display_name, mode, listen_on, mdns_info) diff --git a/src/xpra/server/proxy/proxy_instance_process.py b/src/xpra/server/proxy/proxy_instance_process.py index a39b8f8f8a..65b70a1673 100644 --- a/src/xpra/server/proxy/proxy_instance_process.py +++ b/src/xpra/server/proxy/proxy_instance_process.py @@ -17,20 +17,19 @@ from xpra.server.server_core import get_server_info, get_thread_info from xpra.scripts.server import deadly_signal -from xpra.scripts.main import setuidgid from xpra.net import compression from xpra.net.compression import Compressed, compressed_wrapper from xpra.net.protocol import Protocol, get_network_caps from xpra.codecs.loader import load_codecs, get_codec from xpra.codecs.image_wrapper import ImageWrapper from xpra.codecs.video_helper import getVideoHelper, PREFERRED_ENCODER_ORDER -from xpra.os_util import Queue, SIGNAMES, strtobytes, memoryview_to_bytes, getuid, getgid, monotonic_time, get_username_for_uid +from xpra.os_util import Queue, SIGNAMES, strtobytes, memoryview_to_bytes, getuid, getgid, monotonic_time, get_username_for_uid, setuidgid from xpra.util import flatten_dict, typedict, updict, repr_ellipsized, xor, std, envint, envbool, csv, \ LOGIN_TIMEOUT, CONTROL_COMMAND_ERROR, AUTHENTICATION_ERROR, CLIENT_EXIT_TIMEOUT, SERVER_SHUTDOWN from xpra.version_util import XPRA_VERSION from xpra.make_thread import start_thread from xpra.scripts.config import parse_number, parse_bool -from xpra.scripts.server import create_unix_domain_socket +from xpra.server.socket_util import create_unix_domain_socket from xpra.platform.dotxpra import DotXpra from xpra.net.bytestreams import SocketConnection, SOCKET_TIMEOUT from multiprocessing import Process @@ -365,12 +364,13 @@ def get_proxy_info(self, proto): sinfo = {} sinfo.update(get_server_info()) sinfo.update(get_thread_info(proto)) - return {"proxy" : { - "version" : local_version, - "" : sinfo, - }, - "window" : self.get_window_info(), - } + return { + "proxy" : { + "version" : XPRA_VERSION, + "" : sinfo, + }, + "window" : self.get_window_info(), + } def send_hello(self, challenge_response=None, client_salt=None): hello = self.filter_client_caps(self.caps) diff --git a/src/xpra/server/server_util.py b/src/xpra/server/server_util.py index 2c3b422708..1d89ad108e 100644 --- a/src/xpra/server/server_util.py +++ b/src/xpra/server/server_util.py @@ -93,7 +93,7 @@ def xpra_runner_shell_script(xpra_file, starting_dir, socket_dir): """) return "".join(script) -def write_runner_shell_scripts(contents, overwrite=True): +def write_runner_shell_scripts(contents, overwrite=True, withfd=None): # This used to be given a display-specific name, but now we give it a # single fixed name and if multiple servers are started then the last one # will clobber the rest. This isn't great, but the tradeoff is that it @@ -128,16 +128,18 @@ def write_runner_shell_scripts(contents, overwrite=True): os.umask(umask) os.fchmod(scriptfile.fileno(), 0o700 & ~umask) scriptfile.write(contents) + if withfd: + withfd(scriptfile.fileno()) except Exception as e: log.error("Error: failed to write script file '%s':", scriptpath) log.error(" %s\n", e) -def find_log_dir(): +def find_log_dir(username="", uid=0, gid=0): from xpra.platform.paths import get_default_log_dirs errs = [] for x in get_default_log_dirs(): - v = osexpand(x) + v = osexpand(x, username, uid, gid) if not os.path.exists(v): try: os.mkdir(v, 0o700) @@ -215,7 +217,7 @@ def daemonize(logfd): return (old_stdout, old_stderr) -def write_pidfile(pidfile): +def write_pidfile(pidfile, withfd=None): from xpra.log import Logger log = Logger("server") pidstr = str(os.getpid()) @@ -227,6 +229,8 @@ def write_pidfile(pidfile): inode = os.fstat(f.fileno()).st_ino except: inode = -1 + if withfd: + withfd(f.fileno()) log.info("wrote pid %s to '%s'", pidstr, pidfile) def cleanuppidfile(): #verify this is the right file! diff --git a/src/xpra/server/socket_util.py b/src/xpra/server/socket_util.py index bafbd964f2..23d2b57b58 100644 --- a/src/xpra/server/socket_util.py +++ b/src/xpra/server/socket_util.py @@ -7,11 +7,15 @@ import socket from xpra.scripts.config import InitException -from xpra.os_util import getuid, getgid, get_username_for_uid, get_groups, get_group_id, monotonic_time, WIN32 -from xpra.util import csv, DEFAULT_PORT +from xpra.os_util import getuid, get_username_for_uid, get_groups, get_group_id, monotonic_time, WIN32, OSX +from xpra.util import envint, envbool, csv, DEFAULT_PORT from xpra.platform.dotxpra import DotXpra, norm_makepath +#what timeout value to use on the socket probe attempt: +WAIT_PROBE_TIMEOUT = envint("XPRA_WAIT_PROBE_TIMEOUT", 6) + + def add_cleanup(f): from xpra.scripts import server server.add_cleanup(f) @@ -173,12 +177,12 @@ def normalize_local_display_name(local_display_name): return local_display_name -def setup_local_sockets(bind, socket_dir, socket_dirs, display_name, clobber, mmap_group=False, socket_permissions="600"): +def setup_local_sockets(bind, socket_dir, socket_dirs, display_name, clobber, mmap_group=False, socket_permissions="600", username="", uid=0, gid=0): if not bind: return [] if not socket_dir and (not socket_dirs or (len(socket_dirs)==1 and not socket_dirs[0])): raise InitException("at least one socket directory must be set to use unix domain sockets") - dotxpra = DotXpra(socket_dir or socket_dirs[0], socket_dirs) + dotxpra = DotXpra(socket_dir or socket_dirs[0], socket_dirs, username, uid, gid) display_name = normalize_local_display_name(display_name) from xpra.log import Logger defs = [] @@ -192,7 +196,7 @@ def setup_local_sockets(bind, socket_dir, socket_dirs, display_name, clobber, mm continue elif b=="auto": sockpaths += dotxpra.norm_socket_paths(display_name) - log("sockpaths(%s)=%s (uid=%i, gid=%i)", display_name, sockpaths, getuid(), getgid()) + log("sockpaths(%s)=%s (uid=%i, gid=%i)", display_name, sockpaths, uid, gid) else: sockpath = dotxpra.osexpand(b) if b.endswith("/") or (os.path.exists(sockpath) and os.path.isdir(sockpath)): @@ -341,3 +345,46 @@ def handle_socket_error(sockpath, e): log.error("Error: failed to create socket '%s':", sockpath) log.error(" %s", e) raise InitException("failed to create socket %s" % sockpath) + + +#warn just once: +MDNS_WARNING = False +def mdns_publish(display_name, mode, listen_on, text_dict={}): + global MDNS_WARNING + if MDNS_WARNING is True: + return + PREFER_PYBONJOUR = envbool("XPRA_PREFER_PYBONJOUR", False) or WIN32 or OSX + try: + from xpra.net import mdns + assert mdns + if PREFER_PYBONJOUR: + from xpra.net.mdns.pybonjour_publisher import BonjourPublishers as MDNSPublishers, get_interface_index + else: + from xpra.net.mdns.avahi_publisher import AvahiPublishers as MDNSPublishers, get_interface_index + except ImportError as e: + MDNS_WARNING = True + from xpra.log import Logger + log = Logger("mdns") + log("mdns import failure", exc_info=True) + log.warn("Warning: failed to load the mdns %s publisher:", ["avahi", "pybonjour"][PREFER_PYBONJOUR]) + log.warn(" %s", e) + log.warn(" either fix your installation or use the 'mdns=no' option") + return + d = text_dict.copy() + d["mode"] = mode + #ensure we don't have duplicate interfaces: + f_listen_on = {} + for host, port in listen_on: + f_listen_on[get_interface_index(host)] = (host, port) + try: + name = socket.gethostname() + except: + name = "Xpra" + if display_name and not (OSX or WIN32): + name += " %s" % display_name + if mode!="tcp": + name += " (%s)" % mode + ap = MDNSPublishers(f_listen_on.values(), name, text_dict=d) + from xpra.scripts.server import add_when_ready, add_cleanup + add_when_ready(ap.start) + add_cleanup(ap.stop) diff --git a/src/xpra/server/vfb_util.py b/src/xpra/server/vfb_util.py index 7d4d3706cc..4521aa58b9 100644 --- a/src/xpra/server/vfb_util.py +++ b/src/xpra/server/vfb_util.py @@ -15,14 +15,14 @@ from xpra.scripts.main import no_gtk from xpra.scripts.config import InitException -from xpra.os_util import setsid, shellsub, monotonic_time, close_fds +from xpra.os_util import setsid, shellsub, monotonic_time, close_fds, setuidgid, getuid from xpra.platform.dotxpra import osexpand DEFAULT_VFB_RESOLUTION = tuple(int(x) for x in os.environ.get("XPRA_DEFAULT_VFB_RESOLUTION", "8192x4096").replace(",", "x").split("x", 1)) -def start_Xvfb(xvfb_str, pixel_depth, display_name, cwd): +def start_Xvfb(xvfb_str, pixel_depth, display_name, cwd, uid, gid): if os.name!="posix": raise InitException("starting an Xvfb is not supported on %s" % os.name) if not xvfb_str: @@ -85,6 +85,8 @@ def start_Xvfb(xvfb_str, pixel_depth, display_name, cwd): xvfb_cmd[0] = "%s-for-Xpra-%s" % (xvfb_executable, display_name) def preexec(): setsid() + if os.name=="posix" and getuid()==0 and uid: + setuidgid(uid, gid) close_fds([0, 1, 2, r_pipe, w_pipe]) #print("xvfb_cmd=%s" % (xvfb_cmd, )) xvfb = subprocess.Popen(xvfb_cmd, executable=xvfb_executable, close_fds=False,