def err(msg): raise InitException(msg)
def init(self, opts): self.pixel_depth = int(opts.pixel_depth) or 32 if self.pixel_depth not in (24, 30, 32): raise InitException("unsupported pixel depth: %s" % self.pixel_depth) GTKShadowServerBase.init(self, opts)
def init(self, opts): self.pixel_depth = int(opts.pixel_depth) or 32 if self.pixel_depth not in (24, 30, 32): raise InitException("unsupported pixel depth: %s" % self.pixel_depth) super().init(opts)
def run_server(error_cb, opts, mode, xpra_file, extra_args, desktop_display=None): try: cwd = os.getcwd() except: 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) from xpra.server.socket_util import parse_bind_ip, parse_bind_vsock, get_network_logger bind_tcp = parse_bind_ip(opts.bind_tcp) bind_udp = parse_bind_ip(opts.bind_udp) bind_ssl = parse_bind_ip(opts.bind_ssl) bind_ws = parse_bind_ip(opts.bind_ws) bind_wss = parse_bind_ip(opts.bind_wss) bind_rfb = parse_bind_ip(opts.bind_rfb, 5900) bind_vsock = parse_bind_vsock(opts.bind_vsock) assert mode in ("start", "start-desktop", "upgrade", "shadow", "proxy") starting = mode == "start" starting_desktop = mode == "start-desktop" upgrading = mode == "upgrade" shadowing = mode == "shadow" proxying = mode == "proxy" clobber = upgrading or opts.use_display start_vfb = not shadowing and not proxying and not clobber if upgrading or shadowing: #there should already be one running opts.pulseaudio = False #get the display name: if shadowing and len(extra_args) == 0: 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) elif upgrading and len(extra_args) == 0: 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 and not proxying and not upgrading 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, write_pidfile, 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 opts.pidfile: write_pidfile(opts.pidfile, uid, gid) 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: 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: #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() log = get_util_logger() netlog = get_network_logger() mdns_recs = {} sockets = [] #SSL sockets: wrap_socket_fn = None need_ssl = False ssl_opt = opts.ssl.lower() if ssl_opt in TRUE_OPTIONS or bind_ssl or bind_wss: need_ssl = True if opts.bind_tcp or opts.bind_ws: if ssl_opt == "auto" and opts.ssl_cert: need_ssl = True elif ssl_opt == "tcp" and opts.bind_tcp: need_ssl = True elif ssl_opt == "www": need_ssl = True if need_ssl: from xpra.scripts.main import ssl_wrap_socket_fn try: wrap_socket_fn = ssl_wrap_socket_fn(opts, server_side=True) netlog("wrap_socket_fn=%s", wrap_socket_fn) except Exception as e: netlog("SSL error", exc_info=True) cpaths = csv("'%s'" % x for x in (opts.ssl_cert, opts.ssl_key) if x) raise InitException( "cannot create SSL socket, check your certificate paths (%s): %s" % (cpaths, e)) from xpra.server.socket_util import setup_tcp_socket, setup_udp_socket, setup_vsock_socket, setup_local_sockets, has_dual_stack min_port = int(opts.min_port) def hosts(host_str): if host_str == "*": if has_dual_stack(): #IPv6 will also listen for IPv4: return ["::"] #no dual stack, so we have to listen on both IPv4 and IPv6 explicitly: return ["0.0.0.0", "::"] return [host_str] def add_mdns(socktype, host_str, port): recs = mdns_recs.setdefault(socktype.lower(), []) for host in hosts(host_str): rec = (host, port) if rec not in recs: recs.append(rec) def add_tcp_socket(socktype, host_str, iport): if iport < min_port: error_cb("invalid %s port number %i (minimum value is %i)" % (socktype, iport, min_port)) for host in hosts(host_str): socket = setup_tcp_socket(host, iport, socktype) sockets.append(socket) add_mdns(socktype, host, iport) def add_udp_socket(socktype, host_str, iport): if iport < min_port: error_cb("invalid %s port number %i (minimum value is %i)" % (socktype, iport, min_port)) for host in hosts(host_str): socket = setup_udp_socket(host, iport, socktype) sockets.append(socket) add_mdns(socktype, host, iport) # Initialize the TCP sockets before the display, # That way, errors won't make us kill the Xvfb # (which may not be ours to kill at that point) ws_upgrades = opts.html and (os.path.isabs(opts.html) or opts.html.lower() in TRUE_OPTIONS + ["auto"]) netlog("setting up SSL sockets: %s", csv(bind_ssl)) for host, iport in bind_ssl: add_tcp_socket("ssl", host, iport) if ws_upgrades: add_tcp_socket("wss", host, iport) netlog("setting up https / wss (secure websockets): %s", csv(bind_wss)) for host, iport in bind_wss: add_tcp_socket("wss", host, iport) tcp_ssl = ssl_opt in TRUE_OPTIONS or (ssl_opt == "auto" and opts.ssl_cert) netlog("setting up TCP sockets: %s", csv(bind_tcp)) for host, iport in bind_tcp: add_tcp_socket("tcp", host, iport) if tcp_ssl: add_mdns("ssl", host, iport) if ws_upgrades: add_mdns("ws", host, iport) if ws_upgrades and tcp_ssl: add_mdns("wss", host, iport) netlog("setting up UDP sockets: %s", csv(bind_udp)) for host, iport in bind_udp: add_udp_socket("udp", host, iport) netlog("setting up http / ws (websockets): %s", csv(bind_ws)) for host, iport in bind_ws: add_tcp_socket("ws", host, iport) if tcp_ssl: add_mdns("wss", host, iport) if bind_rfb and (proxying or starting): log.warn("Warning: bind-rfb sockets cannot be used with '%s' mode" % mode) else: netlog("setting up rfb sockets: %s", csv(bind_rfb)) for host, iport in bind_rfb: add_tcp_socket("rfb", host, iport) netlog("setting up vsock sockets: %s", csv(bind_vsock)) for cid, iport in bind_vsock: socket = setup_vsock_socket(cid, iport) sockets.append(socket) #add_mdns("vsock", str(cid), iport) # systemd socket activation: try: from xpra.platform.xposix.sd_listen import get_sd_listen_sockets except ImportError: pass else: sd_sockets = get_sd_listen_sockets() netlog("systemd sockets: %s", sd_sockets) for stype, socket, addr in sd_sockets: sockets.append((stype, socket, addr)) netlog("%s : %s", (stype, [addr]), socket) if stype == "tcp": host, iport = addr add_mdns("tcp", host, iport) sanitize_env() if POSIX: if xrd: os.environ["XDG_RUNTIME_DIR"] = xrd 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 os.environ["CKCON_X11_DISPLAY"] = display_name else: try: del os.environ["DISPLAY"] except: pass os.environ.update(protected_env) 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) 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): 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 = display_name[1:] log("writing display='%s' to displayfd=%i", display, displayfd) assert write_displayfd(displayfd, display), "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 try: os.close(displayfd) except: pass if not proxying: def close_display(): close_gtk_display() if xvfb_pid: kill_xvfb(xvfb_pid) add_cleanup(close_display) else: close_display = None if opts.daemon: def noerr(fn, *args): try: fn(*args) except: 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: #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 cmd = ["xprop", "-display", display_name, "-root", "_XPRA_UINPUT_ID"] 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: os.chdir(opts.chdir) display = None if not proxying: no_gtk() if POSIX and not OSX and (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 if not PYTHON3: from xpra.x11.gtk2.gdk_display_util import verify_gdk_display #@UnusedImport else: from xpra.x11.gtk3.gdk_display_util import verify_gdk_display #@Reimport display = verify_gdk_display(display_name) if not display: return 1 #on win32, this ensures that we get the correct screen size to shadow: from xpra.platform.gui import init as gui_init gui_init() #setup unix domain socket: if not opts.socket_dir and not opts.socket_dirs: #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, username, uid, gid) netlog("setting up local sockets: %s", local_sockets) for rec, cleanup_socket in local_sockets: socktype, socket, sockpath = rec #ie: ("unix-domain", sock, sockpath), cleanup_socket sockets.append(rec) netlog("%s %s : %s", socktype, sockpath, socket) add_cleanup(cleanup_socket) if opts.mdns: ssh_port = get_ssh_port() netlog("ssh %s:%s : %s", "", ssh_port, socket) if ssh_port: add_mdns("ssh", "", ssh_port) kill_dbus = None if shadowing: from xpra.platform.shadow_server import ShadowServer app = ShadowServer() elif proxying: from xpra.server.proxy.proxy_server import ProxyServer app = ProxyServer() else: if not check_xvfb(): return 1 assert starting or starting_desktop or upgrading from xpra.x11.gtk2.gdk_display_source import init_gdk_display_source init_gdk_display_source() #(now we can access the X11 server) #make sure the pid we save is the real one: if not check_xvfb(): return 1 if xvfb_pid is not None: #save the new pid (we should have one): save_xvfb_pid(xvfb_pid) if POSIX: save_uinput_id(uinput_uuid or "") dbus_pid = -1 dbus_env = {} if clobber: #get the saved pids and env dbus_pid = get_dbus_pid() dbus_env = get_dbus_env() log("retrieved existing dbus attributes") else: assert starting or starting_desktop if xvfb_pid is not None: #save the new pid (we should have one): save_xvfb_pid(xvfb_pid) bus_address = protected_env.get("DBUS_SESSION_BUS_ADDRESS") log("dbus_launch=%s, current DBUS_SESSION_BUS_ADDRESS=%s", opts.dbus_launch, bus_address) if opts.dbus_launch and not bus_address: #start a dbus server: def kill_dbus(): log("kill_dbus: dbus_pid=%s" % dbus_pid) if dbus_pid <= 0: return try: os.kill(dbus_pid, signal.SIGINT) except Exception as e: log.warn( "Warning: error trying to stop dbus with pid %i:", dbus_pid) log.warn(" %s", e) add_cleanup(kill_dbus) #this also updates os.environ with the dbus attributes: dbus_pid, dbus_env = start_dbus(opts.dbus_launch) if dbus_pid > 0: save_dbus_pid(dbus_pid) if dbus_env: save_dbus_env(dbus_env) log("dbus attributes: pid=%s, env=%s", dbus_pid, dbus_env) if dbus_env: os.environ.update(dbus_env) os.environ.update(protected_env) if opts.forward_xdg_open and os.environ.get( "DISPLAY") and local_sockets: #find one xdg-open can use to talk back to us: udpaths = [ sockpath for (stype, _, sockpath), _ in local_sockets if stype == "unix-domain" ] if udpaths: os.environ["XPRA_XDG_OPEN_SERVER_SOCKET"] = udpaths[0] 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", "") break log("env=%s", os.environ) try: # This import is delayed because the module depends on gtk: from xpra.x11.bindings.window_bindings import X11WindowBindings X11Window = X11WindowBindings() if (starting or starting_desktop) and not clobber and opts.resize_display: from xpra.x11.vfb_util import set_initial_resolution set_initial_resolution(starting_desktop) except ImportError as e: log.error( "Failed to load Xpra server components, check your installation: %s" % e) return 1 if starting or upgrading: if not X11Window.displayHasXComposite(): log.error( "Xpra 'start' subcommand runs as a compositing manager") log.error( " it cannot use a display which lacks the XComposite extension!" ) return 1 assert not PYTHON3 if starting: #check for an existing window manager: from xpra.x11.gtk2.wm import wm_check if not wm_check(display, opts.wm_name, upgrading): return 1 log("XShape=%s", X11Window.displayHasXShape()) from xpra.x11.server import XpraServer app = XpraServer(clobber) else: assert starting_desktop and not PYTHON3 from xpra.x11.desktop_server import XpraDesktopServer app = XpraDesktopServer() app.init_virtual_devices(devices) if proxying or upgrading: #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: log.warn("Warning: the 'start-child' option is used,") log.warn(" but 'exit-with-children' is not enabled,") log.warn(" use 'start' instead") try: app._ssl_wrap_socket = wrap_socket_fn app.original_desktop_display = desktop_display app.exec_cwd = opts.chdir or cwd app.init(opts) app.setup() except InitException as e: log.error("xpra server initialization error:") log.error(" %s", e) 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("") return 1 #publish mdns records: if opts.mdns: from xpra.platform.info import get_username from xpra.server.socket_util import mdns_publish mdns_info = { "display": display_name, "username": get_username(), "uuid": app.uuid, "platform": sys.platform, "type": app.session_type, } MDNS_EXPOSE_NAME = envbool("XPRA_MDNS_EXPOSE_NAME", True) if MDNS_EXPOSE_NAME and app.session_name: mdns_info["name"] = app.session_name for mode, listen_on in mdns_recs.items(): mdns_publish(display_name, mode, listen_on, mdns_info) del opts log("%s(%s)", app.init_sockets, sockets) app.init_sockets(sockets) log("%s(%s)", app.init_when_ready, _when_ready) app.init_when_ready(_when_ready) try: #from here on, we own the vfb, even if we inherited one: if (starting or starting_desktop or upgrading) and clobber: #and it will be killed if exit cleanly: xvfb_pid = get_xvfb_pid() log("running %s", app.run) r = app.run() log("%s()=%s", app.run, r) except KeyboardInterrupt: log.info("stopping on KeyboardInterrupt") r = 0 except Exception: log.error("server error", exc_info=True) r = -128 if r > 0: # Upgrading/exiting, so leave X and dbus servers running if close_display: _cleanups.remove(close_display) if kill_dbus: _cleanups.remove(kill_dbus) from xpra.server import EXITING_CODE if r == EXITING_CODE: log.info("exiting: not cleaning up Xvfb") else: log.info("upgrading: not cleaning up Xvfb") log("cleanups=%s", _cleanups) r = 0 return r
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, username, uid, gid) display_name = normalize_local_display_name(display_name) log = get_network_logger() defs = [] try: sockpaths = [] log("setup_local_sockets: bind=%s", bind) for b in bind: sockpath = b if b == "none" or b == "": continue elif b == "auto": sockpaths += dotxpra.norm_socket_paths(display_name) 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)): sockpath = os.path.abspath(sockpath) if not os.path.exists(sockpath): os.makedirs(sockpath) sockpath = norm_makepath(sockpath, display_name) elif os.path.isabs(b): sockpath = b else: sockpath = dotxpra.socket_path(b) sockpaths += [sockpath] assert sockpaths, "no socket paths to try for %s" % b #expand and remove duplicate paths: tmp = [] for tsp in sockpaths: sockpath = dotxpra.osexpand(tsp) if sockpath in tmp: log.warn("Warning: skipping duplicate bind path %s", sockpath) continue tmp.append(sockpath) sockpaths = tmp #create listeners: if WIN32: from xpra.platform.win32.namedpipes.listener import NamedPipeListener for sockpath in sockpaths: npl = NamedPipeListener(sockpath) log.info("created named pipe: %s", sockpath) defs.append((("named-pipe", npl, sockpath), npl.stop)) else: def checkstate(sockpath, state): if state not in (DotXpra.DEAD, DotXpra.UNKNOWN): if state == DotXpra.INACCESSIBLE: raise InitException( "An xpra server is already running at %s\n" % (sockpath, )) raise InitException( "You already have an xpra server running at %s\n" " (did you want 'xpra upgrade'?)" % (sockpath, )) #remove exisiting sockets if clobber is set, #otherwise verify there isn't a server already running #and create the directories for the sockets: unknown = [] for sockpath in sockpaths: if clobber and os.path.exists(sockpath): os.unlink(sockpath) else: state = dotxpra.get_server_state(sockpath, 1) log("state(%s)=%s", sockpath, state) checkstate(sockpath, state) if state == dotxpra.UNKNOWN: unknown.append(sockpath) d = os.path.dirname(sockpath) try: dotxpra.mksockdir(d) except Exception as e: log.warn("Warning: failed to create socket directory '%s'", d) log.warn(" %s", e) del e #wait for all the unknown ones: log("sockets in unknown state: %s", unknown) if unknown: #re-probe them using threads so we can do them in parallel: from time import sleep from xpra.make_thread import start_thread threads = [] def timeout_probe(sockpath): #we need a loop because "DEAD" sockets may return immediately #(ie: when the server is starting up) start = monotonic_time() while monotonic_time() - start < WAIT_PROBE_TIMEOUT: state = dotxpra.get_server_state( sockpath, WAIT_PROBE_TIMEOUT) log("timeout_probe() get_server_state(%s)=%s", sockpath, state) if state not in (DotXpra.UNKNOWN, DotXpra.DEAD): break sleep(1) log.warn( "Warning: some of the sockets are in an unknown state:") for sockpath in unknown: log.warn(" %s", sockpath) t = start_thread(timeout_probe, "probe-%s" % sockpath, daemon=True, args=(sockpath, )) threads.append(t) log.warn( " please wait as we allow the socket probing to timeout") #wait for all the threads to do their job: for t in threads: t.join(WAIT_PROBE_TIMEOUT + 1) #now we can re-check quickly: #(they should all be DEAD or UNKNOWN): for sockpath in sockpaths: state = dotxpra.get_server_state(sockpath, 1) log("state(%s)=%s", sockpath, state) checkstate(sockpath, state) try: if os.path.exists(sockpath): os.unlink(sockpath) except: pass #now try to create all the sockets: for sockpath in sockpaths: #create it: try: sock, cleanup_socket = create_unix_domain_socket( sockpath, mmap_group, socket_permissions) log.info("created unix domain socket: %s", sockpath) defs.append( (("unix-domain", sock, sockpath), cleanup_socket)) except Exception as e: handle_socket_error(sockpath, e) del e except: for sock, cleanup_socket in defs: try: cleanup_socket() except Exception as e: log.error("Error cleaning up socket %s:", sock) log.error(" %s", e) del e defs = [] raise return defs
def start_Xvfb(xvfb_str, pixel_depth, display_name, cwd, uid, gid, username, xauth_data, uinput_uuid=None): if not POSIX: raise InitException("starting an Xvfb is not supported on %s" % os.name) if OSX: raise InitException("starting an Xvfb is not supported on MacOS") if not xvfb_str: raise InitException("the 'xvfb' command is not defined") cleanups = [] log = get_vfb_logger() log("start_Xvfb%s", (xvfb_str, pixel_depth, display_name, cwd, uid, gid, username, xauth_data, uinput_uuid)) xauthority = get_xauthority_path(display_name, username, uid, gid) os.environ["XAUTHORITY"] = xauthority if not os.path.exists(xauthority): log("creating XAUTHORITY=%s with data=%s", xauthority, xauth_data) try: with open(xauthority, 'wa') as f: if getuid() == 0 and (uid != 0 or gid != 0): os.fchown(f.fileno(), uid, gid) except Exception as e: #trying to continue anyway! log.error("Error trying to create XAUTHORITY file %s:", xauthority) log.error(" %s", e) else: log("found existing XAUTHORITY file '%s'", xauthority) use_display_fd = display_name[0] == 'S' subs = {} def pathexpand(s): return osexpand(s, actual_username=username, uid=uid, gid=gid, subs=subs) subs.update({ "DISPLAY": display_name, "XPRA_LOG_DIR": pathexpand(os.environ.get("XPRA_LOG_DIR")), }) #identify logfile argument if it exists, #as we may have to rename it, or create the directory for it: import shlex xvfb_cmd = shlex.split(xvfb_str) if not xvfb_cmd: raise InitException( "cannot start Xvfb, the command definition is missing!") #make sure all path values are expanded: xvfb_cmd = [pathexpand(s) for s in xvfb_cmd] try: logfile_argindex = xvfb_cmd.index('-logfile') assert logfile_argindex + 1 < len( xvfb_cmd ), "invalid xvfb command string: -logfile should not be last (found at index %i)" % logfile_argindex xorg_log_file = xvfb_cmd[logfile_argindex + 1] except ValueError: xorg_log_file = None tmp_xorg_log_file = None if xorg_log_file: if use_display_fd: #keep track of it so we can rename it later: tmp_xorg_log_file = xorg_log_file #make sure the Xorg log directory exists: xorg_log_dir = os.path.dirname(xorg_log_file) if not os.path.exists(xorg_log_dir): try: log("creating Xorg log dir '%s'", xorg_log_dir) os.mkdir(xorg_log_dir, 0o700) if POSIX and uid != getuid() or gid != getgid(): try: os.lchown(xorg_log_dir, uid, gid) except: pass except OSError as e: raise InitException( "failed to create the Xorg log directory '%s': %s" % (xorg_log_dir, e)) if uinput_uuid: #use uinput: #identify -config xorg.conf argument and replace it with the uinput one: try: config_argindex = xvfb_cmd.index("-config") except ValueError as e: log.warn("Warning: cannot use uinput") log.warn(" '-config' argument not found in the xvfb command") else: assert config_argindex + 1 < len( xvfb_cmd ), "invalid xvfb command string: -config should not be last (found at index %i)" % config_argindex xorg_conf = xvfb_cmd[config_argindex + 1] if xorg_conf.endswith("xorg.conf"): xorg_conf = xorg_conf.replace("xorg.conf", "xorg-uinput.conf") if os.path.exists(xorg_conf): xvfb_cmd[config_argindex + 1] = xorg_conf #create uinput device definition files: #(we have to assume that Xorg is configured to use this path..) xorg_conf_dir = pathexpand(get_Xdummy_confdir()) cleanups = create_xorg_device_configs(xorg_conf_dir, uinput_uuid, uid, gid) xvfb_executable = xvfb_cmd[0] if (xvfb_executable.endswith("Xorg") or xvfb_executable.endswith("Xdummy")) and pixel_depth > 0: xvfb_cmd.append("-depth") xvfb_cmd.append(str(pixel_depth)) xvfb = None try: if use_display_fd: def displayfd_err(msg): raise InitException("%s: %s" % (xvfb_executable, msg)) r_pipe, w_pipe = os.pipe() try: if PYTHON3: os.set_inheritable(w_pipe, True) #@UndefinedVariable xvfb_cmd += ["-displayfd", str(w_pipe)] xvfb_cmd[0] = "%s-for-Xpra-%s" % (xvfb_executable, display_name) def preexec(): setsid() if getuid() == 0 and uid: setuidgid(uid, gid) close_fds([0, 1, 2, r_pipe, w_pipe]) try: xvfb = subprocess.Popen(xvfb_cmd, executable=xvfb_executable, close_fds=False, stdin=subprocess.PIPE, preexec_fn=preexec, cwd=cwd) except OSError as e: log("Popen%s", (xvfb_cmd, xvfb_executable, cwd), exc_info=True) raise InitException( "failed to execute xvfb command %s: %s" % (xvfb_cmd, e)) assert xvfb.poll() is None, "xvfb command failed" # Read the display number from the pipe we gave to Xvfb try: buf = read_displayfd(r_pipe) except Exception as e: log("read_displayfd(%s)", r_pipe, exc_info=True) displayfd_err("failed to read displayfd pipe %s: %s" % (r_pipe, e)) finally: osclose(r_pipe) osclose(w_pipe) n = parse_displayfd(buf, displayfd_err) new_display_name = ":%s" % n log("Using display number provided by %s: %s", xvfb_executable, new_display_name) if tmp_xorg_log_file != None: #ie: ${HOME}/.xpra/Xorg.${DISPLAY}.log -> /home/antoine/.xpra/Xorg.S14700.log f0 = shellsub(tmp_xorg_log_file, subs) subs["DISPLAY"] = new_display_name #ie: ${HOME}/.xpra/Xorg.${DISPLAY}.log -> /home/antoine/.xpra/Xorg.:1.log f1 = shellsub(tmp_xorg_log_file, subs) if f0 != f1: try: os.rename(f0, f1) except Exception as e: log.warn("Warning: failed to rename Xorg log file,") log.warn(" from '%s' to '%s'" % (f0, f1)) log.warn(" %s" % e) display_name = new_display_name else: # use display specified xvfb_cmd[0] = "%s-for-Xpra-%s" % (xvfb_executable, display_name) xvfb_cmd.append(display_name) def preexec(): if getuid() == 0 and (uid != 0 or gid != 0): setuidgid(uid, gid) else: setsid() log("xvfb_cmd=%s", xvfb_cmd) xvfb = subprocess.Popen(xvfb_cmd, executable=xvfb_executable, close_fds=True, stdin=subprocess.PIPE, preexec_fn=preexec) xauth_add(xauthority, display_name, xauth_data, uid, gid) except Exception as e: if xvfb and xvfb.poll() is None: log.error(" stopping vfb process with pid %i", xvfb.pid) xvfb.terminate() raise log("xvfb process=%s", xvfb) log("display_name=%s", display_name) return xvfb, display_name, cleanups
def displayfd_err(msg): raise InitException("%s: %s" % (xvfb_executable, msg))
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 = int(upgrading or upgrading_desktop) | int(opts.use_display or 0) * 2 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: from xpra.os_util import saved_env saved_env["GDK_BACKEND"] = "x11" 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 upgrading 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 = None if POSIX and getuid() != 0: 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 script: # 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 try: from xpra.server.dbus.dbus_start import start_dbus except ImportError as e: log("dbus components are not installed: %s", e) else: 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("") if upgrading or upgrading_desktop: #something abnormal occurred, #don't kill the vfb on exit: from xpra.server import EXITING_CODE app._upgrading = EXITING_CODE 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
def start_Xvfb(xvfb_str, vfb_geom, pixel_depth, display_name, cwd, uid, gid, username, uinput_uuid=None): if not POSIX: raise InitException("starting an Xvfb is not supported on %s" % os.name) if OSX: raise InitException("starting an Xvfb is not supported on MacOS") if not xvfb_str: raise InitException("the 'xvfb' command is not defined") log = get_vfb_logger() log("start_Xvfb%s", (xvfb_str, vfb_geom, pixel_depth, display_name, cwd, uid, gid, username, uinput_uuid)) use_display_fd = display_name[0] == 'S' subs = {} def pathexpand(s): return osexpand(s, actual_username=username, uid=uid, gid=gid, subs=subs) subs.update({ "DISPLAY": display_name, "XPRA_LOG_DIR": pathexpand(os.environ.get("XPRA_LOG_DIR")), }) #identify logfile argument if it exists, #as we may have to rename it, or create the directory for it: xvfb_cmd = shlex.split(xvfb_str) if not xvfb_cmd: raise InitException( "cannot start Xvfb, the command definition is missing!") #make sure all path values are expanded: xvfb_cmd = [pathexpand(s) for s in xvfb_cmd] #try to honour initial geometries if specified: if vfb_geom and xvfb_cmd[0].endswith("Xvfb"): #find the '-screen' arguments: #"-screen 0 8192x4096x24+32" try: no_arg = xvfb_cmd.index("0") except ValueError: no_arg = -1 geom_str = "%sx%s" % ("x".join(str(x) for x in vfb_geom), pixel_depth) if no_arg > 0 and xvfb_cmd[ no_arg - 1] == "-screen" and len(xvfb_cmd) > (no_arg + 1): #found an existing "-screen" argument for this screen number, #replace it: xvfb_cmd[no_arg + 1] = geom_str else: #append: xvfb_cmd += ["-screen", "0", geom_str] try: logfile_argindex = xvfb_cmd.index('-logfile') if logfile_argindex + 1 >= len(xvfb_cmd): raise InitException( "invalid xvfb command string: -logfile should not be last") xorg_log_file = xvfb_cmd[logfile_argindex + 1] except ValueError: xorg_log_file = None tmp_xorg_log_file = None if xorg_log_file: if use_display_fd: #keep track of it so we can rename it later: tmp_xorg_log_file = xorg_log_file #make sure the Xorg log directory exists: xorg_log_dir = os.path.dirname(xorg_log_file) if not os.path.exists(xorg_log_dir): try: log("creating Xorg log dir '%s'", xorg_log_dir) os.mkdir(xorg_log_dir, 0o700) if POSIX and uid != getuid() or gid != getgid(): try: os.lchown(xorg_log_dir, uid, gid) except OSError: log("lchown(%s, %i, %i)", xorg_log_dir, uid, gid, exc_info=True) except OSError as e: raise InitException( "failed to create the Xorg log directory '%s': %s" % (xorg_log_dir, e)) from None if uinput_uuid: #use uinput: #identify -config xorg.conf argument and replace it with the uinput one: try: config_argindex = xvfb_cmd.index("-config") except ValueError as e: log.warn("Warning: cannot use uinput") log.warn(" '-config' argument not found in the xvfb command") else: if config_argindex + 1 >= len(xvfb_cmd): raise InitException( "invalid xvfb command string: -config should not be last") xorg_conf = xvfb_cmd[config_argindex + 1] if xorg_conf.endswith("xorg.conf"): xorg_conf = xorg_conf.replace("xorg.conf", "xorg-uinput.conf") if os.path.exists(xorg_conf): xvfb_cmd[config_argindex + 1] = xorg_conf #create uinput device definition files: #(we have to assume that Xorg is configured to use this path..) xorg_conf_dir = pathexpand(get_Xdummy_confdir()) create_xorg_device_configs(xorg_conf_dir, uinput_uuid, uid, gid) xvfb_executable = xvfb_cmd[0] if (xvfb_executable.endswith("Xorg") or xvfb_executable.endswith("Xdummy")) and pixel_depth > 0: xvfb_cmd.append("-depth") xvfb_cmd.append(str(pixel_depth)) xvfb = None try: if use_display_fd: def displayfd_err(msg): raise InitException("%s: %s" % (xvfb_executable, msg)) r_pipe, w_pipe = os.pipe() try: os.set_inheritable(w_pipe, True) #@UndefinedVariable xvfb_cmd += ["-displayfd", str(w_pipe)] xvfb_cmd[0] = "%s-for-Xpra-%s" % (xvfb_executable, display_name.lstrip(":")) def preexec(): os.setpgrp() if getuid() == 0 and uid: setuidgid(uid, gid) try: xvfb = Popen(xvfb_cmd, executable=xvfb_executable, preexec_fn=preexec, cwd=cwd, pass_fds=(w_pipe, )) except OSError as e: log("Popen%s", (xvfb_cmd, xvfb_executable, cwd), exc_info=True) raise InitException( "failed to execute xvfb command %s: %s" % (xvfb_cmd, e)) from None assert xvfb.poll() is None, "xvfb command failed" # Read the display number from the pipe we gave to Xvfb try: buf = read_displayfd(r_pipe) except Exception as e: log("read_displayfd(%s)", r_pipe, exc_info=True) displayfd_err("failed to read displayfd pipe %s: %s" % (r_pipe, e)) finally: osclose(r_pipe) osclose(w_pipe) n = parse_displayfd(buf, displayfd_err) new_display_name = ":%s" % n log("Using display number provided by %s: %s", xvfb_executable, new_display_name) if tmp_xorg_log_file: #ie: ${HOME}/.xpra/Xorg.${DISPLAY}.log -> /home/antoine/.xpra/Xorg.S14700.log f0 = shellsub(tmp_xorg_log_file, subs) subs["DISPLAY"] = new_display_name #ie: ${HOME}/.xpra/Xorg.${DISPLAY}.log -> /home/antoine/.xpra/Xorg.:1.log f1 = shellsub(tmp_xorg_log_file, subs) if f0 != f1: try: os.rename(f0, f1) except Exception as e: log.warn("Warning: failed to rename Xorg log file,") log.warn(" from '%s' to '%s'" % (f0, f1)) log.warn(" %s" % e) display_name = new_display_name else: # use display specified xvfb_cmd[0] = "%s-for-Xpra-%s" % (xvfb_executable, display_name) xvfb_cmd.append(display_name) def preexec(): if getuid() == 0 and (uid != 0 or gid != 0): setuidgid(uid, gid) else: os.setsid() log("xvfb_cmd=%s", xvfb_cmd) xvfb = Popen(xvfb_cmd, executable=xvfb_executable, stdin=PIPE, preexec_fn=preexec) except Exception as e: if xvfb and xvfb.poll() is None: log.error(" stopping vfb process with pid %i", xvfb.pid) xvfb.terminate() raise log("xvfb process=%s", xvfb) log("display_name=%s", display_name) return xvfb, display_name
def start_Xvfb(xvfb_str, pixel_depth, display_name, cwd, uid, gid, xauth_data): if not POSIX: raise InitException("starting an Xvfb is not supported on %s" % os.name) if not xvfb_str: raise InitException("the 'xvfb' command is not defined") from xpra.log import Logger log = Logger("server", "x11") # We need to set up a new server environment xauthority = os.environ.get("XAUTHORITY", os.path.expanduser("~/.Xauthority")) if not os.path.exists(xauthority): log("creating XAUTHORITY=%s with data=%s", xauthority, xauth_data) try: with open(xauthority, 'wa') as f: if getuid() == 0 and (uid != 0 or gid != 0): os.fchown(f.fileno(), uid, gid) except Exception as e: #trying to continue anyway! log.error("Error trying to create XAUTHORITY file %s:", xauthority) log.error(" %s", e) use_display_fd = display_name[0] == 'S' #identify logfile argument if it exists, #as we may have to rename it, or create the directory for it: import shlex xvfb_cmd = shlex.split(xvfb_str) try: logfile_argindex = xvfb_cmd.index('-logfile') except ValueError: logfile_argindex = -1 assert logfile_argindex + 1 < len( xvfb_cmd ), "invalid xvfb command string: -logfile should not be last (found at index %i)" % logfile_argindex tmp_xorg_log_file = None if logfile_argindex > 0: if use_display_fd: #keep track of it so we can rename it later: tmp_xorg_log_file = xvfb_cmd[logfile_argindex + 1] #make sure the Xorg log directory exists: xorg_log_dir = osexpand(os.path.dirname(xvfb_cmd[logfile_argindex + 1])) if not os.path.exists(xorg_log_dir): try: log("creating Xorg log dir '%s'", xorg_log_dir) os.mkdir(xorg_log_dir, 0o700) if POSIX and uid != getuid() or gid != getgid(): try: os.lchown(xorg_log_dir, uid, gid) except: pass except OSError as e: raise InitException( "failed to create the Xorg log directory '%s': %s" % (xorg_log_dir, e)) #apply string substitutions: subs = { "XAUTHORITY": xauthority, "USER": os.environ.get("USER", "unknown-user"), "UID": os.getuid(), "GID": os.getgid(), "HOME": os.environ.get("HOME", cwd), "DISPLAY": display_name, "XPRA_LOG_DIR": os.environ.get("XPRA_LOG_DIR"), } xvfb_cmd = shellsub(xvfb_str, subs).split() log("shellsub(%s, %s)=%s", xvfb_str, subs, xvfb_cmd) if not xvfb_cmd: raise InitException( "cannot start Xvfb, the command definition is missing!") xvfb_executable = xvfb_cmd[0] if (xvfb_executable.endswith("Xorg") or xvfb_executable.endswith("Xdummy")) and pixel_depth > 0: xvfb_cmd.append("-depth") xvfb_cmd.append(str(pixel_depth)) if use_display_fd: # 'S' means that we allocate the display automatically r_pipe, w_pipe = os.pipe() xvfb_cmd += ["-displayfd", str(w_pipe)] xvfb_cmd[0] = "%s-for-Xpra-%s" % (xvfb_executable, display_name) def preexec(): setsid() if POSIX and getuid() == 0 and uid: setuidgid(uid, gid) close_fds([0, 1, 2, r_pipe, w_pipe]) log("xvfb_cmd=%s", xvfb_cmd) xvfb = subprocess.Popen(xvfb_cmd, executable=xvfb_executable, close_fds=False, stdin=subprocess.PIPE, preexec_fn=preexec, cwd=cwd) # Read the display number from the pipe we gave to Xvfb # waiting up to 10 seconds for it to show up limit = monotonic_time() + 10 buf = "" import select #@UnresolvedImport while monotonic_time() < limit and len(buf) < 8: r, _, _ = select.select([r_pipe], [], [], max(0, limit - monotonic_time())) if r_pipe in r: buf += os.read(r_pipe, 8) if buf[-1] == '\n': break os.close(r_pipe) os.close(w_pipe) if len(buf) == 0: raise InitException( "%s did not provide a display number using -displayfd" % xvfb_executable) if buf[-1] != '\n': raise InitException("%s output not terminated by newline: %s" % (xvfb_executable, buf)) try: n = int(buf[:-1]) except: raise InitException("%s display number is not a valid number: %s" % (xvfb_executable, buf[:-1])) if n < 0 or n >= 2**16: raise InitException("%s provided an invalid display number: %s" % (xvfb_executable, n)) new_display_name = ":%s" % n log("Using display number provided by %s: %s", xvfb_executable, new_display_name) if tmp_xorg_log_file != None: #ie: ${HOME}/.xpra/Xorg.${DISPLAY}.log -> /home/antoine/.xpra/Xorg.S14700.log f0 = shellsub(tmp_xorg_log_file, subs) subs["DISPLAY"] = new_display_name #ie: ${HOME}/.xpra/Xorg.${DISPLAY}.log -> /home/antoine/.xpra/Xorg.:1.log f1 = shellsub(tmp_xorg_log_file, subs) if f0 != f1: try: os.rename(f0, f1) except Exception as e: sys.stderr.write( "failed to rename Xorg log file from '%s' to '%s'\n" % (f0, f1)) sys.stderr.write(" %s\n" % e) display_name = new_display_name else: # use display specified xvfb_cmd[0] = "%s-for-Xpra-%s" % (xvfb_executable, display_name) xvfb_cmd.append(display_name) def preexec(): if getuid() == 0 and (uid != 0 or gid != 0): setuidgid(uid, gid) else: setsid() log("xvfb_cmd=%s", xvfb_cmd) xvfb = subprocess.Popen(xvfb_cmd, executable=xvfb_executable, close_fds=True, stdin=subprocess.PIPE, preexec_fn=preexec) xauth_add(display_name, xauth_data) log("xvfb process=%s", xvfb) log("display_name=%s", display_name) return xvfb, display_name
def get_ssl_wrap_socket_fn(cert=None, key=None, ca_certs=None, ca_data=None, protocol="TLSv1_2", client_verify_mode="optional", server_verify_mode="required", verify_flags="X509_STRICT", check_hostname=False, server_hostname=None, options="ALL,NO_COMPRESSION", ciphers="DEFAULT", server_side=True): if server_side and not cert: raise InitException( "you must specify an 'ssl-cert' file to use ssl sockets") if server_side: verify_mode = client_verify_mode else: verify_mode = server_verify_mode from xpra.log import Logger ssllog = Logger("ssl") ssllog("get_ssl_wrap_socket_fn%s", (cert, key, ca_certs, ca_data, protocol, client_verify_mode, server_verify_mode, verify_flags, check_hostname, server_hostname, options, ciphers, server_side)) import ssl ssllog(" verify_mode for server_side=%s : %s", server_side, verify_mode) #ca-certs: if ca_certs == "default": ca_certs = None ssllog(" ca_certs=%s", ca_certs) #parse verify-mode: ssl_cert_reqs = getattr(ssl, "CERT_%s" % verify_mode.upper(), None) if ssl_cert_reqs is None: values = [ k[len("CERT_"):].lower() for k in dir(ssl) if k.startswith("CERT_") ] raise InitException( "invalid ssl-server-verify-mode '%s', must be one of: %s" % (verify_mode, csv(values))) ssllog(" cert_reqs=%#x", ssl_cert_reqs) #parse protocol: proto = getattr(ssl, "PROTOCOL_%s" % (protocol.upper().replace("V", "v")), None) if proto is None: values = [ k[len("PROTOCOL_"):] for k in dir(ssl) if k.startswith("PROTOCOL_") ] raise InitException("invalid ssl-protocol '%s', must be one of: %s" % (protocol, csv(values))) ssllog(" protocol=%#x", proto) #ca_data may be hex encoded: ca_data = parse_encoded_bin_data(ca_data) ssllog(" cadata=%s", ellipsizer(ca_data)) kwargs = { "server_side": server_side, "do_handshake_on_connect": False, "suppress_ragged_eofs": True, } #parse ssl-verify-flags as CSV: ssl_verify_flags = 0 for x in verify_flags.split(","): x = x.strip() if not x: continue v = getattr(ssl, "VERIFY_" + x.upper(), None) if v is None: raise InitException("invalid ssl verify-flag: %s" % x) ssl_verify_flags |= v ssllog(" verify_flags=%#x", ssl_verify_flags) #parse ssl-options as CSV: ssl_options = 0 for x in options.split(","): x = x.strip() if not x: continue v = getattr(ssl, "OP_" + x.upper(), None) if v is None: raise InitException("invalid ssl option: %s" % x) ssl_options |= v ssllog(" options=%#x", ssl_options) context = ssl.SSLContext(proto) context.set_ciphers(ciphers) context.verify_mode = ssl_cert_reqs context.verify_flags = ssl_verify_flags context.options = ssl_options ssllog(" cert=%s, key=%s", cert, key) if cert: SSL_KEY_PASSWORD = os.environ.get("XPRA_SSL_KEY_PASSWORD") ssllog("context.load_cert_chain%s", (cert or None, key or None, SSL_KEY_PASSWORD)) context.load_cert_chain(certfile=cert or None, keyfile=key or None, password=SSL_KEY_PASSWORD) if ssl_cert_reqs != ssl.CERT_NONE: if server_side: purpose = ssl.Purpose.CLIENT_AUTH #@UndefinedVariable else: purpose = ssl.Purpose.SERVER_AUTH #@UndefinedVariable context.check_hostname = check_hostname ssllog(" check_hostname=%s, server_hostname=%s", check_hostname, server_hostname) if context.check_hostname: if not server_hostname: raise InitException( "ssl error: check-hostname is set but server-hostname is not" ) kwargs["server_hostname"] = server_hostname ssllog(" load_default_certs(%s)", purpose) context.load_default_certs(purpose) if not ca_certs or ca_certs.lower() == "default": ssllog(" using default certs") #load_default_certs already calls set_default_verify_paths() elif not os.path.exists(ca_certs): raise InitException("invalid ssl-ca-certs file or directory: %s" % ca_certs) elif os.path.isdir(ca_certs): ssllog(" loading ca certs from directory '%s'", ca_certs) context.load_verify_locations(capath=ca_certs) else: ssllog(" loading ca certs from file '%s'", ca_certs) assert os.path.isfile( ca_certs), "'%s' is not a valid ca file" % ca_certs context.load_verify_locations(cafile=ca_certs) #handle cadata: if ca_data: #PITA: because of a bug in the ssl module, we can't pass cadata, #so we use a temporary file instead: import tempfile with tempfile.NamedTemporaryFile(prefix='cadata') as f: ssllog(" loading cadata '%s'", ellipsizer(ca_data)) ssllog(" using temporary file '%s'", f.name) f.file.write(ca_data) f.file.flush() context.load_verify_locations(cafile=f.name) elif check_hostname and not server_side: ssllog("cannot check hostname client side with verify mode %s", verify_mode) wrap_socket = context.wrap_socket def do_wrap_socket(tcp_socket): assert tcp_socket ssllog("do_wrap_socket(%s)", tcp_socket) if WIN32: #on win32, setting the tcp socket to blocking doesn't work? #we still hit the following errors that we need to retry: from xpra.net import bytestreams bytestreams.CAN_RETRY_EXCEPTIONS = (ssl.SSLWantReadError, ssl.SSLWantWriteError) else: tcp_socket.setblocking(True) try: ssl_sock = wrap_socket(tcp_socket, **kwargs) except Exception as e: ssllog.debug("wrap_socket(%s, %s)", tcp_socket, kwargs, exc_info=True) SSLEOFError = getattr(ssl, "SSLEOFError", None) if SSLEOFError and isinstance(e, SSLEOFError): return None raise InitExit(EXIT_SSL_FAILURE, "Cannot wrap socket %s: %s" % (tcp_socket, e)) if not server_side: try: ssl_sock.do_handshake(True) except Exception as e: ssllog.debug("do_handshake", exc_info=True) SSLEOFError = getattr(ssl, "SSLEOFError", None) if SSLEOFError and isinstance(e, SSLEOFError): return None status = EXIT_SSL_FAILURE SSLCertVerificationError = getattr(ssl, "SSLCertVerificationError", None) if SSLCertVerificationError and isinstance( e, SSLCertVerificationError): try: msg = e.args[1].split(":", 2)[2] except (ValueError, IndexError): msg = str(e) status = EXIT_SSL_CERTIFICATE_VERIFY_FAILURE #ssllog.warn("host failed SSL verification: %s", msg) else: msg = str(e) raise InitExit(status, "SSL handshake failed: %s" % msg) return ssl_sock return do_wrap_socket
def setup_local_sockets(bind, socket_dir, socket_dirs, display_name, clobber, mmap_group="auto", 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])): if WIN32: socket_dirs = [""] else: raise InitException( "at least one socket directory must be set to use unix domain sockets" ) from xpra.platform.dotxpra import DotXpra, norm_makepath dotxpra = DotXpra(socket_dir or socket_dirs[0], socket_dirs, username, uid, gid) if display_name is not None: display_name = normalize_local_display_name(display_name) log = get_network_logger() defs = [] try: sockpaths = [] log("setup_local_sockets: bind=%s", bind) for b in bind: sockpath = b if b in ("none", ""): continue elif b == "auto": assert display_name is not None sockpaths += dotxpra.norm_socket_paths(display_name) 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)): assert display_name is not None sockpath = os.path.abspath(sockpath) if not os.path.exists(sockpath): os.makedirs(sockpath) sockpath = norm_makepath(sockpath, display_name) elif os.path.isabs(b): sockpath = b else: sockpath = dotxpra.socket_path(b) sockpaths += [sockpath] assert sockpaths, "no socket paths to try for %s" % b #expand and remove duplicate paths: tmp = [] for tsp in sockpaths: sockpath = dotxpra.osexpand(tsp) if sockpath in tmp: log.warn("Warning: skipping duplicate bind path %s", sockpath) continue tmp.append(sockpath) sockpaths = tmp #create listeners: if WIN32: from xpra.platform.win32.namedpipes.listener import NamedPipeListener for sockpath in sockpaths: npl = NamedPipeListener(sockpath) log.info("created named pipe '%s'", sockpath) defs.append(("named-pipe", npl, sockpath, npl.stop)) else: def checkstate(sockpath, state): if state not in (DotXpra.DEAD, DotXpra.UNKNOWN): if state == DotXpra.INACCESSIBLE: raise InitException( "An xpra server is already running at %s\n" % (sockpath, )) raise InitException( "You already have an xpra server running at %s\n" " (did you want 'xpra upgrade'?)" % (sockpath, )) #remove exisiting sockets if clobber is set, #otherwise verify there isn't a server already running #and create the directories for the sockets: unknown = [] for sockpath in sockpaths: if clobber and os.path.exists(sockpath): os.unlink(sockpath) else: state = dotxpra.get_server_state(sockpath, 1) log("state(%s)=%s", sockpath, state) checkstate(sockpath, state) if state == dotxpra.UNKNOWN: unknown.append(sockpath) d = os.path.dirname(sockpath) try: kwargs = {} if getuid( ) == 0 and d == "/var/run/xpra" or d == "/run/xpra": #this is normally done by tmpfiles.d, #but we may need to do it ourselves in some cases: kwargs = {"mode": 0o775} xpra_gid = get_group_id("xpra") if xpra_gid > 0: kwargs["gid"] = xpra_gid log("creating sockdir=%s, kwargs=%s" % (d, kwargs)) dotxpra.mksockdir(d, **kwargs) except Exception as e: log.warn("Warning: failed to create socket directory '%s'", d) log.warn(" %s", e) del e #wait for all the unknown ones: log("sockets in unknown state: %s", unknown) if unknown: #re-probe them using threads so we can do them in parallel: from time import sleep from xpra.make_thread import start_thread threads = [] def timeout_probe(sockpath): #we need a loop because "DEAD" sockets may return immediately #(ie: when the server is starting up) start = monotonic_time() while monotonic_time() - start < WAIT_PROBE_TIMEOUT: state = dotxpra.get_server_state( sockpath, WAIT_PROBE_TIMEOUT) log("timeout_probe() get_server_state(%s)=%s", sockpath, state) if state not in (DotXpra.UNKNOWN, DotXpra.DEAD): break sleep(1) log.warn( "Warning: some of the sockets are in an unknown state:") for sockpath in unknown: log.warn(" %s", sockpath) t = start_thread(timeout_probe, "probe-%s" % sockpath, daemon=True, args=(sockpath, )) threads.append(t) log.warn( " please wait as we allow the socket probing to timeout") #wait for all the threads to do their job: for t in threads: t.join(WAIT_PROBE_TIMEOUT + 1) if sockpaths: #now we can re-check quickly: #(they should all be DEAD or UNKNOWN): for sockpath in sockpaths: state = dotxpra.get_server_state(sockpath, 1) log("state(%s)=%s", sockpath, state) checkstate(sockpath, state) try: if os.path.exists(sockpath): os.unlink(sockpath) except (OSError, IOError): pass #socket permissions: if mmap_group.lower() in TRUE_OPTIONS: #when using the mmap group option, use '660' sperms = 0o660 else: #parse octal mode given as config option: try: if isinstance(socket_permissions, int): sperms = socket_permissions else: #assume octal string: sperms = int(socket_permissions, 8) assert 0 <= sperms <= 0o777, "invalid socket permission value %s" % oct( sperms) except ValueError: raise ValueError("invalid socket permissions " + "(must be an octal number): '%s'" % socket_permissions) #now try to create all the sockets: for sockpath in sockpaths: #create it: try: sock, cleanup_socket = create_unix_domain_socket( sockpath, sperms) log.info("created unix domain socket '%s'", sockpath) defs.append( ("unix-domain", sock, sockpath, cleanup_socket)) except Exception as e: handle_socket_error(sockpath, sperms, e) del e except: for socktype, sock, sockpath, cleanup_socket in defs: try: cleanup_socket() except Exception as e: log.error("Error cleaning up socket %s:", sock) log.error(" %s", e) del e defs = [] raise return defs
def get_ssl_wrap_socket_context(cert=None, key=None, ca_certs=None, ca_data=None, protocol="TLSv1_2", client_verify_mode="optional", server_verify_mode="required", verify_flags="X509_STRICT", check_hostname=False, server_hostname=None, options="ALL,NO_COMPRESSION", ciphers="DEFAULT", server_side=True): if server_side and not cert: raise InitException("you must specify an 'ssl-cert' file to use ssl sockets") if server_side: verify_mode = client_verify_mode else: verify_mode = server_verify_mode from xpra.log import Logger ssllog = Logger("ssl") ssllog("get_ssl_wrap_socket_context%s", (cert, key, ca_certs, ca_data, protocol, client_verify_mode, server_verify_mode, verify_flags, check_hostname, server_hostname, options, ciphers, server_side)) import ssl ssllog(" verify_mode for server_side=%s : %s", server_side, verify_mode) #ca-certs: if ca_certs=="default": ca_certs = None elif ca_certs=="auto": ca_certs = find_ssl_cert("ca-cert.pem") ssllog(" ca_certs=%s", ca_certs) #parse verify-mode: ssl_cert_reqs = getattr(ssl, "CERT_%s" % verify_mode.upper(), None) if ssl_cert_reqs is None: values = [k[len("CERT_"):].lower() for k in dir(ssl) if k.startswith("CERT_")] raise InitException("invalid ssl-server-verify-mode '%s', must be one of: %s" % (verify_mode, csv(values))) ssllog(" cert_reqs=%#x", ssl_cert_reqs) #parse protocol: proto = getattr(ssl, "PROTOCOL_%s" % (protocol.upper().replace("V", "v")), None) if proto is None: values = [k[len("PROTOCOL_"):] for k in dir(ssl) if k.startswith("PROTOCOL_")] raise InitException("invalid ssl-protocol '%s', must be one of: %s" % (protocol, csv(values))) ssllog(" protocol=%#x", proto) #ca_data may be hex encoded: ca_data = parse_encoded_bin_data(ca_data) ssllog(" cadata=%s", ellipsizer(ca_data)) kwargs = { "server_side" : server_side, "do_handshake_on_connect" : False, "suppress_ragged_eofs" : True, } #parse ssl-verify-flags as CSV: ssl_verify_flags = 0 for x in verify_flags.split(","): x = x.strip() if not x: continue v = getattr(ssl, "VERIFY_"+x.upper(), None) if v is None: raise InitException("invalid ssl verify-flag: %s" % x) ssl_verify_flags |= v ssllog(" verify_flags=%#x", ssl_verify_flags) #parse ssl-options as CSV: ssl_options = 0 for x in options.split(","): x = x.strip() if not x: continue v = getattr(ssl, "OP_"+x.upper(), None) if v is None: raise InitException("invalid ssl option: %s" % x) ssl_options |= v ssllog(" options=%#x", ssl_options) context = ssl.SSLContext(proto) context.set_ciphers(ciphers) context.verify_mode = ssl_cert_reqs context.verify_flags = ssl_verify_flags context.options = ssl_options ssllog(" cert=%s, key=%s", cert, key) if cert: if cert=="auto": #try to locate the cert file from known locations cert = find_ssl_cert() if not cert: raise InitException("failed to automatically locate an SSL certificate to use") SSL_KEY_PASSWORD = os.environ.get("XPRA_SSL_KEY_PASSWORD") ssllog("context.load_cert_chain%s", (cert or None, key or None, SSL_KEY_PASSWORD)) try: context.load_cert_chain(certfile=cert or None, keyfile=key or None, password=SSL_KEY_PASSWORD) except ssl.SSLError as e: ssllog("load_cert_chain", exc_info=True) raise InitException("SSL error, failed to load certificate chain: %s" % e) from e if ssl_cert_reqs!=ssl.CERT_NONE: if server_side: purpose = ssl.Purpose.CLIENT_AUTH #@UndefinedVariable else: purpose = ssl.Purpose.SERVER_AUTH #@UndefinedVariable context.check_hostname = check_hostname ssllog(" check_hostname=%s, server_hostname=%s", check_hostname, server_hostname) if context.check_hostname: if not server_hostname: raise InitException("ssl error: check-hostname is set but server-hostname is not") kwargs["server_hostname"] = server_hostname ssllog(" load_default_certs(%s)", purpose) context.load_default_certs(purpose) if not ca_certs or ca_certs.lower()=="default": ssllog(" using default certs") #load_default_certs already calls set_default_verify_paths() elif not os.path.exists(ca_certs): raise InitException("invalid ssl-ca-certs file or directory: %s" % ca_certs) elif os.path.isdir(ca_certs): ssllog(" loading ca certs from directory '%s'", ca_certs) context.load_verify_locations(capath=ca_certs) else: ssllog(" loading ca certs from file '%s'", ca_certs) assert os.path.isfile(ca_certs), "'%s' is not a valid ca file" % ca_certs context.load_verify_locations(cafile=ca_certs) #handle cadata: if ca_data: #PITA: because of a bug in the ssl module, we can't pass cadata, #so we use a temporary file instead: import tempfile with tempfile.NamedTemporaryFile(prefix='cadata') as f: ssllog(" loading cadata '%s'", ellipsizer(ca_data)) ssllog(" using temporary file '%s'", f.name) f.file.write(ca_data) f.file.flush() context.load_verify_locations(cafile=f.name) elif check_hostname and not server_side: ssllog("cannot check hostname client side with verify mode %s", verify_mode) return context, kwargs
def create_sockets(opts, error_cb, retry=0): bind_tcp = parse_bind_ip(opts.bind_tcp) bind_ssl = parse_bind_ip(opts.bind_ssl, 443) bind_ssh = parse_bind_ip(opts.bind_ssh, 22) bind_ws = parse_bind_ip(opts.bind_ws, 80) bind_wss = parse_bind_ip(opts.bind_wss, 443) bind_rfb = parse_bind_ip(opts.bind_rfb, 5900) bind_vsock = parse_bind_vsock(opts.bind_vsock) min_port = int(opts.min_port) # Initialize the TCP sockets before the display, # That way, errors won't make us kill the Xvfb # (which may not be ours to kill at that point) ssh_upgrades = opts.ssh_upgrade if ssh_upgrades: try: from xpra.net.ssh import nogssapi_context with nogssapi_context(): import paramiko assert paramiko except ImportError as e: from xpra.log import Logger sshlog = Logger("ssh") sshlog("import paramiko", exc_info=True) sshlog.error("Error: cannot enable SSH socket upgrades:") sshlog.error(" %s", e) ssh_upgrades = False log = get_network_logger() #prepare tcp socket definitions: tcp_defs = [] for socktype, defs in { "tcp" : bind_tcp, "ssl" : bind_ssl, "ssh" : bind_ssh, "ws" : bind_ws, "wss" : bind_wss, "rfb" : bind_rfb, }.items(): log("setting up %s sockets: %s", socktype, csv(defs.items())) for (host, iport), options in defs.items(): if iport!=0 and iport<min_port: error_cb("invalid %s port number %i (minimum value is %i)" % (socktype, iport, min_port)) for h in hosts(host): tcp_defs.append((socktype, h, iport, options, None)) sockets = {} for attempt in range(retry+1): if not tcp_defs: break try_list = tuple(tcp_defs) tcp_defs = [] for socktype, host, iport, options, _ in try_list: try: sock = setup_tcp_socket(host, iport, socktype) except Exception as e: log("setup_tcp_socket%s attempt=%s", (host, iport, options), attempt) tcp_defs.append((socktype, host, iport, options, e)) else: host, iport = sock[2] sockets[sock] = options if tcp_defs: sleep(1) if tcp_defs: #failed to create some sockets: for socktype, host, iport, options, exception in tcp_defs: log.error("Error creating %s socket", socktype) log.error(" on %s:%s", host, iport) log.error(" %s", exception) raise InitException("failed to create %s socket: %s" % (socktype, exception)) log("setting up vsock sockets: %s", csv(bind_vsock.items())) for (cid, iport), options in bind_vsock.items(): sock = setup_vsock_socket(cid, iport) sockets[sock] = options # systemd socket activation: if POSIX and not OSX: try: from xpra.platform.xposix.sd_listen import get_sd_listen_sockets except ImportError as e: log("no systemd socket activation: %s", e) else: sd_sockets = get_sd_listen_sockets() log("systemd sockets: %s", sd_sockets) for stype, sock, addr in sd_sockets: sock = setup_sd_listen_socket(stype, sock, addr) sockets[sock] = {} log("%s : %s", (stype, [addr]), sock) return sockets