comparison sat/plugins/plugin_xep_0260.py @ 3028:ab2696e34d29

Python 3 port: /!\ this is a huge commit /!\ starting from this commit, SàT is needs Python 3.6+ /!\ SàT maybe be instable or some feature may not work anymore, this will improve with time This patch port backend, bridge and frontends to Python 3. Roughly this has been done this way: - 2to3 tools has been applied (with python 3.7) - all references to python2 have been replaced with python3 (notably shebangs) - fixed files not handled by 2to3 (notably the shell script) - several manual fixes - fixed issues reported by Python 3 that where not handled in Python 2 - replaced "async" with "async_" when needed (it's a reserved word from Python 3.7) - replaced zope's "implements" with @implementer decorator - temporary hack to handle data pickled in database, as str or bytes may be returned, to be checked later - fixed hash comparison for password - removed some code which is not needed anymore with Python 3 - deactivated some code which needs to be checked (notably certificate validation) - tested with jp, fixed reported issues until some basic commands worked - ported Primitivus (after porting dependencies like urwid satext) - more manual fixes
author Goffi <goffi@goffi.org>
date Tue, 13 Aug 2019 19:08:41 +0200
parents 69e4716d6268
children 9d0df638c8b4
comparison
equal deleted inserted replaced
3027:ff5bcb12ae60 3028:ab2696e34d29
1 #!/usr/bin/env python2 1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*- 2 # -*- coding: utf-8 -*-
3 3
4 # SAT plugin for Jingle (XEP-0260) 4 # SAT plugin for Jingle (XEP-0260)
5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org) 5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org)
6 6
22 from sat.core.log import getLogger 22 from sat.core.log import getLogger
23 23
24 log = getLogger(__name__) 24 log = getLogger(__name__)
25 from sat.core import exceptions 25 from sat.core import exceptions
26 from wokkel import disco, iwokkel 26 from wokkel import disco, iwokkel
27 from zope.interface import implements 27 from zope.interface import implementer
28 from twisted.words.xish import domish 28 from twisted.words.xish import domish
29 from twisted.words.protocols.jabber import jid 29 from twisted.words.protocols.jabber import jid
30 from twisted.internet import defer 30 from twisted.internet import defer
31 import uuid 31 import uuid
32 32
120 transport_elt["dstaddr"] = session_hash 120 transport_elt["dstaddr"] = session_hash
121 if mode is not None: 121 if mode is not None:
122 transport_elt["mode"] = "tcp" # XXX: we only manage tcp for now 122 transport_elt["mode"] = "tcp" # XXX: we only manage tcp for now
123 123
124 for candidate in candidates: 124 for candidate in candidates:
125 log.debug(u"Adding candidate: {}".format(candidate)) 125 log.debug("Adding candidate: {}".format(candidate))
126 candidate_elt = transport_elt.addElement("candidate", NS_JINGLE_S5B) 126 candidate_elt = transport_elt.addElement("candidate", NS_JINGLE_S5B)
127 if candidate.id is None: 127 if candidate.id is None:
128 candidate.id = unicode(uuid.uuid4()) 128 candidate.id = str(uuid.uuid4())
129 candidate_elt["cid"] = candidate.id 129 candidate_elt["cid"] = candidate.id
130 candidate_elt["host"] = candidate.host 130 candidate_elt["host"] = candidate.host
131 candidate_elt["jid"] = candidate.jid.full() 131 candidate_elt["jid"] = candidate.jid.full()
132 candidate_elt["port"] = unicode(candidate.port) 132 candidate_elt["port"] = str(candidate.port)
133 candidate_elt["priority"] = unicode(candidate.priority) 133 candidate_elt["priority"] = str(candidate.priority)
134 candidate_elt["type"] = candidate.type 134 candidate_elt["type"] = candidate.type
135 return transport_elt 135 return transport_elt
136 136
137 @defer.inlineCallbacks 137 @defer.inlineCallbacks
138 def jingleSessionInit(self, client, session, content_name): 138 def jingleSessionInit(self, client, session, content_name):
139 content_data = session["contents"][content_name] 139 content_data = session["contents"][content_name]
140 transport_data = content_data["transport_data"] 140 transport_data = content_data["transport_data"]
141 sid = transport_data["sid"] = unicode(uuid.uuid4()) 141 sid = transport_data["sid"] = str(uuid.uuid4())
142 session_hash = transport_data["session_hash"] = self._s5b.getSessionHash( 142 session_hash = transport_data["session_hash"] = self._s5b.getSessionHash(
143 session[u"local_jid"], session["peer_jid"], sid 143 session["local_jid"], session["peer_jid"], sid
144 ) 144 )
145 transport_data["peer_session_hash"] = self._s5b.getSessionHash( 145 transport_data["peer_session_hash"] = self._s5b.getSessionHash(
146 session["peer_jid"], session[u"local_jid"], sid 146 session["peer_jid"], session["local_jid"], sid
147 ) # requester and target are inversed for peer candidates 147 ) # requester and target are inversed for peer candidates
148 transport_data["stream_d"] = self._s5b.registerHash(client, session_hash, None) 148 transport_data["stream_d"] = self._s5b.registerHash(client, session_hash, None)
149 candidates = transport_data["candidates"] = yield self._s5b.getCandidates( 149 candidates = transport_data["candidates"] = yield self._s5b.getCandidates(
150 client, session["local_jid"]) 150 client, session["local_jid"])
151 mode = "tcp" # XXX: we only manage tcp for now 151 mode = "tcp" # XXX: we only manage tcp for now
179 client, self._j.A_TRANSPORT_INFO, session, content_name 179 client, self._j.A_TRANSPORT_INFO, session, content_name
180 ) 180 )
181 transport_elt.addElement("proxy-error") 181 transport_elt.addElement("proxy-error")
182 iq_elt.send() 182 iq_elt.send()
183 log.warning( 183 log.warning(
184 u"Can't activate proxy, we need to fallback to IBB: {reason}".format( 184 "Can't activate proxy, we need to fallback to IBB: {reason}".format(
185 reason=stanza_error.value.condition 185 reason=stanza_error.value.condition
186 ) 186 )
187 ) 187 )
188 self.doFallback(session, content_name, client) 188 self.doFallback(session, content_name, client)
189 189
209 del transport_data["peer_candidates"] 209 del transport_data["peer_candidates"]
210 iq_elt, transport_elt = self._j.buildAction( 210 iq_elt, transport_elt = self._j.buildAction(
211 client, self._j.A_TRANSPORT_INFO, session, content_name 211 client, self._j.A_TRANSPORT_INFO, session, content_name
212 ) 212 )
213 if candidate is None: 213 if candidate is None:
214 log.warning(u"Can't connect to any peer candidate") 214 log.warning("Can't connect to any peer candidate")
215 candidate_elt = transport_elt.addElement("candidate-error") 215 candidate_elt = transport_elt.addElement("candidate-error")
216 else: 216 else:
217 log.info(u"Found best peer candidate: {}".format(unicode(candidate))) 217 log.info("Found best peer candidate: {}".format(str(candidate)))
218 candidate_elt = transport_elt.addElement("candidate-used") 218 candidate_elt = transport_elt.addElement("candidate-used")
219 candidate_elt["cid"] = candidate.id 219 candidate_elt["cid"] = candidate.id
220 iq_elt.send() # TODO: check result stanza 220 iq_elt.send() # TODO: check result stanza
221 self._checkCandidates(session, content_name, transport_data, client) 221 self._checkCandidates(session, content_name, transport_data, client)
222 222
246 choosed_candidate = best_candidate or peer_best_candidate 246 choosed_candidate = best_candidate or peer_best_candidate
247 else: 247 else:
248 if best_candidate.priority == peer_best_candidate.priority: 248 if best_candidate.priority == peer_best_candidate.priority:
249 # same priority, we choose initiator one according to XEP-0260 §2.4 #4 249 # same priority, we choose initiator one according to XEP-0260 §2.4 #4
250 log.debug( 250 log.debug(
251 u"Candidates have same priority, we select the one choosed by initiator" 251 "Candidates have same priority, we select the one choosed by initiator"
252 ) 252 )
253 if session["initiator"] == session[u"local_jid"]: 253 if session["initiator"] == session["local_jid"]:
254 choosed_candidate = best_candidate 254 choosed_candidate = best_candidate
255 else: 255 else:
256 choosed_candidate = peer_best_candidate 256 choosed_candidate = peer_best_candidate
257 else: 257 else:
258 choosed_candidate = max( 258 choosed_candidate = max(
259 best_candidate, peer_best_candidate, key=lambda c: c.priority 259 best_candidate, peer_best_candidate, key=lambda c: c.priority
260 ) 260 )
261 261
262 if choosed_candidate is None: 262 if choosed_candidate is None:
263 log.warning(u"Socks5 negociation failed, we need to fallback to IBB") 263 log.warning("Socks5 negociation failed, we need to fallback to IBB")
264 self.doFallback(session, content_name, client) 264 self.doFallback(session, content_name, client)
265 else: 265 else:
266 if choosed_candidate == peer_best_candidate: 266 if choosed_candidate == peer_best_candidate:
267 # peer_best_candidate was choosed from the candidates we have sent 267 # peer_best_candidate was choosed from the candidates we have sent
268 # so our_candidate is true if choosed_candidate is peer_best_candidate 268 # so our_candidate is true if choosed_candidate is peer_best_candidate
274 pass 274 pass
275 else: 275 else:
276 our_candidate = False 276 our_candidate = False
277 277
278 log.info( 278 log.info(
279 u"Socks5 negociation successful, {who} candidate will be used: {candidate}".format( 279 "Socks5 negociation successful, {who} candidate will be used: {candidate}".format(
280 who=u"our" if our_candidate else u"other peer", 280 who="our" if our_candidate else "other peer",
281 candidate=choosed_candidate, 281 candidate=choosed_candidate,
282 ) 282 )
283 ) 283 )
284 del transport_data["best_candidate"] 284 del transport_data["best_candidate"]
285 del transport_data["peer_best_candidate"] 285 del transport_data["peer_best_candidate"]
319 """Called when it's not possible to start the transfer 319 """Called when it's not possible to start the transfer
320 320
321 Will try to fallback to IBB 321 Will try to fallback to IBB
322 """ 322 """
323 try: 323 try:
324 reason = unicode(fail.value) 324 reason = str(fail.value)
325 except AttributeError: 325 except AttributeError:
326 reason = unicode(fail) 326 reason = str(fail)
327 log.warning(u"Cant start transfert, we'll try fallback method: {}".format(reason)) 327 log.warning("Cant start transfert, we'll try fallback method: {}".format(reason))
328 self.doFallback(session, content_name, client) 328 self.doFallback(session, content_name, client)
329 329
330 def _candidateInfo( 330 def _candidateInfo(
331 self, candidate_elt, session, content_name, transport_data, client 331 self, candidate_elt, session, content_name, transport_data, client
332 ): 332 ):
345 else: 345 else:
346 # candidate-used, one candidate was choosed 346 # candidate-used, one candidate was choosed
347 try: 347 try:
348 cid = candidate_elt.attributes["cid"] 348 cid = candidate_elt.attributes["cid"]
349 except KeyError: 349 except KeyError:
350 log.warning(u"No cid found in <candidate-used>") 350 log.warning("No cid found in <candidate-used>")
351 raise exceptions.DataError 351 raise exceptions.DataError
352 try: 352 try:
353 candidate = ( 353 candidate = next((
354 c for c in transport_data["candidates"] if c.id == cid 354 c for c in transport_data["candidates"] if c.id == cid
355 ).next() 355 ))
356 except StopIteration: 356 except StopIteration:
357 log.warning(u"Given cid doesn't correspond to any known candidate !") 357 log.warning("Given cid doesn't correspond to any known candidate !")
358 raise exceptions.DataError # TODO: send an error to other peer, and use better exception 358 raise exceptions.DataError # TODO: send an error to other peer, and use better exception
359 except KeyError: 359 except KeyError:
360 # a transport-info can also be intentionaly sent too early by other peer 360 # a transport-info can also be intentionaly sent too early by other peer
361 # but there is little probability 361 # but there is little probability
362 log.error( 362 log.error(
363 u'"candidates" key doesn\'t exists in transport_data, it should at this point' 363 '"candidates" key doesn\'t exists in transport_data, it should at this point'
364 ) 364 )
365 raise exceptions.InternalError 365 raise exceptions.InternalError
366 # at this point we have the candidate choosed by other peer 366 # at this point we have the candidate choosed by other peer
367 transport_data["peer_best_candidate"] = candidate 367 transport_data["peer_best_candidate"] = candidate
368 log.info(u"Other peer best candidate: {}".format(candidate)) 368 log.info("Other peer best candidate: {}".format(candidate))
369 369
370 del transport_data["candidates"] 370 del transport_data["candidates"]
371 self._checkCandidates(session, content_name, transport_data, client) 371 self._checkCandidates(session, content_name, transport_data, client)
372 372
373 def _proxyActivationInfo( 373 def _proxyActivationInfo(
383 @param client(unicode): %(doc_client)s 383 @param client(unicode): %(doc_client)s
384 """ 384 """
385 try: 385 try:
386 activation_d = transport_data.pop("activation_d") 386 activation_d = transport_data.pop("activation_d")
387 except KeyError: 387 except KeyError:
388 log.warning(u"Received unexpected transport-info for proxy activation") 388 log.warning("Received unexpected transport-info for proxy activation")
389 389
390 if proxy_elt.name == "activated": 390 if proxy_elt.name == "activated":
391 activation_d.callback(None) 391 activation_d.callback(None)
392 else: 392 else:
393 activation_d.errback(ProxyError()) 393 activation_d.errback(ProxyError())
465 (self._candidateInfo, ("candidate-used", "candidate-error")), 465 (self._candidateInfo, ("candidate-used", "candidate-error")),
466 (self._proxyActivationInfo, ("activated", "proxy-error")), 466 (self._proxyActivationInfo, ("activated", "proxy-error")),
467 ): 467 ):
468 for name in names: 468 for name in names:
469 try: 469 try:
470 candidate_elt = transport_elt.elements(NS_JINGLE_S5B, name).next() 470 candidate_elt = next(transport_elt.elements(NS_JINGLE_S5B, name))
471 except StopIteration: 471 except StopIteration:
472 continue 472 continue
473 else: 473 else:
474 method( 474 method(
475 candidate_elt, session, content_name, transport_data, client 475 candidate_elt, session, content_name, transport_data, client
476 ) 476 )
477 break 477 break
478 478
479 if candidate_elt is None: 479 if candidate_elt is None:
480 log.warning( 480 log.warning(
481 u"Unexpected transport element: {}".format(transport_elt.toXml()) 481 "Unexpected transport element: {}".format(transport_elt.toXml())
482 ) 482 )
483 elif action == self._j.A_DESTROY: 483 elif action == self._j.A_DESTROY:
484 # the transport is replaced (fallback ?), We need mainly to kill XEP-0065 session. 484 # the transport is replaced (fallback ?), We need mainly to kill XEP-0065 session.
485 # note that sid argument is not necessary for sessions created by this plugin 485 # note that sid argument is not necessary for sessions created by this plugin
486 self._s5b.killSession(None, transport_data["session_hash"], None, client) 486 self._s5b.killSession(None, transport_data["session_hash"], None, client)
487 else: 487 else:
488 log.warning(u"FIXME: unmanaged action {}".format(action)) 488 log.warning("FIXME: unmanaged action {}".format(action))
489 489
490 defer.returnValue(transport_elt) 490 defer.returnValue(transport_elt)
491 491
492 def jingleTerminate(self, client, action, session, content_name, reason_elt): 492 def jingleTerminate(self, client, action, session, content_name, reason_elt):
493 if reason_elt.decline: 493 if reason_elt.decline:
494 log.debug(u"Session declined, deleting S5B session") 494 log.debug("Session declined, deleting S5B session")
495 # we just need to clean the S5B session if it is declined 495 # we just need to clean the S5B session if it is declined
496 content_data = session["contents"][content_name] 496 content_data = session["contents"][content_name]
497 transport_data = content_data["transport_data"] 497 transport_data = content_data["transport_data"]
498 self._s5b.killSession(None, transport_data["session_hash"], None, client) 498 self._s5b.killSession(None, transport_data["session_hash"], None, client)
499 499
502 502
503 @param feature_checked(bool): True if other peer can do IBB 503 @param feature_checked(bool): True if other peer can do IBB
504 """ 504 """
505 if not feature_checked: 505 if not feature_checked:
506 log.warning( 506 log.warning(
507 u"Other peer can't manage jingle IBB, be have to terminate the session" 507 "Other peer can't manage jingle IBB, be have to terminate the session"
508 ) 508 )
509 self._j.terminate(client, self._j.REASON_CONNECTIVITY_ERROR, session) 509 self._j.terminate(client, self._j.REASON_CONNECTIVITY_ERROR, session)
510 else: 510 else:
511 self._j.transportReplace( 511 self._j.transportReplace(
512 client, self._jingle_ibb.NAMESPACE, session, content_name 512 client, self._jingle_ibb.NAMESPACE, session, content_name
522 if session["role"] != self._j.ROLE_INITIATOR: 522 if session["role"] != self._j.ROLE_INITIATOR:
523 # only initiator must do the fallback, see XEP-0260 §3 523 # only initiator must do the fallback, see XEP-0260 §3
524 return 524 return
525 if self._jingle_ibb is None: 525 if self._jingle_ibb is None:
526 log.warning( 526 log.warning(
527 u"Jingle IBB (XEP-0261) plugin is not available, we have to close the session" 527 "Jingle IBB (XEP-0261) plugin is not available, we have to close the session"
528 ) 528 )
529 self._j.terminate(client, self._j.REASON_CONNECTIVITY_ERROR, session) 529 self._j.terminate(client, self._j.REASON_CONNECTIVITY_ERROR, session)
530 else: 530 else:
531 d = self.host.hasFeature( 531 d = self.host.hasFeature(
532 client, self._jingle_ibb.NAMESPACE, session["peer_jid"] 532 client, self._jingle_ibb.NAMESPACE, session["peer_jid"]
533 ) 533 )
534 d.addCallback(self._doFallback, session, content_name, client) 534 d.addCallback(self._doFallback, session, content_name, client)
535 return d 535 return d
536 536
537 537
538 @implementer(iwokkel.IDisco)
538 class XEP_0260_handler(XMPPHandler): 539 class XEP_0260_handler(XMPPHandler):
539 implements(iwokkel.IDisco)
540 540
541 def getDiscoInfo(self, requestor, target, nodeIdentifier=""): 541 def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
542 return [disco.DiscoFeature(NS_JINGLE_S5B)] 542 return [disco.DiscoFeature(NS_JINGLE_S5B)]
543 543
544 def getDiscoItems(self, requestor, target, nodeIdentifier=""): 544 def getDiscoItems(self, requestor, target, nodeIdentifier=""):