Gajim 1.3 support [WIP]
This commit is contained in:
parent
9cbc6991ae
commit
1c71637adc
10
manifest.ini
10
manifest.ini
|
@ -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
|
||||
|
|
63
module.py
Normal file
63
module.py
Normal file
|
@ -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'
|
34
otr.py
34
otr.py
|
@ -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())
|
||||
|
||||
|
|
39
plugin.py
39
plugin.py
|
@ -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…
Reference in a new issue