Skip to content

Commit 1698d88

Browse files
committed
use gpg module instead of pyme module for PGP encryption
1 parent a1f47c9 commit 1698d88

File tree

13 files changed

+64
-71
lines changed

13 files changed

+64
-71
lines changed

.travis.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ addons:
1717
packages:
1818
# Required to build/install the xapian-binding
1919
- libxapian-dev
20-
# Required to install pyme
20+
# Required to install gpg
2121
- libgpgme11-dev
2222
- swig
2323

@@ -37,8 +37,8 @@ before_install:
3737
- cd $TRAVIS_BUILD_DIR
3838

3939
install:
40-
- pip install psycopg2 pytz whoosh
41-
- if [[ $TRAVIS_PYTHON_VERSION == "2."* ]]; then pip install MySQL-python pyme; fi
40+
- pip install gpg psycopg2 pytz whoosh
41+
- if [[ $TRAVIS_PYTHON_VERSION == "2."* ]]; then pip install MySQL-python; fi
4242
- pip install pytest-cov codecov
4343

4444
before_script:

CHANGES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ Features:
2020
2.7.8+), to improve performance over bundled pure Python
2121
version. Note that acceleration via m2crypto is no longer supported
2222
(Christof Meerwald)
23+
- issue2550989: PGP encryption is now done using the gpg module
24+
instead of pyme. (Christof Meerwald)
2325

2426
Fixed:
2527

doc/customizing.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ Section **pgp**
415415
OpenPGP mail processing options
416416

417417
enable -- ``no``
418-
Enable PGP processing. Requires pyme.
418+
Enable PGP processing. Requires gpg.
419419

420420
roles -- default *blank*
421421
If specified, a comma-separated list of roles to perform PGP

doc/installation.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ pyopenssl
101101
proxy through a server with SSL support (e.g. apache) then this is
102102
unnecessary.
103103

104-
pyme
105-
If pyme_ is installed you can configure the mail gateway to perform
104+
gpg
105+
If gpg_ is installed you can configure the mail gateway to perform
106106
verification or decryption of incoming OpenPGP MIME messages. When
107107
configured, you can require email to be cryptographically signed
108108
before roundup will allow it to make modifications to issues.
@@ -121,7 +121,7 @@ Windows Service
121121
.. _pytz: https://pypi.org/project/pytz/
122122
.. _Olson tz database: https://www.iana.org/time-zones
123123
.. _pyopenssl: http://pyopenssl.sourceforge.net
124-
.. _pyme: http://pyme.sourceforge.net
124+
.. _gpg: https://www.gnupg.org/software/gpgme/index.html
125125
.. _pywin32: https://pypi.org/project/pywin32/
126126
.. _jinja2: http://jinja.pocoo.org/
127127

roundup/anypy/email_.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
import email
44
from email import quoprimime, base64mime
55

6+
if str == bytes:
7+
message_from_bytes = email.message_from_string
8+
else:
9+
message_from_bytes = email.message_from_bytes
10+
611
## please import this file if you are using the email module
712

813
# Match encoded-word strings in the form =?charset?q?Hello_World?=

