Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/server/rfb/rfb_protocol.py : 16%
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 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 struct
7from socket import error as socket_error
8from queue import Queue
10from xpra.os_util import hexstr, strtobytes
11from xpra.util import repr_ellipsized, envint
12from xpra.make_thread import make_thread, start_thread
13from xpra.net.protocol import force_flush_queue, exit_queue
14from xpra.net.common import ConnectionClosedException #@UndefinedVariable (pydev false positive)
15from xpra.net.bytestreams import ABORT
16from xpra.server.rfb.rfb_const import RFBClientMessage, RFBAuth, PIXEL_FORMAT
17from xpra.log import Logger
19log = Logger("network", "protocol", "rfb")
21READ_BUFFER_SIZE = envint("XPRA_READ_BUFFER_SIZE", 65536)
24class RFBProtocol:
25 CONNECTION_LOST = "connection-lost"
26 INVALID = "invalid"
28 def __init__(self, scheduler, conn, auth, process_packet_cb, get_rfb_pixelformat, session_name="Xpra"):
29 """
30 You must call this constructor and source_has_more() from the main thread.
31 """
32 assert scheduler is not None
33 assert conn is not None
34 self.timeout_add = scheduler.timeout_add
35 self.idle_add = scheduler.idle_add
36 self._conn = conn
37 self._authenticator = auth
38 self._process_packet_cb = process_packet_cb
39 self._get_rfb_pixelformat = get_rfb_pixelformat
40 self.session_name = session_name
41 self._write_queue = Queue()
42 self._buffer = b""
43 self._challenge = None
44 self.share = False
45 #counters:
46 self.input_packetcount = 0
47 self.input_raw_packetcount = 0
48 self.output_packetcount = 0
49 self.output_raw_packetcount = 0
50 self._protocol_version = ()
51 self._closed = False
52 self._packet_parser = self._parse_protocol_handshake
53 self._write_thread = None
54 self._read_thread = make_thread(self._read_thread_loop, "read", daemon=True)
57 def is_closed(self):
58 return self._closed
61 def send_protocol_handshake(self):
62 self.send(b"RFB 003.008\n")
64 def _parse_invalid(self, packet):
65 return len(packet)
67 def _parse_protocol_handshake(self, packet):
68 log("parse_protocol_handshake(%r)", packet)
69 if len(packet)<12:
70 return 0
71 if not packet.startswith(b'RFB '):
72 self.invalid_header(self, packet, "invalid RFB protocol handshake packet header")
73 return 0
74 #ie: packet==b'RFB 003.008\n'
75 self._protocol_version = tuple(int(x) for x in packet[4:11].split(b"."))
76 log.info("RFB version %s connection from %s",
77 ".".join(str(x) for x in self._protocol_version), self._conn.target)
78 if self._protocol_version!=(3, 8):
79 msg = "unsupported protocol version"
80 log.error("Error: %s", msg)
81 self.send(struct.pack(b"!BI", 0, len(msg))+msg)
82 self.invalid(msg, packet)
83 return 0
84 #reply with Security Handshake:
85 self._packet_parser = self._parse_security_handshake
86 if self._authenticator and self._authenticator.requires_challenge():
87 security_types = [RFBAuth.VNC]
88 else:
89 security_types = [RFBAuth.NONE]
90 packet = struct.pack(b"B", len(security_types))
91 for x in security_types:
92 packet += struct.pack(b"B", x)
93 self.send(packet)
94 return 12
96 def _parse_security_handshake(self, packet):
97 log("parse_security_handshake(%s)", hexstr(packet))
98 try:
99 auth = struct.unpack(b"B", packet)[0]
100 except struct.error:
101 self._internal_error(packet, "cannot parse security handshake response '%s'" % hexstr(packet))
102 return 0
103 auth_str = RFBAuth.AUTH_STR.get(auth, auth)
104 if auth==RFBAuth.VNC:
105 #send challenge:
106 self._packet_parser = self._parse_challenge
107 assert self._authenticator
108 challenge, digest = self._authenticator.get_challenge("des")
109 assert digest=="des"
110 self._challenge = challenge[:16]
111 log("sending RFB challenge value: %s", hexstr(self._challenge))
112 self.send(self._challenge)
113 return 1
114 if self._authenticator and self._authenticator.requires_challenge():
115 self.invalid_header(self, packet, "invalid security handshake response, authentication is required")
116 return 0
117 log("parse_security_handshake: auth=%s, sending SecurityResult", auth_str)
118 #Security Handshake, send SecurityResult Handshake
119 self._packet_parser = self._parse_security_result
120 self.send(struct.pack(b"!I", 0))
121 return 1
123 def _parse_challenge(self, response):
124 assert self._authenticator
125 log("parse_challenge(%s)", hexstr(response))
126 try:
127 assert len(response)==16
128 hex_response = hexstr(response)
129 #log("padded password=%s", password)
130 if self._authenticator.authenticate(hex_response):
131 log("challenge authentication succeeded")
132 self.send(struct.pack(b"!I", 0))
133 self._packet_parser = self._parse_security_result
134 return 16
135 log.warn("Warning: authentication challenge response failure")
136 log.warn(" password does not match")
137 except Exception as e:
138 log("parse_challenge(%s)", hexstr(response), exc_info=True)
139 log.error("Error: authentication challenge failure:")
140 log.error(" %s", e)
141 self.timeout_add(1000, self.send_fail_challenge)
142 return len(response)
144 def send_fail_challenge(self):
145 self.send(struct.pack(b"!I", 1))
146 self.close()
148 def _parse_security_result(self, packet):
149 self.share = packet != b"\0"
150 log("parse_security_result: sharing=%s, sending ClientInit with session-name=%s", self.share, self.session_name)
151 #send ClientInit
152 self._packet_parser = self._parse_rfb
153 w, h, bpp, depth, bigendian, truecolor, rmax, gmax, bmax, rshift, bshift, gshift = self._get_rfb_pixelformat()
154 packet = struct.pack(b"!HH"+PIXEL_FORMAT+b"I",
155 w, h, bpp, depth, bigendian, truecolor,
156 rmax, gmax, bmax, rshift, bshift, gshift,
157 0, 0, 0, len(self.session_name))+strtobytes(self.session_name)
158 self.send(packet)
159 self._process_packet_cb(self, [b"authenticated"])
160 return 1
162 def _parse_rfb(self, packet):
163 try:
164 ptype = ord(packet[0])
165 except TypeError:
166 ptype = packet[0]
167 packet_type = RFBClientMessage.PACKET_TYPE_STR.get(ptype)
168 if not packet_type:
169 self.invalid("unknown RFB packet type: %#x" % ptype, packet)
170 return 0
171 s = RFBClientMessage.PACKET_STRUCT.get(ptype) #ie: Struct("!BBBB")
172 if not s:
173 self.invalid("RFB packet type '%s' is not supported" % packet_type, packet)
174 return 0
175 if len(packet)<s.size:
176 return 0
177 size = s.size
178 values = list(s.unpack(packet[:size]))
179 values[0] = packet_type
180 #some packets require parsing extra data:
181 if ptype==RFBClientMessage.SETENCODINGS:
182 N = values[2]
183 estruct = struct.Struct(b"!"+b"i"*N)
184 size += estruct.size
185 if len(packet)<size:
186 return 0
187 encodings = estruct.unpack(packet[s.size:size])
188 values.append(encodings)
189 elif ptype==RFBClientMessage.CLIENTCUTTEXT:
190 l = values[4]
191 size += l
192 if len(packet)<size:
193 return 0
194 text = packet[s.size:size]
195 values.append(text)
196 self.input_packetcount += 1
197 log("RFB packet: %s: %s", packet_type, values[1:])
198 #now trigger the callback:
199 self._process_packet_cb(self, values)
200 #return part of packet not consumed:
201 return size
204 def __repr__(self):
205 return "RFBProtocol(%s)" % self._conn
207 def get_threads(self):
208 return tuple(x for x in (
209 self._write_thread,
210 self._read_thread,
211 ) if x is not None)
214 def get_info(self, *_args):
215 info = {"protocol" : self._protocol_version}
216 for t in self.get_threads():
217 info.setdefault("thread", {})[t.name] = t.is_alive()
218 return info
221 def start(self):
222 def start_network_read_thread():
223 if not self._closed:
224 self._read_thread.start()
225 self.idle_add(start_network_read_thread)
228 def send_disconnect(self, *_args, **_kwargs):
229 #no such packet in RFB, just close
230 self.close()
233 def queue_size(self):
234 return self._write_queue.qsize()
236 def send(self, packet):
237 if self._closed:
238 log("connection is closed already, not sending packet")
239 return
240 if log.is_debug_enabled():
241 if len(packet)<=16:
242 log("send(%i bytes: %s)", len(packet), hexstr(packet))
243 else:
244 from xpra.simple_stats import std_unit
245 log("send(%sBytes: %s..)", std_unit(len(packet)), hexstr(packet[:16]))
246 if self._write_thread is None:
247 self.start_write_thread()
248 self._write_queue.put(packet)
250 def start_write_thread(self):
251 self._write_thread = start_thread(self._write_thread_loop, "write", daemon=True)
253 def _io_thread_loop(self, name, callback):
254 try:
255 log("io_thread_loop(%s, %s) loop starting", name, callback)
256 while not self._closed and callback():
257 pass
258 log("io_thread_loop(%s, %s) loop ended, closed=%s", name, callback, self._closed)
259 except ConnectionClosedException as e:
260 log("%s closed", self._conn, exc_info=True)
261 if not self._closed:
262 #ConnectionClosedException means the warning has been logged already
263 self._connection_lost("%s connection %s closed" % (name, self._conn))
264 except (OSError, socket_error) as e:
265 if not self._closed:
266 self._internal_error("%s connection %s reset" % (name, self._conn), e, exc_info=e.args[0] not in ABORT)
267 except Exception as e:
268 #can happen during close(), in which case we just ignore:
269 if not self._closed:
270 log.error("Error: %s on %s failed: %s", name, self._conn, type(e), exc_info=True)
271 self.close()
273 def _write_thread_loop(self):
274 self._io_thread_loop("write", self._write)
275 def _write(self):
276 buf = self._write_queue.get()
277 # Used to signal that we should exit:
278 if buf is None:
279 log("write thread: empty marker, exiting")
280 self.close()
281 return False
282 con = self._conn
283 if not con:
284 return False
285 while buf and not self._closed:
286 written = con.write(buf)
287 if written:
288 buf = buf[written:]
289 self.output_raw_packetcount += 1
290 self.output_packetcount += 1
291 return True
293 def _read_thread_loop(self):
294 self._io_thread_loop("read", self._read)
295 def _read(self):
296 c = self._conn
297 if not c:
298 return None
299 buf = c.read(READ_BUFFER_SIZE)
300 #log("read()=%s", repr_ellipsized(buf))
301 if not buf:
302 log("read thread: eof")
303 #give time to the parse thread to call close itself
304 #so it has time to parse and process the last packet received
305 self.timeout_add(1000, self.close)
306 return False
307 self.input_raw_packetcount += 1
308 self._buffer += buf
309 #log("calling %s(%s)", self._packet_parser, repr_ellipsized(self._buffer))
310 while self._buffer:
311 consumed = self._packet_parser(self._buffer)
312 if consumed==0:
313 break
314 self._buffer = self._buffer[consumed:]
315 return True
317 def _internal_error(self, message="", exc=None, exc_info=False):
318 #log exception info with last log message
319 if self._closed:
320 return
321 ei = exc_info
322 if exc:
323 ei = None #log it separately below
324 log.error("Error: %s", message, exc_info=ei)
325 if exc:
326 log.error(" %s", exc, exc_info=exc_info)
327 self.idle_add(self._connection_lost, message)
329 def _connection_lost(self, message="", exc_info=False):
330 log("connection lost: %s", message, exc_info=exc_info)
331 self.close()
332 return False
335 def invalid(self, msg, data):
336 self._packet_parser = self._parse_invalid
337 self.idle_add(self._process_packet_cb, self, [RFBProtocol.INVALID, msg, data])
338 # Then hang up:
339 self.timeout_add(1000, self._connection_lost, msg)
342 #delegates to invalid_header()
343 #(so this can more easily be intercepted and overriden
344 # see tcp-proxy)
345 def invalid_header(self, proto, data, msg=""):
346 self._invalid_header(proto, data, msg)
348 def _invalid_header(self, _proto, data, msg="invalid packet header"):
349 self._packet_parser = self._parse_invalid
350 err = "%s: '%s'" % (msg, hexstr(data[:8]))
351 if len(data)>1:
352 err += " read buffer=%s (%i bytes)" % (repr_ellipsized(data), len(data))
353 self.invalid(err, data)
356 def gibberish(self, msg, data):
357 log("gibberish(%s, %r)", msg, data)
358 self.close()
361 def close(self):
362 log("RFBProtocol.close() closed=%s, connection=%s", self._closed, self._conn)
363 if self._closed:
364 return
365 self._closed = True
366 #self.idle_add(self._process_packet_cb, self, [Protocol.CONNECTION_LOST])
367 c = self._conn
368 if c:
369 try:
370 log("RFBProtocol.close() calling %s", c.close)
371 c.close()
372 except IOError:
373 log.error("Error closing %s", self._conn, exc_info=True)
374 self._conn = None
375 self.terminate_queue_threads()
376 self._process_packet_cb(self, [RFBProtocol.CONNECTION_LOST])
377 self.idle_add(self.clean)
378 log("RFBProtocol.close() done")
380 def clean(self):
381 #clear all references to ensure we can get garbage collected quickly:
382 self._write_thread = None
383 self._read_thread = None
384 self._process_packet_cb = None
386 def terminate_queue_threads(self):
387 log("terminate_queue_threads()")
388 #make all the queue based threads exit by adding the empty marker:
389 owq = self._write_queue
390 self._write_queue = exit_queue()
391 force_flush_queue(owq)