parent
56fbe32b11
commit
32b74b459e
|
@ -15,7 +15,6 @@
|
||||||
|
|
||||||
- python3-keyring for saving your password to your system keyring
|
- python3-keyring for saving your password to your system keyring
|
||||||
- python3-pil (pillow) for support of webp avatars
|
- python3-pil (pillow) for support of webp avatars
|
||||||
- python3-crypto to enable End to end encryption
|
|
||||||
- python3-gnupg to enable GPG encryption
|
- python3-gnupg to enable GPG encryption
|
||||||
- For zeroconf (bonjour) you need python3-dbus
|
- For zeroconf (bonjour) you need python3-dbus
|
||||||
- gir1.2-gspell-1 and hunspell-LANG where lang is your locale eg. en, fr etc
|
- 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 helpers
|
||||||
from gajim.common import ged
|
from gajim.common import ged
|
||||||
from gajim.common import i18n
|
from gajim.common import i18n
|
||||||
from gajim.common.stanza_session import EncryptedStanzaSession, ArchivingStanzaSession
|
|
||||||
from gajim.common.contacts import GC_Contact
|
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_XHTML, NS_XHTML_IM, NS_FILE, NS_MUC
|
||||||
from nbxmpp.protocol import NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO
|
from nbxmpp.protocol import NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO
|
||||||
|
@ -207,9 +206,6 @@ class ChatControl(ChatControlBase):
|
||||||
session.control = self
|
session.control = self
|
||||||
self.session = session
|
self.session = session
|
||||||
|
|
||||||
if session.enable_encryption:
|
|
||||||
self.print_esession_details()
|
|
||||||
|
|
||||||
# Enable encryption if needed
|
# Enable encryption if needed
|
||||||
self.no_autonegotiation = False
|
self.no_autonegotiation = False
|
||||||
self.add_actions()
|
self.add_actions()
|
||||||
|
@ -231,8 +227,6 @@ class ChatControl(ChatControlBase):
|
||||||
# Dont connect this when PrivateChatControl is used
|
# Dont connect this when PrivateChatControl is used
|
||||||
app.ged.register_event_handler('update-roster-avatar', ged.GUI1,
|
app.ged.register_event_handler('update-roster-avatar', ged.GUI1,
|
||||||
self._nec_update_avatar)
|
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,
|
app.ged.register_event_handler('chatstate-received', ged.GUI1,
|
||||||
self._nec_chatstate_received)
|
self._nec_chatstate_received)
|
||||||
app.ged.register_event_handler('caps-received', ged.GUI1,
|
app.ged.register_event_handler('caps-received', ged.GUI1,
|
||||||
|
@ -920,53 +914,6 @@ class ChatControl(ChatControlBase):
|
||||||
chatstate=chatstate_to_send, xhtml=xhtml,
|
chatstate=chatstate_to_send, xhtml=xhtml,
|
||||||
process_commands=process_commands, attention=attention)
|
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):
|
def get_our_nick(self):
|
||||||
return app.nicks[self.account]
|
return app.nicks[self.account]
|
||||||
|
|
||||||
|
@ -1186,8 +1133,6 @@ class ChatControl(ChatControlBase):
|
||||||
if self.TYPE_ID == message_control.TYPE_CHAT:
|
if self.TYPE_ID == message_control.TYPE_CHAT:
|
||||||
app.ged.remove_event_handler('update-roster-avatar', ged.GUI1,
|
app.ged.remove_event_handler('update-roster-avatar', ged.GUI1,
|
||||||
self._nec_update_avatar)
|
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,
|
app.ged.remove_event_handler('chatstate-received', ged.GUI1,
|
||||||
self._nec_chatstate_received)
|
self._nec_chatstate_received)
|
||||||
app.ged.remove_event_handler('caps-received', ged.GUI1,
|
app.ged.remove_event_handler('caps-received', ged.GUI1,
|
||||||
|
@ -1482,57 +1427,6 @@ class ChatControl(ChatControlBase):
|
||||||
"""
|
"""
|
||||||
dialogs.TransformChatToMUC(self.account, [self.contact.jid])
|
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):
|
def got_connected(self):
|
||||||
ChatControlBase.got_connected(self)
|
ChatControlBase.got_connected(self)
|
||||||
# Refreshing contact
|
# Refreshing contact
|
||||||
|
|
|
@ -451,9 +451,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
|
||||||
plugin = app.plugin_manager.encryption_plugins[encryption]
|
plugin = app.plugin_manager.encryption_plugins[encryption]
|
||||||
if not plugin.activate_encryption(self):
|
if not plugin.activate_encryption(self):
|
||||||
return
|
return
|
||||||
else:
|
|
||||||
if not self.widget_name == 'groupchat_control':
|
|
||||||
self.terminate_esessions()
|
|
||||||
action.set_state(param)
|
action.set_state(param)
|
||||||
self.set_encryption_state(encryption)
|
self.set_encryption_state(encryption)
|
||||||
self.set_encryption_menu_icon()
|
self.set_encryption_menu_icon()
|
||||||
|
|
|
@ -147,7 +147,6 @@ caps_hash = {}
|
||||||
_dependencies = {
|
_dependencies = {
|
||||||
'PYTHON-DBUS': False,
|
'PYTHON-DBUS': False,
|
||||||
'PYBONJOUR': False,
|
'PYBONJOUR': False,
|
||||||
'PYCRYPTO': False,
|
|
||||||
'PYGPG': False,
|
'PYGPG': False,
|
||||||
'GPG_BINARY': False,
|
'GPG_BINARY': False,
|
||||||
'FARSTREAM': False,
|
'FARSTREAM': False,
|
||||||
|
@ -188,13 +187,6 @@ def detect_dependencies():
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# PYCRYPTO
|
|
||||||
try:
|
|
||||||
import Crypto
|
|
||||||
_dependencies['PYCRYPTO'] = True
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# python-gnupg
|
# python-gnupg
|
||||||
try:
|
try:
|
||||||
import gnupg
|
import gnupg
|
||||||
|
|
|
@ -338,8 +338,6 @@ class Config:
|
||||||
'keyid': [ opt_str, '', '', True ],
|
'keyid': [ opt_str, '', '', True ],
|
||||||
'gpg_sign_presence': [ opt_bool, True, _('If disabled, don\'t sign presences with GPG key, even if GPG is configured.') ],
|
'gpg_sign_presence': [ opt_bool, True, _('If disabled, don\'t sign presences with GPG key, even if GPG is configured.') ],
|
||||||
'keyname': [ opt_str, '', '', True ],
|
'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')],
|
'allow_plaintext_connection': [ opt_bool, False, _('Allow plaintext connections')],
|
||||||
'tls_version': [ opt_str, '1.2', '' ],
|
'tls_version': [ opt_str, '1.2', '' ],
|
||||||
'cipher_list': [ opt_str, 'HIGH:!aNULL:RC4-SHA', '' ],
|
'cipher_list': [ opt_str, 'HIGH:!aNULL:RC4-SHA', '' ],
|
||||||
|
|
|
@ -340,10 +340,7 @@ class CommonConnection:
|
||||||
# <body> tag is the active event
|
# <body> tag is the active event
|
||||||
if obj.chatstate and contact and contact.supports(nbxmpp.NS_CHATSTATES):
|
if obj.chatstate and contact and contact.supports(nbxmpp.NS_CHATSTATES):
|
||||||
msg_iq.setTag(obj.chatstate, namespace=nbxmpp.NS_CHATSTATES)
|
msg_iq.setTag(obj.chatstate, namespace=nbxmpp.NS_CHATSTATES)
|
||||||
only_chatste = False
|
|
||||||
if not obj.message:
|
if not obj.message:
|
||||||
only_chatste = True
|
|
||||||
if only_chatste and not obj.session.enable_encryption:
|
|
||||||
msg_iq.setTag('no-store',
|
msg_iq.setTag('no-store',
|
||||||
namespace=nbxmpp.NS_MSG_HINTS)
|
namespace=nbxmpp.NS_MSG_HINTS)
|
||||||
|
|
||||||
|
|
|
@ -830,8 +830,6 @@ class ConnectionHandlersBase:
|
||||||
self._nec_iq_error_received)
|
self._nec_iq_error_received)
|
||||||
app.ged.register_event_handler('presence-received', ged.CORE,
|
app.ged.register_event_handler('presence-received', ged.CORE,
|
||||||
self._nec_presence_received)
|
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,
|
app.ged.register_event_handler('message-received', ged.CORE,
|
||||||
self._nec_message_received)
|
self._nec_message_received)
|
||||||
app.ged.register_event_handler('mam-message-received', ged.CORE,
|
app.ged.register_event_handler('mam-message-received', ged.CORE,
|
||||||
|
@ -848,8 +846,6 @@ class ConnectionHandlersBase:
|
||||||
self._nec_iq_error_received)
|
self._nec_iq_error_received)
|
||||||
app.ged.remove_event_handler('presence-received', ged.CORE,
|
app.ged.remove_event_handler('presence-received', ged.CORE,
|
||||||
self._nec_presence_received)
|
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,
|
app.ged.remove_event_handler('message-received', ged.CORE,
|
||||||
self._nec_message_received)
|
self._nec_message_received)
|
||||||
app.ged.remove_event_handler('mam-message-received', ged.CORE,
|
app.ged.remove_event_handler('mam-message-received', ged.CORE,
|
||||||
|
@ -975,24 +971,6 @@ class ConnectionHandlersBase:
|
||||||
# resource signs off!
|
# resource signs off!
|
||||||
self.stop_all_active_file_transfers(obj.contact)
|
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 \
|
if app.config.get('log_contact_status_changes') and \
|
||||||
app.config.should_log(self.name, obj.jid):
|
app.config.should_log(self.name, obj.jid):
|
||||||
show = app.logger.convert_show_values_to_db_api_values(obj.show)
|
show = app.logger.convert_show_values_to_db_api_values(obj.show)
|
||||||
|
@ -1004,15 +982,6 @@ class ConnectionHandlersBase:
|
||||||
message=obj.status,
|
message=obj.status,
|
||||||
show=show)
|
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):
|
def _nec_message_received(self, obj):
|
||||||
if obj.conn.name != self.name:
|
if obj.conn.name != self.name:
|
||||||
return
|
return
|
||||||
|
@ -1210,14 +1179,7 @@ class ConnectionHandlersBase:
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def terminate_sessions(self, send_termination=False):
|
def terminate_sessions(self):
|
||||||
"""
|
|
||||||
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)
|
|
||||||
|
|
||||||
self.sessions = {}
|
self.sessions = {}
|
||||||
|
|
||||||
def delete_session(self, jid, thread_id):
|
def delete_session(self, jid, thread_id):
|
||||||
|
|
|
@ -2309,17 +2309,6 @@ class Oauth2CredentialsRequiredEvent(nec.NetworkIncomingEvent):
|
||||||
name = 'oauth2-credentials-required'
|
name = 'oauth2-credentials-required'
|
||||||
base_network_events = []
|
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):
|
class SignedInEvent(nec.NetworkIncomingEvent):
|
||||||
name = 'signed-in'
|
name = 'signed-in'
|
||||||
base_network_events = []
|
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
|
pass
|
||||||
|
|
||||||
class DecryptionError(Exception):
|
|
||||||
"""
|
|
||||||
A message couldn't be decrypted into usable XML
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
class Cancelled(Exception):
|
class Cancelled(Exception):
|
||||||
"""
|
"""
|
||||||
The user cancelled an operation
|
The user cancelled an operation
|
||||||
|
|
|
@ -1367,9 +1367,6 @@ def update_optional_features(account = None):
|
||||||
app.gajim_optional_features[a].append(nbxmpp.NS_CHATSTATES)
|
app.gajim_optional_features[a].append(nbxmpp.NS_CHATSTATES)
|
||||||
if not app.config.get('ignore_incoming_xhtml'):
|
if not app.config.get('ignore_incoming_xhtml'):
|
||||||
app.gajim_optional_features[a].append(nbxmpp.NS_XHTML_IM)
|
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'):
|
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_RECEIPTS)
|
||||||
app.gajim_optional_features[a].append(nbxmpp.NS_JINGLE)
|
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.'),
|
_('Ability to measure idle time, in order to set auto status.'),
|
||||||
_('Requires libxss library.'),
|
_('Requires libxss library.'),
|
||||||
_('Requires python2.5.')),
|
_('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,
|
_('RST Generator'): (self.docutils_available,
|
||||||
_('Generate XHTML output from RST code (see http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html).'),
|
_('Generate XHTML output from RST code (see http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html).'),
|
||||||
_('Requires python-docutils.'),
|
_('Requires python-docutils.'),
|
||||||
|
@ -166,9 +162,6 @@ class FeaturesWindow:
|
||||||
from gajim.common import idle
|
from gajim.common import idle
|
||||||
return idle.Monitor.is_available()
|
return idle.Monitor.is_available()
|
||||||
|
|
||||||
def pycrypto_available(self):
|
|
||||||
return app.is_installed('PYCRYPTO')
|
|
||||||
|
|
||||||
def docutils_available(self):
|
def docutils_available(self):
|
||||||
try:
|
try:
|
||||||
__import__('docutils')
|
__import__('docutils')
|
||||||
|
|
|
@ -200,13 +200,6 @@ class PrivateChatControl(ChatControl):
|
||||||
c = gc_c.as_contact()
|
c = gc_c.as_contact()
|
||||||
self.gc_contact = gc_c
|
self.gc_contact = gc_c
|
||||||
self.contact = 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()
|
self.draw_banner()
|
||||||
old_jid = obj.room_jid + '/' + obj.nick
|
old_jid = obj.room_jid + '/' + obj.nick
|
||||||
new_jid = obj.room_jid + '/' + obj.new_nick
|
new_jid = obj.room_jid + '/' + obj.new_nick
|
||||||
|
@ -282,17 +275,6 @@ class PrivateChatControl(ChatControl):
|
||||||
def update_contact(self):
|
def update_contact(self):
|
||||||
self.contact = self.gc_contact.as_contact()
|
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):
|
def got_disconnected(self):
|
||||||
ChatControl.got_disconnected(self)
|
ChatControl.got_disconnected(self)
|
||||||
|
|
||||||
|
@ -1859,20 +1841,6 @@ class GroupchatControl(ChatControlBase):
|
||||||
self.nick = obj.new_nick
|
self.nick = obj.new_nick
|
||||||
self.new_nick = ''
|
self.new_nick = ''
|
||||||
s = _('You are now known as %s') % self.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:
|
else:
|
||||||
s = _('%(nick)s is now known as %(new_nick)s') % {
|
s = _('%(nick)s is now known as %(new_nick)s') % {
|
||||||
'nick': nick, 'new_nick': obj.new_nick}
|
'nick': nick, 'new_nick': obj.new_nick}
|
||||||
|
@ -2303,13 +2271,6 @@ class GroupchatControl(ChatControlBase):
|
||||||
contact.status = ''
|
contact.status = ''
|
||||||
ctrl.update_ui()
|
ctrl.update_ui()
|
||||||
ctrl.parent_win.redraw_tab(ctrl)
|
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
|
# They can already be removed by the destroy function
|
||||||
if self.room_jid in app.contacts.get_gc_list(self.account):
|
if self.room_jid in app.contacts.get_gc_list(self.account):
|
||||||
app.contacts.remove_room(self.account, self.room_jid)
|
app.contacts.remove_room(self.account, self.room_jid)
|
||||||
|
|
|
@ -1184,12 +1184,6 @@ class Interface:
|
||||||
def handle_atom_entry(obj):
|
def handle_atom_entry(obj):
|
||||||
AtomWindow.newAtomEntry(obj.atom_entry)
|
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 handle_event_zc_name_conflict(self, obj):
|
||||||
def on_ok(new_name):
|
def on_ok(new_name):
|
||||||
app.config.set_per('accounts', obj.conn.name, 'name', new_name)
|
app.config.set_per('accounts', obj.conn.name, 'name', new_name)
|
||||||
|
@ -1531,7 +1525,6 @@ class Interface:
|
||||||
'client-cert-passphrase': [
|
'client-cert-passphrase': [
|
||||||
self.handle_event_client_cert_passphrase],
|
self.handle_event_client_cert_passphrase],
|
||||||
'connection-lost': [self.handle_event_connection_lost],
|
'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-error': [self.handle_event_file_request_error],
|
||||||
'file-request-received': [self.handle_event_file_request],
|
'file-request-received': [self.handle_event_file_request],
|
||||||
'gc-invitation-received': [self.handle_event_gc_invitation],
|
'gc-invitation-received': [self.handle_event_gc_invitation],
|
||||||
|
|
|
@ -32,7 +32,6 @@ import uuid
|
||||||
from gajim.common import app
|
from gajim.common import app
|
||||||
from gajim.common import helpers
|
from gajim.common import helpers
|
||||||
from gajim.common import ged
|
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
|
# Derived types MUST register their type IDs here if custom behavor is required
|
||||||
TYPE_CHAT = 'chat'
|
TYPE_CHAT = 'chat'
|
||||||
|
@ -200,19 +199,6 @@ class MessageControl(object):
|
||||||
if session and oldsession:
|
if session and oldsession:
|
||||||
oldsession.control = None
|
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):
|
def remove_session(self, session):
|
||||||
if session != self.session:
|
if session != self.session:
|
||||||
return
|
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/>.
|
## 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 helpers
|
||||||
from gajim.common import events
|
from gajim.common import events
|
||||||
from gajim.common import exceptions
|
|
||||||
from gajim.common import app
|
from gajim.common import app
|
||||||
from gajim.common import stanza_session
|
|
||||||
from gajim.common import contacts
|
from gajim.common import contacts
|
||||||
from gajim.common import ged
|
from gajim.common import ged
|
||||||
from gajim.common.connection_handlers_events import ChatstateReceivedEvent, \
|
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 message_control
|
||||||
from gajim import notify
|
from gajim import notify
|
||||||
from gajim import dialogs
|
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'):
|
def __init__(self, conn, jid, thread_id, type_='chat'):
|
||||||
stanza_session.EncryptedStanzaSession.__init__(self, conn, jid, thread_id,
|
self.conn = conn
|
||||||
type_='chat')
|
self.jid = jid
|
||||||
app.ged.register_event_handler('decrypted-message-received', ged.PREGUI,
|
self.type_ = type_
|
||||||
self._nec_decrypted_message_received)
|
self.resource = jid.getResource()
|
||||||
|
|
||||||
self.control = None
|
self.control = None
|
||||||
|
|
||||||
def detach_from_control(self):
|
if thread_id:
|
||||||
if self.control:
|
self.received_thread_id = True
|
||||||
self.control.set_session(None)
|
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.loggable = True
|
||||||
self.detach_from_control()
|
|
||||||
stanza_session.EncryptedStanzaSession.acknowledge_termination(self)
|
|
||||||
|
|
||||||
def terminate(self, send_termination = True):
|
self.last_send = 0
|
||||||
stanza_session.EncryptedStanzaSession.terminate(self, send_termination)
|
self.last_receive = 0
|
||||||
self.detach_from_control()
|
|
||||||
|
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):
|
def _nec_decrypted_message_received(self, obj):
|
||||||
"""
|
"""
|
||||||
|
@ -400,177 +422,3 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession):
|
||||||
else:
|
else:
|
||||||
bb_jid, bb_account = jid, self.conn.name
|
bb_jid, bb_account = jid, self.conn.name
|
||||||
app.interface.roster.select_contact(bb_jid, bb_account)
|
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 */
|
/* gnupg support */
|
||||||
{
|
{
|
||||||
"name": "python3-python-gnupg",
|
"name": "python3-python-gnupg",
|
||||||
|
|
|
@ -57,8 +57,6 @@ configpaths.init()
|
||||||
|
|
||||||
configpaths.override_path('DATA', gajim_root + '/gajim/data')
|
configpaths.override_path('DATA', gajim_root + '/gajim/data')
|
||||||
|
|
||||||
from common.stanza_session import StanzaSession
|
|
||||||
|
|
||||||
# name to use for the test account
|
# name to use for the test account
|
||||||
account_name = 'test'
|
account_name = 'test'
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@ from gajim.common.connection_handlers_events import MessageReceivedEvent
|
||||||
from gajim.common.connection_handlers_events import DecryptedMessageReceivedEvent
|
from gajim.common.connection_handlers_events import DecryptedMessageReceivedEvent
|
||||||
import nbxmpp
|
import nbxmpp
|
||||||
|
|
||||||
from gajim.common.stanza_session import StanzaSession
|
|
||||||
from gajim.session import ChatControlSession
|
from gajim.session import ChatControlSession
|
||||||
from gajim.roster_window import RosterWindow
|
from gajim.roster_window import RosterWindow
|
||||||
|
|
||||||
|
@ -30,59 +29,6 @@ app.interface = MockInterface()
|
||||||
# name to use for the test account
|
# name to use for the test account
|
||||||
account_name = account1
|
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):
|
class TestChatControlSession(unittest.TestCase):
|
||||||
''' Testclass for session.py '''
|
''' Testclass for session.py '''
|
||||||
|
|
|
@ -91,7 +91,6 @@ function install_deps {
|
||||||
|
|
||||||
PIP_REQUIREMENTS="\
|
PIP_REQUIREMENTS="\
|
||||||
git+https://dev.gajim.org/gajim/python-nbxmpp.git
|
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
|
git+https://dev.gajim.org/lovetox/pybonjour-python3.git
|
||||||
keyring
|
keyring
|
||||||
python-gnupg
|
python-gnupg
|
||||||
|
|
|
@ -23,7 +23,6 @@ certifi
|
||||||
git+https://dev.gajim.org/gajim/python-nbxmpp.git
|
git+https://dev.gajim.org/gajim/python-nbxmpp.git
|
||||||
git+https://dev.gajim.org/lovetox/pybonjour-python3.git
|
git+https://dev.gajim.org/lovetox/pybonjour-python3.git
|
||||||
protobuf
|
protobuf
|
||||||
git+https://github.com/dlitz/pycrypto.git
|
|
||||||
cryptography
|
cryptography
|
||||||
pyopenssl
|
pyopenssl
|
||||||
python-gnupg
|
python-gnupg
|
||||||
|
|
Loading…
Reference in New Issue