Mercurial > libervia-backend
comparison sat/plugins/plugin_sec_otr.py @ 3028:ab2696e34d29
Python 3 port:
/!\ this is a huge commit
/!\ starting from this commit, SàT is needs Python 3.6+
/!\ SàT maybe be instable or some feature may not work anymore, this will improve with time
This patch port backend, bridge and frontends to Python 3.
Roughly this has been done this way:
- 2to3 tools has been applied (with python 3.7)
- all references to python2 have been replaced with python3 (notably shebangs)
- fixed files not handled by 2to3 (notably the shell script)
- several manual fixes
- fixed issues reported by Python 3 that where not handled in Python 2
- replaced "async" with "async_" when needed (it's a reserved word from Python 3.7)
- replaced zope's "implements" with @implementer decorator
- temporary hack to handle data pickled in database, as str or bytes may be returned,
to be checked later
- fixed hash comparison for password
- removed some code which is not needed anymore with Python 3
- deactivated some code which needs to be checked (notably certificate validation)
- tested with jp, fixed reported issues until some basic commands worked
- ported Primitivus (after porting dependencies like urwid satext)
- more manual fixes
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 13 Aug 2019 19:08:41 +0200 |
parents | 3d735e0ab2fa |
children | fee60f17ebac |
comparison
equal
deleted
inserted
replaced
3027:ff5bcb12ae60 | 3028:ab2696e34d29 |
---|---|
1 #!/usr/bin/env python2 | 1 #!/usr/bin/env python3 |
2 # -*- coding: utf-8 -*- | 2 # -*- coding: utf-8 -*- |
3 | 3 |
4 # SAT plugin for OTR encryption | 4 # SAT plugin for OTR encryption |
5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org) | 5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org) |
6 | 6 |
19 | 19 |
20 # XXX: thanks to Darrik L Mazey for his documentation | 20 # XXX: thanks to Darrik L Mazey for his documentation |
21 # (https://blog.darmasoft.net/2013/06/30/using-pure-python-otr.html) | 21 # (https://blog.darmasoft.net/2013/06/30/using-pure-python-otr.html) |
22 # this implentation is based on it | 22 # this implentation is based on it |
23 | 23 |
24 import copy | |
25 import time | |
26 import uuid | |
27 from binascii import hexlify, unhexlify | |
24 from sat.core.i18n import _, D_ | 28 from sat.core.i18n import _, D_ |
25 from sat.core.constants import Const as C | 29 from sat.core.constants import Const as C |
26 from sat.core.log import getLogger | 30 from sat.core.log import getLogger |
27 from sat.core import exceptions | 31 from sat.core import exceptions |
28 | |
29 log = getLogger(__name__) | |
30 from sat.tools import xml_tools | 32 from sat.tools import xml_tools |
31 from twisted.words.protocols.jabber import jid | 33 from twisted.words.protocols.jabber import jid |
32 from twisted.python import failure | 34 from twisted.python import failure |
33 from twisted.internet import defer | 35 from twisted.internet import defer |
34 from sat.memory import persistent | 36 from sat.memory import persistent |
35 import potr | 37 import potr |
36 import copy | 38 |
37 import time | 39 log = getLogger(__name__) |
38 import uuid | |
39 | 40 |
40 | 41 |
41 PLUGIN_INFO = { | 42 PLUGIN_INFO = { |
42 C.PI_NAME: u"OTR", | 43 C.PI_NAME: "OTR", |
43 C.PI_IMPORT_NAME: u"OTR", | 44 C.PI_IMPORT_NAME: "OTR", |
44 C.PI_TYPE: u"SEC", | 45 C.PI_TYPE: "SEC", |
45 C.PI_PROTOCOLS: [u"XEP-0364"], | 46 C.PI_PROTOCOLS: ["XEP-0364"], |
46 C.PI_DEPENDENCIES: [u"XEP-0280", u"XEP-0334"], | 47 C.PI_DEPENDENCIES: ["XEP-0280", "XEP-0334"], |
47 C.PI_MAIN: u"OTR", | 48 C.PI_MAIN: "OTR", |
48 C.PI_HANDLER: u"no", | 49 C.PI_HANDLER: "no", |
49 C.PI_DESCRIPTION: _(u"""Implementation of OTR"""), | 50 C.PI_DESCRIPTION: _("""Implementation of OTR"""), |
50 } | 51 } |
51 | 52 |
52 NS_OTR = "urn:xmpp:otr:0" | 53 NS_OTR = "urn:xmpp:otr:0" |
53 PRIVATE_KEY = "PRIVATE KEY" | 54 PRIVATE_KEY = "PRIVATE KEY" |
54 OTR_MENU = D_(u"OTR") | 55 OTR_MENU = D_("OTR") |
55 AUTH_TXT = D_( | 56 AUTH_TXT = D_( |
56 u"To authenticate your correspondent, you need to give your below fingerprint " | 57 "To authenticate your correspondent, you need to give your below fingerprint " |
57 u"*BY AN EXTERNAL CANAL* (i.e. not in this chat), and check that the one he gives " | 58 "*BY AN EXTERNAL CANAL* (i.e. not in this chat), and check that the one he gives " |
58 u"you is the same as below. If there is a mismatch, there can be a spy between you!" | 59 "you is the same as below. If there is a mismatch, there can be a spy between you!" |
59 ) | 60 ) |
60 DROP_TXT = D_( | 61 DROP_TXT = D_( |
61 u"You private key is used to encrypt messages for your correspondent, nobody except " | 62 "You private key is used to encrypt messages for your correspondent, nobody except " |
62 u"you must know it, if you are in doubt, you should drop it!\n\nAre you sure you " | 63 "you must know it, if you are in doubt, you should drop it!\n\nAre you sure you " |
63 u"want to drop your private key?" | 64 "want to drop your private key?" |
64 ) | 65 ) |
65 # NO_LOG_AND = D_(u"/!\\Your history is not logged anymore, and") # FIXME: not used at the moment | 66 # NO_LOG_AND = D_(u"/!\\Your history is not logged anymore, and") # FIXME: not used at the moment |
66 NO_ADV_FEATURES = D_(u"Some of advanced features are disabled !") | 67 NO_ADV_FEATURES = D_("Some of advanced features are disabled !") |
67 | 68 |
68 DEFAULT_POLICY_FLAGS = {"ALLOW_V1": False, "ALLOW_V2": True, "REQUIRE_ENCRYPTION": True} | 69 DEFAULT_POLICY_FLAGS = {"ALLOW_V1": False, "ALLOW_V2": True, "REQUIRE_ENCRYPTION": True} |
69 | 70 |
70 OTR_STATE_TRUSTED = "trusted" | 71 OTR_STATE_TRUSTED = "trusted" |
71 OTR_STATE_UNTRUSTED = "untrusted" | 72 OTR_STATE_UNTRUSTED = "untrusted" |
105 @param msg_str(str): encrypted message body | 106 @param msg_str(str): encrypted message body |
106 @param appdata(None, dict): None for signal message, | 107 @param appdata(None, dict): None for signal message, |
107 message data when an encrypted message is going to be sent | 108 message data when an encrypted message is going to be sent |
108 """ | 109 """ |
109 assert isinstance(self.peer, jid.JID) | 110 assert isinstance(self.peer, jid.JID) |
110 msg = msg_str.decode("utf-8") | 111 msg = msg_str |
111 client = self.user.client | 112 client = self.user.client |
112 log.debug(u"injecting encrypted message to {to}".format(to=self.peer)) | 113 log.debug("injecting encrypted message to {to}".format(to=self.peer)) |
113 if appdata is None: | 114 if appdata is None: |
114 mess_data = { | 115 mess_data = { |
115 "from": client.jid, | 116 "from": client.jid, |
116 "to": self.peer, | 117 "to": self.peer, |
117 "uid": unicode(uuid.uuid4()), | 118 "uid": str(uuid.uuid4()), |
118 "message": {"": msg}, | 119 "message": {"": msg}, |
119 "subject": {}, | 120 "subject": {}, |
120 "type": "chat", | 121 "type": "chat", |
121 "extra": {}, | 122 "extra": {}, |
122 "timestamp": time.time(), | 123 "timestamp": time.time(), |
123 } | 124 } |
124 client.generateMessageXML(mess_data) | 125 client.generateMessageXML(mess_data) |
125 xml = mess_data[u'xml'] | 126 xml = mess_data['xml'] |
126 self._p_carbons.setPrivate(xml) | 127 self._p_carbons.setPrivate(xml) |
127 self._p_hints.addHintElements(xml, [ | 128 self._p_hints.addHintElements(xml, [ |
128 self._p_hints.HINT_NO_COPY, | 129 self._p_hints.HINT_NO_COPY, |
129 self._p_hints.HINT_NO_PERMANENT_STORE]) | 130 self._p_hints.HINT_NO_PERMANENT_STORE]) |
130 client.send(mess_data["xml"]) | 131 client.send(mess_data["xml"]) |
131 else: | 132 else: |
132 message_elt = appdata[u"xml"] | 133 message_elt = appdata["xml"] |
133 assert message_elt.name == u"message" | 134 assert message_elt.name == "message" |
134 message_elt.addElement("body", content=msg) | 135 message_elt.addElement("body", content=msg) |
135 | 136 |
136 def stopCb(self, __, feedback): | 137 def stopCb(self, __, feedback): |
137 client = self.user.client | 138 client = self.user.client |
138 self.host.bridge.otrState( | 139 self.host.bridge.otrState( |
141 client.feedback(self.peer, feedback) | 142 client.feedback(self.peer, feedback) |
142 | 143 |
143 def stopEb(self, failure_): | 144 def stopEb(self, failure_): |
144 # encryption may be already stopped in case of manual stop | 145 # encryption may be already stopped in case of manual stop |
145 if not failure_.check(exceptions.NotFound): | 146 if not failure_.check(exceptions.NotFound): |
146 log.error(u"Error while stopping OTR encryption: {msg}".format(msg=failure_)) | 147 log.error("Error while stopping OTR encryption: {msg}".format(msg=failure_)) |
147 | 148 |
148 def isTrusted(self): | 149 def isTrusted(self): |
149 # we have to check value because potr code says that a 2-tuples should be | 150 # we have to check value because potr code says that a 2-tuples should be |
150 # returned while in practice it's either None or u"trusted" | 151 # returned while in practice it's either None or u"trusted" |
151 trusted = self.getCurrentTrust() | 152 trusted = self.getCurrentTrust() |
152 if trusted is None: | 153 if trusted is None: |
153 return False | 154 return False |
154 elif trusted == u'trusted': | 155 elif trusted == 'trusted': |
155 return True | 156 return True |
156 else: | 157 else: |
157 log.error(u"Unexpected getCurrentTrust() value: {value}".format( | 158 log.error("Unexpected getCurrentTrust() value: {value}".format( |
158 value=trusted)) | 159 value=trusted)) |
159 return False | 160 return False |
160 | 161 |
161 def setState(self, state): | 162 def setState(self, state): |
162 client = self.user.client | 163 client = self.user.client |
163 old_state = self.state | 164 old_state = self.state |
164 super(Context, self).setState(state) | 165 super(Context, self).setState(state) |
165 log.debug(u"setState: %s (old_state=%s)" % (state, old_state)) | 166 log.debug("setState: %s (old_state=%s)" % (state, old_state)) |
166 | 167 |
167 if state == potr.context.STATE_PLAINTEXT: | 168 if state == potr.context.STATE_PLAINTEXT: |
168 feedback = _(u"/!\\ conversation with %(other_jid)s is now UNENCRYPTED") % { | 169 feedback = _("/!\\ conversation with %(other_jid)s is now UNENCRYPTED") % { |
169 "other_jid": self.peer.full() | 170 "other_jid": self.peer.full() |
170 } | 171 } |
171 d = client.encryption.stop(self.peer, NS_OTR) | 172 d = client.encryption.stop(self.peer, NS_OTR) |
172 d.addCallback(self.stopCb, feedback=feedback) | 173 d.addCallback(self.stopCb, feedback=feedback) |
173 d.addErrback(self.stopEb) | 174 d.addErrback(self.stopEb) |
176 client.encryption.start(self.peer, NS_OTR) | 177 client.encryption.start(self.peer, NS_OTR) |
177 try: | 178 try: |
178 trusted = self.isTrusted() | 179 trusted = self.isTrusted() |
179 except TypeError: | 180 except TypeError: |
180 trusted = False | 181 trusted = False |
181 trusted_str = _(u"trusted") if trusted else _(u"untrusted") | 182 trusted_str = _("trusted") if trusted else _("untrusted") |
182 | 183 |
183 if old_state == potr.context.STATE_ENCRYPTED: | 184 if old_state == potr.context.STATE_ENCRYPTED: |
184 feedback = D_( | 185 feedback = D_( |
185 u"{trusted} OTR conversation with {other_jid} REFRESHED" | 186 "{trusted} OTR conversation with {other_jid} REFRESHED" |
186 ).format(trusted=trusted_str, other_jid=self.peer.full()) | 187 ).format(trusted=trusted_str, other_jid=self.peer.full()) |
187 else: | 188 else: |
188 feedback = D_( | 189 feedback = D_( |
189 u"{trusted} encrypted OTR conversation started with {other_jid}\n" | 190 "{trusted} encrypted OTR conversation started with {other_jid}\n" |
190 u"{extra_info}" | 191 "{extra_info}" |
191 ).format( | 192 ).format( |
192 trusted=trusted_str, | 193 trusted=trusted_str, |
193 other_jid=self.peer.full(), | 194 other_jid=self.peer.full(), |
194 extra_info=NO_ADV_FEATURES, | 195 extra_info=NO_ADV_FEATURES, |
195 ) | 196 ) |
196 self.host.bridge.otrState( | 197 self.host.bridge.otrState( |
197 OTR_STATE_ENCRYPTED, self.peer.full(), client.profile | 198 OTR_STATE_ENCRYPTED, self.peer.full(), client.profile |
198 ) | 199 ) |
199 elif state == potr.context.STATE_FINISHED: | 200 elif state == potr.context.STATE_FINISHED: |
200 feedback = D_(u"OTR conversation with {other_jid} is FINISHED").format( | 201 feedback = D_("OTR conversation with {other_jid} is FINISHED").format( |
201 other_jid=self.peer.full() | 202 other_jid=self.peer.full() |
202 ) | 203 ) |
203 d = client.encryption.stop(self.peer, NS_OTR) | 204 d = client.encryption.stop(self.peer, NS_OTR) |
204 d.addCallback(self.stopCb, feedback=feedback) | 205 d.addCallback(self.stopCb, feedback=feedback) |
205 d.addErrback(self.stopEb) | 206 d.addErrback(self.stopEb) |
206 return | 207 return |
207 else: | 208 else: |
208 log.error(D_(u"Unknown OTR state")) | 209 log.error(D_("Unknown OTR state")) |
209 return | 210 return |
210 | 211 |
211 client.feedback(self.peer, feedback) | 212 client.feedback(self.peer, feedback) |
212 | 213 |
213 def disconnect(self): | 214 def disconnect(self): |
229 # we have no way to remove it from database yet (same thing for a | 230 # we have no way to remove it from database yet (same thing for a |
230 # correspondent jid) | 231 # correspondent jid) |
231 # TODO: manage explicit message encryption | 232 # TODO: manage explicit message encryption |
232 | 233 |
233 def __init__(self, host, client): | 234 def __init__(self, host, client): |
234 log.debug(u"new account: %s" % client.jid) | 235 log.debug("new account: %s" % client.jid) |
235 if not client.jid.resource: | 236 if not client.jid.resource: |
236 log.warning("Account created without resource") | 237 log.warning("Account created without resource") |
237 super(Account, self).__init__(unicode(client.jid), "xmpp", 1024) | 238 super(Account, self).__init__(str(client.jid), "xmpp", 1024) |
238 self.host = host | 239 self.host = host |
239 self.client = client | 240 self.client = client |
240 | 241 |
241 def loadPrivkey(self): | 242 def loadPrivkey(self): |
242 log.debug(u"loadPrivkey") | 243 log.debug("loadPrivkey") |
243 return self.privkey | 244 return self.privkey |
244 | 245 |
245 def savePrivkey(self): | 246 def savePrivkey(self): |
246 log.debug(u"savePrivkey") | 247 log.debug("savePrivkey") |
247 if self.privkey is None: | 248 if self.privkey is None: |
248 raise exceptions.InternalError(_(u"Save is called but privkey is None !")) | 249 raise exceptions.InternalError(_("Save is called but privkey is None !")) |
249 priv_key = self.privkey.serializePrivateKey().encode("hex") | 250 priv_key = hexlify(self.privkey.serializePrivateKey()) |
250 d = self.host.memory.encryptValue(priv_key, self.client.profile) | 251 d = self.host.memory.encryptValue(priv_key, self.client.profile) |
251 | 252 |
252 def save_encrypted_key(encrypted_priv_key): | 253 def save_encrypted_key(encrypted_priv_key): |
253 self.client._otr_data[PRIVATE_KEY] = encrypted_priv_key | 254 self.client._otr_data[PRIVATE_KEY] = encrypted_priv_key |
254 | 255 |
255 d.addCallback(save_encrypted_key) | 256 d.addCallback(save_encrypted_key) |
256 | 257 |
257 def loadTrusts(self): | 258 def loadTrusts(self): |
258 trust_data = self.client._otr_data.get("trust", {}) | 259 trust_data = self.client._otr_data.get("trust", {}) |
259 for jid_, jid_data in trust_data.iteritems(): | 260 for jid_, jid_data in trust_data.items(): |
260 for fingerprint, trust_level in jid_data.iteritems(): | 261 for fingerprint, trust_level in jid_data.items(): |
261 log.debug( | 262 log.debug( |
262 u'setting trust for {jid}: [{fingerprint}] = "{trust_level}"'.format( | 263 'setting trust for {jid}: [{fingerprint}] = "{trust_level}"'.format( |
263 jid=jid_, fingerprint=fingerprint, trust_level=trust_level | 264 jid=jid_, fingerprint=fingerprint, trust_level=trust_level |
264 ) | 265 ) |
265 ) | 266 ) |
266 self.trusts.setdefault(jid.JID(jid_), {})[fingerprint] = trust_level | 267 self.trusts.setdefault(jid.JID(jid_), {})[fingerprint] = trust_level |
267 | 268 |
268 def saveTrusts(self): | 269 def saveTrusts(self): |
269 log.debug(u"saving trusts for {profile}".format(profile=self.client.profile)) | 270 log.debug("saving trusts for {profile}".format(profile=self.client.profile)) |
270 log.debug(u"trusts = {}".format(self.client._otr_data["trust"])) | 271 log.debug("trusts = {}".format(self.client._otr_data["trust"])) |
271 self.client._otr_data.force("trust") | 272 self.client._otr_data.force("trust") |
272 | 273 |
273 def setTrust(self, other_jid, fingerprint, trustLevel): | 274 def setTrust(self, other_jid, fingerprint, trustLevel): |
274 try: | 275 try: |
275 trust_data = self.client._otr_data["trust"] | 276 trust_data = self.client._otr_data["trust"] |
297 other_jid, Context(self, other_jid) | 298 other_jid, Context(self, other_jid) |
298 ) | 299 ) |
299 return context | 300 return context |
300 | 301 |
301 def getContextForUser(self, other): | 302 def getContextForUser(self, other): |
302 log.debug(u"getContextForUser [%s]" % other) | 303 log.debug("getContextForUser [%s]" % other) |
303 if not other.resource: | 304 if not other.resource: |
304 log.warning(u"getContextForUser called with a bare jid: %s" % other.full()) | 305 log.warning("getContextForUser called with a bare jid: %s" % other.full()) |
305 return self.startContext(other) | 306 return self.startContext(other) |
306 | 307 |
307 | 308 |
308 class OTR(object): | 309 class OTR(object): |
309 | 310 |
310 def __init__(self, host): | 311 def __init__(self, host): |
311 log.info(_(u"OTR plugin initialization")) | 312 log.info(_("OTR plugin initialization")) |
312 self.host = host | 313 self.host = host |
313 self.context_managers = {} | 314 self.context_managers = {} |
314 self.skipped_profiles = ( | 315 self.skipped_profiles = ( |
315 set() | 316 set() |
316 ) # FIXME: OTR should not be skipped per profile, this need to be refactored | 317 ) # FIXME: OTR should not be skipped per profile, this need to be refactored |
317 self._p_hints = host.plugins[u"XEP-0334"] | 318 self._p_hints = host.plugins["XEP-0334"] |
318 self._p_carbons = host.plugins[u"XEP-0280"] | 319 self._p_carbons = host.plugins["XEP-0280"] |
319 host.trigger.add("MessageReceived", self.MessageReceivedTrigger, priority=100000) | 320 host.trigger.add("MessageReceived", self.MessageReceivedTrigger, priority=100000) |
320 host.trigger.add("sendMessage", self.sendMessageTrigger, priority=100000) | 321 host.trigger.add("sendMessage", self.sendMessageTrigger, priority=100000) |
321 host.trigger.add("sendMessageData", self._sendMessageDataTrigger) | 322 host.trigger.add("sendMessageData", self._sendMessageDataTrigger) |
322 host.bridge.addMethod( | 323 host.bridge.addMethod( |
323 "skipOTR", ".plugin", in_sign="s", out_sign="", method=self._skipOTR | 324 "skipOTR", ".plugin", in_sign="s", out_sign="", method=self._skipOTR |
353 # self._dropPrivKey, | 354 # self._dropPrivKey, |
354 # security_limit=0, | 355 # security_limit=0, |
355 # type_=C.MENU_SINGLE, | 356 # type_=C.MENU_SINGLE, |
356 # ) | 357 # ) |
357 host.trigger.add("presence_received", self._presenceReceivedTrigger) | 358 host.trigger.add("presence_received", self._presenceReceivedTrigger) |
358 self.host.registerEncryptionPlugin(self, u"OTR", NS_OTR, directed=True) | 359 self.host.registerEncryptionPlugin(self, "OTR", NS_OTR, directed=True) |
359 | 360 |
360 def _skipOTR(self, profile): | 361 def _skipOTR(self, profile): |
361 """Tell the backend to not handle OTR for this profile. | 362 """Tell the backend to not handle OTR for this profile. |
362 | 363 |
363 @param profile (str): %(doc_profile)s | 364 @param profile (str): %(doc_profile)s |
378 if encrypted_priv_key is not None: | 379 if encrypted_priv_key is not None: |
379 priv_key = yield self.host.memory.decryptValue( | 380 priv_key = yield self.host.memory.decryptValue( |
380 encrypted_priv_key, client.profile | 381 encrypted_priv_key, client.profile |
381 ) | 382 ) |
382 ctxMng.account.privkey = potr.crypt.PK.parsePrivateKey( | 383 ctxMng.account.privkey = potr.crypt.PK.parsePrivateKey( |
383 priv_key.decode("hex") | 384 unhexlify(priv_key.encode('utf-8')) |
384 )[0] | 385 )[0] |
385 else: | 386 else: |
386 ctxMng.account.privkey = None | 387 ctxMng.account.privkey = None |
387 ctxMng.account.loadTrusts() | 388 ctxMng.account.loadTrusts() |
388 | 389 |
389 def profileDisconnected(self, client): | 390 def profileDisconnected(self, client): |
390 if client.profile in self.skipped_profiles: | 391 if client.profile in self.skipped_profiles: |
391 self.skipped_profiles.remove(client.profile) | 392 self.skipped_profiles.remove(client.profile) |
392 return | 393 return |
393 for context in client._otr_context_manager.contexts.values(): | 394 for context in list(client._otr_context_manager.contexts.values()): |
394 context.disconnect() | 395 context.disconnect() |
395 del client._otr_context_manager | 396 del client._otr_context_manager |
396 | 397 |
397 # encryption plugin methods | 398 # encryption plugin methods |
398 | 399 |
417 dialog = xml_tools.XMLUI( | 418 dialog = xml_tools.XMLUI( |
418 C.XMLUI_DIALOG, | 419 C.XMLUI_DIALOG, |
419 dialog_opt={ | 420 dialog_opt={ |
420 C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE, | 421 C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE, |
421 C.XMLUI_DATA_MESS: _( | 422 C.XMLUI_DATA_MESS: _( |
422 u"You have no private key yet, start an OTR conversation to " | 423 "You have no private key yet, start an OTR conversation to " |
423 u"have one" | 424 "have one" |
424 ), | 425 ), |
425 C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_WARNING, | 426 C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_WARNING, |
426 }, | 427 }, |
427 title=_(u"No private key"), | 428 title=_("No private key"), |
428 ) | 429 ) |
429 return dialog | 430 return dialog |
430 | 431 |
431 other_fingerprint = otrctx.getCurrentKey() | 432 other_fingerprint = otrctx.getCurrentKey() |
432 | 433 |
435 dialog = xml_tools.XMLUI( | 436 dialog = xml_tools.XMLUI( |
436 C.XMLUI_DIALOG, | 437 C.XMLUI_DIALOG, |
437 dialog_opt={ | 438 dialog_opt={ |
438 C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE, | 439 C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE, |
439 C.XMLUI_DATA_MESS: _( | 440 C.XMLUI_DATA_MESS: _( |
440 u"Your fingerprint is:\n{fingerprint}\n\n" | 441 "Your fingerprint is:\n{fingerprint}\n\n" |
441 u"Start an OTR conversation to have your correspondent one." | 442 "Start an OTR conversation to have your correspondent one." |
442 ).format(fingerprint=priv_key), | 443 ).format(fingerprint=priv_key), |
443 C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_INFO, | 444 C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_INFO, |
444 }, | 445 }, |
445 title=_(u"Fingerprint"), | 446 title=_("Fingerprint"), |
446 ) | 447 ) |
447 return dialog | 448 return dialog |
448 | 449 |
449 def setTrust(raw_data, profile): | 450 def setTrust(raw_data, profile): |
450 if xml_tools.isXMLUICancelled(raw_data): | 451 if xml_tools.isXMLUICancelled(raw_data): |
451 return {} | 452 return {} |
452 # This method is called when authentication form is submited | 453 # This method is called when authentication form is submited |
453 data = xml_tools.XMLUIResult2DataFormResult(raw_data) | 454 data = xml_tools.XMLUIResult2DataFormResult(raw_data) |
454 if data["match"] == "yes": | 455 if data["match"] == "yes": |
455 otrctx.setCurrentTrust(OTR_STATE_TRUSTED) | 456 otrctx.setCurrentTrust(OTR_STATE_TRUSTED) |
456 note_msg = _(u"Your correspondent {correspondent} is now TRUSTED") | 457 note_msg = _("Your correspondent {correspondent} is now TRUSTED") |
457 self.host.bridge.otrState( | 458 self.host.bridge.otrState( |
458 OTR_STATE_TRUSTED, entity_jid.full(), client.profile | 459 OTR_STATE_TRUSTED, entity_jid.full(), client.profile |
459 ) | 460 ) |
460 else: | 461 else: |
461 otrctx.setCurrentTrust("") | 462 otrctx.setCurrentTrust("") |
462 note_msg = _(u"Your correspondent {correspondent} is now UNTRUSTED") | 463 note_msg = _("Your correspondent {correspondent} is now UNTRUSTED") |
463 self.host.bridge.otrState( | 464 self.host.bridge.otrState( |
464 OTR_STATE_UNTRUSTED, entity_jid.full(), client.profile | 465 OTR_STATE_UNTRUSTED, entity_jid.full(), client.profile |
465 ) | 466 ) |
466 note = xml_tools.XMLUI( | 467 note = xml_tools.XMLUI( |
467 C.XMLUI_DIALOG, | 468 C.XMLUI_DIALOG, |
475 submit_id = self.host.registerCallback(setTrust, with_data=True, one_shot=True) | 476 submit_id = self.host.registerCallback(setTrust, with_data=True, one_shot=True) |
476 trusted = otrctx.isTrusted() | 477 trusted = otrctx.isTrusted() |
477 | 478 |
478 xmlui = xml_tools.XMLUI( | 479 xmlui = xml_tools.XMLUI( |
479 C.XMLUI_FORM, | 480 C.XMLUI_FORM, |
480 title=_(u"Authentication ({entity_jid})").format(entity_jid=entity_jid.full()), | 481 title=_("Authentication ({entity_jid})").format(entity_jid=entity_jid.full()), |
481 submit_id=submit_id, | 482 submit_id=submit_id, |
482 ) | 483 ) |
483 xmlui.addText(_(AUTH_TXT)) | 484 xmlui.addText(_(AUTH_TXT)) |
484 xmlui.addDivider() | 485 xmlui.addDivider() |
485 xmlui.addText( | 486 xmlui.addText( |
486 D_(u"Your own fingerprint is:\n{fingerprint}").format(fingerprint=priv_key) | 487 D_("Your own fingerprint is:\n{fingerprint}").format(fingerprint=priv_key) |
487 ) | 488 ) |
488 xmlui.addText( | 489 xmlui.addText( |
489 D_(u"Your correspondent fingerprint should be:\n{fingerprint}").format( | 490 D_("Your correspondent fingerprint should be:\n{fingerprint}").format( |
490 fingerprint=other_fingerprint | 491 fingerprint=other_fingerprint |
491 ) | 492 ) |
492 ) | 493 ) |
493 xmlui.addDivider("blank") | 494 xmlui.addDivider("blank") |
494 xmlui.changeContainer("pairs") | 495 xmlui.changeContainer("pairs") |
495 xmlui.addLabel(D_(u"Is your correspondent fingerprint the same as here ?")) | 496 xmlui.addLabel(D_("Is your correspondent fingerprint the same as here ?")) |
496 xmlui.addList( | 497 xmlui.addList( |
497 "match", [("yes", _("yes")), ("no", _("no"))], ["yes" if trusted else "no"] | 498 "match", [("yes", _("yes")), ("no", _("no"))], ["yes" if trusted else "no"] |
498 ) | 499 ) |
499 return xmlui | 500 return xmlui |
500 | 501 |
506 """ | 507 """ |
507 client = self.host.getClient(profile) | 508 client = self.host.getClient(profile) |
508 try: | 509 try: |
509 to_jid = jid.JID(menu_data["jid"]) | 510 to_jid = jid.JID(menu_data["jid"]) |
510 except KeyError: | 511 except KeyError: |
511 log.error(_(u"jid key is not present !")) | 512 log.error(_("jid key is not present !")) |
512 return defer.fail(exceptions.DataError) | 513 return defer.fail(exceptions.DataError) |
513 self.startRefresh(client, to_jid) | 514 self.startRefresh(client, to_jid) |
514 return {} | 515 return {} |
515 | 516 |
516 def startRefresh(self, client, to_jid): | 517 def startRefresh(self, client, to_jid): |
517 """Start or refresh an OTR session | 518 """Start or refresh an OTR session |
518 | 519 |
519 @param to_jid(jid.JID): jid to start encrypted session with | 520 @param to_jid(jid.JID): jid to start encrypted session with |
520 """ | 521 """ |
521 encrypted_session = client.encryption.getSession(to_jid.userhostJID()) | 522 encrypted_session = client.encryption.getSession(to_jid.userhostJID()) |
522 if encrypted_session and encrypted_session[u'plugin'].namespace != NS_OTR: | 523 if encrypted_session and encrypted_session['plugin'].namespace != NS_OTR: |
523 raise exceptions.ConflictError(_( | 524 raise exceptions.ConflictError(_( |
524 u"Can't start an OTR session, there is already an encrypted session " | 525 "Can't start an OTR session, there is already an encrypted session " |
525 u"with {name}").format(name=encrypted_session[u'plugin'].name)) | 526 "with {name}").format(name=encrypted_session['plugin'].name)) |
526 if not to_jid.resource: | 527 if not to_jid.resource: |
527 to_jid.resource = self.host.memory.getMainResource( | 528 to_jid.resource = self.host.memory.getMainResource( |
528 client, to_jid | 529 client, to_jid |
529 ) # FIXME: temporary and unsecure, must be changed when frontends | 530 ) # FIXME: temporary and unsecure, must be changed when frontends |
530 # are refactored | 531 # are refactored |
540 """ | 541 """ |
541 client = self.host.getClient(profile) | 542 client = self.host.getClient(profile) |
542 try: | 543 try: |
543 to_jid = jid.JID(menu_data["jid"]) | 544 to_jid = jid.JID(menu_data["jid"]) |
544 except KeyError: | 545 except KeyError: |
545 log.error(_(u"jid key is not present !")) | 546 log.error(_("jid key is not present !")) |
546 return defer.fail(exceptions.DataError) | 547 return defer.fail(exceptions.DataError) |
547 self.endSession(client, to_jid) | 548 self.endSession(client, to_jid) |
548 return {} | 549 return {} |
549 | 550 |
550 def endSession(self, client, to_jid): | 551 def endSession(self, client, to_jid): |
566 """ | 567 """ |
567 client = self.host.getClient(profile) | 568 client = self.host.getClient(profile) |
568 try: | 569 try: |
569 to_jid = jid.JID(menu_data["jid"]) | 570 to_jid = jid.JID(menu_data["jid"]) |
570 except KeyError: | 571 except KeyError: |
571 log.error(_(u"jid key is not present !")) | 572 log.error(_("jid key is not present !")) |
572 return defer.fail(exceptions.DataError) | 573 return defer.fail(exceptions.DataError) |
573 return self.authenticate(client, to_jid) | 574 return self.authenticate(client, to_jid) |
574 | 575 |
575 def authenticate(self, client, to_jid): | 576 def authenticate(self, client, to_jid): |
576 """Authenticate other user and see our own fingerprint""" | 577 """Authenticate other user and see our own fingerprint""" |
590 to_jid.resource = self.host.memory.getMainResource( | 591 to_jid.resource = self.host.memory.getMainResource( |
591 client, to_jid | 592 client, to_jid |
592 ) # FIXME: temporary and unsecure, must be changed when frontends | 593 ) # FIXME: temporary and unsecure, must be changed when frontends |
593 # are refactored | 594 # are refactored |
594 except KeyError: | 595 except KeyError: |
595 log.error(_(u"jid key is not present !")) | 596 log.error(_("jid key is not present !")) |
596 return defer.fail(exceptions.DataError) | 597 return defer.fail(exceptions.DataError) |
597 | 598 |
598 ctxMng = client._otr_context_manager | 599 ctxMng = client._otr_context_manager |
599 if ctxMng.account.privkey is None: | 600 if ctxMng.account.privkey is None: |
600 return { | 601 return { |
601 "xmlui": xml_tools.note(_(u"You don't have a private key yet !")).toXml() | 602 "xmlui": xml_tools.note(_("You don't have a private key yet !")).toXml() |
602 } | 603 } |
603 | 604 |
604 def dropKey(data, profile): | 605 def dropKey(data, profile): |
605 if C.bool(data["answer"]): | 606 if C.bool(data["answer"]): |
606 # we end all sessions | 607 # we end all sessions |
607 for context in ctxMng.contexts.values(): | 608 for context in list(ctxMng.contexts.values()): |
608 context.disconnect() | 609 context.disconnect() |
609 ctxMng.account.privkey = None | 610 ctxMng.account.privkey = None |
610 ctxMng.account.getPrivkey() # as account.privkey is None, getPrivkey | 611 ctxMng.account.getPrivkey() # as account.privkey is None, getPrivkey |
611 # will generate a new key, and save it | 612 # will generate a new key, and save it |
612 return { | 613 return { |
613 "xmlui": xml_tools.note( | 614 "xmlui": xml_tools.note( |
614 D_(u"Your private key has been dropped") | 615 D_("Your private key has been dropped") |
615 ).toXml() | 616 ).toXml() |
616 } | 617 } |
617 return {} | 618 return {} |
618 | 619 |
619 submit_id = self.host.registerCallback(dropKey, with_data=True, one_shot=True) | 620 submit_id = self.host.registerCallback(dropKey, with_data=True, one_shot=True) |
620 | 621 |
621 confirm = xml_tools.XMLUI( | 622 confirm = xml_tools.XMLUI( |
622 C.XMLUI_DIALOG, | 623 C.XMLUI_DIALOG, |
623 title=_(u"Confirm private key drop"), | 624 title=_("Confirm private key drop"), |
624 dialog_opt={"type": C.XMLUI_DIALOG_CONFIRM, "message": _(DROP_TXT)}, | 625 dialog_opt={"type": C.XMLUI_DIALOG_CONFIRM, "message": _(DROP_TXT)}, |
625 submit_id=submit_id, | 626 submit_id=submit_id, |
626 ) | 627 ) |
627 return {"xmlui": confirm.toXml()} | 628 return {"xmlui": confirm.toXml()} |
628 | 629 |
629 def _receivedTreatment(self, data, client): | 630 def _receivedTreatment(self, data, client): |
630 from_jid = data["from"] | 631 from_jid = data["from"] |
631 log.debug(u"_receivedTreatment [from_jid = %s]" % from_jid) | 632 log.debug("_receivedTreatment [from_jid = %s]" % from_jid) |
632 otrctx = client._otr_context_manager.getContextForUser(from_jid) | 633 otrctx = client._otr_context_manager.getContextForUser(from_jid) |
633 | 634 |
634 try: | 635 try: |
635 message = ( | 636 message = ( |
636 data["message"].itervalues().next() | 637 next(iter(data["message"].values())) |
637 ) # FIXME: Q&D fix for message refactoring, message is now a dict | 638 ) # FIXME: Q&D fix for message refactoring, message is now a dict |
638 res = otrctx.receiveMessage(message.encode("utf-8")) | 639 res = otrctx.receiveMessage(message.encode("utf-8")) |
639 except potr.context.UnencryptedMessage: | 640 except potr.context.UnencryptedMessage: |
640 encrypted = False | 641 encrypted = False |
641 if otrctx.state == potr.context.STATE_ENCRYPTED: | 642 if otrctx.state == potr.context.STATE_ENCRYPTED: |
642 log.warning( | 643 log.warning( |
643 u"Received unencrypted message in an encrypted context (from {jid})" | 644 "Received unencrypted message in an encrypted context (from {jid})" |
644 .format(jid=from_jid.full()) | 645 .format(jid=from_jid.full()) |
645 ) | 646 ) |
646 | 647 |
647 feedback = ( | 648 feedback = ( |
648 D_( | 649 D_( |
649 u"WARNING: received unencrypted data in a supposedly encrypted " | 650 "WARNING: received unencrypted data in a supposedly encrypted " |
650 u"context" | 651 "context" |
651 ), | 652 ), |
652 ) | 653 ) |
653 client.feedback(from_jid, feedback) | 654 client.feedback(from_jid, feedback) |
654 except potr.context.NotEncryptedError: | 655 except potr.context.NotEncryptedError: |
655 msg = D_(u"WARNING: received OTR encrypted data in an unencrypted context") | 656 msg = D_("WARNING: received OTR encrypted data in an unencrypted context") |
656 log.warning(msg) | 657 log.warning(msg) |
657 feedback = msg | 658 feedback = msg |
658 client.feedback(from_jid, msg) | 659 client.feedback(from_jid, msg) |
659 raise failure.Failure(exceptions.CancelError(msg)) | 660 raise failure.Failure(exceptions.CancelError(msg)) |
660 except potr.context.ErrorReceived as e: | 661 except potr.context.ErrorReceived as e: |
661 msg = D_(u"WARNING: received OTR error message: {msg}".format(msg=e)) | 662 msg = D_("WARNING: received OTR error message: {msg}".format(msg=e)) |
662 log.warning(msg) | 663 log.warning(msg) |
663 feedback = msg | 664 feedback = msg |
664 client.feedback(from_jid, msg) | 665 client.feedback(from_jid, msg) |
665 raise failure.Failure(exceptions.CancelError(msg)) | 666 raise failure.Failure(exceptions.CancelError(msg)) |
666 except potr.crypt.InvalidParameterError as e: | 667 except potr.crypt.InvalidParameterError as e: |
667 msg = D_(u"Error while trying de decrypt OTR message: {msg}".format(msg=e)) | 668 msg = D_("Error while trying de decrypt OTR message: {msg}".format(msg=e)) |
668 log.warning(msg) | 669 log.warning(msg) |
669 feedback = msg | 670 feedback = msg |
670 client.feedback(from_jid, msg) | 671 client.feedback(from_jid, msg) |
671 raise failure.Failure(exceptions.CancelError(msg)) | 672 raise failure.Failure(exceptions.CancelError(msg)) |
672 except StopIteration: | 673 except StopIteration: |
678 if res[0] != None: | 679 if res[0] != None: |
679 # decrypted messages handling. | 680 # decrypted messages handling. |
680 # receiveMessage() will return a tuple, | 681 # receiveMessage() will return a tuple, |
681 # the first part of which will be the decrypted message | 682 # the first part of which will be the decrypted message |
682 data["message"] = { | 683 data["message"] = { |
683 "": res[0].decode("utf-8") | 684 "": res[0] |
684 } # FIXME: Q&D fix for message refactoring, message is now a dict | 685 } # FIXME: Q&D fix for message refactoring, message is now a dict |
685 try: | 686 try: |
686 # we want to keep message in history, even if no store is | 687 # we want to keep message in history, even if no store is |
687 # requested in message hints | 688 # requested in message hints |
688 del data[u"history"] | 689 del data["history"] |
689 except KeyError: | 690 except KeyError: |
690 pass | 691 pass |
691 # TODO: add skip history as an option, but by default we don't skip it | 692 # TODO: add skip history as an option, but by default we don't skip it |
692 # data[u'history'] = C.HISTORY_SKIP # we send the decrypted message to | 693 # data[u'history'] = C.HISTORY_SKIP # we send the decrypted message to |
693 # frontends, but we don't want it in | 694 # frontends, but we don't want it in |
713 but we still need to check if the message must be stored in history or not | 714 but we still need to check if the message must be stored in history or not |
714 """ | 715 """ |
715 # XXX: FIXME: this should not be done on a per-profile basis, but per-message | 716 # XXX: FIXME: this should not be done on a per-profile basis, but per-message |
716 try: | 717 try: |
717 message = ( | 718 message = ( |
718 data["message"].itervalues().next().encode("utf-8") | 719 iter(data["message"].values()).next().encode("utf-8") |
719 ) # FIXME: Q&D fix for message refactoring, message is now a dict | 720 ) # FIXME: Q&D fix for message refactoring, message is now a dict |
720 except StopIteration: | 721 except StopIteration: |
721 return data | 722 return data |
722 if message.startswith(potr.proto.OTRTAG): | 723 if message.startswith(potr.proto.OTRTAG): |
723 # FIXME: it may be better to cancel the message and send it direclty to | 724 # FIXME: it may be better to cancel the message and send it direclty to |
724 # bridge | 725 # bridge |
725 # this is used by Libervia, but this may send garbage message to | 726 # this is used by Libervia, but this may send garbage message to |
726 # other frontends | 727 # other frontends |
727 # if they are used at the same time as Libervia. | 728 # if they are used at the same time as Libervia. |
728 # Hard to avoid with decryption on Libervia though. | 729 # Hard to avoid with decryption on Libervia though. |
729 data[u"history"] = C.HISTORY_SKIP | 730 data["history"] = C.HISTORY_SKIP |
730 return data | 731 return data |
731 | 732 |
732 def MessageReceivedTrigger(self, client, message_elt, post_treat): | 733 def MessageReceivedTrigger(self, client, message_elt, post_treat): |
733 if message_elt.getAttribute("type") == C.MESS_TYPE_GROUPCHAT: | 734 if message_elt.getAttribute("type") == C.MESS_TYPE_GROUPCHAT: |
734 # OTR is not possible in group chats | 735 # OTR is not possible in group chats |
753 client, to_jid | 754 client, to_jid |
754 ) # FIXME: temporary and unsecure, must be changed when frontends | 755 ) # FIXME: temporary and unsecure, must be changed when frontends |
755 otrctx = client._otr_context_manager.getContextForUser(to_jid) | 756 otrctx = client._otr_context_manager.getContextForUser(to_jid) |
756 message_elt = mess_data["xml"] | 757 message_elt = mess_data["xml"] |
757 if otrctx.state == potr.context.STATE_ENCRYPTED: | 758 if otrctx.state == potr.context.STATE_ENCRYPTED: |
758 log.debug(u"encrypting message") | 759 log.debug("encrypting message") |
759 body = None | 760 body = None |
760 for child in list(message_elt.children): | 761 for child in list(message_elt.children): |
761 if child.name == "body": | 762 if child.name == "body": |
762 # we remove all unencrypted body, | 763 # we remove all unencrypted body, |
763 # and will only encrypt the first one | 764 # and will only encrypt the first one |
766 message_elt.children.remove(child) | 767 message_elt.children.remove(child) |
767 elif child.name == "html": | 768 elif child.name == "html": |
768 # we don't want any XHTML-IM element | 769 # we don't want any XHTML-IM element |
769 message_elt.children.remove(child) | 770 message_elt.children.remove(child) |
770 if body is None: | 771 if body is None: |
771 log.warning(u"No message found") | 772 log.warning("No message found") |
772 else: | 773 else: |
773 self._p_carbons.setPrivate(message_elt) | 774 self._p_carbons.setPrivate(message_elt) |
774 self._p_hints.addHintElements(message_elt, [ | 775 self._p_hints.addHintElements(message_elt, [ |
775 self._p_hints.HINT_NO_COPY, | 776 self._p_hints.HINT_NO_COPY, |
776 self._p_hints.HINT_NO_PERMANENT_STORE]) | 777 self._p_hints.HINT_NO_PERMANENT_STORE]) |
777 otrctx.sendMessage(0, unicode(body).encode("utf-8"), appdata=mess_data) | 778 otrctx.sendMessage(0, str(body).encode("utf-8"), appdata=mess_data) |
778 else: | 779 else: |
779 feedback = D_( | 780 feedback = D_( |
780 u"Your message was not sent because your correspondent closed the " | 781 "Your message was not sent because your correspondent closed the " |
781 u"encrypted conversation on his/her side. " | 782 "encrypted conversation on his/her side. " |
782 u"Either close your own side, or refresh the session." | 783 "Either close your own side, or refresh the session." |
783 ) | 784 ) |
784 log.warning(_(u"Message discarded because closed encryption channel")) | 785 log.warning(_("Message discarded because closed encryption channel")) |
785 client.feedback(to_jid, feedback) | 786 client.feedback(to_jid, feedback) |
786 raise failure.Failure(exceptions.CancelError(u"Cancelled by OTR plugin")) | 787 raise failure.Failure(exceptions.CancelError("Cancelled by OTR plugin")) |
787 | 788 |
788 def sendMessageTrigger(self, client, mess_data, pre_xml_treatments, | 789 def sendMessageTrigger(self, client, mess_data, pre_xml_treatments, |
789 post_xml_treatments): | 790 post_xml_treatments): |
790 if mess_data["type"] == "groupchat": | 791 if mess_data["type"] == "groupchat": |
791 return True | 792 return True |