Hide keyboard shortcuts

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. 

5 

6import struct 

7from socket import error as socket_error 

8from queue import Queue 

9 

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 

18 

19log = Logger("network", "protocol", "rfb") 

20 

21READ_BUFFER_SIZE = envint("XPRA_READ_BUFFER_SIZE", 65536) 

22 

23 

24class RFBProtocol: 

25 CONNECTION_LOST = "connection-lost" 

26 INVALID = "invalid" 

27 

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) 

55 

56 

57 def is_closed(self): 

58 return self._closed 

59 

60 

61 def send_protocol_handshake(self): 

62 self.send(b"RFB 003.008\n") 

63 

64 def _parse_invalid(self, packet): 

65 return len(packet) 

66 

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 

95 

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 

122 

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) 

143 

144 def send_fail_challenge(self): 

145 self.send(struct.pack(b"!I", 1)) 

146 self.close() 

147 

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 

161 

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 

202 

203 

204 def __repr__(self): 

205 return "RFBProtocol(%s)" % self._conn 

206 

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) 

212 

213 

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 

219 

220 

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) 

226 

227 

228 def send_disconnect(self, *_args, **_kwargs): 

229 #no such packet in RFB, just close 

230 self.close() 

231 

232 

233 def queue_size(self): 

234 return self._write_queue.qsize() 

235 

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) 

249 

250 def start_write_thread(self): 

251 self._write_thread = start_thread(self._write_thread_loop, "write", daemon=True) 

252 

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() 

272 

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 

292 

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 

316 

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) 

328 

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 

333 

334 

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) 

340 

341 

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) 

347 

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) 

354 

355 

356 def gibberish(self, msg, data): 

357 log("gibberish(%s, %r)", msg, data) 

358 self.close() 

359 

360 

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") 

379 

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 

385 

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)