Skip to content
Snippets Groups Projects
Commit 5982d828 authored by Bahtiar Gadimov's avatar Bahtiar Gadimov
Browse files

Merge branch 'omemo-0.9.5'

parents 9396ec09 6210cb6c
No related branches found
No related tags found
No related merge requests found
0.9.5 / 2016-10-10
- Add GroupChat BETA
- Add Option to delete Fingerprints
- Add Option to deactivate Accounts for OMEMO
0.9.0 / 2016-08-28
- Send INFO message to resources who dont support OMEMO
- Check dependencys and give correct error message
......
......@@ -57,6 +57,13 @@ you have to trust at least **one** fingerprint to send messages.
you can receive messages from fingerprints where you didnt made a trust decision, but you cant
receive Messages from *not trusted* fingerprints
## Filetransfer
For Filetransfer use the **httpupload** plugin.
For decrypting and showing pictures in chat use the **url_image_preview** plugin.
If you want to use these plugins together with *OMEMO* you have to install the `python-cryptography` package
## Debugging
To see OMEMO related debug output start Gajim with the parameter `-l
......
This diff is collapsed.
......@@ -4,7 +4,7 @@
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkListStore" id="account_store">
<columns>
<!-- column-name accountname -->
<!-- column-name accounts -->
<column type="gchararray"/>
</columns>
</object>
......@@ -14,6 +14,12 @@
<column type="gchararray"/>
</columns>
</object>
<object class="GtkListStore" id="disabled_account_store">
<columns>
<!-- column-name accounts -->
<column type="gchararray"/>
</columns>
</object>
<object class="GtkListStore" id="fingerprint_store">
<columns>
<!-- column-name id -->
......@@ -24,6 +30,8 @@
<column type="gchararray"/>
<!-- column-name fingerprint -->
<column type="gchararray"/>
<!-- column-name deviceid -->
<column type="gint"/>
</columns>
</object>
<object class="GtkNotebook" id="notebook1">
......@@ -104,7 +112,6 @@
<object class="GtkLabel" id="fingerprint_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label">&lt;tt&gt;-------- -------- -------- -------- -------- &lt;/tt&gt;</property>
<property name="use_markup">True</property>
<property name="selectable">True</property>
</object>
......@@ -147,7 +154,6 @@
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">0</property>
</object>
<packing>
<property name="expand">False</property>
......@@ -200,6 +206,9 @@
<object class="GtkTreeViewColumn" id="name_column">
<property name="resizable">True</property>
<property name="title">Name</property>
<property name="clickable">True</property>
<property name="sort_indicator">True</property>
<property name="sort_column_id">1</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext2"/>
<attributes>
......@@ -261,6 +270,21 @@
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="delfprbutton">
<property name="label" translatable="yes">Delete Fingerprint</property>
<property name="width_request">200</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="delfpr_button_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
......@@ -400,6 +424,170 @@
<property name="tab_fill">False</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel" id="label6">
<property name="height_request">30</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">You have to restart Gajim for changes to take effect !</property>
<attributes>
<attribute name="weight" value="bold"/>
<attribute name="foreground" value="#ffff00000000"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">12</property>
<property name="spacing">5</property>
<child>
<object class="GtkVBox" id="vbox5">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow3">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<child>
<object class="GtkTreeView" id="active_accounts_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">account_store</property>
<child>
<object class="GtkTreeViewColumn" id="treeviewcolumn1">
<property name="title" translatable="yes">Active Accounts</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext5"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="disable_accounts_btn">
<property name="label" translatable="yes">Disable Account</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="disable_accounts_btn_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow4">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<child>
<object class="GtkTreeView" id="disabled_accounts_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">disabled_account_store</property>
<child>
<object class="GtkTreeViewColumn" id="treeviewcolumn2">
<property name="title" translatable="yes">Disabled Accounts</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext6"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="activate_accounts_btn">
<property name="label" translatable="yes">Activate Account</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="activate_accounts_btn_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="position">3</property>
</packing>
</child>
<child type="tab">
<object class="GtkLabel" id="disable_accounts">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Disable Accounts</property>
</object>
<packing>
<property name="position">3</property>
<property name="tab_fill">False</property>
</packing>
</child>
</object>
<object class="GtkMenu" id="fprclipboard_menu">
<property name="visible">True</property>
......
......@@ -2,12 +2,6 @@
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkListStore" id="account_store">
<columns>
<!-- column-name accountname -->
<column type="gchararray"/>
</columns>
</object>
<object class="GtkListStore" id="fingerprint_store">
<columns>
<!-- column-name id -->
......@@ -18,6 +12,8 @@
<column type="gchararray"/>
<!-- column-name fingerprint -->
<column type="gchararray"/>
<!-- column-name deviceid -->
<column type="gint"/>
</columns>
</object>
<object class="GtkNotebook" id="notebook1">
......@@ -41,7 +37,9 @@
<object class="GtkTreeView" id="fingerprint_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="has_tooltip">True</property>
<property name="model">fingerprint_store</property>
<property name="headers_clickable">False</property>
<property name="search_column">0</property>
<property name="tooltip_column">3</property>
<signal name="button-press-event" handler="fpr_button_pressed_cb" swapped="no"/>
......@@ -49,8 +47,11 @@
<object class="GtkTreeViewColumn" id="name_column">
<property name="resizable">True</property>
<property name="title">Name</property>
<property name="clickable">True</property>
<property name="sort_indicator">True</property>
<property name="sort_column_id">1</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext2"/>
<object class="GtkCellRendererText" id="NameCell"/>
<attributes>
<attribute name="text">1</attribute>
</attributes>
......@@ -62,7 +63,7 @@
<property name="resizable">True</property>
<property name="title">Trust</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertoggle1"/>
<object class="GtkCellRendererText" id="TrustCell"/>
<attributes>
<attribute name="text">2</attribute>
</attributes>
......@@ -74,7 +75,7 @@
<property name="resizable">True</property>
<property name="title">Fingerprint</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext4"/>
<object class="GtkCellRendererText" id="FingerprintCell"/>
<attributes>
<attribute name="markup">3</attribute>
</attributes>
......@@ -198,7 +199,7 @@
<property name="resizable">True</property>
<property name="title">Name</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext1"/>
<object class="GtkCellRendererText" id="NameCell1"/>
<attributes>
<attribute name="text">1</attribute>
</attributes>
......@@ -210,7 +211,7 @@
<property name="resizable">True</property>
<property name="title">Trust</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertoggle2"/>
<object class="GtkCellRendererText" id="TrustCell2"/>
<attributes>
<attribute name="text">2</attribute>
</attributes>
......@@ -222,7 +223,7 @@
<property name="resizable">True</property>
<property name="title">Fingerprint</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext3"/>
<object class="GtkCellRendererText" id="FingerprintCell1"/>
<attributes>
<attribute name="markup">3</attribute>
</attributes>
......@@ -289,8 +290,6 @@
<object class="GtkMenuItem" id="copyfprclipboard_item">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes" comments="Context menu item">Copy to clipboard</property>
<property name="use_underline">True</property>
<signal name="activate" handler="clipboard_button_cb" swapped="no"/>
</object>
</child>
......
[info]
name: OMEMO
short_name: omemo
version: 0.9.0
version: 0.9.5
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 dependencys, you can find install instructions for your system in the Github Wiki.
authors: Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
Daniel Gultsch <daniel@gultsch.de>
......
......@@ -83,10 +83,16 @@ class LiteAxolotlStore(AxolotlStore):
def saveIdentity(self, recepientId, identityKey):
self.identityKeyStore.saveIdentity(recepientId, identityKey)
def deleteIdentity(self, recipientId, identityKey):
self.identityKeyStore.deleteIdentity(recipientId, identityKey)
def isTrustedIdentity(self, recepientId, identityKey):
return self.identityKeyStore.isTrustedIdentity(recepientId,
identityKey)
def setTrust(self, identityKey, trust):
return self.identityKeyStore.setTrust(identityKey, trust)
def getTrustedFingerprints(self, jid):
return self.identityKeyStore.getTrustedFingerprints(jid)
......@@ -127,6 +133,9 @@ class LiteAxolotlStore(AxolotlStore):
# TODO Reuse this
return self.sessionStore.getSubDeviceSessions(recepientId)
def getJidFromDevice(self, device_id):
return self.sessionStore.getJidFromDevice(device_id)
def storeSession(self, recepientId, deviceId, sessionRecord):
self.sessionStore.storeSession(recepientId, deviceId, sessionRecord)
......@@ -139,6 +148,15 @@ class LiteAxolotlStore(AxolotlStore):
def deleteAllSessions(self, recepientId):
self.sessionStore.deleteAllSessions(recepientId)
def getSessionsFromJid(self, recipientId):
return self.sessionStore.getSessionsFromJid(recipientId)
def getSessionsFromJids(self, recipientId):
return self.sessionStore.getSessionsFromJids(recipientId)
def getAllSessions(self):
return self.sessionStore.getAllSessions()
def loadSignedPreKey(self, signedPreKeyId):
return self.signedPreKeyStore.loadSignedPreKey(signedPreKeyId)
......
......@@ -86,6 +86,13 @@ class LiteIdentityKeyStore(IdentityKeyStore):
return result is not None
def deleteIdentity(self, recipientId, identityKey):
q = "DELETE FROM identities WHERE recipient_id = ? AND public_key = ?"
c = self.dbConn.cursor()
c.execute(q, (recipientId,
identityKey.getPublicKey().serialize()))
self.dbConn.commit()
def isTrustedIdentity(self, recipientId, identityKey):
q = "SELECT trust FROM identities WHERE recipient_id = ? " \
"AND public_key = ?"
......@@ -160,8 +167,8 @@ class LiteIdentityKeyStore(IdentityKeyStore):
c.execute(q, fingerprints)
self.dbConn.commit()
def setTrust(self, _id, trust):
q = "UPDATE identities SET trust = ? WHERE _id = ?"
def setTrust(self, identityKey, trust):
q = "UPDATE identities SET trust = ? WHERE public_key = ?"
c = self.dbConn.cursor()
c.execute(q, (trust, _id))
c.execute(q, (trust, identityKey.getPublicKey().serialize()))
self.dbConn.commit()
......@@ -48,6 +48,14 @@ class LiteSessionStore(SessionStore):
deviceIds = [r[0] for r in result]
return deviceIds
def getJidFromDevice(self, device_id):
q = "SELECT recipient_id from sessions WHERE device_id = ?"
c = self.dbConn.cursor()
c.execute(q, (device_id, ))
result = c.fetchone()
return result[0]
def getActiveDeviceTuples(self):
q = "SELECT recipient_id, device_id FROM sessions WHERE active = 1"
c = self.dbConn.cursor()
......@@ -82,6 +90,33 @@ class LiteSessionStore(SessionStore):
self.dbConn.cursor().execute(q, (recipientId, ))
self.dbConn.commit()
def getAllSessions(self):
q = "SELECT _id, recipient_id, device_id, record, active from sessions"
c = self.dbConn.cursor()
result = []
for row in c.execute(q):
result.append((row[0], row[1], row[2], row[3], row[4]))
return result
def getSessionsFromJid(self, recipientId):
q = "SELECT _id, recipient_id, device_id, record, active from sessions" \
" WHERE recipient_id = ?"
c = self.dbConn.cursor()
result = []
for row in c.execute(q, (recipientId,)):
result.append((row[0], row[1], row[2], row[3], row[4]))
return result
def getSessionsFromJids(self, recipientId):
q = "SELECT _id, recipient_id, device_id, record, active from sessions" \
" WHERE recipient_id IN ({})" \
.format(', '.join(['?'] * len(recipientId)))
c = self.dbConn.cursor()
result = []
for row in c.execute(q, recipientId):
result.append((row[0], row[1], row[2], row[3], row[4]))
return result
def setActiveState(self, deviceList, jid):
c = self.dbConn.cursor()
......@@ -96,28 +131,6 @@ class LiteSessionStore(SessionStore):
c.execute(q, deviceList)
self.dbConn.commit()
def getActiveSessionsKeys(self, recipientId):
q = "SELECT record FROM sessions WHERE active = 1 AND recipient_id = ?"
c = self.dbConn.cursor()
result = []
for row in c.execute(q, (recipientId,)):
public_key = (SessionRecord(serialized=row[0]).
getSessionState().getRemoteIdentityKey().
getPublicKey())
result.append(public_key.serialize())
return result
def getAllActiveSessionsKeys(self):
q = "SELECT record FROM sessions WHERE active = 1"
c = self.dbConn.cursor()
result = []
for row in c.execute(q):
public_key = (SessionRecord(serialized=row[0]).
getSessionState().getRemoteIdentityKey().
getPublicKey())
result.append(public_key.serialize())
return result
def getInactiveSessionsKeys(self, recipientId):
q = "SELECT record FROM sessions WHERE active = 0 AND recipient_id = ?"
c = self.dbConn.cursor()
......
......@@ -201,7 +201,7 @@ class OmemoState:
except (NoSessionException, InvalidMessageException) as e:
log.warning('No Session found ' + e.message)
log.warning('sender_jid => ' + str(sender_jid) + ' sid =>' +
str(sid))
str(sid))
return
except (DuplicateMessageException) as e:
log.warning('Duplicate message found ' + str(e.args))
......@@ -226,24 +226,82 @@ class OmemoState:
log.error('No known devices')
return
for dev in devices_list:
self.get_session_cipher(jid, dev)
session_ciphers = self.session_ciphers[jid]
if not session_ciphers:
log.warning('No session ciphers for ' + jid)
return
# Encrypt the message key with for each of receivers devices
for rid, cipher in session_ciphers.items():
for device in devices_list:
try:
if self.isTrusted(cipher) == TRUSTED:
encrypted_keys[rid] = cipher.encrypt(key).serialize()
if self.isTrusted(jid, device) == TRUSTED:
cipher = self.get_session_cipher(jid, device)
encrypted_keys[device] = cipher.encrypt(key).serialize()
else:
log.debug('Skipped Device because Trust is: ' +
str(self.isTrusted(cipher)))
str(self.isTrusted(jid, device)))
except:
log.warning('Failed to find key for device ' + str(rid))
log.warning('Failed to find key for device ' + str(device))
if len(encrypted_keys) == 0:
log.error('Encrypted keys empty')
raise NoValidSessions('Encrypted keys empty')
my_other_devices = set(self.own_devices) - set({self.own_device_id})
# Encrypt the message key with for each of our own devices
for device in my_other_devices:
try:
if self.isTrusted(from_jid, device) == TRUSTED:
cipher = self.get_session_cipher(from_jid, device)
encrypted_keys[device] = cipher.encrypt(key).serialize()
else:
log.debug('Skipped own Device because Trust is: ' +
str(self.isTrusted(from_jid, device)))
except:
log.warning('Failed to find key for device ' + str(device))
payload = encrypt(key, iv, plaintext)
result = {'sid': self.own_device_id,
'keys': encrypted_keys,
'jid': jid,
'iv': iv,
'payload': payload}
log.debug('Finished encrypting message')
return result
def create_gc_msg(self, from_jid, jid, plaintext):
key = get_random_bytes(16)
iv = get_random_bytes(16)
encrypted_keys = {}
room = jid
encrypted_jids = []
devices_list = self.device_list_for(jid, True)
if len(devices_list) == 0:
log.error('No known devices')
return
for tup in devices_list:
self.get_session_cipher(tup[0], tup[1])
# Encrypt the message key with for each of receivers devices
for nick in self.plugin.groupchat[room]:
jid_to = self.plugin.groupchat[room][nick]
if jid_to == self.own_jid:
continue
if jid_to in encrypted_jids: # We already encrypted to this JID
continue
for rid, cipher in self.session_ciphers[jid_to].items():
try:
if self.isTrusted(jid_to, rid) == TRUSTED:
encrypted_keys[rid] = cipher.encrypt(key). \
serialize()
else:
log.debug('Skipped Device because Trust is: ' +
str(self.isTrusted(jid_to, rid)))
except:
log.exception('ERROR:')
log.warning('Failed to find key for device ' +
str(rid))
encrypted_jids.append(jid_to)
if len(encrypted_keys) == 0:
log_msg = 'Encrypted keys empty'
log.error(log_msg)
......@@ -254,12 +312,13 @@ class OmemoState:
for dev in my_other_devices:
try:
cipher = self.get_session_cipher(from_jid, dev)
if self.isTrusted(cipher) == TRUSTED:
if self.isTrusted(from_jid, dev) == TRUSTED:
encrypted_keys[dev] = cipher.encrypt(key).serialize()
else:
log.debug('Skipped own Device because Trust is: ' +
str(self.isTrusted(cipher)))
str(self.isTrusted(from_jid, dev)))
except:
log.exception('ERROR:')
log.warning('Failed to find key for device ' + str(dev))
payload = encrypt(key, iv, plaintext)
......@@ -273,14 +332,36 @@ class OmemoState:
log.debug('Finished encrypting message')
return result
def isTrusted(self, cipher):
self.cipher = cipher
self.state = self.cipher.sessionStore. \
loadSession(self.cipher.recipientId, self.cipher.deviceId). \
getSessionState()
self.key = self.state.getRemoteIdentityKey()
return self.store.identityKeyStore. \
isTrustedIdentity(self.cipher.recipientId, self.key)
def device_list_for(self, jid, gc=False):
""" Return a list of known device ids for the specified jid.
Parameters
----------
jid : string
The contacts jid
gc : bool
Groupchat Message
"""
if gc:
room = jid
devicelist = []
for nick in self.plugin.groupchat[room]:
jid_to = self.plugin.groupchat[room][nick]
if jid_to == self.own_jid:
continue
for device in self.device_ids[jid_to]:
devicelist.append((jid_to, device))
return devicelist
if jid == self.own_jid:
return set(self.own_devices) - set({self.own_device_id})
if jid not in self.device_ids:
return set()
return set(self.device_ids[jid])
def isTrusted(self, recipient_id, device_id):
record = self.store.loadSession(recipient_id, device_id)
identity_key = record.getSessionState().getRemoteIdentityKey()
return self.store.isTrustedIdentity(recipient_id, identity_key)
def getTrustedFingerprints(self, recipient_id):
inactive = self.store.getInactiveSessionsKeys(recipient_id)
......@@ -296,20 +377,6 @@ class OmemoState:
return undecided
def device_list_for(self, jid):
""" Return a list of known device ids for the specified jid.
Parameters
----------
jid : string
The contacts jid
"""
if jid == self.own_jid:
return set(self.own_devices) - set({self.own_device_id})
if jid not in self.device_ids:
return set()
return set(self.device_ids[jid])
def devices_without_sessions(self, jid):
""" List device_ids for the given jid which have no axolotl session.
......@@ -364,10 +431,10 @@ class OmemoState:
def handleWhisperMessage(self, recipient_id, device_id, key):
whisperMessage = WhisperMessage(serialized=key)
sessionCipher = self.get_session_cipher(recipient_id, device_id)
log.debug(self.account + " => Received WhisperMessage from " +
recipient_id)
if self.isTrusted(sessionCipher) >= TRUSTED:
if self.isTrusted(recipient_id, device_id):
sessionCipher = self.get_session_cipher(recipient_id, device_id)
key = sessionCipher.decryptMsg(whisperMessage)
return key
else:
......
......@@ -2,8 +2,8 @@
pkgname=gajim-plugin-omemo
_pkgname=gajim-omemo
pkgver=0.8.1
pkgrel=2
pkgver=0.9
pkgrel=1
pkgdesc="Gajim plugin for OMEMO Multi-End Message and Object Encryption."
arch=(any)
url="https://github.com/omemo/${_pkgname}"
......@@ -12,7 +12,7 @@ depends=("gajim" "python2-setuptools" "python2-cryptography" "python2-axolotl-gi
provides=('gajim-plugin-omemo')
conflicts=('gajim-plugin-omemo-git')
source=("https://github.com/omemo/${_pkgname}/archive/${pkgver}.tar.gz")
sha512sums=('e9280033fbe111f5010f2e9e8fa32c5b8c0abe308000f9a043a1c5e8215c96f8be434876b1d72cc8d68aed4ddaebe9655c70f9648a2db718cba71d90434fee2e')
sha512sums=('536d0a9e368dadefefba34b02e74194c314eb0fc6343fcbb64390b7e447fb8be0214e921359959f831d0bcfaef09ae6825110ebeea947ac5a5ef3bc73da72541')
package() {
cd $srcdir/gajim-omemo-${pkgver}
......
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment