parent
56fbe32b11
commit
32b74b459e
|
@ -15,7 +15,6 @@
|
|||
|
||||
- python3-keyring for saving your password to your system keyring
|
||||
- python3-pil (pillow) for support of webp avatars
|
||||
- python3-crypto to enable End to end encryption
|
||||
- python3-gnupg to enable GPG encryption
|
||||
- For zeroconf (bonjour) you need python3-dbus
|
||||
- gir1.2-gspell-1 and hunspell-LANG where lang is your locale eg. en, fr etc
|
||||
|
|
|
@ -42,7 +42,6 @@ from gajim.common import app
|
|||
from gajim.common import helpers
|
||||
from gajim.common import ged
|
||||
from gajim.common import i18n
|
||||
from gajim.common.stanza_session import EncryptedStanzaSession, ArchivingStanzaSession
|
||||
from gajim.common.contacts import GC_Contact
|
||||
from nbxmpp.protocol import NS_XHTML, NS_XHTML_IM, NS_FILE, NS_MUC
|
||||
from nbxmpp.protocol import NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO
|
||||
|
@ -207,9 +206,6 @@ class ChatControl(ChatControlBase):
|
|||
session.control = self
|
||||
self.session = session
|
||||
|
||||
if session.enable_encryption:
|
||||
self.print_esession_details()
|
||||
|
||||
# Enable encryption if needed
|
||||
self.no_autonegotiation = False
|
||||
self.add_actions()
|
||||
|
@ -231,8 +227,6 @@ class ChatControl(ChatControlBase):
|
|||
# Dont connect this when PrivateChatControl is used
|
||||
app.ged.register_event_handler('update-roster-avatar', ged.GUI1,
|
||||
self._nec_update_avatar)
|
||||
app.ged.register_event_handler('failed-decrypt', ged.GUI1,
|
||||
self._nec_failed_decrypt)
|
||||
app.ged.register_event_handler('chatstate-received', ged.GUI1,
|
||||
self._nec_chatstate_received)
|
||||
app.ged.register_event_handler('caps-received', ged.GUI1,
|
||||
|
@ -920,53 +914,6 @@ class ChatControl(ChatControlBase):
|
|||
chatstate=chatstate_to_send, xhtml=xhtml,
|
||||
process_commands=process_commands, attention=attention)
|
||||
|
||||
def on_cancel_session_negotiation(self):
|
||||
msg = _('Session negotiation cancelled')
|
||||
ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
|
||||
|
||||
def print_archiving_session_details(self):
|
||||
"""
|
||||
Print esession settings to textview
|
||||
"""
|
||||
archiving = bool(self.session) and isinstance(self.session,
|
||||
ArchivingStanzaSession) and self.session.archiving
|
||||
if archiving:
|
||||
msg = _('This session WILL be archived on server')
|
||||
else:
|
||||
msg = _('This session WILL NOT be archived on server')
|
||||
ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
|
||||
|
||||
def print_esession_details(self):
|
||||
"""
|
||||
Print esession settings to textview
|
||||
"""
|
||||
e2e_is_active = bool(self.session) and self.session.enable_encryption
|
||||
if e2e_is_active:
|
||||
msg = _('This session is encrypted')
|
||||
|
||||
if self.session.is_loggable():
|
||||
msg += _(' and WILL be logged')
|
||||
else:
|
||||
msg += _(' and WILL NOT be logged')
|
||||
|
||||
ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
|
||||
|
||||
if not self.session.verified_identity:
|
||||
ChatControlBase.print_conversation_line(self, _("Remote contact's identity not verified. Click the shield button for more details."), 'status', '', None)
|
||||
else:
|
||||
msg = _('end-to-end encryption disabled')
|
||||
ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
|
||||
|
||||
self._show_lock_image(e2e_is_active, 'E2E',
|
||||
self.session and self.session.verified_identity)
|
||||
|
||||
def print_session_details(self, old_session=None):
|
||||
if isinstance(self.session, EncryptedStanzaSession) or \
|
||||
(old_session and isinstance(old_session, EncryptedStanzaSession)):
|
||||
self.print_esession_details()
|
||||
elif isinstance(self.session, ArchivingStanzaSession):
|
||||
self.print_archiving_session_details()
|
||||
|
||||
def get_our_nick(self):
|
||||
return app.nicks[self.account]
|
||||
|
||||
|
@ -1186,8 +1133,6 @@ class ChatControl(ChatControlBase):
|
|||
if self.TYPE_ID == message_control.TYPE_CHAT:
|
||||
app.ged.remove_event_handler('update-roster-avatar', ged.GUI1,
|
||||
self._nec_update_avatar)
|
||||
app.ged.remove_event_handler('failed-decrypt', ged.GUI1,
|
||||
self._nec_failed_decrypt)
|
||||
app.ged.remove_event_handler('chatstate-received', ged.GUI1,
|
||||
self._nec_chatstate_received)
|
||||
app.ged.remove_event_handler('caps-received', ged.GUI1,
|
||||
|
@ -1482,57 +1427,6 @@ class ChatControl(ChatControlBase):
|
|||
"""
|
||||
dialogs.TransformChatToMUC(self.account, [self.contact.jid])
|
||||
|
||||
def activate_esessions(self):
|
||||
if not (self.session and self.session.enable_encryption):
|
||||
self.begin_e2e_negotiation()
|
||||
|
||||
def terminate_esessions(self):
|
||||
if not (self.session and self.session.enable_encryption):
|
||||
return
|
||||
# e2e was enabled, disable it
|
||||
jid = str(self.session.jid)
|
||||
thread_id = self.session.thread_id
|
||||
|
||||
self.session.terminate_e2e()
|
||||
|
||||
app.connections[self.account].delete_session(jid, thread_id)
|
||||
|
||||
# presumably the user had a good reason to shut it off, so
|
||||
# disable autonegotiation too
|
||||
self.no_autonegotiation = True
|
||||
|
||||
def begin_negotiation(self):
|
||||
self.no_autonegotiation = True
|
||||
|
||||
if not self.session:
|
||||
fjid = self.contact.get_full_jid()
|
||||
new_sess = app.connections[self.account].make_new_session(fjid, type_=self.type_id)
|
||||
self.set_session(new_sess)
|
||||
|
||||
def begin_e2e_negotiation(self):
|
||||
self.begin_negotiation()
|
||||
self.session.resource = self.contact.resource
|
||||
self.session.negotiate_e2e(False)
|
||||
|
||||
def _nec_failed_decrypt(self, obj):
|
||||
if obj.session != self.session:
|
||||
return
|
||||
|
||||
details = _('Unable to decrypt message from %s\nIt may have been '
|
||||
'tampered with.') % obj.fjid
|
||||
self.print_conversation_line(details, 'status', '', obj.timestamp)
|
||||
|
||||
# terminate the session
|
||||
thread_id = self.session.thread_id
|
||||
self.session.terminate_e2e()
|
||||
obj.conn.delete_session(obj.fjid, thread_id)
|
||||
|
||||
# restart the session
|
||||
self.begin_e2e_negotiation()
|
||||
|
||||
# Stop emission so it doesn't go to gui_interface
|
||||
return True
|
||||
|
||||
def got_connected(self):
|
||||
ChatControlBase.got_connected(self)
|
||||
# Refreshing contact
|
||||
|
|
|
@ -451,9 +451,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
|
|||
plugin = app.plugin_manager.encryption_plugins[encryption]
|
||||
if not plugin.activate_encryption(self):
|
||||
return
|
||||
else:
|
||||
if not self.widget_name == 'groupchat_control':
|
||||
self.terminate_esessions()
|
||||
|
||||
action.set_state(param)
|
||||
self.set_encryption_state(encryption)
|
||||
self.set_encryption_menu_icon()
|
||||
|
|
|
@ -147,7 +147,6 @@ caps_hash = {}
|
|||
_dependencies = {
|
||||
'PYTHON-DBUS': False,
|
||||
'PYBONJOUR': False,
|
||||
'PYCRYPTO': False,
|
||||
'PYGPG': False,
|
||||
'GPG_BINARY': False,
|
||||
'FARSTREAM': False,
|
||||
|
@ -188,13 +187,6 @@ def detect_dependencies():
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
# PYCRYPTO
|
||||
try:
|
||||
import Crypto
|
||||
_dependencies['PYCRYPTO'] = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# python-gnupg
|
||||
try:
|
||||
import gnupg
|
||||
|
|
|
@ -338,8 +338,6 @@ class Config:
|
|||
'keyid': [ opt_str, '', '', True ],
|
||||
'gpg_sign_presence': [ opt_bool, True, _('If disabled, don\'t sign presences with GPG key, even if GPG is configured.') ],
|
||||
'keyname': [ opt_str, '', '', True ],
|
||||
'enable_esessions': [opt_bool, True, _('Enable ESessions encryption for this account.'), True],
|
||||
'autonegotiate_esessions': [opt_bool, False, _('Should Gajim automatically start an encrypted session when possible?')],
|
||||
'allow_plaintext_connection': [ opt_bool, False, _('Allow plaintext connections')],
|
||||
'tls_version': [ opt_str, '1.2', '' ],
|
||||
'cipher_list': [ opt_str, 'HIGH:!aNULL:RC4-SHA', '' ],
|
||||
|
|
|
@ -340,10 +340,7 @@ class CommonConnection:
|
|||
# <body> tag is the active event
|
||||
if obj.chatstate and contact and contact.supports(nbxmpp.NS_CHATSTATES):
|
||||
msg_iq.setTag(obj.chatstate, namespace=nbxmpp.NS_CHATSTATES)
|
||||
only_chatste = False
|
||||
if not obj.message:
|
||||
only_chatste = True
|
||||
if only_chatste and not obj.session.enable_encryption:
|
||||
msg_iq.setTag('no-store',
|
||||
namespace=nbxmpp.NS_MSG_HINTS)
|
||||
|
||||
|
|
|
@ -830,8 +830,6 @@ class ConnectionHandlersBase:
|
|||
self._nec_iq_error_received)
|
||||
app.ged.register_event_handler('presence-received', ged.CORE,
|
||||
self._nec_presence_received)
|
||||
app.ged.register_event_handler('gc-presence-received', ged.CORE,
|
||||
self._nec_gc_presence_received)
|
||||
app.ged.register_event_handler('message-received', ged.CORE,
|
||||
self._nec_message_received)
|
||||
app.ged.register_event_handler('mam-message-received', ged.CORE,
|
||||
|
@ -848,8 +846,6 @@ class ConnectionHandlersBase:
|
|||
self._nec_iq_error_received)
|
||||
app.ged.remove_event_handler('presence-received', ged.CORE,
|
||||
self._nec_presence_received)
|
||||
app.ged.remove_event_handler('gc-presence-received', ged.CORE,
|
||||
self._nec_gc_presence_received)
|
||||
app.ged.remove_event_handler('message-received', ged.CORE,
|
||||
self._nec_message_received)
|
||||
app.ged.remove_event_handler('mam-message-received', ged.CORE,
|
||||
|
@ -975,24 +971,6 @@ class ConnectionHandlersBase:
|
|||
# resource signs off!
|
||||
self.stop_all_active_file_transfers(obj.contact)
|
||||
|
||||
# disable encryption, since if any messages are
|
||||
# lost they'll be not decryptable (note that
|
||||
# this contradicts XEP-0201 - trying to get that
|
||||
# in the XEP, though)
|
||||
|
||||
# there won't be any sessions here if the contact terminated
|
||||
# their sessions before going offline (which we do)
|
||||
for sess in self.get_sessions(jid):
|
||||
sess_fjid = sess.jid.getStripped()
|
||||
if sess.resource:
|
||||
sess_fjid += '/' + sess.resource
|
||||
if obj.fjid != sess_fjid:
|
||||
continue
|
||||
if sess.control:
|
||||
sess.control.no_autonegotiation = False
|
||||
if sess.enable_encryption:
|
||||
sess.terminate_e2e()
|
||||
|
||||
if app.config.get('log_contact_status_changes') and \
|
||||
app.config.should_log(self.name, obj.jid):
|
||||
show = app.logger.convert_show_values_to_db_api_values(obj.show)
|
||||
|
@ -1004,15 +982,6 @@ class ConnectionHandlersBase:
|
|||
message=obj.status,
|
||||
show=show)
|
||||
|
||||
def _nec_gc_presence_received(self, obj):
|
||||
if obj.conn.name != self.name:
|
||||
return
|
||||
for sess in self.get_sessions(obj.fjid):
|
||||
if obj.fjid != sess.jid:
|
||||
continue
|
||||
if sess.enable_encryption:
|
||||
sess.terminate_e2e()
|
||||
|
||||
def _nec_message_received(self, obj):
|
||||
if obj.conn.name != self.name:
|
||||
return
|
||||
|
@ -1210,14 +1179,7 @@ class ConnectionHandlersBase:
|
|||
except KeyError:
|
||||
return None
|
||||
|
||||
def terminate_sessions(self, send_termination=False):
|
||||
"""
|
||||
Send termination messages and delete all active sessions
|
||||
"""
|
||||
for jid in self.sessions:
|
||||
for thread_id in self.sessions[jid]:
|
||||
self.sessions[jid][thread_id].terminate(send_termination)
|
||||
|
||||
def terminate_sessions(self):
|
||||
self.sessions = {}
|
||||
|
||||
def delete_session(self, jid, thread_id):
|
||||
|
|
|
@ -2309,17 +2309,6 @@ class Oauth2CredentialsRequiredEvent(nec.NetworkIncomingEvent):
|
|||
name = 'oauth2-credentials-required'
|
||||
base_network_events = []
|
||||
|
||||
class FailedDecryptEvent(nec.NetworkIncomingEvent):
|
||||
name = 'failed-decrypt'
|
||||
base_network_events = []
|
||||
|
||||
def generate(self):
|
||||
self.conn = self.msg_obj.conn
|
||||
self.fjid = self.msg_obj.fjid
|
||||
self.timestamp = self.msg_obj.timestamp
|
||||
self.session = self.msg_obj.session
|
||||
return True
|
||||
|
||||
class SignedInEvent(nec.NetworkIncomingEvent):
|
||||
name = 'signed-in'
|
||||
base_network_events = []
|
||||
|
|
|
@ -1,105 +0,0 @@
|
|||
# common crypto functions (mostly specific to XEP-0116, but useful elsewhere)
|
||||
# -*- coding:utf-8 -*-
|
||||
## src/common/crypto.py
|
||||
##
|
||||
## Copyright (C) 2007 Brendan Taylor <whateley AT gmail.com>
|
||||
##
|
||||
## This file is part of Gajim.
|
||||
##
|
||||
## Gajim is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published
|
||||
## by the Free Software Foundation; version 3 only.
|
||||
##
|
||||
## Gajim is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
import sys
|
||||
import os
|
||||
import math
|
||||
|
||||
from hashlib import sha256 as SHA256
|
||||
|
||||
# convert a large integer to a big-endian bitstring
|
||||
def encode_mpi(n):
|
||||
if n >= 256:
|
||||
return encode_mpi(n // 256) + bytes([n % 256])
|
||||
else:
|
||||
return bytes([n])
|
||||
|
||||
# convert a large integer to a big-endian bitstring, padded with \x00s to
|
||||
# a multiple of 16 bytes
|
||||
def encode_mpi_with_padding(n):
|
||||
return pad_to_multiple(encode_mpi(n), 16, '\x00', True)
|
||||
|
||||
# pad 'string' to a multiple of 'multiple_of' with 'char'.
|
||||
# pad on the left if 'left', otherwise pad on the right.
|
||||
def pad_to_multiple(string, multiple_of, char, left):
|
||||
mod = len(string) % multiple_of
|
||||
if mod == 0:
|
||||
return string
|
||||
else:
|
||||
padding = (multiple_of - mod) * char
|
||||
|
||||
if left:
|
||||
return padding + string
|
||||
else:
|
||||
return string + padding
|
||||
|
||||
# convert a big-endian bitstring to an integer
|
||||
def decode_mpi(s):
|
||||
if len(s) == 0:
|
||||
return 0
|
||||
else:
|
||||
return 256 * decode_mpi(s[:-1]) + s[-1]
|
||||
|
||||
def sha256(string):
|
||||
sh = SHA256()
|
||||
sh.update(string)
|
||||
return sh.digest()
|
||||
|
||||
base28_chr = "acdefghikmopqruvwxy123456789"
|
||||
|
||||
def sas_28x5(m_a, form_b):
|
||||
sha = sha256(m_a + form_b + b'Short Authentication String')
|
||||
lsb24 = decode_mpi(sha[-3:])
|
||||
return base28(lsb24)
|
||||
|
||||
def base28(n):
|
||||
if n >= 28:
|
||||
return base28(n // 28) + base28_chr[n % 28]
|
||||
else:
|
||||
return base28_chr[n]
|
||||
|
||||
def random_bytes(bytes_):
|
||||
return os.urandom(bytes_)
|
||||
|
||||
def generate_nonce():
|
||||
return random_bytes(8)
|
||||
|
||||
# generate a random number between 'bottom' and 'top'
|
||||
def srand(bottom, top):
|
||||
# minimum number of bytes needed to represent that range
|
||||
bytes = int(math.ceil(math.log(top - bottom, 256)))
|
||||
|
||||
# in retrospect, this is horribly inadequate.
|
||||
return (decode_mpi(random_bytes(bytes)) % (top - bottom)) + bottom
|
||||
|
||||
# a faster version of (base ** exp) % mod
|
||||
# taken from <http://lists.danga.com/pipermail/yadis/2005-September/001445.html>
|
||||
def powmod(base, exp, mod):
|
||||
square = base % mod
|
||||
result = 1
|
||||
|
||||
while exp > 0:
|
||||
if exp & 1: # exponent is odd
|
||||
result = (result * square) % mod
|
||||
|
||||
square = (square * square) % mod
|
||||
exp //= 2
|
||||
return result
|
|
@ -100,12 +100,6 @@ class NegotiationError(Exception):
|
|||
"""
|
||||
pass
|
||||
|
||||
class DecryptionError(Exception):
|
||||
"""
|
||||
A message couldn't be decrypted into usable XML
|
||||
"""
|
||||
pass
|
||||
|
||||
class Cancelled(Exception):
|
||||
"""
|
||||
The user cancelled an operation
|
||||
|
|
|
@ -1367,9 +1367,6 @@ def update_optional_features(account = None):
|
|||
app.gajim_optional_features[a].append(nbxmpp.NS_CHATSTATES)
|
||||
if not app.config.get('ignore_incoming_xhtml'):
|
||||
app.gajim_optional_features[a].append(nbxmpp.NS_XHTML_IM)
|
||||
if app.is_installed('PYCRYPTO') \
|
||||
and app.config.get_per('accounts', a, 'enable_esessions'):
|
||||
app.gajim_optional_features[a].append(nbxmpp.NS_ESESSION)
|
||||
if app.config.get_per('accounts', a, 'answer_receipts'):
|
||||
app.gajim_optional_features[a].append(nbxmpp.NS_RECEIPTS)
|
||||
app.gajim_optional_features[a].append(nbxmpp.NS_JINGLE)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -69,10 +69,6 @@ class FeaturesWindow:
|
|||
_('Ability to measure idle time, in order to set auto status.'),
|
||||
_('Requires libxss library.'),
|
||||
_('Requires python2.5.')),
|
||||
_('End to End message encryption'): (self.pycrypto_available,
|
||||
_('Encrypting chat messages.'),
|
||||
_('Requires python-crypto.'),
|
||||
_('Requires python-crypto.')),
|
||||
_('RST Generator'): (self.docutils_available,
|
||||
_('Generate XHTML output from RST code (see http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html).'),
|
||||
_('Requires python-docutils.'),
|
||||
|
@ -166,9 +162,6 @@ class FeaturesWindow:
|
|||
from gajim.common import idle
|
||||
return idle.Monitor.is_available()
|
||||
|
||||
def pycrypto_available(self):
|
||||
return app.is_installed('PYCRYPTO')
|
||||
|
||||
def docutils_available(self):
|
||||
try:
|
||||
__import__('docutils')
|
||||
|
|
|
@ -200,13 +200,6 @@ class PrivateChatControl(ChatControl):
|
|||
c = gc_c.as_contact()
|
||||
self.gc_contact = gc_c
|
||||
self.contact = c
|
||||
if self.session:
|
||||
# stop e2e
|
||||
if self.session.enable_encryption:
|
||||
thread_id = self.session.thread_id
|
||||
self.session.terminate_e2e()
|
||||
obj.conn.delete_session(obj.fjid, thread_id)
|
||||
self.no_autonegotiation = False
|
||||
self.draw_banner()
|
||||
old_jid = obj.room_jid + '/' + obj.nick
|
||||
new_jid = obj.room_jid + '/' + obj.new_nick
|
||||
|
@ -282,17 +275,6 @@ class PrivateChatControl(ChatControl):
|
|||
def update_contact(self):
|
||||
self.contact = self.gc_contact.as_contact()
|
||||
|
||||
def begin_e2e_negotiation(self):
|
||||
self.no_autonegotiation = True
|
||||
|
||||
if not self.session:
|
||||
fjid = self.gc_contact.get_full_jid()
|
||||
new_sess = app.connections[self.account].make_new_session(fjid,
|
||||
type_=self.type_id)
|
||||
self.set_session(new_sess)
|
||||
|
||||
self.session.negotiate_e2e(False)
|
||||
|
||||
def got_disconnected(self):
|
||||
ChatControl.got_disconnected(self)
|
||||
|
||||
|
@ -1859,20 +1841,6 @@ class GroupchatControl(ChatControlBase):
|
|||
self.nick = obj.new_nick
|
||||
self.new_nick = ''
|
||||
s = _('You are now known as %s') % self.nick
|
||||
# Stop all E2E sessions
|
||||
nick_list = app.contacts.get_nick_list(self.account,
|
||||
self.room_jid)
|
||||
for nick_ in nick_list:
|
||||
fjid_ = self.room_jid + '/' + nick_
|
||||
ctrl = app.interface.msg_win_mgr.get_control(
|
||||
fjid_, self.account)
|
||||
if ctrl and ctrl.session and \
|
||||
ctrl.session.enable_encryption:
|
||||
thread_id = ctrl.session.thread_id
|
||||
ctrl.session.terminate_e2e()
|
||||
app.connections[self.account].delete_session(
|
||||
fjid_, thread_id)
|
||||
ctrl.no_autonegotiation = False
|
||||
else:
|
||||
s = _('%(nick)s is now known as %(new_nick)s') % {
|
||||
'nick': nick, 'new_nick': obj.new_nick}
|
||||
|
@ -2303,13 +2271,6 @@ class GroupchatControl(ChatControlBase):
|
|||
contact.status = ''
|
||||
ctrl.update_ui()
|
||||
ctrl.parent_win.redraw_tab(ctrl)
|
||||
for sess in app.connections[self.account].get_sessions(fjid):
|
||||
if sess.control:
|
||||
sess.control.no_autonegotiation = False
|
||||
if sess.enable_encryption:
|
||||
sess.terminate_e2e()
|
||||
app.connections[self.account].delete_session(fjid,
|
||||
sess.thread_id)
|
||||
# They can already be removed by the destroy function
|
||||
if self.room_jid in app.contacts.get_gc_list(self.account):
|
||||
app.contacts.remove_room(self.account, self.room_jid)
|
||||
|
|
|
@ -1184,12 +1184,6 @@ class Interface:
|
|||
def handle_atom_entry(obj):
|
||||
AtomWindow.newAtomEntry(obj.atom_entry)
|
||||
|
||||
@staticmethod
|
||||
def handle_event_failed_decrypt(obj):
|
||||
details = _('Unable to decrypt message from %s\nIt may have been '
|
||||
'tampered with.') % obj.fjid
|
||||
dialogs.WarningDialog(_('Unable to decrypt message'), details)
|
||||
|
||||
def handle_event_zc_name_conflict(self, obj):
|
||||
def on_ok(new_name):
|
||||
app.config.set_per('accounts', obj.conn.name, 'name', new_name)
|
||||
|
@ -1531,7 +1525,6 @@ class Interface:
|
|||
'client-cert-passphrase': [
|
||||
self.handle_event_client_cert_passphrase],
|
||||
'connection-lost': [self.handle_event_connection_lost],
|
||||
'failed-decrypt': [(self.handle_event_failed_decrypt, ged.GUI2)],
|
||||
'file-request-error': [self.handle_event_file_request_error],
|
||||
'file-request-received': [self.handle_event_file_request],
|
||||
'gc-invitation-received': [self.handle_event_gc_invitation],
|
||||
|
|
|
@ -32,7 +32,6 @@ import uuid
|
|||
from gajim.common import app
|
||||
from gajim.common import helpers
|
||||
from gajim.common import ged
|
||||
from gajim.common.stanza_session import EncryptedStanzaSession, ArchivingStanzaSession
|
||||
|
||||
# Derived types MUST register their type IDs here if custom behavor is required
|
||||
TYPE_CHAT = 'chat'
|
||||
|
@ -200,19 +199,6 @@ class MessageControl(object):
|
|||
if session and oldsession:
|
||||
oldsession.control = None
|
||||
|
||||
crypto_changed = bool(session and isinstance(session,
|
||||
EncryptedStanzaSession) and session.enable_encryption) != \
|
||||
bool(oldsession and isinstance(oldsession, EncryptedStanzaSession) \
|
||||
and oldsession.enable_encryption)
|
||||
|
||||
archiving_changed = bool(session and isinstance(session,
|
||||
ArchivingStanzaSession) and session.archiving) != \
|
||||
bool(oldsession and isinstance(oldsession,
|
||||
ArchivingStanzaSession) and oldsession.archiving)
|
||||
|
||||
if crypto_changed or archiving_changed:
|
||||
self.print_session_details(oldsession)
|
||||
|
||||
def remove_session(self, session):
|
||||
if session != self.session:
|
||||
return
|
||||
|
|
128
gajim/secrets.py
128
gajim/secrets.py
|
@ -1,128 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
## src/secrets.py
|
||||
##
|
||||
## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
|
||||
## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
|
||||
##
|
||||
## This file is part of Gajim.
|
||||
##
|
||||
## Gajim is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published
|
||||
## by the Free Software Foundation; version 3 only.
|
||||
##
|
||||
## Gajim is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
from gajim.common import configpaths
|
||||
|
||||
import Crypto
|
||||
from gajim.common import crypto
|
||||
from gajim.common import exceptions
|
||||
|
||||
import os
|
||||
import pickle
|
||||
|
||||
secrets_filename = configpaths.get('SECRETS_FILE')
|
||||
secrets_cache = None
|
||||
|
||||
|
||||
class Secrets():
|
||||
def __init__(self, filename):
|
||||
self.filename = filename
|
||||
self.srs = {}
|
||||
self.pubkeys = {}
|
||||
self.privkeys = {}
|
||||
|
||||
def cancel(self):
|
||||
raise exceptions.Cancelled
|
||||
|
||||
def save(self):
|
||||
f = open(secrets_filename, 'wb')
|
||||
pickle.dump(self, f, protocol=2)
|
||||
f.close()
|
||||
|
||||
def retained_secrets(self, account, bare_jid):
|
||||
try:
|
||||
return self.srs[account][bare_jid]
|
||||
except KeyError:
|
||||
return []
|
||||
|
||||
# retained secrets are stored as a tuple of the secret and whether the user
|
||||
# has verified it
|
||||
def save_new_srs(self, account, jid, secret, verified):
|
||||
if not account in self.srs:
|
||||
self.srs[account] = {}
|
||||
|
||||
if not jid in self.srs[account]:
|
||||
self.srs[account][jid] = []
|
||||
|
||||
self.srs[account][jid].append((secret, verified))
|
||||
|
||||
self.save()
|
||||
|
||||
def find_srs(self, account, jid, srs):
|
||||
our_secrets = self.srs[account][jid]
|
||||
return [(x, y) for x, y in our_secrets if x == srs][0]
|
||||
|
||||
# has the user verified this retained secret?
|
||||
def srs_verified(self, account, jid, srs):
|
||||
return self.find_srs(account, jid, srs)[1]
|
||||
|
||||
def replace_srs(self, account, jid, old_secret, new_secret, verified):
|
||||
our_secrets = self.srs[account][jid]
|
||||
|
||||
idx = our_secrets.index(self.find_srs(account, jid, old_secret))
|
||||
|
||||
our_secrets[idx] = (new_secret, verified)
|
||||
|
||||
self.save()
|
||||
|
||||
# the public key associated with 'account'
|
||||
def my_pubkey(self, account):
|
||||
try:
|
||||
pk = self.privkeys[account]
|
||||
except KeyError:
|
||||
pk = Crypto.PublicKey.RSA.generate(2048, crypto.random_bytes)
|
||||
|
||||
self.privkeys[account] = pk
|
||||
self.save()
|
||||
|
||||
return pk
|
||||
|
||||
def load_secrets(filename):
|
||||
f = open(filename, 'rb')
|
||||
|
||||
try:
|
||||
secrets = pickle.load(f, encoding='latin1')
|
||||
# We do that to be able to read files written in py2
|
||||
for acct in secrets.srs:
|
||||
for jid in secrets.srs[acct]:
|
||||
for (secret, verified) in list(secrets.srs[acct][jid]):
|
||||
if type(secret) is str:
|
||||
secrets.srs[acct][jid].remove((secret, verified))
|
||||
secrets.srs[acct][jid].append((secret.encode('latin1'), verified))
|
||||
except (KeyError, EOFError, ImportError):
|
||||
f.close()
|
||||
secrets = Secrets(filename)
|
||||
|
||||
f.close()
|
||||
return secrets
|
||||
|
||||
def secrets():
|
||||
global secrets_cache
|
||||
|
||||
if secrets_cache:
|
||||
return secrets_cache
|
||||
|
||||
if os.path.exists(secrets_filename):
|
||||
secrets_cache = load_secrets(secrets_filename)
|
||||
else:
|
||||
secrets_cache = Secrets(secrets_filename)
|
||||
|
||||
return secrets_cache
|
232
gajim/session.py
232
gajim/session.py
|
@ -21,11 +21,13 @@
|
|||
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
import string
|
||||
import random
|
||||
import itertools
|
||||
|
||||
from gajim.common import helpers
|
||||
from gajim.common import events
|
||||
from gajim.common import exceptions
|
||||
from gajim.common import app
|
||||
from gajim.common import stanza_session
|
||||
from gajim.common import contacts
|
||||
from gajim.common import ged
|
||||
from gajim.common.connection_handlers_events import ChatstateReceivedEvent, \
|
||||
|
@ -34,28 +36,48 @@ from gajim.common.const import KindConstant
|
|||
from gajim import message_control
|
||||
from gajim import notify
|
||||
from gajim import dialogs
|
||||
from gajim import negotiation
|
||||
|
||||
class ChatControlSession(stanza_session.EncryptedStanzaSession):
|
||||
|
||||
class ChatControlSession(object):
|
||||
def __init__(self, conn, jid, thread_id, type_='chat'):
|
||||
stanza_session.EncryptedStanzaSession.__init__(self, conn, jid, thread_id,
|
||||
type_='chat')
|
||||
app.ged.register_event_handler('decrypted-message-received', ged.PREGUI,
|
||||
self._nec_decrypted_message_received)
|
||||
|
||||
self.conn = conn
|
||||
self.jid = jid
|
||||
self.type_ = type_
|
||||
self.resource = jid.getResource()
|
||||
self.control = None
|
||||
|
||||
def detach_from_control(self):
|
||||
if self.control:
|
||||
self.control.set_session(None)
|
||||
if thread_id:
|
||||
self.received_thread_id = True
|
||||
self.thread_id = thread_id
|
||||
else:
|
||||
self.received_thread_id = False
|
||||
if type_ == 'normal':
|
||||
self.thread_id = None
|
||||
else:
|
||||
self.thread_id = self.generate_thread_id()
|
||||
|
||||
def acknowledge_termination(self):
|
||||
self.detach_from_control()
|
||||
stanza_session.EncryptedStanzaSession.acknowledge_termination(self)
|
||||
self.loggable = True
|
||||
|
||||
def terminate(self, send_termination = True):
|
||||
stanza_session.EncryptedStanzaSession.terminate(self, send_termination)
|
||||
self.detach_from_control()
|
||||
self.last_send = 0
|
||||
self.last_receive = 0
|
||||
|
||||
app.ged.register_event_handler('decrypted-message-received',
|
||||
ged.PREGUI,
|
||||
self._nec_decrypted_message_received)
|
||||
|
||||
def generate_thread_id(self):
|
||||
return ''.join(
|
||||
[f(string.ascii_letters) for f in itertools.repeat(
|
||||
random.choice, 32)]
|
||||
)
|
||||
|
||||
def is_loggable(self):
|
||||
return app.config.should_log(self.conn.name,
|
||||
self.jid.getStripped())
|
||||
|
||||
def get_to(self):
|
||||
to = str(self.jid)
|
||||
return app.get_jid_without_resource(to) + '/' + self.resource
|
||||
|
||||
def _nec_decrypted_message_received(self, obj):
|
||||
"""
|
||||
|
@ -400,177 +422,3 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession):
|
|||
else:
|
||||
bb_jid, bb_account = jid, self.conn.name
|
||||
app.interface.roster.select_contact(bb_jid, bb_account)
|
||||
|
||||
# ---- ESessions stuff ---
|
||||
|
||||
def handle_negotiation(self, form):
|
||||
if form.getField('accept') and not form['accept'] in ('1', 'true'):
|
||||
self.cancelled_negotiation()
|
||||
return
|
||||
|
||||
# encrypted session states. these are described in stanza_session.py
|
||||
|
||||
try:
|
||||
if form.getType() == 'form' and 'security' in form.asDict():
|
||||
security_options = [x[1] for x in form.getField('security').\
|
||||
getOptions()]
|
||||
if security_options == ['none']:
|
||||
self.respond_archiving(form)
|
||||
else:
|
||||
# bob responds
|
||||
|
||||
# we don't support 3-message negotiation as the responder
|
||||
if 'dhkeys' in form.asDict():
|
||||
self.fail_bad_negotiation('3 message negotiation not '
|
||||
'supported when responding', ('dhkeys',))
|
||||
return
|
||||
|
||||
negotiated, not_acceptable, ask_user = \
|
||||
self.verify_options_bob(form)
|
||||
|
||||
if ask_user:
|
||||
def accept_nondefault_options(is_checked):
|
||||
self.dialog.destroy()
|
||||
negotiated.update(ask_user)
|
||||
self.respond_e2e_bob(form, negotiated,
|
||||
not_acceptable)
|
||||
|
||||
def reject_nondefault_options():
|
||||
self.dialog.destroy()
|
||||
for key in ask_user.keys():
|
||||
not_acceptable.append(key)
|
||||
self.respond_e2e_bob(form, negotiated,
|
||||
not_acceptable)
|
||||
|
||||
self.dialog = dialogs.YesNoDialog(_('Confirm these '
|
||||
'session options'),
|
||||
_('The remote client wants to negotiate a session '
|
||||
'with these features:\n\n%s\n\nAre these options '
|
||||
'acceptable?''') % (
|
||||
negotiation.describe_features(ask_user)),
|
||||
on_response_yes=accept_nondefault_options,
|
||||
on_response_no=reject_nondefault_options,
|
||||
transient_for=self.control.parent_win.window)
|
||||
else:
|
||||
self.respond_e2e_bob(form, negotiated, not_acceptable)
|
||||
|
||||
return
|
||||
|
||||
elif self.status == 'requested-archiving' and form.getType() == \
|
||||
'submit':
|
||||
try:
|
||||
self.archiving_accepted(form)
|
||||
except exceptions.NegotiationError as details:
|
||||
self.fail_bad_negotiation(details)
|
||||
|
||||
return
|
||||
|
||||
# alice accepts
|
||||
elif self.status == 'requested-e2e' and form.getType() == 'submit':
|
||||
negotiated, not_acceptable, ask_user = self.verify_options_alice(
|
||||
form)
|
||||
|
||||
if ask_user:
|
||||
def accept_nondefault_options(is_checked):
|
||||
if dialog:
|
||||
dialog.destroy()
|
||||
|
||||
if is_checked:
|
||||
allow_no_log_for = app.config.get_per(
|
||||
'accounts', self.conn.name,
|
||||
'allow_no_log_for').split()
|
||||
jid = str(self.jid)
|
||||
if jid not in allow_no_log_for:
|
||||
allow_no_log_for.append(jid)
|
||||
app.config.set_per('accounts', self.conn.name,
|
||||
'allow_no_log_for', ' '.join(allow_no_log_for))
|
||||
|
||||
negotiated.update(ask_user)
|
||||
|
||||
try:
|
||||
self.accept_e2e_alice(form, negotiated)
|
||||
except exceptions.NegotiationError as details:
|
||||
self.fail_bad_negotiation(details)
|
||||
|
||||
def reject_nondefault_options():
|
||||
self.reject_negotiation()
|
||||
dialog.destroy()
|
||||
|
||||
allow_no_log_for = app.config.get_per('accounts',
|
||||
self.conn.name, 'allow_no_log_for').split()
|
||||
if str(self.jid) in allow_no_log_for:
|
||||
dialog = None
|
||||
accept_nondefault_options(False)
|
||||
else:
|
||||
dialog = dialogs.YesNoDialog(_('Confirm these session '
|
||||
'options'),
|
||||
_('The remote client selected these options:\n\n%s'
|
||||
'\n\nContinue with the session?') % (
|
||||
negotiation.describe_features(ask_user)),
|
||||
_('Always accept for this contact'),
|
||||
on_response_yes = accept_nondefault_options,
|
||||
on_response_no = reject_nondefault_options,
|
||||
transient_for=self.control.parent_win.window)
|
||||
else:
|
||||
try:
|
||||
self.accept_e2e_alice(form, negotiated)
|
||||
except exceptions.NegotiationError as details:
|
||||
self.fail_bad_negotiation(details)
|
||||
|
||||
return
|
||||
elif self.status == 'responded-archiving' and form.getType() == \
|
||||
'result':
|
||||
try:
|
||||
self.we_accept_archiving(form)
|
||||
except exceptions.NegotiationError as details:
|
||||
self.fail_bad_negotiation(details)
|
||||
|
||||
return
|
||||
elif self.status == 'responded-e2e' and form.getType() == 'result':
|
||||
try:
|
||||
self.accept_e2e_bob(form)
|
||||
except exceptions.NegotiationError as details:
|
||||
self.fail_bad_negotiation(details)
|
||||
|
||||
return
|
||||
elif self.status == 'identified-alice' and form.getType() == 'result':
|
||||
try:
|
||||
self.final_steps_alice(form)
|
||||
except exceptions.NegotiationError as details:
|
||||
self.fail_bad_negotiation(details)
|
||||
|
||||
return
|
||||
except exceptions.Cancelled:
|
||||
# user cancelled the negotiation
|
||||
|
||||
self.reject_negotiation()
|
||||
|
||||
return
|
||||
|
||||
if form.getField('terminate') and\
|
||||
form.getField('terminate').getValue() in ('1', 'true'):
|
||||
self.acknowledge_termination()
|
||||
|
||||
self.conn.delete_session(str(self.jid), self.thread_id)
|
||||
|
||||
return
|
||||
|
||||
# non-esession negotiation. this isn't very useful, but i'm keeping it
|
||||
# around to test my test suite.
|
||||
if form.getType() == 'form':
|
||||
if not self.control:
|
||||
jid, resource = app.get_room_and_nick_from_fjid(str(self.jid))
|
||||
|
||||
account = self.conn.name
|
||||
contact = app.contacts.get_contact(account, str(self.jid),
|
||||
resource)
|
||||
|
||||
if not contact:
|
||||
contact = app.contacts.create_contact(jid=jid, account=account,
|
||||
resource=resource, show=self.conn.get_status())
|
||||
|
||||
app.interface.new_chat(contact, account, resource=resource,
|
||||
session=self)
|
||||
|
||||
negotiation.FeatureNegotiationWindow(account, str(self.jid), self,
|
||||
form)
|
||||
|
|
|
@ -212,25 +212,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
/* ESession support */
|
||||
{
|
||||
"name": "python3-pycrypto",
|
||||
"buildsystem": "simple",
|
||||
"build-commands": [
|
||||
"python3 setup.py install --prefix=/app"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://pypi.python.org/packages/60/db/645aa9af249f059cc3a368b118de33889219e0362141e75d4eaf6f80f163/pycrypto-2.6.1.tar.gz",
|
||||
"sha256": "f2ce1e989b272cfcb677616763e0a2e7ec659effa67a88aa92b3a65528f60a3c"
|
||||
},
|
||||
{
|
||||
"type": "patch",
|
||||
"path": "flatpak/CVE-2013-7459.patch"
|
||||
}
|
||||
]
|
||||
},
|
||||
/* gnupg support */
|
||||
{
|
||||
"name": "python3-python-gnupg",
|
||||
|
|
|
@ -57,8 +57,6 @@ configpaths.init()
|
|||
|
||||
configpaths.override_path('DATA', gajim_root + '/gajim/data')
|
||||
|
||||
from common.stanza_session import StanzaSession
|
||||
|
||||
# name to use for the test account
|
||||
account_name = 'test'
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@ from gajim.common.connection_handlers_events import MessageReceivedEvent
|
|||
from gajim.common.connection_handlers_events import DecryptedMessageReceivedEvent
|
||||
import nbxmpp
|
||||
|
||||
from gajim.common.stanza_session import StanzaSession
|
||||
from gajim.session import ChatControlSession
|
||||
from gajim.roster_window import RosterWindow
|
||||
|
||||
|
@ -30,59 +29,6 @@ app.interface = MockInterface()
|
|||
# name to use for the test account
|
||||
account_name = account1
|
||||
|
||||
class TestStanzaSession(unittest.TestCase):
|
||||
''' Testclass for common/stanzasession.py '''
|
||||
|
||||
def setUp(self):
|
||||
self.jid = nbxmpp.JID('test@example.org/Gajim')
|
||||
self.conn = MockConnection(account_name, {'send_stanza': None})
|
||||
self.sess = StanzaSession(self.conn, self.jid, None, 'chat')
|
||||
|
||||
def test_generate_thread_id(self):
|
||||
# thread_id is a string
|
||||
self.assertTrue(isinstance(self.sess.thread_id, str))
|
||||
|
||||
# it should be somewhat long, to avoid clashes
|
||||
self.assertTrue(len(self.sess.thread_id) >= 32)
|
||||
|
||||
def test_is_loggable(self):
|
||||
# by default a session should be loggable
|
||||
# (unless the no_log_for setting says otherwise)
|
||||
self.assertTrue(self.sess.is_loggable())
|
||||
|
||||
def test_terminate(self):
|
||||
# termination is sent by default
|
||||
self.sess.last_send = time.time()
|
||||
self.sess.terminate()
|
||||
|
||||
self.assertEqual(None, self.sess.status)
|
||||
|
||||
calls = self.conn.mockGetNamedCalls('send_stanza')
|
||||
msg = calls[0].getParam(0)
|
||||
|
||||
self.assertEqual(msg.getThread(), self.sess.thread_id)
|
||||
|
||||
def test_terminate_without_sending(self):
|
||||
# no termination is sent if no messages have been sent in the session
|
||||
self.sess.terminate()
|
||||
|
||||
self.assertEqual(None, self.sess.status)
|
||||
|
||||
calls = self.conn.mockGetNamedCalls('send_stanza')
|
||||
self.assertEqual(0, len(calls))
|
||||
|
||||
def test_terminate_no_remote_xep_201(self):
|
||||
# no termination is sent if only messages without thread ids have been
|
||||
# received
|
||||
self.sess.last_send = time.time()
|
||||
self.sess.last_receive = time.time()
|
||||
self.sess.terminate()
|
||||
|
||||
self.assertEqual(None, self.sess.status)
|
||||
|
||||
calls = self.conn.mockGetNamedCalls('send_stanza')
|
||||
self.assertEqual(0, len(calls))
|
||||
|
||||
|
||||
class TestChatControlSession(unittest.TestCase):
|
||||
''' Testclass for session.py '''
|
||||
|
|
|
@ -91,7 +91,6 @@ function install_deps {
|
|||
|
||||
PIP_REQUIREMENTS="\
|
||||
git+https://dev.gajim.org/gajim/python-nbxmpp.git
|
||||
git+https://github.com/dlitz/pycrypto.git
|
||||
git+https://dev.gajim.org/lovetox/pybonjour-python3.git
|
||||
keyring
|
||||
python-gnupg
|
||||
|
|
|
@ -23,7 +23,6 @@ certifi
|
|||
git+https://dev.gajim.org/gajim/python-nbxmpp.git
|
||||
git+https://dev.gajim.org/lovetox/pybonjour-python3.git
|
||||
protobuf
|
||||
git+https://github.com/dlitz/pycrypto.git
|
||||
cryptography
|
||||
pyopenssl
|
||||
python-gnupg
|
||||
|
|
Loading…
Reference in New Issue