diff --git a/gpg-mailgate.py b/gpg-mailgate.py index 5c8090a..02e1c80 100755 --- a/gpg-mailgate.py +++ b/gpg-mailgate.py @@ -70,227 +70,6 @@ raw_message = email.message_from_string( raw ) from_addr = raw_message['From'] to_addrs = sys.argv[1:] -def gpg_decrypt( raw_message, recipients ): - - gpg_to = list() - ungpg_to = list() - - # This is needed to avoid encryption if decryption is set to keymap only, - # private key is present but not in keymap. - noenc_to = list() - - if not get_bool_from_cfg('gpg', 'keyhome'): - log("No valid entry for gpg keyhome. Decryption aborted.") - return recipients - - keys = GnuPG.private_keys( cfg['gpg']['keyhome'] ) - - if get_bool_from_cfg('default', 'dec_regex'): - dec_regex = cfg['default']['dec_regex'] - else: - dec_regex = None - - for fingerprint in keys: - keys[fingerprint] = sanitize_case_sense(keys[fingerprint]) - - for to in recipients: - if to in keys.values() and not get_bool_from_cfg('default', 'dec_keymap_only', 'yes'): - gpg_to.append(to) - # Is this recipient defined in regex for default decryption? - elif not (dec_regex is None) and not (re.match(dec_regex, to) is None): - log("Using default decrytion defined in dec_regex for recipient '%s'" % to) - gpg_to.append(to) - elif get_bool_from_cfg('dec_keymap', to): - log("Decrypt keymap has key '%s'" % cfg['dec_keymap'][to] ) - # Check we've got a matching key! If not, decline to attempt decryption. The key is checked for safty reasons. - if not cfg['dec_keymap'][to] in keys: - log("Key '%s' in decryption keymap not found in keyring for email address '%s'. Won't decrypt." % (cfg['dec_keymap'][to], to)) - # Avoid unwanted encryption if set - if to in keys.values() and get_bool_from_cfg('default', 'failsave_dec', 'yes'): - noenc_to.append(to) - else: - ungpg_to.append(to) - else: - gpg_to.append(to) - else: - if verbose: - log("Recipient (%s) not in PGP domain list for decrypting." % to) - # Avoid unwanted encryption if set - if to in keys.values() and get_bool_from_cfg('default', 'failsave_dec', 'yes'): - noenc_to.append(to) - else: - ungpg_to.append(to) - - if gpg_to != list(): - send_msg( gpg_decrypt_all_payloads( raw_message ).as_string(), gpg_to ) - - if noenc_to != list(): - log("Do not try to encrypt mails for: %s" % ', '.join( noenc_to )) - send_msg(raw_message.as_string(), noenc_to) - - return ungpg_to - -def gpg_decrypt_all_payloads( message ): - - # We don't want to modify the original message - decrypted_message = copy.deepcopy(message) - - # Check if message is PGP/MIME encrypted - if not (message.get_param('protocol') is None) and message.get_param('protocol') == 'application/pgp-encrypted' and message.is_multipart(): - decrypted_message = decrypt_mime(decrypted_message) - - # At this point the message could only be PGP/INLINE encrypted, unencrypted or - # encrypted with a mechanism not covered by GPG-Mailgate - elif get_bool_from_cfg('default', 'no_inline_dec', 'no'): - # Check if message is PGP/INLINE encrypted and has attachments (or unencrypted with attachments) - if message.is_multipart(): - - # Set message's payload to list so payloads can be attached later on - decrypted_message.set_payload(list()) - - # We only need to hand over the original message here. Not needed for other decrypt implementations. - decrypted_message, success = decrypt_inline_with_attachments(message, False, decrypted_message) - - # Add header here to avoid it being appended several times - if get_bool_from_cfg('default', 'add_header', 'yes') and success: - decrypted_message['X-GPG-Mailgate'] = 'Decrypted by GPG Mailgate' - - # Check if message is PGP/INLINE encrypted without attachments (or unencrypted without attachments) - else: - decrypted_message = decrypt_inline_without_attachments(decrypted_message) - - return decrypted_message - -def decrypt_mime( decrypted_message ): - # Please note: Signatures will disappear while decrypting and will not be checked - - # Getting the part which should be PGP encrypted (according to RFC) - msg_content = decrypted_message.get_payload(1).get_payload() - - if "-----BEGIN PGP MESSAGE-----" in msg_content and "-----END PGP MESSAGE-----" in msg_content: - start = msg_content.find("-----BEGIN PGP MESSAGE-----") - end = msg_content.find("-----END PGP MESSAGE-----") - decrypted_payload, decrypt_success = decrypt_payload(msg_content[start:end + 25]) - - if decrypt_success: - # Making decrypted_message a "normal" unencrypted message - decrypted_message.del_param('protocol') - decrypted_message.set_type(decrypted_payload.get_content_type()) - - # Restore Content-Disposition header from original message - if not (decrypted_payload.get('Content-Disposition') is None): - if not (decrypted_message.get('Content-Disposition') is None): - decrypted_message.replace_header('Content-Disposition', decrypted_payload.get('Content-Disposition')) - else: - decrypted_message.set_param(decrypted_payload.get('Content-Disposition'), "", 'Content-Disposition') - - if decrypted_payload.is_multipart(): - # Clear message's original payload and insert the decrypted payloads - decrypted_message.set_payload(list()) - decrypted_message = generate_message_from_payloads( decrypted_payload, decrypted_message ) - decrypted_message.preamble = "This is a multi-part message in MIME format" - else: - decrypted_message.set_payload(decrypted_payload.get_payload()) - decrypted_message.preamble = None - - - if get_bool_from_cfg('default', 'add_header', 'yes'): - decrypted_message['X-GPG-Mailgate'] = 'Decrypted by GPG Mailgate' - - # If decryption fails, decrypted_message is equal to the original message - return decrypted_message - -def decrypt_inline_with_attachments( payloads, success, message = None ): - - if message is None: - message = email.mime.multipart.MIMEMultipart(payloads.get_content_subtype()) - - for payload in payloads.get_payload(): - if( isinstance(payload.get_payload(), list) ): - # Take care of cascaded MIME messages - submessage, subsuccess = decrypt_inline_with_attachments( payload, success ) - message.attach(submessage) - success = success or subsuccess - else: - msg_content = payload.get_payload() - - # Getting values for different implementations as PGP/INLINE is not implemented - # the same on different clients - pgp_inline_tags = "-----BEGIN PGP MESSAGE-----" in msg_content and "-----END PGP MESSAGE-----" in msg_content - attachment_filename = payload.get_filename() - - if pgp_inline_tags or not (attachment_filename is None) and not (re.search('.\.pgp$', attachment_filename) is None): - if pgp_inline_tags: - start = msg_content.find("-----BEGIN PGP MESSAGE-----") - end = msg_content.find("-----END PGP MESSAGE-----") - decrypted_payload, decrypt_success = decrypt_payload(msg_content[start:end + 25]) - # Some implementations like Enigmail have strange interpretations of PGP/INLINE - # This tries to cope with it as good as possible. - else: - build_message = """ ------BEGIN PGP MESSAGE----- - -%s ------END PGP MESSAGE-----""" % msg_content - - decrypted_payload, decrypt_success = decrypt_payload(build_message) - - # Was at least one decryption successful? - success = success or decrypt_success - - if decrypt_success: - - if not (attachment_filename is None): - attachment_filename = re.sub('\.pgp$', '', attachment_filename) - payload.set_param('filename', attachment_filename, 'Content-Disposition') - payload.set_param('name', attachment_filename, 'Content-Type') - # Need this nasty hack to avoid double blank lines at beginning of message - payload.set_payload(decrypted_payload.as_string()[1:]) - - message.attach(payload) - else: - # Message could not be decrypted, so non-decrypted message is attached - message.attach(payload) - else: - # There was no encrypted payload found, so the original payload is attached - message.attach(payload) - - return message, success - -def decrypt_inline_without_attachments( decrypted_message ): - - msg_content = decrypted_message.get_payload() - if "-----BEGIN PGP MESSAGE-----" in msg_content and "-----END PGP MESSAGE-----" in msg_content: - start = msg_content.find("-----BEGIN PGP MESSAGE-----") - end = msg_content.find("-----END PGP MESSAGE-----") - decrypted_payload, decrypt_success = decrypt_payload(msg_content[start:end + 25]) - - if decrypt_success: - # Need this nasty hack to avoid double blank lines at beginning of message - decrypted_message.set_payload(decrypted_payload.as_string()[1:]) - - if get_bool_from_cfg('default', 'add_header', 'yes'): - decrypted_message['X-GPG-Mailgate'] = 'Decrypted by GPG Mailgate' - - # If message was not encrypted, this will just return the original message - return decrypted_message - -def decrypt_payload( payload ): - - gpg = GnuPG.GPGDecryptor( cfg['gpg']['keyhome'] ) - gpg.update( payload ) - decrypted_data, returncode = gpg.decrypt() - if verbose: - log("Return code from decryption=%d (0 indicates success)." % returncode) - if returncode != 0: - log("Decrytion failed with return code %d. Decryption aborted." % returncode) - return payload, False - - # Decryption always generate a new message - decrypted_msg = email.message_from_string(decrypted_data) - - return decrypted_msg, True - def gpg_encrypt( raw_message, recipients ): if not get_bool_from_cfg('gpg', 'keyhome'): @@ -459,7 +238,7 @@ def encrypt_all_payloads_mime( message, gpg_to_cmdline ): junk_str = junk_msg.as_string() # WTF! Without this, get_boundary() will return 'None'! boundary = junk_msg.get_boundary() - # This also modifies the boundary in the body of the message, ie it gets parsed. + # This also modifies the boundary in the body of the message, ie it gets parsed. if 'Content-Type' in message: message.replace_header('Content-Type', "multipart/encrypted; protocol=\"application/pgp-encrypted\";\nboundary=\"%s\"\n" % boundary) else: @@ -648,11 +427,6 @@ def sort_recipients( raw_message, from_addr, to_addrs ): for recipient in to_addrs: recipients_left.append(sanitize_case_sense(recipient)) - # Decrypt mails for recipients with known private PGP keys - recipients_left = gpg_decrypt(raw_message, recipients_left) - if recipients_left == list(): - return - # There is no need for nested encryption first_payload = get_first_payload(raw_message) if first_payload.get_content_type() == 'application/pkcs7-mime':