diff --git a/roundup/mailgw.py b/roundup/mailgw.py
index d269554f8783216a05f51de73ed062c15fa28ca3..1558c0f421718505d2d8258bdd3754a779d59aaf 100644 (file)
--- a/roundup/mailgw.py
+++ b/roundup/mailgw.py
for u in key.uids:
yield getattr(u, attr)
-def check_pgp_sigs(sigs, gpgctx, author):
+def check_pgp_sigs(sigs, gpgctx, author, may_be_unsigned=False):
''' Theoretically a PGP message can have several signatures. GPGME
returns status on all signatures in a list. Walk that list
- looking for the author's signature
+ looking for the author's signature. Note that even if incoming
+ signatures are not required, the processing fails if there is an
+ invalid signature.
'''
for sig in sigs:
key = gpgctx.get_key(sig.fpr, False)
_("Invalid PGP signature detected.")
# we couldn't find a key belonging to the author of the email
- raise MailUsageError, _("Message signed with unknown key: %s") % sig.fpr
+ if sigs:
+ raise MailUsageError, _("Message signed with unknown key: %s") % sig.fpr
+ elif not may_be_unsigned:
+ raise MailUsageError, _("Unsigned Message")
class Message(mimetools.Message):
''' subclass mimetools.Message so we can retrieve the parts of the
return self.gettype() == 'multipart/encrypted' \
and self.typeheader.find('protocol="application/pgp-encrypted"') != -1
- def decrypt(self, author):
+ def decrypt(self, author, may_be_unsigned=False):
''' decrypt an OpenPGP MIME message
- This message must be signed as well as encrypted using the "combined"
- method. The decrypted contents are returned as a new message.
+ This message must be signed as well as encrypted using the
+ "combined" method if incoming signatures are configured.
+ The decrypted contents are returned as a new message.
'''
(hdr, msg) = self.getparts()
# According to the RFC 3156 encrypted mail must have exactly two parts.
# The first part contains the control information. Let's verify that
# the message meets the RFC before we try to decrypt it.
- if hdr.getbody() != 'Version: 1' or hdr.gettype() != 'application/pgp-encrypted':
+ if hdr.getbody().strip() != 'Version: 1' \
+ or hdr.gettype() != 'application/pgp-encrypted':
raise MailUsageError, \
_("Unknown multipart/encrypted version.")
# key to send it to us. now check the signatures to see if it
# was signed by someone we trust
result = context.op_verify_result()
- check_pgp_sigs(result.signatures, context, author)
+ check_pgp_sigs(result.signatures, context, author,
+ may_be_unsigned = may_be_unsigned)
plaintext.seek(0,0)
# pyme.core.Data implements a seek method with a different signature
self.props = None
self.content = None
self.attachments = None
+ self.crypt = False
def handle_ignore(self):
''' Check to see if message can be safely ignored:
else:
return True
- if self.config.PGP_ENABLE and pgp_role():
+ if self.config.PGP_ENABLE:
+ if pgp_role() and self.config.PGP_ENCRYPT:
+ self.crypt = True
assert pyme, 'pyme is not installed'
# signed/encrypted mail must come from the primary address
author_address = self.db.user.get(self.author, 'address')
if self.config.PGP_HOMEDIR:
os.environ['GNUPGHOME'] = self.config.PGP_HOMEDIR
+ if self.config.PGP_REQUIRE_INCOMING in ('encrypted', 'both') \
+ and pgp_role() and not self.message.pgp_encrypted():
+ raise MailUsageError, _(
+ "This tracker has been configured to require all email "
+ "be PGP encrypted.")
if self.message.pgp_signed():
self.message.verify_signature(author_address)
elif self.message.pgp_encrypted():
- # replace message with the contents of the decrypted
+ # Replace message with the contents of the decrypted
# message for content extraction
- # TODO: encrypted message handling is far from perfect
- # bounces probably include the decrypted message, for
- # instance :(
- self.message = self.message.decrypt(author_address)
- else:
+ # Note: the bounce-handling code now makes sure that
+ # either the encrypted mail received is sent back or
+ # that the error message is encrypted if needed.
+ encr_only = self.config.PGP_REQUIRE_INCOMING == 'encrypted'
+ encr_only = encr_only or not pgp_role()
+ self.crypt = True
+ self.message = self.message.decrypt(author_address,
+ may_be_unsigned = encr_only)
+ elif pgp_role():
raise MailUsageError, _("""
This tracker has been configured to require all email be PGP signed or
encrypted.""")
return self.handle_message(message)
# no, we want to trap exceptions
+ # Note: by default we return the message received not the
+ # internal state of the parsedMessage -- except for
+ # MailUsageError, Unauthorized and for unknown exceptions. For
+ # the latter cases we make sure the error message is encrypted
+ # if needed (if it either was received encrypted or pgp
+ # processing is turned on for the user).
try:
return self.handle_message(message)
except MailUsageHelp:
m.append(str(value))
m.append('\n\nMail Gateway Help\n=================')
m.append(fulldoc)
- self.mailer.bounce_message(message, [sendto[0][1]], m)
+ if self.parsed_message:
+ message = self.parsed_message.message
+ crypt = self.parsed_message.crypt
+ self.mailer.bounce_message(message, [sendto[0][1]], m, crypt=crypt)
except Unauthorized, value:
# just inform the user that he is not authorized
m = ['']
m.append(str(value))
- self.mailer.bounce_message(message, [sendto[0][1]], m)
+ if self.parsed_message:
+ message = self.parsed_message.message
+ crypt = self.parsed_message.crypt
+ self.mailer.bounce_message(message, [sendto[0][1]], m, crypt=crypt)
except IgnoreMessage:
# do not take any action
# this exception is thrown when email should be ignored
m.append('An unexpected error occurred during the processing')
m.append('of your message. The tracker administrator is being')
m.append('notified.\n')
- self.mailer.bounce_message(message, [sendto[0][1]], m)
+ if self.parsed_message:
+ message = self.parsed_message.message
+ crypt = self.parsed_message.crypt
+ self.mailer.bounce_message(message, [sendto[0][1]], m, crypt=crypt)
m.append('----------------')
m.append(traceback.format_exc())
# commit the changes to the DB
self.db.commit()
+ self.parsed_message = None
return nodeid
def get_class_arguments(self, class_type, classname=None):