plugin otr: first draft

This commit is contained in:
Goffi 2014-06-07 16:39:08 +02:00
commit 503b408675

View file

@ -0,0 +1,176 @@
#!/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