plugin XEP-0260, XEP-0065: fixed session hash handling:

with XEP-0260, we need to manage 2 hashes (one with SHA1(SID + Initiator JID + Responder JID) and the other with SHA1(SID + Responder JID + Initiator JID)). This was not handled correclty, resulting in transfer failure in several cases.
This commit is contained in:
Goffi 2015-12-17 22:37:59 +01:00
commit 7d54ddff12

View file

@ -283,15 +283,15 @@ class Candidate(object):
self.factory.startTransfer(session_hash, chunk_size=chunk_size)
def getSessionHash(from_jid, to_jid, sid):
def getSessionHash(requester_jid, target_jid, sid):
"""Calculate SHA1 Hash according to XEP-0065 §5.3.2
@param from_jid(jid.JID): jid of the requester
@param to_jid(jid.JID): jid of the target
@param requester_jid(jid.JID): jid of the requester (the one which activate the proxy)
@param target_jid(jid.JID): jid of the target
@param sid(unicode): session id
@return (str): hash
"""
return hashlib.sha1((sid + from_jid.full() + to_jid.full()).encode('utf-8')).hexdigest()
return hashlib.sha1((sid + requester_jid.full() + target_jid.full()).encode('utf-8')).hexdigest()
class SOCKSv5(protocol.Protocol, FileSender):
@ -617,14 +617,15 @@ class Socks5ServerFactory(protocol.ServerFactory):
class Socks5ClientFactory(protocol.ClientFactory):
protocol = SOCKSv5
def __init__(self, parent, session_hash, profile):
def __init__(self, parent, session, session_hash, profile):
"""Init the Client Factory
@param session_hash(unicode): hash of the session
hash is the same as hostname computer in XEP-0065 § 5.3.2 #1
@param session(dict): session data
@param session_hash(unicode): hash used for peer_connection
hash is the same as hostname computed in XEP-0065 § 5.3.2 #1
@param profile(unciode): %(doc_profile)s
"""
self.session = parent.getSession(session_hash, profile)
self.session = session
self.session_hash = session_hash
self.profile = profile
self.connection = defer.Deferred()
@ -862,18 +863,28 @@ class XEP_0065(object):
candidate.factory.connector = connector
return candidate.factory.connection
def connectCandidate(self, candidate, session_hash, delay=None, profile=C.PROF_KEY_NONE):
def connectCandidate(self, candidate, session_hash, peer_session_hash=None, delay=None, profile=C.PROF_KEY_NONE):
"""Connect to a candidate
Connection will be done with a Socks5ClientFactory
@param candidate(Candidate): candidate to connect to
@param session_hash(unicode): hash of the session
hash is the same as hostname computer in XEP-0065 § 5.3.2 #1
hash is the same as hostname computed in XEP-0065 § 5.3.2 #1
@param peer_session_hash(unicode, None): hash used with the peer
None to use session_hash.
None must be used in 2 cases:
- when XEP-0065 is used with XEP-0096
- when a peer connect to a proxy *he proposed himself*
in practice, peer_session_hash is only used by tryCandidates
@param delay(None, float): optional delay to wait before connection, in seconds
@param profile: %(doc_profile)s
@return (D): Deferred launched when TCP connection + Socks5 connection is done
"""
factory = Socks5ClientFactory(self, session_hash, profile)
if peer_session_hash is None:
# for XEP-0065, only one hash is needed
peer_session_hash = session_hash
session = self.getSession(session_hash, profile)
factory = Socks5ClientFactory(self, session, peer_session_hash, profile)
candidate.factory = factory
if delay is None:
d = defer.succeed(candidate.host)
@ -883,14 +894,14 @@ class XEP_0065(object):
d.addCallback(self._addConnector, candidate)
return d
def tryCandidates(self, candidates, session_hash, connection_cb=None, connection_eb=None, profile=C.PROF_KEY_NONE):
def tryCandidates(self, candidates, session_hash, peer_session_hash, connection_cb=None, connection_eb=None, profile=C.PROF_KEY_NONE):
defers_list = []
for candidate in candidates:
delay = CANDIDATE_DELAY * len(defers_list)
if candidate.type == XEP_0065.TYPE_PROXY:
delay += CANDIDATE_DELAY_PROXY
d = self.connectCandidate(candidate, session_hash, delay, profile)
d = self.connectCandidate(candidate, session_hash, peer_session_hash, delay, profile)
if connection_cb is not None:
d.addCallback(lambda dummy, candidate=candidate, profile=profile: connection_cb(candidate, profile))
if connection_eb is not None:
@ -899,12 +910,14 @@ class XEP_0065(object):
return defers_list
def getBestCandidate(self, candidates, session_hash, profile=C.PROF_KEY_NONE):
def getBestCandidate(self, candidates, session_hash, peer_session_hash=None, profile=C.PROF_KEY_NONE):
"""Get best candidate (according to priority) which can connect
@param candidates(iterable[Candidate]): candidates to test
@param session_hash(unicode): hash of the session
hash is the same as hostname computer in XEP-0065 § 5.3.2 #1
hash is the same as hostname computed in XEP-0065 § 5.3.2 #1
@param peer_session_hash(unicode, None): hash of the other peer
only useful for XEP-0260, must be None for XEP-0065 streamhost candidates
@param profile: %(doc_profile)s
@return (D(None, Candidate)): best candidate or None if none can connect
"""
@ -934,7 +947,7 @@ class XEP_0065(object):
good_candidates = [c for c in candidates if c]
return good_candidates[0] if good_candidates else None
defer_candidates = self.tryCandidates(candidates, session_hash, connectionCb, connectionEb, profile)
defer_candidates = self.tryCandidates(candidates, session_hash, peer_session_hash, connectionCb, connectionEb, profile)
d_list = defer.DeferredList(defer_candidates)
d_list.addCallback(allTested)
return d_list
@ -966,12 +979,8 @@ class XEP_0065(object):
))
try:
# XXX: we need to be sure that hash is removed from self.hash_profiles_map
# ONLY if it's the profile requesting the session killing
# otherwise, this will result in a missing hash when the 2 peers
# are on the same instance
if self.hash_profiles_map[session_hash] == client.profile:
del self.hash_profiles_map[session_hash]
assert self.hash_profiles_map[session_hash] == client.profile
del self.hash_profiles_map[session_hash]
except KeyError:
pass
@ -1121,7 +1130,7 @@ class XEP_0065(object):
"""Return session data
@param session_hash(unicode): hash of the session
hash is the same as hostname computer in XEP-0065 § 5.3.2 #1
hash is the same as hostname computed in XEP-0065 § 5.3.2 #1
@param profile(None, unicode): profile of the peer
None is used only if profile is unknown (this is only the case
for incoming request received by Socks5ServerFactory). None must
@ -1165,23 +1174,8 @@ class XEP_0065(object):
if file_obj is not None:
session_data['file'] = file_obj
if session_hash in self.hash_profiles_map:
# The only case when 2 profiles want to register the same hash
# is when they are on the same instance
log.info(u"Both Socks5 peers are on the same instance")
# XXX:If both peers are on the same instance, they'll register the same
# session_hash, so we'll have 2 profiles for the same hash. The first
# one will be the responder (and so the second one the initiator).
# As we'll keep the initiator choosed candidate (see XEP-0260 § 2.4 #4),
# responder will handle the Socks5 server. Only the server will use
# self.hash_profiles_map to get the profile, so we can ignore the second
# one (the initiator profile).
# There is no easy way to known if the incoming connection
# to the Socks5Server is from initiator or responder, so this seams a
# reasonable workaround.
# NOTE: this workaround is only used with XEP-0260
else:
self.hash_profiles_map[session_hash] = profile
assert session_hash not in self.hash_profiles_map
self.hash_profiles_map[session_hash] = profile
return session_data
@ -1234,7 +1228,7 @@ class XEP_0065(object):
for candidate in candidates:
log.info(u"Candidate proposed: {}".format(candidate))
d = self.getBestCandidate(candidates, session_data['hash'], profile)
d = self.getBestCandidate(candidates, session_data['hash'], profile=profile)
d.addCallback(self._ackStream, iq_elt, session_data, client)
def _ackStream(self, candidate, iq_elt, session_data, client):