roundup/configuration.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1051,7 +1051,7 @@ def str2value(self, value):
10511051
), "Roundup Mail Gateway options"),
10521052
("pgp", (
10531053
(BooleanOption, "enable", "no",
1054-
"Enable PGP processing. Requires pyme. If you're planning\n"
1054+
"Enable PGP processing. Requires gpg. If you're planning\n"
10551055
"to send encrypted PGP mail to the tracker, you should also\n"
10561056
"enable the encrypt-option below, otherwise mail received\n"
10571057
"encrypted might be sent unencrypted to another user."),

roundup/mailer.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@
2121
from roundup.anypy.strings import b2s, s2b, s2u
2222

2323
try:
24-
import pyme, pyme.core
24+
import gpg, gpg.core
2525
except ImportError:
26-
pyme = None
26+
gpg = None
2727

2828

2929
class MessageSendError(RuntimeError):
@@ -205,9 +205,9 @@ def bounce_message(self, bounced_message, to, error,
205205
self.logger.debug("MessageSendError: %s", str(e))
206206
pass
207207
if crypt_to:
208-
plain = pyme.core.Data(message.as_string())
209-
cipher = pyme.core.Data()
210-
ctx = pyme.core.Context()
208+
plain = gpg.core.Data(message.as_string())
209+
cipher = gpg.core.Data()
210+
ctx = gpg.core.Context()
211211
ctx.set_armor(1)
212212
keys = []
213213
adrs = []
@@ -235,7 +235,7 @@ def bounce_message(self, bounced_message, to, error,
235235
part=MIMEBase('application', 'octet-stream')
236236
part.set_payload(cipher.read())
237237
message.attach(part)
238-
except pyme.GPGMEError:
238+
except gpg.GPGMEError:
239239
self.logger.debug("bounce_message: Cannot encrypt to %s",
240240
str(crypto_to))
241241
crypt_to = None

roundup/mailgw.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ class node. Any parts of other types are each stored in separate files
103103
import email.utils
104104
from email.generator import Generator
105105

106-
from .anypy.email_ import decode_header
106+
from roundup.anypy.email_ import decode_header, message_from_bytes
107107
from roundup.anypy.my_input import my_input
108108

109109
from roundup import configuration, hyperdb, date, password, exceptions
@@ -114,9 +114,9 @@ class node. Any parts of other types are each stored in separate files
114114
import roundup.anypy.random_ as random_
115115

116116
try:
117-
import pyme, pyme.core, pyme.constants, pyme.constants.sigsum
117+
import gpg, gpg.core, gpg.constants, gpg.constants.sigsum
118118
except ImportError:
119-
pyme = None
119+
gpg = None
120120

121121
SENDMAILDEBUG = os.environ.get('SENDMAILDEBUG', '')
122122

@@ -173,18 +173,18 @@ def check_pgp_sigs(sigs, gpgctx, author, may_be_unsigned=False):
173173
# we really only care about the signature of the user who
174174
# submitted the email
175175
if key and (author in gpgh_key_getall(key, 'email')):
176-
if sig.summary & pyme.constants.sigsum.VALID:
176+
if sig.summary & gpg.constants.sigsum.VALID:
177177
return True
178178
else:
179179
# try to narrow down the actual problem to give a more useful
180180
# message in our bounce
181-
if sig.summary & pyme.constants.sigsum.KEY_MISSING:
181+
if sig.summary & gpg.constants.sigsum.KEY_MISSING:
182182
raise MailUsageError( \
183183
_("Message signed with unknown key: %s") % sig.fpr)
184-
elif sig.summary & pyme.constants.sigsum.KEY_EXPIRED:
184+
elif sig.summary & gpg.constants.sigsum.KEY_EXPIRED:
185185
raise MailUsageError( \
186186
_("Message signed with an expired key: %s") % sig.fpr)
187-
elif sig.summary & pyme.constants.sigsum.KEY_REVOKED:
187+
elif sig.summary & gpg.constants.sigsum.KEY_REVOKED:
188188
raise MailUsageError( \
189189
_("Message signed with a revoked key: %s") % sig.fpr)
190190
else:
@@ -415,9 +415,9 @@ def decrypt(self, author, may_be_unsigned=False):
415415
hdr.get_content_type() != 'application/pgp-encrypted'):
416416
raise MailUsageError(_("Unknown multipart/encrypted version."))
417417

418-
context = pyme.core.Context()
419-
ciphertext = pyme.core.Data(msg.get_payload())
420-
plaintext = pyme.core.Data()
418+
context = gpg.core.Context()
419+
ciphertext = gpg.core.Data(msg.get_payload())
420+
plaintext = gpg.core.Data()
421421

422422
result = context.op_decrypt_verify(ciphertext, plaintext)
423423

@@ -432,10 +432,10 @@ def decrypt(self, author, may_be_unsigned=False):
432432
may_be_unsigned=may_be_unsigned)
433433

434434
plaintext.seek(0, 0)
435-
# pyme.core.Data implements a seek method with a different signature
435+
# gpg.core.Data implements a seek method with a different signature
436436
# than roundup can handle. So we'll put the data in a container that
437437
# the Message class can work with.
438-
return email.message_from_string(plaintext.read(), RoundupMessage)
438+
return message_from_bytes(plaintext.read(), RoundupMessage)
439439

440440
def verify_signature(self, author):
441441
"""
@@ -458,10 +458,10 @@ def verify_signature(self, author):
458458
# canonical <CR><LF> sequence."
459459
# TODO: what about character set conversion?
460460
canonical_msg = re.sub('(?<!\r)\n', '\r\n', msg.flatten())
461-
msg_data = pyme.core.Data(canonical_msg)
462-
sig_data = pyme.core.Data(sig.get_payload())
461+
msg_data = gpg.core.Data(canonical_msg)
462+
sig_data = gpg.core.Data(sig.get_payload())
463463

464-
context = pyme.core.Context()
464+
context = gpg.core.Context()
465465
context.op_verify(sig_data, msg_data, None)
466466

467467
# check all signatures for validity
@@ -942,7 +942,7 @@ def pgp_role():
942942
if self.config.PGP_ENABLE:
943943
if pgp_role() and self.config.PGP_ENCRYPT:
944944
self.crypt = True
945-
assert pyme, 'pyme is not installed'
945+
assert gpg, 'gpg is not installed'
946946
# signed/encrypted mail must come from the primary address
947947
author_address = self.db.user.get(self.author, 'address')
948948
if self.config.PGP_HOMEDIR:

roundup/roundupdb.py

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,9 @@
4242
import roundup.anypy.random_ as random_
4343

4444
try:
45-
import pyme, pyme.core
46-
# gpgme_check_version() must have been called once in a programm
47-
# to initialise some subsystems of gpgme.
48-
# See the gpgme documentation (at least from v1.1.6 to 1.3.1, e.g.
49-
# http://gnupg.org/documentation/manuals/gpgme/Library-Version-Check.html)
50-
# This is not done by pyme (at least v0.7.0 - 0.8.1). So we do it here.
51-
# FIXME: Make sure it is done only once (the gpgme documentation does
52-
# not tell if calling this several times has drawbacks).
53-
pyme.core.check_version(None)
45+
import gpg, gpg.core
5446
except ImportError:
55-
pyme = None
47+
gpg = None
5648

5749

5850
class Database:
@@ -374,9 +366,9 @@ def encrypt_to(self, message, sendto):
374366
""" Encrypt given message to sendto receivers.
375367
Returns a new RFC 3156 conforming message.
376368
"""
377-
plain = pyme.core.Data(message.as_string())
378-
cipher = pyme.core.Data()
379-
ctx = pyme.core.Context()
369+
plain = gpg.core.Data(message.as_string())
370+
cipher = gpg.core.Data()
371+
ctx = gpg.core.Context()
380372
ctx.set_armor(1)
381373
keys = []
382374
for adr in sendto:

test/db_test_base.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import unittest, os, shutil, errno, imp, sys, time, pprint, base64, os.path
2020
import logging, cgi
2121
from . import gpgmelib
22-
from email.parser import FeedParser
22+
from email import message_from_string
2323

2424
import pytest
2525
from roundup.hyperdb import String, Password, Link, Multilink, Date, \
@@ -36,6 +36,7 @@
3636

3737
from roundup.anypy.strings import b2s, s2b, u2s
3838
from roundup.anypy.cmp_ import NoneAndDictComparable
39+
from roundup.anypy.email_ import message_from_bytes
3940

4041
from .mocknull import MockNull
4142

@@ -2632,7 +2633,7 @@ def dummy_snd(s, to, msg, res=res) :
26322633
roundupdb._ = old_translate_
26332634
Mailer.smtp_send = backup
26342635

2635-
@pytest.mark.skipif(gpgmelib.pyme is None, reason='Skipping PGPNosy test')
2636+
@pytest.mark.skipif(gpgmelib.gpg is None, reason='Skipping PGPNosy test')
26362637
def testPGPNosyMail(self) :
26372638
"""Creates one issue with two attachments, one smaller and one larger
26382639
than the set max_attachment_size. Recipients are one with and
@@ -2674,23 +2675,19 @@ def dummy_snd(s, to, msg, res=res) :
26742675
self.assert_(b2s(base64.encodestring(s2b("xxx"))).rstrip() in mail_msg)
26752676
self.assert_("File 'test2.txt' not attached" in mail_msg)
26762677
self.assert_(b2s(base64.encodestring(s2b("yyy"))).rstrip() not in mail_msg)
2677-
fp = FeedParser()
26782678
mail_msg = str(res[1]["mail_msg"])
2679-
fp.feed(mail_msg)
2680-
parts = fp.close().get_payload()
2679+
parts = message_from_string(mail_msg).get_payload()
26812680
self.assertEqual(len(parts),2)
26822681
self.assertEqual(parts[0].get_payload().strip(), 'Version: 1')
2683-
crypt = gpgmelib.pyme.core.Data(parts[1].get_payload())
2684-
plain = gpgmelib.pyme.core.Data()
2685-
ctx = gpgmelib.pyme.core.Context()
2682+
crypt = gpgmelib.gpg.core.Data(parts[1].get_payload())
2683+
plain = gpgmelib.gpg.core.Data()
2684+
ctx = gpgmelib.gpg.core.Context()
26862685
res = ctx.op_decrypt(crypt, plain)
26872686
self.assertEqual(res, None)
26882687
plain.seek(0,0)
2689-
fp = FeedParser()
2690-
fp.feed(plain.read())
26912688
self.assert_("From: admin" in mail_msg)
26922689
self.assert_("Subject: [issue1] spam" in mail_msg)
2693-
mail_msg = str(fp.close())
2690+
mail_msg = str(message_from_bytes(plain.read()))
26942691
self.assert_("New submission from admin" in mail_msg)
26952692
self.assert_("one two" in mail_msg)
26962693
self.assert_("File 'test1.txt' not attached" not in mail_msg)

0 commit comments

Comments
 (0)