def verify_display(xvfb=None, display_name=None, shadowing=False, log_errors=True): #check that we can access the X11 display: from xpra.x11.vfb_util import verify_display_ready if not verify_display_ready(xvfb, display_name, shadowing, log_errors): return 1 from xpra.log import Logger log = Logger("screen", "x11") log("X11 display is ready") no_gtk() from xpra.x11.gtk_x11.gdk_display_source import verify_gdk_display display = verify_gdk_display(display_name) if not display: return 1 log("GDK can access the display") return 0
def do_run_server(error_cb, opts, mode, xpra_file, extra_args, desktop_display=None): assert mode in ( "start", "start-desktop", "upgrade", "upgrade-desktop", "shadow", "proxy", ) try: cwd = os.getcwd() except OSError: cwd = os.path.expanduser("~") warn("current working directory does not exist, using '%s'\n" % cwd) validate_encryption(opts) if opts.encoding=="help" or "help" in opts.encodings: return show_encoding_help(opts) #remove anything pointing to dbus from the current env #(so we only detect a dbus instance started by pam, # and override everything else) for k in tuple(os.environ.keys()): if k.startswith("DBUS_"): del os.environ[k] starting = mode == "start" starting_desktop = mode == "start-desktop" upgrading = mode == "upgrade" upgrading_desktop = mode == "upgrade-desktop" shadowing = mode == "shadow" proxying = mode == "proxy" clobber = upgrading or upgrading_desktop or opts.use_display start_vfb = not (shadowing or proxying or clobber) if not proxying and PYTHON3 and POSIX and not OSX: #we don't support wayland servers, #so make sure GDK will use the X11 backend: os.environ["GDK_BACKEND"] = "x11" if proxying or upgrading or upgrading_desktop: #when proxying or upgrading, don't exec any plain start commands: opts.start = opts.start_child = [] elif opts.exit_with_children: assert opts.start_child, "exit-with-children was specified but start-child is missing!" elif opts.start_child: warn("Warning: the 'start-child' option is used,") warn(" but 'exit-with-children' is not enabled,") warn(" use 'start' instead") if opts.bind_rfb and (proxying or starting): get_util_logger().warn("Warning: bind-rfb sockets cannot be used with '%s' mode" % mode) opts.bind_rfb = "" if not shadowing and not starting_desktop: opts.rfb_upgrade = 0 if upgrading or upgrading_desktop or shadowing: #there should already be one running opts.pulseaudio = False #get the display name: if shadowing and not extra_args: if WIN32 or OSX: #just a virtual name for the only display available: display_name = ":0" else: from xpra.scripts.main import guess_X11_display dotxpra = DotXpra(opts.socket_dir, opts.socket_dirs) display_name = guess_X11_display(dotxpra, desktop_display) elif (upgrading or upgrading_desktop) and not extra_args: display_name = guess_xpra_display(opts.socket_dir, opts.socket_dirs) else: if len(extra_args) > 1: error_cb("too many extra arguments (%i): only expected a display number" % len(extra_args)) if len(extra_args) == 1: display_name = extra_args[0] if not shadowing and not proxying and not opts.use_display: display_name_check(display_name) else: if proxying: #find a free display number: dotxpra = DotXpra(opts.socket_dir, opts.socket_dirs) all_displays = dotxpra.sockets() #ie: [("LIVE", ":100"), ("LIVE", ":200"), ...] displays = [v[1] for v in all_displays] display_name = None for x in range(1000, 20000): v = ":%s" % x if v not in displays: display_name = v break if not display_name: error_cb("you must specify a free virtual display name to use with the proxy server") elif opts.use_display: #only use automatic guess for xpra displays and not X11 displays: display_name = guess_xpra_display(opts.socket_dir, opts.socket_dirs) else: # We will try to find one automaticaly # Use the temporary magic value 'S' as marker: display_name = 'S' + str(os.getpid()) if not (shadowing or proxying or upgrading or upgrading_desktop) and \ opts.exit_with_children and not opts.start_child: error_cb("--exit-with-children specified without any children to spawn; exiting immediately") atexit.register(run_cleanups) # Generate the script text now, because os.getcwd() will # change if/when we daemonize: from xpra.server.server_util import ( xpra_runner_shell_script, write_runner_shell_scripts, find_log_dir, create_input_devices, ) 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) xauth_data = None if start_vfb: xauth_data = get_hex_uuid() ROOT = POSIX and getuid()==0 protected_fds = [] protected_env = {} stdout = sys.stdout stderr = sys.stderr # Daemonize: if POSIX and opts.daemon: #daemonize will chdir to "/", so try to use an absolute path: if opts.password_file: opts.password_file = tuple(os.path.abspath(x) for x in opts.password_file) from xpra.server.server_util import daemonize daemonize() displayfd = 0 if POSIX and opts.displayfd: try: displayfd = int(opts.displayfd) if displayfd>0: protected_fds.append(displayfd) except ValueError as e: stderr.write("Error: invalid displayfd '%s':\n" % opts.displayfd) stderr.write(" %s\n" % e) del e # if pam is present, try to create a new session: pam = None PAM_OPEN = POSIX and envbool("XPRA_PAM_OPEN", ROOT and uid!=0) if PAM_OPEN: try: from xpra.server.pam import pam_session #@UnresolvedImport except ImportError as e: stderr.write("Error: failed to import pam module\n") stderr.write(" %s" % e) del e PAM_OPEN = False if PAM_OPEN: fdc = FDChangeCaptureContext() with fdc: pam = pam_session(username) env = { #"XDG_SEAT" : "seat1", #"XDG_VTNR" : "0", "XDG_SESSION_TYPE" : "x11", #"XDG_SESSION_CLASS" : "user", "XDG_SESSION_DESKTOP" : "xpra", } #maybe we should just bail out instead? if pam.start(): pam.set_env(env) items = {} if display_name.startswith(":"): items["XDISPLAY"] = display_name if xauth_data: items["XAUTHDATA"] = xauth_data pam.set_items(items) if pam.open(): #we can't close it, because we're not going to be root any more, #but since we're the process leader for the session, #terminating will also close the session #add_cleanup(pam.close) protected_env = pam.get_envlist() os.environ.update(protected_env) #closing the pam fd causes the session to be closed, #and we don't want that! protected_fds += fdc.get_new_fds() #get XDG_RUNTIME_DIR from env options, #which may not be have updated os.environ yet when running as root with "--uid=" xrd = os.path.abspath(parse_env(opts.env).get("XDG_RUNTIME_DIR", "")) if ROOT and (uid>0 or gid>0): #we're going to chown the directory if we create it, #ensure this cannot be abused, only use "safe" paths: if not any(x for x in ("/run/user/%i" % uid, "/tmp", "/var/tmp") if xrd.startswith(x)): xrd = "" #these paths could cause problems if we were to create and chown them: if xrd.startswith("/tmp/.X11-unix") or xrd.startswith("/tmp/.XIM-unix"): xrd = "" if not xrd: xrd = os.environ.get("XDG_RUNTIME_DIR") xrd = create_runtime_dir(xrd, uid, gid) if xrd: #this may override the value we get from pam #with the value supplied by the user: protected_env["XDG_RUNTIME_DIR"] = xrd if POSIX and not ROOT: # Write out a shell-script so that we can start our proxy in a clean # environment: write_runner_shell_scripts(script) 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(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", #this is used by Xdummy for the Xorg log file if "XPRA_LOG_DIR" not in os.environ: os.environ["XPRA_LOG_DIR"] = log_dir if opts.daemon: from xpra.server.server_util import select_log_file, open_log_file, redirect_std_to_log log_filename0 = osexpand(select_log_file(log_dir, opts.log_file, display_name), username, uid, gid) logfd = open_log_file(log_filename0) if ROOT and (uid>0 or gid>0): try: os.fchown(logfd, uid, gid) except (OSError, IOError): pass stdout, stderr = redirect_std_to_log(logfd, *protected_fds) try: stderr.write("Entering daemon mode; " + "any further errors will be reported to:\n" + (" %s\n" % log_filename0)) except IOError: #we tried our best, logging another error won't help pass #warn early about this: if (starting or starting_desktop) and desktop_display and opts.notifications and not opts.dbus_launch: print_DE_warnings() from xpra.net.socket_util import get_network_logger, setup_local_sockets, create_sockets sockets = create_sockets(opts, error_cb) sanitize_env() if POSIX: if xrd: os.environ["XDG_RUNTIME_DIR"] = xrd if not OSX: os.environ["XDG_SESSION_TYPE"] = "x11" if not starting_desktop: os.environ["XDG_CURRENT_DESKTOP"] = opts.wm_name configure_imsettings_env(opts.input_method) if display_name[0] != 'S': os.environ["DISPLAY"] = display_name if POSIX: os.environ["CKCON_X11_DISPLAY"] = display_name else: try: del os.environ["DISPLAY"] except KeyError: pass os.environ.update(protected_env) from xpra.log import Logger log = Logger("server") log("env=%s", os.environ) UINPUT_UUID_LEN = 12 UINPUT_UUID_MIN_LEN = 12 UINPUT_UUID_MAX_LEN = 32 # Start the Xvfb server first to get the display_name if needed odisplay_name = display_name xvfb = None xvfb_pid = None uinput_uuid = None if start_vfb: assert not proxying and xauth_data pixel_depth = validate_pixel_depth(opts.pixel_depth, starting_desktop) from xpra.x11.vfb_util import start_Xvfb, check_xvfb_process from xpra.server.server_util import has_uinput uinput_uuid = None if has_uinput() and opts.input_devices.lower() in ("uinput", "auto") and not shadowing: from xpra.os_util import get_rand_chars uinput_uuid = get_rand_chars(UINPUT_UUID_LEN) xvfb, display_name, cleanups = start_Xvfb(opts.xvfb, pixel_depth, display_name, cwd, uid, gid, username, xauth_data, uinput_uuid) for f in cleanups: add_cleanup(f) xvfb_pid = xvfb.pid #always update as we may now have the "real" display name: os.environ["DISPLAY"] = display_name os.environ["CKCON_X11_DISPLAY"] = display_name os.environ.update(protected_env) if display_name!=odisplay_name and pam: pam.set_items({"XDISPLAY" : display_name}) def check_xvfb(): return check_xvfb_process(xvfb) else: if POSIX and clobber: #if we're meant to be using a private XAUTHORITY file, #make sure to point to it: from xpra.x11.vfb_util import get_xauthority_path xauthority = get_xauthority_path(display_name, username, uid, gid) if os.path.exists(xauthority): log("found XAUTHORITY=%s", xauthority) os.environ["XAUTHORITY"] = xauthority def check_xvfb(): return True if POSIX and not OSX and displayfd>0: from xpra.platform.displayfd import write_displayfd try: display_no = display_name[1:] #ensure it is a string containing the number: display_no = str(int(display_no)) log("writing display_no='%s' to displayfd=%i", display_no, displayfd) assert write_displayfd(displayfd, display_no), "timeout" except Exception as e: log.error("write_displayfd failed", exc_info=True) log.error("Error: failed to write '%s' to fd=%s", display_name, displayfd) log.error(" %s", str(e) or type(e)) del e if opts.daemon: def noerr(fn, *args): try: fn(*args) except Exception: pass log_filename1 = osexpand(select_log_file(log_dir, opts.log_file, display_name), username, uid, gid) if log_filename0 != log_filename1: # we now have the correct log filename, so use it: os.rename(log_filename0, log_filename1) if odisplay_name!=display_name: #this may be used by scripts, let's try not to change it: noerr(stderr.write, "Actual display used: %s\n" % display_name) noerr(stderr.write, "Actual log file name is now: %s\n" % log_filename1) noerr(stderr.flush) noerr(stdout.close) noerr(stderr.close) #we should not be using stdout or stderr from this point: del stdout del stderr if not check_xvfb(): #xvfb problem: exit now return 1 #create devices for vfb if needed: devices = {} if not start_vfb and not proxying and not shadowing and envbool("XPRA_UINPUT", True): #try to find the existing uinput uuid: #use a subprocess to avoid polluting our current process #with X11 connections before we get a chance to change uid prop = "_XPRA_UINPUT_ID" cmd = ["xprop", "-display", display_name, "-root", prop] log("looking for '%s' on display '%s' with XAUTHORITY='%s'", prop, display_name, os.environ.get("XAUTHORITY")) try: code, out, err = get_status_output(cmd) except Exception as e: log("failed to get existing uinput id: %s", e) del e else: log("Popen(%s)=%s", cmd, (code, out, err)) if code==0 and out.find("=")>0: uinput_uuid = out.split("=", 1)[1] log("raw uinput uuid=%s", uinput_uuid) uinput_uuid = strtobytes(uinput_uuid.strip('\n\r"\\ ')) if uinput_uuid: if len(uinput_uuid)>UINPUT_UUID_MAX_LEN or len(uinput_uuid)<UINPUT_UUID_MIN_LEN: log.warn("Warning: ignoring invalid uinput id:") log.warn(" '%s'", uinput_uuid) uinput_uuid = None else: log.info("retrieved existing uinput id: %s", bytestostr(uinput_uuid)) if uinput_uuid: devices = create_input_devices(uinput_uuid, uid) if ROOT and (uid!=0 or gid!=0): log("root: switching to uid=%i, gid=%i", uid, gid) setuidgid(uid, gid) os.environ.update({ "HOME" : home, "USER" : username, "LOGNAME" : username, }) shell = get_shell_for_uid(uid) if shell: os.environ["SHELL"] = shell #now we've changed uid, it is safe to honour all the env updates: configure_env(opts.env) os.environ.update(protected_env) if opts.chdir: log("chdir(%s)", opts.chdir) os.chdir(opts.chdir) dbus_pid, dbus_env = 0, {} if not shadowing and POSIX and not OSX and not clobber: no_gtk() assert starting or starting_desktop or proxying from xpra.server.dbus.dbus_start import start_dbus dbus_pid, dbus_env = start_dbus(opts.dbus_launch) if dbus_env: os.environ.update(dbus_env) display = None if not proxying: if POSIX and not OSX: no_gtk() if starting or starting_desktop or shadowing: #check that we can access the X11 display: from xpra.x11.vfb_util import verify_display_ready if not verify_display_ready(xvfb, display_name, shadowing): return 1 log("X11 display is ready") no_gtk() from xpra.x11.gtk_x11.gdk_display_source import verify_gdk_display display = verify_gdk_display(display_name) if not display: return 1 log("GDK can access the display") #on win32, this ensures that we get the correct screen size to shadow: from xpra.platform.gui import init as gui_init log("gui_init()") gui_init() #setup unix domain socket: netlog = get_network_logger() 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) netlog("setting up local sockets: %s", local_sockets) sockets += local_sockets if POSIX and (starting or upgrading or starting_desktop or upgrading_desktop): #all unix domain sockets: ud_paths = [sockpath for stype, _, sockpath, _ in local_sockets if stype=="unix-domain"] if ud_paths: #choose one so our xdg-open override script can use to talk back to us: if opts.forward_xdg_open: for x in ("/usr/libexec/xpra", "/usr/lib/xpra"): xdg_override = os.path.join(x, "xdg-open") if os.path.exists(xdg_override): os.environ["PATH"] = x+os.pathsep+os.environ.get("PATH", "") os.environ["XPRA_SERVER_SOCKET"] = ud_paths[0] break else: log.warn("Warning: no local server sockets,") if opts.forward_xdg_open: log.warn(" forward-xdg-open cannot be enabled") log.warn(" non-embedded ssh connections will not be available") set_server_features(opts) if not proxying and POSIX and not OSX: if not check_xvfb(): return 1 from xpra.x11.gtk_x11.gdk_display_source import init_gdk_display_source if os.environ.get("NO_AT_BRIDGE") is None: os.environ["NO_AT_BRIDGE"] = "1" init_gdk_display_source() #(now we can access the X11 server) if uinput_uuid: save_uinput_id(uinput_uuid) if shadowing: app = make_shadow_server() elif proxying: app = make_proxy_server() else: if starting or upgrading: app = make_server(clobber) else: assert starting_desktop or upgrading_desktop app = make_desktop_server(clobber) app.init_virtual_devices(devices) try: app.exec_cwd = opts.chdir or cwd app.display_name = display_name app.init(opts) app.init_sockets(sockets) app.init_dbus(dbus_pid, dbus_env) if not shadowing and (xvfb_pid or clobber): app.init_display_pid(xvfb_pid) app.original_desktop_display = desktop_display del opts if not app.server_ready(): return 1 app.server_init() app.setup() app.init_when_ready(_when_ready) except InitException as e: log.error("xpra server initialization error:") log.error(" %s", e) app.cleanup() return 1 except Exception as e: log.error("Error: cannot start the %s server", app.session_type, exc_info=True) log.error(str(e)) log.info("") app.cleanup() return 1 try: log("running %s", app.run) r = app.run() log("%s()=%s", app.run, r) except KeyboardInterrupt: log.info("stopping on KeyboardInterrupt") app.cleanup() return 0 except Exception: log.error("server error", exc_info=True) app.cleanup() return -128 else: if r>0: r = 0 return r