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