Mercurial > libervia-backend
comparison 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 |
comparison
equal
deleted
inserted
replaced
1054:a32ef03d4af0 | 1055:abcac1ac27a7 |
---|---|
1 #!/usr/bin/python | |
2 # -*- coding: utf-8 -*- | |
3 | |
4 # SAT plugin for OTR encryption | |
5 # Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (goffi@goffi.org) | |
6 | |
7 # This program is free software: you can redistribute it and/or modify | |
8 # it under the terms of the GNU Affero General Public License as published by | |
9 # the Free Software Foundation, either version 3 of the License, or | |
10 # (at your option) any later version. | |
11 | |
12 # This program is distributed in the hope that it will be useful, | |
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 # GNU Affero General Public License for more details. | |
16 | |
17 # You should have received a copy of the GNU Affero General Public License | |
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 | |
20 # XXX: thanks to Darrik L Mazey for his documentation (https://blog.darmasoft.net/2013/06/30/using-pure-python-otr.html) | |
21 # this implentation is based on it | |
22 | |
23 from sat.core.i18n import _ | |
24 from sat.core.log import getLogger | |
25 from sat.core import exceptions | |
26 log = getLogger(__name__) | |
27 from twisted.words.protocols.jabber import jid | |
28 from twisted.python import failure | |
29 import potr | |
30 | |
31 DEFAULT_POLICY_FLAGS = { | |
32 'ALLOW_V1':False, | |
33 'ALLOW_V2':True, | |
34 'REQUIRE_ENCRYPTION':True, | |
35 } | |
36 | |
37 PLUGIN_INFO = { | |
38 "name": "OTR", | |
39 "import_name": "OTR", | |
40 "type": "SEC", | |
41 "protocols": [], | |
42 "dependencies": [], | |
43 "main": "OTR", | |
44 "handler": "no", | |
45 "description": _("""Implementation of OTR""") | |
46 } | |
47 | |
48 | |
49 PROTOCOL='xmpp' | |
50 MMS=1024 | |
51 | |
52 | |
53 class Context(potr.context.Context): | |
54 | |
55 def __init__(self, host, account, peer): | |
56 super(Context, self).__init__(account, peer) | |
57 self.host = host | |
58 | |
59 def getPolicy(self, key): | |
60 if key in DEFAULT_POLICY_FLAGS: | |
61 return DEFAULT_POLICY_FLAGS[key] | |
62 else: | |
63 return False | |
64 | |
65 def inject(self, msg, appdata=None): | |
66 to_jid, profile = appdata | |
67 assert isinstance(to_jid, jid.JID) | |
68 client = self.host.getClient(profile) | |
69 log.debug('inject(%s, appdata=%s, to=%s)' % (msg, appdata, to_jid)) | |
70 mess_data = {'message': msg, | |
71 'type': 'chat', | |
72 'from': client.jid, | |
73 'to': to_jid, | |
74 'subject': None, | |
75 } | |
76 self.host.generateMessageXML(mess_data) | |
77 client.xmlstream.send(mess_data['xml']) | |
78 | |
79 def setState(self, state): | |
80 super(Context, self).setState(state) | |
81 log.debug("setState: %s (self = %s)" % (state, self)) | |
82 # TODO: send signal to frontends, maybe a message feedback too | |
83 | |
84 | |
85 class Account(potr.context.Account): | |
86 | |
87 def __init__(self, account_jid): | |
88 global PROTOCOL, MMS | |
89 assert isinstance(account_jid, jid.JID) | |
90 log.debug("new account: %s" % account_jid) | |
91 super(Account, self).__init__(account_jid, PROTOCOL, MMS) | |
92 | |
93 def loadPrivkey(self): | |
94 # TODO | |
95 log.debug("loadPrivkey") | |
96 return None | |
97 | |
98 def savePrivkey(self): | |
99 # TODO | |
100 log.debug("savePrivkey") | |
101 | |
102 | |
103 class ContextManager(object): | |
104 | |
105 def __init__(self, host, client): | |
106 self.host = host | |
107 self.account = Account(client.jid) | |
108 self.contexts = {} | |
109 | |
110 def startContext(self, other): | |
111 assert isinstance(other, jid.JID) | |
112 if not other in self.contexts: | |
113 self.contexts[other] = Context(self.host, self.account, other) | |
114 return self.contexts[other] | |
115 | |
116 def getContextForUser(self, other): | |
117 log.debug("getContextForUser [%s]" % other) | |
118 return self.startContext(other) | |
119 | |
120 | |
121 class OTR(object): | |
122 | |
123 def __init__(self, host): | |
124 log.info(_("OTR plugin initialization")) | |
125 self.host = host | |
126 self.context_managers = {} | |
127 host.trigger.add("MessageReceived", self.MessageReceivedTrigger, priority=100000) | |
128 host.trigger.add("sendMessage", self.sendMessageTrigger, priority=100000) | |
129 | |
130 def profileConnected(self, profile): | |
131 client = self.host.getClient(profile) | |
132 self.context_managers[profile] = ContextManager(self.host, client) | |
133 | |
134 def _receivedTreatment(self, data, profile): | |
135 from_jid = jid.JID(data['from']) | |
136 log.debug("_receivedTreatment [from_jid = %s]" % from_jid) | |
137 otrctx = self.context_managers[profile].getContextForUser(from_jid) | |
138 | |
139 encrypted = True | |
140 try: | |
141 res = otrctx.receiveMessage(data['body'].encode('utf-8'), appdata=(from_jid, profile)) | |
142 except potr.context.UnencryptedMessage: | |
143 log.warning("Received unencrypted message in an encrypted context") | |
144 # TODO: feedback to frontends (either message or popup) | |
145 encrypted = False | |
146 | |
147 if encrypted == False: | |
148 return data | |
149 else: | |
150 if res[0] != None: | |
151 # decrypted messages handling. | |
152 # receiveMessage() will return a tuple, the first part of which will be the decrypted message | |
153 data['body'] = res[0].decode('utf-8') | |
154 raise failure.Failure(exceptions.SkipHistory()) # we send the decrypted message to frontends, but we don't want it in history | |
155 else: | |
156 raise failure.Failure(exceptions.CancelError()) # no message at all (no history, no signal) | |
157 | |
158 def MessageReceivedTrigger(self, message, post_treat, profile): | |
159 post_treat.addCallback(self._receivedTreatment, profile) | |
160 return True | |
161 | |
162 def sendMessageTrigger(self, mess_data, pre_xml_treatments, post_xml_treatments, profile): | |
163 to_jid = mess_data['to'] | |
164 if mess_data['type'] != 'groupchat' and not to_jid.resource: | |
165 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 | |
166 otrctx = self.context_managers[profile].getContextForUser(to_jid) | |
167 if mess_data['type'] != 'groupchat' and otrctx.state == potr.context.STATE_ENCRYPTED: | |
168 log.debug("encrypting message") | |
169 otrctx.sendMessage(0, mess_data['message'].encode('utf-8'), appdata=(to_jid, profile)) | |
170 client = self.host.getClient(profile) | |
171 self.host.sendMessageToBridge(mess_data, client) | |
172 return False | |
173 else: | |
174 log.debug("sending message unencrypted") | |
175 return True | |
176 |