def run(self): log("ProxyProcess.run() pid=%s, uid=%s, gid=%s", os.getpid(), getuid(), getgid()) setuidgid(self.uid, self.gid) if self.env_options: #TODO: whitelist env update? os.environ.update(self.env_options) self.video_init() log.info("new proxy instance started") log.info(" for client %s", self.client_conn) log.info(" and server %s", self.server_conn) signal.signal(signal.SIGTERM, self.signal_quit) signal.signal(signal.SIGINT, self.signal_quit) log("registered signal handler %s", self.signal_quit) start_thread(self.server_message_queue, "server message queue") if not self.create_control_socket(): #TODO: should send a message to the client return self.control_socket_thread = start_thread(self.control_socket_loop, "control") self.main_queue = Queue() #setup protocol wrappers: self.server_packets = Queue(PROXY_QUEUE_SIZE) self.client_packets = Queue(PROXY_QUEUE_SIZE) self.client_protocol = Protocol(self, self.client_conn, self.process_client_packet, self.get_client_packet) self.client_protocol.restore_state(self.client_state) self.server_protocol = Protocol(self, self.server_conn, self.process_server_packet, self.get_server_packet) #server connection tweaks: self.server_protocol.large_packets.append("draw") self.server_protocol.large_packets.append("window-icon") self.server_protocol.large_packets.append("keymap-changed") self.server_protocol.large_packets.append("server-settings") if self.caps.boolget("file-transfer"): self.client_protocol.large_packets.append("send-file") self.client_protocol.large_packets.append("send-file-chunk") self.server_protocol.large_packets.append("send-file") self.server_protocol.large_packets.append("send-file-chunk") self.server_protocol.set_compression_level(self.session_options.get("compression_level", 0)) self.server_protocol.enable_default_encoder() self.lost_windows = set() self.encode_queue = Queue() self.encode_thread = start_thread(self.encode_loop, "encode") log("starting network threads") self.server_protocol.start() self.client_protocol.start() self.send_hello() self.timeout_add(VIDEO_TIMEOUT*1000, self.timeout_video_encoders) try: self.run_queue() except KeyboardInterrupt as e: self.stop(str(e)) finally: log("ProxyProcess.run() ending %s", os.getpid())
def add_user(filename, username, password, uid=getuid(), gid=getgid(), displays="", env_options="", session_options=""): sql = "INSERT INTO users(username, password, uid, gid, displays, env_options, session_options) VALUES(?, ?, ?, ?, ?, ?, ?)" return exec_database_sql_script( None, filename, sql, (username, password, uid, gid, displays, env_options, session_options))
def run(self): register_SIGUSR_signals(self.idle_add) client_protocol_class = get_client_protocol_class(self.client_conn.socktype) server_protocol_class = get_server_protocol_class(self.server_conn.socktype) self.client_protocol = client_protocol_class(self, self.client_conn, self.process_client_packet, self.get_client_packet) self.client_protocol.restore_state(self.client_state) self.server_protocol = server_protocol_class(self, self.server_conn, self.process_server_packet, self.get_server_packet) self.log_start() log("ProxyProcessProcess.run() pid=%s, uid=%s, gid=%s", os.getpid(), getuid(), getgid()) set_proc_title("Xpra Proxy Instance for %s" % self.server_conn) if POSIX and (getuid()!=self.uid or getgid()!=self.gid): #do we need a valid XDG_RUNTIME_DIR for the socket-dir? username = get_username_for_uid(self.uid) socket_dir = osexpand(self.socket_dir, username, self.uid, self.gid) if not os.path.exists(socket_dir): log("the socket directory '%s' does not exist, checking for $XDG_RUNTIME_DIR path", socket_dir) for prefix in ("/run/user/", "/var/run/user/"): if socket_dir.startswith(prefix): from xpra.scripts.server import create_runtime_dir xrd = os.path.join(prefix, str(self.uid)) #ie: /run/user/99 log("creating XDG_RUNTIME_DIR=%s for uid=%i, gid=%i", xrd, self.uid, self.gid) create_runtime_dir(xrd, self.uid, self.gid) break #change uid or gid: setuidgid(self.uid, self.gid) if self.env_options: #TODO: whitelist env update? os.environ.update(self.env_options) signal.signal(signal.SIGTERM, self.signal_quit) signal.signal(signal.SIGINT, self.signal_quit) log("registered signal handler %s", self.signal_quit) start_thread(self.server_message_queue, "server message queue") if not self.create_control_socket(): return self.control_socket_thread = start_thread(self.control_socket_loop, "control", daemon=True) self.main_queue = Queue() ProxyInstance.run(self) try: QueueScheduler.run(self) except KeyboardInterrupt as e: self.stop(None, str(e)) finally: log("ProxyProcess.run() ending %s", os.getpid())
def add_user(self, username, password, uid=getuid(), gid=getgid(), displays="", env_options="", session_options=""): sql = "INSERT INTO users(username, password, uid, gid, displays, env_options, session_options) "+\ "VALUES(%s, %s, %s, %s, %s, %s, %s)" % ((self.param,)*7) self.exec_database_sql_script(None, sql, (username, password, uid, gid, displays, env_options, session_options))
def test_posix_wrappers(self): if not POSIX: return assert isinstance(getuid(), int) assert isinstance(getgid(), int) def isstr(v): assert v assert isinstance(v, str) isstr(get_shell_for_uid(getuid())) isstr(get_username_for_uid(getuid())) isstr(get_home_for_uid(getuid())) assert not get_shell_for_uid(999999999)
def create_control_socket(self): assert self.socket_dir def stop(msg): self.stop(None, "cannot create the proxy control socket: %s" % msg) username = get_username_for_uid(self.uid) dotxpra = DotXpra(self.socket_dir, actual_username=username, uid=self.uid, gid=self.gid) sockname = ":proxy-%s" % os.getpid() sockpath = dotxpra.socket_path(sockname) log("%s.socket_path(%s)=%s", dotxpra, sockname, sockpath) state = dotxpra.get_server_state(sockpath) log( "create_control_socket: socket path='%s', uid=%i, gid=%i, state=%s", sockpath, getuid(), getgid(), state) if state in (DotXpra.LIVE, DotXpra.UNKNOWN, DotXpra.INACCESSIBLE): log.error("Error: you already have a proxy server running at '%s'", sockpath) log.error(" the control socket will not be created") stop("socket already exists") return False d = os.path.dirname(sockpath) try: dotxpra.mksockdir(d, SOCKET_DIR_MODE) except Exception as e: log.warn("Warning: failed to create socket directory '%s'", d) log.warn(" %s", e) try: sock, self.control_socket_cleanup = create_unix_domain_socket( sockpath, 0o600) sock.listen(5) except Exception as e: log("create_unix_domain_socket failed for '%s'", sockpath, exc_info=True) log.error("Error: failed to setup control socket '%s':", sockpath) handle_socket_error(sockpath, 0o600, e) stop(e) return False self.control_socket = sock self.control_socket_path = sockpath log.info("proxy instance now also available using unix domain socket:") log.info(" %s", self.control_socket_path) return True
def write_pidfile(pidfile, uid, gid): from xpra.log import Logger log = Logger("server") pidstr = str(os.getpid()) try: with open(pidfile, "w") as f: os.fchmod(f.fileno(), 0o600) f.write("%s\n" % pidstr) try: inode = os.fstat(f.fileno()).st_ino except: inode = -1 if POSIX and uid != getuid() or gid != getgid(): try: os.fchown(f.fileno(), uid, gid) except: pass log.info("wrote pid %s to '%s'", pidstr, pidfile) def cleanuppidfile(): #verify this is the right file! log("cleanuppidfile: inode=%i", inode) if inode > 0: try: i = os.stat(pidfile).st_ino log("cleanuppidfile: current inode=%i", i) if i != inode: return except: pass try: os.unlink(pidfile) except: pass add_cleanup(cleanuppidfile) except Exception as e: log.error("Error: failed to write pid %i to pidfile '%s':", os.getpid(), pidfile) log.error(" %s", e)
def get_info(self, proto, *_args): authenticated = proto and proto.authenticators if not authenticated: info = super().get_server_info() else: #only show more info if we have authenticated #as the user running the proxy server process: info = super().get_info(proto) sessions = () for authenticator in proto.authenticators: auth_sessions = authenticator.get_sessions() if auth_sessions: sessions = auth_sessions break if sessions: uid, gid = sessions[:2] if not POSIX or (uid == getuid() and gid == getgid()): self.reap() i = 0 instances = dict(self.instances) instances_info = {} for proxy_instance, v in instances.items(): isprocess, d, _ = v iinfo = { "display": d, "live": proxy_instance.is_alive(), } if isprocess: iinfo.update({ "pid": proxy_instance.pid, }) else: iinfo.update(proxy_instance.get_info()) instances_info[i] = iinfo i += 1 info["instances"] = instances_info info["proxies"] = len(instances) info.setdefault("server", {})["type"] = "Python/GLib/proxy" return info
def create_control_socket(self): assert self.socket_dir username = get_username_for_uid(self.uid) dotxpra = DotXpra(self.socket_dir, actual_username=username, uid=self.uid, gid=self.gid) sockpath = dotxpra.socket_path(":proxy-%s" % os.getpid()) state = dotxpra.get_server_state(sockpath) log( "create_control_socket: socket path='%s', uid=%i, gid=%i, state=%s", sockpath, getuid(), getgid(), state) if state in (DotXpra.LIVE, DotXpra.UNKNOWN): log.error("Error: you already have a proxy server running at '%s'", sockpath) log.error(" the control socket will not be created") return False 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) try: sock, self.control_socket_cleanup = create_unix_domain_socket( sockpath, None, 0o600) sock.listen(5) except Exception as e: log("create_unix_domain_socket failed for '%s'", sockpath, exc_info=True) log.error("Error: failed to setup control socket '%s':", sockpath) log.error(" %s", e) return False self.control_socket = sock self.control_socket_path = sockpath log.info("proxy instance now also available using unix domain socket:") log.info(" %s", self.control_socket_path) return True
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 proxy_session(self, client_proto, c, auth_caps, sessions): def disconnect(reason, *extras): log("disconnect(%s, %s)", reason, extras) self.send_disconnect(client_proto, reason, *extras) uid, gid, displays, env_options, session_options = sessions if POSIX: if getuid()==0: if uid==0 or gid==0: log.error("Error: proxy instances cannot run as root") log.error(" use a different uid and gid (ie: nobody)") disconnect(AUTHENTICATION_ERROR, "cannot run proxy instances as root") return else: uid = getuid() gid = getgid() username = get_username_for_uid(uid) groups = get_groups(username) log("username(%i)=%s, groups=%s", uid, username, groups) else: #the auth module recorded the username we authenticate against assert client_proto.authenticators for authenticator in client_proto.authenticators: username = getattr(authenticator, "username", "") if username: break #ensure we don't loop back to the proxy: proxy_virtual_display = os.environ.get("DISPLAY") if proxy_virtual_display in displays: displays.remove(proxy_virtual_display) #remove proxy instance virtual displays: displays = [x for x in displays if not x.startswith(":proxy-")] #log("unused options: %s, %s", env_options, session_options) proc = None socket_path = None display = None sns = c.dictget("start-new-session") authlog("proxy_session: displays=%s, start_sessions=%s, start-new-session=%s", displays, self._start_sessions, sns) if len(displays)==0 or sns: if not self._start_sessions: disconnect(SESSION_NOT_FOUND, "no displays found") return try: proc, socket_path, display = self.start_new_session(username, uid, gid, sns, displays) log("start_new_session%s=%s", (username, uid, gid, sns, displays), (proc, socket_path, display)) except Exception as e: log("start_server_subprocess failed", exc_info=True) log.error("Error: failed to start server subprocess:") log.error(" %s", e) disconnect(SERVER_ERROR, "failed to start a new session") return if display is None: display = c.strget("display") authlog("proxy_session: proxy-virtual-display=%s (ignored), user specified display=%s, found displays=%s", proxy_virtual_display, display, displays) if display==proxy_virtual_display: disconnect(SESSION_NOT_FOUND, "invalid display") return if display: if display not in displays: disconnect(SESSION_NOT_FOUND, "display '%s' not found" % display) return else: if len(displays)!=1: disconnect(SESSION_NOT_FOUND, "please specify a display, more than one is available: %s" % csv(displays)) return display = displays[0] connect = c.boolget("connect", True) #ConnectTestXpraClient doesn't want to connect to the real session either: ctr = c.strget("connect_test_request") log("connect=%s, connect_test_request=%s", connect, ctr) if not connect or ctr: log("proxy_session: not connecting to the session") hello = {"display" : display} if socket_path: hello["socket-path"] = socket_path #echo mode if present: mode = sns.get("mode") if mode: hello["mode"] = mode client_proto.send_now(("hello", hello)) return def stop_server_subprocess(): log("stop_server_subprocess() proc=%s", proc) if proc and proc.poll() is None: proc.terminate() log("start_proxy(%s, {..}, %s) using server display at: %s", client_proto, auth_caps, display) def parse_error(*args): stop_server_subprocess() disconnect(SESSION_NOT_FOUND, "invalid display string") log.warn("Error: parsing failed for display string '%s':", display) for arg in args: log.warn(" %s", arg) raise Exception("parse error on %s: %s" % (display, args)) opts = make_defaults_struct(username=username, uid=uid, gid=gid) opts.username = username disp_desc = parse_display_name(parse_error, opts, display) if uid or gid: disp_desc["uid"] = uid disp_desc["gid"] = gid log("display description(%s) = %s", display, disp_desc) try: server_conn = connect_to(disp_desc, opts) except Exception as e: log("cannot connect", exc_info=True) log.error("Error: cannot start proxy connection:") for x in str(e).splitlines(): log.error(" %s", x) log.error(" connection definition:") print_nested_dict(disp_desc, prefix=" ", lchar="*", pad=20, print_fn=log.error) disconnect(SESSION_NOT_FOUND, "failed to connect to display") stop_server_subprocess() return log("server connection=%s", server_conn) #no other packets should be arriving until the proxy instance responds to the initial hello packet def unexpected_packet(packet): if packet: log.warn("Warning: received an unexpected packet on the proxy connection %s:", client_proto) log.warn(" %s", repr_ellipsized(packet)) client_conn = client_proto.steal_connection(unexpected_packet) client_state = client_proto.save_state() cipher = None encryption_key = None if auth_caps: cipher = auth_caps.get("cipher") if cipher: encryption_key = self.get_encryption_key(client_proto.authenticators, client_proto.keyfile) log("start_proxy(..) client connection=%s", client_conn) log("start_proxy(..) client state=%s", client_state) #this may block, so run it in a thread: def do_start_proxy(): log("do_start_proxy()") message_queue = MQueue() try: ioe = client_proto.wait_for_io_threads_exit(5+self._socket_timeout) if not ioe: log.error("Error: some network IO threads have failed to terminate") return client_conn.set_active(True) process = ProxyInstanceProcess(uid, gid, env_options, session_options, self._socket_dir, self.video_encoders, self.csc_modules, client_conn, disp_desc, client_state, cipher, encryption_key, server_conn, c, message_queue) log("starting %s from pid=%s", process, os.getpid()) self.processes[process] = (display, message_queue) process.start() log("process started") popen = process._popen assert popen #when this process dies, run reap to update our list of proxy processes: self.child_reaper.add_process(popen, "xpra-proxy-%s" % display, "xpra-proxy-instance", True, True, self.reap) finally: #now we can close our handle on the connection: client_conn.close() server_conn.close() message_queue.put("socket-handover-complete") start_thread(do_start_proxy, "start_proxy(%s)" % client_conn)
def run(self): log("ProxyProcess.run() pid=%s, uid=%s, gid=%s", os.getpid(), getuid(), getgid()) setuidgid(self.uid, self.gid) if self.env_options: #TODO: whitelist env update? os.environ.update(self.env_options) self.video_init() log.info("new proxy instance started") log.info(" for client %s", self.client_conn) log.info(" and server %s", self.server_conn) signal.signal(signal.SIGTERM, self.signal_quit) signal.signal(signal.SIGINT, self.signal_quit) log("registered signal handler %s", self.signal_quit) start_thread(self.server_message_queue, "server message queue") if not self.create_control_socket(): #TODO: should send a message to the client return self.control_socket_thread = start_thread(self.control_socket_loop, "control") self.main_queue = Queue() #setup protocol wrappers: self.server_packets = Queue(PROXY_QUEUE_SIZE) self.client_packets = Queue(PROXY_QUEUE_SIZE) self.client_protocol = Protocol(self, self.client_conn, self.process_client_packet, self.get_client_packet) self.client_protocol.restore_state(self.client_state) self.server_protocol = Protocol(self, self.server_conn, self.process_server_packet, self.get_server_packet) #server connection tweaks: self.server_protocol.large_packets.append("input-devices") self.server_protocol.large_packets.append("draw") self.server_protocol.large_packets.append("window-icon") self.server_protocol.large_packets.append("keymap-changed") self.server_protocol.large_packets.append("server-settings") if self.caps.boolget("file-transfer"): self.client_protocol.large_packets.append("send-file") self.client_protocol.large_packets.append("send-file-chunk") self.server_protocol.large_packets.append("send-file") self.server_protocol.large_packets.append("send-file-chunk") self.server_protocol.set_compression_level(self.session_options.get("compression_level", 0)) self.server_protocol.enable_default_encoder() self.lost_windows = set() self.encode_queue = Queue() self.encode_thread = start_thread(self.encode_loop, "encode") log("starting network threads") self.server_protocol.start() self.client_protocol.start() self.send_hello() self.timeout_add(VIDEO_TIMEOUT*1000, self.timeout_video_encoders) try: self.run_queue() except KeyboardInterrupt as e: self.stop(str(e)) finally: log("ProxyProcess.run() ending %s", os.getpid())
def create_control_socket(self): assert self.socket_dir dotxpra = DotXpra(self.socket_dir) sockpath = dotxpra.socket_path(":proxy-%s" % os.getpid()) state = dotxpra.get_server_state(sockpath) if state in (DotXpra.LIVE, DotXpra.UNKNOWN): log.error("Error: you already have a proxy server running at '%s'", sockpath) log.error(" the control socket will not be created") return False log("create_control_socket: socket path='%s', uid=%i, gid=%i", sockpath, getuid(), getgid()) try: sock, self.control_socket_cleanup = create_unix_domain_socket(sockpath, None, 0o600) sock.listen(5) except Exception as e: log("create_unix_domain_socket failed for '%s'", sockpath, exc_info=True) log.error("Error: failed to setup control socket '%s':", sockpath) log.error(" %s", e) return False self.control_socket = sock self.control_socket_path = sockpath log.info("proxy instance now also available using unix domain socket:") log.info(" %s", self.control_socket_path) return True
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) etc_prefix = os.environ.get("XPRA_INSTALL_PREFIX", "") if etc_prefix.endswith("/usr"): etc_prefix = etc_prefix[:-4] subs.update({ "DISPLAY" : display_name, "XPRA_LOG_DIR" : pathexpand(os.environ.get("XPRA_LOG_DIR")), "XORG_CONFIG_PREFIX" : os.environ.get("XORG_CONFIG_PREFIX", etc_prefix), }) #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 run(self): log("ProxyProcess.run() pid=%s, uid=%s, gid=%s", os.getpid(), getuid(), getgid()) self.setproctitle("Xpra Proxy Instance for %s" % self.server_conn) if POSIX and (os.getuid() != self.uid or os.getgid() != self.gid): #do we need a valid XDG_RUNTIME_DIR for the socket-dir? username = get_username_for_uid(self.uid) socket_dir = osexpand(self.socket_dir, username, self.uid, self.gid) if not os.path.exists(socket_dir): log( "the socket directory '%s' does not exist, checking for $XDG_RUNTIME_DIR path", socket_dir) for prefix in ("/run/user/", "/var/run/user/"): if socket_dir.startswith(prefix): from xpra.scripts.server import create_runtime_dir xrd = os.path.join(prefix, str(self.uid)) #ie: /run/user/99 log("creating XDG_RUNTIME_DIR=%s for uid=%i, gid=%i", xrd, self.uid, self.gid) create_runtime_dir(xrd, self.uid, self.gid) break #change uid or gid: setuidgid(self.uid, self.gid) if self.env_options: #TODO: whitelist env update? os.environ.update(self.env_options) self.video_init() log.info("new proxy instance started") log.info(" for client %s", self.client_conn) log.info(" and server %s", self.server_conn) signal.signal(signal.SIGTERM, self.signal_quit) signal.signal(signal.SIGINT, self.signal_quit) log("registered signal handler %s", self.signal_quit) start_thread(self.server_message_queue, "server message queue") if not self.create_control_socket(): #TODO: should send a message to the client return self.control_socket_thread = start_thread(self.control_socket_loop, "control") self.main_queue = Queue() #setup protocol wrappers: self.server_packets = Queue(PROXY_QUEUE_SIZE) self.client_packets = Queue(PROXY_QUEUE_SIZE) self.client_protocol = Protocol(self, self.client_conn, self.process_client_packet, self.get_client_packet) self.client_protocol.restore_state(self.client_state) self.server_protocol = Protocol(self, self.server_conn, self.process_server_packet, self.get_server_packet) #server connection tweaks: for x in (b"input-devices", b"draw", b"window-icon", b"keymap-changed", b"server-settings"): self.server_protocol.large_packets.append(x) if self.caps.boolget("file-transfer"): for x in (b"send-file", b"send-file-chunk"): self.server_protocol.large_packets.append(x) self.client_protocol.large_packets.append(x) self.server_protocol.set_compression_level( self.session_options.get("compression_level", 0)) self.server_protocol.enable_default_encoder() self.lost_windows = set() self.encode_queue = Queue() self.encode_thread = start_thread(self.encode_loop, "encode") log("starting network threads") self.server_protocol.start() self.client_protocol.start() self.send_hello() self.timeout_add(VIDEO_TIMEOUT * 1000, self.timeout_video_encoders) try: self.run_queue() except KeyboardInterrupt as e: self.stop(str(e)) finally: log("ProxyProcess.run() ending %s", os.getpid())
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