diff --git a/src/common/zeroconf/connection_handlers_zeroconf.py b/src/common/zeroconf/connection_handlers_zeroconf.py index dc4e7ba28..6956f5430 100644 --- a/src/common/zeroconf/connection_handlers_zeroconf.py +++ b/src/common/zeroconf/connection_handlers_zeroconf.py @@ -44,6 +44,8 @@ except: gajim.log.debug(_('Unable to load idle module')) HAS_IDLE = False +from common.stanza_session import EncryptedStanzaSession + class ConnectionVcard: def __init__(self): self.vcard_sha = None @@ -629,6 +631,8 @@ class ConnectionHandlersZeroconf(ConnectionVcard, ConnectionBytestream): # keep the jids we auto added (transports contacts) to not send the # SUBSCRIBED event to gui self.automatically_added = [] + # keep track of sessions this connection has with other JIDs + self.sessions = {} try: idle.init() except: @@ -636,25 +640,52 @@ class ConnectionHandlersZeroconf(ConnectionVcard, ConnectionBytestream): def _messageCB(self, ip, con, msg): '''Called when we receive a message''' - msgtxt = msg.getBody() - msghtml = msg.getXHTML() mtype = msg.getType() - subject = msg.getSubject() # if not there, it's None - thread = msg.getThread() + thread_id = msg.getThread() tim = msg.getTimestamp() tim = helpers.datetime_tuple(tim) tim = time.localtime(timegm(tim)) frm = msg.getFrom() + if frm == None: for key in self.connection.zeroconf.contacts: if ip == self.connection.zeroconf.contacts[key][zeroconf.C_ADDRESS]: frm = key frm = unicode(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').split() encrypted = False 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) decmsg = '' 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, subject = 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)) elif mtype == 'normal': # it's single message 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: self.dispatch('MSG', (frm, msgtxt, tim, encrypted, 'normal', subject, chatstate, msg_id, composing_xep, user_nick, msghtml, - thread, form_node)) + session, form_node)) # 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): dic = {} tag = node.getTag('title') diff --git a/src/common/zeroconf/connection_zeroconf.py b/src/common/zeroconf/connection_zeroconf.py index 6b4299271..99710ef0d 100644 --- a/src/common/zeroconf/connection_zeroconf.py +++ b/src/common/zeroconf/connection_zeroconf.py @@ -351,7 +351,7 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf): def send_message(self, jid, msg, keyID, type = 'chat', subject='', chatstate = None, msg_id = None, composing_xep = None, resource = None, - user_nick = None): + user_nick = None, session=None): fjid = jid if not self.connection: @@ -412,12 +412,20 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf): if chatstate is 'composing' or msgtxt: 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): return no_log_for = gajim.config.get_per('accounts', self.name, 'no_log_for') 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 if subject: log_msg = _('Subject: %s\n%s') % (subject, msg)