1# This file is part of Xpra. 2# Copyright (C) 2013-2020 Antoine Martin <antoine@xpra.org> 3# Copyright (C) 2008 Nathaniel Smith <njs@pobox.com> 4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any 5# later version. See the file COPYING for details. 6 7import os 8import sys 9import time 10from time import monotonic 11from multiprocessing import Queue as MQueue, freeze_support #@UnresolvedImport 12from gi.repository import GLib 13 14from xpra.util import ( 15 LOGIN_TIMEOUT, AUTHENTICATION_ERROR, SESSION_NOT_FOUND, SERVER_ERROR, 16 repr_ellipsized, print_nested_dict, csv, envfloat, envbool, envint, typedict, 17 ) 18from xpra.os_util import ( 19 get_username_for_uid, get_groups, get_home_for_uid, bytestostr, 20 getuid, getgid, WIN32, POSIX, OSX, 21 umask_context, get_group_id, 22 ) 23from xpra.net.socket_util import SOCKET_DIR_MODE, SOCKET_DIR_GROUP 24from xpra.server.server_core import ServerCore 25from xpra.server.control_command import ArgsControlCommand, ControlError 26from xpra.child_reaper import getChildReaper 27from xpra.scripts.parsing import parse_bool 28from xpra.scripts.config import make_defaults_struct, PROXY_START_OVERRIDABLE_OPTIONS, OPTION_TYPES 29from xpra.scripts.main import parse_display_name, connect_to, start_server_subprocess 30from xpra.make_thread import start_thread 31from xpra.log import Logger 32 33log = Logger("proxy") 34authlog = Logger("proxy", "auth") 35 36freeze_support() 37 38 39PROXY_SOCKET_TIMEOUT = envfloat("XPRA_PROXY_SOCKET_TIMEOUT", "0.1") 40PROXY_WS_TIMEOUT = envfloat("XPRA_PROXY_WS_TIMEOUT", "1.0") 41assert PROXY_SOCKET_TIMEOUT>0, "invalid proxy socket timeout" 42CAN_STOP_PROXY = envbool("XPRA_CAN_STOP_PROXY", getuid()!=0 or WIN32) 43STOP_PROXY_SOCKET_TYPES = os.environ.get("XPRA_STOP_PROXY_SOCKET_TYPES", "unix-domain,named-pipe").split(",") 44STOP_PROXY_AUTH_SOCKET_TYPES = os.environ.get("XPRA_STOP_PROXY_AUTH_SOCKET_TYPES", "unix-domain").split(",") 45#something (a thread lock?) doesn't allow us to use multiprocessing on MS Windows: 46PROXY_INSTANCE_THREADED = envbool("XPRA_PROXY_INSTANCE_THREADED", WIN32) 47PROXY_CLEANUP_GRACE_PERIOD = envfloat("XPRA_PROXY_CLEANUP_GRACE_PERIOD", "0.5") 48 49MAX_CONCURRENT_CONNECTIONS = envint("XPRA_PROXY_MAX_CONCURRENT_CONNECTIONS", 200) 50if WIN32: 51 #DEFAULT_ENV_WHITELIST = "ALLUSERSPROFILE,APPDATA,COMMONPROGRAMFILES,COMMONPROGRAMFILES(X86),COMMONPROGRAMW6432,COMPUTERNAME,COMSPEC,FP_NO_HOST_CHECK,LOCALAPPDATA,NUMBER_OF_PROCESSORS,OS,PATH,PATHEXT,PROCESSOR_ARCHITECTURE,PROCESSOR_ARCHITECTURE,PROCESSOR_IDENTIFIER,PROCESSOR_LEVEL,PROCESSOR_REVISION,PROGRAMDATA,PROGRAMFILES,PROGRAMFILES(X86),PROGRAMW6432,PSMODULEPATH,PUBLIC,SYSTEMDRIVE,SYSTEMROOT,TEMP,TMP,USERDOMAIN,WORKGROUP,USERNAME,USERPROFILE,WINDIR,XPRA_REDIRECT_OUTPUT,XPRA_LOG_FILENAME,XPRA_ALL_DEBUG" 52 DEFAULT_ENV_WHITELIST = "*" 53else: 54 DEFAULT_ENV_WHITELIST = "LANG,HOSTNAME,PWD,TERM,SHELL,SHLVL,PATH,USER,HOME" 55ENV_WHITELIST = os.environ.get("XPRA_PROXY_ENV_WHITELIST", DEFAULT_ENV_WHITELIST).split(",") 56 57 58def get_socktype(proto): 59 try: 60 return proto._conn.socktype 61 except AttributeError: 62 return "unknown" 63 64 65class ProxyServer(ServerCore): 66 """ 67 This is the proxy server you can launch with "xpra proxy", 68 once authenticated, it will dispatch the connection 69 to the session found using the authenticator's 70 get_sessions() function. 71 """ 72 73 def __init__(self): 74 log("ProxyServer.__init__()") 75 super().__init__() 76 self._max_connections = MAX_CONCURRENT_CONNECTIONS 77 self._start_sessions = False 78 self.session_type = "proxy" 79 self.main_loop = None 80 #proxy servers may have to connect to remote servers, 81 #or even start them, so allow more time before timing out: 82 self._accept_timeout += 10 83 self.pings = 0 84 self.video_encoders = () 85 self._start_sessions = False 86 #keep track of the proxy process instances 87 #the display they're on and the message queue we can 88 # use to communicate with them 89 self.instances = {} 90 #connections used exclusively for requests: 91 self._requests = set() 92 self.idle_add = GLib.idle_add 93 self.timeout_add = GLib.timeout_add 94 self.source_remove = GLib.source_remove 95 self._socket_timeout = PROXY_SOCKET_TIMEOUT 96 self._ws_timeout = PROXY_WS_TIMEOUT 97 98 def init(self, opts): 99 log("ProxyServer.init(%s)", opts) 100 self.pings = int(opts.pings) 101 self.video_encoders = opts.proxy_video_encoders 102 self._start_sessions = opts.proxy_start_sessions 103 super().init(opts) 104 #ensure we cache the platform info before intercepting SIGCHLD 105 #as this will cause a fork and SIGCHLD to be emitted: 106 from xpra.version_util import get_platform_info 107 get_platform_info() 108 self.child_reaper = getChildReaper() 109 self.create_system_dir(opts.system_proxy_socket) 110 111 def create_system_dir(self, sps): 112 if not POSIX or OSX or not sps: 113 return 114 xpra_group_id = get_group_id(SOCKET_DIR_GROUP) 115 if sps.startswith("/run/xpra") or sps.startswith("/var/run/xpra"): 116 #create the directory and verify its permissions 117 #which should have been set correctly by tmpfiles.d, 118 #but may have been set wrong if created by systemd's socket activation instead 119 d = sps.split("/xpra")[0]+"/xpra" 120 try: 121 if os.path.exists(d): 122 stat = os.stat(d) 123 mode = stat.st_mode 124 if (mode & SOCKET_DIR_MODE)!=SOCKET_DIR_MODE: 125 log.warn("Warning: invalid permissions on '%s' : %s", d, oct(mode)) 126 mode = mode | SOCKET_DIR_MODE 127 log.warn(" changing to %s", oct(mode)) 128 os.chmod(d, mode) 129 if xpra_group_id>=0 and stat.st_gid!=xpra_group_id: 130 import grp 131 group = grp.getgrgid(stat.st_gid)[0] 132 log.warn("Warning: invalid group on '%s': %s", d, group) 133 log.warn(" changing to '%s'", SOCKET_DIR_GROUP) 134 os.lchown(d, stat.st_uid, xpra_group_id) 135 else: 136 log.info("creating '%s' with permissions %s and group '%s'", 137 d, oct(SOCKET_DIR_MODE), SOCKET_DIR_GROUP) 138 with umask_context(0): 139 os.mkdir(d, SOCKET_DIR_MODE) 140 stat = os.stat(d) 141 if xpra_group_id>=0 and stat.st_gid!=xpra_group_id: 142 os.lchown(d, stat.st_uid, xpra_group_id) 143 mode = os.stat(d).st_mode 144 log("%s permissions: %s", d, oct(mode)) 145 except OSError as e: 146 log("create_system_dir()", exc_info=True) 147 log.error("Error: failed to create or change the permissions on '%s':", d) 148 log.error(" %s", e) 149 150 def init_control_commands(self): 151 super().init_control_commands() 152 self.control_commands["stop"] = ArgsControlCommand("stop", "stops the proxy instance on the given display", 153 self.handle_stop_command, min_args=1, max_args=1) 154 155 156 def install_signal_handlers(self, callback): 157 from xpra.gtk_common.gobject_compat import register_os_signals, register_SIGUSR_signals 158 register_os_signals(callback, "Proxy Server") 159 register_SIGUSR_signals("Proxy Server") 160 161 162 def make_dbus_server(self): 163 from xpra.server.proxy.proxy_dbus_server import Proxy_DBUS_Server 164 return Proxy_DBUS_Server(self) 165 166 167 def init_packet_handlers(self): 168 super().init_packet_handlers() 169 #add shutdown handler 170 self._default_packet_handlers["shutdown-server"] = self._process_proxy_shutdown_server 171 172 def _process_proxy_shutdown_server(self, proto, _packet): 173 assert proto in self._requests 174 self.clean_quit(False) 175 176 177 def print_screen_info(self): 178 #no screen, we just use a virtual display number 179 pass 180 181 def get_server_mode(self): 182 return "proxy" 183 184 def init_aliases(self): 185 """ 186 It is a lot less confusing if proxy servers don't use packet aliases at all. 187 So we override the aliases initialization and skip it. 188 """ 189 190 def do_run(self): 191 self.main_loop = GLib.MainLoop() 192 self.main_loop.run() 193 194 def handle_stop_command(self, *args): 195 display = args[0] 196 log("stop command: will try to find proxy process for display %s", display) 197 for instance, v in dict(self.instances).items(): 198 _, disp, _ = v 199 if disp==display: 200 log.info("stop command: found matching process %s with pid %i for display %s", 201 instance, instance.pid, display) 202 self.stop_proxy(instance) 203 return "stopped proxy instance for display %s" % display 204 raise ControlError("no proxy found for display %s" % display) 205 206 def stop_all_proxies(self, force=False): 207 instances = self.instances 208 log("stop_all_proxies() will stop proxy instances: %s", instances) 209 for instance in tuple(instances.keys()): 210 self.stop_proxy(instance, force) 211 log("stop_all_proxies() done") 212 213 def stop_proxy(self, instance, force=False): 214 v = self.instances.get(instance) 215 if not v: 216 log.error("Error: proxy instance not found for %s", instance) 217 return 218 log("stop_proxy(%s) is_alive=%s", instance, instance.is_alive()) 219 if not instance.is_alive() and not force: 220 return 221 isprocess, _, mq = v 222 log("stop_proxy(%s) %s", instance, v) 223 #different ways of stopping for process vs threaded implementations: 224 if isprocess: 225 #send message: 226 if force: 227 instance.terminate() 228 else: 229 mq.put_nowait("stop") 230 try: 231 mq.close() 232 except Exception as e: 233 log("%s() %s", mq.close, e) 234 else: 235 #direct method call: 236 instance.stop(None, "proxy server request") 237 238 239 def cleanup(self): 240 self.stop_all_proxies() 241 super().cleanup() 242 start = monotonic() 243 live = True 244 log("cleanup() proxy instances: %s", self.instances) 245 while monotonic()-start<PROXY_CLEANUP_GRACE_PERIOD and live: 246 live = tuple(x for x in tuple(self.instances.keys()) if x.is_alive()) 247 if live: 248 log("cleanup() still %i proxies alive: %s", len(live), live) 249 time.sleep(0.1) 250 if live: 251 self.stop_all_proxies(True) 252 log("cleanup() frames remaining:") 253 from xpra.util import dump_all_frames 254 dump_all_frames(log) 255 256 257 def do_quit(self): 258 self.main_loop.quit() 259 #from now on, we can't rely on the main loop: 260 from xpra.os_util import register_SIGUSR_signals 261 register_SIGUSR_signals() 262 263 def log_closing_message(self): 264 log.info("Proxy Server process ended") 265 266 267 def verify_connection_accepted(self, protocol): 268 #if we start a proxy, the protocol will be closed 269 #(a new one is created in the proxy process) 270 if not protocol.is_closed(): 271 self.send_disconnect(protocol, LOGIN_TIMEOUT) 272 273 def hello_oked(self, proto, packet, c, auth_caps): 274 if super().hello_oked(proto, packet, c, auth_caps): 275 #already handled in superclass 276 return 277 self.accept_client(proto, c) 278 generic_request = c.strget("request") 279 def is_req(mode): 280 return generic_request==mode or c.boolget("%s_request" % mode) 281 for x in ("screenshot", "event", "print", "exit"): 282 if is_req(x): 283 self.send_disconnect(proto, "error: invalid request, '%s' is not supported by the proxy server" % x) 284 return 285 if is_req("stop"): 286 #global kill switch: 287 if not CAN_STOP_PROXY: 288 msg = "cannot stop proxy server" 289 log.warn("Warning: %s", msg) 290 self.send_disconnect(proto, msg) 291 return 292 #verify socket type (only local connections by default): 293 socktype = get_socktype(proto) 294 if socktype not in STOP_PROXY_SOCKET_TYPES: 295 msg = "cannot stop proxy server from a '%s' connection" % socktype 296 log.warn("Warning: %s", msg) 297 log.warn(" only from: %s", csv(STOP_PROXY_SOCKET_TYPES)) 298 self.send_disconnect(proto, msg) 299 return 300 #connection must be authenticated: 301 if socktype in STOP_PROXY_AUTH_SOCKET_TYPES and not proto.authenticators: 302 msg = "cannot stop proxy server from unauthenticated connections" 303 log.warn("Warning: %s", msg) 304 self.send_disconnect(proto, msg) 305 return 306 self._requests.add(proto) 307 #send a hello back and the client should then send its "shutdown-server" packet 308 capabilities = self.make_hello() 309 proto.send_now(("hello", capabilities)) 310 def force_exit_request_client(): 311 try: 312 self._requests.remove(proto) 313 except KeyError: 314 pass 315 if not proto.is_closed(): 316 self.send_disconnect(proto, "timeout") 317 self.timeout_add(10*1000, force_exit_request_client) 318 return 319 self.proxy_auth(proto, c, auth_caps) 320 321 def proxy_auth(self, client_proto, c, auth_caps): 322 def disconnect(reason, *extras): 323 log("disconnect(%s, %s)", reason, extras) 324 self.send_disconnect(client_proto, reason, *extras) 325 326 #find the target server session: 327 if not client_proto.authenticators: 328 log.error("Error: the proxy server requires an authentication mode,") 329 try: 330 log.error(" client connection '%s' does not specify one", get_socktype(client_proto)) 331 except AttributeError: 332 pass 333 log.error(" use 'none' to disable authentication") 334 disconnect(SESSION_NOT_FOUND, "no sessions found") 335 return 336 sessions = None 337 authenticator = None 338 for authenticator in client_proto.authenticators: 339 try: 340 auth_sessions = authenticator.get_sessions() 341 authlog("proxy_auth %s.get_sessions()=%s", authenticator, auth_sessions) 342 if auth_sessions: 343 sessions = auth_sessions 344 break 345 except Exception as e: 346 authlog("failed to get the list of sessions from %s", authenticator, exc_info=True) 347 authlog.error("Error: failed to get the list of sessions using '%s' authenticator", authenticator) 348 authlog.error(" %s", e) 349 disconnect(AUTHENTICATION_ERROR, "cannot access sessions") 350 return 351 authlog("proxy_auth(%s, {..}, %s) found sessions: %s", client_proto, auth_caps, sessions) 352 if sessions is None: 353 disconnect(SESSION_NOT_FOUND, "no sessions found") 354 return 355 self.proxy_session(client_proto, c, auth_caps, sessions) 356 357 def proxy_session(self, client_proto, c, auth_caps, sessions): 358 def disconnect(reason, *extras): 359 log("disconnect(%s, %s)", reason, extras) 360 self.send_disconnect(client_proto, reason, *extras) 361 uid, gid, displays, env_options, session_options = sessions 362 if POSIX: 363 if getuid()==0: 364 if uid==0 or gid==0: 365 log.error("Error: proxy instances cannot run as root") 366 log.error(" use a different uid and gid (ie: nobody)") 367 disconnect(AUTHENTICATION_ERROR, "cannot run proxy instances as root") 368 return 369 else: 370 uid = getuid() 371 gid = getgid() 372 username = get_username_for_uid(uid) 373 password = None 374 groups = get_groups(username) 375 log("username(%i)=%s, groups=%s", uid, username, groups) 376 else: 377 #the auth module recorded the username we authenticate against 378 assert client_proto.authenticators 379 for authenticator in client_proto.authenticators: 380 username = getattr(authenticator, "username", "") 381 password = authenticator.get_password() 382 if username: 383 break 384 #ensure we don't loop back to the proxy: 385 proxy_virtual_display = os.environ.get("DISPLAY") 386 if proxy_virtual_display in displays: 387 displays.remove(proxy_virtual_display) 388 #remove proxy instance virtual displays: 389 displays = [x for x in displays if not x.startswith(":proxy-")] 390 #log("unused options: %s, %s", env_options, session_options) 391 proc = None 392 socket_path = None 393 display = None 394 sns = typedict(c.dictget("start-new-session", {})) 395 authlog("proxy_session: displays=%s, start_sessions=%s, start-new-session=%s", 396 displays, self._start_sessions, sns) 397 if not displays or sns: 398 if not self._start_sessions: 399 disconnect(SESSION_NOT_FOUND, "no displays found") 400 return 401 try: 402 proc, socket_path, display = self.start_new_session(username, password, uid, gid, sns, displays) 403 log("start_new_session%s=%s", (username, "..", uid, gid, sns, displays), (proc, socket_path, display)) 404 except Exception as e: 405 log("start_server_subprocess failed", exc_info=True) 406 log.error("Error: failed to start server subprocess:") 407 log.error(" %s", e) 408 disconnect(SERVER_ERROR, "failed to start a new session") 409 return 410 if display is None: 411 display = c.strget("display") 412 authlog("proxy_session: proxy-virtual-display=%s (ignored), user specified display=%s, found displays=%s", 413 proxy_virtual_display, display, displays) 414 if display==proxy_virtual_display: 415 disconnect(SESSION_NOT_FOUND, "invalid display: proxy display") 416 return 417 if display: 418 if display not in displays: 419 if ":%s" % display in displays: 420 display = ":%s" % display 421 else: 422 disconnect(SESSION_NOT_FOUND, "display '%s' not found" % display) 423 return 424 else: 425 if len(displays)!=1: 426 disconnect(SESSION_NOT_FOUND, 427 "please specify a display, more than one is available: %s" % csv(displays)) 428 return 429 display = displays[0] 430 431 connect = c.boolget("connect", True) 432 #ConnectTestXpraClient doesn't want to connect to the real session either: 433 ctr = c.strget("connect_test_request") 434 log("connect=%s, connect_test_request=%s", connect, ctr) 435 if not connect or ctr: 436 log("proxy_session: not connecting to the session") 437 hello = {"display" : display} 438 if socket_path: 439 hello["socket-path"] = socket_path 440 #echo mode if present: 441 mode = sns.strget("mode") 442 if mode: 443 hello["mode"] = mode 444 client_proto.send_now(("hello", hello)) 445 return 446 447 def stop_server_subprocess(): 448 log("stop_server_subprocess() proc=%s", proc) 449 if proc and proc.poll() is None: 450 proc.terminate() 451 452 log("start_proxy(%s, {..}, %s) using server display at: %s", client_proto, auth_caps, display) 453 def parse_error(*args): 454 stop_server_subprocess() 455 disconnect(SESSION_NOT_FOUND, "invalid display string") 456 log.warn("Error: parsing failed for display string '%s':", display) 457 for arg in args: 458 log.warn(" %s", arg) 459 raise Exception("parse error on %s: %s" % (display, args)) 460 opts = make_defaults_struct(username=username, uid=uid, gid=gid) 461 opts.username = username 462 disp_desc = parse_display_name(parse_error, opts, display) 463 if uid or gid: 464 disp_desc["uid"] = uid 465 disp_desc["gid"] = gid 466 log("display description(%s) = %s", display, disp_desc) 467 try: 468 server_conn = connect_to(disp_desc, opts) 469 except Exception as e: 470 log("cannot connect", exc_info=True) 471 log.error("Error: cannot start proxy connection:") 472 for x in str(e).splitlines(): 473 log.error(" %s", x) 474 log.error(" connection definition:") 475 print_nested_dict(disp_desc, prefix=" ", lchar="*", pad=20, print_fn=log.error) 476 disconnect(SESSION_NOT_FOUND, "failed to connect to display") 477 stop_server_subprocess() 478 return 479 log("server connection=%s", server_conn) 480 481 cipher = cipher_mode = None 482 encryption_key = None 483 if auth_caps: 484 cipher = auth_caps.get("cipher") 485 if cipher: 486 from xpra.net.crypto import DEFAULT_MODE 487 cipher_mode = auth_caps.get("cipher.mode", DEFAULT_MODE) 488 encryption_key = self.get_encryption_key(client_proto.authenticators, client_proto.keyfile) 489 490 use_thread = PROXY_INSTANCE_THREADED 491 if not use_thread: 492 client_socktype = get_socktype(client_proto) 493 server_socktype = disp_desc["type"] 494 if client_socktype in ("ssl", "wss", "ssh"): 495 log.info("using threaded mode for %s client connection", client_socktype) 496 use_thread = True 497 elif server_socktype in ("ssl", "wss", "ssh"): 498 log.info("using threaded mode for %s server connection", server_socktype) 499 use_thread = True 500 if use_thread: 501 if env_options: 502 log.warn("environment options are ignored in threaded mode") 503 from xpra.server.proxy.proxy_instance_thread import ProxyInstanceThread 504 pit = ProxyInstanceThread(session_options, self.video_encoders, self.pings, 505 client_proto, server_conn, 506 disp_desc, cipher, cipher_mode, encryption_key, c) 507 pit.stopped = self.reap 508 pit.run() 509 self.instances[pit] = (False, display, None) 510 return 511 512 #this may block, so run it in a thread: 513 def start_proxy_process(): 514 log("start_proxy_process()") 515 message_queue = MQueue() 516 client_conn = None 517 try: 518 #no other packets should be arriving until the proxy instance responds to the initial hello packet 519 def unexpected_packet(packet): 520 if packet: 521 log.warn("Warning: received an unexpected packet") 522 log.warn(" from the proxy connection %s:", client_proto) 523 log.warn(" %s", repr_ellipsized(packet)) 524 client_proto.close() 525 client_conn = client_proto.steal_connection(unexpected_packet) 526 client_state = client_proto.save_state() 527 log("start_proxy_process(..) client connection=%s", client_conn) 528 log("start_proxy_process(..) client state=%s", client_state) 529 530 ioe = client_proto.wait_for_io_threads_exit(5+self._socket_timeout) 531 if not ioe: 532 log.error("Error: some network IO threads have failed to terminate") 533 client_proto.close() 534 return 535 client_conn.set_active(True) 536 from xpra.server.proxy.proxy_instance_process import ProxyInstanceProcess 537 process = ProxyInstanceProcess(uid, gid, env_options, session_options, self._socket_dir, 538 self.video_encoders, self.pings, 539 client_conn, disp_desc, client_state, 540 cipher, cipher_mode, encryption_key, server_conn, c, message_queue) 541 log("starting %s from pid=%s", process, os.getpid()) 542 self.instances[process] = (True, display, message_queue) 543 process.start() 544 log("ProxyInstanceProcess started") 545 popen = process._popen 546 assert popen 547 #when this process dies, run reap to update our list of proxy instances: 548 self.child_reaper.add_process(popen, "xpra-proxy-%s" % display, 549 "xpra-proxy-instance", True, True, self.reap) 550 except Exception as e: 551 log("start_proxy_process() failed", exc_info=True) 552 log.error("Error starting proxy instance process:") 553 log.error(" %s", e) 554 message_queue.put("error: %s" % e) 555 message_queue.put("stop") 556 finally: 557 #now we can close our handle on the connection: 558 log("handover complete: closing connection from proxy server") 559 if client_conn: 560 client_conn.close() 561 server_conn.close() 562 log("sending socket-handover-complete") 563 message_queue.put("socket-handover-complete") 564 start_thread(start_proxy_process, "start_proxy(%s)" % client_proto) 565 566 def start_new_session(self, username, _password, uid, gid, new_session_dict=None, displays=()): 567 log("start_new_session%s", (username, "..", uid, gid, new_session_dict, displays)) 568 sns = typedict(new_session_dict or {}) 569 mode = sns.strget("mode", "start") 570 assert mode in ("start", "start-desktop", "shadow"), "invalid start-new-session mode '%s'" % mode 571 display = sns.strget("display") 572 if display in displays: 573 raise Exception("display %s is already active!" % display) 574 log("starting new server subprocess: mode=%s, display=%s", mode, display) 575 args = [] 576 if display: 577 args = [display] 578 #allow the client to override some options: 579 opts = make_defaults_struct(username=username, uid=uid, gid=gid) 580 for k,v in sns.items(): 581 k = bytestostr(k) 582 if k in ("mode", "display"): 583 continue #those special attributes have been consumed already 584 if k not in PROXY_START_OVERRIDABLE_OPTIONS: 585 log.warn("Warning: ignoring invalid start override") 586 log.warn(" %s=%s", k, v) 587 continue 588 try: 589 vt = OPTION_TYPES[k] 590 if vt==str: 591 v = bytestostr(v) 592 elif vt==bool: 593 v = parse_bool(k, v) 594 elif vt==int: 595 v = int(v) 596 elif vt==list: 597 v = list(bytestostr(x) for x in v) 598 except ValueError: 599 log("start_new_session: override option %s", k, exc_info=True) 600 log.warn("Warning: ignoring invalid value %s for %s (%s)", v, k, vt) 601 continue 602 if v is not None: 603 fn = k.replace("-", "_") 604 curr = getattr(opts, fn, None) 605 if curr!=v: 606 log("start override: %24s=%-24s (default=%s)", k, v, curr) 607 setattr(opts, fn, v) 608 else: 609 log("start override: %24s=%-24s (unchanged)", k, v) 610 else: 611 log("start override: %24s=%-24s (invalid, unchanged)", k, v) 612 opts.attach = False 613 opts.start_via_proxy = False 614 env = self.get_proxy_env() 615 cwd = None 616 if uid>0: 617 cwd = get_home_for_uid(uid) or None 618 if not cwd or not os.path.exists(cwd): 619 import tempfile 620 cwd = tempfile.gettempdir() 621 log("starting new server subprocess: options=%s", opts) 622 log("env=%s", env) 623 log("args=%s", args) 624 log("cwd=%s", cwd) 625 proc, socket_path, display = start_server_subprocess(sys.argv[0], args, 626 mode, opts, username, uid, gid, env, cwd) 627 if proc: 628 self.child_reaper.add_process(proc, "server-%s" % (display or socket_path), "xpra %s" % mode, True, True) 629 log("start_new_session(..) pid=%s, socket_path=%s, display=%s, ", proc.pid, socket_path, display) 630 return proc, socket_path, display 631 632 def get_proxy_env(self): 633 env = dict((k,v) for k,v in os.environ.items() if k in ENV_WHITELIST or "*" in ENV_WHITELIST) 634 #env var to add to environment of subprocess: 635 extra_env_str = os.environ.get("XPRA_PROXY_START_ENV", "") 636 if extra_env_str: 637 extra_env = {} 638 for e in extra_env_str.split(os.path.pathsep): #ie: "A=1:B=2" 639 parts = e.split("=", 1) 640 if len(parts)==2: 641 extra_env[parts[0]]= parts[1] 642 log("extra_env(%s)=%s", extra_env_str, extra_env) 643 env.update(extra_env) 644 return env 645 646 647 def reap(self, *args): 648 log("reap%s", args) 649 dead = [] 650 for instance in tuple(self.instances.keys()): 651 #instance is a process 652 if not instance.is_alive(): 653 dead.append(instance) 654 log("reap%s dead processes: %s", args, dead or None) 655 for p in dead: 656 del self.instances[p] 657 658 659 def get_info(self, proto, *_args): 660 authenticated = proto and proto.authenticators 661 if not authenticated: 662 info = super().get_server_info() 663 else: 664 #only show more info if we have authenticated 665 #as the user running the proxy server process: 666 info = super().get_info(proto) 667 sessions = () 668 for authenticator in proto.authenticators: 669 auth_sessions = authenticator.get_sessions() 670 if auth_sessions: 671 sessions = auth_sessions 672 break 673 if sessions: 674 uid, gid = sessions[:2] 675 if not POSIX or (uid==getuid() and gid==getgid()): 676 self.reap() 677 i = 0 678 instances = dict(self.instances) 679 instances_info = {} 680 for proxy_instance, v in instances.items(): 681 isprocess, d, _ = v 682 iinfo = { 683 "display" : d, 684 "live" : proxy_instance.is_alive(), 685 } 686 if isprocess: 687 iinfo.update({ 688 "pid" : proxy_instance.pid, 689 }) 690 else: 691 iinfo.update(proxy_instance.get_info()) 692 instances_info[i] = iinfo 693 i += 1 694 info["instances"] = instances_info 695 info["proxies"] = len(instances) 696 info.setdefault("server", {})["type"] = "Python/GLib/proxy" 697 return info 698