Mercurial > p > roundup > code
diff roundup/mailgw.py @ 4541:62239a524beb
PGP support is again working (pyme API has changed significantly)...
...and we now have a regression test. We now take care that
bounce-messages for incoming encrypted mails or mails where the policy
dictates that outgoing traffic should be encrypted is actually
pgp-encrypted. Note that the new pgp encrypt option for outgoing mails
works only for bounces for now.
| author | Ralf Schlatterbeck <schlatterbeck@users.sourceforge.net> |
|---|---|
| date | Fri, 07 Oct 2011 14:21:57 +0000 |
| parents | bf67fed13ef9 |
| children | 3f4179eb348f |
line wrap: on
line diff
--- a/roundup/mailgw.py Thu Oct 06 21:02:09 2011 +0000 +++ b/roundup/mailgw.py Fri Oct 07 14:21:57 2011 +0000 @@ -159,10 +159,12 @@ 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) @@ -188,7 +190,10 @@ _("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 @@ -452,16 +457,18 @@ 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.") @@ -478,7 +485,8 @@ # 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 @@ -550,6 +558,7 @@ 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: @@ -991,22 +1000,33 @@ 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.""") @@ -1449,6 +1469,12 @@ 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: @@ -1466,12 +1492,18 @@ 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 @@ -1492,7 +1524,10 @@ 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()) @@ -1523,6 +1558,7 @@ # commit the changes to the DB self.db.commit() + self.parsed_message = None return nodeid def get_class_arguments(self, class_type, classname=None):
