1# -*- coding: utf-8 -*- 2# This file is part of Xpra. 3# Copyright (C) 2011 Serviware (Arthur Huillet, <ahuillet@serviware.com>) 4# Copyright (C) 2010-2021 Antoine Martin <antoine@xpra.org> 5# Copyright (C) 2008 Nathaniel Smith <njs@pobox.com> 6# Xpra is released under the terms of the GNU GPL v2, or, at your option, any 7# later version. See the file COPYING for details. 8 9import os 10import sys 11import errno 12import socket 13import signal 14import platform 15import threading 16from urllib.parse import urlparse, parse_qsl, unquote 17from weakref import WeakKeyDictionary 18from time import sleep, time, monotonic 19from threading import Thread, Lock 20 21from xpra.version_util import ( 22 XPRA_VERSION, full_version_str, version_compat_check, get_version_info_full, 23 get_platform_info, get_host_info, 24 ) 25from xpra.scripts.server import deadly_signal, clean_session_files, rm_session_dir 26from xpra.server.server_util import write_pidfile, rm_pidfile 27from xpra.scripts.config import parse_bool, parse_with_unit, TRUE_OPTIONS, FALSE_OPTIONS 28from xpra.net.common import may_log_packet, SOCKET_TYPES, MAX_PACKET_SIZE 29from xpra.net.socket_util import ( 30 hosts, mdns_publish, peek_connection, 31 PEEK_TIMEOUT_MS, UNIXDOMAIN_PEEK_TIMEOUT_MS, 32 add_listen_socket, accept_connection, guess_packet_type, 33 ssl_wrap_socket, 34 ) 35from xpra.net.bytestreams import ( 36 SocketConnection, SSLSocketConnection, 37 log_new_connection, pretty_socket, SOCKET_TIMEOUT, 38 ) 39from xpra.net.net_util import ( 40 get_network_caps, get_info as get_net_info, 41 import_netifaces, get_interfaces_addresses, 42 ) 43from xpra.net.protocol import Protocol, CONNECTION_LOST, GIBBERISH, INVALID 44from xpra.net.digest import get_salt, gendigest, choose_digest 45from xpra.platform import set_name, threaded_server_init 46from xpra.platform.info import get_username 47from xpra.platform.paths import ( 48 get_app_dir, get_system_conf_dirs, get_user_conf_dirs, 49 get_icon_filename, 50 ) 51from xpra.platform.dotxpra import DotXpra 52from xpra.os_util import ( 53 register_SIGUSR_signals, force_quit, 54 get_frame_info, get_info_env, get_sysconfig_info, 55 filedata_nocrlf, get_machine_id, get_user_uuid, platform_name, get_ssh_port, 56 strtobytes, bytestostr, get_hex_uuid, 57 getuid, hexstr, 58 WIN32, POSIX, BITS, 59 parse_encoded_bin_data, load_binary_file, 60 osexpand, 61 ) 62from xpra.server.background_worker import stop_worker, get_worker, add_work_item 63from xpra.server.menu_provider import get_menu_provider 64from xpra.server.auth.auth_helper import get_auth_module 65from xpra.make_thread import start_thread 66from xpra.util import ( 67 first_time, noerr, net_utf8, 68 csv, merge_dicts, typedict, notypedict, flatten_dict, 69 ellipsizer, dump_all_frames, envint, envbool, envfloat, 70 SERVER_SHUTDOWN, SERVER_UPGRADE, LOGIN_TIMEOUT, DONE, PROTOCOL_ERROR, 71 SERVER_ERROR, VERSION_ERROR, CLIENT_REQUEST, SERVER_EXIT, 72 ) 73from xpra.log import Logger, get_info as get_log_info 74 75#pylint: disable=import-outside-toplevel 76 77log = Logger("server") 78netlog = Logger("network") 79ssllog = Logger("ssl") 80httplog = Logger("http") 81wslog = Logger("websocket") 82proxylog = Logger("proxy") 83commandlog = Logger("command") 84authlog = Logger("auth") 85cryptolog = Logger("crypto") 86timeoutlog = Logger("timeout") 87dbuslog = Logger("dbus") 88mdnslog = Logger("mdns") 89 90main_thread = threading.current_thread() 91 92MAX_CONCURRENT_CONNECTIONS = envint("XPRA_MAX_CONCURRENT_CONNECTIONS", 100) 93SIMULATE_SERVER_HELLO_ERROR = envbool("XPRA_SIMULATE_SERVER_HELLO_ERROR", False) 94SERVER_SOCKET_TIMEOUT = envfloat("XPRA_SERVER_SOCKET_TIMEOUT", "0.1") 95LEGACY_SALT_DIGEST = envbool("XPRA_LEGACY_SALT_DIGEST", False) 96CHALLENGE_TIMEOUT = envint("XPRA_CHALLENGE_TIMEOUT", 120) 97SYSCONFIG = envbool("XPRA_SYSCONFIG", True) 98SHOW_NETWORK_ADDRESSES = envbool("XPRA_SHOW_NETWORK_ADDRESSES", True) 99INIT_THREAD_TIMEOUT = envint("XPRA_INIT_THREAD_TIMEOUT", 10) 100HTTP_HTTPS_REDIRECT = envbool("XPRA_HTTP_HTTPS_REDIRECT", True) 101 102ENCRYPTED_SOCKET_TYPES = os.environ.get("XPRA_ENCRYPTED_SOCKET_TYPES", "tcp,ws") 103 104HTTP_UNSUPORTED = b"""HTTP/1.1 400 Bad request syntax or unsupported method 105 106<head> 107<title>Server Error</title> 108</head> 109<body> 110<h1>Server Error</h1> 111<p>Error code 400. 112<p>Message: this port does not support HTTP requests. 113<p>Error code explanation: 400 = Bad request syntax or unsupported method. 114</body> 115""" 116 117 118#class used to distinguish internal errors 119#which should not be shown to the client, 120#from useful messages we do want to pass on 121class ClientException(Exception): 122 pass 123 124 125def get_server_info(): 126 #this function is for non UI thread info 127 info = { 128 "platform" : get_platform_info(), 129 "build" : get_version_info_full(), 130 } 131 info.update(get_host_info()) 132 return info 133 134def get_thread_info(proto=None): 135 #threads: 136 if proto: 137 info_threads = proto.get_threads() 138 else: 139 info_threads = () 140 return get_frame_info(info_threads) 141 142 143class ServerCore: 144 """ 145 This is the simplest base class for servers. 146 It only handles the connection layer: 147 authentication and the initial handshake. 148 """ 149 150 def __init__(self): 151 log("ServerCore.__init__()") 152 self.start_time = time() 153 self.auth_classes = {} 154 self.child_reaper = None 155 self.original_desktop_display = None 156 self.session_type = "unknown" 157 self.display_name = "" 158 self.display_options = "" 159 self.dotxpra = None 160 161 self._closing = False 162 self._upgrading = None 163 #networking bits: 164 self._socket_info = {} 165 self._potential_protocols = [] 166 self._tcp_proxy_clients = [] 167 self._tcp_proxy = "" 168 self._rfb_upgrade = 0 169 self._ssl_attributes = {} 170 self._accept_timeout = SOCKET_TIMEOUT + 1 171 self.ssl_mode = None 172 self._html = False 173 self._http_scripts = {} 174 self._www_dir = None 175 self._http_headers_dirs = () 176 self._aliases = {} 177 self.socket_info = {} 178 self.socket_options = {} 179 self.socket_cleanup = [] 180 self.socket_verify_timer = WeakKeyDictionary() 181 self.socket_rfb_upgrade_timer = WeakKeyDictionary() 182 self._max_connections = MAX_CONCURRENT_CONNECTIONS 183 self._socket_timeout = SERVER_SOCKET_TIMEOUT 184 self._ws_timeout = 5 185 self._socket_dir = None 186 self._socket_dirs = [] 187 self.dbus_pid = 0 188 self.dbus_env = {} 189 self.dbus_control = False 190 self.dbus_server = None 191 self.unix_socket_paths = [] 192 self.touch_timer = None 193 self.exec_cwd = os.getcwd() 194 self.pidfile = None 195 self.pidinode = 0 196 self.session_files = ["cmdline", "server.env", "config", "server.log*"] 197 self.splash_process = None 198 199 self.session_name = "" 200 201 #Features: 202 self.mdns = False 203 self.mdns_publishers = {} 204 self.encryption = None 205 self.encryption_keyfile = None 206 self.tcp_encryption = None 207 self.tcp_encryption_keyfile = None 208 self.password_file = None 209 self.compression_level = 1 210 self.exit_with_client = False 211 self.server_idle_timeout = 0 212 self.server_idle_timer = None 213 self.bandwidth_limit = 0 214 215 self.init_thread = None 216 self.init_thread_callbacks = [] 217 self.init_thread_lock = Lock() 218 self.menu_provider = None 219 220 self.init_uuid() 221 222 def get_server_mode(self): 223 return "core" 224 225 226 def idle_add(self, *args, **kwargs): 227 raise NotImplementedError() 228 229 def timeout_add(self, *args, **kwargs): 230 raise NotImplementedError() 231 232 def source_remove(self, timer): 233 raise NotImplementedError() 234 235 236 def init(self, opts): 237 log("ServerCore.init(%s)", opts) 238 self.session_name = bytestostr(opts.session_name) 239 set_name("Xpra", self.session_name or "Xpra") 240 241 self.bandwidth_limit = parse_with_unit("bandwidth-limit", opts.bandwidth_limit) 242 self.unix_socket_paths = [] 243 self._socket_dir = opts.socket_dir or "" 244 if not self._socket_dir and opts.socket_dirs: 245 self._socket_dir = opts.socket_dirs[0] 246 self._socket_dirs = opts.socket_dirs 247 self.encryption = opts.encryption 248 self.encryption_keyfile = opts.encryption_keyfile 249 self.tcp_encryption = opts.tcp_encryption 250 self.tcp_encryption_keyfile = opts.tcp_encryption_keyfile 251 if self.encryption or self.tcp_encryption: 252 from xpra.net.crypto import crypto_backend_init #pylint: disable=import-outside-toplevel 253 crypto_backend_init() 254 self.password_file = opts.password_file 255 self.compression_level = opts.compression_level 256 self.exit_with_client = opts.exit_with_client 257 self.server_idle_timeout = opts.server_idle_timeout 258 self.readonly = opts.readonly 259 self.ssh_upgrade = opts.ssh_upgrade 260 self.dbus_control = opts.dbus_control 261 self.pidfile = osexpand(opts.pidfile) 262 self.mdns = opts.mdns 263 if opts.start_new_commands: 264 #must be initialized before calling init_html_proxy 265 self.menu_provider = get_menu_provider() 266 self.init_html_proxy(opts) 267 self.init_auth(opts) 268 self.init_ssl(opts) 269 if self.pidfile: 270 self.pidinode = write_pidfile(os.path.normpath(self.pidfile)) 271 self.dotxpra = DotXpra(opts.socket_dir, opts.socket_dirs+opts.client_socket_dirs) 272 273 274 def init_ssl(self, opts): 275 self.ssl_mode = opts.ssl 276 from xpra.net.socket_util import get_ssl_attributes 277 self._ssl_attributes = get_ssl_attributes(opts, True) 278 netlog("init_ssl(..) ssl attributes=%s", self._ssl_attributes) 279 280 def validate(self): 281 return True 282 283 def server_init(self): 284 if self.mdns: 285 add_work_item(self.mdns_publish) 286 self.start_listen_sockets() 287 288 def setup(self): 289 self.init_packet_handlers() 290 self.init_aliases() 291 self.init_dbus_server() 292 self.init_control_commands() 293 #for things that can take longer: 294 self.init_thread = Thread(target=self.threaded_init) 295 self.init_thread.start() 296 297 298 ###################################################################### 299 # run / stop: 300 def signal_quit(self, signum, _frame=None): 301 self.closing() 302 self.install_signal_handlers(deadly_signal) 303 self.idle_add(self.clean_quit) 304 self.idle_add(sys.exit, 128+signum) 305 306 def clean_quit(self, upgrading=False): 307 log("clean_quit(%s)", upgrading) 308 if self._upgrading is None: 309 self._upgrading = upgrading 310 self.timeout_add(5000, self.force_quit) 311 self.closing() 312 self.cleanup() 313 self.quit_worker() 314 315 def force_quit(self): 316 log("force_quit()") 317 force_quit() 318 319 def quit_worker(self): 320 w = get_worker() 321 log("clean_quit: worker=%s", w) 322 if not w: 323 self.quit() 324 return 325 stop_worker() 326 try: 327 w.join(0.05) 328 except Exception: 329 pass 330 if not w.is_alive(): 331 self.quit() 332 return 333 def quit_timer(): 334 log("quit_timer() worker=%s", w) 335 if w and w.is_alive(): 336 #wait up to 1 second for the worker thread to exit 337 try: 338 w.wait(1) 339 except Exception: 340 pass 341 if w.is_alive(): 342 #still alive, force stop: 343 stop_worker(True) 344 try: 345 w.wait(1) 346 except Exception: 347 pass 348 self.quit() 349 self.timeout_add(250, quit_timer) 350 log("clean_quit(..) quit timer scheduled, worker=%s", w) 351 352 def quit(self, upgrading=False): 353 log("quit(%s)", upgrading) 354 if self._upgrading is None: 355 self._upgrading = upgrading 356 self.closing() 357 noerr(sys.stdout.flush) 358 self.late_cleanup() 359 self.do_quit() 360 log("quit(%s) do_quit done!", upgrading) 361 dump_all_frames() 362 363 def closing(self): 364 if not self._closing: 365 self._closing = True 366 self.log_closing_message() 367 368 def log_closing_message(self): 369 log.info("xpra %s server is %s", self.get_server_mode(), ["terminating", "exiting"][bool(self._upgrading)]) 370 371 def do_quit(self): 372 raise NotImplementedError() 373 374 def install_signal_handlers(self, callback): 375 def os_signal(signum, _frame=None): 376 callback(signum) 377 signal.signal(signal.SIGINT, os_signal) 378 signal.signal(signal.SIGTERM, os_signal) 379 register_SIGUSR_signals(self.idle_add) 380 381 382 def threaded_init(self): 383 self.do_threaded_init() 384 self.call_init_thread_callbacks() 385 386 def do_threaded_init(self): 387 log("do_threaded_init() servercore start") 388 #platform specific init: 389 threaded_server_init() 390 #populate the platform info cache: 391 get_platform_info() 392 if self.menu_provider: 393 self.menu_provider.setup() 394 log("threaded_init() servercore end") 395 396 def call_init_thread_callbacks(self): 397 #run the init callbacks: 398 with self.init_thread_lock: 399 log("call_init_thread_callbacks() init_thread_callbacks=%s", self.init_thread_callbacks) 400 for cb in self.init_thread_callbacks: 401 try: 402 cb() 403 except Exception as e: 404 log("threaded_init()", exc_info=True) 405 log.error("Error in initialization thread callback %s", cb) 406 log.error(" %s", e) 407 408 def after_threaded_init(self, callback): 409 with self.init_thread_lock: 410 if self.init_thread is None or self.init_thread.is_alive(): 411 self.init_thread_callbacks.append(callback) 412 else: 413 callback() 414 415 def wait_for_threaded_init(self): 416 if not self.init_thread: 417 #looks like we didn't make it as far as calling setup() 418 log("wait_for_threaded_init() no init thread") 419 return 420 log("wait_for_threaded_init() %s.is_alive()=%s", self.init_thread, self.init_thread.is_alive()) 421 if self.init_thread.is_alive(): 422 log.info("waiting for initialization thread to complete") 423 self.init_thread.join(INIT_THREAD_TIMEOUT) 424 if self.init_thread.is_alive(): 425 log.warn("Warning: initialization thread is still active") 426 427 428 def run(self): 429 self.install_signal_handlers(self.signal_quit) 430 self.idle_add(self.reset_server_timeout) 431 self.idle_add(self.server_is_ready) 432 self.idle_add(self.print_run_info) 433 self.stop_splash_process() 434 self.do_run() 435 log("run()") 436 return 0 437 438 def server_is_ready(self): 439 log.info("xpra is ready.") 440 noerr(sys.stdout.flush) 441 442 def do_run(self): 443 raise NotImplementedError() 444 445 def cleanup(self): 446 self.stop_splash_process() 447 self.stop_tcp_proxy_clients() 448 self.cancel_touch_timer() 449 self.mdns_cleanup() 450 self.cleanup_all_protocols() 451 self.do_cleanup() 452 self.cleanup_sockets() 453 self.cleanup_dbus_server() 454 self.cleanup_menu_provider() 455 netlog("cleanup() done for server core") 456 457 def do_cleanup(self): 458 #allow just a bit of time for the protocol packet flush 459 sleep(0.1) 460 461 def late_cleanup(self): 462 if not self._upgrading: 463 self.stop_dbus_server() 464 self.cleanup_all_protocols(force=True) 465 self._potential_protocols = [] 466 if self.pidfile: 467 netlog("cleanup removing pidfile %s", self.pidfile) 468 self.pidinode = rm_pidfile(self.pidfile, self.pidinode) 469 if not self._upgrading: 470 self.clean_session_files() 471 rm_session_dir() 472 473 def clean_session_files(self): 474 self.do_clean_session_files(*self.session_files) 475 476 def do_clean_session_files(self, *filenames): 477 log("do_clean_session_files%s", filenames) 478 clean_session_files(*filenames) 479 480 def stop_splash_process(self): 481 sp = self.splash_process 482 if sp: 483 self.splash_process = None 484 try: 485 sp.terminate() 486 except OSError: 487 log("stop_splash_process()", exc_info=True) 488 489 490 def cleanup_menu_provider(self): 491 mp = self.menu_provider 492 if mp: 493 self.menu_provider = None 494 mp.cleanup() 495 496 def cleanup_sockets(self): 497 netlog("cleanup_sockets() %s", self.socket_cleanup) 498 #stop listening for IO events: 499 for sc in self.socket_cleanup: 500 sc() 501 #actually close the socket: 502 si = self._socket_info 503 self._socket_info = {} 504 for socktype, _, info, cleanup in si: 505 log("cleanup_sockets() calling %s for %s %s", cleanup, socktype, info) 506 try: 507 cleanup() 508 except Exception: 509 log("cleanup error on %s", cleanup, exc_info=True) 510 511 512 ###################################################################### 513 # dbus: 514 def init_dbus(self, dbus_pid, dbus_env): 515 if not POSIX: 516 return 517 self.dbus_pid = dbus_pid 518 self.dbus_env = dbus_env 519 520 def stop_dbus_server(self): 521 dbuslog("stop_dbus_server() dbus_pid=%s", self.dbus_pid) 522 if not self.dbus_pid: 523 return 524 try: 525 os.kill(self.dbus_pid, signal.SIGINT) 526 self.do_clean_session_files("dbus.pid", "dbus.env") 527 except ProcessLookupError as e: 528 dbuslog("os.kill(%i, SIGINT)", self.dbus_pid, exc_info=True) 529 dbuslog.warn("Warning: dbus process not found (pid=%i)", self.dbus_pid) 530 except Exception as e: 531 dbuslog("os.kill(%i, SIGINT)", self.dbus_pid, exc_info=True) 532 dbuslog.warn("Warning: error trying to stop dbus with pid %i:", self.dbus_pid) 533 dbuslog.warn(" %s", e) 534 535 def init_dbus_server(self): 536 if not POSIX: 537 return 538 dbuslog("init_dbus_server() dbus_control=%s", self.dbus_control) 539 dbuslog("init_dbus_server() env: %s", dict((k,v) for k,v in os.environ.items() 540 if bytestostr(k).startswith("DBUS_"))) 541 if not self.dbus_control: 542 return 543 try: 544 from xpra.server.dbus.dbus_common import dbus_exception_wrap 545 self.dbus_server = dbus_exception_wrap(self.make_dbus_server, "setting up server dbus instance") 546 except Exception as e: 547 log("init_dbus_server()", exc_info=True) 548 log.error("Error: cannot load dbus server:") 549 log.error(" %s", e) 550 self.dbus_server = None 551 552 def cleanup_dbus_server(self): 553 ds = self.dbus_server 554 netlog("cleanup_dbus_server() dbus_server=%s", ds) 555 if ds: 556 ds.cleanup() 557 self.dbus_server = None 558 559 def make_dbus_server(self): #pylint: disable=useless-return 560 dbuslog("make_dbus_server() no dbus server for %s", self) 561 return None 562 563 564 def init_uuid(self): 565 # Define a server UUID if needed: 566 self.uuid = os.environ.get("XPRA_PROXY_START_UUID") or self.get_uuid() 567 if not self.uuid: 568 self.uuid = bytestostr(get_hex_uuid()) 569 self.save_uuid() 570 log("server uuid is %s", self.uuid) 571 572 def get_uuid(self): 573 return None 574 575 def save_uuid(self): 576 pass 577 578 579 def init_html_proxy(self, opts): 580 httplog("init_html_proxy(..) options: tcp_proxy=%s, html='%s'", opts.tcp_proxy, opts.html) 581 self._tcp_proxy = opts.tcp_proxy 582 #opts.html can contain a boolean, "auto" or the path to the webroot 583 www_dir = None 584 if opts.html and os.path.isabs(opts.html): 585 www_dir = opts.html 586 self._html = True 587 else: 588 self._html = parse_bool("html", opts.html) 589 if self._html is not False: #True or None (for "auto") 590 if not (opts.bind_tcp or opts.bind_ws or opts.bind_wss or opts.bind or opts.bind_ssl): 591 #we need a socket! 592 if self._html: 593 #html was enabled, so log an error: 594 httplog.error("Error: cannot use the html server without a socket") 595 self._html = False 596 httplog("init_html_proxy(..) html=%s", self._html) 597 if self._html is not False: 598 try: 599 from xpra.net.websockets.handler import WebSocketRequestHandler 600 assert WebSocketRequestHandler 601 self._html = True 602 except ImportError as e: 603 httplog("importing WebSocketRequestHandler", exc_info=True) 604 if self._html is None: #auto mode 605 httplog.info("html server unavailable, cannot find websocket module") 606 else: 607 httplog.error("Error: cannot import websocket connection handler:") 608 httplog.error(" %s", e) 609 httplog.error(" the html server will not be available") 610 self._html = False 611 #make sure we have the web root: 612 from xpra.platform.paths import get_resources_dir 613 if www_dir: 614 self._www_dir = www_dir 615 else: 616 for ad,d in ( 617 (get_resources_dir(), "html5"), 618 (get_resources_dir(), "www"), 619 (get_app_dir(), "www"), 620 ): 621 self._www_dir = os.path.abspath(os.path.join(ad, d)) 622 if os.path.exists(self._www_dir): 623 httplog("found html5 client in '%s'", self._www_dir) 624 break 625 if not os.path.exists(self._www_dir) and self._html: 626 httplog.error("Error: cannot find the html web root") 627 httplog.error(" '%s' does not exist", self._www_dir) 628 httplog.error(" install the xpra-html5 package") 629 self._html = False 630 if self._html: 631 httplog.info("serving html content from '%s'", self._www_dir) 632 self._http_headers_dirs = [] 633 for d in get_system_conf_dirs(): 634 self._http_headers_dirs.append(os.path.join(d, "http-headers")) 635 if not POSIX or getuid()>0: 636 for d in get_user_conf_dirs(): 637 self._http_headers_dirs.append(os.path.join(d, "http-headers")) 638 self._http_headers_dirs.append(os.path.abspath(os.path.join(self._www_dir, "../http-headers"))) 639 if self._html and self._tcp_proxy: 640 httplog.warn("Warning: the built in html server is enabled,") 641 httplog.warn(" disabling the tcp-proxy option") 642 self._tcp_proxy = False 643 if opts.http_scripts.lower() not in FALSE_OPTIONS: 644 script_options = { 645 "/Status" : self.http_status_request, 646 "/Info" : self.http_info_request, 647 "/Sessions" : self.http_sessions_request, 648 "/Displays" : self.http_displays_request, 649 } 650 if self.menu_provider: 651 #we have menu data we can expose: 652 script_options.update({ 653 "/Menu" : self.http_menu_request, 654 "/MenuIcon" : self.http_menu_icon_request, 655 "/DesktopMenu" : self.http_desktop_menu_request, 656 "/DesktopMenuIcon" : self.http_desktop_menu_icon_request, 657 }) 658 if opts.http_scripts.lower() in ("all", "*"): 659 self._http_scripts = script_options 660 else: 661 for script in opts.http_scripts.split(","): 662 if not script.startswith("/"): 663 script = "/"+script 664 handler = script_options.get(script) 665 if not handler: 666 httplog.warn("Warning: unknown script '%s'", script) 667 else: 668 self._http_scripts[script] = handler 669 httplog("http_scripts(%s)=%s", opts.http_scripts, self._http_scripts) 670 671 672 ###################################################################### 673 # authentication: 674 def init_auth(self, opts): 675 auth = self.get_auth_modules("local-auth", opts.auth or []) 676 if WIN32: 677 self.auth_classes["named-pipe"] = auth 678 else: 679 self.auth_classes["unix-domain"] = auth 680 for x in SOCKET_TYPES: 681 opts_value = getattr(opts, "%s_auth" % x) 682 self.auth_classes[x] = self.get_auth_modules(x, opts_value) 683 authlog("init_auth(..) auth=%s", self.auth_classes) 684 685 def get_auth_modules(self, socket_type, auth_strs): 686 authlog("get_auth_modules(%s, %s, {..})", socket_type, auth_strs) 687 if not auth_strs: 688 return None 689 return tuple(get_auth_module(auth_str) for auth_str in auth_strs) 690 691 692 ###################################################################### 693 # control commands: 694 def init_control_commands(self): 695 from xpra.server.control_command import HelloCommand, HelpCommand, DebugControl 696 self.control_commands = {"hello" : HelloCommand(), 697 "debug" : DebugControl()} 698 help_command = HelpCommand(self.control_commands) 699 self.control_commands["help"] = help_command 700 701 def handle_command_request(self, proto, *args): 702 """ client sent a command request as part of the hello packet """ 703 assert args, "no arguments supplied" 704 code, response = self.process_control_command(*args) 705 hello = {"command_response" : (code, response)} 706 proto.send_now(("hello", hello)) 707 708 def process_control_command(self, *args): 709 from xpra.server.control_command import ControlError 710 assert args, "control command must have arguments" 711 name = args[0] 712 try: 713 command = self.control_commands.get(name) 714 commandlog("process_control_command control_commands[%s]=%s", name, command) 715 if not command: 716 commandlog.warn("invalid command: '%s' (must be one of: %s)", name, csv(self.control_commands)) 717 return 6, "invalid command" 718 commandlog("process_control_command calling %s%s", command.run, args[1:]) 719 v = command.run(*args[1:]) 720 return 0, v 721 except ControlError as e: 722 commandlog.error("error %s processing control command '%s'", e.code, name) 723 msgs = [" %s" % e] 724 if e.help: 725 msgs.append(" '%s': %s" % (name, e.help)) 726 for msg in msgs: 727 commandlog.error(msg) 728 return e.code, "\n".join(msgs) 729 except Exception as e: 730 commandlog.error("error processing control command '%s'", name, exc_info=True) 731 return 127, "error processing control command: %s" % e 732 733 734 def print_run_info(self): 735 add_work_item(self.do_print_run_info) 736 737 def do_print_run_info(self): 738 log.info("xpra %s version %s %i-bit", self.get_server_mode(), full_version_str(), BITS) 739 try: 740 pinfo = get_platform_info() 741 osinfo = " on %s" % platform_name(sys.platform, pinfo.get("linux_distribution") or pinfo.get("sysrelease", "")) 742 except Exception: 743 log("platform name error:", exc_info=True) 744 osinfo = "" 745 if POSIX: 746 uid = os.getuid() 747 gid = os.getgid() 748 try: 749 import pwd 750 import grp #@UnresolvedImport 751 user = pwd.getpwuid(uid)[0] 752 group = grp.getgrgid(gid)[0] 753 log.info(" uid=%i (%s), gid=%i (%s)", uid, user, gid, group) 754 except (TypeError, KeyError): 755 log("failed to get user and group information", exc_info=True) 756 log.info(" uid=%i, gid=%i", uid, gid) 757 log.info(" running with pid %s%s", os.getpid(), osinfo) 758 self.idle_add(self.print_screen_info) 759 760 def notify_new_user(self, ss): 761 pass 762 763 764 ###################################################################### 765 # screen / display: 766 def get_display_bit_depth(self): 767 return 0 768 769 def print_screen_info(self): 770 display = os.environ.get("DISPLAY") 771 if display and display.startswith(":"): 772 extra = "" 773 bit_depth = self.get_display_bit_depth() 774 if bit_depth: 775 extra = " with %i bit colors" % bit_depth 776 log.info(" connected to X11 display %s%s", display, extra) 777 778 779 ###################################################################### 780 # sockets / connections / packets: 781 def init_sockets(self, sockets): 782 self._socket_info = sockets 783 784 785 def mdns_publish(self): 786 if not self.mdns: 787 return 788 #find all the records we want to publish: 789 mdns_recs = {} 790 for sock_def, options in self._socket_info.items(): 791 socktype, _, info, _ = sock_def 792 socktypes = self.get_mdns_socktypes(socktype) 793 mdns_option = options.get("mdns") 794 if mdns_option: 795 v = parse_bool("mdns", mdns_option, False) 796 if not v: 797 mdnslog("mdns_publish() mdns(%s)=%s, skipped", info, mdns_option) 798 continue 799 mdnslog("mdns_publish() info=%s, socktypes(%s)=%s", info, socktype, socktypes) 800 for st in socktypes: 801 recs = mdns_recs.setdefault(st, []) 802 if socktype=="unix-domain": 803 assert st=="ssh" 804 host = "*" 805 iport = get_ssh_port() 806 if not iport: 807 continue 808 else: 809 host, iport = info 810 for h in hosts(host): 811 rec = (h, iport) 812 if rec not in recs: 813 recs.append(rec) 814 mdnslog("mdns_publish() recs[%s]=%s", st, recs) 815 mdns_info = self.get_mdns_info() 816 self.mdns_publishers = {} 817 for mdns_mode, listen_on in mdns_recs.items(): 818 info = dict(mdns_info) 819 info["mode"] = mdns_mode 820 aps = mdns_publish(self.display_name, listen_on, info) 821 for ap in aps: 822 ap.start() 823 self.mdns_publishers[ap] = mdns_mode 824 825 def get_mdns_socktypes(self, socktype): 826 #for a given socket type, 827 #what socket types we should expose via mdns 828 if socktype in ("vsock", "named-pipe"): 829 #cannot be accessed remotely 830 return () 831 ssh_access = get_ssh_port()>0 #and opts.ssh.lower().strip() not in FALSE_OPTIONS 832 ssl = bool(self._ssl_attributes) 833 #only available with the RFBServer 834 rfb_upgrades = getattr(self, "_rfb_upgrade", False) 835 socktypes = [socktype] 836 if socktype=="tcp": 837 if ssl: 838 socktypes.append("ssl") 839 if self._html: 840 socktypes.append("ws") 841 if self._html and ssl: 842 socktypes.append("wss") 843 if self.ssh_upgrade: 844 socktypes.append("ssh") 845 if rfb_upgrades: 846 socktypes.append("rfb") 847 elif socktype=="ws": 848 if ssl: 849 socktypes.append("wss") 850 elif socktype=="unix-domain": 851 if ssh_access: 852 socktypes = ["ssh"] 853 return socktypes 854 855 def get_mdns_info(self) -> dict: 856 mdns_info = { 857 "display" : self.display_name, 858 "username" : get_username(), 859 "uuid" : self.uuid, 860 "platform" : sys.platform, 861 "type" : self.session_type, 862 } 863 MDNS_EXPOSE_NAME = envbool("XPRA_MDNS_EXPOSE_NAME", True) 864 if MDNS_EXPOSE_NAME and self.session_name: 865 mdns_info["name"] = self.session_name 866 return mdns_info 867 868 def mdns_cleanup(self): 869 if self.mdns_publishers: 870 add_work_item(self.do_mdns_cleanup) 871 872 def do_mdns_cleanup(self): 873 mp = dict(self.mdns_publishers) 874 self.mdns_publishers = {} 875 for ap in tuple(mp.keys()): 876 ap.stop() 877 878 def mdns_update(self): 879 if not self.mdns: 880 return 881 txt = self.get_mdns_info() 882 for mdns_publisher, mode in dict(self.mdns_publishers).items(): 883 info = dict(txt) 884 info["mode"] = mode 885 try: 886 mdns_publisher.update_txt(info) 887 except Exception as e: 888 mdnslog("mdns_update: %s(%s)", mdns_publisher.update_txt, info, exc_info=True) 889 mdnslog.warn("Warning: mdns update failed") 890 mdnslog.warn(" %s", e) 891 892 893 def start_listen_sockets(self): 894 ### All right, we're ready to accept customers: 895 for sock_def, options in self._socket_info.items(): 896 socktype, sock, info, _ = sock_def 897 netlog("init_sockets(%s) will add %s socket %s (%s)", self._socket_info, socktype, sock, info) 898 self.socket_info[sock] = info 899 self.socket_options[sock] = options 900 self.idle_add(self.add_listen_socket, socktype, sock, options) 901 if socktype=="unix-domain" and info: 902 try: 903 p = os.path.abspath(info) 904 self.unix_socket_paths.append(p) 905 netlog("added unix socket path: %s", p) 906 except Exception as e: 907 log.error("failed to set socket path to %s: %s", info, e) 908 del e 909 if self.unix_socket_paths: 910 self.touch_timer = self.timeout_add(60*1000, self.touch_sockets) 911 912 913 def cancel_touch_timer(self): 914 tt = self.touch_timer 915 if tt: 916 self.touch_timer = None 917 self.source_remove(tt) 918 919 def touch_sockets(self): 920 netlog("touch_sockets() unix socket paths=%s", self.unix_socket_paths) 921 for sockpath in self.unix_socket_paths: 922 if not os.path.exists(sockpath): 923 if first_time("missing-socket-%s" % sockpath): 924 log.warn("Warning: the unix domain socket cannot be found:") 925 log.warn(" '%s'", sockpath) 926 log.warn(" was it deleted by mistake?") 927 continue 928 try: 929 os.utime(sockpath, None) 930 except Exception: 931 netlog("touch_sockets() error on %s", sockpath, exc_info=True) 932 return True 933 934 def init_packet_handlers(self): 935 netlog("initializing packet handlers") 936 self._default_packet_handlers = { 937 "hello": self._process_hello, 938 "disconnect": self._process_disconnect, 939 CONNECTION_LOST: self._process_connection_lost, 940 GIBBERISH: self._process_gibberish, 941 INVALID: self._process_invalid, 942 } 943 944 def init_aliases(self): 945 self.do_init_aliases(self._default_packet_handlers.keys()) 946 947 def do_init_aliases(self, packet_types): 948 i = 1 949 for key in packet_types: 950 self._aliases[i] = key 951 i += 1 952 953 def cleanup_all_protocols(self, reason=None, force=False): 954 protocols = self.get_all_protocols() 955 self.cleanup_protocols(protocols, reason=reason, force=force) 956 957 def get_all_protocols(self): 958 return tuple(self._potential_protocols) 959 960 def cleanup_protocols(self, protocols, reason=None, force=False): 961 if reason is None: 962 if self._upgrading: 963 reason = SERVER_UPGRADE 964 else: 965 reason = SERVER_SHUTDOWN 966 netlog("cleanup_protocols(%s, %s, %s)", protocols, reason, force) 967 for protocol in protocols: 968 if force: 969 self.force_disconnect(protocol) 970 else: 971 self.disconnect_protocol(protocol, reason) 972 973 def add_listen_socket(self, socktype, sock, options): 974 info = self.socket_info.get(sock) 975 netlog("add_listen_socket(%s, %s, %s) info=%s", socktype, sock, options, info) 976 cleanup = add_listen_socket(socktype, sock, info, self._new_connection, options) 977 if cleanup: 978 self.socket_cleanup.append(cleanup) 979 980 def _new_connection(self, socktype, listener, handle=0): 981 """ 982 Accept the new connection, 983 verify that there aren't too many, 984 start a thread to dispatch it to the correct handler. 985 """ 986 log("_new_connection%s", (listener, socktype, handle)) 987 if self._closing: 988 netlog("ignoring new connection during shutdown") 989 return False 990 socket_info = self.socket_info.get(listener) 991 assert socktype, "cannot find socket type for %s" % listener 992 #TODO: just like add_listen_socket above, this needs refactoring 993 socket_options = self.socket_options.get(listener, {}) 994 if socktype=="named-pipe": 995 from xpra.platform.win32.namedpipes.connection import NamedPipeConnection 996 conn = NamedPipeConnection(listener.pipe_name, handle, socket_options) 997 netlog.info("New %s connection received on %s", socktype, conn.target) 998 return self.make_protocol(socktype, conn, socket_options) 999 1000 conn = accept_connection(socktype, listener, self._socket_timeout, socket_options) 1001 if conn is None: 1002 return True 1003 #limit number of concurrent network connections: 1004 if socktype not in ("unix-domain", ) and len(self._potential_protocols)>=self._max_connections: 1005 netlog.error("Error: too many connections (%i)", len(self._potential_protocols)) 1006 netlog.error(" ignoring new one: %s", conn.endpoint) 1007 conn.close() 1008 return True 1009 #from here on, we run in a thread, so we can poll (peek does) 1010 start_thread(self.handle_new_connection, "new-%s-connection" % socktype, True, 1011 args=(conn, socket_info, socket_options)) 1012 return True 1013 1014 def new_conn_err(self, conn, sock, socktype, socket_info, packet_type, msg=None): 1015 #not an xpra client 1016 netlog.error("Error: %s connection failed:", socktype) 1017 if conn.remote: 1018 netlog.error(" packet from %s", pretty_socket(conn.remote)) 1019 if socket_info: 1020 netlog.error(" received on %s", pretty_socket(socket_info)) 1021 if packet_type: 1022 netlog.error(" this packet looks like a '%s' packet", packet_type) 1023 else: 1024 netlog.error(" invalid packet format, not an xpra client?") 1025 packet_data = b"disconnect: connection setup failed" 1026 if msg: 1027 netlog.error(" %s", msg) 1028 packet_data += b", %s?" % strtobytes(msg) 1029 packet_data += b"\n" 1030 try: 1031 #default to plain text: 1032 sock.settimeout(1) 1033 if packet_type=="xpra": 1034 #try xpra packet format: 1035 from xpra.net.packet_encoding import pack_one_packet 1036 packet_data = pack_one_packet(["disconnect", "invalid protocol for this port"]) or packet_data 1037 elif packet_type=="http": 1038 #HTTP 400 error: 1039 packet_data = HTTP_UNSUPORTED 1040 conn.write(packet_data) 1041 self.timeout_add(500, self.force_close_connection, conn) 1042 except Exception as e: 1043 netlog("error sending %r: %s", packet_data, e) 1044 1045 def force_close_connection(self, conn): 1046 try: 1047 conn.close() 1048 except OSError: 1049 log("close_connection()", exc_info=True) 1050 1051 def handle_new_connection(self, conn, socket_info, socket_options): 1052 """ 1053 Use peek to decide what sort of connection this is, 1054 and start the appropriate handler for it. 1055 """ 1056 sock = conn._socket 1057 address = conn.remote 1058 socktype = conn.socktype 1059 peername = conn.endpoint 1060 1061 sockname = sock.getsockname() 1062 target = peername or sockname 1063 sock.settimeout(self._socket_timeout) 1064 1065 netlog("handle_new_connection%s sockname=%s, target=%s", 1066 (conn, socket_info, socket_options), sockname, target) 1067 #peek so we can detect invalid clients early, 1068 #or handle non-xpra / wrapped traffic: 1069 timeout = PEEK_TIMEOUT_MS 1070 if socktype=="rfb": 1071 #rfb does not send any data, waits for a server packet 1072 #so don't bother waiting for something that should never come: 1073 timeout = 0 1074 elif socktype=="unix-domain": 1075 timeout = UNIXDOMAIN_PEEK_TIMEOUT_MS 1076 peek_data = b"" 1077 if timeout>0: 1078 peek_data = peek_connection(conn, timeout) 1079 line1 = peek_data.split(b"\n")[0] 1080 netlog("socket peek=%s", ellipsizer(peek_data, limit=512)) 1081 netlog("socket peek hex=%s", hexstr(peek_data[:128])) 1082 netlog("socket peek line1=%s", ellipsizer(line1)) 1083 packet_type = guess_packet_type(peek_data) 1084 netlog("guess_packet_type(..)=%s", packet_type) 1085 1086 def ssl_wrap(): 1087 ssl_sock = self._ssl_wrap_socket(socktype, sock, socket_options) 1088 ssllog("ssl wrapped socket(%s)=%s", sock, ssl_sock) 1089 if ssl_sock is None: 1090 return None 1091 ssl_conn = SSLSocketConnection(ssl_sock, sockname, address, target, socktype) 1092 ssllog("ssl_wrap()=%s", ssl_conn) 1093 return ssl_conn 1094 1095 if socktype in ("ssl", "wss"): 1096 #verify that this isn't plain HTTP / xpra: 1097 if packet_type not in ("ssl", None): 1098 self.new_conn_err(conn, sock, socktype, socket_info, packet_type) 1099 return 1100 #always start by wrapping with SSL: 1101 ssl_conn = ssl_wrap() 1102 if not ssl_conn: 1103 return 1104 if socktype=="wss": 1105 http = True 1106 else: 1107 assert socktype=="ssl" 1108 wss = socket_options.get("wss", None) 1109 if wss is not None: 1110 if wss=="auto": 1111 http = None 1112 else: 1113 http = wss.lower() in TRUE_OPTIONS 1114 netlog("socket option wss=%s, http=%s", wss, http) 1115 else: 1116 #no "wss" option, fallback to "ssl_mode" option: 1117 if self.ssl_mode.lower()=="auto": 1118 http = None 1119 else: 1120 http = self.ssl_mode.lower()=="wss" 1121 netlog("ssl-mode=%s, http=%s", self.ssl_mode, http) 1122 if http is None: 1123 #look for HTTPS request to handle: 1124 if line1.find(b"HTTP/")>0 or peek_data.find(b"\x08http/")>0: 1125 http = True 1126 else: 1127 ssl_conn.enable_peek() 1128 peek_data = peek_connection(ssl_conn) 1129 line1 = peek_data.split(b"\n")[0] 1130 http = line1.find(b"HTTP/")>0 1131 netlog("looking for 'HTTP' in %r: %s", line1, http) 1132 if http: 1133 if not self._html: 1134 self.new_conn_err(conn, sock, socktype, socket_info, packet_type, 1135 "the builtin http server is not enabled") 1136 return 1137 self.start_http_socket(socktype, ssl_conn, socket_options, True, peek_data) 1138 else: 1139 ssl_conn._socket.settimeout(self._socket_timeout) 1140 log_new_connection(ssl_conn, socket_info) 1141 self.make_protocol(socktype, ssl_conn, socket_options) 1142 return 1143 1144 if socktype=="ws": 1145 if peek_data: 1146 #honour socket option, fallback to "ssl_mode" attribute: 1147 wss = socket_options.get("wss", "").lower() 1148 if wss: 1149 wss_upgrade = wss in TRUE_OPTIONS 1150 else: 1151 wss_upgrade = self.ssl_mode.lower() in TRUE_OPTIONS or self.ssl_mode.lower() in ("auto", "wss") 1152 if wss_upgrade and packet_type=="ssl": 1153 ssllog("ws socket receiving ssl, upgrading to wss") 1154 conn = ssl_wrap() 1155 if conn is None: 1156 return 1157 elif packet_type not in (None, "http"): 1158 self.new_conn_err(conn, sock, socktype, socket_info, packet_type) 1159 return 1160 self.start_http_socket(socktype, conn, socket_options, False, peek_data) 1161 return 1162 1163 if socktype=="rfb": 1164 if peek_data and peek_data[:4]!=b"RFB ": 1165 self.new_conn_err(conn, sock, socktype, socket_info, packet_type) 1166 return 1167 self.handle_rfb_connection(conn) 1168 return 1169 1170 if socktype=="ssh": 1171 conn = self.handle_ssh_connection(conn, socket_options) 1172 if not conn: 1173 return 1174 peek_data, line1, packet_type = b"", b"", None 1175 1176 if socktype in ("tcp", "unix-domain", "named-pipe") and peek_data: 1177 #see if the packet data is actually xpra or something else 1178 #that we need to handle via a tcp proxy, ssl wrapper or the websocket adapter: 1179 try: 1180 cont, conn, peek_data = self.may_wrap_socket(conn, socktype, socket_info, socket_options, peek_data) 1181 netlog("may_wrap_socket(..)=(%s, %s, %r)", cont, conn, peek_data) 1182 if not cont: 1183 return 1184 packet_type = guess_packet_type(peek_data) 1185 except IOError as e: 1186 netlog("socket wrapping failed", exc_info=True) 1187 self.new_conn_err(conn, sock, socktype, socket_info, None, str(e)) 1188 return 1189 1190 if packet_type not in ("xpra", None): 1191 self.new_conn_err(conn, sock, socktype, socket_info, packet_type) 1192 return 1193 1194 #get the new socket object as we may have wrapped it with ssl: 1195 sock = getattr(conn, "_socket", sock) 1196 pre_read = None 1197 if socktype=="unix-domain" and not peek_data: 1198 #try to read from this socket, 1199 #so short lived probes don't go through the whole protocol instantation 1200 try: 1201 sock.settimeout(0.001) 1202 data = conn.read(1) 1203 if not data: 1204 netlog("%s connection already closed", socktype) 1205 return 1206 pre_read = [data, ] 1207 netlog("pre_read data=%r", data) 1208 except Exception: 1209 netlog.error("Error reading from %s", conn, exc_info=True) 1210 return 1211 sock.settimeout(self._socket_timeout) 1212 log_new_connection(conn, socket_info) 1213 proto = self.make_protocol(socktype, conn, socket_options, pre_read=pre_read) 1214 if socktype=="tcp" and not peek_data and self._rfb_upgrade>0: 1215 t = self.timeout_add(self._rfb_upgrade*1000, self.try_upgrade_to_rfb, proto) 1216 self.socket_rfb_upgrade_timer[proto] = t 1217 1218 def _ssl_wrap_socket(self, socktype, sock, socket_options): 1219 ssllog("ssl_wrap_socket(%s, %s, %s)", socktype, sock, socket_options) 1220 try: 1221 kwargs = self._ssl_attributes.copy() 1222 for k,v in socket_options.items(): 1223 #options use '-' but attributes and parameters use '_': 1224 k = k.replace("-", "_") 1225 if k.startswith("ssl_"): 1226 k = k[4:] 1227 kwargs[k] = v 1228 ssl_sock = ssl_wrap_socket(sock, **kwargs) 1229 ssllog("_ssl_wrap_socket(%s, %s)=%s", sock, kwargs, ssl_sock) 1230 if ssl_sock is None: 1231 #None means EOF! (we don't want to import ssl bits here) 1232 ssllog("ignoring SSL EOF error") 1233 return ssl_sock 1234 except Exception as e: 1235 ssllog("SSL error", exc_info=True) 1236 ssl_paths = [socket_options.get(x, kwargs.get(x)) for x in ("ssl-cert", "ssl-key")] 1237 cpaths = csv("'%s'" % x for x in ssl_paths if x) 1238 log.error("Error: failed to create SSL socket") 1239 log.error(" from %s socket: %s", socktype, sock) 1240 if not cpaths: 1241 log.error(" no certificate paths specified") 1242 else: 1243 log.error(" check your certificate paths: %s", cpaths) 1244 log.error(" %s", e) 1245 return None 1246 1247 1248 def handle_ssh_connection(self, conn, socket_options): 1249 from xpra.server.ssh import make_ssh_server_connection, log as sshlog 1250 socktype = conn.socktype_wrapped 1251 none_auth = not self.auth_classes[socktype] 1252 sshlog("handle_ssh_connection(%s, %s) socktype wrapped=%s", conn, socket_options, socktype) 1253 def ssh_password_authenticate(username, password): 1254 if not POSIX or getuid()!=0: 1255 import getpass 1256 sysusername = getpass.getuser() 1257 if sysusername!=username: 1258 sshlog.warn("Warning: ssh password authentication failed,") 1259 sshlog.warn(" username does not match:") 1260 sshlog.warn(" expected '%s', got '%s'", sysusername, username) 1261 return False 1262 auth_modules = self.make_authenticators(socktype, {"username" : username}, conn) 1263 sshlog("ssh_password_authenticate auth_modules(%s, %s)=%s", username, "*"*len(password), auth_modules) 1264 for auth in auth_modules: 1265 #mimic a client challenge: 1266 digests = ["xor"] 1267 try: 1268 salt, digest = auth.get_challenge(digests) 1269 salt_digest = auth.choose_salt_digest(digests) 1270 assert digest=="xor" and salt_digest=="xor" 1271 except ValueError as e: 1272 sshlog("authentication with %s", auth, exc_info=True) 1273 sshlog.warn("Warning: ssh transport cannot use %r authentication:", auth) 1274 sshlog.warn(" %s", e) 1275 return False 1276 else: 1277 client_salt = get_salt(len(salt)) 1278 combined_salt = gendigest("xor", client_salt, salt) 1279 xored_password = gendigest("xor", password, combined_salt) 1280 r = auth.authenticate(xored_password, client_salt) 1281 sshlog("%s.authenticate(..)=%s", auth, r) 1282 if not r: 1283 return False 1284 return True 1285 return make_ssh_server_connection(conn, socket_options, none_auth=none_auth, password_auth=ssh_password_authenticate) 1286 1287 def try_upgrade_to_rfb(self, proto): 1288 self.cancel_upgrade_to_rfb_timer(proto) 1289 if proto.is_closed(): 1290 netlog("try_upgrade_to_rfb() protocol is already closed") 1291 return False 1292 conn = proto._conn 1293 netlog("may_upgrade_to_rfb() input_bytecount=%i", conn.input_bytecount) 1294 if conn.input_bytecount==0: 1295 self.upgrade_protocol_to_rfb(proto) 1296 return False 1297 1298 def upgrade_protocol_to_rfb(self, proto, data=b""): 1299 conn = proto.steal_connection() 1300 netlog("upgrade_protocol_to_rfb(%s) connection=%s", proto, conn) 1301 self._potential_protocols.remove(proto) 1302 proto.wait_for_io_threads_exit(1) 1303 conn.set_active(True) 1304 self.handle_rfb_connection(conn, data) 1305 1306 def cancel_upgrade_to_rfb_timer(self, protocol): 1307 t = self.socket_rfb_upgrade_timer.pop(protocol, None) 1308 if t: 1309 self.source_remove(t) 1310 1311 1312 def make_protocol(self, socktype, conn, socket_options, protocol_class=Protocol, pre_read=None): 1313 """ create a new xpra Protocol instance and start it """ 1314 def xpra_protocol_class(conn): 1315 """ adds xpra protocol tweaks after creating the instance """ 1316 protocol = protocol_class(self, conn, self.process_packet) 1317 protocol.large_packets.append("info-response") 1318 protocol.receive_aliases.update(self._aliases) 1319 return protocol 1320 return self.do_make_protocol(socktype, conn, socket_options, xpra_protocol_class, pre_read) 1321 1322 def do_make_protocol(self, socktype, conn, socket_options, protocol_class, pre_read=None): 1323 """ create a new Protocol instance and start it """ 1324 netlog("make_protocol%s", (socktype, conn, socket_options, protocol_class, pre_read)) 1325 socktype = socktype.lower() 1326 protocol = protocol_class(conn) 1327 protocol._pre_read = pre_read 1328 protocol.socket_type = socktype 1329 self._potential_protocols.append(protocol) 1330 protocol.authenticators = () 1331 protocol.encryption = socket_options.get("encryption", None) 1332 protocol.keyfile = socket_options.get("encryption-keyfile") or socket_options.get("keyfile") 1333 protocol.keydata = parse_encoded_bin_data(socket_options.get("encryption-keydata") or socket_options.get("keydata")) 1334 if socktype in ENCRYPTED_SOCKET_TYPES: 1335 #special case for legacy encryption code: 1336 protocol.encryption = protocol.encryption or self.tcp_encryption 1337 protocol.keyfile = protocol.keyfile or self.tcp_encryption_keyfile 1338 netlog("%s: encryption=%s, keyfile=%s", socktype, protocol.encryption, protocol.keyfile) 1339 if protocol.encryption: 1340 from xpra.net.crypto import crypto_backend_init 1341 crypto_backend_init() 1342 from xpra.net.crypto import ( 1343 ENCRYPT_FIRST_PACKET, 1344 DEFAULT_IV, 1345 DEFAULT_SALT, 1346 DEFAULT_KEY_HASH, 1347 DEFAULT_KEYSIZE, 1348 DEFAULT_ITERATIONS, 1349 INITIAL_PADDING, 1350 ) 1351 if ENCRYPT_FIRST_PACKET: 1352 authlog("encryption=%s, keyfile=%s", protocol.encryption, protocol.keyfile) 1353 password = protocol.keydata or self.get_encryption_key(None, protocol.keyfile) 1354 protocol.set_cipher_in(protocol.encryption, 1355 DEFAULT_IV, password, 1356 DEFAULT_SALT, DEFAULT_KEY_HASH, DEFAULT_KEYSIZE, 1357 DEFAULT_ITERATIONS, INITIAL_PADDING) 1358 protocol.invalid_header = self.invalid_header 1359 authlog("socktype=%s, encryption=%s, keyfile=%s", socktype, protocol.encryption, protocol.keyfile) 1360 protocol.start() 1361 self.schedule_verify_connection_accepted(protocol, self._accept_timeout) 1362 return protocol 1363 1364 def may_wrap_socket(self, conn, socktype, socket_info, socket_options, peek_data=b""): 1365 """ 1366 Returns: 1367 * a flag indicating if we should continue processing this connection 1368 * (False for webosocket and tcp proxies as they take over the socket) 1369 * the connection object (which may now be wrapped, ie: for ssl) 1370 * new peek data (which may now be empty), 1371 """ 1372 if not peek_data: 1373 netlog("may_wrap_socket: no data, not wrapping") 1374 return True, conn, peek_data 1375 line1 = peek_data.split(b"\n")[0] 1376 packet_type = guess_packet_type(peek_data) 1377 if packet_type=="xpra": 1378 netlog("may_wrap_socket: xpra protocol header '%s', not wrapping", peek_data[0]) 1379 #xpra packet header, no need to wrap this connection 1380 return True, conn, peek_data 1381 frominfo = pretty_socket(conn.remote) 1382 netlog("may_wrap_socket(..) peek_data=%s from %s", ellipsizer(peek_data), frominfo) 1383 netlog("may_wrap_socket(..) packet_type=%s", packet_type) 1384 def conn_err(msg): 1385 self.new_conn_err(conn, conn._socket, socktype, socket_info, packet_type, msg) 1386 return False, None, None 1387 if packet_type=="ssh": 1388 ssh_upgrade = socket_options.get("ssh", self.ssh_upgrade) in TRUE_OPTIONS 1389 if not ssh_upgrade: 1390 conn_err("ssh upgrades are not enabled") 1391 return False, None, None 1392 conn = self.handle_ssh_connection(conn, socket_options) 1393 return conn is not None, conn, None 1394 if packet_type=="ssl": 1395 ssl_mode = socket_options.get("ssl", self.ssl_mode) 1396 if ssl_mode in FALSE_OPTIONS: 1397 conn_err("ssl upgrades are not enabled") 1398 return False, None, None 1399 sock, sockname, address, endpoint = conn._socket, conn.local, conn.remote, conn.endpoint 1400 sock = self._ssl_wrap_socket(socktype, sock, socket_options) 1401 if sock is None: 1402 return False, None, None 1403 conn = SSLSocketConnection(sock, sockname, address, endpoint, "ssl", socket_options=socket_options) 1404 conn.socktype_wrapped = socktype 1405 #we cannot peek on SSL sockets, just clear the unencrypted data: 1406 http = False 1407 if ssl_mode=="tcp": 1408 http = False 1409 elif ssl_mode=="www": 1410 http = True 1411 elif ssl_mode=="auto" or ssl_mode in TRUE_OPTIONS: 1412 http = False 1413 #use the header to guess: 1414 if line1.find(b"HTTP/")>0 or peek_data.find(b"\x08http/1.1")>0: 1415 http = True 1416 else: 1417 conn.enable_peek() 1418 peek_data = peek_connection(conn) 1419 line1 = peek_data.split(b"\n")[0] 1420 http = line1.find(b"HTTP/")>0 1421 ssllog("may_wrap_socket SSL: %s, ssl mode=%s, http=%s", conn, ssl_mode, http) 1422 is_ssl = True 1423 else: 1424 http = line1.find(b"HTTP/")>0 1425 is_ssl = False 1426 if http: 1427 http_protocol = "https" if is_ssl else "http" 1428 http_upgrade = socket_options.get(http_protocol, self._html) not in FALSE_OPTIONS 1429 if not http_upgrade: 1430 conn_err("%s upgrades are not enabled" % http_protocol) 1431 return False, None, None 1432 self.start_http_socket(socktype, conn, socket_options, is_ssl, peek_data) 1433 return False, conn, None 1434 if self._tcp_proxy and not is_ssl: 1435 netlog.info("New tcp proxy connection received from %s", frominfo) 1436 t = start_thread(self.start_tcp_proxy, "tcp-proxy-for-%s" % frominfo, daemon=True, args=(conn, conn.remote)) 1437 netlog("may_wrap_socket handling via tcp proxy thread %s", t) 1438 return False, conn, None 1439 return True, conn, peek_data 1440 1441 def invalid_header(self, proto, data, msg=""): 1442 netlog("invalid header: %s, input_packetcount=%s, tcp_proxy=%s, html=%s, ssl=%s", 1443 ellipsizer(data), proto.input_packetcount, self._tcp_proxy, self._html, bool(self._ssl_attributes)) 1444 if data==b"RFB " and self._rfb_upgrade>0: 1445 netlog("RFB header, trying to upgrade protocol") 1446 self.cancel_upgrade_to_rfb_timer(proto) 1447 self.upgrade_protocol_to_rfb(proto, data) 1448 else: 1449 proto._invalid_header(proto, data, msg) 1450 1451 1452 ###################################################################### 1453 # http / websockets: 1454 def start_http_socket(self, socktype, conn, socket_options, is_ssl=False, peek_data=""): 1455 frominfo = pretty_socket(conn.remote) 1456 line1 = peek_data.split(b"\n")[0] 1457 http_proto = "http"+["","s"][int(is_ssl)] 1458 netlog("start_http_socket(%s, %s, %s, %s, ..) http proto=%s, line1=%r", 1459 socktype, conn, socket_options, is_ssl, http_proto, bytestostr(line1)) 1460 if line1.startswith(b"GET ") or line1.startswith(b"POST "): 1461 parts = bytestostr(line1).split(" ") 1462 httplog("New %s %s request received from %s for '%s'", http_proto, parts[0], frominfo, parts[1]) 1463 tname = "%s-request" % parts[0] 1464 req_info = "%s %s" % (http_proto, parts[0]) 1465 else: 1466 httplog("New %s connection received from %s", http_proto, frominfo) 1467 req_info = "ws"+["","s"][int(is_ssl)] 1468 tname = "%s-proxy" % req_info 1469 #we start a new thread, 1470 #only so that the websocket handler thread is named correctly: 1471 start_thread(self.start_http, "%s-for-%s" % (tname, frominfo), 1472 daemon=True, args=(socktype, conn, socket_options, is_ssl, req_info, line1, conn.remote)) 1473 1474 def start_http(self, socktype, conn, socket_options, is_ssl, req_info, line1, frominfo): 1475 httplog("start_http(%s, %s, %s, %s, %s, %r, %s) www dir=%s, headers dir=%s", 1476 socktype, conn, socket_options, is_ssl, req_info, line1, frominfo, 1477 self._www_dir, self._http_headers_dirs) 1478 try: 1479 from xpra.net.websockets.handler import WebSocketRequestHandler 1480 sock = conn._socket 1481 sock.settimeout(self._ws_timeout) 1482 def new_websocket_client(wsh): 1483 from xpra.net.websockets.protocol import WebSocketProtocol 1484 wslog("new_websocket_client(%s) socket=%s", wsh, sock) 1485 newsocktype = "ws%s" % ["","s"][int(is_ssl)] 1486 self.make_protocol(newsocktype, conn, socket_options, WebSocketProtocol) 1487 scripts = self.get_http_scripts() 1488 conn.socktype = "wss" if is_ssl else "ws" 1489 redirect_https = False 1490 if HTTP_HTTPS_REDIRECT and not req_info in ("ws", "wss"): 1491 redirect_https = not is_ssl and self.ssl_mode.lower() in TRUE_OPTIONS 1492 WebSocketRequestHandler(sock, frominfo, new_websocket_client, 1493 self._www_dir, self._http_headers_dirs, scripts, 1494 redirect_https) 1495 return 1496 except (IOError, ValueError) as e: 1497 httplog("start_http%s", (socktype, conn, is_ssl, req_info, frominfo), exc_info=True) 1498 err = e.args[0] 1499 if err==1 and line1 and line1[0]==0x16: 1500 l = httplog 1501 elif err in (errno.EPIPE, errno.ECONNRESET): 1502 l = httplog 1503 else: 1504 l = httplog.error 1505 l("Error: %s request failure", req_info) 1506 l(" errno=%s", err) 1507 l(" for client %s:", pretty_socket(frominfo)) 1508 if line1 and line1[0]>=128 or line1[0]==0x16: 1509 l(" request as hex: '%s'", hexstr(line1)) 1510 else: 1511 l(" request: %r", bytestostr(line1)) 1512 l(" %s", e) 1513 except Exception as e: 1514 wslog.error("Error: %s request failure for client %s:", req_info, pretty_socket(frominfo), exc_info=True) 1515 try: 1516 conn.close() 1517 except Exception as ce: 1518 wslog("error closing connection following error: %s", ce) 1519 1520 1521 def get_http_scripts(self): 1522 return self._http_scripts 1523 1524 def http_err(self, handler, code=500): #pylint: disable=useless-return 1525 handler.send_response(code) 1526 return None 1527 1528 def http_query_dict(self, path): 1529 return dict(parse_qsl(urlparse(path).query)) 1530 1531 def send_json_response(self, handler, data): 1532 import json #pylint: disable=import-outside-toplevel 1533 return self.send_http_response(handler, json.dumps(data), "application/json") 1534 1535 def send_icon(self, handler, icon_type, icon_data): 1536 httplog("send_icon%s", (handler, icon_type, ellipsizer(icon_data))) 1537 if not icon_data: 1538 icon_filename = get_icon_filename("noicon.png") 1539 icon_data = load_binary_file(icon_filename) 1540 icon_type = "png" 1541 httplog("using fallback transparent icon") 1542 if icon_type=="svg" and icon_data: 1543 from xpra.codecs.icon_util import svg_to_png #pylint: disable=import-outside-toplevel 1544 #call svg_to_png via the main thread, 1545 #and wait for it to complete via an Event: 1546 icon = [icon_data, icon_type] 1547 event = threading.Event() 1548 def convert(): 1549 icon[0] = svg_to_png(None, icon_data, 48, 48) 1550 icon[1] = "png" 1551 event.set() 1552 self.idle_add(convert) 1553 event.wait() 1554 icon_data, icon_type = icon 1555 if icon_type in ("png", "jpeg", "svg", "webp"): 1556 mime_type = "image/%s" % icon_type 1557 else: 1558 mime_type = "application/octet-stream" 1559 return self.send_http_response(handler, icon_data, mime_type) 1560 1561 def http_menu_request(self, handler): 1562 xdg_menu = self.menu_provider.get_menu_data(remove_icons=True) 1563 return self.send_json_response(handler, xdg_menu or "not available") 1564 1565 def http_desktop_menu_request(self, handler): 1566 xsessions = self.menu_provider.get_desktop_sessions(remove_icons=True) 1567 return self.send_json_response(handler, xsessions or "not available") 1568 1569 def http_menu_icon_request(self, handler): 1570 def invalid_path(): 1571 httplog("invalid menu-icon request path '%s'", handler.path) 1572 return self.http_err(404) 1573 parts = unquote(handler.path).split("/MenuIcon/", 1) 1574 #ie: "/menu-icon/a/b" -> ['', 'a/b'] 1575 if len(parts)<2: 1576 return invalid_path() 1577 path = parts[1].split("/") 1578 #ie: "a/b" -> ['a', 'b'] 1579 category_name = path[0] 1580 if len(path)<2: 1581 #only the category is present 1582 app_name = None 1583 else: 1584 app_name = path[1] 1585 httplog("http_menu_icon_request: category_name=%s, app_name=%s", category_name, app_name) 1586 icon_type, icon_data = self.menu_provider.get_menu_icon(category_name, app_name) 1587 return self.send_icon(handler, icon_type, icon_data) 1588 1589 def http_desktop_menu_icon_request(self, handler): 1590 def invalid_path(): 1591 httplog("invalid menu-icon request path '%s'", handler.path) 1592 return self.http_err(handler, 404) 1593 parts = unquote(handler.path).split("/DesktopMenuIcon/", 1) 1594 #ie: "/menu-icon/wmname" -> ['', 'sessionname'] 1595 if len(parts)<2: 1596 return invalid_path() 1597 #in case the sessionname is followed by a slash: 1598 sessionname = parts[1].split("/")[0] 1599 httplog("http_desktop_menu_icon_request: sessionname=%s", sessionname) 1600 icon_type, icon_data = self.menu_provider.get_desktop_menu_icon(sessionname) 1601 return self.send_icon(handler, icon_type, icon_data) 1602 1603 def _filter_display_dict(self, display_dict, *whitelist): 1604 displays_info = {} 1605 for display, info in display_dict.items(): 1606 displays_info[display] = dict((k,v) for k,v in info.items() if k in whitelist) 1607 httplog("_filter_display_dict(%s)=%s", display_dict, displays_info) 1608 return displays_info 1609 1610 def http_displays_request(self, handler): 1611 displays = self.get_displays() 1612 displays_info = self._filter_display_dict(displays, "state", "wmname", "xpra-server-mode") 1613 return self.send_json_response(handler, displays_info) 1614 1615 def get_displays(self): 1616 from xpra.scripts.main import get_displays_info #pylint: disable=import-outside-toplevel 1617 return get_displays_info(self.dotxpra) 1618 1619 def http_sessions_request(self, handler): 1620 sessions = self.get_xpra_sessions() 1621 sessions_info = self._filter_display_dict(sessions, "state", "username", "session-type", "session-name", "uuid") 1622 return self.send_json_response(handler, sessions_info) 1623 1624 def get_xpra_sessions(self): 1625 from xpra.scripts.main import get_xpra_sessions #pylint: disable=import-outside-toplevel 1626 return get_xpra_sessions(self.dotxpra) 1627 1628 def http_info_request(self, handler): 1629 return self.send_json_response(handler, self.get_http_info()) 1630 1631 def get_http_info(self) -> dict: 1632 return { 1633 "mode" : self.get_server_mode(), 1634 "type" : "Python", 1635 "uuid" : self.uuid, 1636 } 1637 1638 def http_status_request(self, handler): 1639 return self.send_http_response(handler, "ready") 1640 1641 def send_http_response(self, handler, content, content_type="text/plain"): 1642 if not content: 1643 handler.send_response(404) 1644 else: 1645 handler.send_response(200) 1646 handler.extra_headers.update({ 1647 "Content-type" : content_type, 1648 "Content-Length" : len(content), 1649 }) 1650 handler.end_headers() 1651 if isinstance(content, str): 1652 content = content.encode("latin1") 1653 return content 1654 1655 1656 def start_tcp_proxy(self, conn, frominfo): 1657 proxylog("start_tcp_proxy(%s, %s)", conn, frominfo) 1658 #connect to web server: 1659 try: 1660 host, port = self._tcp_proxy.split(":", 1) 1661 port = int(port) 1662 except ValueError as e: 1663 proxylog.error("Error: invalid tcp proxy value '%s'", self._tcp_proxy) 1664 proxylog.error(" %s", e) 1665 conn.close() 1666 return 1667 try: 1668 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 1669 sock.settimeout(10) 1670 sock.connect((host, int(port))) 1671 sock.settimeout(None) 1672 tcp_server_connection = SocketConnection(sock, sock.getsockname(), sock.getpeername(), 1673 "tcp-proxy-for-%s" % frominfo, "tcp") 1674 except Exception as e: 1675 proxylog("start_tcp_proxy(%s, %s)", conn, frominfo, exc_info=True) 1676 proxylog.error("Error: failed to connect to TCP proxy endpoint: %s:%s", host, port) 1677 proxylog.error(" %s", e) 1678 conn.close() 1679 return 1680 proxylog("proxy connected to tcp server at %s:%s : %s", host, port, tcp_server_connection) 1681 sock.settimeout(self._socket_timeout) 1682 1683 #we can use blocking sockets for the client: 1684 conn.settimeout(None) 1685 #but not for the server, which could deadlock on exit: 1686 sock.settimeout(1) 1687 1688 #now start forwarding: 1689 from xpra.scripts.fdproxy import XpraProxy #pylint: disable=import-outside-toplevel 1690 p = XpraProxy(frominfo, conn, tcp_server_connection, self.tcp_proxy_quit) 1691 self._tcp_proxy_clients.append(p) 1692 proxylog.info("client connection from %s forwarded to proxy server on %s:%s", frominfo, host, port) 1693 p.start_threads() 1694 1695 1696 def stop_tcp_proxy_clients(self): 1697 tpc = tuple(self._tcp_proxy_clients) 1698 netlog("stop_tcp_proxy_clients() stopping %s tcp proxy clients: %s", len(tpc), tpc) 1699 self._tcp_proxy_clients = [] 1700 for p in tpc: 1701 p.quit() 1702 1703 def tcp_proxy_quit(self, proxy): 1704 proxylog("tcp_proxy_quit(%s)", proxy) 1705 if proxy in self._tcp_proxy_clients: 1706 self._tcp_proxy_clients.remove(proxy) 1707 1708 def is_timedout(self, protocol): 1709 #subclasses may override this method (ServerBase does) 1710 v = not protocol.is_closed() and protocol in self._potential_protocols and \ 1711 protocol not in self._tcp_proxy_clients 1712 netlog("is_timedout(%s)=%s", protocol, v) 1713 return v 1714 1715 def schedule_verify_connection_accepted(self, protocol, timeout=60): 1716 t = self.timeout_add(timeout*1000, self.verify_connection_accepted, protocol) 1717 self.socket_verify_timer[protocol] = t 1718 1719 def verify_connection_accepted(self, protocol): 1720 self.cancel_verify_connection_accepted(protocol) 1721 if self.is_timedout(protocol): 1722 conn = getattr(protocol, "_conn", None) 1723 log.error("Error: connection timed out: %s", conn or protocol) 1724 elapsed = monotonic()-protocol.start_time 1725 log.error(" after %i seconds", elapsed) 1726 if conn: 1727 log.error(" received %i bytes", conn.input_bytecount) 1728 if conn.input_bytecount==0: 1729 try: 1730 data = conn.peek(200) 1731 except Exception: 1732 data = b"" 1733 if data: 1734 log.error(" read buffer=%r", data) 1735 packet_type = guess_packet_type(data) 1736 if packet_type: 1737 log.error(" looks like '%s' ", packet_type) 1738 self.send_disconnect(protocol, LOGIN_TIMEOUT) 1739 1740 def cancel_verify_connection_accepted(self, protocol): 1741 t = self.socket_verify_timer.pop(protocol, None) 1742 if t: 1743 self.source_remove(t) 1744 1745 def send_disconnect(self, proto, *reasons): 1746 netlog("send_disconnect(%s, %s)", proto, reasons) 1747 self.cancel_verify_connection_accepted(proto) 1748 self.cancel_upgrade_to_rfb_timer(proto) 1749 if proto.is_closed(): 1750 return 1751 proto.send_disconnect(reasons) 1752 self.timeout_add(1000, self.force_disconnect, proto) 1753 1754 def force_disconnect(self, proto): 1755 netlog("force_disconnect(%s)", proto) 1756 self.cleanup_protocol(proto) 1757 self.cancel_verify_connection_accepted(proto) 1758 self.cancel_upgrade_to_rfb_timer(proto) 1759 proto.close() 1760 1761 def disconnect_client(self, protocol, reason, *extra): 1762 netlog("disconnect_client(%s, %s, %s)", protocol, reason, extra) 1763 if protocol and not protocol.is_closed(): 1764 self.disconnect_protocol(protocol, reason, *extra) 1765 1766 def disconnect_protocol(self, protocol, *reasons): 1767 netlog("disconnect_protocol(%s, %s)", protocol, reasons) 1768 i = str(reasons[0]) 1769 if len(reasons)>1: 1770 i += " (%s)" % csv(reasons[1:]) 1771 proto_info = " %s" % protocol 1772 try: 1773 conn = protocol._conn 1774 info = conn.get_info() 1775 endpoint = info.get("endpoint") 1776 if endpoint: 1777 proto_info = " %s" % pretty_socket(endpoint) 1778 else: 1779 proto_info = " %s" % pretty_socket(conn.local) 1780 except (KeyError, AttributeError): 1781 pass 1782 self._log_disconnect(protocol, "Disconnecting client%s:", proto_info) 1783 self._log_disconnect(protocol, " %s", i) 1784 self.cancel_verify_connection_accepted(protocol) 1785 self.cancel_upgrade_to_rfb_timer(protocol) 1786 protocol.send_disconnect(reasons) 1787 self.cleanup_protocol(protocol) 1788 1789 def cleanup_protocol(self, protocol): 1790 pass 1791 1792 def _process_disconnect(self, proto, packet): 1793 info = bytestostr(packet[1]) 1794 if len(packet)>2: 1795 info += " (%s)" % csv(bytestostr(x) for x in packet[2:]) 1796 #only log protocol info if there is more than one client: 1797 proto_info = self._disconnect_proto_info(proto) 1798 self._log_disconnect(proto, "client%s has requested disconnection: %s", proto_info, info) 1799 self.disconnect_protocol(proto, CLIENT_REQUEST) 1800 1801 def _log_disconnect(self, _proto, *args): 1802 netlog.info(*args) 1803 1804 def _disconnect_proto_info(self, _proto): 1805 #overriden in server_base in case there is more than one protocol 1806 return "" 1807 1808 def _process_connection_lost(self, proto, packet): 1809 netlog("process_connection_lost(%s, %s)", proto, packet) 1810 self.cancel_verify_connection_accepted(proto) 1811 self.cancel_upgrade_to_rfb_timer(proto) 1812 if proto in self._potential_protocols: 1813 if not proto.is_closed(): 1814 self._log_disconnect(proto, "Connection lost") 1815 self._potential_protocols.remove(proto) 1816 self.cleanup_protocol(proto) 1817 1818 def _process_gibberish(self, proto, packet): 1819 message, data = packet[1:3] 1820 netlog("Received uninterpretable nonsense from %s: %s", proto, message) 1821 netlog(" data: %s", ellipsizer(data)) 1822 self.disconnect_client(proto, message) 1823 1824 def _process_invalid(self, protocol, packet): 1825 message, data = packet[1:3] 1826 netlog("Received invalid packet: %s", message) 1827 netlog(" data: %s", ellipsizer(data)) 1828 self.disconnect_client(protocol, message) 1829 1830 1831 ###################################################################### 1832 # hello / authentication: 1833 def send_version_info(self, proto, full=False): 1834 version = XPRA_VERSION 1835 if full: 1836 version = full_version_str() 1837 proto.send_now(("hello", {"version" : version})) 1838 #client is meant to close the connection itself, but just in case: 1839 self.timeout_add(5*1000, self.send_disconnect, proto, DONE, "version sent") 1840 1841 def _process_hello(self, proto, packet): 1842 capabilities = packet[1] 1843 c = typedict(capabilities) 1844 proto.set_compression_level(c.intget("compression_level", self.compression_level)) 1845 proto.enable_compressor_from_caps(c) 1846 if not proto.enable_encoder_from_caps(c): 1847 #this should never happen: 1848 #if we got here, we parsed a packet from the client! 1849 #(maybe the client used an encoding it claims not to support?) 1850 self.disconnect_client(proto, PROTOCOL_ERROR, "failed to negotiate a packet encoder") 1851 return 1852 1853 log("process_hello: capabilities=%s", capabilities) 1854 if c.boolget("version_request"): 1855 self.send_version_info(proto, c.boolget("full-version-request")) 1856 return 1857 #verify version: 1858 remote_version = c.strget("version") 1859 verr = version_compat_check(remote_version) 1860 if verr is not None: 1861 self.disconnect_client(proto, VERSION_ERROR, "incompatible version: %s" % verr) 1862 proto.close() 1863 return 1864 #this will call auth_verified if successful 1865 #it may also just send challenge packets, 1866 #in which case we'll end up here parsing the hello again 1867 start_thread(self.verify_auth, "authenticate connection", daemon=True, args=(proto, packet, c)) 1868 1869 def make_authenticators(self, socktype, remote, conn): 1870 authlog("make_authenticators%s socket options=%s", (socktype, remote, conn), conn.options) 1871 sock_options = conn.options 1872 sock_auth = sock_options.get("auth", "") 1873 if sock_auth: 1874 #per socket authentication option: 1875 #ie: --bind-tcp=0.0.0.0:10000,auth=hosts,auth=file:filename=pass.txt:foo=bar 1876 # -> sock_auth = ["hosts", "file:filename=pass.txt:foo=bar"] 1877 if not isinstance(sock_auth, list): 1878 sock_auth = sock_auth.split(",") 1879 auth_classes = self.get_auth_modules(conn.socktype, sock_auth) 1880 else: 1881 #use authentication configuration defined for all sockets of this type: 1882 auth_classes = self.auth_classes[socktype] 1883 i = 0 1884 authenticators = [] 1885 if auth_classes: 1886 authlog("creating authenticators %s for %s", 1887 csv(auth_classes), socktype) 1888 for auth_name, _, aclass, options in auth_classes: 1889 opts = dict(options) 1890 opts["remote"] = remote 1891 opts.update(sock_options) 1892 opts["connection"] = conn 1893 def parse_socket_dirs(v): 1894 if isinstance(v, (tuple, list)): 1895 return v 1896 #FIXME: this can never actually match "," 1897 # because we already split connection options with it. 1898 # We need to change the connection options parser to be smarter 1899 return str(v).split(",") 1900 opts["socket-dirs"] = parse_socket_dirs(opts.get("socket-dirs", self._socket_dirs)) 1901 try: 1902 for o in ("self", ): 1903 if o in opts: 1904 raise Exception("illegal authentication module options '%s'" % o) 1905 authlog("%s : %s(%s)", auth_name, aclass, opts) 1906 authenticator = aclass(**opts) 1907 except Exception: 1908 authlog("%s%s", aclass, (opts,), exc_info=True) 1909 raise 1910 authlog("authenticator %i=%s", i, authenticator) 1911 authenticators.append(authenticator) 1912 i += 1 1913 return tuple(authenticators) 1914 1915 def send_challenge(self, proto, salt, auth_caps, digest, salt_digest, prompt="password"): 1916 proto.send_now(("challenge", salt, auth_caps or {}, digest, salt_digest, prompt)) 1917 self.schedule_verify_connection_accepted(proto, CHALLENGE_TIMEOUT) 1918 1919 def auth_failed(self, proto, msg): 1920 authlog.warn("Warning: authentication failed") 1921 authlog.warn(" %s", msg) 1922 self.timeout_add(1000, self.disconnect_client, proto, msg) 1923 1924 def verify_auth(self, proto, packet, c): 1925 def auth_failed(msg): 1926 self.auth_failed(proto, msg) 1927 remote = {} 1928 for key in ("hostname", "uuid", "session-id", "username", "name"): 1929 v = c.strget(key) 1930 if v: 1931 remote[key] = v 1932 conn = proto._conn 1933 #authenticator: 1934 if not proto.authenticators: 1935 socktype = conn.socktype_wrapped 1936 try: 1937 proto.authenticators = self.make_authenticators(socktype, remote, conn) 1938 except Exception as e: 1939 authlog("instantiating authenticator for %s", socktype, exc_info=True) 1940 authlog.error("Error instantiating authenticator for %s:", proto.socket_type) 1941 authlog.error(" %s", e) 1942 auth_failed(str(e)) 1943 return 1944 1945 digest_modes = c.strtupleget("digest", ("hmac", )) 1946 salt_digest_modes = c.strtupleget("salt-digest", ("xor",)) 1947 #client may have requested encryption: 1948 auth_caps = self.setup_encryption(proto, c) 1949 if auth_caps is None: 1950 return 1951 1952 def send_fake_challenge(): 1953 #fake challenge so the client will send the real hello: 1954 salt = get_salt() 1955 digest = choose_digest(digest_modes) 1956 salt_digest = choose_digest(salt_digest_modes) 1957 self.send_challenge(proto, salt, auth_caps, digest, salt_digest) 1958 1959 #skip the authentication module we have "passed" already: 1960 remaining_authenticators = tuple(x for x in proto.authenticators if not x.passed) 1961 1962 client_expects_challenge = c.strget("challenge") is not None 1963 if client_expects_challenge and not remaining_authenticators: 1964 authlog.warn("Warning: client expects an authentication challenge,") 1965 authlog.warn(" sending a fake one") 1966 send_fake_challenge() 1967 return 1968 1969 authlog("processing authentication with %s, remaining=%s, digest_modes=%s, salt_digest_modes=%s", 1970 proto.authenticators, remaining_authenticators, digest_modes, salt_digest_modes) 1971 #verify each remaining authenticator: 1972 for index, authenticator in enumerate(proto.authenticators): 1973 if authenticator not in remaining_authenticators: 1974 authlog("authenticator[%i]=%s (already passed)", index, authenticator) 1975 continue 1976 req = authenticator.requires_challenge() 1977 authlog("authenticator[%i]=%s, requires-challenge=%s, challenge-sent=%s", 1978 index, authenticator, req, authenticator.challenge_sent) 1979 if not req: 1980 #this authentication module does not need a challenge 1981 #(ie: "peercred" or "none") 1982 if not authenticator.authenticate(c): 1983 auth_failed("%s authentication failed" % authenticator) 1984 return 1985 authenticator.passed = True 1986 authlog("authentication passed for %s (no challenge provided)", authenticator) 1987 continue 1988 if not authenticator.challenge_sent: 1989 #we'll re-schedule this when we call send_challenge() 1990 #as the authentication module is free to take its time 1991 self.cancel_verify_connection_accepted(proto) 1992 #note: we may have received a challenge_response from a previous auth module's challenge 1993 challenge = authenticator.get_challenge(digest_modes) 1994 if challenge is None: 1995 if authenticator.requires_challenge(): 1996 auth_failed("invalid state, unexpected challenge response") 1997 return 1998 authlog.warn("Warning: authentication module '%s' does not require any credentials", authenticator) 1999 authlog.warn(" but the client %s supplied them", proto) 2000 #fake challenge so the client will send the real hello: 2001 send_fake_challenge() 2002 return 2003 salt, digest = challenge 2004 actual_digest = digest.split(":", 1)[0] 2005 authlog("get_challenge(%s)= %s, %s", digest_modes, hexstr(salt), digest) 2006 countinfo = "" 2007 if len(proto.authenticators)>1: 2008 countinfo += " (%i of %i)" % (index+1, len(proto.authenticators)) 2009 authlog.info("Authentication required by %s authenticator module%s", authenticator, countinfo) 2010 authlog.info(" sending challenge using %s digest over %s connection", actual_digest, conn.socktype_wrapped) 2011 if actual_digest not in digest_modes: 2012 auth_failed("cannot proceed without %s digest support" % actual_digest) 2013 return 2014 salt_digest = authenticator.choose_salt_digest(salt_digest_modes) 2015 if salt_digest in ("xor", "des"): 2016 if not LEGACY_SALT_DIGEST: 2017 auth_failed("insecure salt digest '%s' rejected" % salt_digest) 2018 return 2019 log.warn("Warning: using legacy support for '%s' salt digest", salt_digest) 2020 authlog("sending challenge: %r", authenticator.prompt) 2021 self.send_challenge(proto, salt, auth_caps, digest, salt_digest, authenticator.prompt) 2022 return 2023 if not authenticator.authenticate(c): 2024 auth_failed("authentication failed") 2025 return 2026 authlog("all authentication modules passed") 2027 self.auth_verified(proto, packet, auth_caps) 2028 2029 def auth_verified(self, proto, packet, auth_caps): 2030 capabilities = packet[1] 2031 c = typedict(capabilities) 2032 command_req = tuple(net_utf8(x) for x in c.tupleget("command_request")) 2033 if command_req: 2034 #call from UI thread: 2035 authlog("auth_verified(..) command request=%s", command_req) 2036 self.idle_add(self.handle_command_request, proto, *command_req) 2037 return 2038 #continue processing hello packet in UI thread: 2039 self.idle_add(self.call_hello_oked, proto, packet, c, auth_caps) 2040 2041 2042 def setup_encryption(self, proto, c : typedict): 2043 def auth_failed(msg): 2044 self.auth_failed(proto, msg) 2045 return None 2046 #client may have requested encryption: 2047 cipher = c.strget("cipher") 2048 cipher_mode = c.strget("cipher.mode") 2049 cipher_iv = c.strget("cipher.iv") 2050 key_salt = c.strget("cipher.key_salt") 2051 auth_caps = {} 2052 if cipher and cipher_iv: 2053 #check that the server supports encryption: 2054 if not proto.encryption: 2055 return auth_failed("the server does not support encryption on this connection") 2056 server_cipher = proto.encryption.split("-")[0] 2057 if server_cipher!=cipher: 2058 return auth_failed("the server is configured for '%s' not '%s' as requested by the client" % ( 2059 server_cipher, cipher)) 2060 from xpra.net.crypto import ( 2061 DEFAULT_PADDING, ALL_PADDING_OPTIONS, ENCRYPTION_CIPHERS, 2062 DEFAULT_MODE, DEFAULT_KEY_HASH, DEFAULT_KEYSIZE, 2063 KEY_HASHES, DEFAULT_KEY_STRETCH, 2064 new_cipher_caps, 2065 ) 2066 if not cipher_mode: 2067 cipher_mode = DEFAULT_MODE 2068 if proto.encryption.find("-")>0: 2069 #server specifies the mode to use 2070 server_cipher_mode = proto.encryption.split("-")[1] 2071 if server_cipher_mode!=cipher_mode: 2072 return auth_failed("the server is configured for %s-%s not %s-%s as requested by the client" % ( 2073 server_cipher, server_cipher_mode, cipher, cipher_mode)) 2074 iterations = c.intget("cipher.key_stretch_iterations") 2075 key_hash = c.strget("cipher.key_hash", DEFAULT_KEY_HASH) 2076 key_stretch = c.strget("cipher.key_stretch", DEFAULT_KEY_STRETCH) 2077 padding = c.strget("cipher.padding", DEFAULT_PADDING) 2078 padding_options = c.strtupleget("cipher.padding.options", (DEFAULT_PADDING,)) 2079 if cipher not in ENCRYPTION_CIPHERS: 2080 authlog.warn("Warning: unsupported cipher: %s", cipher) 2081 if ENCRYPTION_CIPHERS: 2082 authlog.warn(" should be: %s", csv(ENCRYPTION_CIPHERS)) 2083 return auth_failed("unsupported cipher") 2084 if key_stretch!="PBKDF2": 2085 return auth_failed("unsupported key stretching %s" % key_stretch) 2086 encryption_key = proto.keydata or self.get_encryption_key(proto.authenticators, proto.keyfile) 2087 if encryption_key is None: 2088 return auth_failed("encryption key is missing") 2089 if padding not in ALL_PADDING_OPTIONS: 2090 return auth_failed("unsupported padding: %s" % padding) 2091 if key_hash not in KEY_HASHES: 2092 return auth_failed("unsupported key hash algorithm: %s" % key_hash) 2093 cryptolog("setting output cipher using %s encryption key '%s'", 2094 cipher, ellipsizer(encryption_key)) 2095 key_size = c.intget("cipher.key_size", DEFAULT_KEYSIZE) 2096 proto.set_cipher_out(cipher+"-"+cipher_mode, cipher_iv, 2097 encryption_key, key_salt, key_hash, key_size, iterations, padding) 2098 #use the same cipher as used by the client: 2099 auth_caps = new_cipher_caps(proto, cipher, cipher_mode or DEFAULT_MODE, encryption_key, padding_options) 2100 cryptolog("server cipher=%s", auth_caps) 2101 return auth_caps 2102 conn = proto._conn 2103 if proto.encryption: 2104 cryptolog("client does not provide encryption tokens") 2105 return auth_failed("missing encryption tokens") 2106 return {} 2107 2108 def get_encryption_key(self, authenticators=None, keyfile=None): 2109 #if we have a keyfile specified, use that: 2110 authlog("get_encryption_key(%s, %s)", authenticators, keyfile) 2111 if keyfile: 2112 authlog("loading encryption key from keyfile: %s", keyfile) 2113 v = filedata_nocrlf(keyfile) 2114 if v: 2115 return v 2116 v = os.environ.get('XPRA_ENCRYPTION_KEY') 2117 if v: 2118 authlog("using encryption key from %s environment variable", 'XPRA_ENCRYPTION_KEY') 2119 return v 2120 if authenticators: 2121 for authenticator in authenticators: 2122 v = authenticator.get_password() 2123 if v: 2124 authlog("using password from authenticator %s", authenticator) 2125 return v 2126 return None 2127 2128 def call_hello_oked(self, proto, packet, c, auth_caps): 2129 try: 2130 if SIMULATE_SERVER_HELLO_ERROR: 2131 raise Exception("Simulating a server error") 2132 self.hello_oked(proto, packet, c, auth_caps) 2133 except ClientException as e: 2134 log("call_hello_oked(%s, %s, %s, %s)", proto, packet, ellipsizer(c), auth_caps, exc_info=True) 2135 log.error("Error setting up new connection for") 2136 log.error(" %s:", proto) 2137 log.error(" %s", e) 2138 self.disconnect_client(proto, SERVER_ERROR, str(e)) 2139 except Exception as e: 2140 #log exception but don't disclose internal details to the client 2141 log.error("server error processing new connection from %s: %s", proto, e, exc_info=True) 2142 self.disconnect_client(proto, SERVER_ERROR, "error accepting new connection") 2143 2144 def hello_oked(self, proto, _packet, c, _auth_caps): 2145 generic_request = c.strget("request") 2146 def is_req(mode): 2147 return generic_request==mode or c.boolget("%s_request" % mode) 2148 if is_req("connect_test"): 2149 ctr = c.strget("connect_test_request") 2150 response = {"connect_test_response" : ctr} 2151 proto.send_now(("hello", response)) 2152 return True 2153 if is_req("id"): 2154 self.send_id_info(proto) 2155 return True 2156 if self._closing: 2157 self.disconnect_client(proto, SERVER_EXIT, "server is shutting down") 2158 return True 2159 if is_req("info"): 2160 self.send_hello_info(proto) 2161 return True 2162 return False 2163 2164 2165 def accept_client(self, proto, c): 2166 #max packet size from client (the biggest we can get are clipboard packets) 2167 netlog("accept_client(%s, %s)", proto, c) 2168 #note: when uploading files, we send them in chunks smaller than this size 2169 proto.max_packet_size = MAX_PACKET_SIZE 2170 proto.parse_remote_caps(c) 2171 self.accept_protocol(proto) 2172 2173 def accept_protocol(self, proto): 2174 if proto in self._potential_protocols: 2175 self._potential_protocols.remove(proto) 2176 self.reset_server_timeout(False) 2177 self.cancel_verify_connection_accepted(proto) 2178 self.cancel_upgrade_to_rfb_timer(proto) 2179 2180 def reset_server_timeout(self, reschedule=True): 2181 timeoutlog("reset_server_timeout(%s) server_idle_timeout=%s, server_idle_timer=%s", 2182 reschedule, self.server_idle_timeout, self.server_idle_timer) 2183 if self.server_idle_timeout<=0: 2184 return 2185 if self.server_idle_timer: 2186 self.source_remove(self.server_idle_timer) 2187 self.server_idle_timer = None 2188 if reschedule: 2189 self.server_idle_timer = self.timeout_add(self.server_idle_timeout*1000, self.server_idle_timedout) 2190 2191 def server_idle_timedout(self): 2192 timeoutlog.info("No valid client connections for %s seconds, exiting the server", self.server_idle_timeout) 2193 self.clean_quit(False) 2194 2195 2196 def make_hello(self, source=None): 2197 now = time() 2198 capabilities = flatten_dict(get_network_caps()) 2199 if source is None or source.wants_versions: 2200 capabilities.update(flatten_dict(get_server_info())) 2201 capabilities.update({ 2202 "version" : XPRA_VERSION, 2203 "start_time" : int(self.start_time), 2204 "current_time" : int(now), 2205 "elapsed_time" : int(now - self.start_time), 2206 "server_type" : "core", 2207 "server.mode" : self.get_server_mode(), 2208 "hostname" : socket.gethostname(), 2209 }) 2210 if source is None or source.wants_features: 2211 capabilities.update({ 2212 "readonly-server" : True, 2213 "readonly" : self.readonly, 2214 "server-log" : os.environ.get("XPRA_SERVER_LOG", ""), 2215 }) 2216 if source is None or source.wants_versions: 2217 capabilities["uuid"] = get_user_uuid() 2218 mid = get_machine_id() 2219 if mid: 2220 capabilities["machine_id"] = mid 2221 if self.session_name: 2222 capabilities["session_name"] = self.session_name 2223 return capabilities 2224 2225 2226 ###################################################################### 2227 # info: 2228 def send_id_info(self, proto): 2229 log("id info request from %s", proto._conn) 2230 proto.send_now(("hello", self.get_session_id_info())) 2231 2232 def get_session_id_info(self) -> dict: 2233 #minimal information for identifying the session 2234 id_info = { 2235 "session-type" : self.session_type, 2236 "session-name" : self.session_name, 2237 "uuid" : self.uuid, 2238 "platform" : sys.platform, 2239 "pid" : os.getpid(), 2240 "machine-id" : get_machine_id(), 2241 } 2242 display = os.environ.get("DISPLAY") 2243 if display: 2244 id_info["display"] = display 2245 return id_info 2246 2247 def send_hello_info(self, proto): 2248 #Note: this can be overriden in subclasses to pass arguments to get_ui_info() 2249 #(ie: see server_base) 2250 log.info("processing info request from %s", proto._conn) 2251 def cb(proto, info): 2252 self.do_send_info(proto, info) 2253 self.get_all_info(cb, proto) 2254 2255 def do_send_info(self, proto, info): 2256 proto.send_now(("hello", notypedict(info))) 2257 2258 def get_all_info(self, callback, proto=None, *args): 2259 start = monotonic() 2260 ui_info = self.get_ui_info(proto, *args) 2261 end = monotonic() 2262 log("get_all_info: ui info collected in %ims", (end-start)*1000) 2263 start_thread(self._get_info_in_thread, "Info", daemon=True, args=(callback, ui_info, proto, args)) 2264 2265 def _get_info_in_thread(self, callback, ui_info, proto, args): 2266 log("get_info_in_thread%s", (callback, {}, proto, args)) 2267 start = monotonic() 2268 #this runs in a non-UI thread 2269 try: 2270 info = self.get_info(proto, *args) 2271 merge_dicts(ui_info, info) 2272 except Exception: 2273 log.error("Error during info collection using %s", self.get_info, exc_info=True) 2274 end = monotonic() 2275 log("get_all_info: non ui info collected in %ims", (end-start)*1000) 2276 callback(proto, ui_info) 2277 2278 def get_ui_info(self, _proto, *_args) -> dict: 2279 #this function is for info which MUST be collected from the UI thread 2280 return {} 2281 2282 def get_thread_info(self, proto) -> dict: 2283 return get_thread_info(proto) 2284 2285 def get_minimal_server_info(self) -> dict: 2286 now = time() 2287 info = { 2288 "mode" : self.get_server_mode(), 2289 "session-type" : self.session_type, 2290 "type" : "Python", 2291 "python" : {"version" : platform.python_version()}, 2292 "start_time" : int(self.start_time), 2293 "current_time" : int(now), 2294 "elapsed_time" : int(now - self.start_time), 2295 "uuid" : self.uuid, 2296 "machine-id" : get_machine_id(), 2297 } 2298 return info 2299 2300 def get_server_info(self) -> dict: 2301 #this function is for non UI thread info 2302 si = {} 2303 si.update(self.get_minimal_server_info()) 2304 si.update(get_server_info()) 2305 si.update({ 2306 "argv" : sys.argv, 2307 "path" : sys.path, 2308 "exec_prefix" : sys.exec_prefix, 2309 "executable" : sys.executable, 2310 "idle-timeout" : int(self.server_idle_timeout), 2311 }) 2312 if self.pidfile: 2313 si["pidfile"] = { 2314 "path" : self.pidfile, 2315 "inode" : self.pidinode, 2316 } 2317 logfile = os.environ.get("XPRA_SERVER_LOG") 2318 if logfile: 2319 si["log-file"] = logfile 2320 if POSIX: 2321 try: 2322 si["load"] = tuple(int(x*1000) for x in os.getloadavg()) 2323 except OSError: 2324 log("cannot get load average", exc_info=True) 2325 if self.original_desktop_display: 2326 si["original-desktop-display"] = self.original_desktop_display 2327 return si 2328 2329 def get_info(self, proto, *_args): 2330 start = monotonic() 2331 #this function is for non UI thread info 2332 info = {} 2333 def up(prefix, d): 2334 info[prefix] = d 2335 2336 si = self.get_server_info() 2337 if SYSCONFIG: 2338 si["sysconfig"] = get_sysconfig_info() 2339 up("server", si) 2340 2341 ni = get_net_info() 2342 ni.update({ 2343 "sockets" : self.get_socket_info(), 2344 "encryption" : self.encryption or "", 2345 "tcp-encryption" : self.tcp_encryption or "", 2346 "bandwidth-limit": self.bandwidth_limit or 0, 2347 "packet-handlers" : self.get_packet_handlers_info(), 2348 "www" : { 2349 "" : self._html, 2350 "dir" : self._www_dir or "", 2351 "http-headers-dirs" : self._http_headers_dirs or "", 2352 }, 2353 "mdns" : self.mdns, 2354 }) 2355 up("network", ni) 2356 up("threads", self.get_thread_info(proto)) 2357 up("logging", get_log_info()) 2358 from xpra.platform.info import get_sys_info 2359 up("sys", get_sys_info()) 2360 up("env", get_info_env()) 2361 if self.session_name: 2362 info["session"] = {"name" : self.session_name} 2363 if self.child_reaper: 2364 info.update(self.child_reaper.get_info()) 2365 if self.dbus_pid: 2366 up("dbus", { 2367 "pid" : self.dbus_pid, 2368 "env" : self.dbus_env, 2369 }) 2370 end = monotonic() 2371 log("ServerCore.get_info took %ims", (end-start)*1000) 2372 return info 2373 2374 def get_packet_handlers_info(self) -> dict: 2375 return { 2376 "default" : sorted(self._default_packet_handlers.keys()), 2377 } 2378 2379 def get_socket_info(self) -> dict: 2380 si = {} 2381 def add_listener(socktype, info): 2382 si.setdefault(socktype, {}).setdefault("listeners", []).append(info) 2383 def add_address(socktype, address, port): 2384 addresses = si.setdefault(socktype, {}).setdefault("addresses", []) 2385 if (address, port) not in addresses: 2386 addresses.append((address, port)) 2387 if socktype=="tcp": 2388 if self._html: 2389 add_address("ws", address, port) 2390 if self._ssl_attributes: 2391 add_address("ssl", address, port) 2392 if self.ssh_upgrade: 2393 add_address("ssh", address, port) 2394 if socktype=="ws": 2395 if self._ssl_attributes: 2396 add_address("wss", address, port) 2397 netifaces = import_netifaces() 2398 for sock_details, options in self._socket_info.items(): 2399 socktype, _, info, _ = sock_details 2400 if not info: 2401 continue 2402 add_listener(socktype, info) 2403 if not SHOW_NETWORK_ADDRESSES: 2404 continue 2405 if socktype not in ("tcp", "ssl", "ws", "wss", "ssh"): 2406 #we expose addresses only for TCP sockets 2407 continue 2408 upnp_address = options.get("upnp-address") 2409 if upnp_address: 2410 add_address(socktype, *upnp_address) 2411 if len(info)!=2 or not isinstance(info[0], str) or not isinstance(info[1], int): 2412 #unsupported listener info format 2413 continue 2414 address, port = info 2415 if address not in ("0.0.0.0", "::/0", "::"): 2416 #not a wildcard address, use it as-is: 2417 add_address(socktype, address, port) 2418 continue 2419 if not netifaces: 2420 if first_time("netifaces-socket-address"): 2421 netlog.warn("Warning: netifaces is missing") 2422 netlog.warn(" socket addresses cannot be queried") 2423 continue 2424 ips = [] 2425 for inet in get_interfaces_addresses().values(): 2426 #ie: inet = { 2427 # 18: [{'addr': ''}], 2428 # 2: [{'peer': '127.0.0.1', 'netmask': '255.0.0.0', 'addr': '127.0.0.1'}], 2429 # 30: [{'peer': '::1', 'netmask': 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', 'addr': '::1'}, 2430 # {'peer': '', 'netmask': 'ffff:ffff:ffff:ffff::', 'addr': 'fe80::1%lo0'}] 2431 # } 2432 for v in (socket.AF_INET, socket.AF_INET6): 2433 addresses = inet.get(v, ()) 2434 for addr in addresses: 2435 #ie: addr = {'peer': '127.0.0.1', 'netmask': '255.0.0.0', 'addr': '127.0.0.1'}] 2436 ip = addr.get("addr") 2437 if ip and ip not in ips: 2438 ips.append(ip) 2439 for ip in ips: 2440 add_address(socktype, ip, port) 2441 2442 for socktype, auth_classes in self.auth_classes.items(): 2443 if auth_classes: 2444 authenticators = si.setdefault(socktype, {}).setdefault("authenticator", {}) 2445 for i, auth_class in enumerate(auth_classes): 2446 authenticators[i] = auth_class[0], auth_class[3] 2447 return si 2448 2449 2450 ###################################################################### 2451 # packet handling: 2452 def process_packet(self, proto, packet): 2453 packet_type = None 2454 handler = None 2455 try: 2456 packet_type = bytestostr(packet[0]) 2457 may_log_packet(False, packet_type, packet) 2458 handler = self._default_packet_handlers.get(packet_type) 2459 if handler: 2460 netlog("process packet %s", packet_type) 2461 handler(proto, packet) 2462 return 2463 if not self._closing: 2464 netlog("invalid packet: %s", packet) 2465 netlog.error("unknown or invalid packet type: '%s' from %s", packet_type, proto) 2466 proto.close() 2467 except Exception: 2468 netlog.error("Unhandled error while processing a '%s' packet from peer using %s", 2469 packet_type, handler, exc_info=True) 2470 2471 2472 def handle_rfb_connection(self, conn, data=b""): 2473 log.error("Error: RFB protocol is not supported by this server") 2474 conn.close() 2475