@@ -159,10 +159,12 @@ def gpgh_key_getall(key, attr):
159159 for u in key .uids :
160160 yield getattr (u , attr )
161161
162- def check_pgp_sigs (sigs , gpgctx , author ):
162+ def check_pgp_sigs (sigs , gpgctx , author , may_be_unsigned = False ):
163163 ''' Theoretically a PGP message can have several signatures. GPGME
164164 returns status on all signatures in a list. Walk that list
165- looking for the author's signature
165+ looking for the author's signature. Note that even if incoming
166+ signatures are not required, the processing fails if there is an
167+ invalid signature.
166168 '''
167169 for sig in sigs :
168170 key = gpgctx .get_key (sig .fpr , False )
@@ -188,7 +190,10 @@ def check_pgp_sigs(sigs, gpgctx, author):
188190 _ ("Invalid PGP signature detected." )
189191
190192 # we couldn't find a key belonging to the author of the email
191- raise MailUsageError , _ ("Message signed with unknown key: %s" ) % sig .fpr
193+ if sigs :
194+ raise MailUsageError , _ ("Message signed with unknown key: %s" ) % sig .fpr
195+ elif not may_be_unsigned :
196+ raise MailUsageError , _ ("Unsigned Message" )
192197
193198class Message (mimetools .Message ):
194199 ''' subclass mimetools.Message so we can retrieve the parts of the
@@ -452,16 +457,18 @@ def pgp_encrypted(self):
452457 return self .gettype () == 'multipart/encrypted' \
453458 and self .typeheader .find ('protocol="application/pgp-encrypted"' ) != - 1
454459
455- def decrypt (self , author ):
460+ def decrypt (self , author , may_be_unsigned = False ):
456461 ''' decrypt an OpenPGP MIME message
457- This message must be signed as well as encrypted using the "combined"
458- method. The decrypted contents are returned as a new message.
462+ This message must be signed as well as encrypted using the
463+ "combined" method if incoming signatures are configured.
464+ The decrypted contents are returned as a new message.
459465 '''
460466 (hdr , msg ) = self .getparts ()
461467 # According to the RFC 3156 encrypted mail must have exactly two parts.
462468 # The first part contains the control information. Let's verify that
463469 # the message meets the RFC before we try to decrypt it.
464- if hdr .getbody () != 'Version: 1' or hdr .gettype () != 'application/pgp-encrypted' :
470+ if hdr .getbody ().strip () != 'Version: 1' \
471+ or hdr .gettype () != 'application/pgp-encrypted' :
465472 raise MailUsageError , \
466473 _ ("Unknown multipart/encrypted version." )
467474
@@ -478,7 +485,8 @@ def decrypt(self, author):
478485 # key to send it to us. now check the signatures to see if it
479486 # was signed by someone we trust
480487 result = context .op_verify_result ()
481- check_pgp_sigs (result .signatures , context , author )
488+ check_pgp_sigs (result .signatures , context , author ,
489+ may_be_unsigned = may_be_unsigned )
482490
483491 plaintext .seek (0 ,0 )
484492 # pyme.core.Data implements a seek method with a different signature
@@ -550,6 +558,7 @@ def __init__(self, mailgw, message):
550558 self .props = None
551559 self .content = None
552560 self .attachments = None
561+ self .crypt = False
553562
554563 def handle_ignore (self ):
555564 ''' Check to see if message can be safely ignored:
@@ -991,22 +1000,33 @@ def pgp_role():
9911000 else :
9921001 return True
9931002
994- if self .config .PGP_ENABLE and pgp_role ():
1003+ if self .config .PGP_ENABLE :
1004+ if pgp_role () and self .config .PGP_ENCRYPT :
1005+ self .crypt = True
9951006 assert pyme , 'pyme is not installed'
9961007 # signed/encrypted mail must come from the primary address
9971008 author_address = self .db .user .get (self .author , 'address' )
9981009 if self .config .PGP_HOMEDIR :
9991010 os .environ ['GNUPGHOME' ] = self .config .PGP_HOMEDIR
1011+ if self .config .PGP_REQUIRE_INCOMING in ('encrypted' , 'both' ) \
1012+ and pgp_role () and not self .message .pgp_encrypted ():
1013+ raise MailUsageError , _ (
1014+ "This tracker has been configured to require all email "
1015+ "be PGP encrypted." )
10001016 if self .message .pgp_signed ():
10011017 self .message .verify_signature (author_address )
10021018 elif self .message .pgp_encrypted ():
1003- # replace message with the contents of the decrypted
1019+ # Replace message with the contents of the decrypted
10041020 # message for content extraction
1005- # TODO: encrypted message handling is far from perfect
1006- # bounces probably include the decrypted message, for
1007- # instance :(
1008- self .message = self .message .decrypt (author_address )
1009- else :
1021+ # Note: the bounce-handling code now makes sure that
1022+ # either the encrypted mail received is sent back or
1023+ # that the error message is encrypted if needed.
1024+ encr_only = self .config .PGP_REQUIRE_INCOMING == 'encrypted'
1025+ encr_only = encr_only or not pgp_role ()
1026+ self .crypt = True
1027+ self .message = self .message .decrypt (author_address ,
1028+ may_be_unsigned = encr_only )
1029+ elif pgp_role ():
10101030 raise MailUsageError , _ ("""
10111031This tracker has been configured to require all email be PGP signed or
10121032encrypted.""" )
@@ -1449,6 +1469,12 @@ def handle_Message(self, message):
14491469 return self .handle_message (message )
14501470
14511471 # no, we want to trap exceptions
1472+ # Note: by default we return the message received not the
1473+ # internal state of the parsedMessage -- except for
1474+ # MailUsageError, Unauthorized and for unknown exceptions. For
1475+ # the latter cases we make sure the error message is encrypted
1476+ # if needed (if it either was received encrypted or pgp
1477+ # processing is turned on for the user).
14521478 try :
14531479 return self .handle_message (message )
14541480 except MailUsageHelp :
@@ -1466,12 +1492,18 @@ def handle_Message(self, message):
14661492 m .append (str (value ))
14671493 m .append ('\n \n Mail Gateway Help\n =================' )
14681494 m .append (fulldoc )
1469- self .mailer .bounce_message (message , [sendto [0 ][1 ]], m )
1495+ if self .parsed_message :
1496+ message = self .parsed_message .message
1497+ crypt = self .parsed_message .crypt
1498+ self .mailer .bounce_message (message , [sendto [0 ][1 ]], m , crypt = crypt )
14701499 except Unauthorized , value :
14711500 # just inform the user that he is not authorized
14721501 m = ['' ]
14731502 m .append (str (value ))
1474- self .mailer .bounce_message (message , [sendto [0 ][1 ]], m )
1503+ if self .parsed_message :
1504+ message = self .parsed_message .message
1505+ crypt = self .parsed_message .crypt
1506+ self .mailer .bounce_message (message , [sendto [0 ][1 ]], m , crypt = crypt )
14751507 except IgnoreMessage :
14761508 # do not take any action
14771509 # this exception is thrown when email should be ignored
@@ -1492,7 +1524,10 @@ def handle_Message(self, message):
14921524 m .append ('An unexpected error occurred during the processing' )
14931525 m .append ('of your message. The tracker administrator is being' )
14941526 m .append ('notified.\n ' )
1495- self .mailer .bounce_message (message , [sendto [0 ][1 ]], m )
1527+ if self .parsed_message :
1528+ message = self .parsed_message .message
1529+ crypt = self .parsed_message .crypt
1530+ self .mailer .bounce_message (message , [sendto [0 ][1 ]], m , crypt = crypt )
14961531
14971532 m .append ('----------------' )
14981533 m .append (traceback .format_exc ())
@@ -1523,6 +1558,7 @@ def _handle_message(self, message):
15231558 # commit the changes to the DB
15241559 self .db .commit ()
15251560
1561+ self .parsed_message = None
15261562 return nodeid
15271563
15281564 def get_class_arguments (self , class_type , classname = None ):
0 commit comments