Gajim 1.3 support [WIP]

dev
Bohdan Horbeshko 2 years ago
parent 9cbc6991ae
commit 1c71637adc

@ -1,9 +1,9 @@
[info]
name: otrplugin
short_name: otrplugin
version: 0.3.2
version: 0.4
description: Off-the-Record encryption
authors: Pavel R <pd@narayana.im>
homepage: https://dev.narayana.im/gajim-otrplugin
min_gajim_version: 1.0.3
max_gajim_version: 1.1.99
authors: Pavel R <pd@narayana.im>, Bohdan Horbeshko <bodqhrohro@gmail.com>
homepage: https://dev.narayana.im/narayana/gajim-otrplugin
min_gajim_version: 1.3.0
max_gajim_version: 1.3.99

@ -0,0 +1,63 @@
## Copyright (C) 2008-2012 Kjell Braden <afflux@pentabarf.de>
## Copyright (C) 2019 Pavel R. <pd at narayana dot im>
## Copyright (C) 2022 Bohdan Horbeshko <bodqhrohro@gmail.com>
# This file is part of Gajim OTR Plugin.
#
# Gajim OTR Plugin 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.
#
# This software 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 can always obtain full license text at <http://www.gnu.org/licenses/>.
from nbxmpp.structs import StanzaHandler
from gajim.common.modules.base import BaseModule
from gajim.common import app
from .otr import OTR as OTRInstance
name = 'OTR'
zeroconf = False
class OTR(BaseModule):
_nbxmpp_extends = 'OTR'
def __init__(self, con):
BaseModule.__init__(self, con, plugin=True)
self.handlers = [
StanzaHandler(name='message',
callback=self._message_received,
priority=9),
]
self.available = True
self.otr = OTRInstance(con.name)
def activate(self):
""" Method called when the Plugin is activated in the PluginManager
"""
pass
def deactivate(self):
""" Method called when the Plugin is deactivated in the PluginManager
"""
pass
def _message_received(self, client, stanza, properties):
if properties.is_omemo or properties.is_openpgp or properties.is_pgp_legacy: return # skip other encryptions
if properties.from_muc: return # skip MUC messages
msgtxt = stanza.getBody() or ''
# if (event.encrypted) or (event.name[0:2] == 'gc') or not (event.msgtxt or '').startswith('?OTR'): return # skip non-OTR messages..
if not msgtxt.startswith('?OTR'): return # skip non-OTR messages..
if properties.mam != None: return stanza.setBody('') # skip MAM messages (we can not decrypt OTR out of session)..
if (app.settings.get_contact_setting(self._account,properties.jid.bare,'encryption')!=self._nbxmpp_extends): return # skip all when encryption not set to OTR
self.otr.decrypt(stanza,properties)
def get_instance(*args, **kwargs):
return OTR(*args, **kwargs), 'OTR'

