Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/net/socket_util.py : 55%
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# This file is part of Xpra.
2# Copyright (C) 2017-2021 Antoine Martin <antoine@xpra.org>
3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
4# later version. See the file COPYING for details.
6import os.path
7import socket
8from time import sleep
9from ctypes import Structure, c_uint8, sizeof
11from xpra.scripts.config import InitException, InitExit, TRUE_OPTIONS
12from xpra.exit_codes import (
13 EXIT_SSL_FAILURE, EXIT_SSL_CERTIFICATE_VERIFY_FAILURE,
14 EXIT_SERVER_ALREADY_EXISTS, EXIT_SOCKET_CREATION_ERROR,
15 )
16from xpra.net.bytestreams import set_socket_timeout, pretty_socket
17from xpra.os_util import (
18 getuid, get_username_for_uid, get_groups, get_group_id,
19 path_permission_info, monotonic_time, umask_context, WIN32, OSX, POSIX,
20 parse_encoded_bin_data,
21 )
22from xpra.util import (
23 envint, envbool, csv, parse_simple_dict,
24 ellipsizer,
25 DEFAULT_PORT,
26 )
27from xpra.make_thread import start_thread
29#what timeout value to use on the socket probe attempt:
30WAIT_PROBE_TIMEOUT = envint("XPRA_WAIT_PROBE_TIMEOUT", 6)
31GROUP = os.environ.get("XPRA_GROUP", "xpra")
32PEEK_TIMEOUT = envint("XPRA_PEEK_TIMEOUT", 1)
33PEEK_TIMEOUT_MS = envint("XPRA_PEEK_TIMEOUT_MS", PEEK_TIMEOUT*1000)
34PEEK_SIZE = envint("XPRA_PEEK_SIZE", 8192)
36SOCKET_DIR_MODE = num = int(os.environ.get("XPRA_SOCKET_DIR_MODE", "775"), 8)
37SOCKET_DIR_GROUP = os.environ.get("XPRA_SOCKET_DIR_GROUP", GROUP)
40network_logger = None
41def get_network_logger():
42 global network_logger
43 if not network_logger:
44 from xpra.log import Logger
45 network_logger = Logger("network")
46 return network_logger
49def create_unix_domain_socket(sockpath, socket_permissions=0o600):
50 assert POSIX
51 #convert this to a umask!
52 umask = (0o777-socket_permissions) & 0o777
53 listener = socket.socket(socket.AF_UNIX)
54 listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
55 #bind the socket, using umask to set the correct permissions
56 with umask_context(umask):
57 listener.bind(sockpath)
58 try:
59 inode = os.stat(sockpath).st_ino
60 except OSError:
61 inode = -1
62 #set to the "xpra" group if we are a member of it, or if running as root:
63 uid = getuid()
64 username = get_username_for_uid(uid)
65 groups = get_groups(username)
66 if uid==0 or GROUP in groups:
67 group_id = get_group_id(GROUP)
68 if group_id>=0:
69 try:
70 os.lchown(sockpath, -1, group_id)
71 except Exception as e:
72 log = get_network_logger()
73 log.warn("Warning: failed to set '%s' group ownership", GROUP)
74 log.warn(" on socket '%s':", sockpath)
75 log.warn(" %s", e)
76 #don't know why this doesn't work:
77 #os.fchown(listener.fileno(), -1, group_id)
78 def cleanup_socket():
79 log = get_network_logger()
80 try:
81 cur_inode = os.stat(sockpath).st_ino
82 except OSError:
83 log.info("socket '%s' already deleted", sockpath)
84 return
85 delpath = sockpath
86 log("cleanup_socket '%s', original inode=%s, new inode=%s", sockpath, inode, cur_inode)
87 if cur_inode==inode:
88 log.info("removing unix domain socket '%s'", delpath)
89 try:
90 os.unlink(delpath)
91 except OSError:
92 pass
93 return listener, cleanup_socket
95def has_dual_stack() -> bool:
96 """
97 Return True if kernel allows creating a socket which is able to
98 listen for both IPv4 and IPv6 connections.
99 If *sock* is provided the check is made against it.
100 """
101 try:
102 assert socket.AF_INET6 and socket.IPPROTO_IPV6 and socket.IPV6_V6ONLY
103 except AttributeError:
104 return False
105 try:
106 import contextlib
107 sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
108 with contextlib.closing(sock):
109 sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False)
110 return True
111 except socket.error:
112 return False
114def hosts(host_str):
115 if host_str=="*":
116 if has_dual_stack():
117 #IPv6 will also listen for IPv4:
118 return ["::"]
119 #no dual stack, so we have to listen on both IPv4 and IPv6 explicitly:
120 return ["0.0.0.0", "::"]
121 return [host_str]
123def add_listen_socket(socktype, sock, info, new_connection_cb, new_udp_connection_cb=None, options=None):
124 log = get_network_logger()
125 log("add_listen_socket(%s, %s, %s, %s, %s, %s)",
126 socktype, sock, info, new_connection_cb, new_udp_connection_cb, options)
127 try:
128 #ugly that we have different ways of starting sockets,
129 #TODO: abstract this into the socket class
130 if socktype=="named-pipe":
131 #named pipe listener uses a thread:
132 sock.new_connection_cb = new_connection_cb
133 sock.start()
134 return None
135 sources = []
136 if socktype=="udp":
137 assert new_udp_connection_cb, "UDP sockets cannot be handled here"
138 new_udp_connection_cb(sock)
139 else:
140 from gi.repository import GLib
141 sock.listen(5)
142 def io_in_cb(sock, flags):
143 log("io_in_cb(%s, %s)", sock, flags)
144 return new_connection_cb(socktype, sock)
145 source = GLib.io_add_watch(sock, GLib.PRIORITY_DEFAULT, GLib.IO_IN, io_in_cb)
146 sources.append(source)
147 upnp_cleanup = []
148 if socktype in ("udp", "tcp", "ws", "wss", "ssh", "ssl"):
149 upnp = (options or {}).get("upnp", "no")
150 if upnp.lower() in TRUE_OPTIONS:
151 from xpra.net.upnp import upnp_add
152 upnp_cleanup.append(upnp_add(socktype, info, options))
153 def cleanup():
154 for source in tuple(sources):
155 GLib.source_remove(source)
156 sources.remove(source)
157 for c in upnp_cleanup:
158 if c:
159 start_thread(c, "pnp-cleanup-%s" % c, daemon=True)
160 return cleanup
161 except Exception as e:
162 log("add_listen_socket(%s, %s)", socktype, sock, exc_info=True)
163 log.error("Error: failed to listen on %s socket %s:", socktype, info or sock)
164 log.error(" %s", e)
165 return None
168def accept_connection(socktype, listener, timeout=None, socket_options=None):
169 log = get_network_logger()
170 try:
171 sock, address = listener.accept()
172 except socket.error as e:
173 log("rejecting new connection on %s", listener, exc_info=True)
174 log.error("Error: cannot accept new connection:")
175 log.error(" %s", e)
176 return None
177 #log("peercred(%s)=%s", sock, get_peercred(sock))
178 try:
179 peername = sock.getpeername()
180 except OSError:
181 peername = address
182 sock.settimeout(timeout)
183 sockname = sock.getsockname()
184 from xpra.net.bytestreams import SocketConnection
185 conn = SocketConnection(sock, sockname, address, peername, socktype, None, socket_options)
186 log("accept_connection(%s, %s, %s)=%s", listener, socktype, timeout, conn)
187 return conn
189def peek_connection(conn, timeout=PEEK_TIMEOUT_MS, size=PEEK_SIZE):
190 log = get_network_logger()
191 log("peek_connection(%s, %i)", conn, timeout)
192 peek_data = b""
193 start = monotonic_time()
194 elapsed = 0
195 set_socket_timeout(conn, PEEK_TIMEOUT_MS*1000)
196 while elapsed<=timeout:
197 try:
198 peek_data = conn.peek(size)
199 if peek_data:
200 break
201 except OSError:
202 log("peek_connection(%s, %i) failed", conn, timeout, exc_info=True)
203 except ValueError:
204 log("peek_connection(%s, %i) failed", conn, timeout, exc_info=True)
205 break
206 sleep(timeout/4000.0)
207 elapsed = int(1000*(monotonic_time()-start))
208 log("peek: elapsed=%s, timeout=%s", elapsed, timeout)
209 log("socket %s peek: got %i bytes", conn, len(peek_data))
210 return peek_data
213POSIX_TCP_INFO = (
214 ("state", c_uint8),
215 )
216def get_sockopt_tcp_info(sock, TCP_INFO, attributes=POSIX_TCP_INFO):
217 def get_tcpinfo_class(fields):
218 class TCPInfo(Structure):
219 _fields_ = tuple(fields)
220 def __repr__(self):
221 return "TCPInfo(%s)" % self.getdict()
222 def getdict(self):
223 return {k[0] : getattr(self, k[0]) for k in self._fields_}
224 return TCPInfo
225 #calculate full structure size with all the fields defined:
226 tcpinfo_class = get_tcpinfo_class(attributes)
227 tcpinfo_size = sizeof(tcpinfo_class)
228 data = sock.getsockopt(socket.SOL_TCP, TCP_INFO, tcpinfo_size)
229 data_size = len(data)
230 #but only define fields in the ctypes.Structure
231 #if they are actually present in the returned data:
232 while tcpinfo_size>data_size:
233 #trim it down:
234 attributes = attributes[:-1]
235 tcpinfo_class = get_tcpinfo_class(attributes)
236 tcpinfo_size = sizeof(tcpinfo_class)
237 log = get_network_logger()
238 if tcpinfo_size==0:
239 log("getsockopt(SOL_TCP, TCP_INFO, %i) no data", tcpinfo_size)
240 return {}
241 #log("total size=%i for fields: %s", size, csv(fdef[0] for fdef in fields))
242 try:
243 tcpinfo = tcpinfo_class.from_buffer_copy(data[:tcpinfo_size])
244 except ValueError as e:
245 log("getsockopt(SOL_TCP, TCP_INFO, %i)", tcpinfo_size, exc_info=True)
246 log("TCPInfo fields=%s", csv(tcpinfo_class._fields_))
247 log.warn("Warning: failed to get TCP_INFO for %s", sock)
248 log.warn(" %s", e)
249 return {}
250 d = tcpinfo.getdict()
251 log("getsockopt(SOL_TCP, TCP_INFO, %i)=%s", tcpinfo_size, d)
252 return d
256def guess_packet_type(data):
257 if not data:
258 return None
259 if data[0]==ord("P"):
260 from xpra.net.header import (
261 unpack_header, HEADER_SIZE,
262 FLAGS_RENCODE, FLAGS_YAML,
263 LZ4_FLAG, LZO_FLAG, BROTLI_FLAG,
264 )
265 header = data.ljust(HEADER_SIZE, b"\0")
266 _, protocol_flags, compression_level, packet_index, data_size = unpack_header(header)
267 #this normally used on the first packet, so the packet index should be 0
268 #and I don't think we can make packets smaller than 8 bytes,
269 #even with packet name aliases and rencode
270 #(and aliases should not be defined for the initial packet anyway)
271 if packet_index==0 and 8<data_size<256*1024*1024:
272 rencode = bool(protocol_flags & FLAGS_RENCODE)
273 yaml = bool(protocol_flags & FLAGS_YAML)
274 lz4 = bool(protocol_flags & LZ4_FLAG)
275 lzo = bool(protocol_flags & LZO_FLAG)
276 brotli = bool(protocol_flags & BROTLI_FLAG)
277 #rencode and yaml are mutually exclusive,
278 #so are the compressors
279 compressors = sum((lz4, lzo, brotli))
280 if not (rencode and yaml) and not compressors>1:
281 #if compression is enabled, the compression level must be set:
282 if not compressors or compression_level>0:
283 pass #return "xpra"
284 if data[:4]==b"SSH-":
285 return "ssh"
286 if data[0]==0x16:
287 return "ssl"
288 line1 = data.splitlines()[0]
289 if line1.find(b"HTTP/")>0 or line1.split(b" ")[0] in (b"GET", b"POST"):
290 return "http"
291 if line1.lower().startswith(b"<!doctype html") or line1.lower().startswith(b"<html"):
292 return "http"
293 return None
296def create_sockets(opts, error_cb):
297 bind_tcp = parse_bind_ip(opts.bind_tcp)
298 bind_udp = parse_bind_ip(opts.bind_udp)
299 bind_ssl = parse_bind_ip(opts.bind_ssl, 443)
300 bind_ssh = parse_bind_ip(opts.bind_ssh, 22)
301 bind_ws = parse_bind_ip(opts.bind_ws, 80)
302 bind_wss = parse_bind_ip(opts.bind_wss, 443)
303 bind_rfb = parse_bind_ip(opts.bind_rfb, 5900)
304 bind_vsock = parse_bind_vsock(opts.bind_vsock)
306 sockets = {}
308 min_port = int(opts.min_port)
309 def add_tcp_socket(socktype, host_str, iport, options):
310 if iport!=0 and iport<min_port:
311 error_cb("invalid %s port number %i (minimum value is %i)" % (socktype, iport, min_port))
312 for host in hosts(host_str):
313 sock = setup_tcp_socket(host, iport, socktype)
314 host, iport = sock[2]
315 sockets[sock] = options
316 def add_udp_socket(socktype, host_str, iport, options):
317 if iport!=0 and iport<min_port:
318 error_cb("invalid %s port number %i (minimum value is %i)" % (socktype, iport, min_port))
319 for host in hosts(host_str):
320 sock = setup_udp_socket(host, iport, socktype)
321 host, iport = sock[2]
322 sockets[sock] = options
323 # Initialize the TCP sockets before the display,
324 # That way, errors won't make us kill the Xvfb
325 # (which may not be ours to kill at that point)
326 ssh_upgrades = opts.ssh_upgrade
327 if ssh_upgrades:
328 try:
329 from xpra.net.ssh import nogssapi_context
330 with nogssapi_context():
331 import paramiko
332 assert paramiko
333 except ImportError as e:
334 from xpra.log import Logger
335 sshlog = Logger("ssh")
336 sshlog("import paramiko", exc_info=True)
337 sshlog.error("Error: cannot enable SSH socket upgrades:")
338 sshlog.error(" %s", e)
339 ssh_upgrades = False
340 log = get_network_logger()
341 for socktype, defs in {
342 "tcp" : bind_tcp,
343 "ssl" : bind_ssl,
344 "ssh" : bind_ssh,
345 "ws" : bind_ws,
346 "wss" : bind_wss,
347 "rfb" : bind_rfb,
348 }.items():
349 log("setting up %s sockets: %s", socktype, csv(defs.items()))
350 for (host, iport), options in defs.items():
351 add_tcp_socket(socktype, host, iport, options)
352 log("setting up UDP sockets: %s", csv(bind_udp.items()))
353 for (host, iport), options in bind_udp.items():
354 add_udp_socket("udp", host, iport, options)
355 log("setting up vsock sockets: %s", csv(bind_vsock.items()))
356 for (cid, iport), options in bind_vsock.items():
357 sock = setup_vsock_socket(cid, iport)
358 sockets[sock] = options
360 # systemd socket activation:
361 if POSIX and not OSX:
362 try:
363 from xpra.platform.xposix.sd_listen import get_sd_listen_sockets
364 except ImportError as e:
365 log("no systemd socket activation: %s", e)
366 else:
367 sd_sockets = get_sd_listen_sockets()
368 log("systemd sockets: %s", sd_sockets)
369 for stype, sock, addr in sd_sockets:
370 sock = setup_sd_listen_socket(stype, sock, addr)
371 sockets[sock] = {}
372 log("%s : %s", (stype, [addr]), sock)
373 return sockets
375def create_tcp_socket(host, iport):
376 log = get_network_logger()
377 if host.find(":")<0:
378 listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
379 sockaddr = (host, iport)
380 else:
381 if host.startswith("[") and host.endswith("]"):
382 host = host[1:-1]
383 assert socket.has_ipv6, "specified an IPv6 address but this is not supported"
384 res = socket.getaddrinfo(host, iport, socket.AF_INET6, socket.SOCK_STREAM, 0, socket.SOL_TCP)
385 log("socket.getaddrinfo(%s, %s, AF_INET6, SOCK_STREAM, 0, SOL_TCP)=%s", host, iport, res)
386 listener = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
387 sockaddr = res[0][-1]
388 listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
389 log("%s.bind(%s)", listener, sockaddr)
390 listener.bind(sockaddr)
391 return listener
393def setup_tcp_socket(host, iport, socktype="tcp"):
394 log = get_network_logger()
395 try:
396 tcp_socket = create_tcp_socket(host, iport)
397 except Exception as e:
398 log("create_tcp_socket%s", (host, iport), exc_info=True)
399 raise InitExit(EXIT_SOCKET_CREATION_ERROR,
400 "failed to setup %s socket on %s:%s %s" % (socktype, host, iport, e)) from None
401 def cleanup_tcp_socket():
402 log.info("closing %s socket '%s:%s'", socktype.lower(), host, iport)
403 try:
404 tcp_socket.close()
405 except OSError:
406 pass
407 if iport==0:
408 iport = tcp_socket.getsockname()[1]
409 log.info("allocated %s port %i on %s", socktype, iport, host)
410 log("%s: %s:%s : %s", socktype, host, iport, socket)
411 log.info("created %s socket '%s:%s'", socktype, host, iport)
412 return socktype, tcp_socket, (host, iport), cleanup_tcp_socket
414def create_udp_socket(host, iport):
415 if host.find(":")<0:
416 listener = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
417 sockaddr = (host, iport)
418 else:
419 assert socket.has_ipv6, "specified an IPv6 address but this is not supported"
420 res = socket.getaddrinfo(host, iport, socket.AF_INET6, socket.SOCK_DGRAM)
421 listener = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
422 sockaddr = res[0][-1]
423 listener.bind(sockaddr)
424 return listener
426def setup_udp_socket(host, iport, socktype="udp"):
427 log = get_network_logger()
428 try:
429 udp_socket = create_udp_socket(host, iport)
430 except Exception as e:
431 log("create_udp_socket%s", (host, iport), exc_info=True)
432 raise InitExit(EXIT_SOCKET_CREATION_ERROR,
433 "failed to setup %s socket on %s:%s %s" % (socktype, host, iport, e)) from None
434 def cleanup_udp_socket():
435 log.info("closing %s socket %s:%s", socktype, host, iport)
436 try:
437 udp_socket.close()
438 except OSError:
439 pass
440 if iport==0:
441 iport = udp_socket.getsockname()[1]
442 log.info("allocated UDP port %i for %s", iport, host)
443 log("%s: %s:%s : %s", socktype, host, iport, socket)
444 log.info("created UDP socket %s:%s", host, iport)
445 return socktype, udp_socket, (host, iport), cleanup_udp_socket
448def parse_bind_ip(bind_ip, default_port=DEFAULT_PORT):
449 ip_sockets = {}
450 if bind_ip:
451 for spec in bind_ip:
452 parts = spec.split(",", 1)
453 ip_port = parts[0]
454 if ":" not in spec:
455 raise InitException("port must be specified as [HOST]:PORT")
456 host, port = ip_port.rsplit(":", 1)
457 if host == "":
458 host = "127.0.0.1"
459 if not port:
460 iport = default_port
461 elif port=="0":
462 iport = 0
463 else:
464 try:
465 iport = int(port)
466 assert 0<iport<2**16
467 except (TypeError, ValueError):
468 raise InitException("invalid port number: %s" % port) from None
469 options = {}
470 if len(parts)==2:
471 options = parse_simple_dict(parts[1])
472 ip_sockets[(host, iport)] = options
473 return ip_sockets
475def setup_vsock_socket(cid, iport):
476 log = get_network_logger()
477 try:
478 from xpra.net.vsock import bind_vsocket #@UnresolvedImport
479 vsock_socket = bind_vsocket(cid=cid, port=iport)
480 except Exception as e:
481 raise InitExit(EXIT_SOCKET_CREATION_ERROR,
482 "failed to setup vsock socket on %s:%s %s" % (cid, iport, e)) from None
483 def cleanup_vsock_socket():
484 log.info("closing vsock socket %s:%s", cid, iport)
485 try:
486 vsock_socket.close()
487 except OSError:
488 pass
489 return "vsock", vsock_socket, (cid, iport), cleanup_vsock_socket
491def parse_bind_vsock(bind_vsock):
492 vsock_sockets = {}
493 if bind_vsock:
494 from xpra.scripts.main import parse_vsock
495 for spec in bind_vsock:
496 parts = spec.split(",", 1)
497 cid, iport = parse_vsock(parts[0])
498 options = {}
499 if len(parts)==2:
500 options = parse_simple_dict(parts[1])
501 vsock_sockets[(cid, iport)] = options
502 return vsock_sockets
504def setup_sd_listen_socket(stype, sock, addr):
505 log = get_network_logger()
506 def cleanup_sd_listen_socket():
507 log.info("closing sd listen socket %s", pretty_socket(addr))
508 try:
509 sock.close()
510 except OSError:
511 pass
512 return stype, sock, addr, cleanup_sd_listen_socket
515def normalize_local_display_name(local_display_name):
516 pos = local_display_name.find(":")
517 if pos<0:
518 after_sc = local_display_name
519 local_display_name = ":" + local_display_name
520 else:
521 after_sc = local_display_name[pos+1:]
522 if WIN32 or OSX:
523 if after_sc.isalnum():
524 return local_display_name
525 raise Exception("non alphanumeric character in display name '%s'" % local_display_name)
526 #we used to strip the screen from the display string, ie: ":0.0" -> ":0"
527 #but now we allow it.. (untested!)
528 for char in after_sc:
529 assert char in "0123456789.", "invalid character in display name '%s': %s" % (local_display_name, char)
530 return local_display_name
533def setup_local_sockets(bind, socket_dir, socket_dirs, display_name, clobber,
534 mmap_group="auto", socket_permissions="600", username="", uid=0, gid=0):
535 log = get_network_logger()
536 log("setup_local_sockets%s", (bind, socket_dir, socket_dirs, display_name, clobber,
537 mmap_group, socket_permissions, username, uid, gid))
538 if not bind:
539 return {}
540 if not socket_dir and (not socket_dirs or (len(socket_dirs)==1 and not socket_dirs[0])):
541 if WIN32:
542 socket_dirs = [""]
543 else:
544 raise InitExit(EXIT_SOCKET_CREATION_ERROR,
545 "at least one socket directory must be set to use unix domain sockets")
546 from xpra.platform.dotxpra import DotXpra, norm_makepath
547 dotxpra = DotXpra(socket_dir or socket_dirs[0], socket_dirs, username, uid, gid)
548 if display_name is not None and not WIN32:
549 display_name = normalize_local_display_name(display_name)
550 defs = {}
551 try:
552 sockpaths = {}
553 log("setup_local_sockets: bind=%s, dotxpra=%s", bind, dotxpra)
554 for b in bind:
555 if b in ("none", ""):
556 continue
557 parts = b.split(",")
558 sockpath = parts[0]
559 options = {}
560 if len(parts)==2:
561 options = parse_simple_dict(parts[1])
562 if sockpath=="auto":
563 assert display_name is not None
564 for sockpath in dotxpra.norm_socket_paths(display_name):
565 sockpaths[sockpath] = options
566 log("sockpaths(%s)=%s (uid=%i, gid=%i)", display_name, sockpaths, uid, gid)
567 else:
568 sockpath = dotxpra.osexpand(sockpath)
569 if os.path.isabs(sockpath):
570 pass
571 elif sockpath.endswith("/") or (os.path.exists(sockpath) and os.path.isdir(sockpath)):
572 assert display_name is not None
573 sockpath = os.path.abspath(sockpath)
574 if not os.path.exists(sockpath):
575 os.makedirs(sockpath)
576 sockpath = norm_makepath(sockpath, display_name)
577 else:
578 sockpath = dotxpra.socket_path(sockpath)
579 sockpaths[sockpath] = options
580 assert sockpaths, "no socket paths to try for %s" % b
581 #expand and remove duplicate paths:
582 tmp = {}
583 for tsp, options in sockpaths.items():
584 sockpath = dotxpra.osexpand(tsp)
585 if sockpath in tmp:
586 log.warn("Warning: skipping duplicate bind path %s", sockpath)
587 continue
588 tmp[sockpath] = options
589 sockpaths = tmp
590 log("sockpaths=%s", sockpaths)
591 #create listeners:
592 if WIN32:
593 from xpra.platform.win32.namedpipes.listener import NamedPipeListener
594 from xpra.platform.win32.dotxpra import PIPE_PATH
595 for sockpath, options in sockpaths.items():
596 npl = NamedPipeListener(sockpath)
597 ppath = sockpath
598 if ppath.startswith(PIPE_PATH):
599 ppath = ppath[len(PIPE_PATH):]
600 log.info("created named pipe '%s'", ppath)
601 defs[("named-pipe", npl, sockpath, npl.stop)] = options
602 else:
603 def checkstate(sockpath, state):
604 if state not in (DotXpra.DEAD, DotXpra.UNKNOWN):
605 if state==DotXpra.INACCESSIBLE:
606 raise InitException("An xpra server is already running at %s\n" % (sockpath,))
607 raise InitExit(EXIT_SERVER_ALREADY_EXISTS,
608 "You already have an xpra server running at %s\n"
609 " (did you want 'xpra upgrade'?)"
610 % (sockpath,))
611 #remove exisiting sockets if clobber is set,
612 #otherwise verify there isn't a server already running
613 #and create the directories for the sockets:
614 unknown = []
615 for sockpath in sockpaths:
616 if clobber and os.path.exists(sockpath):
617 os.unlink(sockpath)
618 else:
619 state = dotxpra.get_server_state(sockpath, 1)
620 log("state(%s)=%s", sockpath, state)
621 checkstate(sockpath, state)
622 if state==dotxpra.UNKNOWN:
623 unknown.append(sockpath)
624 d = os.path.dirname(sockpath)
625 try:
626 kwargs = {}
627 if d in ("/var/run/xpra", "/run/xpra"):
628 #this is normally done by tmpfiles.d,
629 #but we may need to do it ourselves in some cases:
630 kwargs["mode"] = SOCKET_DIR_MODE
631 xpra_gid = get_group_id(SOCKET_DIR_GROUP)
632 if xpra_gid>0:
633 kwargs["gid"] = xpra_gid
634 log("creating sockdir=%s, kwargs=%s" % (d, kwargs))
635 dotxpra.mksockdir(d, **kwargs)
636 log("%s permission mask: %s", d, oct(os.stat(d).st_mode))
637 except Exception as e:
638 log.warn("Warning: failed to create socket directory '%s'", d)
639 log.warn(" %s", e)
640 del e
641 #wait for all the unknown ones:
642 log("sockets in unknown state: %s", unknown)
643 if unknown:
644 #re-probe them using threads so we can do them in parallel:
645 threads = []
646 def timeout_probe(sockpath):
647 #we need a loop because "DEAD" sockets may return immediately
648 #(ie: when the server is starting up)
649 start = monotonic_time()
650 while monotonic_time()-start<WAIT_PROBE_TIMEOUT:
651 state = dotxpra.get_server_state(sockpath, WAIT_PROBE_TIMEOUT)
652 log("timeout_probe() get_server_state(%s)=%s", sockpath, state)
653 if state not in (DotXpra.UNKNOWN, DotXpra.DEAD):
654 break
655 sleep(1)
656 log.warn("Warning: some of the sockets are in an unknown state:")
657 for sockpath in unknown:
658 log.warn(" %s", sockpath)
659 t = start_thread(timeout_probe, "probe-%s" % sockpath, daemon=True, args=(sockpath,))
660 threads.append(t)
661 log.warn(" please wait as we allow the socket probing to timeout")
662 #wait for all the threads to do their job:
663 for t in threads:
664 t.join(WAIT_PROBE_TIMEOUT+1)
665 if sockpaths:
666 #now we can re-check quickly:
667 #(they should all be DEAD or UNKNOWN):
668 for sockpath in sockpaths:
669 state = dotxpra.get_server_state(sockpath, 1)
670 log("state(%s)=%s", sockpath, state)
671 checkstate(sockpath, state)
672 try:
673 if os.path.exists(sockpath):
674 os.unlink(sockpath)
675 except OSError:
676 pass
677 #socket permissions:
678 if mmap_group.lower() in TRUE_OPTIONS:
679 #when using the mmap group option, use '660'
680 sperms = 0o660
681 else:
682 #parse octal mode given as config option:
683 try:
684 if isinstance(socket_permissions, int):
685 sperms = socket_permissions
686 else:
687 #assume octal string:
688 sperms = int(socket_permissions, 8)
689 assert 0<=sperms<=0o777, "invalid socket permission value %s" % oct(sperms)
690 except ValueError:
691 raise ValueError("invalid socket permissions "+
692 "(must be an octal number): '%s'" % socket_permissions) from None
693 #now try to create all the sockets:
694 for sockpath, options in sockpaths.items():
695 #create it:
696 try:
697 sock, cleanup_socket = create_unix_domain_socket(sockpath, sperms)
698 log.info("created unix domain socket '%s'", sockpath)
699 defs[("unix-domain", sock, sockpath, cleanup_socket)] = options
700 except Exception as e:
701 handle_socket_error(sockpath, sperms, e)
702 del e
703 except Exception:
704 for sock, cleanup_socket in defs.items():
705 try:
706 cleanup_socket()
707 except Exception as e:
708 log.error("Error cleaning up socket %s:", sock)
709 log.error(" %s", e)
710 del e
711 raise
712 return defs
714def handle_socket_error(sockpath, sperms, e):
715 log = get_network_logger()
716 log("socket creation error", exc_info=True)
717 if sockpath.startswith("/var/run/xpra") or sockpath.startswith("/run/xpra"):
718 log.info("cannot create group socket '%s'", sockpath)
719 log.info(" %s", e)
720 dirname = sockpath[:sockpath.find("xpra")+len("xpra")]
721 if not os.path.exists(dirname):
722 log.info(" %s does not exist", dirname)
723 #only show extra information if the socket permissions
724 #would have been accessible by the group:
725 elif POSIX and (sperms & 0o40):
726 uid = getuid()
727 username = get_username_for_uid(uid)
728 groups = get_groups(username)
729 log.info(" user '%s' is a member of groups: %s", username, csv(groups) or "no groups!")
730 if "xpra" not in groups:
731 log.info(" add 'xpra' group membership to enable group socket sharing")
732 for x in path_permission_info(dirname):
733 log.info(" %s", x)
734 elif sockpath.startswith("/var/run/user") or sockpath.startswith("/run/user"):
735 log.warn("Warning: cannot create socket '%s':", sockpath)
736 log.warn(" %s", e)
737 run_user = sockpath.split("/user")[0]+"/user"
738 if not os.path.exists(run_user):
739 log.warn(" %s does not exist", run_user)
740 else:
741 log.warn(" ($XDG_RUNTIME_DIR has not been created?)")
742 else:
743 log.error("Error: failed to create socket '%s':", sockpath)
744 log.error(" %s", e)
745 raise InitExit(EXIT_SOCKET_CREATION_ERROR,
746 "failed to create socket %s" % sockpath)
749#warn just once:
750MDNS_WARNING = False
751def mdns_publish(display_name, listen_on, text_dict=None):
752 global MDNS_WARNING
753 if MDNS_WARNING is True:
754 return ()
755 from xpra.log import Logger
756 log = Logger("mdns")
757 log("mdns_publish%s", (display_name, listen_on, text_dict))
758 try:
759 from xpra.net import mdns
760 assert mdns
761 from xpra.net.mdns import XPRA_MDNS_TYPE, RFB_MDNS_TYPE
762 PREFER_ZEROCONF = envbool("XPRA_PREFER_ZEROCONF", WIN32 or OSX)
763 if PREFER_ZEROCONF:
764 from xpra.net.mdns.zeroconf_publisher import ZeroconfPublishers as MDNSPublishers, get_interface_index
765 else:
766 from xpra.net.mdns.avahi_publisher import AvahiPublishers as MDNSPublishers, get_interface_index
767 except ImportError as e:
768 MDNS_WARNING = True
769 log("mdns import failure", exc_info=True)
770 log.warn("Warning: failed to load the mdns publisher")
771 try:
772 einfo = str(e)
773 except Exception:
774 einfo = str(type(e))
775 log.warn(" %s", einfo)
776 log.warn(" either install the 'python-avahi' module")
777 log.warn(" or use the 'mdns=no' option")
778 return ()
779 d = dict(text_dict or {})
780 #ensure we don't have duplicate interfaces:
781 f_listen_on = {}
782 for host, port in listen_on:
783 f_listen_on[(get_interface_index(host), port)] = (host, port)
784 try:
785 name = socket.gethostname()
786 except OSError:
787 name = "Xpra"
788 if display_name and not (OSX or WIN32):
789 name += " %s" % display_name
790 mode = d.get("mode", "tcp")
791 service_type = {"rfb" : RFB_MDNS_TYPE}.get(mode, XPRA_MDNS_TYPE)
792 index = 0
793 aps = []
794 for host, port in listen_on:
795 sn = name
796 mode_str = mode
797 if index>0:
798 mode_str = "%s-%i" % (mode, index+1)
799 if mode not in ("tcp", "rfb"):
800 sn += " (%s)" % mode_str
801 listen = ( (host, port), )
802 index += 1
803 aps.append(MDNSPublishers(listen, sn, service_type=service_type, text_dict=d))
804 return aps
807SSL_ATTRIBUTES = (
808 "cert", "key", "ca_certs", "ca_data",
809 "protocol",
810 "client_verify_mode", "server_verify_mode", "verify_flags",
811 "check_hostname", "server_hostname",
812 "options", "ciphers",
813 )
815def get_ssl_attributes(opts, server_side=True, overrides=None):
816 args = {
817 "server_side" : server_side,
818 }
819 for attr in SSL_ATTRIBUTES:
820 ssl_attr = "ssl_%s" % attr #ie: "ssl_ca_certs"
821 option = ssl_attr.replace("_", "-") #ie: "ssl-ca-certs"
822 v = (overrides or {}).get(option)
823 if v is None:
824 v = getattr(opts, ssl_attr)
825 args[attr] = v
826 return args
828def ssl_wrap_socket(sock, **kwargs):
829 fn = get_ssl_wrap_socket_fn(**kwargs)
830 return fn(sock)
832def get_ssl_wrap_socket_fn(cert=None, key=None, ca_certs=None, ca_data=None,
833 protocol="TLSv1_2",
834 client_verify_mode="optional", server_verify_mode="required", verify_flags="X509_STRICT",
835 check_hostname=False, server_hostname=None,
836 options="ALL,NO_COMPRESSION", ciphers="DEFAULT",
837 server_side=True):
838 if server_side and not cert:
839 raise InitException("you must specify an 'ssl-cert' file to use ssl sockets")
840 if server_side:
841 verify_mode = client_verify_mode
842 else:
843 verify_mode = server_verify_mode
844 from xpra.log import Logger
845 ssllog = Logger("ssl")
846 ssllog("get_ssl_wrap_socket_fn%s", (cert, key, ca_certs, ca_data,
847 protocol,
848 client_verify_mode, server_verify_mode, verify_flags,
849 check_hostname, server_hostname,
850 options, ciphers,
851 server_side))
852 import ssl
853 ssllog(" verify_mode for server_side=%s : %s", server_side, verify_mode)
854 #ca-certs:
855 if ca_certs=="default":
856 ca_certs = None
857 ssllog(" ca_certs=%s", ca_certs)
858 #parse verify-mode:
859 ssl_cert_reqs = getattr(ssl, "CERT_%s" % verify_mode.upper(), None)
860 if ssl_cert_reqs is None:
861 values = [k[len("CERT_"):].lower() for k in dir(ssl) if k.startswith("CERT_")]
862 raise InitException("invalid ssl-server-verify-mode '%s', must be one of: %s" % (verify_mode, csv(values)))
863 ssllog(" cert_reqs=%#x", ssl_cert_reqs)
864 #parse protocol:
865 proto = getattr(ssl, "PROTOCOL_%s" % (protocol.upper().replace("V", "v")), None)
866 if proto is None:
867 values = [k[len("PROTOCOL_"):] for k in dir(ssl) if k.startswith("PROTOCOL_")]
868 raise InitException("invalid ssl-protocol '%s', must be one of: %s" % (protocol, csv(values)))
869 ssllog(" protocol=%#x", proto)
870 #ca_data may be hex encoded:
871 ca_data = parse_encoded_bin_data(ca_data)
872 ssllog(" cadata=%s", ellipsizer(ca_data))
874 kwargs = {
875 "server_side" : server_side,
876 "do_handshake_on_connect" : False,
877 "suppress_ragged_eofs" : True,
878 }
879 #parse ssl-verify-flags as CSV:
880 ssl_verify_flags = 0
881 for x in verify_flags.split(","):
882 x = x.strip()
883 if not x:
884 continue
885 v = getattr(ssl, "VERIFY_"+x.upper(), None)
886 if v is None:
887 raise InitException("invalid ssl verify-flag: %s" % x)
888 ssl_verify_flags |= v
889 ssllog(" verify_flags=%#x", ssl_verify_flags)
890 #parse ssl-options as CSV:
891 ssl_options = 0
892 for x in options.split(","):
893 x = x.strip()
894 if not x:
895 continue
896 v = getattr(ssl, "OP_"+x.upper(), None)
897 if v is None:
898 raise InitException("invalid ssl option: %s" % x)
899 ssl_options |= v
900 ssllog(" options=%#x", ssl_options)
902 context = ssl.SSLContext(proto)
903 context.set_ciphers(ciphers)
904 context.verify_mode = ssl_cert_reqs
905 context.verify_flags = ssl_verify_flags
906 context.options = ssl_options
907 ssllog(" cert=%s, key=%s", cert, key)
908 if cert:
909 SSL_KEY_PASSWORD = os.environ.get("XPRA_SSL_KEY_PASSWORD")
910 ssllog("context.load_cert_chain%s", (cert or None, key or None, SSL_KEY_PASSWORD))
911 context.load_cert_chain(certfile=cert or None, keyfile=key or None, password=SSL_KEY_PASSWORD)
912 if ssl_cert_reqs!=ssl.CERT_NONE:
913 if server_side:
914 purpose = ssl.Purpose.CLIENT_AUTH #@UndefinedVariable
915 else:
916 purpose = ssl.Purpose.SERVER_AUTH #@UndefinedVariable
917 context.check_hostname = check_hostname
918 ssllog(" check_hostname=%s, server_hostname=%s", check_hostname, server_hostname)
919 if context.check_hostname:
920 if not server_hostname:
921 raise InitException("ssl error: check-hostname is set but server-hostname is not")
922 kwargs["server_hostname"] = server_hostname
923 ssllog(" load_default_certs(%s)", purpose)
924 context.load_default_certs(purpose)
926 if not ca_certs or ca_certs.lower()=="default":
927 ssllog(" using default certs")
928 #load_default_certs already calls set_default_verify_paths()
929 elif not os.path.exists(ca_certs):
930 raise InitException("invalid ssl-ca-certs file or directory: %s" % ca_certs)
931 elif os.path.isdir(ca_certs):
932 ssllog(" loading ca certs from directory '%s'", ca_certs)
933 context.load_verify_locations(capath=ca_certs)
934 else:
935 ssllog(" loading ca certs from file '%s'", ca_certs)
936 assert os.path.isfile(ca_certs), "'%s' is not a valid ca file" % ca_certs
937 context.load_verify_locations(cafile=ca_certs)
938 #handle cadata:
939 if ca_data:
940 #PITA: because of a bug in the ssl module, we can't pass cadata,
941 #so we use a temporary file instead:
942 import tempfile
943 with tempfile.NamedTemporaryFile(prefix='cadata') as f:
944 ssllog(" loading cadata '%s'", ellipsizer(ca_data))
945 ssllog(" using temporary file '%s'", f.name)
946 f.file.write(ca_data)
947 f.file.flush()
948 context.load_verify_locations(cafile=f.name)
949 elif check_hostname and not server_side:
950 ssllog("cannot check hostname client side with verify mode %s", verify_mode)
951 wrap_socket = context.wrap_socket
952 def do_wrap_socket(tcp_socket):
953 assert tcp_socket
954 ssllog("do_wrap_socket(%s)", tcp_socket)
955 if WIN32:
956 #on win32, setting the tcp socket to blocking doesn't work?
957 #we still hit the following errors that we need to retry:
958 from xpra.net import bytestreams
959 bytestreams.CAN_RETRY_EXCEPTIONS = (ssl.SSLWantReadError, ssl.SSLWantWriteError)
960 else:
961 tcp_socket.setblocking(True)
962 try:
963 ssl_sock = wrap_socket(tcp_socket, **kwargs)
964 except Exception as e:
965 ssllog.debug("wrap_socket(%s, %s)", tcp_socket, kwargs, exc_info=True)
966 SSLEOFError = getattr(ssl, "SSLEOFError", None)
967 if SSLEOFError and isinstance(e, SSLEOFError):
968 return None
969 raise InitExit(EXIT_SSL_FAILURE, "Cannot wrap socket %s: %s" % (tcp_socket, e))
970 if not server_side:
971 try:
972 ssl_sock.do_handshake(True)
973 except Exception as e:
974 ssllog.debug("do_handshake", exc_info=True)
975 SSLEOFError = getattr(ssl, "SSLEOFError", None)
976 if SSLEOFError and isinstance(e, SSLEOFError):
977 return None
978 status = EXIT_SSL_FAILURE
979 SSLCertVerificationError = getattr(ssl, "SSLCertVerificationError", None)
980 if SSLCertVerificationError and isinstance(e, SSLCertVerificationError):
981 try:
982 msg = e.args[1].split(":", 2)[2]
983 except (ValueError, IndexError):
984 msg = str(e)
985 status = EXIT_SSL_CERTIFICATE_VERIFY_FAILURE
986 #ssllog.warn("host failed SSL verification: %s", msg)
987 else:
988 msg = str(e)
989 raise InitExit(status, "SSL handshake failed: %s" % msg)
990 return ssl_sock
991 return do_wrap_socket