def handle_socket_error(sockpath, sperms, e): log = get_network_logger() log("socket creation error", exc_info=True) if sockpath.startswith("/var/run/xpra") or sockpath.startswith("/run/xpra"): log.info("cannot create group socket '%s'", sockpath) log.info(" %s", e) dirname = sockpath[:sockpath.find("xpra")+len("xpra")] if not os.path.exists(dirname): log.info(" %s does not exist", dirname) #only show extra information if the socket permissions #would have been accessible by the group: elif POSIX and (sperms & 0o40): uid = getuid() username = get_username_for_uid(uid) groups = get_groups(username) log.info(" user '%s' is a member of groups: %s", username, csv(groups) or "no groups!") if "xpra" not in groups: log.info(" add 'xpra' group membership to enable group socket sharing") for x in path_permission_info(dirname): log.info(" %s", x) elif sockpath.startswith("/var/run/user") or sockpath.startswith("/run/user"): log.warn("Warning: cannot create socket '%s':", sockpath) log.warn(" %s", e) run_user = sockpath.split("/user")[0]+"/user" if not os.path.exists(run_user): log.warn(" %s does not exist", run_user) else: log.warn(" ($XDG_RUNTIME_DIR has not been created?)") else: log.error("Error: failed to create socket '%s':", sockpath) log.error(" %s", e) raise InitExit(EXIT_SOCKET_CREATION_ERROR, "failed to create socket %s" % sockpath)
def handle_socket_error(sockpath, e): log = get_network_logger() log("socket creation error", exc_info=True) if sockpath.startswith("/var/run/xpra") or sockpath.startswith( "/run/xpra"): log.warn("Warning: cannot create socket '%s'", sockpath) log.warn(" %s", e) dirname = sockpath[:sockpath.find("xpra") + len("xpra")] if not os.path.exists(dirname): log.warn(" %s does not exist", dirname) if POSIX: uid = getuid() username = get_username_for_uid(uid) groups = get_groups(username) log.warn(" user '%s' is a member of groups: %s", username, csv(groups) or "no groups!") if "xpra" not in groups: log.warn(" missing 'xpra' group membership?") for x in path_permission_info(dirname): log.warn(" %s", x) elif sockpath.startswith("/var/run/user") or sockpath.startswith( "/run/user"): log.warn("Warning: cannot create socket '%s':", sockpath) log.warn(" %s", e) run_user = sockpath.split("/user")[0] + "/user" if not os.path.exists(run_user): log.warn(" %s does not exist", run_user) else: log.warn(" ($XDG_RUNTIME_DIR has not been created?)") else: log.error("Error: failed to create socket '%s':", sockpath) log.error(" %s", e) raise InitException("failed to create socket %s" % sockpath)
def xpra_group(): if POSIX: try: username = os.getgroups() groups = get_groups(username) if MMAP_GROUP in groups: group_id = get_group_id(MMAP_GROUP) if group_id >= 0: return group_id except Exception: log("xpra_group()", exc_info=True) return 0
def create_unix_domain_socket(sockpath, socket_permissions=0o600): #convert this to a umask! umask = 0o777 - socket_permissions listener = socket.socket(socket.AF_UNIX) listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #bind the socket, using umask to set the correct permissions orig_umask = os.umask(umask) try: listener.bind(sockpath) finally: os.umask(orig_umask) try: inode = os.stat(sockpath).st_ino except: inode = -1 #set to the "xpra" group if we are a member of it, or if running as root: uid = getuid() username = get_username_for_uid(uid) groups = get_groups(username) if uid == 0 or GROUP in groups: group_id = get_group_id(GROUP) if group_id >= 0: try: os.lchown(sockpath, -1, group_id) except Exception as e: log = get_network_logger() log.warn("Warning: failed to set '%s' group ownership", GROUP) log.warn(" on socket '%s':", sockpath) log.warn(" %s", e) #don't know why this doesn't work: #os.fchown(listener.fileno(), -1, group_id) def cleanup_socket(): log = get_network_logger() try: cur_inode = os.stat(sockpath).st_ino except: log.info("socket '%s' already deleted", sockpath) return delpath = sockpath log("cleanup_socket '%s', original inode=%s, new inode=%s", sockpath, inode, cur_inode) if cur_inode == inode: log.info("removing socket %s", delpath) try: os.unlink(delpath) except: pass return listener, cleanup_socket
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 create_unix_domain_socket(sockpath, mmap_group=False, socket_permissions="600"): if mmap_group: #when using the mmap group option, use '660' umask = 0o117 else: #parse octal mode given as config option: try: if type(socket_permissions) == int: sperms = socket_permissions else: #assume octal string: sperms = int(socket_permissions, 8) assert sperms >= 0 and sperms <= 0o777 except ValueError: raise ValueError( "invalid socket permissions (must be an octal number): '%s'" % socket_permissions) #now convert this to a umask! umask = 0o777 - sperms listener = socket.socket(socket.AF_UNIX) listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #bind the socket, using umask to set the correct permissions orig_umask = os.umask(umask) try: listener.bind(sockpath) finally: os.umask(orig_umask) try: inode = os.stat(sockpath).st_ino except: inode = -1 #set to the "xpra" group if we are a member of it, or if running as root: uid = getuid() username = get_username_for_uid(uid) groups = get_groups(username) if uid == 0 or GROUP in groups: group_id = get_group_id(GROUP) if group_id >= 0: try: os.lchown(sockpath, -1, group_id) except Exception as e: log = get_network_logger() log.warn("Warning: failed to set '%s' group ownership", GROUP) log.warn(" on socket '%s':", sockpath) log.warn(" %s", e) #don't know why this doesn't work: #os.fchown(listener.fileno(), -1, group_id) def cleanup_socket(): log = get_network_logger() try: cur_inode = os.stat(sockpath).st_ino except: log.info("socket '%s' already deleted", sockpath) return delpath = sockpath log("cleanup_socket '%s', original inode=%s, new inode=%s", sockpath, inode, cur_inode) if cur_inode == inode: log.info("removing socket %s", delpath) try: os.unlink(delpath) except: pass return listener, cleanup_socket
def start_proxy(self, client_proto, c, auth_caps): def disconnect(reason, *extras): log("disconnect(%s, %s)", reason, extras) self.send_disconnect(client_proto, reason, *extras) #find the target server session: if not client_proto.authenticator: log.error("Error: the proxy server requires an authentication mode,") try: log.error(" client connection '%s' does not specify one", client_proto._conn.socktype) except: pass log.error(" use 'none' to disable authentication") disconnect(SESSION_NOT_FOUND, "no sessions found") return try: sessions = client_proto.authenticator.get_sessions() except Exception as e: authlog("failed to get the list of sessions", exc_info=True) authlog.error("Error: failed to get the list of sessions using '%s' authenticator", client_proto.authenticator) authlog.error(" %s", e) disconnect(AUTHENTICATION_ERROR, "cannot access sessions") return authlog("start_proxy(%s, {..}, %s) found sessions: %s", client_proto, auth_caps, sessions) if sessions is None: disconnect(SESSION_NOT_FOUND, "no sessions found") return uid, gid, displays, env_options, session_options = sessions if os.name=="posix": if uid==0 or gid==0: log.error("Error: proxy instances should not run as root") log.error(" use a different uid and gid (ie: nobody)") disconnect(AUTHENTICATION_ERROR, "cannot run proxy instances as root") return username = get_username_for_uid(uid) groups = get_groups(username) if "xpra" not in groups: log.error("Error: user '%s' (uid=%i) is not in the xpra group", username, uid) log.error(" it belongs to: %s", csv(groups) or None) disconnect(PERMISSION_ERROR, "user missing 'xpra' group membership") return #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) opts = make_defaults_struct() display = None proc = None sns = c.dictget("start-new-session") authlog("start_proxy: displays=%s, start-new-session=%s", displays, bool(sns)) if len(displays)==0 or sns: if self._start_sessions: #start a new session mode = sns.get("mode", "start") assert mode in ("start", "start-desktop", "shadow"), "invalid start-new-session mode '%s'" % mode sns = typedict(sns) display = sns.get("display") args = [] if display: args = [display] start = sns.strlistget("start") start_child = sns.strlistget("start-child") exit_with_children = sns.boolget("exit-with-children") exit_with_client = sns.boolget("exit-with-client") log("starting new server subprocess: mode=%s, socket-dir=%s, socket-dirs=%s, start=%s, start-child=%s, exit-with-children=%s, exit-with-client=%s, uid=%s, gid=%s", mode, opts.socket_dir, opts.socket_dirs, start, start_child, exit_with_children, exit_with_client, uid, gid) try: proc, socket_path = start_server_subprocess(sys.argv[0], args, mode, opts, opts.socket_dir, opts.socket_dirs, start, start_child, exit_with_children, exit_with_client, uid=uid, gid=gid) display = "socket:%s" % socket_path 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 proc: self.child_reaper.add_process(proc, "server-%s" % display, "xpra start", True, True) else: disconnect(SESSION_NOT_FOUND, "no displays found") return if display is None: display = c.strget("display") authlog("start_proxy: 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] def stop_server_subprocess(): if proc: 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.username = client_proto.authenticator.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:") log.error(" %s", e) 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.authenticator, 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, 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 start_proxy(self, client_proto, c, auth_caps): def disconnect(reason, *extras): log("disconnect(%s, %s)", reason, extras) self.send_disconnect(client_proto, reason, *extras) #find the target server session: if not client_proto.authenticator: log.error( "Error: the proxy server requires an authentication mode,") try: log.error(" client connection '%s' does not specify one", client_proto._conn.socktype) except: pass log.error(" use 'none' to disable authentication") disconnect(SESSION_NOT_FOUND, "no sessions found") return try: sessions = client_proto.authenticator.get_sessions() except Exception as e: authlog("failed to get the list of sessions", exc_info=True) authlog.error( "Error: failed to get the list of sessions using '%s' authenticator", client_proto.authenticator) authlog.error(" %s", e) disconnect(AUTHENTICATION_ERROR, "cannot access sessions") return authlog("start_proxy(%s, {..}, %s) found sessions: %s", client_proto, auth_caps, sessions) if sessions is None: disconnect(SESSION_NOT_FOUND, "no sessions found") return uid, gid, displays, env_options, session_options = sessions if os.name == "posix": if uid == 0 or gid == 0: log.error("Error: proxy instances should not run as root") log.error(" use a different uid and gid (ie: nobody)") disconnect(AUTHENTICATION_ERROR, "cannot run proxy instances as root") return username = get_username_for_uid(uid) groups = get_groups(username) if "xpra" not in groups: log("user '%s' (uid=%i) is not in the xpra group", username, uid) log(" it belongs to: %s", csv(groups) or None) #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) opts = make_defaults_struct() display = None proc = None sns = c.dictget("start-new-session") authlog("start_proxy: displays=%s, start-new-session=%s", displays, bool(sns)) if len(displays) == 0 or sns: if self._start_sessions: #start a new session mode = sns.get("mode", "start") assert mode in ( "start", "start-desktop", "shadow"), "invalid start-new-session mode '%s'" % mode sns = typedict(sns) display = sns.get("display") args = [] if display: args = [display] start = sns.strlistget("start") start_child = sns.strlistget("start-child") exit_with_children = sns.boolget("exit-with-children") exit_with_client = sns.boolget("exit-with-client") log( "starting new server subprocess: mode=%s, socket-dir=%s, socket-dirs=%s, start=%s, start-child=%s, exit-with-children=%s, exit-with-client=%s, uid=%s, gid=%s", mode, opts.socket_dir, opts.socket_dirs, start, start_child, exit_with_children, exit_with_client, uid, gid) try: proc, socket_path = start_server_subprocess( sys.argv[0], args, mode, opts, opts.socket_dir, opts.socket_dirs, start, start_child, exit_with_children, exit_with_client, uid=uid, gid=gid) display = "socket:%s" % socket_path 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 proc: self.child_reaper.add_process(proc, "server-%s" % display, "xpra start", True, True) else: disconnect(SESSION_NOT_FOUND, "no displays found") return if display is None: display = c.strget("display") authlog( "start_proxy: 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] def stop_server_subprocess(): if proc: 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.username = client_proto.authenticator.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:") log.error(" %s", e) 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.authenticator, 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, 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)