@ -15,9 +15,12 @@
#
# You can always obtain full license text at <http://www.gnu.org/licenses/>.
import os
import string
import random
import itertools
import logging
from inspect import signature
from gajim.common import const, app, helpers, configpaths
from gajim.session import ChatControlSession
from nbxmpp.protocol import Message, JID
from potr import context, crypt, proto
from .keystore import Keystore
@ -28,7 +31,7 @@ class OTRChannel(context.Context):
# this method may be called self.sendMessage() when we need to send some data to our <peer> via XMPP
def inject(self,msg,appdata=None):
stanza = Message(to=self.peer, body=msg.decode(), typ='chat')
stanza.setThread(appdata or ChatControlSession.generate_thread_id(None))
stanza.setThread(appdata or self.generateThreadId())
self.user.stream.send_stanza(stanza)
# this method called on channel state change
@ -46,6 +49,13 @@ class OTRChannel(context.Context):
@staticmethod
def getPolicy(policy): return OTR.DEFAULT_POLICY.get(policy)
@staticmethod
def generateThreadId():
return ''.join(
[f(string.ascii_letters) for f in itertools.repeat(
random.choice, 32)]
)
# OTR instance for Gajim user (Alice)
class OTR(context.Account):
PROTO = ('XMPP', 1024)
@ -69,10 +79,9 @@ class OTR(context.Account):
crypt.InvalidParameterError: "unable to decrypt message (key/signature mismatch)",
}
def __init__(self,plugin,account):
def __init__(self,account):
super(OTR,self).__init__(account,*OTR.PROTO)
self.plugin = plugin
self.log = plugin.log
self.log = logging.getLogger('gajim.p.otr.otr')
self.account = account
self.stream = app.connections[account]
self.jid = self.stream.get_own_jid()
@ -109,19 +118,18 @@ class OTR(context.Account):
for fingerprint,trust in fingerprints.items(): self.keystore.save(jid=peer,fingerprint=fingerprint,trust=trust)
# decrypt message
def decrypt(self,event,callback):
peer = event.stanza.getFrom()
def decrypt(self,stanza,properties):
peer = stanza.getFrom()
msgtxt = stanza.getBody()
channel, ctl = self.getContext(peer), self.getControl(peer)
try:
text, tlvs = channel.receiveMessage(event.msgtxt.encode(),appdata=event.stanza.getThread()) or b''
text, tlvs = channel.receiveMessage(msgtxt.encode(),appdata=stanza.getThread()) or b''
except (context.UnencryptedMessage,context.NotEncryptedError,context.ErrorReceived,crypt.InvalidParameterError) as e:
self.log.error("** got exception while decrypting message: %s" % e)
channel.printl(OTR.STATUS[e].format(msg=event.msgtxt,err=e.args[0].error))
channel.printl(OTR.STATUS[e].format(msg=msgtxt,err=e.args[0].error))
else:
event.msgtxt = text and text.decode() or ""
event.encrypted = OTR.ENCRYPTION_NAME
event.additional_data["encrypted"] = {"name":OTR.ENCRYPTION_NAME}
callback(event)
event.setBody(text and text.decode() or "")
properties.encrypted = OTR.ENCRYPTION_NAME
finally:
if channel.mayRetransmit and channel.state and ctl: channel.mayRetransmit = ctl.send_message(channel.lastMessage.decode())

@ -1,5 +1,6 @@
# Copyright (C) 2019 Philipp Hörist <philipp AT hoerist.com>
# Copyright (C) 2019 Pavel R. <pd at narayana dot im>
# Copyright (C) 2022 Bohdan Horbeshko <bodqhrohro@gmail.com>
# This file is part of Gajim OTR Plugin.
#
@ -21,8 +22,20 @@ ERROR = None
import logging
from gajim.plugins import GajimPlugin
from gajim.common import app
log = logging.getLogger('gajim.p.otr')
try: from .otr import *
except ImportError: ERROR = 'python3-potr is missing'
except ImportError as e:
log.error(e)
ERROR = 'python3-potr is missing'
if not ERROR:
try:
from . import module
except Exception as error:
log.error(error)
ERROR = 'Error: %s' % error
# ...
class OTRPlugin(GajimPlugin):
@ -30,17 +43,23 @@ class OTRPlugin(GajimPlugin):
# init plugin #
def init(self):
self.activatable = (not ERROR)
if ERROR:
self.available_text = (ERROR)
return
self.encryption_name = 'OTR'
self.description = 'Provides Off-the-Record encryption'
self.activatable = (not ERROR)
self.available_text = (ERROR)
self.instances = {}
self.modules = [module]
self.getinstance = lambda acct: self.instances.get(acct) or self.instances.setdefault(acct,OTR(self,acct))
self.gui_extension_points = {
'encrypt' + self.encryption_name: (self._encrypt_message, None),
'decrypt': (self._decrypt_message, None),
}
@staticmethod
def get_otr(account):
return app.connections[account].get_module('OTR')
# activate encryption #
@staticmethod
def activate_encryption(ctl):
@ -54,13 +73,5 @@ class OTRPlugin(GajimPlugin):
# encrypt message #
def _encrypt_message(self,con,event,callback):
if not event.message or event.type_ != 'chat': return # drop empty and non-chat messages
otr = self.getinstance(event.conn.name)
otr.encrypt(event,callback)
# decrypt message #
def _decrypt_message(self,con,event,callback):
if (event.encrypted) or (event.name[0:2] == 'gc') or not (event.msgtxt or '').startswith('?OTR'): return # skip non-OTR messages..
if (event.name[0:3] == 'mam'): return setattr(event,'msgtxt','') # skip MAM messages (we can not decrypt OTR out of session)..
if (app.config.get_per('encryption','%s-%s'%(event.conn.name,event.jid),'encryption')!=self.encryption_name): return # skip all when encryption not set to OTR
otr = self.getinstance(event.conn.name)
otr.decrypt(event,callback)
otr = self.get_otr(event.account)
otr.otr.encrypt(event,callback)

Loading…
Cancel
Save