[omemo] Use Gajims encryption API
This commit is contained in:
@@ -1,35 +1,40 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
|
||||||
# Copyright 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
|
'''
|
||||||
# Copyright 2015 Daniel Gultsch <daniel@cgultsch.de>
|
Copyright 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
|
||||||
#
|
Copyright 2015 Daniel Gultsch <daniel@cgultsch.de>
|
||||||
# This file is part of Gajim-OMEMO plugin.
|
Copyright 2016 Philipp Hörist <philipp@hoerist.com>
|
||||||
#
|
|
||||||
# The Gajim-OMEMO plugin is free software: you can redistribute it and/or modify
|
This file is part of Gajim-OMEMO plugin.
|
||||||
# it under the terms of the GNU General Public License as published by the Free
|
|
||||||
# Software Foundation, either version 3 of the License, or (at your option) any
|
The Gajim-OMEMO plugin is free software: you can redistribute it and/or modify
|
||||||
# later version.
|
it under the terms of the GNU General Public License as published by the Free
|
||||||
#
|
Software Foundation, either version 3 of the License, or (at your option) any
|
||||||
# Gajim-OMEMO is distributed in the hope that it will be useful, but WITHOUT ANY
|
later version.
|
||||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
||||||
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
Gajim-OMEMO is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
#
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
# You should have received a copy of the GNU General Public License along with
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
# the Gajim-OMEMO plugin. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
You should have received a copy of the GNU General Public License along with
|
||||||
|
the Gajim-OMEMO plugin. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
'''
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import shutil
|
import shutil
|
||||||
import message_control
|
import message_control
|
||||||
|
import nbxmpp
|
||||||
|
|
||||||
|
from nbxmpp.simplexml import Node
|
||||||
|
from nbxmpp import NS_CORRECT, NS_ADDRESS
|
||||||
|
|
||||||
|
import dialogs
|
||||||
from common import caps_cache, gajim, ged, configpaths
|
from common import caps_cache, gajim, ged, configpaths
|
||||||
from common.pep import SUPPORTED_PERSONAL_USER_EVENTS
|
from common.pep import SUPPORTED_PERSONAL_USER_EVENTS
|
||||||
from plugins import GajimPlugin
|
from plugins import GajimPlugin
|
||||||
from plugins.helpers import log_calls
|
from groupchat_control import GroupchatControl
|
||||||
from nbxmpp.simplexml import Node
|
|
||||||
from nbxmpp import NS_CORRECT, NS_ADDRESS
|
|
||||||
|
|
||||||
from .xmpp import (
|
from .xmpp import (
|
||||||
NS_NOTIFY, NS_OMEMO, NS_EME, BundleInformationAnnouncement,
|
NS_NOTIFY, NS_OMEMO, NS_EME, BundleInformationAnnouncement,
|
||||||
@@ -37,6 +42,9 @@ from .xmpp import (
|
|||||||
DevicelistPEP, OmemoMessage, successful, unpack_device_bundle,
|
DevicelistPEP, OmemoMessage, successful, unpack_device_bundle,
|
||||||
unpack_device_list_update, unpack_encrypted)
|
unpack_device_list_update, unpack_encrypted)
|
||||||
|
|
||||||
|
from common.connection_handlers_events import (
|
||||||
|
MessageReceivedEvent, MamMessageReceivedEvent)
|
||||||
|
|
||||||
|
|
||||||
IQ_CALLBACK = {}
|
IQ_CALLBACK = {}
|
||||||
|
|
||||||
@@ -49,10 +57,22 @@ GAJIM_VERSION = 'OMEMO only works with the latest Gajim version, get the ' \
|
|||||||
ERROR_MSG = ''
|
ERROR_MSG = ''
|
||||||
|
|
||||||
NS_HINTS = 'urn:xmpp:hints'
|
NS_HINTS = 'urn:xmpp:hints'
|
||||||
NS_PGP = 'urn:xmpp:openpgp:0'
|
|
||||||
DB_DIR_OLD = gajim.gajimpaths.data_root
|
DB_DIR_OLD = gajim.gajimpaths.data_root
|
||||||
DB_DIR_NEW = configpaths.gajimpaths['MY_DATA']
|
DB_DIR_NEW = configpaths.gajimpaths['MY_DATA']
|
||||||
|
|
||||||
|
ALLOWED_TAGS = [('request', nbxmpp.NS_RECEIPTS),
|
||||||
|
('active', nbxmpp.NS_CHATSTATES),
|
||||||
|
('gone', nbxmpp.NS_CHATSTATES),
|
||||||
|
('inactive', nbxmpp.NS_CHATSTATES),
|
||||||
|
('paused', nbxmpp.NS_CHATSTATES),
|
||||||
|
('composing', nbxmpp.NS_CHATSTATES),
|
||||||
|
('no-store', nbxmpp.NS_MSG_HINTS),
|
||||||
|
('store', nbxmpp.NS_MSG_HINTS),
|
||||||
|
('no-copy', nbxmpp.NS_MSG_HINTS),
|
||||||
|
('no-permanent-store', nbxmpp.NS_MSG_HINTS),
|
||||||
|
('replace', nbxmpp.NS_CORRECT),
|
||||||
|
('thread', None)]
|
||||||
|
|
||||||
log = logging.getLogger('gajim.plugin_system.omemo')
|
log = logging.getLogger('gajim.plugin_system.omemo')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -76,7 +96,7 @@ except Exception as e:
|
|||||||
if not ERROR_MSG:
|
if not ERROR_MSG:
|
||||||
try:
|
try:
|
||||||
from .omemo.state import OmemoState
|
from .omemo.state import OmemoState
|
||||||
from .ui import Ui, OMEMOConfigDialog
|
from .ui import OMEMOConfigDialog, FingerprintWindow
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error(e)
|
log.error(e)
|
||||||
ERROR_MSG = 'Error: ' + str(e)
|
ERROR_MSG = 'Error: ' + str(e)
|
||||||
@@ -88,11 +108,9 @@ if not ERROR_MSG:
|
|||||||
class OmemoPlugin(GajimPlugin):
|
class OmemoPlugin(GajimPlugin):
|
||||||
|
|
||||||
omemo_states = {}
|
omemo_states = {}
|
||||||
ui_list = {}
|
|
||||||
groupchat = {}
|
groupchat = {}
|
||||||
temp_groupchat = {}
|
temp_groupchat = {}
|
||||||
|
|
||||||
@log_calls('OmemoPlugin')
|
|
||||||
def init(self):
|
def init(self):
|
||||||
""" Init """
|
""" Init """
|
||||||
if ERROR_MSG:
|
if ERROR_MSG:
|
||||||
@@ -100,18 +118,12 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
self.available_text = ERROR_MSG
|
self.available_text = ERROR_MSG
|
||||||
self.config_dialog = None
|
self.config_dialog = None
|
||||||
return
|
return
|
||||||
|
self.encryption_name = 'OMEMO'
|
||||||
|
self.allow_groupchat = True
|
||||||
self.events_handlers = {
|
self.events_handlers = {
|
||||||
'mam-message-received': (ged.PRECORE, self.mam_message_received),
|
|
||||||
'message-received': (ged.PRECORE, self.message_received),
|
|
||||||
'pep-received': (ged.PRECORE, self.handle_device_list_update),
|
'pep-received': (ged.PRECORE, self.handle_device_list_update),
|
||||||
'raw-iq-received': (ged.PRECORE, self.handle_iq_received),
|
'raw-iq-received': (ged.PRECORE, self.handle_iq_received),
|
||||||
'signed-in': (ged.PRECORE, self.signed_in),
|
'signed-in': (ged.PRECORE, self.signed_in),
|
||||||
'stanza-message-outgoing':
|
|
||||||
(ged.PRECORE, self.handle_outgoing_stanza),
|
|
||||||
'message-outgoing':
|
|
||||||
(ged.PRECORE, self.handle_outgoing_event),
|
|
||||||
'gc-stanza-message-outgoing':
|
|
||||||
(ged.PRECORE, self.handle_outgoing_gc_stanza),
|
|
||||||
'gc-presence-received': (ged.PRECORE, self.gc_presence_received),
|
'gc-presence-received': (ged.PRECORE, self.gc_presence_received),
|
||||||
'gc-config-changed-received':
|
'gc-config-changed-received':
|
||||||
(ged.PRECORE, self.gc_config_changed_received),
|
(ged.PRECORE, self.gc_config_changed_received),
|
||||||
@@ -119,18 +131,25 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.config_dialog = OMEMOConfigDialog(self)
|
self.config_dialog = OMEMOConfigDialog(self)
|
||||||
self.gui_extension_points = {'chat_control': (self.connect_ui,
|
self.gui_extension_points = {
|
||||||
self.disconnect_ui),
|
'hyperlink_handler': (self.file_decryption, None),
|
||||||
'groupchat_control': (self.connect_ui,
|
'encrypt' + self.encryption_name: (self._encrypt_message, None),
|
||||||
self.disconnect_ui),
|
'gc_encrypt' + self.encryption_name: (self._gc_encrypt_message, None),
|
||||||
'hyperlink_handler': (self.file_decryption,
|
'decrypt': (self.message_received, None),
|
||||||
None)}
|
'send_message' + self.encryption_name: (
|
||||||
|
self.before_sendmessage, None),
|
||||||
|
'encryption_dialog' + self.encryption_name: (
|
||||||
|
self.on_encryption_button_clicked, None),
|
||||||
|
'encryption_state' + self.encryption_name: (
|
||||||
|
self.encryption_state, None)}
|
||||||
|
|
||||||
SUPPORTED_PERSONAL_USER_EVENTS.append(DevicelistPEP)
|
SUPPORTED_PERSONAL_USER_EVENTS.append(DevicelistPEP)
|
||||||
self.plugin = self
|
self.plugin = self
|
||||||
self.announced = []
|
self.announced = []
|
||||||
self.query_for_bundles = []
|
self.query_for_bundles = []
|
||||||
self.disabled_accounts = []
|
self.disabled_accounts = []
|
||||||
self.gc_message = {}
|
self.gc_message = {}
|
||||||
|
self.windowinstances = {}
|
||||||
|
|
||||||
self.config_default_values = {'DISABLED_ACCOUNTS': ([], ''), }
|
self.config_default_values = {'DISABLED_ACCOUNTS': ([], ''), }
|
||||||
|
|
||||||
@@ -158,7 +177,6 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
|
|
||||||
return new_dbpath
|
return new_dbpath
|
||||||
|
|
||||||
@log_calls('OmemoPlugin')
|
|
||||||
def get_omemo_state(self, account):
|
def get_omemo_state(self, account):
|
||||||
""" Returns the the OmemoState for the specified account.
|
""" Returns the the OmemoState for the specified account.
|
||||||
Creates the OmemoState if it does not exist yet.
|
Creates the OmemoState if it does not exist yet.
|
||||||
@@ -175,7 +193,6 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
if account in self.disabled_accounts:
|
if account in self.disabled_accounts:
|
||||||
return
|
return
|
||||||
if account not in self.omemo_states:
|
if account not in self.omemo_states:
|
||||||
self.deactivate_gajim_e2e(account)
|
|
||||||
my_jid = gajim.get_jid_from_account(account)
|
my_jid = gajim.get_jid_from_account(account)
|
||||||
db_path = self.migrate_dbpath(account, my_jid)
|
db_path = self.migrate_dbpath(account, my_jid)
|
||||||
|
|
||||||
@@ -185,19 +202,9 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
|
|
||||||
return self.omemo_states[account]
|
return self.omemo_states[account]
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def deactivate_gajim_e2e(account):
|
|
||||||
""" Deativates E2E encryption in Gajim """
|
|
||||||
gajim.config.set_per('accounts', account,
|
|
||||||
'autonegotiate_esessions', False)
|
|
||||||
gajim.config.set_per('accounts', account,
|
|
||||||
'enable_esessions', False)
|
|
||||||
log.info(str(account) + " => Gajim E2E encryption disabled")
|
|
||||||
|
|
||||||
def file_decryption(self, url, kind, instance, window):
|
def file_decryption(self, url, kind, instance, window):
|
||||||
FileDecryption(self).hyperlink_handler(url, kind, instance, window)
|
FileDecryption(self).hyperlink_handler(url, kind, instance, window)
|
||||||
|
|
||||||
@log_calls('OmemoPlugin')
|
|
||||||
def signed_in(self, event):
|
def signed_in(self, event):
|
||||||
""" Method called on SignIn
|
""" Method called on SignIn
|
||||||
|
|
||||||
@@ -216,7 +223,6 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
self.publish_bundle(account)
|
self.publish_bundle(account)
|
||||||
self.query_own_devicelist(account)
|
self.query_own_devicelist(account)
|
||||||
|
|
||||||
@log_calls('OmemoPlugin')
|
|
||||||
def activate(self):
|
def activate(self):
|
||||||
""" Method called when the Plugin is activated in the PluginManager
|
""" Method called when the Plugin is activated in the PluginManager
|
||||||
"""
|
"""
|
||||||
@@ -238,7 +244,6 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
self.publish_bundle(account)
|
self.publish_bundle(account)
|
||||||
self.query_own_devicelist(account)
|
self.query_own_devicelist(account)
|
||||||
|
|
||||||
@log_calls('OmemoPlugin')
|
|
||||||
def deactivate(self):
|
def deactivate(self):
|
||||||
""" Method called when the Plugin is deactivated in the PluginManager
|
""" Method called when the Plugin is deactivated in the PluginManager
|
||||||
|
|
||||||
@@ -251,6 +256,98 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
gajim.gajim_optional_features[account].remove(NS_NOTIFY)
|
gajim.gajim_optional_features[account].remove(NS_NOTIFY)
|
||||||
self._compute_caps_hash(account)
|
self._compute_caps_hash(account)
|
||||||
|
|
||||||
|
def activate_encryption(self, chat_control):
|
||||||
|
if isinstance(chat_control, GroupchatControl):
|
||||||
|
if chat_control.room_jid not in self.groupchat:
|
||||||
|
dialogs.ErrorDialog(
|
||||||
|
_('Bad Configuration'),
|
||||||
|
_('To use OMEMO in a Groupchat, the Groupchat should be'
|
||||||
|
' non-anonymous and members-only.'))
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def encryption_state(chat_control, state):
|
||||||
|
state['visible'] = True
|
||||||
|
state['authenticated'] = True
|
||||||
|
|
||||||
|
def on_encryption_button_clicked(self, chat_control):
|
||||||
|
self.show_fingerprint_window(chat_control)
|
||||||
|
|
||||||
|
def before_sendmessage(self, chat_control):
|
||||||
|
account = chat_control.account
|
||||||
|
contact = chat_control.contact
|
||||||
|
self.new_fingerprints_available(chat_control)
|
||||||
|
if isinstance(chat_control, GroupchatControl):
|
||||||
|
missing = True
|
||||||
|
own_jid = gajim.get_jid_from_account(account)
|
||||||
|
for nick in self.plugin.groupchat[self.room]:
|
||||||
|
real_jid = self.plugin.groupchat[self.room][nick]
|
||||||
|
if real_jid == own_jid:
|
||||||
|
continue
|
||||||
|
if not self.plugin.are_keys_missing(self.account,
|
||||||
|
real_jid):
|
||||||
|
missing = False
|
||||||
|
if missing:
|
||||||
|
log.debug(self.account +
|
||||||
|
' => No Trusted Fingerprints for ' +
|
||||||
|
self.room)
|
||||||
|
self.no_trusted_fingerprints_warning()
|
||||||
|
else:
|
||||||
|
if self.are_keys_missing(account, contact.jid):
|
||||||
|
log.debug(account + ' => No Trusted Fingerprints for ' +
|
||||||
|
contact.jid)
|
||||||
|
self.no_trusted_fingerprints_warning(chat_control)
|
||||||
|
chat_control.sendmessage = False
|
||||||
|
else:
|
||||||
|
log.debug(account + ' => Sending Message to ' +
|
||||||
|
contact.jid)
|
||||||
|
|
||||||
|
def new_fingerprints_available(self, chat_control):
|
||||||
|
jid = chat_control.contact.jid
|
||||||
|
account = chat_control.account
|
||||||
|
state = self.get_omemo_state(account)
|
||||||
|
if isinstance(chat_control, GroupchatControl):
|
||||||
|
room_jid = chat_control.room_jid
|
||||||
|
if room_jid in self.groupchat:
|
||||||
|
for nick in self.groupchat[room_jid]:
|
||||||
|
real_jid = self.groupchat[room_jid][nick]
|
||||||
|
fingerprints = state.store. \
|
||||||
|
getNewFingerprints(real_jid)
|
||||||
|
if fingerprints:
|
||||||
|
self.show_fingerprint_window(
|
||||||
|
chat_control, fingerprints)
|
||||||
|
elif not isinstance(chat_control, GroupchatControl):
|
||||||
|
fingerprints = state.store.getNewFingerprints(jid)
|
||||||
|
if fingerprints:
|
||||||
|
self.show_fingerprint_window(
|
||||||
|
chat_control, fingerprints)
|
||||||
|
|
||||||
|
def show_fingerprint_window(self, chat_control, fingerprints=None):
|
||||||
|
contact = chat_control.contact
|
||||||
|
account = chat_control.account
|
||||||
|
state = self.get_omemo_state(account)
|
||||||
|
transient = chat_control.parent_win.window
|
||||||
|
if 'dialog' not in self.windowinstances:
|
||||||
|
if isinstance(chat_control, GroupchatControl):
|
||||||
|
self.windowinstances['dialog'] = \
|
||||||
|
FingerprintWindow(self, contact, transient,
|
||||||
|
self.windowinstances, groupchat=True)
|
||||||
|
else:
|
||||||
|
self.windowinstances['dialog'] = \
|
||||||
|
FingerprintWindow(self, contact, transient,
|
||||||
|
self.windowinstances)
|
||||||
|
self.windowinstances['dialog'].show_all()
|
||||||
|
if fingerprints:
|
||||||
|
log.debug(account +
|
||||||
|
' => Showing Fingerprint Prompt for ' +
|
||||||
|
contact.jid)
|
||||||
|
state.store.setShownFingerprints(fingerprints)
|
||||||
|
else:
|
||||||
|
self.windowinstances['dialog'].update_context_list()
|
||||||
|
if fingerprints:
|
||||||
|
state.store.setShownFingerprints(fingerprints)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _compute_caps_hash(account):
|
def _compute_caps_hash(account):
|
||||||
""" Computes the hash for Entity Capabilities and publishes it """
|
""" Computes the hash for Entity Capabilities and publishes it """
|
||||||
@@ -264,8 +361,17 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
gajim.connections[account].change_status(
|
gajim.connections[account].change_status(
|
||||||
gajim.SHOW_LIST[connected], gajim.connections[account].status)
|
gajim.SHOW_LIST[connected], gajim.connections[account].status)
|
||||||
|
|
||||||
@log_calls('OmemoPlugin')
|
def message_received(self, conn, obj, callback):
|
||||||
def mam_message_received(self, msg):
|
if obj.encrypted:
|
||||||
|
return
|
||||||
|
if isinstance(obj, MessageReceivedEvent):
|
||||||
|
self._message_received(obj)
|
||||||
|
elif isinstance(obj, MamMessageReceivedEvent):
|
||||||
|
self._mam_message_received(obj)
|
||||||
|
if obj.encrypted == 'OMEMO':
|
||||||
|
callback(obj)
|
||||||
|
|
||||||
|
def _mam_message_received(self, msg):
|
||||||
""" Handles an incoming MAM message
|
""" Handles an incoming MAM message
|
||||||
|
|
||||||
Payload is decrypted and the plaintext is written into the
|
Payload is decrypted and the plaintext is written into the
|
||||||
@@ -283,9 +389,6 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
if account in self.disabled_accounts:
|
if account in self.disabled_accounts:
|
||||||
return
|
return
|
||||||
|
|
||||||
if msg.msg_.getTag('openpgp', namespace=NS_PGP):
|
|
||||||
return
|
|
||||||
|
|
||||||
omemo_encrypted_tag = msg.msg_.getTag('encrypted', namespace=NS_OMEMO)
|
omemo_encrypted_tag = msg.msg_.getTag('encrypted', namespace=NS_OMEMO)
|
||||||
if omemo_encrypted_tag:
|
if omemo_encrypted_tag:
|
||||||
log.debug(account + ' => OMEMO MAM msg received')
|
log.debug(account + ' => OMEMO MAM msg received')
|
||||||
@@ -302,31 +405,28 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
plaintext = state.decrypt_msg(msg_dict)
|
plaintext = state.decrypt_msg(msg_dict)
|
||||||
|
|
||||||
if not plaintext:
|
if not plaintext:
|
||||||
|
msg.encrypted = 'drop'
|
||||||
return
|
return
|
||||||
|
|
||||||
self.print_msg_to_log(msg.msg_)
|
self.print_msg_to_log(msg.msg_)
|
||||||
|
|
||||||
msg.msgtxt = plaintext
|
msg.msgtxt = plaintext
|
||||||
|
msg.encrypted = 'OMEMO'
|
||||||
contact_jid = msg.with_
|
return
|
||||||
|
|
||||||
if account in self.ui_list and \
|
|
||||||
contact_jid in self.ui_list[account]:
|
|
||||||
self.ui_list[account][contact_jid].activate_omemo()
|
|
||||||
return False
|
|
||||||
|
|
||||||
elif msg.msg_.getTag('body'):
|
elif msg.msg_.getTag('body'):
|
||||||
account = msg.conn.name
|
account = msg.conn.name
|
||||||
|
|
||||||
jid = msg.with_
|
from_jid = str(msg.msg_.getAttr('from'))
|
||||||
state = self.get_omemo_state(account)
|
from_jid = gajim.get_jid_without_resource(from_jid)
|
||||||
omemo_enabled = state.encryption.is_active(jid)
|
|
||||||
|
|
||||||
if omemo_enabled:
|
state = self.get_omemo_state(account)
|
||||||
|
encryption = gajim.config.get_per('contacts', from_jid, 'encryption')
|
||||||
|
|
||||||
|
if encryption == 'OMEMO':
|
||||||
msg.msgtxt = '**Unencrypted** ' + msg.msgtxt
|
msg.msgtxt = '**Unencrypted** ' + msg.msgtxt
|
||||||
|
|
||||||
@log_calls('OmemoPlugin')
|
def _message_received(self, msg):
|
||||||
def message_received(self, msg):
|
|
||||||
""" Handles an incoming message
|
""" Handles an incoming message
|
||||||
|
|
||||||
Payload is decrypted and the plaintext is written into the
|
Payload is decrypted and the plaintext is written into the
|
||||||
@@ -344,9 +444,6 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
if account in self.disabled_accounts:
|
if account in self.disabled_accounts:
|
||||||
return
|
return
|
||||||
|
|
||||||
if msg.stanza.getTag('openpgp', namespace=NS_PGP):
|
|
||||||
return
|
|
||||||
|
|
||||||
if msg.stanza.getTag('encrypted', namespace=NS_OMEMO):
|
if msg.stanza.getTag('encrypted', namespace=NS_OMEMO):
|
||||||
log.debug(account + ' => OMEMO msg received')
|
log.debug(account + ' => OMEMO msg received')
|
||||||
|
|
||||||
@@ -379,7 +476,8 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
log.error(account +
|
log.error(account +
|
||||||
' => Cant decrypt GroupChat Message '
|
' => Cant decrypt GroupChat Message '
|
||||||
'from ' + msg.resource)
|
'from ' + msg.resource)
|
||||||
return True
|
msg.encrypted = 'drop'
|
||||||
|
return
|
||||||
self.groupchat[msg.jid][msg.resource] = from_jid
|
self.groupchat[msg.jid][msg.resource] = from_jid
|
||||||
|
|
||||||
log.debug('GroupChat Message from: %s', from_jid)
|
log.debug('GroupChat Message from: %s', from_jid)
|
||||||
@@ -392,25 +490,21 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
else:
|
else:
|
||||||
log.error(account + ' => Cant decrypt own GroupChat '
|
log.error(account + ' => Cant decrypt own GroupChat '
|
||||||
'Message')
|
'Message')
|
||||||
|
msg.encrypted = 'drop'
|
||||||
else:
|
else:
|
||||||
msg_dict['sender_jid'] = gajim. \
|
msg_dict['sender_jid'] = gajim. \
|
||||||
get_jid_without_resource(from_jid)
|
get_jid_without_resource(from_jid)
|
||||||
plaintext = state.decrypt_msg(msg_dict)
|
plaintext = state.decrypt_msg(msg_dict)
|
||||||
|
|
||||||
if not plaintext:
|
if not plaintext:
|
||||||
return True
|
msg.encrypted = 'drop'
|
||||||
|
return
|
||||||
|
|
||||||
msg.msgtxt = plaintext
|
msg.msgtxt = plaintext
|
||||||
# Gajim bug: there must be a body or the message
|
# Gajim bug: there must be a body or the message
|
||||||
# gets dropped from history
|
# gets dropped from history
|
||||||
msg.stanza.setBody(plaintext)
|
msg.stanza.setBody(plaintext)
|
||||||
|
msg.encrypted = 'OMEMO'
|
||||||
if msg.mtype != 'groupchat':
|
|
||||||
contact_jid = gajim.get_jid_without_resource(from_jid)
|
|
||||||
if account in self.ui_list and \
|
|
||||||
contact_jid in self.ui_list[account]:
|
|
||||||
self.ui_list[account][contact_jid].activate_omemo()
|
|
||||||
return False
|
|
||||||
|
|
||||||
elif msg.stanza.getTag('body'):
|
elif msg.stanza.getTag('body'):
|
||||||
account = msg.conn.name
|
account = msg.conn.name
|
||||||
@@ -418,19 +512,15 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
from_jid = str(msg.stanza.getFrom())
|
from_jid = str(msg.stanza.getFrom())
|
||||||
jid = gajim.get_jid_without_resource(from_jid)
|
jid = gajim.get_jid_without_resource(from_jid)
|
||||||
state = self.get_omemo_state(account)
|
state = self.get_omemo_state(account)
|
||||||
omemo_enabled = state.encryption.is_active(jid)
|
encryption = gajim.config.get_per('contacts', jid, 'encryption')
|
||||||
|
|
||||||
if omemo_enabled:
|
if encryption == 'OMEMO':
|
||||||
msg.msgtxt = '**Unencrypted** ' + msg.msgtxt
|
msg.msgtxt = '**Unencrypted** ' + msg.msgtxt
|
||||||
# msg.stanza.setBody(msg.msgtxt)
|
msg.stanza.setBody(msg.msgtxt)
|
||||||
|
|
||||||
try:
|
ctrl = gajim.interface.msg_win_mgr.get_control(jid, account)
|
||||||
gui = self.ui_list[account].get(jid, None)
|
if ctrl:
|
||||||
if gui and gui.encryption_active():
|
self.plain_warning(ctrl)
|
||||||
gui.plain_warning()
|
|
||||||
except KeyError:
|
|
||||||
log.debug('No Ui present for ' + jid +
|
|
||||||
', Ui Warning not shown')
|
|
||||||
|
|
||||||
def room_memberlist_received(self, event):
|
def room_memberlist_received(self, event):
|
||||||
account = event.conn.name
|
account = event.conn.name
|
||||||
@@ -440,6 +530,9 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
event.fjid, event.users_dict)
|
event.fjid, event.users_dict)
|
||||||
room = event.fjid
|
room = event.fjid
|
||||||
|
|
||||||
|
if room not in self.groupchat:
|
||||||
|
self.groupchat[room] = {}
|
||||||
|
|
||||||
def jid_known(jid):
|
def jid_known(jid):
|
||||||
for nick in self.groupchat[room]:
|
for nick in self.groupchat[room]:
|
||||||
if self.groupchat[room][nick] == jid:
|
if self.groupchat[room][nick] == jid:
|
||||||
@@ -452,7 +545,6 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
self.groupchat[room][jid] = jid
|
self.groupchat[room][jid] = jid
|
||||||
log.debug('JID Added: ' + jid)
|
log.debug('JID Added: ' + jid)
|
||||||
|
|
||||||
@log_calls('OmemoPlugin')
|
|
||||||
def gc_presence_received(self, event):
|
def gc_presence_received(self, event):
|
||||||
account = event.conn.name
|
account = event.conn.name
|
||||||
if account in self.disabled_accounts:
|
if account in self.disabled_accounts:
|
||||||
@@ -508,18 +600,19 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
gajim.connections[account].get_affiliation_list(room, 'admin')
|
gajim.connections[account].get_affiliation_list(room, 'admin')
|
||||||
gajim.connections[account].get_affiliation_list(room, 'member')
|
gajim.connections[account].get_affiliation_list(room, 'member')
|
||||||
|
|
||||||
self.ui_list[account][room].sensitive(True)
|
|
||||||
|
|
||||||
@log_calls('OmemoPlugin')
|
|
||||||
def gc_config_changed_received(self, event):
|
def gc_config_changed_received(self, event):
|
||||||
account = event.conn.name
|
account = event.conn.name
|
||||||
|
room = event.room_jid
|
||||||
if account in self.disabled_accounts:
|
if account in self.disabled_accounts:
|
||||||
return
|
return
|
||||||
|
if '172' in event.status_code:
|
||||||
|
if room not in self.groupchat:
|
||||||
|
self.groupchat[room] = self.temp_groupchat[room]
|
||||||
log.debug('CONFIG CHANGE')
|
log.debug('CONFIG CHANGE')
|
||||||
log.debug(event.room_jid)
|
log.debug(event.room_jid)
|
||||||
log.debug(event.status_code)
|
log.debug(event.status_code)
|
||||||
|
|
||||||
def handle_outgoing_gc_stanza(self, event):
|
def _gc_encrypt_message(self, conn, event, callback):
|
||||||
""" Manipulates the outgoing groupchat stanza
|
""" Manipulates the outgoing groupchat stanza
|
||||||
|
|
||||||
The body is getting encrypted
|
The body is getting encrypted
|
||||||
@@ -546,10 +639,7 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
state = self.get_omemo_state(account)
|
state = self.get_omemo_state(account)
|
||||||
full_jid = str(event.msg_iq.getAttr('to'))
|
full_jid = str(event.msg_iq.getAttr('to'))
|
||||||
to_jid = gajim.get_jid_without_resource(full_jid)
|
to_jid = gajim.get_jid_without_resource(full_jid)
|
||||||
if to_jid not in self.groupchat:
|
|
||||||
return
|
|
||||||
if not state.encryption.is_active(to_jid):
|
|
||||||
return
|
|
||||||
# Delete previous Message out of Correction Message Stanza
|
# Delete previous Message out of Correction Message Stanza
|
||||||
if event.msg_iq.getTag('replace', namespace=NS_CORRECT):
|
if event.msg_iq.getTag('replace', namespace=NS_CORRECT):
|
||||||
event.msg_iq.delChild('encrypted', attrs={'xmlns': NS_OMEMO})
|
event.msg_iq.delChild('encrypted', attrs={'xmlns': NS_OMEMO})
|
||||||
@@ -562,9 +652,11 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
if not msg_dict:
|
if not msg_dict:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
self.cleanup_stanza(event)
|
||||||
|
|
||||||
self.gc_message[msg_dict['payload']] = plaintext
|
self.gc_message[msg_dict['payload']] = plaintext
|
||||||
encrypted_node = OmemoMessage(msg_dict)
|
encrypted_node = OmemoMessage(msg_dict)
|
||||||
event.msg_iq.delChild('body')
|
|
||||||
event.msg_iq.addChild(node=encrypted_node)
|
event.msg_iq.addChild(node=encrypted_node)
|
||||||
|
|
||||||
# XEP-0380: Explicit Message Encryption
|
# XEP-0380: Explicit Message Encryption
|
||||||
@@ -588,40 +680,12 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
self.print_msg_to_log(event.correction_msg)
|
self.print_msg_to_log(event.correction_msg)
|
||||||
else:
|
else:
|
||||||
self.print_msg_to_log(event.msg_iq)
|
self.print_msg_to_log(event.msg_iq)
|
||||||
|
callback(event)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.debug(e)
|
log.debug(e)
|
||||||
return True
|
|
||||||
|
|
||||||
@log_calls('OmemoPlugin')
|
|
||||||
def handle_outgoing_event(self, event):
|
|
||||||
""" Handles a message outgoing event
|
|
||||||
|
|
||||||
In this event we have no stanza. XHTML is set to None
|
|
||||||
so that it doesnt make its way into the stanza
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
event : MessageOutgoingEvent
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
Return if encryption is not activated
|
|
||||||
"""
|
|
||||||
if event.type_ == 'normal':
|
|
||||||
return False
|
|
||||||
|
|
||||||
account = event.account
|
|
||||||
if account in self.disabled_accounts:
|
|
||||||
return
|
return
|
||||||
state = self.get_omemo_state(account)
|
|
||||||
|
|
||||||
if not state.encryption.is_active(event.jid):
|
def _encrypt_message(self, conn, event, callback):
|
||||||
return False
|
|
||||||
|
|
||||||
event.xhtml = None
|
|
||||||
|
|
||||||
@log_calls('OmemoPlugin')
|
|
||||||
def handle_outgoing_stanza(self, event):
|
|
||||||
""" Manipulates the outgoing stanza
|
""" Manipulates the outgoing stanza
|
||||||
|
|
||||||
The body is getting encrypted
|
The body is getting encrypted
|
||||||
@@ -645,8 +709,6 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
state = self.get_omemo_state(account)
|
state = self.get_omemo_state(account)
|
||||||
full_jid = str(event.msg_iq.getAttr('to'))
|
full_jid = str(event.msg_iq.getAttr('to'))
|
||||||
to_jid = gajim.get_jid_without_resource(full_jid)
|
to_jid = gajim.get_jid_without_resource(full_jid)
|
||||||
if not state.encryption.is_active(to_jid):
|
|
||||||
return
|
|
||||||
|
|
||||||
# Delete previous Message out of Correction Message Stanza
|
# Delete previous Message out of Correction Message Stanza
|
||||||
if event.msg_iq.getTag('replace', namespace=NS_CORRECT):
|
if event.msg_iq.getTag('replace', namespace=NS_CORRECT):
|
||||||
@@ -660,23 +722,7 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
encrypted_node = OmemoMessage(msg_dict)
|
encrypted_node = OmemoMessage(msg_dict)
|
||||||
|
self.cleanup_stanza(event)
|
||||||
# Check if non-OMEMO resource is online
|
|
||||||
contacts = gajim.contacts.get_contacts(account, to_jid)
|
|
||||||
non_omemo_resource_online = False
|
|
||||||
for contact in contacts:
|
|
||||||
if contact.show == 'offline':
|
|
||||||
continue
|
|
||||||
if not contact.supports(NS_NOTIFY):
|
|
||||||
log.debug(contact.get_full_jid() +
|
|
||||||
' => Contact doesnt support OMEMO, '
|
|
||||||
'adding Info Message to Body')
|
|
||||||
support_msg = 'You received a message encrypted with ' \
|
|
||||||
'OMEMO but your client doesnt support OMEMO.'
|
|
||||||
event.msg_iq.setBody(support_msg)
|
|
||||||
non_omemo_resource_online = True
|
|
||||||
if not non_omemo_resource_online:
|
|
||||||
event.msg_iq.delChild('body')
|
|
||||||
|
|
||||||
event.msg_iq.addChild(node=encrypted_node)
|
event.msg_iq.addChild(node=encrypted_node)
|
||||||
|
|
||||||
@@ -691,11 +737,25 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
store = Node('store', attrs={'xmlns': NS_HINTS})
|
store = Node('store', attrs={'xmlns': NS_HINTS})
|
||||||
event.msg_iq.addChild(node=store)
|
event.msg_iq.addChild(node=store)
|
||||||
self.print_msg_to_log(event.msg_iq)
|
self.print_msg_to_log(event.msg_iq)
|
||||||
|
event.xhtml = None
|
||||||
|
|
||||||
|
callback(event)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.debug(e)
|
log.debug(e)
|
||||||
return True
|
|
||||||
|
|
||||||
@log_calls('OmemoPlugin')
|
@staticmethod
|
||||||
|
def cleanup_stanza(obj):
|
||||||
|
''' We make sure only allowed tags are in the stanza '''
|
||||||
|
stanza = nbxmpp.Message(
|
||||||
|
to=obj.msg_iq.getTo(),
|
||||||
|
typ=obj.msg_iq.getType())
|
||||||
|
stanza.setThread(obj.msg_iq.getThread())
|
||||||
|
for tag, ns in ALLOWED_TAGS:
|
||||||
|
node = obj.msg_iq.getTag(tag, namespace=ns)
|
||||||
|
if node:
|
||||||
|
stanza.addChild(node=node)
|
||||||
|
obj.msg_iq = stanza
|
||||||
|
|
||||||
def handle_device_list_update(self, event):
|
def handle_device_list_update(self, event):
|
||||||
""" Check if the passed event is a device list update and store the new
|
""" Check if the passed event is a device list update and store the new
|
||||||
device ids.
|
device ids.
|
||||||
@@ -762,29 +822,10 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
self.query_for_bundles.remove(contact_jid)
|
self.query_for_bundles.remove(contact_jid)
|
||||||
|
|
||||||
# Enable Encryption on receiving first Device List
|
# Enable Encryption on receiving first Device List
|
||||||
if not state.encryption.exist(contact_jid):
|
# TODO
|
||||||
if account in self.ui_list and \
|
|
||||||
contact_jid in self.ui_list[account]:
|
|
||||||
log.debug(account +
|
|
||||||
' => Switch encryption ON automatically ...')
|
|
||||||
self.ui_list[account][contact_jid].activate_omemo()
|
|
||||||
else:
|
|
||||||
log.debug(account +
|
|
||||||
' => Switch encryption ON automatically ...')
|
|
||||||
self.omemo_enable_for(contact_jid, account)
|
|
||||||
|
|
||||||
if account in self.ui_list and \
|
|
||||||
contact_jid not in self.ui_list[account]:
|
|
||||||
|
|
||||||
chat_control = gajim.interface.msg_win_mgr.get_control(
|
|
||||||
contact_jid, account)
|
|
||||||
|
|
||||||
if chat_control:
|
|
||||||
self.connect_ui(chat_control)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@log_calls('OmemoPlugin')
|
|
||||||
def publish_own_devices_list(self, account, new=False):
|
def publish_own_devices_list(self, account, new=False):
|
||||||
""" Get all currently known own active device ids and publish them
|
""" Get all currently known own active device ids and publish them
|
||||||
|
|
||||||
@@ -813,58 +854,6 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
id_ = str(iq.getAttr('id'))
|
id_ = str(iq.getAttr('id'))
|
||||||
IQ_CALLBACK[id_] = lambda event: log.debug(event)
|
IQ_CALLBACK[id_] = lambda event: log.debug(event)
|
||||||
|
|
||||||
@log_calls('OmemoPlugin')
|
|
||||||
def connect_ui(self, chat_control):
|
|
||||||
""" Method called from Gajim when a Chat Window is opened
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
chat_control : ChatControl
|
|
||||||
Gajim ChatControl object
|
|
||||||
"""
|
|
||||||
account = chat_control.contact.account.name
|
|
||||||
if account in self.disabled_accounts:
|
|
||||||
return
|
|
||||||
contact_jid = chat_control.contact.jid
|
|
||||||
if account not in self.ui_list:
|
|
||||||
self.ui_list[account] = {}
|
|
||||||
state = self.get_omemo_state(account)
|
|
||||||
my_jid = gajim.get_jid_from_account(account)
|
|
||||||
omemo_enabled = state.encryption.is_active(contact_jid)
|
|
||||||
if omemo_enabled:
|
|
||||||
log.debug(account + " => Adding OMEMO ui for " + contact_jid)
|
|
||||||
self.ui_list[account][contact_jid] = Ui(self, chat_control,
|
|
||||||
omemo_enabled, state)
|
|
||||||
self.ui_list[account][contact_jid].new_fingerprints_available()
|
|
||||||
return
|
|
||||||
if contact_jid in state.device_ids or contact_jid == my_jid:
|
|
||||||
log.debug(account + " => Adding OMEMO ui for " + contact_jid)
|
|
||||||
self.ui_list[account][contact_jid] = Ui(self, chat_control,
|
|
||||||
omemo_enabled, state)
|
|
||||||
self.ui_list[account][contact_jid].new_fingerprints_available()
|
|
||||||
else:
|
|
||||||
log.warning(account + " => No devices for " + contact_jid)
|
|
||||||
|
|
||||||
if chat_control.type_id == message_control.TYPE_GC:
|
|
||||||
self.ui_list[account][contact_jid] = Ui(self, chat_control,
|
|
||||||
omemo_enabled, state)
|
|
||||||
self.ui_list[account][contact_jid].sensitive(False)
|
|
||||||
|
|
||||||
@log_calls('OmemoPlugin')
|
|
||||||
def disconnect_ui(self, chat_control):
|
|
||||||
""" Calls the removeUi method to remove all relatad UI objects.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
chat_control : ChatControl
|
|
||||||
Gajim ChatControl object
|
|
||||||
"""
|
|
||||||
contact_jid = chat_control.contact.jid
|
|
||||||
account = chat_control.contact.account.name
|
|
||||||
if account in self.disabled_accounts:
|
|
||||||
return
|
|
||||||
self.ui_list[account][contact_jid].removeUi()
|
|
||||||
|
|
||||||
def are_keys_missing(self, account, contact_jid):
|
def are_keys_missing(self, account, contact_jid):
|
||||||
""" Checks if devicekeys are missing and querys the
|
""" Checks if devicekeys are missing and querys the
|
||||||
bundles
|
bundles
|
||||||
@@ -932,7 +921,6 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
finally:
|
finally:
|
||||||
del IQ_CALLBACK[id_]
|
del IQ_CALLBACK[id_]
|
||||||
|
|
||||||
@log_calls('OmemoPlugin')
|
|
||||||
def fetch_device_bundle_information(self, account, jid, device_id):
|
def fetch_device_bundle_information(self, account, jid, device_id):
|
||||||
""" Fetch bundle information for specified jid, key, and create axolotl
|
""" Fetch bundle information for specified jid, key, and create axolotl
|
||||||
session on success.
|
session on success.
|
||||||
@@ -956,7 +944,6 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
device_id)
|
device_id)
|
||||||
gajim.connections[account].connection.send(iq)
|
gajim.connections[account].connection.send(iq)
|
||||||
|
|
||||||
@log_calls('OmemoPlugin')
|
|
||||||
def session_from_prekey_bundle(self, account, stanza,
|
def session_from_prekey_bundle(self, account, stanza,
|
||||||
recipient_id, device_id):
|
recipient_id, device_id):
|
||||||
""" Starts a session from a PreKey bundle.
|
""" Starts a session from a PreKey bundle.
|
||||||
@@ -995,12 +982,11 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
log.info(account + ' => session created for: ' + recipient_id)
|
log.info(account + ' => session created for: ' + recipient_id)
|
||||||
# Trigger dialog to trust new Fingerprints if
|
# Trigger dialog to trust new Fingerprints if
|
||||||
# the Chat Window is Open
|
# the Chat Window is Open
|
||||||
if account in self.ui_list and \
|
ctrl = gajim.interface.msg_win_mgr.get_control(
|
||||||
recipient_id in self.ui_list[account]:
|
recipient_id, account)
|
||||||
self.ui_list[account][recipient_id]. \
|
if ctrl:
|
||||||
new_fingerprints_available()
|
self.new_fingerprints_available(ctrl)
|
||||||
|
|
||||||
@log_calls('OmemoPlugin')
|
|
||||||
def query_own_devicelist(self, account):
|
def query_own_devicelist(self, account):
|
||||||
""" Query own devicelist from the server.
|
""" Query own devicelist from the server.
|
||||||
|
|
||||||
@@ -1017,7 +1003,6 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
IQ_CALLBACK[id_] = lambda stanza: \
|
IQ_CALLBACK[id_] = lambda stanza: \
|
||||||
self.handle_devicelist_result(account, stanza)
|
self.handle_devicelist_result(account, stanza)
|
||||||
|
|
||||||
@log_calls('OmemoPlugin')
|
|
||||||
def publish_bundle(self, account):
|
def publish_bundle(self, account):
|
||||||
""" Publish our bundle information to the PEP node.
|
""" Publish our bundle information to the PEP node.
|
||||||
|
|
||||||
@@ -1055,7 +1040,6 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
else:
|
else:
|
||||||
log.error(account + ' => Publishing bundle was NOT successful')
|
log.error(account + ' => Publishing bundle was NOT successful')
|
||||||
|
|
||||||
@log_calls('OmemoPlugin')
|
|
||||||
def handle_devicelist_result(self, account, stanza):
|
def handle_devicelist_result(self, account, stanza):
|
||||||
""" If query was successful add own device to the list.
|
""" If query was successful add own device to the list.
|
||||||
|
|
||||||
@@ -1094,7 +1078,6 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
log.error(account + ' => Devicelistquery was NOT successful')
|
log.error(account + ' => Devicelistquery was NOT successful')
|
||||||
self.publish_own_devices_list(account, new=True)
|
self.publish_own_devices_list(account, new=True)
|
||||||
|
|
||||||
@log_calls('OmemoPlugin')
|
|
||||||
def clear_device_list(self, account):
|
def clear_device_list(self, account):
|
||||||
""" Clears the local devicelist of our own devices and publishes
|
""" Clears the local devicelist of our own devices and publishes
|
||||||
a new one including only the current ID of this device
|
a new one including only the current ID of this device
|
||||||
@@ -1126,7 +1109,18 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
log.debug(stanzastr)
|
log.debug(stanzastr)
|
||||||
log.debug('-'*15)
|
log.debug('-'*15)
|
||||||
|
|
||||||
@log_calls('OmemoPlugin')
|
@staticmethod
|
||||||
|
def plain_warning(chat_control):
|
||||||
|
chat_control.print_conversation_line(
|
||||||
|
'Received plaintext message! ' +
|
||||||
|
'Your next message will still be encrypted!', 'status', '', None)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def no_trusted_fingerprints_warning(chat_control):
|
||||||
|
msg = "To send an encrypted message, you have to " \
|
||||||
|
"first trust the fingerprint of your contact!"
|
||||||
|
chat_control.print_conversation_line(msg, 'status', '', None)
|
||||||
|
|
||||||
def omemo_enable_for(self, jid, account):
|
def omemo_enable_for(self, jid, account):
|
||||||
""" Used by the UI to enable OMEMO for a specified contact.
|
""" Used by the UI to enable OMEMO for a specified contact.
|
||||||
|
|
||||||
@@ -1144,7 +1138,6 @@ class OmemoPlugin(GajimPlugin):
|
|||||||
state = self.get_omemo_state(account)
|
state = self.get_omemo_state(account)
|
||||||
state.encryption.activate(jid)
|
state.encryption.activate(jid)
|
||||||
|
|
||||||
@log_calls('OmemoPlugin')
|
|
||||||
def omemo_disable_for(self, jid, account):
|
def omemo_disable_for(self, jid, account):
|
||||||
""" Used by the UI to disable OMEMO for a specified contact.
|
""" Used by the UI to disable OMEMO for a specified contact.
|
||||||
|
|
||||||
|
|||||||
349
omemo/ui.py
349
omemo/ui.py
@@ -1,37 +1,32 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
|
||||||
# Copyright 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
|
'''
|
||||||
# Copyright 2015 Daniel Gultsch <daniel@cgultsch.de>
|
Copyright 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
|
||||||
#
|
Copyright 2015 Daniel Gultsch <daniel@cgultsch.de>
|
||||||
# This file is part of Gajim-OMEMO plugin.
|
Copyright 2016 Philipp Hörist <philipp@hoerist.com>
|
||||||
#
|
|
||||||
# The Gajim-OMEMO plugin is free software: you can redistribute it and/or modify
|
This file is part of Gajim-OMEMO plugin.
|
||||||
# it under the terms of the GNU General Public License as published by the Free
|
|
||||||
# Software Foundation, either version 3 of the License, or (at your option) any
|
The Gajim-OMEMO plugin is free software: you can redistribute it and/or modify
|
||||||
# later version.
|
it under the terms of the GNU General Public License as published by the Free
|
||||||
#
|
Software Foundation, either version 3 of the License, or (at your option) any
|
||||||
# Gajim-OMEMO is distributed in the hope that it will be useful, but WITHOUT ANY
|
later version.
|
||||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
||||||
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
Gajim-OMEMO is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
#
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
# You should have received a copy of the GNU General Public License along with
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
# the Gajim-OMEMO plugin. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
You should have received a copy of the GNU General Public License along with
|
||||||
|
the Gajim-OMEMO plugin. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
'''
|
||||||
|
|
||||||
import binascii
|
import binascii
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import message_control
|
from enum import IntEnum, unique
|
||||||
|
|
||||||
from gi.repository import GObject, Gtk, GdkPixbuf, Gdk
|
from gi.repository import Gtk, GdkPixbuf, Gdk
|
||||||
|
|
||||||
# pylint: disable=import-error
|
|
||||||
import gtkgui_helpers
|
|
||||||
from common import gajim
|
|
||||||
from dialogs import YesNoDialog
|
|
||||||
from plugins.gui import GajimPluginConfigDialog
|
|
||||||
from axolotl.state.sessionrecord import SessionRecord
|
from axolotl.state.sessionrecord import SessionRecord
|
||||||
from common import configpaths
|
|
||||||
|
|
||||||
log = logging.getLogger('gajim.plugin_system.omemo')
|
log = logging.getLogger('gajim.plugin_system.omemo')
|
||||||
|
|
||||||
@@ -43,285 +38,17 @@ except Exception as e:
|
|||||||
log.exception('Error:')
|
log.exception('Error:')
|
||||||
log.error('python-qrcode or dependencies of it, are not available')
|
log.error('python-qrcode or dependencies of it, are not available')
|
||||||
|
|
||||||
# pylint: enable=import-error
|
from common import gajim
|
||||||
UNDECIDED = 2
|
from common import configpaths
|
||||||
TRUSTED = 1
|
from dialogs import YesNoDialog
|
||||||
UNTRUSTED = 0
|
from plugins.gui import GajimPluginConfigDialog
|
||||||
|
|
||||||
|
|
||||||
class OmemoButton(Gtk.Button):
|
@unique
|
||||||
def __init__(self, plugin, chat_control, ui, enabled):
|
class State(IntEnum):
|
||||||
super(OmemoButton, self).__init__(label=None, stock=None)
|
UNTRUSTED = 0
|
||||||
self.chat_control = chat_control
|
TRUSTED = 1
|
||||||
|
UNDECIDED = 2
|
||||||
self.set_property('relief', Gtk.ReliefStyle.NONE)
|
|
||||||
self.set_property('can-focus', False)
|
|
||||||
self.set_sensitive(True)
|
|
||||||
|
|
||||||
icon = Gtk.Image.new_from_file(
|
|
||||||
plugin.local_file_path('omemo16x16.png'))
|
|
||||||
self.set_image(icon)
|
|
||||||
self.set_tooltip_text('OMEMO Encryption')
|
|
||||||
|
|
||||||
self.connect('clicked', self.on_click)
|
|
||||||
|
|
||||||
self.menu = OmemoMenu(ui, enabled)
|
|
||||||
|
|
||||||
def on_click(self, widget):
|
|
||||||
"""
|
|
||||||
Popup omemo menu
|
|
||||||
"""
|
|
||||||
gtkgui_helpers.popup_emoticons_under_button(
|
|
||||||
self.menu, widget, self.chat_control.parent_win)
|
|
||||||
|
|
||||||
def set_omemo_state(self, state):
|
|
||||||
self.menu.set_omemo_state(state)
|
|
||||||
|
|
||||||
|
|
||||||
class OmemoMenu(Gtk.Menu):
|
|
||||||
def __init__(self, ui, enabled):
|
|
||||||
super(OmemoMenu, self).__init__()
|
|
||||||
self.ui = ui
|
|
||||||
|
|
||||||
self.item_omemo_state = Gtk.CheckMenuItem('Activate OMEMO')
|
|
||||||
self.item_omemo_state.set_active(enabled)
|
|
||||||
self.item_omemo_state.connect('activate', self.on_toggle_omemo)
|
|
||||||
self.append(self.item_omemo_state)
|
|
||||||
|
|
||||||
item = Gtk.ImageMenuItem('Fingerprints')
|
|
||||||
icon = Gtk.Image.new_from_stock(Gtk.STOCK_DIALOG_AUTHENTICATION,
|
|
||||||
Gtk.IconSize.MENU)
|
|
||||||
item.set_image(icon)
|
|
||||||
item.connect('activate', self.on_open_fingerprint_window)
|
|
||||||
self.append(item)
|
|
||||||
|
|
||||||
self.show_all()
|
|
||||||
|
|
||||||
def on_toggle_omemo(self, widget):
|
|
||||||
self.ui.set_omemo_state(widget.get_active())
|
|
||||||
|
|
||||||
def on_open_fingerprint_window(self, widget):
|
|
||||||
self.ui.show_fingerprint_window()
|
|
||||||
|
|
||||||
def set_omemo_state(self, state):
|
|
||||||
self.item_omemo_state.handler_block_by_func(self.on_toggle_omemo)
|
|
||||||
self.item_omemo_state.set_active(state)
|
|
||||||
self.item_omemo_state.handler_unblock_by_func(self.on_toggle_omemo)
|
|
||||||
|
|
||||||
|
|
||||||
class Ui(object):
|
|
||||||
def __init__(self, plugin, chat_control, enabled, state):
|
|
||||||
self.contact = chat_control.contact
|
|
||||||
self.chat_control = chat_control
|
|
||||||
self.plugin = plugin
|
|
||||||
self.state = state
|
|
||||||
self.account = self.contact.account.name
|
|
||||||
self.windowinstances = {}
|
|
||||||
|
|
||||||
self.groupchat = False
|
|
||||||
if chat_control.type_id == message_control.TYPE_GC:
|
|
||||||
self.groupchat = True
|
|
||||||
self.omemo_capable = False
|
|
||||||
self.room = self.chat_control.room_jid
|
|
||||||
|
|
||||||
self.display_omemo_state()
|
|
||||||
self.refresh_auth_lock_icon()
|
|
||||||
|
|
||||||
self.omemobutton = OmemoButton(plugin, chat_control, self, enabled)
|
|
||||||
|
|
||||||
self.actions_hbox = chat_control.xml.get_object('actions_hbox')
|
|
||||||
send_button = chat_control.xml.get_object('send_button')
|
|
||||||
send_button_pos = self.actions_hbox.child_get_property(send_button,
|
|
||||||
'position')
|
|
||||||
self.actions_hbox.add(self.omemobutton)
|
|
||||||
self.actions_hbox.reorder_child(self.omemobutton, send_button_pos - 1)
|
|
||||||
self.omemobutton.show_all()
|
|
||||||
|
|
||||||
# add a OMEMO entry to the context/advanced menu
|
|
||||||
self.chat_control.omemo_orig_prepare_context_menu = \
|
|
||||||
self.chat_control.prepare_context_menu
|
|
||||||
|
|
||||||
def omemo_prepare_context_menu(hide_buttonbar_items=False):
|
|
||||||
menu = self.chat_control. \
|
|
||||||
omemo_orig_prepare_context_menu(hide_buttonbar_items)
|
|
||||||
submenu = OmemoMenu(self, self.encryption_active())
|
|
||||||
|
|
||||||
item = Gtk.ImageMenuItem('OMEMO Encryption')
|
|
||||||
icon_path = plugin.local_file_path('omemo16x16.png')
|
|
||||||
item.set_image(Gtk.Image.new_from_file(icon_path))
|
|
||||||
item.set_submenu(submenu)
|
|
||||||
|
|
||||||
if self.groupchat:
|
|
||||||
item.set_sensitive(self.omemo_capable)
|
|
||||||
|
|
||||||
# at index 8 is the separator after the esession encryption entry
|
|
||||||
menu.insert(item, 8)
|
|
||||||
return menu
|
|
||||||
self.chat_control.prepare_context_menu = omemo_prepare_context_menu
|
|
||||||
|
|
||||||
# Hook into Send Button so we can check Stuff before sending
|
|
||||||
self.chat_control.orig_send_message = \
|
|
||||||
self.chat_control.send_message
|
|
||||||
|
|
||||||
def omemo_send_message(message, keyID='', chatstate=None, xhtml=None,
|
|
||||||
process_commands=True, attention=False):
|
|
||||||
self.new_fingerprints_available()
|
|
||||||
if self.encryption_active() and \
|
|
||||||
self.plugin.are_keys_missing(self.account,
|
|
||||||
self.contact.jid):
|
|
||||||
|
|
||||||
log.debug(self.account + ' => No Trusted Fingerprints for ' +
|
|
||||||
self.contact.jid)
|
|
||||||
self.no_trusted_fingerprints_warning()
|
|
||||||
else:
|
|
||||||
self.chat_control.orig_send_message(message, keyID, chatstate,
|
|
||||||
xhtml, process_commands,
|
|
||||||
attention)
|
|
||||||
log.debug(self.account + ' => Sending Message to ' +
|
|
||||||
self.contact.jid)
|
|
||||||
|
|
||||||
def omemo_send_gc_message(message, xhtml=None, process_commands=True):
|
|
||||||
self.new_fingerprints_available()
|
|
||||||
if self.encryption_active():
|
|
||||||
missing = True
|
|
||||||
own_jid = gajim.get_jid_from_account(self.account)
|
|
||||||
for nick in self.plugin.groupchat[self.room]:
|
|
||||||
real_jid = self.plugin.groupchat[self.room][nick]
|
|
||||||
if real_jid == own_jid:
|
|
||||||
continue
|
|
||||||
if not self.plugin.are_keys_missing(self.account,
|
|
||||||
real_jid):
|
|
||||||
missing = False
|
|
||||||
if missing:
|
|
||||||
log.debug(self.account +
|
|
||||||
' => No Trusted Fingerprints for ' +
|
|
||||||
self.room)
|
|
||||||
self.no_trusted_fingerprints_warning()
|
|
||||||
else:
|
|
||||||
self.chat_control.orig_send_message(message, xhtml,
|
|
||||||
process_commands)
|
|
||||||
log.debug(self.account + ' => Sending Message to ' +
|
|
||||||
self.room)
|
|
||||||
else:
|
|
||||||
self.chat_control.orig_send_message(message, xhtml,
|
|
||||||
process_commands)
|
|
||||||
log.debug(self.account + ' => Sending Message to ' +
|
|
||||||
self.room)
|
|
||||||
|
|
||||||
if self.groupchat:
|
|
||||||
self.chat_control.send_message = omemo_send_gc_message
|
|
||||||
else:
|
|
||||||
self.chat_control.send_message = omemo_send_message
|
|
||||||
|
|
||||||
def set_omemo_state(self, enabled):
|
|
||||||
"""
|
|
||||||
Enable or disable OMEMO for this window's contact and update the
|
|
||||||
window ui accordingly
|
|
||||||
"""
|
|
||||||
if enabled:
|
|
||||||
log.debug(self.contact.account.name + ' => Enable OMEMO for ' +
|
|
||||||
self.contact.jid)
|
|
||||||
self.plugin.omemo_enable_for(self.contact.jid,
|
|
||||||
self.contact.account.name)
|
|
||||||
self.refresh_auth_lock_icon()
|
|
||||||
else:
|
|
||||||
log.debug(self.contact.account.name + ' => Disable OMEMO for ' +
|
|
||||||
self.contact.jid)
|
|
||||||
self.plugin.omemo_disable_for(self.contact.jid,
|
|
||||||
self.contact.account.name)
|
|
||||||
self.refresh_auth_lock_icon()
|
|
||||||
|
|
||||||
self.omemobutton.set_omemo_state(enabled)
|
|
||||||
self.display_omemo_state()
|
|
||||||
|
|
||||||
def sensitive(self, value):
|
|
||||||
self.omemobutton.set_sensitive(value)
|
|
||||||
self.omemo_capable = value
|
|
||||||
if value:
|
|
||||||
self.chat_control.prepare_context_menu
|
|
||||||
|
|
||||||
def encryption_active(self):
|
|
||||||
return self.state.encryption.is_active(self.contact.jid)
|
|
||||||
|
|
||||||
def activate_omemo(self):
|
|
||||||
if not self.encryption_active():
|
|
||||||
self.set_omemo_state(True)
|
|
||||||
|
|
||||||
def new_fingerprints_available(self):
|
|
||||||
jid = self.contact.jid
|
|
||||||
if self.groupchat and self.room in self.plugin.groupchat:
|
|
||||||
for nick in self.plugin.groupchat[self.room]:
|
|
||||||
real_jid = self.plugin.groupchat[self.room][nick]
|
|
||||||
fingerprints = self.state.store. \
|
|
||||||
getNewFingerprints(real_jid)
|
|
||||||
if fingerprints:
|
|
||||||
self.show_fingerprint_window(fingerprints)
|
|
||||||
elif not self.groupchat:
|
|
||||||
fingerprints = self.state.store.getNewFingerprints(jid)
|
|
||||||
if fingerprints:
|
|
||||||
self.show_fingerprint_window(fingerprints)
|
|
||||||
|
|
||||||
def show_fingerprint_window(self, fingerprints=None):
|
|
||||||
if 'dialog' not in self.windowinstances:
|
|
||||||
if self.groupchat:
|
|
||||||
self.windowinstances['dialog'] = \
|
|
||||||
FingerprintWindow(self.plugin, self.contact,
|
|
||||||
self.chat_control.parent_win.window,
|
|
||||||
self.windowinstances, groupchat=True)
|
|
||||||
else:
|
|
||||||
self.windowinstances['dialog'] = \
|
|
||||||
FingerprintWindow(self.plugin, self.contact,
|
|
||||||
self.chat_control.parent_win.window,
|
|
||||||
self.windowinstances)
|
|
||||||
self.windowinstances['dialog'].show_all()
|
|
||||||
if fingerprints:
|
|
||||||
log.debug(self.account +
|
|
||||||
' => Showing Fingerprint Prompt for ' +
|
|
||||||
self.contact.jid)
|
|
||||||
self.state.store.setShownFingerprints(fingerprints)
|
|
||||||
else:
|
|
||||||
self.windowinstances['dialog'].update_context_list()
|
|
||||||
if fingerprints:
|
|
||||||
self.state.store.setShownFingerprints(fingerprints)
|
|
||||||
|
|
||||||
def plain_warning(self):
|
|
||||||
self.chat_control.print_conversation_line(
|
|
||||||
'Received plaintext message! ' +
|
|
||||||
'Your next message will still be encrypted!', 'status', '', None)
|
|
||||||
|
|
||||||
def display_omemo_state(self):
|
|
||||||
if self.encryption_active():
|
|
||||||
msg = u'OMEMO encryption enabled'
|
|
||||||
else:
|
|
||||||
msg = u'OMEMO encryption disabled'
|
|
||||||
self.chat_control.print_conversation_line(msg, 'status', '', None)
|
|
||||||
|
|
||||||
def no_trusted_fingerprints_warning(self):
|
|
||||||
msg = "To send an encrypted message, you have to " \
|
|
||||||
"first trust the fingerprint of your contact!"
|
|
||||||
self.chat_control.print_conversation_line(msg, 'status', '', None)
|
|
||||||
|
|
||||||
def refresh_auth_lock_icon(self):
|
|
||||||
if self.groupchat:
|
|
||||||
return
|
|
||||||
if self.encryption_active():
|
|
||||||
if self.state.getUndecidedFingerprints(self.contact.jid):
|
|
||||||
self.chat_control._show_lock_image(True, 'OMEMO', True, True,
|
|
||||||
False)
|
|
||||||
else:
|
|
||||||
self.chat_control._show_lock_image(True, 'OMEMO', True, True,
|
|
||||||
True)
|
|
||||||
else:
|
|
||||||
self.chat_control._show_lock_image(False, 'OMEMO', False, True,
|
|
||||||
False)
|
|
||||||
|
|
||||||
def removeUi(self):
|
|
||||||
self.actions_hbox.remove(self.omemobutton)
|
|
||||||
self.chat_control._show_lock_image(False, 'OMEMO', False, True,
|
|
||||||
False)
|
|
||||||
self.chat_control.prepare_context_menu = \
|
|
||||||
self.chat_control.omemo_orig_prepare_context_menu
|
|
||||||
self.chat_control.send_message = self.chat_control.orig_send_message
|
|
||||||
|
|
||||||
|
|
||||||
class OMEMOConfigDialog(GajimPluginConfigDialog):
|
class OMEMOConfigDialog(GajimPluginConfigDialog):
|
||||||
@@ -480,7 +207,7 @@ class OMEMOConfigDialog(GajimPluginConfigDialog):
|
|||||||
mod, paths = self.fpr_view.get_selection().get_selected_rows()
|
mod, paths = self.fpr_view.get_selection().get_selected_rows()
|
||||||
|
|
||||||
def on_yes(checked, identity_key):
|
def on_yes(checked, identity_key):
|
||||||
state.store.setTrust(identity_key, TRUSTED)
|
state.store.setTrust(identity_key, State.TRUSTED)
|
||||||
try:
|
try:
|
||||||
if self.plugin.ui_list[account]:
|
if self.plugin.ui_list[account]:
|
||||||
self.plugin.ui_list[account][jid]. \
|
self.plugin.ui_list[account][jid]. \
|
||||||
@@ -490,7 +217,7 @@ class OMEMOConfigDialog(GajimPluginConfigDialog):
|
|||||||
self.update_context_list()
|
self.update_context_list()
|
||||||
|
|
||||||
def on_no(identity_key):
|
def on_no(identity_key):
|
||||||
state.store.setTrust(identity_key, UNTRUSTED)
|
state.store.setTrust(identity_key, State.UNTRUSTED)
|
||||||
try:
|
try:
|
||||||
if jid in self.plugin.ui_list[account]:
|
if jid in self.plugin.ui_list[account]:
|
||||||
self.plugin.ui_list[account][jid]. \
|
self.plugin.ui_list[account][jid]. \
|
||||||
@@ -700,17 +427,11 @@ class FingerprintWindow(Gtk.Dialog):
|
|||||||
mod, paths = self.fpr_view.get_selection().get_selected_rows()
|
mod, paths = self.fpr_view.get_selection().get_selected_rows()
|
||||||
|
|
||||||
def on_yes(checked, identity_key):
|
def on_yes(checked, identity_key):
|
||||||
state.store.setTrust(identity_key, TRUSTED)
|
state.store.setTrust(identity_key, State.TRUSTED)
|
||||||
if not self.groupchat:
|
|
||||||
self.plugin.ui_list[self.account][self.contact.jid]. \
|
|
||||||
refresh_auth_lock_icon()
|
|
||||||
self.update_context_list()
|
self.update_context_list()
|
||||||
|
|
||||||
def on_no(identity_key):
|
def on_no(identity_key):
|
||||||
state.store.setTrust(identity_key, UNTRUSTED)
|
state.store.setTrust(identity_key, State.UNTRUSTED)
|
||||||
if not self.groupchat:
|
|
||||||
self.plugin.ui_list[self.account][self.contact.jid]. \
|
|
||||||
refresh_auth_lock_icon()
|
|
||||||
self.update_context_list()
|
self.update_context_list()
|
||||||
|
|
||||||
for path in paths:
|
for path in paths:
|
||||||
|
|||||||
Reference in New Issue
Block a user