Mercurial > libervia-backend
view src/plugins/plugin_sec_otr.py @ 1055:abcac1ac27a7
plugin otr: first draft
author | Goffi <goffi@goffi.org> |
---|---|
date | Sat, 07 Jun 2014 16:39:08 +0200 |
parents | |
children | ef7b7dd5c5db |
line wrap: on
line source
#!/usr/bin/python # -*- coding: utf-8 -*- # SAT plugin for OTR encryption # Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (goffi@goffi.org) # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # XXX: thanks to Darrik L Mazey for his documentation (https://blog.darmasoft.net/2013/06/30/using-pure-python-otr.html) # this implentation is based on it from sat.core.i18n import _ from sat.core.log import getLogger from sat.core import exceptions log = getLogger(__name__) from twisted.words.protocols.jabber import jid from twisted.python import failure import potr DEFAULT_POLICY_FLAGS = { 'ALLOW_V1':False, 'ALLOW_V2':True, 'REQUIRE_ENCRYPTION':True, } PLUGIN_INFO = { "name": "OTR", "import_name": "OTR", "type": "SEC", "protocols": [], "dependencies": [], "main": "OTR", "handler": "no", "description": _("""Implementation of OTR""") } PROTOCOL='xmpp' MMS=1024 class Context(potr.context.Context): def __init__(self, host, account, peer): super(Context, self).__init__(account, peer) self.host = host def getPolicy(self, key): if key in DEFAULT_POLICY_FLAGS: return DEFAULT_POLICY_FLAGS[key] else: return False def inject(self, msg, appdata=None): to_jid, profile = appdata assert isinstance(to_jid, jid.JID) client = self.host.getClient(profile) log.debug('inject(%s, appdata=%s, to=%s)' % (msg, appdata, to_jid)) mess_data = {'message': msg, 'type': 'chat', 'from': client.jid, 'to': to_jid, 'subject': None, } self.host.generateMessageXML(mess_data) client.xmlstream.send(mess_data['xml']) def setState(self, state): super(Context, self).setState(state) log.debug("setState: %s (self = %s)" % (state, self)) # TODO: send signal to frontends, maybe a message feedback too class Account(potr.context.Account): def __init__(self, account_jid): global PROTOCOL, MMS assert isinstance(account_jid, jid.JID) log.debug("new account: %s" % account_jid) super(Account, self).__init__(account_jid, PROTOCOL, MMS) def loadPrivkey(self): # TODO log.debug("loadPrivkey") return None def savePrivkey(self): # TODO log.debug("savePrivkey") class ContextManager(object): def __init__(self, host, client): self.host = host self.account = Account(client.jid) self.contexts = {} def startContext(self, other): assert isinstance(other, jid.JID) if not other in self.contexts: self.contexts[other] = Context(self.host, self.account, other) return self.contexts[other] def getContextForUser(self, other): log.debug("getContextForUser [%s]" % other) return self.startContext(other) class OTR(object): def __init__(self, host): log.info(_("OTR plugin initialization")) self.host = host self.context_managers = {} host.trigger.add("MessageReceived", self.MessageReceivedTrigger, priority=100000) host.trigger.add("sendMessage", self.sendMessageTrigger, priority=100000) def profileConnected(self, profile): client = self.host.getClient(profile) self.context_managers[profile] = ContextManager(self.host, client) def _receivedTreatment(self, data, profile): from_jid = jid.JID(data['from']) log.debug("_receivedTreatment [from_jid = %s]" % from_jid) otrctx = self.context_managers[profile].getContextForUser(from_jid) encrypted = True try: res = otrctx.receiveMessage(data['body'].encode('utf-8'), appdata=(from_jid, profile)) except potr.context.UnencryptedMessage: log.warning("Received unencrypted message in an encrypted context") # TODO: feedback to frontends (either message or popup) encrypted = False if encrypted == False: return data else: if res[0] != None: # decrypted messages handling. # receiveMessage() will return a tuple, the first part of which will be the decrypted message data['body'] = res[0].decode('utf-8') raise failure.Failure(exceptions.SkipHistory()) # we send the decrypted message to frontends, but we don't want it in history else: raise failure.Failure(exceptions.CancelError()) # no message at all (no history, no signal) def MessageReceivedTrigger(self, message, post_treat, profile): post_treat.addCallback(self._receivedTreatment, profile) return True def sendMessageTrigger(self, mess_data, pre_xml_treatments, post_xml_treatments, profile): to_jid = mess_data['to'] if mess_data['type'] != 'groupchat' and not to_jid.resource: to_jid.resource = self.host.memory.getLastResource(to_jid, profile) # FIXME: it's dirty, but frontends don't manage resources correctly now, refactoring is planed otrctx = self.context_managers[profile].getContextForUser(to_jid) if mess_data['type'] != 'groupchat' and otrctx.state == potr.context.STATE_ENCRYPTED: log.debug("encrypting message") otrctx.sendMessage(0, mess_data['message'].encode('utf-8'), appdata=(to_jid, profile)) client = self.host.getClient(profile) self.host.sendMessageToBridge(mess_data, client) return False else: log.debug("sending message unencrypted") return True