def __init__(self): dotxpra = DotXpra() display = os.environ.get("DISPLAY") from xpra.scripts.config import make_defaults_struct opts = make_defaults_struct() target = dotxpra.socket_path(display) log.info("attempting to connect to socket: %s", target) sock = socket.socket(socket.AF_UNIX) sock.connect(target) conn = SocketConnection(sock, sock.getsockname(), sock.getpeername(), target, "scroll-test") log.info("successfully created our socket connection: %s", conn) self.server = ServerMessenger(conn, opts) self.vscroll_events = deque(maxlen=1000) self.hscroll_events = deque(maxlen=1000) browser = WebBrowser() #hook some events: browser.content_tabs.connect("focus-view-title-changed", self.title_changed) vscroll_listeners.append(self.vscroll) hscroll_listeners.append(self.hscroll) #the things we tune: self.quality = -1 self.speed = -1 self.encoding = None self.strict = False
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.warn( "You already have a proxy server running at %s, the control socket will not be created!", sockpath) return False 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 get_sessions(self): uid = self.get_uid() gid = self.get_gid() log("%s.get_sessions() uid=%i, gid=%i", self, uid, gid) try: sockdir = DotXpra(socket_dir, socket_dirs, actual_username=self.username, uid=uid, gid=gid) results = sockdir.sockets(check_uid=uid) displays = [] for state, display in results: if state == DotXpra.LIVE and display not in displays: displays.append(display) log("sockdir=%s, results=%s, displays=%s", sockdir, results, displays) except Exception as e: log.error("Error: cannot get socket directory for '%s':", self.username) log.error(" %s", e) displays = [] v = uid, gid, displays, {}, {} log("%s.get_sessions()=%s", self, v) return v
def __init__(self, options, title="Xpra Session Browser"): gtk.Window.__init__(self) self.exit_code = 0 self.set_title(title) self.set_border_width(20) self.set_resizable(True) self.set_decorated(True) icon = self.get_pixbuf("xpra") if icon: self.set_icon(icon) add_close_accel(self, self.quit) self.connect("delete_event", self.quit) self.child_reaper = getChildReaper() self.vbox = gtk.VBox(False, 20) self.add(self.vbox) title_label = gtk.Label(title) title_label.modify_font(pango.FontDescription("sans 14")) self.vbox.add(title_label) self.warning = gtk.Label(" ") red = color_parse("red") self.warning.modify_fg(STATE_NORMAL, red) self.vbox.add(self.warning) hbox = gtk.HBox(False, 10) al = gtk.Alignment(xalign=1, yalign=0.5) al.add(gtk.Label("Password:"******"" #log.info("options=%s (%s)", options, type(options)) self.local_info_cache = {} self.dotxpra = DotXpra(options.socket_dir, options.socket_dirs, username) self.poll_local_sessions() glib.timeout_add(5 * 1000, self.poll_local_sessions) self.populate_table() import signal signal.signal(signal.SIGINT, self.app_signal) signal.signal(signal.SIGTERM, self.app_signal) self.show_all()
def __init__(self, opts): self.stdscr = None self.socket_dirs = opts.socket_dirs self.socket_dir = opts.socket_dir self.position = 0 self.selected_session = None self.exit_code = None self.subprocess_exit_code = None self.dotxpra = DotXpra(self.socket_dir, self.socket_dirs)
def guess_xpra_display(socket_dir, socket_dirs): dotxpra = DotXpra(socket_dir, socket_dirs) results = dotxpra.sockets() live = [display for state, display in results if state==DotXpra.LIVE] if len(live)==0: raise InitException("no existing xpra servers found") if len(live)>1: raise InitException("too many existing xpra servers found, cannot guess which one to use") return live[0]
def __init__(self, opts): self.stdscr = None self.socket_dirs = opts.socket_dirs self.socket_dir = opts.socket_dir self.position = 0 self.selected_session = None self.message = None self.exit_code = None self.dotxpra = DotXpra(self.socket_dir, self.socket_dirs) self.last_getch = 0 self.psprocess = {}
def get_sessions(self): uid = self.get_uid() gid = self.get_gid() try: sockdir = DotXpra(socket_dir, socket_dirs, actual_username=self.username) results = sockdir.sockets(check_uid=uid) displays = [display for state, display in results if state==DotXpra.LIVE] except Exception as e: log.error("Error: cannot get socket directory for '%s':", self.username) log.error(" %s", e) displays = [] return uid, gid, displays, {}, {}
def main(): import time import sys assert len(sys.argv)==2, "usage: %s :DISPLAY" % sys.argv[0] display = sys.argv[1] from xpra.scripts.config import make_defaults_struct opts = make_defaults_struct() from xpra.platform.dotxpra import DotXpra target = DotXpra().socket_path(display) print("will attempt to connect to socket: %s" % target) import socket sock = socket.socket(socket.AF_UNIX) sock.connect(target) from xpra.net.bytestreams import SocketConnection conn = SocketConnection(sock, sock.getsockname(), sock.getpeername(), target, "scroll-test") print("socket connection=%s" % conn) app = ServerMessenger(conn, opts) window = ScrolledWindowExample() vscroll_events = deque(maxlen=1000) hscroll_events = deque(maxlen=1000) def vscroll(scrollbar, scrolltype, value): #print("vscroll(%s) n=%s" % ((scrollbar, scrolltype, value), len(vscroll_events))) now = time.time() needs_reset = False if len(vscroll_events)>0: #get the previous event t, _ = vscroll_events[-1] #print("last vscroll event was %sms ago" % (now-t)) if now-t<1: #last event was less than a second ago print("lowering quality to jpeg @ 1%!") app.send("command_request", "encoding", "jpeg", "strict") app.send("command_request", "quality", 1, "*") app.send("command_request", "speed", 100, "*") needs_reset = True vscroll_events.append((now, value)) if needs_reset: def may_reset_quality(*args): #if no new events since, reset quality: t, _ = vscroll_events[-1] if now==t: print("resetting quality to h264") app.send("command_request", "encoding", "h264", "nostrict") app.send("command_request", "quality", -1, "*") #means auto app.send("command_request", "speed", -1, "*") #means auto gobject.timeout_add(1000, may_reset_quality) def hscroll(scrollbar, scrolltype, value): print("hscroll(%s)" % (scrollbar, scrolltype, value)) hscroll_events.append((time.time(), value)) window.vscroll.connect("change-value", vscroll) window.hscroll.connect("change-value", hscroll) try: app.run() finally: app.cleanup()
def test_DoS(client_class_constructor, args): """ utility method for running DoS tests See: test_DoS_*_client.py """ assert len(args) == 2, "usage: test_DoS_client :DISPLAY" log.enable_debug() opts = make_defaults_struct() opts.password_file = "" opts.encoding = "rgb24" opts.jpegquality = 70 opts.quality = 70 opts.compression_level = 1 opts.encryption = "" display = sys.argv[1] target = DotXpra().socket_path(display) print("will attempt to connect to socket: %s" % target) sock = socket.socket(socket.AF_UNIX) sock.connect(target) conn = SocketConnection(sock, sock.getsockname(), sock.getpeername(), target, "test_DoS") print("socket connection=%s" % conn) app = client_class_constructor(conn, opts) try: app.run() finally: app.cleanup() print("ended") print("")
def main(): print("main()") import gtk import signal from xpra.server.socket_util import create_unix_domain_socket from xpra.x11.vfb_util import start_Xvfb, check_xvfb_process from xpra.scripts.parsing import parse_cmdline from xpra.scripts.main import configure_logging from xpra.platform.dotxpra import DotXpra script_file = sys.argv[0] print("main() script_file=%s" % script_file) cmdline = sys.argv print("main() cmdline=%s" % cmdline) parser, opts, args, mode = parse_cmdline(cmdline) print("main() parser=%s" % parser) print("main() options=%s" % opts) print("main() mode=%s" % mode) display_name = args.pop(0) print("main() display=%s" % display_name) assert mode == "start", "only start mode is supported by this test server" configure_logging(opts, mode) dotxpra = DotXpra(opts.socket_dir) sockpath = dotxpra.socket_path(display_name) socket, cleanup_socket = create_unix_domain_socket(sockpath) sockets = [socket] xvfb = start_Xvfb(opts.xvfb, display_name) assert check_xvfb_process(xvfb), "xvfb error" from xpra.x11.bindings import posix_display_source #@UnusedImport from xpra.x11.bindings.window_bindings import X11WindowBindings #@UnresolvedImport X11Window = X11WindowBindings() assert X11Window try: app = UnrespondingServer() app.init(opts) app.init_sockets(sockets) register_os_signals(app.signal_quit) signal.signal(signal.SIGINT, app.signal_quit) return app.run() finally: for display in gtk.gdk.display_manager_get().list_displays(): display.close() xvfb.terminate() cleanup_socket()
def get_sessions(self): uid = self.get_uid() gid = self.get_gid() log("%s.get_sessions() uid=%i, gid=%i", self, uid, gid) try: sockdir = DotXpra(socket_dir, socket_dirs, actual_username=self.username, uid=uid, gid=gid) results = sockdir.sockets(check_uid=uid) displays = [] for state, display in results: if state==DotXpra.LIVE and display not in displays: displays.append(display) log("sockdir=%s, results=%s, displays=%s", sockdir, results, displays) except Exception as e: log.error("Error: cannot get socket directory for '%s':", self.username) log.error(" %s", e) displays = [] v = uid, gid, displays, {}, {} log("%s.get_sessions()=%s", self, v) return v
def setUpClass(cls): ProcessTestUtil.setUpClass() tmpdir = tempfile.gettempdir() cls.dotxpra = DotXpra(tmpdir, [tmpdir]) cls.default_xpra_args = ["--speaker=no", "--microphone=no"] if not WIN32: cls.default_xpra_args += ["--systemd-run=no", "--pulseaudio=no"] for x in cls.dotxpra._sockdirs: cls.default_xpra_args += ["--socket-dirs=%s" % x] cls.existing_displays = cls.displays()
def setUpClass(cls): from xpra.scripts.server import find_log_dir os.environ["XPRA_LOG_DIR"] = find_log_dir() cls.default_config = get_defaults() cls.display_start = 100 cls.dotxpra = DotXpra("/tmp", ["/tmp"]) cls.default_xpra_args = ["--systemd-run=no", "--pulseaudio=no", "--socket-dirs=/tmp", "--speaker=no", "--microphone=no"] ServerTestUtil.existing_displays = cls.displays() ServerTestUtil.processes = [] xpra_list = cls.run_xpra(["list"]) assert pollwait(xpra_list, 15) is not None, "xpra list returned %s" % xpra_list.poll()
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.warn("You already have a proxy server running at %s, the control socket will not be created!", sockpath) return False 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 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 setUpClass(cls): from xpra.server.server_util import find_log_dir cls.xauthority_temp = tempfile.NamedTemporaryFile(prefix="xpra-test.", suffix=".xauth", delete=False) cls.xauthority_temp.close() os.environ["XAUTHORITY"] = os.path.expanduser(cls.xauthority_temp.name) os.environ["XPRA_LOG_DIR"] = find_log_dir() os.environ["XPRA_NOTTY"] = "1" os.environ["XPRA_WAIT_FOR_INPUT"] = "0" os.environ["XPRA_FLATTEN_INFO"] = "0" os.environ["XPRA_NOTTY"] = "1" cls.default_env = os.environ.copy() cls.default_config = get_defaults() cls.display_start = 100+sys.version_info[0] cls.dotxpra = DotXpra("/tmp", ["/tmp"]) cls.default_xpra_args = ["--speaker=no", "--microphone=no"] if not WIN32: cls.default_xpra_args += ["--systemd-run=no", "--pulseaudio=no", "--socket-dirs=/tmp"] cls.existing_displays = cls.displays() cls.processes = []
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
class SessionsGUI(Gtk.Window): def __init__(self, options, title="Xpra Session Browser"): super().__init__() self.exit_code = 0 self.set_title(title) self.set_border_width(20) self.set_resizable(True) self.set_default_size(800, 220) self.set_decorated(True) self.set_size_request(800, 220) self.set_position(Gtk.WindowPosition.CENTER) self.set_wmclass("xpra-sessions-gui", "Xpra-Sessions-GUI") add_close_accel(self, self.quit) self.connect("delete_event", self.quit) icon = get_icon_pixbuf("browse.png") if icon: self.set_icon(icon) hb = Gtk.HeaderBar() hb.set_show_close_button(True) hb.props.title = "Xpra" button = Gtk.Button() icon = Gio.ThemedIcon(name="help-about") image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON) button.add(image) button.set_tooltip_text("About") button.connect("clicked", self.show_about) hb.add(button) hb.show_all() self.set_titlebar(hb) self.clients = {} self.clients_disconnecting = set() self.child_reaper = getChildReaper() self.vbox = Gtk.VBox(False, 20) self.add(self.vbox) title_label = Gtk.Label(title) title_label.modify_font(Pango.FontDescription("sans 14")) title_label.show() self.vbox.add(title_label) self.warning = Gtk.Label(" ") red = color_parse("red") self.warning.modify_fg(Gtk.StateType.NORMAL, red) self.warning.show() self.vbox.add(self.warning) self.password_box = Gtk.HBox(False, 10) self.password_label = Gtk.Label("Password:"******"" #log.info("options=%s (%s)", options, type(options)) self.local_info_cache = {} self.dotxpra = DotXpra(options.socket_dir, options.socket_dirs, username) self.poll_local_sessions() self.populate() GLib.timeout_add(5*1000, self.update) self.vbox.show() self.show() def show(self): #pylint: disable=arguments-differ super().show() def show(): force_focus() self.present() GLib.idle_add(show) def quit(self, *args): log("quit%s", args) self.do_quit() def do_quit(self): log("do_quit()") self.cleanup() Gtk.main_quit() def app_signal(self, signum): self.exit_code = 128 + signum log("app_signal(%s) exit_code=%i", signum, self.exit_code) self.do_quit() def cleanup(self): self.destroy() def show_about(self, *_args): from xpra.gtk_common.about import about about() def update(self): if self.poll_local_sessions(): self.populate() return True def populate(self): self.populate_table() def poll_local_sessions(self): #TODO: run in a thread so we don't block the UI thread! d = self.dotxpra.socket_details(matching_state=DotXpra.LIVE) log("poll_local_sessions() socket_details=%s", d) info_cache = {} for d, details in d.items(): log("poll_local_sessions() %s : %s", d, details) for state, display, sockpath in details: assert state==DotXpra.LIVE key = (display, sockpath) info = self.local_info_cache.get(key) if not info: #try to query it try: info = self.get_session_info(sockpath) if not info: log(" no data for '%s'", sockpath) continue if info.get("session-type")=="client": log(" skipped client socket '%s': %s", sockpath, info) continue except Exception as e: log("get_session_info(%s)", sockpath, exc_info=True) log.error("Error querying session info for %s", sockpath) log.error(" %s", e) del e if not info: continue #log("info(%s)=%s", sockpath, repr_ellipsized(str(info))) info_cache[key] = info if WIN32: socktype = "namedpipe" else: socktype = "socket" def make_text(info): text = {"mode" : socktype} for k, name in { "platform" : "platform", "uuid" : "uuid", "display" : "display", "session-type" : "type", "session-name" : "name", }.items(): v = info.get(k) if v is not None: text[name] = v return text #first remove any records that are no longer found: for key in self.local_info_cache: if key not in info_cache: display, sockpath = key self.records = [(interface, protocol, name, stype, domain, host, address, port, text) for (interface, protocol, name, stype, domain, host, address, port, text) in self.records if (protocol!="socket" or domain!="local" or address!=sockpath)] #add the new ones: for key, info in info_cache.items(): if key not in self.local_info_cache: display, sockpath = key self.records.append(("", "socket", "", "", "local", socket.gethostname(), sockpath, 0, make_text(info))) log("poll_local_sessions() info_cache=%s", info_cache) changed = self.local_info_cache!=info_cache self.local_info_cache = info_cache return changed def get_session_info(self, sockpath): #the lazy way using a subprocess if WIN32: socktype = "namedpipe" else: socktype = "socket" cmd = get_nodock_command()+["id", "%s:%s" % (socktype, sockpath)] p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout = p.communicate()[0] log("get_sessions_info(%s) returncode(%s)=%s", sockpath, cmd, p.returncode) if p.returncode!=0: return None out = bytestostr(stdout) info = {} for line in out.splitlines(): parts = line.split("=", 1) if len(parts)==2: info[parts[0]] = parts[1] log("get_sessions_info(%s)=%s", sockpath, info) return info def populate_table(self): log("populate_table: %i records", len(self.records)) if self.table: self.vbox.remove(self.table) self.table = None if not self.records: self.table = Gtk.Label("No sessions found") self.vbox.add(self.table) self.table.show() self.set_size_request(440, 200) self.password_box.hide() return self.password_box.show() self.set_size_request(-1, -1) tb = TableBuilder(1, 6, False) labels = [Gtk.Label(x) for x in ( "Host", "Display", "Name", "Platform", "Type", "URI", "Connect", "Open in Browser", )] tb.add_row(*labels) self.table = tb.get_table() self.vbox.add(self.table) self.table.resize(1+len(self.records), 5) #group them by uuid d = {} session_names = {} for i, record in enumerate(self.records): interface, protocol, name, stype, domain, host, address, port, text = record td = typedict(text) log("populate_table: record[%i]=%s", i, record) uuid = td.strget("uuid", "") display = td.strget("display", "") if domain=="local" and host.endswith(".local"): host = host[:-len(".local")] if uuid: key = uuid else: key = (host.rstrip("."), display) log("populate_table: key[%i]=%s", i, key) d.setdefault(key, []).append((interface, protocol, name, stype, domain, host, address, port, text)) #older servers expose the "session-name" as "session": td = typedict(text) session_name = td.strget("name", "") or td.strget("session", "") if session_name: session_names[key] = session_name for key, recs in d.items(): if isinstance(key, tuple): host, display = key else: uuid = key host, platform, dtype = None, sys.platform, None #try to find a valid host name: hosts = [rec[5] for rec in recs if not rec[5].startswith("local")] if not hosts: hosts = [rec[5] for rec in recs] host = hosts[0] platform, dtype = None, None for rec in recs: td = typedict(rec[-1]) if not platform: platform = td.strget("platform", "") if not dtype: dtype = td.strget("type", "") title = uuid if display: title = display label = Gtk.Label(title) if uuid!=title: label.set_tooltip_text(uuid) #try to use an icon for the platform: platform_icon_name = self.get_platform_icon_name(platform) pwidget = None if platform_icon_name: pwidget = scaled_image(get_icon_pixbuf("%s.png" % platform_icon_name), 28) if pwidget: pwidget.set_tooltip_text(platform_icon_name) if not pwidget: pwidget = Gtk.Label(platform) w, c, b = self.make_connect_widgets(key, recs, address, port, display) session_name = session_names.get(key, "") tb.add_row(Gtk.Label(host), label, Gtk.Label(session_name), pwidget, Gtk.Label(dtype), w, c, b) self.table.show_all() def get_uri(self, password, interface, protocol, name, stype, domain, host, address, port, text): dstr = "" tt = typedict(text) display = tt.strget("display", "") username = tt.strget("username", "") mode = tt.strget("mode", "") if not mode: #guess the mode from the service name, #ie: "localhost.localdomain :2 (wss)" -> "wss" #ie: "localhost.localdomain :2 (ssh-2)" -> "ssh" pos = name.rfind("(") if name.endswith(")") and pos>0: mode = name[pos+1:-1].split("-")[0] if mode not in ("tcp", "ws", "wss", "ssl", "ssh"): return "" else: mode = "tcp" if display and display.startswith(":"): dstr = display[1:] #append interface to IPv6 host URI for link local addresses ("fe80:"): if interface and if_indextoname and address.lower().startswith("fe80:"): #ie: "fe80::c1:ac45:7351:ea69%eth1" address += "%%%s" % if_indextoname(interface) if username: if password: uri = "%s://%s:%s@%s" % (mode, username, password, address) else: uri = "%s://%s@%s" % (mode, username, address) else: uri = "%s://%s" % (mode, address) if port>0 and DEFAULT_PORTS.get(mode, 0)!=port: uri += ":%s" % port if protocol not in ("socket", "namedpipe"): uri += "/" if dstr: uri += "%s" % dstr return uri def attach(self, key, uri): self.warning.set_text("") cmd = get_xpra_command() + ["attach", uri] env = os.environ.copy() env["XPRA_NOTTY"] = "1" proc = subprocess.Popen(cmd, env=env) log("attach() Popen(%s)=%s", cmd, proc) def proc_exit(*args): log("proc_exit%s", args) c = proc.poll() if key in self.clients_disconnecting: self.clients_disconnecting.remove(key) elif c not in (0, None): self.warning.set_text(EXIT_STR.get(c, "exit code %s" % c).replace("_", " ")) client_proc = self.clients.pop(key, None) if client_proc: def update(): self.update() self.populate() GLib.idle_add(update) self.child_reaper.add_process(proc, "client-%s" % uri, cmd, True, True, proc_exit) self.clients[key] = proc self.populate() def browser_open(self, rec): import webbrowser password = self.password_entry.get_text() url = self.get_uri(password, *rec) if url.startswith("wss"): url = "https"+url[3:] else: assert url.startswith("ws") url = "http"+url[2:] #trim end of URL: #http://192.168.1.7:10000/10 -> http://192.168.1.7:10000/ url = url[:url.rfind("/")] webbrowser.open_new_tab(url) def make_connect_widgets(self, key, recs, address, port, display): d = {} proc = self.clients.get(key) if proc and proc.poll() is None: icon = get_icon_pixbuf("disconnected.png") def disconnect_client(btn): log("disconnect_client(%s) proc=%s", btn, proc) self.clients_disconnecting.add(key) proc.terminate() self.populate() btn = imagebutton("Disconnect", icon, clicked_callback=disconnect_client) return Gtk.Label("Already connected with pid=%i" % proc.pid), btn, Gtk.Label("") icon = get_icon_pixbuf("browser.png") bopen = imagebutton("Open", icon) icon = get_icon_pixbuf("connect.png") if len(recs)==1: #single record, single uri: rec = recs[0] uri = self.get_uri(None, *rec) bopen.set_sensitive(uri.startswith("ws")) def browser_open(*_args): self.browser_open(rec) bopen.connect("clicked", browser_open) d[uri] = rec def clicked(*_args): password = self.password_entry.get_text() uri = self.get_uri(password, *rec) self.attach(key, uri) btn = imagebutton("Connect", icon, clicked_callback=clicked) return Gtk.Label(uri), btn, bopen #multiple modes / uris uri_menu = Gtk.ComboBoxText() uri_menu.set_size_request(340, 48) #sort by protocol so TCP comes first order = {"socket" : 0, "ssh" : 1, "tcp" :2, "ssl" : 3, "ws" : 4, "wss" : 8} if WIN32: #on MS Windows, prefer ssh which has a GUI for accepting keys #and entering the password: order["ssh"] = 0 def cmp_key(v): text = v[-1] #the text record mode = (text or {}).get("mode", "") host = v[6] host_len = len(host) #log("cmp_key(%s) text=%s, mode=%s, host=%s, host_len=%s", v, text, mode, host, host_len) #prefer order (from mode), then shorter host string: return "%s-%s" % (order.get(mode, mode), host_len) srecs = sorted(recs, key=cmp_key) has_ws = False for rec in srecs: uri = self.get_uri(None, *rec) uri_menu.append_text(uri) d[uri] = rec if uri.startswith("ws"): has_ws = True def connect(*_args): uri = uri_menu.get_active_text() rec = d[uri] password = self.password_entry.get_text() uri = self.get_uri(password, *rec) self.attach(key, uri) uri_menu.set_active(0) btn = imagebutton("Connect", icon, clicked_callback=connect) def uri_changed(*_args): uri = uri_menu.get_active_text() ws = uri.startswith("ws") bopen.set_sensitive(ws) if ws: bopen.set_tooltip_text("") elif not has_ws: bopen.set_tooltip_text("no 'ws' or 'wss' URIs found") else: bopen.set_tooltip_text("select a 'ws' or 'wss' URI") uri_menu.connect("changed", uri_changed) uri_changed() def browser_open_option(*_args): uri = uri_menu.get_active_text() rec = d[uri] self.browser_open(rec) bopen.connect("clicked", browser_open_option) return uri_menu, btn, bopen def get_platform_icon_name(self, platform): for p,i in { "win32" : "win32", "darwin" : "osx", "linux" : "linux", "freebsd" : "freebsd", }.items(): if platform.startswith(p): return i return None
class TopClient: def __init__(self, opts): self.stdscr = None self.socket_dirs = opts.socket_dirs self.socket_dir = opts.socket_dir self.position = 0 self.selected_session = None self.message = None self.exit_code = None self.dotxpra = DotXpra(self.socket_dir, self.socket_dirs) self.last_getch = 0 self.psprocess = {} def run(self): self.setup() for signum in (signal.SIGINT, signal.SIGTERM): signal.signal(signum, self.signal_handler) self.update_loop() self.cleanup() return self.exit_code def signal_handler(self, signum, *_args): self.exit_code = 128 + signum def setup(self): self.stdscr = curses_init() curses.cbreak() def cleanup(self): scr = self.stdscr if scr: curses.nocbreak() scr.erase() curses_clean(scr) self.stdscr = None def update_loop(self): while self.exit_code is None: self.update_screen() elapsed = int(1000 * monotonic_time() - self.last_getch) delay = max(100, min(1000, 1000 - elapsed)) // 100 curses.halfdelay(delay) try: v = self.stdscr.getch() except Exception: v = -1 self.last_getch = int(1000 * monotonic_time()) if v in EXIT_KEYS: self.exit_code = 0 if v in SIGNAL_KEYS: self.exit_code = 128 + SIGNAL_KEYS[v] if v == 258: #down arrow self.position += 1 elif v == 259: #up arrow self.position = max(self.position - 1, 0) elif v == 10 and self.selected_session: self.show_selected_session() elif v in (ord("s"), ord("S")): self.run_subcommand("stop") elif v in (ord("a"), ord("A")): self.run_subcommand("attach") elif v in (ord("d"), ord("D")): self.run_subcommand("detach") def show_selected_session(self): #show this session: try: self.cleanup() env = os.environ.copy() #we only deal with local sessions, should be fast: env["XPRA_CONNECT_TIMEOUT"] = "3" proc = self.do_run_subcommand("top", env=env) if not proc: self.message = monotonic_time( ), "failed to execute subprocess", curses.color_pair(RED) return exit_code = proc.wait() txt = "top subprocess terminated" attr = 0 if exit_code != 0: attr = curses.color_pair(RED) txt += " with error code %i" % exit_code if exit_code in EXIT_STR: txt += " (%s)" % EXIT_STR.get(exit_code, "").replace( "_", " ") elif (exit_code - 128) in SIGNAMES: #pylint: disable=superfluous-parens txt += " (%s)" % SIGNAMES[exit_code - 128] self.message = monotonic_time(), txt, attr finally: self.setup() def run_subcommand(self, subcommand): return self.do_run_subcommand(subcommand, stdout=DEVNULL, stderr=DEVNULL) def do_run_subcommand(self, subcommand, **kwargs): cmd = get_nodock_command() + [subcommand, self.selected_session] try: return Popen(cmd, **kwargs) except Exception: return None def update_screen(self): self.stdscr.erase() try: self.do_update_screen() finally: self.stdscr.refresh() return True def do_update_screen(self): #c = self.stdscr.getch() #if c==curses.KEY_RESIZE: height, width = self.stdscr.getmaxyx() #log.info("update_screen() %ix%i", height, width) title = get_title() x = max(0, width // 2 - len(title) // 2) try: hpos = 0 self.stdscr.addstr(hpos, x, title, curses.A_BOLD) hpos += 1 if height <= hpos: return sd = self.dotxpra.socket_details() #group them by display instead of socket dir: displays = {} for sessions in sd.values(): for state, display, path in sessions: displays.setdefault(display, []).append((state, path)) self.stdscr.addstr( hpos, 0, "found %i display%s" % (len(displays), engs(displays))) self.position = min(len(displays), self.position) self.selected_session = None hpos += 1 if height <= hpos: return if self.message: ts, txt, attr = self.message if monotonic_time() - ts < 10: self.stdscr.addstr(hpos, 0, txt, attr) hpos += 1 if height <= hpos: return else: self.message = None n = len(displays) for i, (display, state_paths) in enumerate(displays.items()): if height <= hpos: return info = self.get_display_info(display, state_paths) l = len(info) if height <= hpos + l + 2: break self.box(1, hpos, width - 2, l + 2, open_top=i > 0, open_bottom=i < n - 1) hpos += 1 if i == self.position: self.selected_session = display attr = curses.A_REVERSE else: attr = 0 for s in info: if len(s) >= width - 4: s = s[:width - 6] + ".." s = s.ljust(width - 4) self.stdscr.addstr(hpos, 2, s, attr) hpos += 1 except Exception as e: curses_err(self.stdscr, e) def get_display_info(self, display, state_paths): info = [display] valid_path = None for state, path in state_paths: sinfo = "%40s : %s" % (path, state) if POSIX: from pwd import getpwuid from grp import getgrgid try: stat = os.stat(path) #if stat.st_uid!=os.getuid(): sinfo += " uid=%s" % getpwuid(stat.st_uid).pw_name #if stat.st_gid!=os.getgid(): sinfo += " gid=%s" % getgrgid(stat.st_gid).gr_name except Exception as e: sinfo += "(stat error: %s)" % e info.append(sinfo) if state == DotXpra.LIVE: valid_path = path if valid_path: d = self.get_display_id_info(valid_path) name = d.get("session-name") uuid = d.get("uuid") stype = d.get("session-type") error = d.get("error") if error: info[0] = "%s %s" % (display, error) else: info[0] = "%s %s" % (display, name) info.insert(1, "uuid=%s, type=%s" % (uuid, stype)) machine_id = d.get("machine-id") if machine_id is None or machine_id == get_machine_id(): try: pid = int(d.get("pid")) except (ValueError, TypeError): pass else: try: process = self.psprocess.get(pid) if not process: import psutil process = psutil.Process(pid) self.psprocess[pid] = process else: cpu = process.cpu_percent() info[0] += ", %i%% CPU" % (cpu) except Exception: pass return info def get_display_id_info(self, path): d = {} try: cmd = get_nodock_command() + ["id", "socket://%s" % path] proc = Popen(cmd, stdout=PIPE, stderr=PIPE) out, err = proc.communicate() for line in bytestostr(out or err).splitlines(): try: k, v = line.split("=", 1) d[k] = v except ValueError: continue return d except Exception as e: d["error"] = str(e) return d def box(self, x, y, w, h, open_top=False, open_bottom=False): if open_top: ul = curses.ACS_LTEE #@UndefinedVariable ur = curses.ACS_RTEE #@UndefinedVariable else: ul = curses.ACS_ULCORNER #@UndefinedVariable ur = curses.ACS_URCORNER #@UndefinedVariable if open_bottom: ll = curses.ACS_LTEE #@UndefinedVariable lr = curses.ACS_RTEE #@UndefinedVariable else: ll = curses.ACS_LLCORNER #@UndefinedVariable lr = curses.ACS_LRCORNER #@UndefinedVariable box(self.stdscr, x, y, w, h, ul, ur, ll, lr)
def do_run_server(error_cb, opts, mode, xpra_file, extra_args, desktop_display=None, progress_cb=None): assert mode in ( "start", "start-desktop", "upgrade", "upgrade-desktop", "shadow", "proxy", ) def _progress(i, msg): if progress_cb: progress_cb(i, msg) progress = _progress progress(10, "initializing environment") 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] use_display = parse_bool("use-display", opts.use_display) starting = mode == "start" starting_desktop = mode == "start-desktop" upgrading = mode == "upgrade" upgrading_desktop = mode == "upgrade-desktop" shadowing = mode == "shadow" proxying = mode == "proxy" if not proxying 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" has_child_arg = (opts.start_child or opts.start_child_on_connect or opts.start_child_after_connect or opts.start_child_on_last_client_exit) 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 has_child_arg, "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 #so change None ('auto') to False if opts.pulseaudio is None: 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 = "Main" 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 upgrading and not 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 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 has_child_arg: 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, source_env, ) 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) 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 clobber = int(upgrading or upgrading_desktop) * CLOBBER_UPGRADE | int( use_display or 0) * CLOBBER_USE_DISPLAY start_vfb = not (shadowing or proxying or clobber) xauth_data = None if start_vfb: xauth_data = get_hex_uuid() # 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) import datetime extra_expand = { "TIMESTAMP": datetime.datetime.now().strftime("%Y%m%d-%H%M%S") } log_to_file = opts.daemon or os.environ.get("XPRA_LOG_TO_FILE", "") == "1" if start_vfb or log_to_file: #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 log_to_file: 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, extra_expand) if os.path.exists(log_filename0) and not display_name.startswith("S"): #don't overwrite the log file just yet, #as we may still fail to start log_filename0 += ".new" logfd = open_log_file(log_filename0) if POSIX and ROOT and (uid > 0 or gid > 0): try: os.fchown(logfd, uid, gid) except OSError as e: noerr(stderr.write, "failed to chown the log file '%s'\n" % log_filename0) noerr(stderr.flush) stdout, stderr = redirect_std_to_log(logfd, *protected_fds) noerr( stderr.write, "Entering daemon mode; " + "any further errors will be reported to:\n" + (" %s\n" % log_filename0)) noerr(stderr.flush) os.environ["XPRA_SERVER_LOG"] = log_filename0 else: #server log does not exist: os.environ.pop("XPRA_SERVER_LOG", None) #warn early about this: if (starting or starting_desktop ) and desktop_display and opts.notifications and not opts.dbus_launch: print_DE_warnings() if start_vfb and opts.xvfb.find("Xephyr") >= 0 and opts.sync_xvfb <= 0: warn("Warning: using Xephyr as vfb") warn(" you should also enable the sync-xvfb option") warn(" to keep the Xephyr window updated") progress(10, "creating sockets") from xpra.net.socket_util import get_network_logger, setup_local_sockets, create_sockets sockets = create_sockets(opts, error_cb) sanitize_env() os.environ.update(source_env(opts.source)) 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 elif not start_vfb or opts.xvfb.find("Xephyr") < 0: os.environ.pop("DISPLAY", None) 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 and use_display is None: #use-display='auto' so we have to figure out #if we have to start the vfb or not: if not display_name: use_display = False else: progress(20, "connecting to the display") start_vfb = verify_display( None, display_name, log_errors=False, timeout=1) != 0 if start_vfb: progress(20, "starting a virtual display") 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, parse_resolution 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) vfb_geom = "" try: vfb_geom = parse_resolution(opts.resize_display) except Exception: pass xvfb, display_name, cleanups = start_Xvfb(opts.xvfb, vfb_geom, 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(timeout=0): return check_xvfb_process(xvfb, timeout=timeout, command=opts.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(timeout=0): #pylint: disable=unused-argument 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 not check_xvfb(1): noerr(stderr.write, "vfb failed to start, exiting\n") return EXIT_VFB_ERROR if WIN32 and os.environ.get("XPRA_LOG_FILENAME"): os.environ["XPRA_SERVER_LOG"] = os.environ["XPRA_LOG_FILENAME"] if opts.daemon: log_filename1 = osexpand( select_log_file(log_dir, opts.log_file, display_name), username, uid, gid, extra_expand) if log_filename0 != log_filename1: # we now have the correct log filename, so use it: try: os.rename(log_filename0, log_filename1) except (OSError, IOError): pass else: os.environ["XPRA_SERVER_LOG"] = 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(): noerr(stderr.write, "vfb failed to start, exiting\n") return EXIT_VFB_ERROR #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) if not proxying: if POSIX and not OSX: no_gtk() if starting or starting_desktop or shadowing: r = verify_display(xvfb, display_name, shadowing) if r: return r #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() progress(50, "creating local sockets") #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.update(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) progress(60, "initializing server") 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) progress(70, "initializing sockets") app.init_sockets(sockets) app.init_dbus(dbus_pid, dbus_env) if not shadowing and not proxying: app.init_display_pid(xvfb_pid) app.original_desktop_display = desktop_display del opts if not app.server_ready(): return 1 progress(80, "finalizing") 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: progress(100, "running") log("running %s", app.run) r = app.run() log("%s()=%s", app.run, r) except KeyboardInterrupt: log.info("stopping on KeyboardInterrupt") app.cleanup() return EXIT_OK except Exception: log.error("server error", exc_info=True) app.cleanup() return -128 else: if r > 0: r = 0 return r
def do_run_server(error_cb, opts, mode, xpra_file, extra_args, desktop_display=None): 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) 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 shadowing and is_Wayland(): warn("shadow servers do not support Wayland, switch to X11") 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 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 and not extra_args: display_name = guess_xpra_display(opts.socket_dir, opts.socket_dirs) else: if len(extra_args) > 1: error_cb( "too many extra arguments (%i): only expected a display number" % len(extra_args)) if len(extra_args) == 1: display_name = extra_args[0] if not shadowing and not proxying and not opts.use_display: display_name_check(display_name) else: if proxying: #find a free display number: dotxpra = DotXpra(opts.socket_dir, opts.socket_dirs) all_displays = dotxpra.sockets() #ie: [("LIVE", ":100"), ("LIVE", ":200"), ...] displays = [v[1] for v in all_displays] display_name = None for x in range(1000, 20000): v = ":%s" % x if v not in displays: display_name = v break if not display_name: error_cb( "you must specify a free virtual display name to use with the proxy server" ) elif opts.use_display: #only use automatic guess for xpra displays and not X11 displays: display_name = guess_xpra_display(opts.socket_dir, opts.socket_dirs) else: # We will try to find one automaticaly # Use the temporary magic value 'S' as marker: display_name = 'S' + str(os.getpid()) if not shadowing 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 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() log = get_util_logger() sockets, mdns_recs, wrap_socket_fn = create_sockets(opts, error_cb) 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 KeyError: 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, 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): 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 IOError: pass kill_display = None if not proxying: add_cleanup(close_gtk_display) if not proxying and not shadowing: def kill_display(): if xvfb_pid: kill_xvfb(xvfb_pid) add_cleanup(kill_display) 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 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: 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: from xpra.server.socket_util import get_network_logger, setup_local_sockets netlog = get_network_logger() 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) ssh_port = get_ssh_port() ssh_access = ssh_port > 0 and opts.ssh.lower().strip() not in FALSE_OPTIONS 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 and ssh_access: netlog("ssh %s:%s : %s", "", ssh_port, socket) add_mdns(mdns_recs, "ssh", "", ssh_port) def b(v): return str(v).lower() not in FALSE_OPTIONS #turn off some server mixins: from xpra.server import server_features impwarned = [] def impcheck(*modules): for mod in modules: try: __import__("xpra.%s" % mod, {}, {}, []) except ImportError: if mod not in impwarned: impwarned.append(mod) log = get_util_logger() log.warn("Warning: missing %s module", mod) return False return True server_features.notifications = opts.notifications and impcheck( "notifications") server_features.webcam = b(opts.webcam) and impcheck("codecs") server_features.clipboard = b(opts.clipboard) and impcheck("clipboard") server_features.audio = (b(opts.speaker) or b(opts.microphone)) and impcheck("sound") server_features.av_sync = server_features.audio and b(opts.av_sync) server_features.fileprint = b(opts.printing) or b(opts.file_transfer) server_features.mmap = b(opts.mmap) server_features.input_devices = not opts.readonly and impcheck("keyboard") server_features.commands = impcheck("server.control_command") server_features.dbus = opts.dbus_proxy and impcheck("dbus") server_features.encoding = impcheck("codecs") server_features.logging = b(opts.remote_logging) #server_features.network_state = ?? server_features.display = opts.windows server_features.windows = opts.windows and impcheck("codecs") server_features.rfb = b(opts.rfb_upgrade) and impcheck("server.rfb") kill_dbus = None if shadowing: app = make_shadow_server() elif proxying: app = make_proxy_server() else: if not check_xvfb(): return 1 assert starting or starting_desktop or upgrading from xpra.x11.gtk_x11.gdk_display_source import init_gdk_display_source, close_gdk_display_source init_gdk_display_source() insert_cleanup(close_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) 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 POSIX: #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_XDG_OPEN_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(" ssh connections will not be available") 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 if starting: #check for an existing window manager: from xpra.x11.gtk_x11.wm_check import wm_check if not wm_check(display, opts.wm_name, upgrading): return 1 log("XShape=%s", X11Window.displayHasXShape()) app = make_server(clobber) else: assert starting_desktop app = make_desktop_server() 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") return 0 except Exception: log.error("server error", exc_info=True) return -128 else: if r > 0: # Upgrading/exiting, so leave X and dbus servers running if kill_display: _cleanups.remove(kill_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") 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
class SessionsGUI(gtk.Window): def __init__(self, options, title="Xpra Session Browser"): gtk.Window.__init__(self) self.exit_code = 0 self.set_title(title) self.set_border_width(20) self.set_resizable(True) self.set_decorated(True) self.set_position(WIN_POS_CENTER) icon = self.get_pixbuf("xpra") if icon: self.set_icon(icon) add_close_accel(self, self.quit) self.connect("delete_event", self.quit) self.clients = {} self.clients_disconnecting = set() self.child_reaper = getChildReaper() self.vbox = gtk.VBox(False, 20) self.add(self.vbox) title_label = gtk.Label(title) title_label.modify_font(pango.FontDescription("sans 14")) title_label.show() self.vbox.add(title_label) self.warning = gtk.Label(" ") red = color_parse("red") self.warning.modify_fg(STATE_NORMAL, red) self.warning.show() self.vbox.add(self.warning) hbox = gtk.HBox(False, 10) self.password_label = gtk.Label("Password:"******"" #log.info("options=%s (%s)", options, type(options)) self.local_info_cache = {} self.dotxpra = DotXpra(options.socket_dir, options.socket_dirs, username) self.poll_local_sessions() self.populate() glib.timeout_add(5*1000, self.update) self.vbox.show() self.show() def quit(self, *args): log("quit%s", args) self.do_quit() def do_quit(self): log("do_quit()") gtk.main_quit() def app_signal(self, signum, frame): self.exit_code = 128 + signum log("app_signal(%s, %s) exit_code=%i", signum, frame, self.exit_code) self.do_quit() def update(self): if self.poll_local_sessions(): self.populate() return True def populate(self): if self.local_info_cache: self.password_entry.show() self.password_label.show() else: self.password_entry.hide() self.password_label.hide() self.populate_table() def poll_local_sessions(self): #TODO: run in a thread so we don't block the UI thread! d = self.dotxpra.socket_details(matching_state=DotXpra.LIVE) log("poll_local_sessions() socket_details=%s", d) info_cache = {} for d, details in d.items(): log("poll_local_sessions() %s : %s", d, details) for state, display, sockpath in details: assert state==DotXpra.LIVE key = (display, sockpath) info = self.local_info_cache.get(key) if not info: #try to query it try: info = self.get_session_info(sockpath) except Exception as e: log("get_session_info(%s)", sockpath, exc_info=True) log.error("Error querying session info for %s", sockpath) log.error(" %s", e) del e if not info: continue #log("info(%s)=%s", sockpath, repr_ellipsized(str(info))) info_cache[key] = info def make_text(info): text = {"mode" : "socket"} for k, name in { "platform" : "platform", "uuid" : "uuid", "display" : "display", "session-type" : "type", "session-name" : "name", }.items(): v = info.get(k) if v is not None: text[name] = v return text #first remove any records that are no longer found: for key in self.local_info_cache.keys(): if key not in info_cache: display, sockpath = key self.records = [(interface, protocol, name, stype, domain, host, address, port, text) for (interface, protocol, name, stype, domain, host, address, port, text) in self.records if (protocol!="socket" or domain!="local" or address!=sockpath)] #add the new ones: for key, info in info_cache.items(): if key not in self.local_info_cache: display, sockpath = key self.records.append(("", "socket", "", "", "local", socket.gethostname(), sockpath, 0, make_text(info))) log("poll_local_sessions() info_cache=%s", info_cache) changed = self.local_info_cache!=info_cache self.local_info_cache = info_cache return changed def get_session_info(self, sockpath): #the lazy way using a subprocess cmd = get_nodock_command()+["id", "socket:%s" % sockpath] p = subprocess.Popen(cmd, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, _ = p.communicate() log("get_sessions_info(%s) returncode(%s)=%s", sockpath, cmd, p.returncode) if p.returncode!=0: return None out = bytestostr(stdout) info = {} for line in out.splitlines(): parts = line.split("=", 1) if len(parts)==2: info[parts[0]] = parts[1] log("get_sessions_info(%s)=%s", sockpath, info) return info def populate_table(self): log("populate_table: %i records", len(self.records)) if self.table: self.vbox.remove(self.table) self.table = None if not self.records: self.table = gtk.Label("No sessions found") self.vbox.add(self.table) self.table.show() return tb = TableBuilder(1, 6, False) tb.add_row(gtk.Label("Host"), gtk.Label("Display"), gtk.Label("Name"), gtk.Label("Platform"), gtk.Label("Type"), gtk.Label("URI"), gtk.Label("Connect"), gtk.Label("Open in Browser")) self.table = tb.get_table() self.vbox.add(self.table) self.table.resize(1+len(self.records), 5) #group them by uuid d = OrderedDict() for i, record in enumerate(self.records): interface, protocol, name, stype, domain, host, address, port, text = record td = typedict(text) log("populate_table: record[%i]=%s", i, record) uuid = td.strget("uuid", "") display = td.strget("display", "") platform = td.strget("platform", "") dtype = td.strget("type", "") #older servers expose the "session-name" as "session": session_name = td.strget("name", "") or td.strget("session", "") if domain=="local" and host.endswith(".local"): host = host[:-len(".local")] key = (uuid, uuid or i, host, display, session_name, platform, dtype) log("populate_table: key[%i]=%s", i, key) d.setdefault(key, []).append((interface, protocol, name, stype, domain, host, address, port, text)) for key, recs in d.items(): if type(key)==tuple: uuid, _, host, display, name, platform, dtype = key else: display = key uuid, host, name, platform, dtype = None, None, "", sys.platform, None title = uuid if display: title = display label = gtk.Label(title) if uuid!=title: label.set_tooltip_text(uuid) #try to use an icon for the platform: platform_icon_name = self.get_platform_icon_name(platform) pwidget = None if platform_icon_name: pwidget = scaled_image(self.get_pixbuf("%s.png" % platform_icon_name), 28) if pwidget: pwidget.set_tooltip_text(platform_icon_name) if not pwidget: pwidget = gtk.Label(platform) w, c, b = self.make_connect_widgets(key, recs, address, port, display) tb.add_row(gtk.Label(host), label, gtk.Label(name), pwidget, gtk.Label(dtype), w, c, b) self.table.show_all() def get_uri(self, password, interface, protocol, name, stype, domain, host, address, port, text): dstr = "" tt = typedict(text) display = tt.strget("display", "") username = tt.strget("username", "") mode = tt.strget("mode", "") if display.startswith(":"): dstr = display[1:] #append interface to IPv6 host URI for link local addresses ("fe80:"): if interface and if_indextoname and address.lower().startswith("fe80:"): #ie: "fe80::c1:ac45:7351:ea69%eth1" address += "%%%s" % if_indextoname(interface) if username: if password: uri = "%s://%s:%s@%s" % (mode, username, password, address) else: uri = "%s://%s@%s" % (mode, username, address) else: uri = "%s://%s" % (mode, address) if port>0: uri += ":%s" % port if protocol not in ("socket", "namedpipe"): uri += "/" if dstr: uri += "%s" % dstr return uri def attach(self, key, uri): self.warning.set_text("") cmd = get_xpra_command() + ["attach", uri] proc = subprocess.Popen(cmd) log("attach() Popen(%s)=%s", cmd, proc) def proc_exit(*args): log("proc_exit%s", args) c = proc.poll() if key in self.clients_disconnecting: self.clients_disconnecting.remove(key) elif c not in (0, None): self.warning.set_text(EXIT_STR.get(c, "exit code %s" % c).replace("_", " ")) try: del self.clients[key] except: pass else: def update(): self.update() self.populate() glib.idle_add(update) self.child_reaper.add_process(proc, "client-%s" % uri, cmd, True, True, proc_exit) self.clients[key] = proc self.populate() def browser_open(self, rec): import webbrowser password = self.password_entry.get_text() url = self.get_uri(password, *rec) if url.startswith("wss"): url = "https"+url[3:] else: assert url.startswith("ws") url = "http"+url[2:] #trim end of URL: #http://192.168.1.7:10000/10 -> http://192.168.1.7:10000/ url = url[:url.rfind("/")] webbrowser.open_new_tab(url) def make_connect_widgets(self, key, recs, address, port, display): d = {} proc = self.clients.get(key) if proc and proc.poll() is None: icon = self.get_pixbuf("disconnected.png") def disconnect_client(btn): log("disconnect_client(%s) proc=%s", btn, proc) self.clients_disconnecting.add(key) proc.terminate() self.populate() btn = imagebutton("Disconnect", icon, clicked_callback=disconnect_client) return gtk.Label("Already connected with pid=%i" % proc.pid), btn, gtk.Label("") icon = self.get_pixbuf("browser.png") bopen = imagebutton("Open", icon) icon = self.get_pixbuf("connect.png") if len(recs)==1: #single record, single uri: rec = recs[0] uri = self.get_uri(None, *rec) bopen.set_sensitive(uri.startswith("ws")) def browser_open(*_args): self.browser_open(rec) bopen.connect("clicked", browser_open) d[uri] = rec def clicked(*_args): password = self.password_entry.get_text() uri = self.get_uri(password, *rec) self.attach(key, uri) btn = imagebutton("Connect", icon, clicked_callback=clicked) return gtk.Label(uri), btn, bopen #multiple modes / uris uri_menu = gtk.combo_box_new_text() uri_menu.set_size_request(340, 48) #sort by protocol so TCP comes first order = {"socket" : 0, "ssl" :2, "wss" : 3, "tcp" : 4, "ssh" : 6, "ws" : 8} if WIN32: #on MS Windows, prefer ssh which has a GUI for accepting keys #and entering the password: order["ssh"] = 0 def cmp_key(v): text = v[-1] #the text record mode = (text or {}).get("mode", "") host = v[6] host_len = len(host) #log("cmp_key(%s) text=%s, mode=%s, host=%s, host_len=%s", v, text, mode, host, host_len) #prefer order (from mode), then shorter host string: return "%s-%s" % (order.get(mode, mode), host_len) srecs = sorted(recs, key=cmp_key) for rec in srecs: uri = self.get_uri(None, *rec) uri_menu.append_text(uri) d[uri] = rec def connect(*_args): uri = uri_menu.get_active_text() rec = d[uri] password = self.password_entry.get_text() uri = self.get_uri(password, *rec) self.attach(key, uri) uri_menu.set_active(0) btn = imagebutton("Connect", icon, clicked_callback=connect) def uri_changed(*_args): uri = uri_menu.get_active_text() bopen.set_sensitive(uri.startswith("ws")) uri_menu.connect("changed", uri_changed) uri_changed() def browser_open_option(*_args): uri = uri_menu.get_active_text() rec = d[uri] self.browser_open(rec) bopen.connect("clicked", browser_open_option) return uri_menu, btn, bopen def get_platform_icon_name(self, platform): for p,i in { "win32" : "win32", "darwin" : "osx", "linux" : "linux", "freebsd" : "freebsd", }.items(): if platform.startswith(p): return i return None def get_pixbuf(self, icon_name): icon_filename = os.path.join(get_icon_dir(), icon_name) if os.path.exists(icon_filename): return pixbuf_new_from_file(icon_filename) return None
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_tcp, parse_bind_vsock bind_tcp = parse_bind_tcp(opts.bind_tcp) bind_ssl = parse_bind_tcp(opts.bind_ssl) 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 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 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 = os.path.abspath(opts.password_file) from xpra.server.server_util import daemonize daemonize() # if pam is present, try to create a new session: pam = None protected_fds = [] protected_env = {} 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) 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() xrd = create_runtime_dir(uid, gid) 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 = select_log_file(log_dir, opts.log_file, display_name) 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) stderr.write("Entering daemon mode; " + "any further errors will be reported to:\n" + (" %s\n" % log_filename0)) #warn early about this: if (starting or starting_desktop) and desktop_display: print_DE_warnings(desktop_display, opts.pulseaudio, opts.notifications, opts.dbus_launch) from xpra.log import Logger log = Logger("server") netlog = Logger("network") 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: need_ssl = True if opts.bind_tcp: if ssl_opt == "auto" and opts.ssl_cert: need_ssl = True elif ssl_opt == "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_vsock_socket, setup_local_sockets min_port = int(opts.min_port) netlog("setting up SSL sockets: %s", bind_ssl) for host, iport in bind_ssl: if iport < min_port: error_cb("invalid %s port number %i (minimum value is %i)" % (socktype, iport, min_port)) _, tcp_socket, host_port = setup_tcp_socket(host, iport, "SSL") socket = ("SSL", wrap_socket_fn(tcp_socket), host_port) sockets.append(socket) rec = "ssl", [(host, iport)] netlog("%s : %s", rec, socket) mdns_recs.append(rec) # 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) tcp_ssl = ssl_opt in TRUE_OPTIONS or (ssl_opt == "auto" and opts.ssl_cert) def add_tcp_mdns_rec(host, iport): rec = "tcp", [(host, iport)] netlog("%s : %s", rec, socket) mdns_recs.append(rec) if tcp_ssl: #SSL is also available on this TCP socket: rec = "ssl", [(host, iport)] netlog("%s : %s", rec, socket) mdns_recs.append(rec) netlog("setting up TCP sockets: %s", bind_tcp) for host, iport in bind_tcp: if iport < min_port: error_cb("invalid %s port number %i (minimum value is %i)" % (socktype, iport, min_port)) socket = setup_tcp_socket(host, iport) sockets.append(socket) add_tcp_mdns_rec(host, iport) # VSOCK: netlog("setting up vsock sockets: %s", bind_vsock) for cid, iport in bind_vsock: socket = setup_vsock_socket(cid, iport) sockets.append(socket) rec = "vsock", [("", iport)] netlog("%s : %s", rec, socket) mdns_recs.append(rec) # systemd socket activation: try: from xpra.server.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_tcp_mdns_rec(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) # Start the Xvfb server first to get the display_name if needed from xpra.server.vfb_util import start_Xvfb, check_xvfb_process, verify_display_ready, verify_gdk_display, set_initial_resolution odisplay_name = display_name xvfb = None xvfb_pid = None if start_vfb: assert not proxying and xauth_data pixel_depth = validate_pixel_depth(opts.pixel_depth) xvfb, display_name = start_Xvfb(opts.xvfb, pixel_depth, display_name, cwd, uid, gid, xauth_data) 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}) close_display = None if not proxying: def close_display(): # Close our display(s) first, so the server dying won't kill us. # (if gtk has been loaded) gtk_mod = sys.modules.get("gtk") if gtk_mod: for d in gtk_mod.gdk.display_manager_get().list_displays(): d.close() if xvfb_pid: log.info("killing xvfb with pid %s", xvfb_pid) try: os.kill(xvfb_pid, signal.SIGTERM) except OSError as e: log.info("failed to kill xvfb process with pid %s:", xvfb_pid) log.info(" %s", e) add_cleanup(close_display) if opts.daemon: def noerr(fn, *args): try: fn(*args) except: pass log_filename1 = select_log_file(log_dir, opts.log_file, display_name) 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_process(xvfb): #xvfb problem: exit now return 1 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 os.environ.update(protected_env) if opts.chdir: os.chdir(opts.chdir) display = None if not proxying: no_gtk() if POSIX and (starting or starting_desktop or shadowing): #check that we can access the X11 display: if not verify_display_ready(xvfb, display_name, shadowing): return 1 display = verify_gdk_display(display_name) if not display: return 1 import gtk #@Reimport assert gtk #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", (socktype, [sockpath]), socket) add_cleanup(cleanup_socket) if opts.mdns: ssh_port = get_ssh_port() rec = "ssh", [("", ssh_port)] netlog("%s : %s", rec, socket) if ssh_port and rec not in mdns_recs: mdns_recs.append(rec) kill_dbus = None if shadowing: from xpra.platform.shadow_server import ShadowServer app = ShadowServer() server_type_info = "shadow" elif proxying: from xpra.server.proxy.proxy_server import ProxyServer app = ProxyServer() server_type_info = "proxy" else: 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) if xvfb_pid is not None: #save the new pid (we should have one): save_xvfb_pid(xvfb_pid) if POSIX: 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) 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: 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 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) server_type_info = "xpra" else: assert starting_desktop from xpra.x11.desktop_server import XpraDesktopServer app = XpraDesktopServer() server_type_info = "xpra desktop" #publish mdns records: if opts.mdns: from xpra.os_util import strtobytes from xpra.platform.info import get_username from xpra.server.socket_util import mdns_publish mdns_info = { "display": display_name, "username": get_username(), "uuid": strtobytes(app.uuid), "platform": sys.platform, "type": { "xpra": "seamless", "xpra desktop": "desktop" }.get(server_type_info, server_type_info), } if opts.session_name: mdns_info["session"] = opts.session_name for mode, listen_on in mdns_recs: mdns_publish(display_name, mode, listen_on, mdns_info) 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.init_components(opts) 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", server_type_info, exc_info=True) log.error(str(e)) log.info("") return 1 #honour start child, html webserver, and setup child reaper if not proxying and not upgrading: if opts.exit_with_children: assert opts.start_child, "exit-with-children was specified but start-child is missing!" app.start_commands = opts.start app.start_child_commands = opts.start_child app.start_after_connect = opts.start_after_connect app.start_child_after_connect = opts.start_child_after_connect app.start_on_connect = opts.start_on_connect app.start_child_on_connect = opts.start_child_on_connect app.exec_start_commands() 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) e = app.run() log("%s()=%s", app.run, e) except KeyboardInterrupt: log.info("stopping on KeyboardInterrupt") e = 0 except Exception as e: log.error("server error", exc_info=True) e = -128 if e > 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.server_core import ServerCore if e == ServerCore.EXITING_CODE: log.info("exiting: not cleaning up Xvfb") else: log.info("upgrading: not cleaning up Xvfb") log("cleanups=%s", _cleanups) e = 0 return e
def setup_local_sockets(bind, socket_dir, socket_dirs, display_name, clobber, mmap_group="auto", socket_permissions="600", username="", uid=0, gid=0): log = get_network_logger() log("setup_local_sockets%s", (bind, socket_dir, socket_dirs, display_name, clobber, mmap_group, socket_permissions, username, uid, gid)) 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 InitExit( EXIT_SOCKET_CREATION_ERROR, "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 and not WIN32: display_name = normalize_local_display_name(display_name) defs = {} try: sockpaths = {} log("setup_local_sockets: bind=%s, dotxpra=%s", bind, dotxpra) for b in bind: if b in ("none", ""): continue parts = b.split(",") sockpath = parts[0] options = {} if len(parts) == 2: options = parse_simple_dict(parts[1]) if sockpath == "auto": assert display_name is not None for sockpath in dotxpra.norm_socket_paths(display_name): sockpaths[sockpath] = options log("sockpaths(%s)=%s (uid=%i, gid=%i)", display_name, sockpaths, uid, gid) else: sockpath = dotxpra.osexpand(sockpath) if os.path.isabs(sockpath): pass elif sockpath.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) else: sockpath = dotxpra.socket_path(sockpath) sockpaths[sockpath] = options assert sockpaths, "no socket paths to try for %s" % b #expand and remove duplicate paths: tmp = {} for tsp, options in sockpaths.items(): sockpath = dotxpra.osexpand(tsp) if sockpath in tmp: log.warn("Warning: skipping duplicate bind path %s", sockpath) continue tmp[sockpath] = options sockpaths = tmp log("sockpaths=%s", sockpaths) #create listeners: if WIN32: from xpra.platform.win32.namedpipes.listener import NamedPipeListener from xpra.platform.win32.dotxpra import PIPE_PATH for sockpath, options in sockpaths.items(): npl = NamedPipeListener(sockpath) ppath = sockpath if ppath.startswith(PIPE_PATH): ppath = ppath[len(PIPE_PATH):] log.info("created named pipe '%s'", ppath) defs[("named-pipe", npl, sockpath, npl.stop)] = options 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 InitExit( EXIT_SERVER_ALREADY_EXISTS, "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 d in ("/var/run/xpra", "/run/xpra"): #this is normally done by tmpfiles.d, #but we may need to do it ourselves in some cases: kwargs["mode"] = SOCKET_DIR_MODE xpra_gid = get_group_id(SOCKET_DIR_GROUP) if xpra_gid > 0: kwargs["gid"] = xpra_gid log("creating sockdir=%s, kwargs=%s" % (d, kwargs)) dotxpra.mksockdir(d, **kwargs) log("%s permission mask: %s", d, oct(os.stat(d).st_mode)) 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: 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: 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) from None #now try to create all the sockets: for sockpath, options in sockpaths.items(): #create it: try: sock, cleanup_socket = create_unix_domain_socket( sockpath, sperms) log.info("created unix domain socket '%s'", sockpath) defs[("unix-domain", sock, sockpath, cleanup_socket)] = options except Exception as e: handle_socket_error(sockpath, sperms, e) del e except Exception: for sock, cleanup_socket in defs.items(): try: cleanup_socket() except Exception as e: log.error("Error cleaning up socket %s:", sock) log.error(" %s", e) del e raise return defs
def __init__(self, options, title="Xpra Session Browser"): super().__init__() self.exit_code = 0 self.set_title(title) self.set_border_width(20) self.set_resizable(True) self.set_default_size(800, 220) self.set_decorated(True) self.set_size_request(800, 220) self.set_position(Gtk.WindowPosition.CENTER) self.set_wmclass("xpra-sessions-gui", "Xpra-Sessions-GUI") add_close_accel(self, self.quit) self.connect("delete_event", self.quit) icon = get_icon_pixbuf("browse.png") if icon: self.set_icon(icon) hb = Gtk.HeaderBar() hb.set_show_close_button(True) hb.props.title = "Xpra" button = Gtk.Button() icon = Gio.ThemedIcon(name="help-about") image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON) button.add(image) button.set_tooltip_text("About") button.connect("clicked", self.show_about) hb.add(button) hb.show_all() self.set_titlebar(hb) self.clients = {} self.clients_disconnecting = set() self.child_reaper = getChildReaper() self.vbox = Gtk.VBox(False, 20) self.add(self.vbox) title_label = Gtk.Label(title) title_label.modify_font(Pango.FontDescription("sans 14")) title_label.show() self.vbox.add(title_label) self.warning = Gtk.Label(" ") red = color_parse("red") self.warning.modify_fg(Gtk.StateType.NORMAL, red) self.warning.show() self.vbox.add(self.warning) self.password_box = Gtk.HBox(False, 10) self.password_label = Gtk.Label("Password:"******"" #log.info("options=%s (%s)", options, type(options)) self.local_info_cache = {} self.dotxpra = DotXpra(options.socket_dir, options.socket_dirs, username) self.poll_local_sessions() self.populate() GLib.timeout_add(5*1000, self.update) self.vbox.show() self.show()
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 = os.path.abspath(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 = select_log_file(log_dir, opts.log_file, display_name) 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: print_DE_warnings(desktop_display, opts.pulseaudio, opts.notifications, opts.dbus_launch) 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) netlog("setting up SSL sockets: %s", csv(bind_ssl)) for host, iport in bind_ssl: add_tcp_socket("ssl", 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) 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: 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() 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 = select_log_file(log_dir, opts.log_file, display_name) 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) 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 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 from xpra.x11.desktop_server import XpraDesktopServer app = XpraDesktopServer() app.init_virtual_devices(devices) #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": strtobytes(app.uuid), "platform": sys.platform, "type": app.session_type, } if opts.session_name: mdns_info["session"] = opts.session_name for mode, listen_on in mdns_recs.items(): mdns_publish(display_name, mode, listen_on, mdns_info) 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.init_components(opts) 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 #honour start child, html webserver, and setup child reaper if not proxying and not upgrading: if opts.exit_with_children: assert opts.start_child, "exit-with-children was specified but start-child is missing!" app.start_commands = opts.start app.start_child_commands = opts.start_child app.start_after_connect = opts.start_after_connect app.start_child_after_connect = opts.start_child_after_connect app.start_on_connect = opts.start_on_connect app.start_child_on_connect = opts.start_child_on_connect app.exec_start_commands() 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.server_core import ServerCore if r == ServerCore.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
class TopClient: def __init__(self, opts): self.stdscr = None self.socket_dirs = opts.socket_dirs self.socket_dir = opts.socket_dir self.position = 0 self.selected_session = None self.exit_code = None self.subprocess_exit_code = None self.dotxpra = DotXpra(self.socket_dir, self.socket_dirs) def run(self): self.stdscr = curses_init() for signum in (signal.SIGINT, signal.SIGTERM): signal.signal(signum, self.signal_handler) self.update_loop() self.cleanup() return self.exit_code def signal_handler(self, *_args): self.exit_code = 128 + signal.SIGINT def cleanup(self): curses_clean(self.stdscr) def update_loop(self): while self.exit_code is None: self.update_screen() curses.halfdelay(50) v = self.stdscr.getch() #print("v=%s" % (v,)) if v in EXIT_KEYS: self.exit_code = 0 elif v == 258: #down arrow self.position += 1 elif v == 259: #up arrow self.position = max(self.position - 1, 0) elif v == 10 and self.selected_session: #show this session: cmd = get_nodock_command() + ["top", self.selected_session] try: self.cleanup() proc = Popen(cmd) exit_code = proc.wait() #TODO: show exit code, especially if non-zero finally: self.stdscr = curses_init() elif v in (ord("s"), ord("S")): self.run_subcommand("stop") elif v in (ord("a"), ord("A")): self.run_subcommand("attach") elif v in (ord("d"), ord("D")): self.run_subcommand("detach") def run_subcommand(self, subcommand): cmd = get_nodock_command() + [subcommand, self.selected_session] try: Popen(cmd, stdout=DEVNULL, stderr=DEVNULL) except: pass def update_screen(self): self.stdscr.erase() try: self.do_update_screen() finally: self.stdscr.refresh() return True def do_update_screen(self): #c = self.stdscr.getch() #if c==curses.KEY_RESIZE: height, width = self.stdscr.getmaxyx() #log.info("update_screen() %ix%i", height, width) title = get_title() x = max(0, width // 2 - len(title) // 2) try: hpos = 0 self.stdscr.addstr(hpos, x, title, curses.A_BOLD) hpos += 1 if height <= hpos: return sd = self.dotxpra.socket_details() #group them by display instead of socket dir: displays = {} for sessions in sd.values(): for state, display, path in sessions: displays.setdefault(display, []).append((state, path)) self.stdscr.addstr( hpos, 0, "found %i display%s" % (len(displays), engs(displays))) self.position = min(len(displays), self.position) self.selected_session = None hpos += 1 if height <= hpos: return n = len(displays) for i, (display, state_paths) in enumerate(displays.items()): if height <= hpos: return info = self.get_display_info(display, state_paths) l = len(info) if height <= hpos + l + 2: break self.box(1, hpos, width - 2, l + 2, open_top=i > 0, open_bottom=i < n - 1) hpos += 1 if i == self.position: self.selected_session = display attr = curses.A_REVERSE else: attr = 0 for s in info: s = s.ljust(width - 4) self.stdscr.addstr(hpos, 2, s, attr) hpos += 1 except Exception as e: curses_err(self.stdscr, e) def get_display_info(self, display, state_paths): info = [display] valid_path = None for state, path in state_paths: sinfo = "%40s : %s" % (path, state) if POSIX: from pwd import getpwuid from grp import getgrgid try: stat = os.stat(path) #if stat.st_uid!=os.getuid(): sinfo += " uid=%s" % getpwuid(stat.st_uid).pw_name #if stat.st_gid!=os.getgid(): sinfo += " gid=%s" % getgrgid(stat.st_gid).gr_name except Exception as e: sinfo += "(stat error: %s)" % e info.append(sinfo) if state == DotXpra.LIVE: valid_path = path if valid_path: d = self.get_display_id_info(valid_path) name = d.get("session-name") uuid = d.get("uuid") stype = d.get("session-type") error = d.get("error") if error: info[0] = "%s %s" % (display, error) else: info[0] = "%s %s" % (display, name) info.insert(1, "uuid=%s, type=%s" % (uuid, stype)) return info def get_display_id_info(self, path): d = {} try: cmd = get_nodock_command() + ["id", "socket://%s" % path] proc = Popen(cmd, stdout=PIPE, stderr=PIPE) out, err = proc.communicate() for line in bytestostr(out or err).splitlines(): try: k, v = line.split("=", 1) d[k] = v except ValueError: continue return d except Exception as e: d["error"] = str(e) return d def box(self, x, y, w, h, open_top=False, open_bottom=False): if open_top: ul = curses.ACS_LTEE #@UndefinedVariable ur = curses.ACS_RTEE #@UndefinedVariable else: ul = curses.ACS_ULCORNER #@UndefinedVariable ur = curses.ACS_URCORNER #@UndefinedVariable if open_bottom: ll = curses.ACS_LTEE #@UndefinedVariable lr = curses.ACS_RTEE #@UndefinedVariable else: ll = curses.ACS_LLCORNER #@UndefinedVariable lr = curses.ACS_LRCORNER #@UndefinedVariable box(self.stdscr, x, y, w, h, ul, ur, ll, lr)