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