fixed ZeroConf and gave it E2E support
This commit is contained in:
parent
0b6e432134
commit
3c936682dc
2 changed files with 151 additions and 8 deletions
|
@ -44,6 +44,8 @@ except:
|
||||||
gajim.log.debug(_('Unable to load idle module'))
|
gajim.log.debug(_('Unable to load idle module'))
|
||||||
HAS_IDLE = False
|
HAS_IDLE = False
|
||||||
|
|
||||||
|
from common.stanza_session import EncryptedStanzaSession
|
||||||
|
|
||||||
class ConnectionVcard:
|
class ConnectionVcard:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.vcard_sha = None
|
self.vcard_sha = None
|
||||||
|
@ -629,6 +631,8 @@ class ConnectionHandlersZeroconf(ConnectionVcard, ConnectionBytestream):
|
||||||
# keep the jids we auto added (transports contacts) to not send the
|
# keep the jids we auto added (transports contacts) to not send the
|
||||||
# SUBSCRIBED event to gui
|
# SUBSCRIBED event to gui
|
||||||
self.automatically_added = []
|
self.automatically_added = []
|
||||||
|
# keep track of sessions this connection has with other JIDs
|
||||||
|
self.sessions = {}
|
||||||
try:
|
try:
|
||||||
idle.init()
|
idle.init()
|
||||||
except:
|
except:
|
||||||
|
@ -636,25 +640,52 @@ class ConnectionHandlersZeroconf(ConnectionVcard, ConnectionBytestream):
|
||||||
|
|
||||||
def _messageCB(self, ip, con, msg):
|
def _messageCB(self, ip, con, msg):
|
||||||
'''Called when we receive a message'''
|
'''Called when we receive a message'''
|
||||||
msgtxt = msg.getBody()
|
|
||||||
msghtml = msg.getXHTML()
|
|
||||||
mtype = msg.getType()
|
mtype = msg.getType()
|
||||||
subject = msg.getSubject() # if not there, it's None
|
thread_id = msg.getThread()
|
||||||
thread = msg.getThread()
|
|
||||||
tim = msg.getTimestamp()
|
tim = msg.getTimestamp()
|
||||||
tim = helpers.datetime_tuple(tim)
|
tim = helpers.datetime_tuple(tim)
|
||||||
tim = time.localtime(timegm(tim))
|
tim = time.localtime(timegm(tim))
|
||||||
frm = msg.getFrom()
|
frm = msg.getFrom()
|
||||||
|
|
||||||
if frm == None:
|
if frm == None:
|
||||||
for key in self.connection.zeroconf.contacts:
|
for key in self.connection.zeroconf.contacts:
|
||||||
if ip == self.connection.zeroconf.contacts[key][zeroconf.C_ADDRESS]:
|
if ip == self.connection.zeroconf.contacts[key][zeroconf.C_ADDRESS]:
|
||||||
frm = key
|
frm = key
|
||||||
frm = unicode(frm)
|
frm = unicode(frm)
|
||||||
jid = frm
|
jid = frm
|
||||||
|
|
||||||
|
session = self.get_session(frm, thread_id, mtype)
|
||||||
|
|
||||||
|
if thread_id and not session.received_thread_id:
|
||||||
|
session.received_thread_id = True
|
||||||
|
|
||||||
|
if msg.getTag('feature') and msg.getTag('feature').namespace == \
|
||||||
|
common.xmpp.NS_FEATURE:
|
||||||
|
if gajim.HAVE_PYCRYPTO:
|
||||||
|
self._FeatureNegCB(con, msg, session)
|
||||||
|
return
|
||||||
|
if msg.getTag('init') and msg.getTag('init').namespace == \
|
||||||
|
common.xmpp.NS_ESESSION_INIT:
|
||||||
|
self._InitE2ECB(con, msg, session)
|
||||||
|
|
||||||
no_log_for = gajim.config.get_per('accounts', self.name,
|
no_log_for = gajim.config.get_per('accounts', self.name,
|
||||||
'no_log_for').split()
|
'no_log_for').split()
|
||||||
encrypted = False
|
encrypted = False
|
||||||
chatstate = None
|
chatstate = None
|
||||||
|
|
||||||
|
e2e_tag = msg.getTag('c', namespace = common.xmpp.NS_STANZA_CRYPTO)
|
||||||
|
if e2e_tag:
|
||||||
|
encrypted = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
msg = session.decrypt_stanza(msg)
|
||||||
|
except:
|
||||||
|
self.dispatch('FAILED_DECRYPT', (frm, tim))
|
||||||
|
|
||||||
|
msgtxt = msg.getBody()
|
||||||
|
msghtml = msg.getXHTML()
|
||||||
|
subject = msg.getSubject() # if not there, it's None
|
||||||
|
|
||||||
encTag = msg.getTag('x', namespace = common.xmpp.NS_ENCRYPTED)
|
encTag = msg.getTag('x', namespace = common.xmpp.NS_ENCRYPTED)
|
||||||
decmsg = ''
|
decmsg = ''
|
||||||
form_node = msg.getTag('x', namespace = common.xmpp.NS_DATA)
|
form_node = msg.getTag('x', namespace = common.xmpp.NS_DATA)
|
||||||
|
@ -718,7 +749,7 @@ class ConnectionHandlersZeroconf(ConnectionVcard, ConnectionBytestream):
|
||||||
msg_id = gajim.logger.write('chat_msg_recv', frm, msgtxt, tim = tim,
|
msg_id = gajim.logger.write('chat_msg_recv', frm, msgtxt, tim = tim,
|
||||||
subject = subject)
|
subject = subject)
|
||||||
self.dispatch('MSG', (frm, msgtxt, tim, encrypted, mtype, subject,
|
self.dispatch('MSG', (frm, msgtxt, tim, encrypted, mtype, subject,
|
||||||
chatstate, msg_id, composing_xep, user_nick, msghtml, thread,
|
chatstate, msg_id, composing_xep, user_nick, msghtml, session,
|
||||||
form_node))
|
form_node))
|
||||||
elif mtype == 'normal': # it's single message
|
elif mtype == 'normal': # it's single message
|
||||||
if self.name not in no_log_for and jid not in no_log_for and msgtxt:
|
if self.name not in no_log_for and jid not in no_log_for and msgtxt:
|
||||||
|
@ -727,9 +758,113 @@ class ConnectionHandlersZeroconf(ConnectionVcard, ConnectionBytestream):
|
||||||
if invite:
|
if invite:
|
||||||
self.dispatch('MSG', (frm, msgtxt, tim, encrypted, 'normal',
|
self.dispatch('MSG', (frm, msgtxt, tim, encrypted, 'normal',
|
||||||
subject, chatstate, msg_id, composing_xep, user_nick, msghtml,
|
subject, chatstate, msg_id, composing_xep, user_nick, msghtml,
|
||||||
thread, form_node))
|
session, form_node))
|
||||||
# END messageCB
|
# END messageCB
|
||||||
|
|
||||||
|
def _FeatureNegCB(self, con, stanza, session):
|
||||||
|
gajim.log.debug('FeatureNegCB')
|
||||||
|
feature = stanza.getTag(name='feature', namespace=common.xmpp.NS_FEATURE)
|
||||||
|
form = common.xmpp.DataForm(node=feature.getTag('x'))
|
||||||
|
|
||||||
|
if form['FORM_TYPE'] == 'urn:xmpp:ssn':
|
||||||
|
self.dispatch('SESSION_NEG', (stanza.getFrom(), session, form))
|
||||||
|
else:
|
||||||
|
reply = stanza.buildReply()
|
||||||
|
reply.setType('error')
|
||||||
|
|
||||||
|
reply.addChild(feature)
|
||||||
|
reply.addChild(node=xmpp.ErrorNode('service-unavailable', typ='cancel'))
|
||||||
|
|
||||||
|
con.send(reply)
|
||||||
|
|
||||||
|
raise common.xmpp.NodeProcessed
|
||||||
|
|
||||||
|
def _InitE2ECB(self, con, stanza, session):
|
||||||
|
gajim.log.debug('InitE2ECB')
|
||||||
|
init = stanza.getTag(name='init', namespace=common.xmpp.NS_ESESSION_INIT)
|
||||||
|
form = common.xmpp.DataForm(node=init.getTag('x'))
|
||||||
|
|
||||||
|
self.dispatch('SESSION_NEG', (stanza.getFrom(), session, form))
|
||||||
|
|
||||||
|
raise common.xmpp.NodeProcessed
|
||||||
|
|
||||||
|
def get_session(self, jid, thread_id, type):
|
||||||
|
'''returns an existing session between this connection and 'jid', returns a new one if none exist.'''
|
||||||
|
session = self.find_session(jid, thread_id, type)
|
||||||
|
|
||||||
|
if session:
|
||||||
|
return session
|
||||||
|
else:
|
||||||
|
# it's possible we initiated a session with a bare JID and this is the
|
||||||
|
# first time we've seen a resource
|
||||||
|
bare_jid = gajim.get_jid_without_resource(jid)
|
||||||
|
if bare_jid != jid:
|
||||||
|
session = self.find_session(bare_jid, thread_id, type)
|
||||||
|
if session:
|
||||||
|
if not session.received_thread_id:
|
||||||
|
thread_id = session.thread_id
|
||||||
|
|
||||||
|
self.move_session(bare_jid, thread_id, jid.split("/")[1])
|
||||||
|
return session
|
||||||
|
|
||||||
|
return self.make_new_session(jid, thread_id, type)
|
||||||
|
|
||||||
|
def find_session(self, jid, thread_id, type):
|
||||||
|
try:
|
||||||
|
if type == 'chat' and not thread_id:
|
||||||
|
return self.find_null_session(jid)
|
||||||
|
else:
|
||||||
|
return self.sessions[jid][thread_id]
|
||||||
|
except KeyError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def delete_session(self, jid, thread_id):
|
||||||
|
try:
|
||||||
|
del self.sessions[jid][thread_id]
|
||||||
|
|
||||||
|
if not self.sessions[jid]:
|
||||||
|
del self.sessions[jid]
|
||||||
|
except KeyError:
|
||||||
|
print "jid %s should have been in %s, but it wasn't. missing session?" % (repr(jid), repr(self.sessions.keys()))
|
||||||
|
|
||||||
|
def move_session(self, original_jid, thread_id, to_resource):
|
||||||
|
'''moves a session to another resource.'''
|
||||||
|
session = self.sessions[original_jid][thread_id]
|
||||||
|
|
||||||
|
del self.sessions[original_jid][thread_id]
|
||||||
|
|
||||||
|
new_jid = gajim.get_jid_without_resource(original_jid) + '/' + to_resource
|
||||||
|
session.jid = common.xmpp.JID(new_jid)
|
||||||
|
|
||||||
|
if not new_jid in self.sessions:
|
||||||
|
self.sessions[new_jid] = {}
|
||||||
|
|
||||||
|
self.sessions[new_jid][thread_id] = session
|
||||||
|
|
||||||
|
def find_null_session(self, jid):
|
||||||
|
'''finds all of the sessions between us and jid that jid hasn't sent a thread_id in yet.
|
||||||
|
|
||||||
|
returns the session that we last sent a message to.'''
|
||||||
|
|
||||||
|
sessions_with_jid = self.sessions[jid].values()
|
||||||
|
no_threadid_sessions = filter(lambda s: not s.received_thread_id, sessions_with_jid)
|
||||||
|
|
||||||
|
if no_threadid_sessions:
|
||||||
|
no_threadid_sessions.sort(key=lambda s: s.last_send)
|
||||||
|
return no_threadid_sessions[-1]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def make_new_session(self, jid, thread_id = None, type = 'chat'):
|
||||||
|
sess = EncryptedStanzaSession(self, common.xmpp.JID(jid), thread_id, type)
|
||||||
|
|
||||||
|
if not jid in self.sessions:
|
||||||
|
self.sessions[jid] = {}
|
||||||
|
|
||||||
|
self.sessions[jid][sess.thread_id] = sess
|
||||||
|
|
||||||
|
return sess
|
||||||
|
|
||||||
def parse_data_form(self, node):
|
def parse_data_form(self, node):
|
||||||
dic = {}
|
dic = {}
|
||||||
tag = node.getTag('title')
|
tag = node.getTag('title')
|
||||||
|
|
|
@ -351,7 +351,7 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
|
||||||
|
|
||||||
def send_message(self, jid, msg, keyID, type = 'chat', subject='',
|
def send_message(self, jid, msg, keyID, type = 'chat', subject='',
|
||||||
chatstate = None, msg_id = None, composing_xep = None, resource = None,
|
chatstate = None, msg_id = None, composing_xep = None, resource = None,
|
||||||
user_nick = None):
|
user_nick = None, session=None):
|
||||||
fjid = jid
|
fjid = jid
|
||||||
|
|
||||||
if not self.connection:
|
if not self.connection:
|
||||||
|
@ -412,12 +412,20 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
|
||||||
if chatstate is 'composing' or msgtxt:
|
if chatstate is 'composing' or msgtxt:
|
||||||
chatstate_node.addChild(name = 'composing')
|
chatstate_node.addChild(name = 'composing')
|
||||||
|
|
||||||
|
if session:
|
||||||
|
session.last_send = time.time()
|
||||||
|
msg_iq.setThread(session.thread_id)
|
||||||
|
|
||||||
|
if session.enable_encryption:
|
||||||
|
msg_iq = session.encrypt_stanza(msg_iq)
|
||||||
|
|
||||||
if not self.connection.send(msg_iq, msg != None):
|
if not self.connection.send(msg_iq, msg != None):
|
||||||
return
|
return
|
||||||
|
|
||||||
no_log_for = gajim.config.get_per('accounts', self.name, 'no_log_for')
|
no_log_for = gajim.config.get_per('accounts', self.name, 'no_log_for')
|
||||||
ji = gajim.get_jid_without_resource(jid)
|
ji = gajim.get_jid_without_resource(jid)
|
||||||
if self.name not in no_log_for and ji not in no_log_for:
|
if session.is_loggable() and self.name not in no_log_for and\
|
||||||
|
ji not in no_log_for:
|
||||||
log_msg = msg
|
log_msg = msg
|
||||||
if subject:
|
if subject:
|
||||||
log_msg = _('Subject: %s\n%s') % (subject, msg)
|
log_msg = _('Subject: %s\n%s') % (subject, msg)
|
||||||
|
|
Loading…
Add table
Reference in a new issue