Mercurial > libervia-backend
comparison src/plugins/plugin_sec_otr.py @ 2128:aa94f33fd2ad
plugin otr: various improvments:
- messageSend trigger now use pre_xml_treatments so it doesn't block other triggers of higher priority
- text commands now use a very high priority, as it is local command and should not be blocked in most of cases
- new otrState signal, to send state change to frontends
- history is not skipped anymore, a future option may change this behaviour
- OTR trigger are skipped on groupchat messages
- context_manager is now in client instead of being global to plugin
- removed fixPotr as it is fixed upstream
note triggers should be improved for encryption methods, as skipping an encrypter may break security, but putting it in top priority may break nice features.
fix bug 170
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 01 Feb 2017 21:44:24 +0100 |
parents | ca82c97db195 |
children | 6a66c8c5a567 |
comparison
equal
deleted
inserted
replaced
2127:8717e9cc95c0 | 2128:aa94f33fd2ad |
---|---|
33 import potr | 33 import potr |
34 import copy | 34 import copy |
35 import time | 35 import time |
36 import uuid | 36 import uuid |
37 | 37 |
38 NS_OTR = "otr_plugin" | |
39 PRIVATE_KEY = "PRIVATE KEY" | |
40 OTR_MENU = D_(u'OTR') | |
41 AUTH_TXT = D_(u"To authenticate your correspondent, you need to give your below fingerprint *BY AN EXTERNAL CANAL* (i.e. not in this chat), and check that the one he gives you is the same as below. If there is a mismatch, there can be a spy between you!") | |
42 DROP_TXT = D_(u"You private key is used to encrypt messages for your correspondent, nobody except you must know it, if you are in doubt, you should drop it!\n\nAre you sure you want to drop your private key?") | |
43 NO_LOG = D_(u"/!\\Your history is not logged anymore, and most of advanced features are disabled !") # FIXME: not used at the moment | |
44 | |
45 DEFAULT_POLICY_FLAGS = { | |
46 'ALLOW_V1':False, | |
47 'ALLOW_V2':True, | |
48 'REQUIRE_ENCRYPTION':True, | |
49 } | |
50 | |
51 | 38 |
52 PLUGIN_INFO = { | 39 PLUGIN_INFO = { |
53 "name": "OTR", | 40 "name": "OTR", |
54 "import_name": "OTR", | 41 "import_name": "OTR", |
55 "type": "SEC", | 42 "type": "SEC", |
57 "dependencies": [], | 44 "dependencies": [], |
58 "main": "OTR", | 45 "main": "OTR", |
59 "handler": "no", | 46 "handler": "no", |
60 "description": _(u"""Implementation of OTR""") | 47 "description": _(u"""Implementation of OTR""") |
61 } | 48 } |
49 | |
50 NS_OTR = "otr_plugin" | |
51 PRIVATE_KEY = "PRIVATE KEY" | |
52 OTR_MENU = D_(u'OTR') | |
53 AUTH_TXT = D_(u"To authenticate your correspondent, you need to give your below fingerprint *BY AN EXTERNAL CANAL* (i.e. not in this chat), and check that the one he gives you is the same as below. If there is a mismatch, there can be a spy between you!") | |
54 DROP_TXT = D_(u"You private key is used to encrypt messages for your correspondent, nobody except you must know it, if you are in doubt, you should drop it!\n\nAre you sure you want to drop your private key?") | |
55 # NO_LOG_AND = D_(u"/!\\Your history is not logged anymore, and") # FIXME: not used at the moment | |
56 NO_ADV_FEATURES = D_(u"Most of advanced features are disabled !") | |
57 | |
58 DEFAULT_POLICY_FLAGS = { | |
59 'ALLOW_V1':False, | |
60 'ALLOW_V2':True, | |
61 'REQUIRE_ENCRYPTION':True, | |
62 } | |
63 | |
64 OTR_STATE_TRUSTED = 'trusted' | |
65 OTR_STATE_UNTRUSTED = 'untrusted' | |
66 OTR_STATE_UNENCRYPTED = 'unencrypted' | |
67 OTR_STATE_ENCRYPTED = 'encrypted' | |
62 | 68 |
63 | 69 |
64 class Context(potr.context.Context): | 70 class Context(potr.context.Context): |
65 def __init__(self, host, account, other_jid): | 71 def __init__(self, host, account, other_jid): |
66 super(Context, self).__init__(account, other_jid) | 72 super(Context, self).__init__(account, other_jid) |
89 } | 95 } |
90 self.host.generateMessageXML(mess_data) | 96 self.host.generateMessageXML(mess_data) |
91 client.xmlstream.send(mess_data['xml']) | 97 client.xmlstream.send(mess_data['xml']) |
92 | 98 |
93 def setState(self, state): | 99 def setState(self, state): |
100 client = self.user.client | |
94 old_state = self.state | 101 old_state = self.state |
95 super(Context, self).setState(state) | 102 super(Context, self).setState(state) |
96 log.debug(u"setState: %s (old_state=%s)" % (state, old_state)) | 103 log.debug(u"setState: %s (old_state=%s)" % (state, old_state)) |
97 | 104 |
98 if state == potr.context.STATE_PLAINTEXT: | 105 if state == potr.context.STATE_PLAINTEXT: |
99 feedback = _(u"/!\\ conversation with %(other_jid)s is now UNENCRYPTED") % {'other_jid': self.peer.full()} | 106 feedback = _(u"/!\\ conversation with %(other_jid)s is now UNENCRYPTED") % {'other_jid': self.peer.full()} |
107 self.host.bridge.otrState(OTR_STATE_UNENCRYPTED, self.peer.full(), client.profile) | |
100 elif state == potr.context.STATE_ENCRYPTED: | 108 elif state == potr.context.STATE_ENCRYPTED: |
101 try: | 109 try: |
102 trusted = self.getCurrentTrust() | 110 trusted = self.getCurrentTrust() |
103 except TypeError: | 111 except TypeError: |
104 trusted = False | 112 trusted = False |
107 if old_state == potr.context.STATE_ENCRYPTED: | 115 if old_state == potr.context.STATE_ENCRYPTED: |
108 feedback = D_(u"{trusted} OTR conversation with {other_jid} REFRESHED").format( | 116 feedback = D_(u"{trusted} OTR conversation with {other_jid} REFRESHED").format( |
109 trusted = trusted_str, | 117 trusted = trusted_str, |
110 other_jid = self.peer.full()) | 118 other_jid = self.peer.full()) |
111 else: | 119 else: |
112 feedback = D_(u"{trusted} Encrypted OTR conversation started with {other_jid}\n{no_log}").format( | 120 feedback = D_(u"{trusted} encrypted OTR conversation started with {other_jid}\n{extra_info}").format( |
113 trusted = trusted_str, | 121 trusted = trusted_str, |
114 other_jid = self.peer.full(), | 122 other_jid = self.peer.full(), |
115 no_log = NO_LOG) | 123 extra_info = NO_ADV_FEATURES) |
124 self.host.bridge.otrState(OTR_STATE_ENCRYPTED, self.peer.full(), client.profile) | |
116 elif state == potr.context.STATE_FINISHED: | 125 elif state == potr.context.STATE_FINISHED: |
117 feedback = D_(u"OTR conversation with {other_jid} is FINISHED").format(other_jid = self.peer.full()) | 126 feedback = D_(u"OTR conversation with {other_jid} is FINISHED").format(other_jid = self.peer.full()) |
127 self.host.bridge.otrState(OTR_STATE_UNENCRYPTED, self.peer.full(), client.profile) | |
118 else: | 128 else: |
119 log.error(D_(u"Unknown OTR state")) | 129 log.error(D_(u"Unknown OTR state")) |
120 return | 130 return |
121 | 131 |
122 client = self.user.client | |
123 self.host.bridge.messageNew(uid=unicode(uuid.uuid4()), | 132 self.host.bridge.messageNew(uid=unicode(uuid.uuid4()), |
124 timestamp=time.time(), | 133 timestamp=time.time(), |
125 from_jid=client.jid.full(), | 134 from_jid=client.jid.full(), |
126 to_jid=self.peer.full(), | 135 to_jid=self.peer.full(), |
127 message={u'': feedback}, | 136 message={u'': feedback}, |
128 subject={}, | 137 subject={}, |
129 mess_type=C.MESS_TYPE_INFO, | 138 mess_type=C.MESS_TYPE_INFO, |
130 extra={}, | 139 extra={}, |
131 profile=client.profile) | 140 profile=client.profile) |
132 # TODO: send signal to frontends | |
133 | 141 |
134 def disconnect(self): | 142 def disconnect(self): |
135 """Disconnect the session.""" | 143 """Disconnect the session.""" |
136 if self.state != potr.context.STATE_PLAINTEXT: | 144 if self.state != potr.context.STATE_PLAINTEXT: |
137 super(Context, self).disconnect() | 145 super(Context, self).disconnect() |
162 if self.privkey is None: | 170 if self.privkey is None: |
163 raise exceptions.InternalError(_(u"Save is called but privkey is None !")) | 171 raise exceptions.InternalError(_(u"Save is called but privkey is None !")) |
164 priv_key = self.privkey.serializePrivateKey().encode('hex') | 172 priv_key = self.privkey.serializePrivateKey().encode('hex') |
165 d = self.host.memory.encryptValue(priv_key, self.client.profile) | 173 d = self.host.memory.encryptValue(priv_key, self.client.profile) |
166 def save_encrypted_key(encrypted_priv_key): | 174 def save_encrypted_key(encrypted_priv_key): |
167 self.client.otr_data[PRIVATE_KEY] = encrypted_priv_key | 175 self.client._otr_data[PRIVATE_KEY] = encrypted_priv_key |
168 d.addCallback(save_encrypted_key) | 176 d.addCallback(save_encrypted_key) |
169 | 177 |
170 def loadTrusts(self): | 178 def loadTrusts(self): |
171 trust_data = self.client.otr_data.get('trust', {}) | 179 trust_data = self.client._otr_data.get('trust', {}) |
172 for jid_, jid_data in trust_data.iteritems(): | 180 for jid_, jid_data in trust_data.iteritems(): |
173 for fingerprint, trust_level in jid_data.iteritems(): | 181 for fingerprint, trust_level in jid_data.iteritems(): |
174 log.debug(u'setting trust for {jid}: [{fingerprint}] = "{trust_level}"'.format(jid=jid_, fingerprint=fingerprint, trust_level=trust_level)) | 182 log.debug(u'setting trust for {jid}: [{fingerprint}] = "{trust_level}"'.format(jid=jid_, fingerprint=fingerprint, trust_level=trust_level)) |
175 self.trusts.setdefault(jid.JID(jid_), {})[fingerprint] = trust_level | 183 self.trusts.setdefault(jid.JID(jid_), {})[fingerprint] = trust_level |
176 | 184 |
177 def saveTrusts(self): | 185 def saveTrusts(self): |
178 log.debug(u"saving trusts for {profile}".format(profile=self.client.profile)) | 186 log.debug(u"saving trusts for {profile}".format(profile=self.client.profile)) |
179 log.debug(u"trusts = {}".format(self.client.otr_data['trust'])) | 187 log.debug(u"trusts = {}".format(self.client._otr_data['trust'])) |
180 self.client.otr_data.force('trust') | 188 self.client._otr_data.force('trust') |
181 | 189 |
182 def setTrust(self, other_jid, fingerprint, trustLevel): | 190 def setTrust(self, other_jid, fingerprint, trustLevel): |
183 try: | 191 try: |
184 trust_data = self.client.otr_data['trust'] | 192 trust_data = self.client._otr_data['trust'] |
185 except KeyError: | 193 except KeyError: |
186 trust_data = {} | 194 trust_data = {} |
187 self.client.otr_data['trust'] = trust_data | 195 self.client._otr_data['trust'] = trust_data |
188 jid_data = trust_data.setdefault(other_jid.full(), {}) | 196 jid_data = trust_data.setdefault(other_jid.full(), {}) |
189 jid_data[fingerprint] = trustLevel | 197 jid_data[fingerprint] = trustLevel |
190 super(Account, self).setTrust(other_jid, fingerprint, trustLevel) | 198 super(Account, self).setTrust(other_jid, fingerprint, trustLevel) |
191 | 199 |
192 | 200 |
211 | 219 |
212 class OTR(object): | 220 class OTR(object): |
213 | 221 |
214 def __init__(self, host): | 222 def __init__(self, host): |
215 log.info(_(u"OTR plugin initialization")) | 223 log.info(_(u"OTR plugin initialization")) |
216 self._fixPotr() # FIXME: to be removed when potr will be fixed | |
217 self.host = host | 224 self.host = host |
218 self.context_managers = {} | 225 self.context_managers = {} |
219 self.skipped_profiles = set() | 226 self.skipped_profiles = set() # FIXME: OTR should not be skipped per profile, this need to be refactored |
220 host.trigger.add("MessageReceived", self.MessageReceivedTrigger, priority=100000) | 227 host.trigger.add("MessageReceived", self.MessageReceivedTrigger, priority=100000) |
221 host.trigger.add("messageSend", self.messageSendTrigger, priority=100000) | 228 host.trigger.add("messageSend", self.messageSendTrigger, priority=100000) |
222 host.bridge.addMethod("skipOTR", ".plugin", in_sign='s', out_sign='', method=self._skipOTR) # FIXME: must be removed, must be done on per-message basis | 229 host.bridge.addMethod("skipOTR", ".plugin", in_sign='s', out_sign='', method=self._skipOTR) # FIXME: must be removed, must be done on per-message basis |
230 host.bridge.addSignal("otrState", ".plugin", signature='sss') # args: state, destinee_jid, profile | |
223 host.importMenu((OTR_MENU, D_(u"Start/Refresh")), self._otrStartRefresh, security_limit=0, help_string=D_(u"Start or refresh an OTR session"), type_=C.MENU_SINGLE) | 231 host.importMenu((OTR_MENU, D_(u"Start/Refresh")), self._otrStartRefresh, security_limit=0, help_string=D_(u"Start or refresh an OTR session"), type_=C.MENU_SINGLE) |
224 host.importMenu((OTR_MENU, D_(u"End session")), self._otrSessionEnd, security_limit=0, help_string=D_(u"Finish an OTR session"), type_=C.MENU_SINGLE) | 232 host.importMenu((OTR_MENU, D_(u"End session")), self._otrSessionEnd, security_limit=0, help_string=D_(u"Finish an OTR session"), type_=C.MENU_SINGLE) |
225 host.importMenu((OTR_MENU, D_(u"Authenticate")), self._otrAuthenticate, security_limit=0, help_string=D_(u"Authenticate user/see your fingerprint"), type_=C.MENU_SINGLE) | 233 host.importMenu((OTR_MENU, D_(u"Authenticate")), self._otrAuthenticate, security_limit=0, help_string=D_(u"Authenticate user/see your fingerprint"), type_=C.MENU_SINGLE) |
226 host.importMenu((OTR_MENU, D_(u"Drop private key")), self._dropPrivKey, security_limit=0, type_=C.MENU_SINGLE) | 234 host.importMenu((OTR_MENU, D_(u"Drop private key")), self._dropPrivKey, security_limit=0, type_=C.MENU_SINGLE) |
227 host.trigger.add("presenceReceived", self._presenceReceivedTrigger) | 235 host.trigger.add("presenceReceived", self._presenceReceivedTrigger) |
228 | 236 |
229 def _fixPotr(self): | |
230 # FIXME: potr fix for bad unicode handling | |
231 # this method monkeypatch it, must be removed when potr | |
232 # is fixed | |
233 | |
234 def getDefaultQueryMessage(self, policy): | |
235 defaultQuery = '?OTRv{versions}?\nI would like to start ' \ | |
236 'an Off-the-Record private conversation. However, you ' \ | |
237 'do not have a plugin to support that.\nSee '\ | |
238 'https://otr.cypherpunks.ca/ for more information.' | |
239 v = '2' if policy('ALLOW_V2') else '' | |
240 msg = defaultQuery.format(versions=v) | |
241 return msg.encode('ascii') | |
242 | |
243 potr.context.Account.getDefaultQueryMessage = getDefaultQueryMessage | |
244 | |
245 def _skipOTR(self, profile): | 237 def _skipOTR(self, profile): |
246 """Tell the backend to not handle OTR for this profile. | 238 """Tell the backend to not handle OTR for this profile. |
247 | 239 |
248 @param profile (str): %(doc_profile)s | 240 @param profile (str): %(doc_profile)s |
249 """ | 241 """ |
255 @defer.inlineCallbacks | 247 @defer.inlineCallbacks |
256 def profileConnected(self, profile): | 248 def profileConnected(self, profile): |
257 if profile in self.skipped_profiles: | 249 if profile in self.skipped_profiles: |
258 return | 250 return |
259 client = self.host.getClient(profile) | 251 client = self.host.getClient(profile) |
260 ctxMng = self.context_managers[profile] = ContextManager(self.host, client) | 252 ctxMng = client._otr_context_manager = ContextManager(self.host, client) |
261 client.otr_data = persistent.PersistentBinaryDict(NS_OTR, profile) | 253 client._otr_data = persistent.PersistentBinaryDict(NS_OTR, profile) |
262 yield client.otr_data.load() | 254 yield client._otr_data.load() |
263 encrypted_priv_key = client.otr_data.get(PRIVATE_KEY, None) | 255 encrypted_priv_key = client._otr_data.get(PRIVATE_KEY, None) |
264 if encrypted_priv_key is not None: | 256 if encrypted_priv_key is not None: |
265 priv_key = yield self.host.memory.decryptValue(encrypted_priv_key, profile) | 257 priv_key = yield self.host.memory.decryptValue(encrypted_priv_key, profile) |
266 ctxMng.account.privkey = potr.crypt.PK.parsePrivateKey(priv_key.decode('hex'))[0] | 258 ctxMng.account.privkey = potr.crypt.PK.parsePrivateKey(priv_key.decode('hex'))[0] |
267 else: | 259 else: |
268 ctxMng.account.privkey = None | 260 ctxMng.account.privkey = None |
269 ctxMng.account.loadTrusts() | 261 ctxMng.account.loadTrusts() |
270 | 262 |
271 def profileDisconnected(self, profile): | 263 def profileDisconnected(self, profile): |
272 try: | 264 if profile in self.skipped_profiles: |
273 for context in self.context_managers[profile].contexts.values(): | |
274 context.disconnect() | |
275 del self.context_managers[profile] | |
276 except KeyError: | |
277 pass | |
278 try: | |
279 self.skipped_profiles.remove(profile) | 265 self.skipped_profiles.remove(profile) |
280 except KeyError: | 266 return |
281 pass | 267 client = self.host.getClient(profile) |
268 for context in client._otr_context_manager.contexts.values(): | |
269 context.disconnect() | |
270 del client._otr_context_manager | |
282 | 271 |
283 def _otrStartRefresh(self, menu_data, profile): | 272 def _otrStartRefresh(self, menu_data, profile): |
284 """Start or refresh an OTR session | 273 """Start or refresh an OTR session |
285 | 274 |
286 @param menu_data: %(menu_data)s | 275 @param menu_data: %(menu_data)s |
300 | 289 |
301 @param to_jid(jid.JID): jid to start encrypted session with | 290 @param to_jid(jid.JID): jid to start encrypted session with |
302 """ | 291 """ |
303 if not to_jid.resource: | 292 if not to_jid.resource: |
304 to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: temporary and unsecure, must be changed when frontends are refactored | 293 to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: temporary and unsecure, must be changed when frontends are refactored |
305 otrctx = self.context_managers[client.profile].getContextForUser(to_jid) | 294 otrctx = client._otr_context_manager.getContextForUser(to_jid) |
306 query = otrctx.sendMessage(0, '?OTRv?') | 295 query = otrctx.sendMessage(0, '?OTRv?') |
307 otrctx.inject(query) | 296 otrctx.inject(query) |
308 | 297 |
309 def _otrSessionEnd(self, menu_data, profile): | 298 def _otrSessionEnd(self, menu_data, profile): |
310 """End an OTR session | 299 """End an OTR session |
323 | 312 |
324 def endSession(self, client, to_jid): | 313 def endSession(self, client, to_jid): |
325 """End an OTR session""" | 314 """End an OTR session""" |
326 if not to_jid.resource: | 315 if not to_jid.resource: |
327 to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: temporary and unsecure, must be changed when frontends are refactored | 316 to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: temporary and unsecure, must be changed when frontends are refactored |
328 otrctx = self.context_managers[client.profile].getContextForUser(to_jid) | 317 otrctx = client._otr_context_manager.getContextForUser(to_jid) |
329 otrctx.disconnect() | 318 otrctx.disconnect() |
330 return {} | 319 return {} |
331 | 320 |
332 def _otrAuthenticate(self, menu_data, profile): | 321 def _otrAuthenticate(self, menu_data, profile): |
333 """End an OTR session | 322 """End an OTR session |
345 | 334 |
346 def authenticate(self, client, to_jid): | 335 def authenticate(self, client, to_jid): |
347 """Authenticate other user and see our own fingerprint""" | 336 """Authenticate other user and see our own fingerprint""" |
348 if not to_jid.resource: | 337 if not to_jid.resource: |
349 to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: temporary and unsecure, must be changed when frontends are refactored | 338 to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: temporary and unsecure, must be changed when frontends are refactored |
350 ctxMng = self.context_managers[client.profile] | 339 ctxMng = client._otr_context_manager |
351 otrctx = ctxMng.getContextForUser(to_jid) | 340 otrctx = ctxMng.getContextForUser(to_jid) |
352 priv_key = ctxMng.account.privkey | 341 priv_key = ctxMng.account.privkey |
353 | 342 |
354 if priv_key is None: | 343 if priv_key is None: |
355 # we have no private key yet | 344 # we have no private key yet |
377 | 366 |
378 def setTrust(raw_data, profile): | 367 def setTrust(raw_data, profile): |
379 # This method is called when authentication form is submited | 368 # This method is called when authentication form is submited |
380 data = xml_tools.XMLUIResult2DataFormResult(raw_data) | 369 data = xml_tools.XMLUIResult2DataFormResult(raw_data) |
381 if data['match'] == 'yes': | 370 if data['match'] == 'yes': |
382 otrctx.setCurrentTrust('verified') | 371 otrctx.setCurrentTrust(OTR_STATE_TRUSTED) |
383 note_msg = _(u"Your correspondent {correspondent} is now TRUSTED") | 372 note_msg = _(u"Your correspondent {correspondent} is now TRUSTED") |
373 self.host.bridge.otrState(OTR_STATE_TRUSTED, to_jid.full(), client.profile) | |
384 else: | 374 else: |
385 otrctx.setCurrentTrust('') | 375 otrctx.setCurrentTrust('') |
386 note_msg = _(u"Your correspondent {correspondent} is now UNTRUSTED") | 376 note_msg = _(u"Your correspondent {correspondent} is now UNTRUSTED") |
377 self.host.bridge.otrState(OTR_STATE_UNTRUSTED, to_jid.full(), client.profile) | |
387 note = xml_tools.XMLUI(C.XMLUI_DIALOG, dialog_opt = { | 378 note = xml_tools.XMLUI(C.XMLUI_DIALOG, dialog_opt = { |
388 C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE, | 379 C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE, |
389 C.XMLUI_DATA_MESS: note_msg.format(correspondent=otrctx.peer)} | 380 C.XMLUI_DATA_MESS: note_msg.format(correspondent=otrctx.peer)} |
390 ) | 381 ) |
391 return {'xmlui': note.toXml()} | 382 return {'xmlui': note.toXml()} |
417 to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: temporary and unsecure, must be changed when frontends are refactored | 408 to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: temporary and unsecure, must be changed when frontends are refactored |
418 except KeyError: | 409 except KeyError: |
419 log.error(_(u"jid key is not present !")) | 410 log.error(_(u"jid key is not present !")) |
420 return defer.fail(exceptions.DataError) | 411 return defer.fail(exceptions.DataError) |
421 | 412 |
422 ctxMng = self.context_managers[profile] | 413 ctxMng = client._otr_context_manager |
423 if ctxMng.account.privkey is None: | 414 if ctxMng.account.privkey is None: |
424 return {'xmlui': xml_tools.note(_(u"You don't have a private key yet !")).toXml()} | 415 return {'xmlui': xml_tools.note(_(u"You don't have a private key yet !")).toXml()} |
425 | 416 |
426 def dropKey(data, profile): | 417 def dropKey(data, profile): |
427 if C.bool(data['answer']): | 418 if C.bool(data['answer']): |
428 # we end all sessions | 419 # we end all sessions |
429 for context in ctxMng.contexts.values(): | 420 for context in ctxMng.contexts.values(): |
430 context.disconnect() | 421 context.disconnect() |
431 ctxMng.account.privkey = None | 422 ctxMng.account.privkey = None |
432 ctxMng.account.getPrivkey() # as account.privkey is None, getPrivkey will generate a new key, and save it | 423 ctxMng.account.getPrivkey() # as account.privkey is None, getPrivkey will generate a new key, and save it |
433 return {'xmlui': xml_tools.note(_(u"Your private key has been dropped")).toXml()} | 424 return {'xmlui': xml_tools.note(D_(u"Your private key has been dropped")).toXml()} |
434 return {} | 425 return {} |
435 | 426 |
436 submit_id = self.host.registerCallback(dropKey, with_data=True, one_shot=True) | 427 submit_id = self.host.registerCallback(dropKey, with_data=True, one_shot=True) |
437 | 428 |
438 confirm = xml_tools.XMLUI(C.XMLUI_DIALOG, title=_('Confirm private key drop'), dialog_opt = {'type': C.XMLUI_DIALOG_CONFIRM, 'message': _(DROP_TXT)}, submit_id = submit_id) | 429 confirm = xml_tools.XMLUI(C.XMLUI_DIALOG, title=_(u'Confirm private key drop'), dialog_opt = {'type': C.XMLUI_DIALOG_CONFIRM, 'message': _(DROP_TXT)}, submit_id = submit_id) |
439 return {'xmlui': confirm.toXml()} | 430 return {'xmlui': confirm.toXml()} |
440 | 431 |
441 def _receivedTreatment(self, data, profile): | 432 def _receivedTreatment(self, data, client): |
442 from_jid = data['from'] | 433 from_jid = data['from'] |
443 log.debug(u"_receivedTreatment [from_jid = %s]" % from_jid) | 434 log.debug(u"_receivedTreatment [from_jid = %s]" % from_jid) |
444 otrctx = self.context_managers[profile].getContextForUser(from_jid) | 435 otrctx = client._otr_context_manager.getContextForUser(from_jid) |
445 encrypted = True | |
446 | 436 |
447 try: | 437 try: |
448 message = data['message'].itervalues().next() # FIXME: Q&D fix for message refactoring, message is now a dict | 438 message = data['message'].itervalues().next() # FIXME: Q&D fix for message refactoring, message is now a dict |
449 res = otrctx.receiveMessage(message.encode('utf-8')) | 439 res = otrctx.receiveMessage(message.encode('utf-8')) |
450 except potr.context.UnencryptedMessage: | 440 except potr.context.UnencryptedMessage: |
441 encrypted = False | |
451 if otrctx.state == potr.context.STATE_ENCRYPTED: | 442 if otrctx.state == potr.context.STATE_ENCRYPTED: |
452 log.warning(u"Received unencrypted message in an encrypted context (from %(jid)s)" % {'jid': from_jid.full()}) | 443 log.warning(u"Received unencrypted message in an encrypted context (from {jid})".format( |
453 client = self.host.getClient(profile) | 444 jid = from_jid.full())) |
454 | 445 |
455 feedback=D_(u"WARNING: received unencrypted data in a supposedly encrypted context"), | 446 feedback=D_(u"WARNING: received unencrypted data in a supposedly encrypted context"), |
456 self.host.bridge.messageNew(uid=unicode(uuid.uuid4()), | 447 self.host.bridge.messageNew(uid=unicode(uuid.uuid4()), |
457 timestamp=time.time(), | 448 timestamp=time.time(), |
458 from_jid=from_jid.full(), | 449 from_jid=from_jid.full(), |
460 message={u'': feedback}, | 451 message={u'': feedback}, |
461 subject={}, | 452 subject={}, |
462 mess_type=C.MESS_TYPE_INFO, | 453 mess_type=C.MESS_TYPE_INFO, |
463 extra={}, | 454 extra={}, |
464 profile=client.profile) | 455 profile=client.profile) |
465 encrypted = False | |
466 except StopIteration: | 456 except StopIteration: |
467 return data | 457 return data |
468 | |
469 if not encrypted: | |
470 return data | |
471 else: | 458 else: |
459 encrypted = True | |
460 | |
461 if encrypted: | |
472 if res[0] != None: | 462 if res[0] != None: |
473 # decrypted messages handling. | 463 # decrypted messages handling. |
474 # receiveMessage() will return a tuple, the first part of which will be the decrypted message | 464 # receiveMessage() will return a tuple, the first part of which will be the decrypted message |
475 data['message'] = {'':res[0].decode('utf-8')} # FIXME: Q&D fix for message refactoring, message is now a dict | 465 data['message'] = {'':res[0].decode('utf-8')} # FIXME: Q&D fix for message refactoring, message is now a dict |
476 raise failure.Failure(exceptions.SkipHistory()) # we send the decrypted message to frontends, but we don't want it in history | 466 # TODO: add skip history as an option, but by default we don't skip it |
467 # raise failure.Failure(exceptions.SkipHistory()) # we send the decrypted message to frontends, but we don't want it in history | |
477 else: | 468 else: |
469 log.warning(u"An encrypted message was expected, but got {}".format(data['message'])) | |
478 raise failure.Failure(exceptions.CancelError('Cancelled by OTR')) # no message at all (no history, no signal) | 470 raise failure.Failure(exceptions.CancelError('Cancelled by OTR')) # no message at all (no history, no signal) |
479 | 471 return data |
480 def _receivedTreatmentForSkippedProfiles(self, data, profile): | 472 |
473 def _receivedTreatmentForSkippedProfiles(self, data): | |
481 """This profile must be skipped because the frontend manages OTR itself, | 474 """This profile must be skipped because the frontend manages OTR itself, |
482 but we still need to check if the message must be stored in history or not""" | 475 |
476 but we still need to check if the message must be stored in history or not | |
477 """ | |
478 # XXX: FIXME: this should not be done on a per-profile basis, but per-message | |
483 try: | 479 try: |
484 message = data['message'].itervalues().next().encode('utf-8') # FIXME: Q&D fix for message refactoring, message is now a dict | 480 message = data['message'].itervalues().next().encode('utf-8') # FIXME: Q&D fix for message refactoring, message is now a dict |
485 except StopIteration: | 481 except StopIteration: |
486 return data | 482 return data |
487 if message.startswith(potr.proto.OTRTAG): | 483 if message.startswith(potr.proto.OTRTAG): |
488 raise failure.Failure(exceptions.SkipHistory()) | 484 raise failure.Failure(exceptions.SkipHistory()) |
489 return data | 485 return data |
490 | 486 |
491 def MessageReceivedTrigger(self, client, message_elt, post_treat): | 487 def MessageReceivedTrigger(self, client, message_elt, post_treat): |
492 profile = client.profile | 488 if message_elt.getAttribute('type') == C.MESS_TYPE_GROUPCHAT: |
493 if profile in self.skipped_profiles: | 489 # OTR is not possible in group chats |
494 post_treat.addCallback(self._receivedTreatmentForSkippedProfiles, profile) | 490 return True |
491 if client.profile in self.skipped_profiles: | |
492 post_treat.addCallback(self._receivedTreatmentForSkippedProfiles) | |
495 else: | 493 else: |
496 post_treat.addCallback(self._receivedTreatment, profile) | 494 post_treat.addCallback(self._receivedTreatment, client) |
497 return True | 495 return True |
498 | 496 |
499 def messageSendTrigger(self, client, mess_data, pre_xml_treatments, post_xml_treatments): | 497 def _messageSendOTR(self, mess_data, client): |
500 profile = client.profile | |
501 if profile in self.skipped_profiles: | |
502 return True | |
503 to_jid = copy.copy(mess_data['to']) | 498 to_jid = copy.copy(mess_data['to']) |
504 if mess_data['type'] != 'groupchat' and not to_jid.resource: | 499 if not to_jid.resource: |
505 to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: it's dirty, but frontends don't manage resources correctly now, refactoring is planed | 500 to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: full jid may not be known |
506 otrctx = self.context_managers[profile].getContextForUser(to_jid) | 501 otrctx = client._otr_context_manager.getContextForUser(to_jid) |
507 if mess_data['type'] != 'groupchat' and otrctx.state != potr.context.STATE_PLAINTEXT: | 502 if otrctx.state != potr.context.STATE_PLAINTEXT: |
508 if otrctx.state == potr.context.STATE_ENCRYPTED: | 503 if otrctx.state == potr.context.STATE_ENCRYPTED: |
509 log.debug(u"encrypting message") | 504 log.debug(u"encrypting message") |
510 try: | 505 try: |
511 msg = mess_data['message'][''] | 506 msg = mess_data['message'][''] |
512 except KeyError: | 507 except KeyError: |
513 try: | 508 try: |
514 msg = mess_data['message'].itervalues().next() | 509 msg = mess_data['message'].itervalues().next() |
515 except StopIteration: | 510 except StopIteration: |
516 log.warning(u"No message found") | 511 log.warning(u"No message found") |
517 return False | 512 for key, value in mess_data['extra'].iteritems(): |
513 if key.startswith('rich') or key.startswith('xhtml'): | |
514 log.error(u'received rich content while OTR encryption is activated, cancelling') | |
515 raise failure.Failure(exceptions.CancelError()) | |
516 return mess_data | |
518 otrctx.sendMessage(0, msg.encode('utf-8')) | 517 otrctx.sendMessage(0, msg.encode('utf-8')) |
518 self.host.messageAddToHistory(mess_data, client) | |
519 self.host.messageSendToBridge(mess_data, client) | 519 self.host.messageSendToBridge(mess_data, client) |
520 else: | 520 else: |
521 feedback = D_(u"Your message was not sent because your correspondent closed the encrypted conversation on his/her side. Either close your own side, or refresh the session.") | 521 feedback = D_(u"Your message was not sent because your correspondent closed the encrypted conversation on his/her side. Either close your own side, or refresh the session.") |
522 self.host.bridge.messageNew(uid=unicode(uuid.uuid4()), | 522 self.host.bridge.messageNew(uid=unicode(uuid.uuid4()), |
523 timestamp=time.time(), | 523 timestamp=time.time(), |
526 message={u'': feedback}, | 526 message={u'': feedback}, |
527 subject={}, | 527 subject={}, |
528 mess_type=C.MESS_TYPE_INFO, | 528 mess_type=C.MESS_TYPE_INFO, |
529 extra={}, | 529 extra={}, |
530 profile=client.profile) | 530 profile=client.profile) |
531 return False | 531 # we stop treatment here for OTR, as message is already sent |
532 raise failure.Failure(exceptions.CancelError()) | |
532 else: | 533 else: |
533 log.debug(u"sending message unencrypted") | 534 return mess_data |
535 | |
536 def messageSendTrigger(self, client, mess_data, pre_xml_treatments, post_xml_treatments): | |
537 if mess_data['type'] == 'groupchat': | |
534 return True | 538 return True |
539 if client.profile in self.skipped_profiles: # FIXME: should not be done on a per-profile basis | |
540 return True | |
541 pre_xml_treatments.addCallback(self._messageSendOTR, client) | |
542 return True | |
535 | 543 |
536 def _presenceReceivedTrigger(self, entity, show, priority, statuses, profile): | 544 def _presenceReceivedTrigger(self, entity, show, priority, statuses, profile): |
537 client = self.host.getClient(profile) | |
538 if show != C.PRESENCE_UNAVAILABLE: | 545 if show != C.PRESENCE_UNAVAILABLE: |
539 return True | 546 return True |
547 client = self.host.getClient(profile) | |
540 if not entity.resource: | 548 if not entity.resource: |
541 try: | 549 try: |
542 entity.resource = self.host.memory.getMainResource(client, entity) # FIXME: temporary and unsecure, must be changed when frontends are refactored | 550 entity.resource = self.host.memory.getMainResource(client, entity) # FIXME: temporary and unsecure, must be changed when frontends are refactored |
543 except exceptions.UnknownEntityError: | 551 except exceptions.UnknownEntityError: |
544 return True # entity was not connected | 552 return True # entity was not connected |
545 if entity in self.context_managers[profile].contexts: | 553 if entity in client._otr_context_manager.contexts: |
546 otrctx = self.context_managers[profile].getContextForUser(entity) | 554 otrctx = client._otr_context_manager.getContextForUser(entity) |
547 otrctx.disconnect() | 555 otrctx.disconnect() |
548 return True | 556 return True |