Mailgate: replace tabs with spaces

This commit is contained in:
Piotr F. Mieszkowski 2022-06-30 22:16:22 +02:00 committed by Gitea
parent 3f2760ba2d
commit 29b5b50901
1 changed files with 409 additions and 401 deletions

View File

@ -1,23 +1,22 @@
# #
# gpg-mailgate # gpg-mailgate
# #
# This file is part of the gpg-mailgate source code. # This file is part of the gpg-mailgate source code.
# #
# gpg-mailgate is free software: you can redistribute it and/or modify # gpg-mailgate is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or # the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version. # (at your option) any later version.
# #
# gpg-mailgate source code is distributed in the hope that it will be useful, # gpg-mailgate source code is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with gpg-mailgate source code. If not, see <http://www.gnu.org/licenses/>. # along with gpg-mailgate source code. If not, see <http://www.gnu.org/licenses/>.
# #
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
import copy import copy
import email import email
@ -25,406 +24,415 @@ import email.message
import email.utils import email.utils
import GnuPG import GnuPG
import os import os
import re
import smtplib import smtplib
import sys import sys
import syslog
import traceback
import time import time
# imports for S/MIME # imports for S/MIME
from M2Crypto import BIO, Rand, SMIME, X509 from M2Crypto import BIO, SMIME, X509
from email.mime.message import MIMEMessage
import logging import logging
import lacre
import lacre.text as text import lacre.text as text
import lacre.config as conf import lacre.config as conf
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
def gpg_encrypt( raw_message, recipients ):
if not conf.config_item_set('gpg', 'keyhome'): def _gpg_encrypt(raw_message, recipients):
LOG.error("No valid entry for gpg keyhome. Encryption aborted.") if not conf.config_item_set('gpg', 'keyhome'):
return recipients LOG.error("No valid entry for gpg keyhome. Encryption aborted.")
return recipients
keys = GnuPG.public_keys( conf.get_item('gpg', 'keyhome') )
for fingerprint in keys: keys = GnuPG.public_keys(conf.get_item('gpg', 'keyhome'))
keys[fingerprint] = sanitize_case_sense(keys[fingerprint]) for fingerprint in keys:
keys[fingerprint] = _sanitize_case_sense(keys[fingerprint])
# This list will be filled with pairs (M, N), where M is the destination
# address we're going to deliver the message to and N is the identity we're # This list will be filled with pairs (M, N), where M is the destination
# going to encrypt it for. # address we're going to deliver the message to and N is the identity we're
gpg_to = list() # going to encrypt it for.
gpg_to = list()
ungpg_to = list()
ungpg_to = list()
enc_keymap_only = conf.config_item_equals('default', 'enc_keymap_only', 'yes')
enc_keymap_only = conf.config_item_equals('default', 'enc_keymap_only', 'yes')
for to in recipients:
for to in recipients:
# Check if recipient is in keymap
if conf.config_item_set('enc_keymap', to): # Check if recipient is in keymap
LOG.info("Encrypt keymap has key '%s'" % conf.get_item('enc_keymap', to) ) if conf.config_item_set('enc_keymap', to):
# Check we've got a matching key! LOG.info("Encrypt keymap has key '%s'" % conf.get_item('enc_keymap', to))
if conf.get_item('enc_keymap', to) in keys: # Check we've got a matching key!
gpg_to.append( (to, conf.get_item('enc_keymap', to)) ) if conf.get_item('enc_keymap', to) in keys:
continue gpg_to.append((to, conf.get_item('enc_keymap', to)))
else: continue
LOG.info("Key '%s' in encrypt keymap not found in keyring for email address '%s'." % (conf.get_item('enc_keymap', to), to)) else:
LOG.info("Key '%s' in encrypt keymap not found in keyring for email address '%s'." % (conf.get_item('enc_keymap', to), to))
# Check if key in keychain is present
if not enc_keymap_only: # Check if key in keychain is present
if to in keys.values(): if not enc_keymap_only:
gpg_to.append( (to, to) ) if to in keys.values():
continue gpg_to.append((to, to))
continue
# If this is an address with a delimiter (i.e. "foo+bar@example.com"),
# then strip whatever is found after the delimiter and try this address. # If this is an address with a delimiter (i.e. "foo+bar@example.com"),
(newto, topic) = text.parse_delimiter(to) # then strip whatever is found after the delimiter and try this address.
if newto in keys.values(): (newto, topic) = text.parse_delimiter(to)
gpg_to.append((to, newto)) if newto in keys.values():
gpg_to.append((to, newto))
# Check if there is a default key for the domain
splitted_to = to.split('@') # Check if there is a default key for the domain
if len(splitted_to) > 1: splitted_to = to.split('@')
domain = splitted_to[1] if len(splitted_to) > 1:
if conf.config_item_set('enc_domain_keymap', domain): domain = splitted_to[1]
LOG.info("Encrypt domain keymap has key '%s'" % conf.get_item('enc_domain_keymap', domain) ) if conf.config_item_set('enc_domain_keymap', domain):
# Check we've got a matching key! LOG.info("Encrypt domain keymap has key '%s'" % conf.get_item('enc_domain_keymap', domain))
if conf.get_item('enc_domain_keymap', domain) in keys: # Check we've got a matching key!
LOG.info("Using default domain key for recipient '%s'" % to) if conf.get_item('enc_domain_keymap', domain) in keys:
gpg_to.append( (to, conf.get_item('enc_domain_keymap', domain)) ) LOG.info("Using default domain key for recipient '%s'" % to)
continue gpg_to.append((to, conf.get_item('enc_domain_keymap', domain)))
else: continue
LOG.info("Key '%s' in encrypt domain keymap not found in keyring for email address '%s'." % (conf.get_item('enc_domain_keymap', domain), to)) else:
LOG.info("Key '%s' in encrypt domain keymap not found in keyring for email address '%s'." % (conf.get_item('enc_domain_keymap', domain), to))
# At this point no key has been found
LOG.debug("Recipient (%s) not in PGP domain list for encrypting." % to) # At this point no key has been found
ungpg_to.append(to) LOG.debug("Recipient (%s) not in PGP domain list for encrypting." % to)
ungpg_to.append(to)
if gpg_to:
LOG.info("Encrypting email to: %s" % ' '.join( x[0] for x in gpg_to )) if gpg_to:
LOG.info("Encrypting email to: %s" % ' '.join(x[0] for x in gpg_to))
# Getting PGP style for recipient
gpg_to_smtp_mime = list() # Getting PGP style for recipient
gpg_to_cmdline_mime = list() gpg_to_smtp_mime = list()
gpg_to_cmdline_mime = list()
gpg_to_smtp_inline = list()
gpg_to_cmdline_inline = list() gpg_to_smtp_inline = list()
gpg_to_cmdline_inline = list()
for rcpt in gpg_to:
# Checking pre defined styles in settings first for rcpt in gpg_to:
if conf.config_item_equals('pgp_style', rcpt[0], 'mime'): # Checking pre defined styles in settings first
gpg_to_smtp_mime.append(rcpt[0]) if conf.config_item_equals('pgp_style', rcpt[0], 'mime'):
gpg_to_cmdline_mime.extend(rcpt[1].split(',')) gpg_to_smtp_mime.append(rcpt[0])
elif conf.config_item_equals('pgp_style', rcpt[0], 'inline'): gpg_to_cmdline_mime.extend(rcpt[1].split(','))
gpg_to_smtp_inline.append(rcpt[0]) elif conf.config_item_equals('pgp_style', rcpt[0], 'inline'):
gpg_to_cmdline_inline.extend(rcpt[1].split(',')) gpg_to_smtp_inline.append(rcpt[0])
else: gpg_to_cmdline_inline.extend(rcpt[1].split(','))
# Log message only if an unknown style is defined else:
if conf.config_item_set('pgp_style', rcpt[0]): # Log message only if an unknown style is defined
LOG.debug("Style %s for recipient %s is not known. Use default as fallback." % (conf.get_item("pgp_style", rcpt[0]), rcpt[0])) if conf.config_item_set('pgp_style', rcpt[0]):
LOG.debug("Style %s for recipient %s is not known. Use default as fallback." % (conf.get_item("pgp_style", rcpt[0]), rcpt[0]))
# If no style is in settings defined for recipient, use default from settings
if conf.config_item_equals('default', 'mime_conversion', 'yes'): # If no style is in settings defined for recipient, use default from settings
gpg_to_smtp_mime.append(rcpt[0]) if conf.config_item_equals('default', 'mime_conversion', 'yes'):
gpg_to_cmdline_mime.extend(rcpt[1].split(',')) gpg_to_smtp_mime.append(rcpt[0])
else: gpg_to_cmdline_mime.extend(rcpt[1].split(','))
gpg_to_smtp_inline.append(rcpt[0]) else:
gpg_to_cmdline_inline.extend(rcpt[1].split(',')) gpg_to_smtp_inline.append(rcpt[0])
gpg_to_cmdline_inline.extend(rcpt[1].split(','))
if gpg_to_smtp_mime:
# Encrypt mail with PGP/MIME if gpg_to_smtp_mime:
raw_message_mime = copy.deepcopy(raw_message) # Encrypt mail with PGP/MIME
raw_message_mime = copy.deepcopy(raw_message)
if conf.config_item_equals('default', 'add_header', 'yes'):
raw_message_mime['X-GPG-Mailgate'] = 'Encrypted by GPG Mailgate' if conf.config_item_equals('default', 'add_header', 'yes'):
raw_message_mime['X-GPG-Mailgate'] = 'Encrypted by GPG Mailgate'
if 'Content-Transfer-Encoding' in raw_message_mime:
raw_message_mime.replace_header('Content-Transfer-Encoding', '8BIT') if 'Content-Transfer-Encoding' in raw_message_mime:
else: raw_message_mime.replace_header('Content-Transfer-Encoding', '8BIT')
raw_message_mime['Content-Transfer-Encoding'] = '8BIT' else:
raw_message_mime['Content-Transfer-Encoding'] = '8BIT'
encrypted_payloads = encrypt_all_payloads_mime( raw_message_mime, gpg_to_cmdline_mime )
raw_message_mime.set_payload( encrypted_payloads ) encrypted_payloads = _encrypt_all_payloads_mime(raw_message_mime, gpg_to_cmdline_mime)
raw_message_mime.set_payload(encrypted_payloads)
send_msg( raw_message_mime.as_string(), gpg_to_smtp_mime )
_send_msg(raw_message_mime.as_string(), gpg_to_smtp_mime)
if gpg_to_smtp_inline:
# Encrypt mail with PGP/INLINE if gpg_to_smtp_inline:
raw_message_inline = copy.deepcopy(raw_message) # Encrypt mail with PGP/INLINE
raw_message_inline = copy.deepcopy(raw_message)
if conf.config_item_equals('default', 'add_header', 'yes'):
raw_message_inline['X-GPG-Mailgate'] = 'Encrypted by GPG Mailgate' if conf.config_item_equals('default', 'add_header', 'yes'):
raw_message_inline['X-GPG-Mailgate'] = 'Encrypted by GPG Mailgate'
if 'Content-Transfer-Encoding' in raw_message_inline:
raw_message_inline.replace_header('Content-Transfer-Encoding', '8BIT') if 'Content-Transfer-Encoding' in raw_message_inline:
else: raw_message_inline.replace_header('Content-Transfer-Encoding', '8BIT')
raw_message_inline['Content-Transfer-Encoding'] = '8BIT' else:
raw_message_inline['Content-Transfer-Encoding'] = '8BIT'
encrypted_payloads = encrypt_all_payloads_inline( raw_message_inline, gpg_to_cmdline_inline )
raw_message_inline.set_payload( encrypted_payloads ) encrypted_payloads = _encrypt_all_payloads_inline(raw_message_inline, gpg_to_cmdline_inline)
raw_message_inline.set_payload(encrypted_payloads)
send_msg( raw_message_inline.as_string(), gpg_to_smtp_inline )
_send_msg(raw_message_inline.as_string(), gpg_to_smtp_inline)
return ungpg_to
return ungpg_to
def encrypt_all_payloads_inline( message, gpg_to_cmdline ):
# This breaks cascaded MIME messages. Blame PGP/INLINE. def _encrypt_all_payloads_inline(message, gpg_to_cmdline):
encrypted_payloads = list()
if isinstance(message.get_payload(), str): # This breaks cascaded MIME messages. Blame PGP/INLINE.
return encrypt_payload( message, gpg_to_cmdline ).get_payload() encrypted_payloads = list()
if isinstance(message.get_payload(), str):
for payload in message.get_payload(): return _encrypt_payload(message, gpg_to_cmdline).get_payload()
if( isinstance(payload.get_payload(), list) ):
encrypted_payloads.extend( encrypt_all_payloads_inline( payload, gpg_to_cmdline ) ) for payload in message.get_payload():
else: if(isinstance(payload.get_payload(), list)):
encrypted_payloads.append( encrypt_payload( payload, gpg_to_cmdline ) ) encrypted_payloads.extend(_encrypt_all_payloads_inline(payload, gpg_to_cmdline))
else:
return encrypted_payloads encrypted_payloads.append(_encrypt_payload(payload, gpg_to_cmdline))
def encrypt_all_payloads_mime( message, gpg_to_cmdline ): return encrypted_payloads
# Convert a plain text email into PGP/MIME attachment style. Modeled after enigmail.
pgp_ver_part = email.message.Message()
pgp_ver_part.set_payload("Version: 1"+text.EOL) def _encrypt_all_payloads_mime(message, gpg_to_cmdline):
pgp_ver_part.set_type("application/pgp-encrypted") # Convert a plain text email into PGP/MIME attachment style. Modeled after enigmail.
pgp_ver_part.set_param('PGP/MIME version identification', "", 'Content-Description' ) pgp_ver_part = email.message.Message()
pgp_ver_part.set_payload("Version: 1"+text.EOL)
encrypted_part = email.message.Message() pgp_ver_part.set_type("application/pgp-encrypted")
encrypted_part.set_type("application/octet-stream") pgp_ver_part.set_param('PGP/MIME version identification', "", 'Content-Description')
encrypted_part.set_param('name', "encrypted.asc")
encrypted_part.set_param('OpenPGP encrypted message', "", 'Content-Description' ) encrypted_part = email.message.Message()
encrypted_part.set_param('inline', "", 'Content-Disposition' ) encrypted_part.set_type("application/octet-stream")
encrypted_part.set_param('filename', "encrypted.asc", 'Content-Disposition' ) encrypted_part.set_param('name', "encrypted.asc")
encrypted_part.set_param('OpenPGP encrypted message', "", 'Content-Description')
if isinstance(message.get_payload(), str): encrypted_part.set_param('inline', "", 'Content-Disposition')
# WTF! It seems to swallow the first line. Not sure why. Perhaps encrypted_part.set_param('filename', "encrypted.asc", 'Content-Disposition')
# it's skipping an imaginary blank line someplace. (ie skipping a header)
# Workaround it here by prepending a blank line. if isinstance(message.get_payload(), str):
# This happens only on text only messages. # WTF! It seems to swallow the first line. Not sure why. Perhaps
additionalSubHeader="" # it's skipping an imaginary blank line someplace. (ie skipping a header)
encoding = sys.getdefaultencoding() # Workaround it here by prepending a blank line.
if 'Content-Type' in message and not message['Content-Type'].startswith('multipart'): # This happens only on text only messages.
additionalSubHeader="Content-Type: "+message['Content-Type']+text.EOL additionalSubHeader = ""
(base, encoding) = text.parse_content_type(message['Content-Type']) encoding = sys.getdefaultencoding()
LOG.debug(f"Identified encoding as {encoding}") if 'Content-Type' in message and not message['Content-Type'].startswith('multipart'):
encrypted_part.set_payload(additionalSubHeader+text.EOL +message.get_payload(decode=True).decode(encoding)) additionalSubHeader = "Content-Type: " + message['Content-Type'] + text.EOL
check_nested = True (base, encoding) = text.parse_content_type(message['Content-Type'])
else: LOG.debug(f"Identified encoding as {encoding}")
processed_payloads = generate_message_from_payloads(message) encrypted_part.set_payload(additionalSubHeader+text.EOL + message.get_payload(decode=True).decode(encoding))
encrypted_part.set_payload(processed_payloads.as_string()) check_nested = True
check_nested = False else:
processed_payloads = _generate_message_from_payloads(message)
message.preamble = "This is an OpenPGP/MIME encrypted message (RFC 2440 and 3156)" encrypted_part.set_payload(processed_payloads.as_string())
check_nested = False
# Use this just to generate a MIME boundary string.
junk_msg = MIMEMultipart() message.preamble = "This is an OpenPGP/MIME encrypted message (RFC 2440 and 3156)"
junk_str = junk_msg.as_string() # WTF! Without this, get_boundary() will return 'None'!
boundary = junk_msg.get_boundary() # Use this just to generate a MIME boundary string.
junk_msg = MIMEMultipart()
# This also modifies the boundary in the body of the message, ie it gets parsed. junk_str = junk_msg.as_string() # WTF! Without this, get_boundary() will return 'None'!
if 'Content-Type' in message: boundary = junk_msg.get_boundary()
message.replace_header('Content-Type', f"multipart/encrypted; protocol=\"application/pgp-encrypted\"; boundary=\"{boundary}\""+text.EOL)
else: # This also modifies the boundary in the body of the message, ie it gets parsed.
message['Content-Type'] = f"multipart/encrypted; protocol=\"application/pgp-encrypted\"; boundary=\"{boundary}\""+text.EOL if 'Content-Type' in message:
message.replace_header('Content-Type', f"multipart/encrypted; protocol=\"application/pgp-encrypted\"; boundary=\"{boundary}\""+text.EOL)
return [ pgp_ver_part, encrypt_payload(encrypted_part, gpg_to_cmdline, check_nested) ] else:
message['Content-Type'] = f"multipart/encrypted; protocol=\"application/pgp-encrypted\"; boundary=\"{boundary}\""+text.EOL
def encrypt_payload( payload, gpg_to_cmdline, check_nested = True ):
raw_payload = payload.get_payload(decode=True) return [pgp_ver_part, _encrypt_payload(encrypted_part, gpg_to_cmdline, check_nested)]
if check_nested and text.is_pgp_inline(raw_payload):
LOG.debug("Message is already pgp encrypted. No nested encryption needed.")
return payload def _encrypt_payload(payload, gpg_to_cmdline, check_nested=True):
raw_payload = payload.get_payload(decode=True)
# No check is needed for conf.get_item('gpg', 'keyhome') as this is already done in method gpg_encrypt if check_nested and text.is_pgp_inline(raw_payload):
gpg = GnuPG.GPGEncryptor( conf.get_item('gpg', 'keyhome'), gpg_to_cmdline, payload.get_content_charset() ) LOG.debug("Message is already pgp encrypted. No nested encryption needed.")
gpg.update( raw_payload ) return payload
encrypted_data, returncode = gpg.encrypt()
LOG.debug("Return code from encryption=%d (0 indicates success)." % returncode) # No check is needed for conf.get_item('gpg', 'keyhome') as this is already done in method gpg_encrypt
if returncode != 0: gpg = GnuPG.GPGEncryptor(conf.get_item('gpg', 'keyhome'), gpg_to_cmdline, payload.get_content_charset())
LOG.info("Encrytion failed with return code %d. Encryption aborted." % returncode) gpg.update(raw_payload)
return payload encrypted_data, returncode = gpg.encrypt()
LOG.debug("Return code from encryption=%d (0 indicates success)." % returncode)
payload.set_payload( encrypted_data ) if returncode != 0:
isAttachment = payload.get_param( 'attachment', None, 'Content-Disposition' ) is not None LOG.info("Encrytion failed with return code %d. Encryption aborted." % returncode)
return payload
if isAttachment:
filename = payload.get_filename() payload.set_payload(encrypted_data)
if filename: isAttachment = payload.get_param('attachment', None, 'Content-Disposition') is not None
pgpFilename = filename + ".pgp"
if not (payload.get('Content-Disposition') is None): if isAttachment:
payload.set_param( 'filename', pgpFilename, 'Content-Disposition' ) filename = payload.get_filename()
if not (payload.get('Content-Type') is None) and not (payload.get_param( 'name' ) is None): if filename:
payload.set_param( 'name', pgpFilename ) pgpFilename = filename + ".pgp"
if not (payload.get('Content-Transfer-Encoding') is None): if not (payload.get('Content-Disposition') is None):
payload.replace_header( 'Content-Transfer-Encoding', "7bit" ) payload.set_param('filename', pgpFilename, 'Content-Disposition')
if not (payload.get('Content-Type') is None) and not (payload.get_param('name') is None):
return payload payload.set_param('name', pgpFilename)
if not (payload.get('Content-Transfer-Encoding') is None):
def smime_encrypt( raw_message, recipients ): payload.replace_header('Content-Transfer-Encoding', "7bit")
global LOG
global from_addr return payload
if not conf.config_item_set('smime', 'cert_path'):
LOG.info("No valid path for S/MIME certs found in config file. S/MIME encryption aborted.") def _smime_encrypt(raw_message, recipients):
return recipients global LOG
global from_addr
cert_path = conf.get_item('smime', 'cert_path')+"/"
s = SMIME.SMIME() if not conf.config_item_set('smime', 'cert_path'):
sk = X509.X509_Stack() LOG.info("No valid path for S/MIME certs found in config file. S/MIME encryption aborted.")
smime_to = list() return recipients
unsmime_to = list()
cert_path = conf.get_item('smime', 'cert_path')+"/"
for addr in recipients: s = SMIME.SMIME()
cert_and_email = get_cert_for_email(addr, cert_path) sk = X509.X509_Stack()
smime_to = list()
if not (cert_and_email is None): unsmime_to = list()
(to_cert, normal_email) = cert_and_email
LOG.debug("Found cert " + to_cert + " for " + addr + ": " + normal_email) for addr in recipients:
smime_to.append(addr) cert_and_email = _get_cert_for_email(addr, cert_path)
x509 = X509.load_cert(to_cert, format=X509.FORMAT_PEM)
sk.push(x509) if not (cert_and_email is None):
else: (to_cert, normal_email) = cert_and_email
unsmime_to.append(addr) LOG.debug("Found cert " + to_cert + " for " + addr + ": " + normal_email)
smime_to.append(addr)
if smime_to: x509 = X509.load_cert(to_cert, format=X509.FORMAT_PEM)
s.set_x509_stack(sk) sk.push(x509)
s.set_cipher(SMIME.Cipher('aes_192_cbc')) else:
p7 = s.encrypt( BIO.MemoryBuffer( raw_message.as_string() ) ) unsmime_to.append(addr)
# Output p7 in mail-friendly format.
out = BIO.MemoryBuffer() if smime_to:
out.write('From: ' + from_addr + text.EOL) s.set_x509_stack(sk)
out.write('To: ' + raw_message['To'] + text.EOL) s.set_cipher(SMIME.Cipher('aes_192_cbc'))
if raw_message['Cc']: p7 = s.encrypt(BIO.MemoryBuffer(raw_message.as_string()))
out.write('Cc: ' + raw_message['Cc'] + text.EOL) # Output p7 in mail-friendly format.
if raw_message['Bcc']: out = BIO.MemoryBuffer()
out.write('Bcc: ' + raw_message['Bcc'] + text.EOL) out.write('From: ' + from_addr + text.EOL)
if raw_message['Subject']: out.write('To: ' + raw_message['To'] + text.EOL)
out.write('Subject: '+ raw_message['Subject'] + text.EOL) if raw_message['Cc']:
out.write('Cc: ' + raw_message['Cc'] + text.EOL)
if conf.config_item_equals('default', 'add_header', 'yes'): if raw_message['Bcc']:
out.write('X-GPG-Mailgate: Encrypted by GPG Mailgate' + text.EOL) out.write('Bcc: ' + raw_message['Bcc'] + text.EOL)
if raw_message['Subject']:
s.write(out, p7) out.write('Subject: ' + raw_message['Subject'] + text.EOL)
LOG.debug(f"Sending message from {from_addr} to {smime_to}") if conf.config_item_equals('default', 'add_header', 'yes'):
out.write('X-GPG-Mailgate: Encrypted by GPG Mailgate' + text.EOL)
send_msg(out.read(), smime_to)
if unsmime_to: s.write(out, p7)
LOG.debug(f"Unable to find valid S/MIME certificates for {unsmime_to}")
LOG.debug(f"Sending message from {from_addr} to {smime_to}")
return unsmime_to
_send_msg(out.read(), smime_to)
def get_cert_for_email( to_addr, cert_path ): if unsmime_to:
insensitive = conf.config_item_equals('default', 'mail_case_insensitive', 'yes') LOG.debug(f"Unable to find valid S/MIME certificates for {unsmime_to}")
files_in_directory = os.listdir(cert_path) return unsmime_to
for filename in files_in_directory:
file_path = os.path.join(cert_path, filename)
if not os.path.isfile(file_path): def _get_cert_for_email(to_addr, cert_path):
continue insensitive = conf.config_item_equals('default', 'mail_case_insensitive', 'yes')
if insensitive: files_in_directory = os.listdir(cert_path)
if filename.casefold() == to_addr: for filename in files_in_directory:
return (file_path, to_addr) file_path = os.path.join(cert_path, filename)
else: if not os.path.isfile(file_path):
if filename == to_addr: continue
return (file_path, to_addr)
if insensitive:
# support foo+ignore@bar.com -> foo@bar.com if filename.casefold() == to_addr:
(fixed_up_email, topic) = text.parse_delimiter(to_addr) return (file_path, to_addr)
if topic is None: else:
# delimiter not used if filename == to_addr:
return None return (file_path, to_addr)
else:
LOG.debug(f"Looking up certificate for {fixed_up_email} after parsing {to_addr}") # support foo+ignore@bar.com -> foo@bar.com
return get_cert_for_email(fixed_up_email, cert_path) (fixed_up_email, topic) = text.parse_delimiter(to_addr)
if topic is None:
def sanitize_case_sense( address ): # delimiter not used
if conf.config_item_equals('default', 'mail_case_insensitive', 'yes'): return None
address = address.lower() else:
else: LOG.debug(f"Looking up certificate for {fixed_up_email} after parsing {to_addr}")
splitted_address = address.split('@') return _get_cert_for_email(fixed_up_email, cert_path)
if len(splitted_address) > 1:
address = splitted_address[0] + '@' + splitted_address[1].lower()
def _sanitize_case_sense(address):
return address if conf.config_item_equals('default', 'mail_case_insensitive', 'yes'):
address = address.lower()
def generate_message_from_payloads( payloads, message = None ): else:
if message == None: splitted_address = address.split('@')
message = email.mime.multipart.MIMEMultipart(payloads.get_content_subtype()) if len(splitted_address) > 1:
address = splitted_address[0] + '@' + splitted_address[1].lower()
for payload in payloads.get_payload():
if( isinstance(payload.get_payload(), list) ): return address
message.attach(generate_message_from_payloads(payload))
else:
message.attach(payload) def _generate_message_from_payloads(payloads, message=None):
if message is None:
return message message = email.mime.multipart.MIMEMultipart(payloads.get_content_subtype())
def get_first_payload( payloads ): for payload in payloads.get_payload():
if payloads.is_multipart(): if(isinstance(payload.get_payload(), list)):
return get_first_payload(payloads.get_payload(0)) message.attach(_generate_message_from_payloads(payload))
else: else:
return payloads message.attach(payload)
def send_msg( message, recipients ): return message
global from_addr
recipients = [_f for _f in recipients if _f] def _get_first_payload(payloads):
if recipients: if payloads.is_multipart():
LOG.info(f"Sending email to: {recipients!r}") return _get_first_payload(payloads.get_payload(0))
relay = conf.relay_params() else:
smtp = smtplib.SMTP(relay[0], relay[1]) return payloads
if conf.config_item_equals('relay', 'starttls', 'yes'):
smtp.starttls()
smtp.sendmail( from_addr, recipients, message ) def _send_msg(message, recipients):
else: global from_addr
LOG.info("No recipient found")
recipients = [_f for _f in recipients if _f]
def is_encrypted(raw_message): if recipients:
if raw_message.get_content_type() == 'multipart/encrypted': LOG.info(f"Sending email to: {recipients!r}")
return True relay = conf.relay_params()
smtp = smtplib.SMTP(relay[0], relay[1])
first_part = get_first_payload(raw_message) if conf.config_item_equals('relay', 'starttls', 'yes'):
if first_part.get_content_type() == 'application/pkcs7-mime': smtp.starttls()
return True smtp.sendmail(from_addr, recipients, message)
else:
first_payload = first_part.get_payload(decode=True) LOG.info("No recipient found")
return text.is_pgp_inline(first_payload)
def deliver_message( raw_message, from_address, to_addrs ): def _is_encrypted(raw_message):
global from_addr if raw_message.get_content_type() == 'multipart/encrypted':
return True
# Ugly workaround to keep the code working without too many changes.
from_addr = from_address first_part = _get_first_payload(raw_message)
if first_part.get_content_type() == 'application/pkcs7-mime':
recipients_left = [sanitize_case_sense(recipient) for recipient in to_addrs] return True
# There is no need for nested encryption first_payload = first_part.get_payload(decode=True)
if is_encrypted(raw_message): return text.is_pgp_inline(first_payload)
LOG.debug("Message is already encrypted. Encryption aborted.")
send_msg(raw_message.as_string(), recipients_left)
return def deliver_message(raw_message, from_address, to_addrs):
"""Send RAW_MESSAGE to all TO_ADDRS using the best encryption method available."""
# Encrypt mails for recipients with known public PGP keys global from_addr
recipients_left = gpg_encrypt(raw_message, recipients_left)
if not recipients_left: # Ugly workaround to keep the code working without too many changes.
return from_addr = from_address
# Encrypt mails for recipients with known S/MIME certificate recipients_left = [_sanitize_case_sense(recipient) for recipient in to_addrs]
recipients_left = smime_encrypt(raw_message, recipients_left)
if not recipients_left: # There is no need for nested encryption
return if _is_encrypted(raw_message):
LOG.debug("Message is already encrypted. Encryption aborted.")
# Send out mail to recipients which are left _send_msg(raw_message.as_string(), recipients_left)
send_msg(raw_message.as_string(), recipients_left) return
# Encrypt mails for recipients with known public PGP keys
recipients_left = _gpg_encrypt(raw_message, recipients_left)
if not recipients_left:
return
# Encrypt mails for recipients with known S/MIME certificate
recipients_left = _smime_encrypt(raw_message, recipients_left)
if not recipients_left:
return
# Send out mail to recipients which are left
_send_msg(raw_message.as_string(), recipients_left)
def exec_time_info(start_timestamp): def exec_time_info(start_timestamp):
elapsed_s = time.time() - start_timestamp """Calculate time since the given timestamp."""
process_t = time.process_time() elapsed_s = time.time() - start_timestamp
return (elapsed_s, process_t) process_t = time.process_time()
return (elapsed_s, process_t)