...
 
Commits (11)
[info]
name: Acronyms Expander
short_name: acronyms_expander
version: 0.4
version: 1.0.0
description: Replaces acronyms (or other strings) with given expansions/substitutes.
authors: Mateusz Biliński <mateusz@bilinski.it>
homepage: https://dev.gajim.org/gajim/gajim-plugins/wikis/AcronymsExpanderPlugin
min_gajim_version: 1.0.0
max_gajim_version: 1.0.90
[info]
name: Anti Spam
short_name: anti_spam
version: 1.4.5
version: 1.4.6
description: Block some incoming messages.
authors = Yann Leboulanger <asterix@lagaule.org>
authors: Yann Leboulanger <asterix@lagaule.org>
Denis Fomin <fominde@gmail.com>
Ilya Kanyukov <ilya.kanukov@gmail.com>
homepage = https://dev.gajim.org/gajim/gajim-plugins/wikis/AntiSpamPlugin
min_gajim_version: 0.16.11
homepage: https://dev.gajim.org/gajim/gajim-plugins/wikis/AntiSpamPlugin
min_gajim_version: 1.0.0
max_gajim_version: 1.0.90
[info]
name: Ayatana Appindicator integration
short_name: appindicator_integration
version: 1.1.1
version: 1.1.2
description: This plugin integrates Gajim with the Ayatana AppIndicator. You must have gir1.2-ayatanaappindicator3-0.1 installed to enable this plugin.
homepage: https://dev.gajim.org/gajim/gajim-plugins/wikis/AppindicatorSupportPlugin
authors: Denis Borenko <borenko@rambler.ru>
Philipp Hörist <philipp@hoerist.com>
min_gajim_version: 1.0.0
max_gajim_version: 1.0.90
[info]
name: Banner Tweaks
short_name: banner_tweaks
version: 0.1.4
version: 1.0.0
description: Allows user to tweak chat window banner appearance (eg. make it compact).
authors = Mateusz Biliński <mateusz@bilinski.it>
homepage = http://trac-plugins.gajim.org/wiki/BannerTweaksPlugin
min_gajim_version: 0.16.11
authors: Mateusz Biliński <mateusz@bilinski.it>
homepage: http://trac-plugins.gajim.org/wiki/BannerTweaksPlugin
min_gajim_version: 1.0.0
max_gajim_version: 1.0.90
[info]
name: Birthday reminder
short_name: birthday_reminder
version: 1.0.1
version: 1.0.0
description: Reminds you if a contact of yours has birthday
authors: Evgeniy Popov <evgeniypopov@gmail.com>
Philipp Hörist <philipp@hoerist.com>
homepage: https://dev.gajim.org/gajim/gajim-plugins/wikis/BirthdayReminderPlugin
min_gajim_version: 1.0.99
min_gajim_version: 1.0.0
max_gajim_version: 1.0.90
import os
import json
import glob
import datetime
import logging
from gi.repository import GLib
from xml.dom.minidom import *
from gi.repository import GObject
from gajim.plugins import GajimPlugin
from gajim.plugins.helpers import log_calls
from gajim.notify import popup
from gajim.common import configpaths
from gajim.common import app
from gajim.common import ged
# Since Gajim 1.1.0 _() has to be imported
try:
from gajim.common.i18n import _
except ImportError:
pass
log = logging.getLogger('gajim.plugin_system.birthday')
TITLE = _('%s has birthday today')
TEXT = _('Send him a message')
class BirthDayPlugin(GajimPlugin):
@log_calls('BirthDayPlugin')
def init(self):
self.config_dialog = None
self.description = ('Birthday reminder plugin')
self.events_handlers = {
'vcard-received': (ged.GUI2, self._vcard_received)}
self.timeout_id = None
self._timeout_id_start = None
'roster-received': (ged.GUI2, self.roster_received)}
configpath = configpaths.ConfigPaths()
cache_path = configpath.cache_root
self.vcard_path = os.path.join(cache_path, 'vcards') + os.sep
self.timeout_id = 0
self.showed_accounts = []
self._birthdays = {}
self._load_birthdays()
def check_birthdays(self, account=None):
def show_popup(account, jid):
contact_instances = app.contacts.get_contacts(account, jid)
contact = app.contacts.get_highest_prio_contact_from_contacts(
contact_instances)
if contact:
nick = GObject.markup_escape_text(contact.get_shown_name())
try:
image = os.path.dirname(__file__) + os.sep + \
'birthday_reminder_large.png'
except:
image = None
popup('Send message', contact.jid, account, type_='',
path_to_image=image, title=title, text=text + ' ' + nick)
def activate(self):
self._timeout_id_start = GLib.timeout_add_seconds(
5, self._check_birthdays_at_start)
self._timeout_id = GLib.timeout_add_seconds(
86400, self._check_birthdays)
accounts = app.contacts.get_accounts()
vcards = []
date_dict = {}
for jid in glob.glob(self.vcard_path + '*@*'):
if os.path.isfile(jid):
vcards.append(jid)
def deactivate(self):
if self._timeout_id is not None:
GLib.source_remove(self.timeout_id)
if self._timeout_id_start is not None:
GLib.source_remove(self._timeout_id_start)
def _load_birthdays(self):
path = os.path.join(configpaths.get('MY_DATA'), 'birthdays.json')
if os.path.exists(path):
with open(path, 'r', encoding='utf-8') as file:
content = file.read()
if content:
self._birthdays = json.loads(content)
def _store_birthdays(self):
path = os.path.join(configpaths.get('MY_DATA'), 'birthdays.json')
with open(path, 'w', encoding='utf-8') as file:
json.dump(self._birthdays, file)
def _vcard_received(self, event):
birthday = event.vcard_dict.get('BDAY')
if not birthday:
if event.jid in self._birthdays:
del self._birthdays[event.jid]
log.info('Received empty birthday: %s', event.jid)
else:
for xmldoc in vcards:
try:
year, month, day = birthday.split('-')
year = int(year)
month = int(month)
day = int(day)
except Exception:
log.warning('Invalid date: %s', birthday)
if event.jid in self._birthdays:
del self._birthdays[event.jid]
xml = parse(xmldoc)
except:
pass
else:
self._birthdays[event.jid] = (year, month, day)
log.info('Received birthday: %s %s',
event.jid, (year, month, day))
self._store_birthdays()
def _check_birthdays_at_start(self):
self._check_birthdays()
name = xml.getElementsByTagName('BDAY')
for node in name:
try:
data = node.childNodes[0].nodeValue
date_dict[xmldoc[len(self.vcard_path):][:-1]] = data
except:
pass
def _check_birthdays(self):
log.info('Check birthdays...')
today = datetime.date.today()
for jid, birthdate in self._birthdays.items():
year, month, day = birthdate
if today.month == month and today.day == day:
account, contact = self._find_contact(jid)
if contact is None:
if jid in self._birthdays:
del self._birthdays[jid]
self._store_birthdays()
continue
else:
log.info('Issue notification for %s', jid)
nick = contact.get_shown_name() or jid
app.notification.popup(
'reminder',
jid,
account,
icon_name='trophy-gold',
title=TITLE % GLib.markup_escape_text(nick),
text=TEXT)
for key, value in date_dict.items():
try:
convert_date = datetime.datetime.strptime(value, "%Y-%m-%d")
user_bday = datetime.date(today.year, convert_date.month,
convert_date.day)
except:
continue
if user_bday < today:
user_bday = user_bday.replace(year=today.year+1)
time_to_bday = abs(user_bday - today)
title = "BirthDay Reminder"
text = None
if time_to_bday.days > 5:
continue
if time_to_bday.days == 5:
text = "5 days before BDay"
elif time_to_bday.days == 3:
text = "3 days before BDay"
elif time_to_bday.days == 1:
text = "Tomorrow BDay"
elif time_to_bday.days == 0:
text = "Today BDay"
if not text:
continue
if account:
show_popup(account,key)
else:
for acct in accounts:
show_popup(account, key)
return True
def _find_contact(self, jid):
accounts = app.contacts.get_accounts()
for account in accounts:
contact = app.contacts.get_contacts(account, jid)
if contact is not None:
return account, contact[0]
@log_calls('BirthDayPlugin')
def activate(self):
self.timeout_id = GObject.timeout_add_seconds(24*3600,
self.check_birthdays)
@log_calls('BirthDayPlugin')
def deactivate(self):
if self.timeout_id > 0:
GObject.source_remove(self.timeout_id)
@log_calls('BirthDayPlugin')
def roster_received(self, obj):
if obj.conn.name not in self.showed_accounts:
self.check_birthdays(obj.conn.name)
self.showed_accounts.append(obj.conn.name)
[info]
name: Chatstate in roster
short_name: chatstate
version: 0.5.6
version: 1.0.0
description: Chat State Notifications in roster.
Font color of the contact varies depending on the chat state.
The plugin does not work if you use custom font color for contacts in roster.
authors = Denis Fomin <fominde@gmail.com>
homepage = https://dev.gajim.org/gajim/gajim-plugins/wikis/ChatstatePlugin
authors: Denis Fomin <fominde@gmail.com>
homepage: https://dev.gajim.org/gajim/gajim-plugins/wikis/ChatstatePlugin
min_gajim_version: 1.0.0
max_gajim_version: 1.0.3
max_gajim_version: 1.0.90
[info]
name: Clickable Nicknames
short_name: clickable_nicknames
version: 0.7
version: 1.0.0
description: Clickable nicknames in the conversation textview.
authors: Andrey Musikhin <melomansegfault@gmail.com>
Denis Fomin <fominde@gmail.com>
homepage: http://trac-plugins.gajim.org/wiki/ClickableNicknamesPlugin
min_gajim_version: 1.0.0
max_gajim_version: 1.0.90
[info]
name: Clients icons
short_name: clients_icons
version: 7.3
version: 7.3.1
description: Shows client icons in roster and in groupchats.
authors: Denis Fomin <fominde@gmail.com>
Artem Klyop <art.klyop@gmail.com>
Thilo Molitor <thilo@eightysoft.de>
homepage: https://dev.gajim.org/gajim/gajim-plugins/wikis/ClientsIconsPlugin
min_gajim_version: 1.0.0
max_gajim_version: 1.0.90
[info]
name: Flashing Keyboard
short_name: flashing_keyboard
version: 0.5
version: 1.0.0
description: Flashing keyboard led when there are unread messages.
authors: Denis Fomin <fominde@gmail.com>
homepage: https://dev.gajim.org/gajim/gajim-plugins/wikis/flashingkeyboardplugin
min_gajim_version: 1.0.0
max_gajim_version: 1.0.90
[info]
name: GUI For Me
short_name: gui_for_me
version: 0.5
version: 1.0.0
description: Adds a button for the '/me' command.
authors: BomberMan
copper
Denis Fomin <fominde@gmail.com>
homepage: http://trac-plugins.gajim.org/wiki/GUIForMePlugin
min_gajim_version: 1.0.0
max_gajim_version: 1.0.90
[info]
name: Hamster integration
short_name: hamster_integration
version: 0.1.5
version: 1.0.0
description: Integration with project hamster
see https://trac.gajim.org/ticket/6993
and http://projecthamster.wordpress.com/about/
authors: Denis Fomin <fominde@gmail.com>
homepage: http://trac-plugins.gajim.org/wiki/
min_gajim_version: 1.0.0
max_gajim_version: 1.0.90
[info]
name: Image
short_name: image
version: 1.1
version: 1.1.1
description: This plugin is designed to send a small(0 - 40 kb) graphic image to your contact.
Client on the other side must support XEP-0071: XHTML-IM and maintain the scheme data: URI.
Psi+ and Jabbim supported this.
authors: Denis Fomin <fominde@gmail.com>
homepage: http://trac-plugins.gajim.org/wiki/ImagePlugin
min_gajim_version: 1.0.0
max_gajim_version: 1.0.90
[info]
name: Juick
short_name: Juick
version: 0.9.9
version: 1.0.0
description: Clickable Juick links , Juick nicks, preview Juick picturs.
The key combination alt + up in the textbox allow insert the number of last message (comment or topic).
authors: Denis Fomin <fominde@gmail.com>
evgen <drujebober@gmail.com>
homepage: http://trac-plugins.gajim.org/wiki/JuickPlugin
min_gajim_version: 1.0.0
max_gajim_version: 1.0.90
[info]
name: Latex
short_name: latex
version: 0.4.0
version: 1.0.0
description: render received latex code
authors: Yves Fischer <yvesf@xapek.org>
Yann Leboulanger <asterix@lagaule.org>
homepage: http://trac-plugins.gajim.org/wiki/LatexPlugin
min_gajim_version: 1.0.0
max_gajim_version: 1.0.3
max_gajim_version: 1.0.90
[info]
name: Message Length Notifier
short_name: length_notifier
version: 0.5
version: 1.0.0
description: Highlights message entry field in chat window when given length of message is exceeded.
authors = Mateusz Biliński <mateusz@bilinski.it>
homepage = http://trac-plugins.gajim.org/wiki/LengthNotifierPlugin
authors: Mateusz Biliński <mateusz@bilinski.it>
homepage: http://trac-plugins.gajim.org/wiki/LengthNotifierPlugin
min_gajim_version: 1.0.0
max_gajim_version: 1.0.90
[info]
name: Message Box Size
short_name: message_box_size
version: 0.5
version: 1.0.0
description: Allows you to adjust the height of the new message input field.
authors: Denis Fomin <fominde@gmail.com>
homepage: http://trac-plugins.gajim.org/wiki/MessageBoxSizePlugin
min_gajim_version: 1.0.0
max_gajim_version: 1.0.90
[info]
name: Now Listen
short_name: now-listen
version: 0.3.3
version: 1.0.0
description: Copy tune info of playing music to conversation input box at cursor position (Alt + N)
authors = Denis Fomin <fominde@gmail.com>
homepage = https://dev.gajim.org/gajim/gajim-plugins/wikis/NowListenPlugin
min_gajim_version: 1.0.0
max_gajim_version: 1.0.90
2.6.3 / 2019-05-21
- Keep compatibility with python-axolotl
2.6.2 / 2019-10-13
- Raise Version
2.6.1 / 2018-08-08
- Save encryption details to the database
- Bugfixes
......
[info]
name: OMEMO
short_name: omemo
version: 2.6.1
version: 2.6.3
description: OMEMO is an XMPP Extension Protocol (XEP) for secure multi-client end-to-end encryption based on Axolotl and PEP. You need to install some dependencies, you can find install instructions for your system in the Github Wiki.
authors: Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
Daniel Gultsch <daniel@gultsch.de>
Philipp Hörist <philipp@hoerist.com>
homepage: https://dev.gajim.org/gajim/gajim-plugins/wikis/OmemoGajimPlugin
min_gajim_version: 1.0.0
max_gajim_version: 1.0.90
......@@ -59,7 +59,7 @@ class LiteAxolotlStore(AxolotlStore):
def _generate_axolotl_keys(self):
identityKeyPair = KeyHelper.generateIdentityKeyPair()
registrationId = KeyHelper.generateRegistrationId()
preKeys = KeyHelper.generatePreKeys(KeyHelper.getRandomSequence(),
preKeys = KeyHelper.generatePreKeys(KeyHelper.getRandomSequence(4294967296),
DEFAULT_PREKEY_AMOUNT)
self.storeLocalData(registrationId, identityKeyPair)
......
from .pgpplugin import OpenPGPPlugin
# Copyright (C) 2018 Philipp Hörist <philipp AT hoerist.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/>.
# XEP-0373: OpenPGP for XMPP
import io
from collections import namedtuple
import logging
import gpg
from gajim.common import app
KeyringItem = namedtuple('KeyringItem',
'type keyid userid fingerprint')
log = logging.getLogger('gajim.plugin_system.openpgp.pgpme')
class PGPContext():
def __init__(self, jid, gnuhome):
self.context = gpg.Context(home_dir=str(gnuhome))
# self.create_new_key()
# self.get_key_by_name()
# self.get_key_by_fingerprint()
self.export_public_key()
def create_new_key(self):
parms = """<GnupgKeyParms format="internal">
Key-Type: RSA
Key-Length: 2048
Subkey-Type: RSA
Subkey-Length: 2048
Name-Real: Joe Tester
Name-Comment: with stupid passphrase
Name-Email: test@example.org
Passphrase: Crypt0R0cks
Expire-Date: 2020-12-31
</GnupgKeyParms>
"""
with self.context as c:
c.set_engine_info(gpg.constants.PROTOCOL_OpenPGP, None, app.gajimpaths['MY_DATA'])
c.set_progress_cb(gpg.callbacks.progress_stdout)
c.op_genkey(parms, None, None)
print("Generated key with fingerprint {0}.".format(
c.op_genkey_result().fpr))
def get_all_keys(self):
c = gpg.Context()
for key in c.keylist():
user = key.uids[0]
print("Keys for %s (%s):" % (user.name, user.email))
for subkey in key.subkeys:
features = []
if subkey.can_authenticate:
features.append('auth')
if subkey.can_certify:
features.append('cert')
if subkey.can_encrypt:
features.append('encrypt')
if subkey.can_sign:
features.append('sign')
print(' %s %s' % (subkey.fpr, ','.join(features)))
def get_key_by_name(self):
c = gpg.Context()
for key in c.keylist('john'):
print(key.subkeys[0].fpr)
def get_key_by_fingerprint(self):
c = gpg.Context()
fingerprint = 'key fingerprint to search for'
try:
key = c.get_key(fingerprint)
print('%s (%s)' % (key.uids[0].name, key.uids[0].email))
except gpg.errors.KeyNotFound:
print("No key for fingerprint '%s'." % fingerprint)
def get_secret_key(self):
'''
Key(can_authenticate=1,
can_certify=1,
can_encrypt=1,
can_sign=1,
chain_id=None,
disabled=0,
expired=0,
fpr='7ECE1F88BAFCA37F168E1556A4DBDD1BA55FE3CE',
invalid=0,
is_qualified=0,
issuer_name=None,
issuer_serial=None,
keylist_mode=1,
last_update=0,
origin=0,
owner_trust=5,
protocol=0,
revoked=0,
secret=1,
subkeys=[
SubKey(can_authenticate=1,
can_certify=1,
can_encrypt=1,
can_sign=1,
card_number=None
curve=None,
disabled=0,
expired=0,
expires=0,
fpr='7ECE1F88BAFCA37F168E1556A4DBDD1BA55FE3CE',
invalid=0,
is_cardkey=0,
is_de_vs=1,
is_qualified=0,
keygrip='15BECB77301E4810ABB9CA6A9391158E575DABEC',
keyid='A4DBDD1BA55FE3CE',
length=2048,
pubkey_algo=1,
revoked=0,
secret=1,
timestamp=1525006759)],
uids=[
UID(address=None,
comment='',
email='',
invalid=0,
last_update=0,
name='xmpp:philw@jabber.at',
origin=0,
revoked=0,
signatures=[],
tofu=[],
uid='xmpp:philw@jabber.at',
validity=5)])
'''
for key in self.context.keylist(secret=True):
break
return key.fpr, key.fpr[-16:]
def get_keys(self, secret=False):
keys = []
for key in self.context.keylist():
for uid in key.uids:
if uid.uid.startswith('xmpp:'):
keys.append((key, uid.uid[5:]))
break
return keys
def export_public_key(self):
# print(dir(self.context))
result = self.context.key_export_minimal()
print(result)
def encrypt_decrypt_files(self):
c = gpg.Context()
recipient = c.get_key("fingerprint of recipient's key")
# Encrypt
with open('foo.txt', 'r') as input_file:
with open('foo.txt.gpg', 'wb') as output_file:
c.encrypt([recipient], 0, input_file, output_file)
# Decrypt
with open('foo.txt.gpg', 'rb') as input_file:
with open('foo2.txt', 'w') as output_file:
c.decrypt(input_file, output_file)
def encrypt(self):
c = gpg.Context()
recipient = c.get_key("fingerprint of recipient's key")
plaintext_string = u'plain text data'
plaintext_bytes = io.BytesIO(plaintext_string.encode('utf8'))
encrypted_bytes = io.BytesIO()
c.encrypt([recipient], 0, plaintext_bytes, encrypted_bytes)
def decrypt(self):
c = gpg.Context()
decrypted_bytes = io.BytesIO()
c.decrypt(encrypted_bytes, decrypted_bytes)
decrypted_string = decrypted_bytes.getvalue().decode('utf8')
# Copyright (C) 2018 Philipp Hörist <philipp AT hoerist.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/>.
# XEP-0373: OpenPGP for XMPP
import os
import logging
from collections import namedtuple
import gnupg
from gajim.common import app
from openpgp.modules.util import DecryptionFailed
log = logging.getLogger('gajim.plugin_system.openpgp.pygnupg')
# gnupg.logger = log
KeyringItem = namedtuple('KeyringItem', 'jid keyid fingerprint')
class PGPContext(gnupg.GPG):
def __init__(self, jid, gnupghome):
gnupg.GPG.__init__(
self, gpgbinary=app.get_gpg_binary(), gnupghome=str(gnupghome))
self._passphrase = 'gajimopenpgppassphrase'
self._jid = jid
self._own_fingerprint = None
def _get_key_params(self, jid, passphrase):
'''
Generate --gen-key input
'''
params = {
'Key-Type': 'RSA',
'Key-Length': 2048,
'Name-Real': 'xmpp:%s' % jid,
'Passphrase': passphrase,
}
out = "Key-Type: %s\n" % params.pop('Key-Type')
for key, val in list(params.items()):
out += "%s: %s\n" % (key, val)
out += "%commit\n"
return out
def generate_key(self):
super().gen_key(self._get_key_params(self._jid, self._passphrase))
def encrypt(self, payload, keys):
recipients = [key.fingerprint for key in keys]
log.info('encrypt to:')
for fingerprint in recipients:
log.info(fingerprint)
result = super().encrypt(str(payload).encode('utf8'),
recipients,
armor=False,
sign=self._own_fingerprint,
always_trust=True,
passphrase=self._passphrase)
if result.ok:
error = ''
else:
error = result.status
return result.data, error
def decrypt(self, payload):
result = super().decrypt(payload,
always_trust=True,
passphrase=self._passphrase)
if not result.ok:
raise DecryptionFailed(result.status)
return result.data.decode('utf8'), result.fingerprint
def get_key(self, fingerprint):
return super().list_keys(keys=[fingerprint])
def get_keys(self, secret=False):
result = super().list_keys(secret=secret)
keys = []
for key in result:
item = self._make_keyring_item(key)
if item is None:
continue
keys.append(self._make_keyring_item(key))
return keys
@staticmethod
def _make_keyring_item(key):
userid = key['uids'][0]
if not userid.startswith('xmpp:'):
log.warning('Incorrect userid: %s found for key, '
'key will be ignored', userid)
return
jid = userid[5:]
return KeyringItem(jid, key['keyid'], key['fingerprint'])
def import_key(self, data, jid):
log.info('Import key from %s', jid)
result = super().import_keys(data)
if not result:
log.error('Could not import key')
log.error(result.results[0])
return
if not self.validate_key(data, jid):
return None
key = self.get_key(result.results[0]['fingerprint'])
return self._make_keyring_item(key[0])
def validate_key(self, public_key, jid):
import tempfile
temppath = os.path.join(tempfile.gettempdir(), 'temp_pubkey')
with open(temppath, 'wb') as tempfile:
tempfile.write(public_key)
result = self.scan_keys(temppath)
if result:
for uid in result.uids:
if uid.startswith('xmpp:'):
if uid[5:] == jid:
key_found = True
else:
log.warning('Found wrong userid in key: %s != %s',
uid[5:], jid)
log.debug(result)
os.remove(temppath)
return False
if not key_found:
log.warning('No valid userid found in key')
log.debug(result)
os.remove(temppath)
return False
log.info('Key validation succesful')
os.remove(temppath)
return True
log.warning('Invalid key data: %s')
log.debug(result)
os.remove(temppath)
return False
def get_own_key_details(self):
result = super().list_keys(secret=True)
if not result:
return None, None
if len(result) > 1:
log.error('More than one secret key found')
return None, None
self._own_fingerprint = result[0]['fingerprint']
return self._own_fingerprint, int(result[0]['date'])
def export_key(self, fingerprint):
key = super().export_keys(
fingerprint, secret=False, armor=False, minimal=False,
passphrase=self._passphrase)
return key
def delete_key(self, fingerprint):
log.info('Delete Key: %s', fingerprint)
super().delete_keys(fingerprint, passphrase=self._passphrase)
# Copyright (C) 2018 Philipp Hörist <philipp AT hoerist.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/>.
# XEP-0373: OpenPGP for XMPP
import sqlite3
import logging
from collections import namedtuple
log = logging.getLogger('gajim.plugin_system.openpgp.sql')
TABLE_LAYOUT = '''
CREATE TABLE contacts (
jid TEXT,
fingerprint TEXT,
active BOOLEAN,
trust INTEGER,
timestamp INTEGER,
comment TEXT
);
CREATE UNIQUE INDEX jid_fingerprint ON contacts (jid, fingerprint);'''
class Storage:
def __init__(self, folder_path):
self._con = sqlite3.connect(str(folder_path / 'contacts.db'),
detect_types=sqlite3.PARSE_DECLTYPES)
self._con.row_factory = self._namedtuple_factory
self._create_database()
self._migrate_database()
self._con.execute("PRAGMA synchronous=FULL;")
self._con.commit()
@staticmethod
def _namedtuple_factory(cursor, row):
fields = [col[0] for col in cursor.description]
Row = namedtuple("Row", fields)
named_row = Row(*row)
return named_row
def _user_version(self):
return self._con.execute('PRAGMA user_version').fetchone()[0]
def _create_database(self):
if not self._user_version():
log.info('Create contacts.db')
self._execute_query(TABLE_LAYOUT)
def _execute_query(self, query):
transaction = """
BEGIN TRANSACTION;
%s
PRAGMA user_version=1;
END TRANSACTION;
""" % (query)
self._con.executescript(transaction)
def _migrate_database(self):
pass
def load_contacts(self):
sql = 'SELECT * from contacts'
rows = self._con.execute(sql).fetchall()
if rows is not None:
return rows
def save_contact(self, db_values):
sql = '''REPLACE INTO
contacts(jid, fingerprint, active, trust, timestamp, comment)
VALUES(?, ?, ?, ?, ?, ?)'''
for values in db_values:
log.info('Store key: %s', values)
self._con.execute(sql, values)
self._con.commit()
def set_trust(self, jid, fingerprint, trust):
sql = 'UPDATE contacts SET trust = ? WHERE jid = ? AND fingerprint = ?'
log.info('Set Trust: %s %s %s', trust, jid, fingerprint)
self._con.execute(sql, (trust, jid, fingerprint))
self._con.commit()
def delete_key(self, jid, fingerprint):
sql = 'DELETE from contacts WHERE jid = ? AND fingerprint = ?'
log.info('Delete Key: %s %s', jid, fingerprint)
self._con.execute(sql, (jid, fingerprint))
self._con.commit()
def cleanup(self):
self._con.close()
# Copyright (C) 2018 Philipp Hörist <philipp AT hoerist.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/>.
# XEP-0373: OpenPGP for XMPP
import logging
import time
from gi.repository import Gtk
from gajim.common import app
from gajim.common.const import DialogButton, ButtonAction
from gajim.gtk import NewConfirmationDialog
from openpgp.modules.util import Trust
log = logging.getLogger('gajim.plugin_system.openpgp.keydialog')
TRUST_DATA = {
Trust.NOT_TRUSTED: ('dialog-error-symbolic',
_('Not Trusted'),
'error-color'),
Trust.UNKNOWN: ('security-low-symbolic',
_('Not Decided'),
'warning-color'),
Trust.BLIND: ('security-medium-symbolic',
_('Blind Trust'),
'openpgp-dark-success-color'),
Trust.VERIFIED: ('security-high-symbolic',
_('Verified'),
'success-color')
}
class KeyDialog(Gtk.Dialog):
def __init__(self, account, jid, transient):
flags = Gtk.DialogFlags.DESTROY_WITH_PARENT
super().__init__(_('Public Keys for %s') % jid, None, flags)
self.set_transient_for(transient)
self.set_resizable(True)
self.set_default_size(500, 300)
self.get_style_context().add_class('openpgp-key-dialog')
self.con = app.connections[account]
self._listbox = Gtk.ListBox()
self._listbox.set_selection_mode(Gtk.SelectionMode.NONE)
self._scrolled = Gtk.ScrolledWindow()
self._scrolled.set_policy(Gtk.PolicyType.NEVER,
Gtk.PolicyType.AUTOMATIC)
self._scrolled.add(self._listbox)
box = self.get_content_area()
box.pack_start(self._scrolled, True, True, 0)
keys = self.con.get_module('OpenPGP').get_keys(jid, only_trusted=False)
for key in keys:
log.info('Load: %s', key.fingerprint)
self._listbox.add(KeyRow(key))
self.show_all()
class KeyRow(Gtk.ListBoxRow):
def __init__(self, key):
Gtk.ListBoxRow.__init__(self)
self.set_activatable(False)
self._dialog = self.get_toplevel()
self.key = key
box = Gtk.Box()
box.set_spacing(12)
self._trust_button = TrustButton(self)
box.add(self._trust_button)
label_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
fingerprint = Gtk.Label(
label=self._format_fingerprint(key.fingerprint))
fingerprint.get_style_context().add_class('openpgp-mono')
if not key.active:
fingerprint.get_style_context().add_class('openpgp-inactive-color')
fingerprint.set_selectable(True)
fingerprint.set_halign(Gtk.Align.START)
fingerprint.set_valign(Gtk.Align.START)
fingerprint.set_hexpand(True)
label_box.add(fingerprint)
date = Gtk.Label(label=self._format_timestamp(key.timestamp))
date.set_halign(Gtk.Align.START)
date.get_style_context().add_class('openpgp-mono')
if not key.active:
date.get_style_context().add_class('openpgp-inactive-color')
label_box.add(date)
box.add(label_box)
self.add(box)
self.show_all()
def delete_fingerprint(self, *args):
def _remove():
self.get_parent().remove(self)
self.key.delete()
self.destroy()
buttons = {
Gtk.ResponseType.CANCEL: DialogButton('Cancel'),
Gtk.ResponseType.OK: DialogButton('Delete',
_remove,
ButtonAction.DESTRUCTIVE),
}
NewConfirmationDialog(
_('Delete Public Key'),
_('This will permanently delete this public key'),
buttons,
transient_for=self.get_toplevel())
def set_trust(self, trust):
icon_name, tooltip, css_class = TRUST_DATA[trust]
image = self._trust_button.get_child()
image.set_from_icon_name(icon_name, Gtk.IconSize.MENU)
image.get_style_context().add_class(css_class)
@staticmethod
def _format_fingerprint(fingerprint):
fplen = len(fingerprint)
wordsize = fplen // 8
buf = ''
for w in range(0, fplen, wordsize):
buf += '{0} '.format(fingerprint[w:w + wordsize])
return buf.rstrip()
@staticmethod
def _format_timestamp(timestamp):
return time.strftime('%Y-%m-%d %H:%M:%S',
time.localtime(timestamp))
class TrustButton(Gtk.MenuButton):
def __init__(self, row):
Gtk.MenuButton.__init__(self)
self._row = row
self._css_class = ''
self.set_popover(TrustPopver(row))
self.update()
def update(self):
icon_name, tooltip, css_class = TRUST_DATA[self._row.key.trust]
image = self.get_child()
image.set_from_icon_name(icon_name, Gtk.IconSize.MENU)
# remove old color from icon
image.get_style_context().remove_class(self._css_class)
if not self._row.key.active:
css_class = 'openpgp-inactive-color'
tooltip = '%s - %s' % (_('Inactive'), tooltip)
image.get_style_context().add_class(css_class)
self._css_class = css_class
self.set_tooltip_text(tooltip)
class TrustPopver(Gtk.Popover):
def __init__(self, row):
Gtk.Popover.__init__(self)
self._row = row
self._listbox = Gtk.ListBox()
self._listbox.set_selection_mode(Gtk.SelectionMode.NONE)
if row.key.trust != Trust.VERIFIED:
self._listbox.add(VerifiedOption())
if row.key.trust != Trust.NOT_TRUSTED:
self._listbox.add(NotTrustedOption())
self._listbox.add(DeleteOption())
self.add(self._listbox)
self._listbox.show_all()
self._listbox.connect('row-activated', self._activated)
self.get_style_context().add_class('openpgp-trust-popover')
def _activated(self, listbox, row):
self.popdown()
if row.type_ is None:
self._row.delete_fingerprint()
else:
self._row.key.trust = row.type_
self.get_relative_to().update()
self.update()
def update(self):
self._listbox.foreach(lambda row: self._listbox.remove(row))
if self._row.key.trust != Trust.VERIFIED:
self._listbox.add(VerifiedOption())
if self._row.key.trust != Trust.NOT_TRUSTED:
self._listbox.add(NotTrustedOption())
self._listbox.add(DeleteOption())
class MenuOption(Gtk.ListBoxRow):
def __init__(self):
Gtk.ListBoxRow.__init__(self)
box = Gtk.Box()
box.set_spacing(6)
image = Gtk.Image.new_from_icon_name(self.icon,
Gtk.IconSize.MENU)
label = Gtk.Label(label=self.label)
image.get_style_context().add_class(self.color)
box.add(image)
box.add(label)
self.add(box)
self.show_all()
class VerifiedOption(MenuOption):
type_ = Trust.VERIFIED
icon = 'security-high-symbolic'
label = _('Verified')
color = 'success-color'
def __init__(self):
MenuOption.__init__(self)
class NotTrustedOption(MenuOption):
type_ = Trust.NOT_TRUSTED
icon = 'dialog-error-symbolic'
label = _('Not Trusted')
color = 'error-color'
def __init__(self):
MenuOption.__init__(self)
class DeleteOption(MenuOption):
type_ = None
icon = 'user-trash-symbolic'
label = _('Delete')
color = ''
def __init__(self):
MenuOption.__init__(self)
.openpgp-dark-success-color { color: darker(@success_color); }
.openpgp-inactive-color { color: @unfocused_borders; }
.openpgp-mono { font-size: 12px; font-family: monospace; }
.openpgp-key-dialog > box { margin: 12px; }
.openpgp-key-dialog scrolledwindow row {
border-bottom: 1px solid;
border-color: @unfocused_borders;
padding: 10px 20px 10px 10px;
}
.openpgp-key-dialog scrolledwindow row:last-child { border-bottom: 0px}
.openpgp-key-dialog scrolledwindow { border: 1px solid; border-color:@unfocused_borders; }
.openpgp-trust-popover row { padding: 10px 15px 10px 10px; }
# Copyright (C) 2018 Philipp Hörist <philipp AT hoerist.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/>.
# XEP-0373: OpenPGP for XMPP
import logging
import threading
from enum import IntEnum
from gi.repository import Gtk
from gi.repository import GLib
from gajim.common import app
log = logging.getLogger('gajim.plugin_system.openpgp.wizard')
class Page(IntEnum):
WELCOME = 0
NEWKEY = 1
SUCCESS = 2
ERROR = 3
class KeyWizard(Gtk.Assistant):
def __init__(self, plugin, account, chat_control):
Gtk.Assistant.__init__(self)
self._con = app.connections[account]
self._plugin = plugin
self._account = account
self._data_form_widget = None
self._is_form = None
self._chat_control = chat_control
self.set_application(app.app)
self.set_transient_for(chat_control.parent_win.window)
self.set_resizable(True)
self.set_position(Gtk.WindowPosition.CENTER)
self.set_default_size(600, 400)
self.get_style_context().add_class('dialog-margin')
self._add_page(WelcomePage())
# self._add_page(BackupKeyPage())
self._add_page(NewKeyPage(self, self._con))
# self._add_page(SaveBackupCodePage())
self._add_page(SuccessfulPage())
self._add_page(ErrorPage())
self.connect('prepare', self._on_page_change)
self.connect('cancel', self._on_cancel)
self.connect('close', self._on_cancel)
self._remove_sidebar()
self.show_all()
def _add_page(self, page):
self.append_page(page)
self.set_page_type(page, page.type_)
self.set_page_title(page, page.title)
self.set_page_complete(page, page.complete)
def _remove_sidebar(self):
main_box = self.get_children()[0]
sidebar = main_box.get_children()[0]
main_box.remove(sidebar)
def _activate_encryption(self):
win = self._chat_control.parent_win.window
action = win.lookup_action(
'set-encryption-%s' % self._chat_control.control_id)
action.activate(GLib.Variant("s", self._plugin.encryption_name))
def _on_page_change(self, assistant, page):
if self.get_current_page() == Page.NEWKEY:
if self._con.get_module('OpenPGP').secret_key_available:
self.set_current_page(Page.SUCCESS)
else:
page.generate()
elif self.get_current_page() == Page.SUCCESS:
self._activate_encryption()
def _on_error(self, error_text):
log.info('Show Error page')
page = self.get_nth_page(Page.ERROR)
page.set_text(error_text)
self.set_current_page(Page.ERROR)
def _on_cancel(self, widget):
self.destroy()
class WelcomePage(Gtk.Box):
type_ = Gtk.AssistantPageType.INTRO
title = _('Welcome')
complete = True
def __init__(self):
super().__init__(orientation=Gtk.Orientation.VERTICAL)
self.set_spacing(18)
title_label = Gtk.Label(label=_('Setup OpenPGP'))
text_label = Gtk.Label(
label=_('Gajim will now try to setup OpenPGP for you'))
self.add(title_label)
self.add(text_label)
class RequestPage(Gtk.Box):
type_ = Gtk.AssistantPageType.INTRO
title = _('Request OpenPGP Key')
complete = False
def __init__(self):
super().__init__(orientation=Gtk.Orientation.VERTICAL)
self.set_spacing(18)
spinner = Gtk.Spinner()
self.pack_start(spinner, True, True, 0)
spinner.start()
# class BackupKeyPage(Gtk.Box):
# type_ = Gtk.AssistantPageType.INTRO
# title = _('Supply Backup Code')
# complete = True
# def __init__(self):
# super().__init__(orientation=Gtk.Orientation.VERTICAL)
# self.set_spacing(18)
# title_label = Gtk.Label(label=_('Backup Code'))
# text_label = Gtk.Label(
# label=_('We found a backup Code, please supply your password'))
# self.add(title_label)
# self.add(text_label)
# entry = Gtk.Entry()
# self.add(entry)
class NewKeyPage(RequestPage):
type_ = Gtk.AssistantPageType.PROGRESS
title = _('Generating new Key')
complete = False
def __init__(self, assistant, con):
super().__init__()
self._assistant = assistant
self._con = con
def generate(self):
log.info('Creating Key')
thread = threading.Thread(target=self.worker)
thread.start()
def worker(self):
error = None
try:
self._con.get_module('OpenPGP').generate_key()
except Exception as e:
error = e
else:
self._con.get_module('OpenPGP').get_own_key_details()
self._con.get_module('OpenPGP').publish_key()
self._con.get_module('OpenPGP').query_key_list()
GLib.idle_add(self.finished, error)
def finished(self, error):
if error is None:
self._assistant.set_current_page(Page.SUCCESS)
else:
log.error(error)
self._assistant.set_current_page(Page.ERROR)
# class SaveBackupCodePage(RequestPage):
# type_ = Gtk.AssistantPageType.PROGRESS
# title = _('Save this code')
# complete = False
# def __init__(self):
# super().__init__(orientation=Gtk.Orientation.VERTICAL)
# self.set_spacing(18)
# title_label = Gtk.Label(label=_('Backup Code'))
# text_label = Gtk.Label(
# label=_('This is your backup code, you need it if you reinstall Gajim'))
# self.add(title_label)
# self.add(text_label)
class SuccessfulPage(Gtk.Box):
type_ = Gtk.AssistantPageType.SUMMARY
title = _('Setup successful')
complete = True
def __init__(self):
super().__init__(orientation=Gtk.Orientation.VERTICAL)
self.set_spacing(12)
self.set_homogeneous(True)
icon = Gtk.Image.new_from_icon_name('object-select-symbolic',
Gtk.IconSize.DIALOG)
icon.get_style_context().add_class('success-color')
icon.set_valign(Gtk.Align.END)
label = Gtk.Label(label=_('Setup successful'))
label.get_style_context().add_class('bold16')
label.set_valign(Gtk.Align.START)
self.add(icon)
self.add(label)
class ErrorPage(Gtk.Box):