Wrap recipient lists
Instead of passing pairs of lists (emails and keys) separately, implement a class RecipientList to wrap such pair of lists.
This commit is contained in:
parent
c5e788b2a0
commit
a5f79c1ae7
264
lacre/core.py
264
lacre/core.py
|
@ -33,6 +33,7 @@ import GnuPG
|
||||||
import os
|
import os
|
||||||
import smtplib
|
import smtplib
|
||||||
import asyncio
|
import asyncio
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
# imports for S/MIME
|
# imports for S/MIME
|
||||||
from M2Crypto import BIO, SMIME, X509
|
from M2Crypto import BIO, SMIME, X509
|
||||||
|
@ -47,120 +48,6 @@ from lacre.mailop import KeepIntact, InlineOpenPGPEncrypt, MimeOpenPGPEncrypt
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def _gpg_encrypt(raw_message, recipients):
|
|
||||||
if not conf.config_item_set('gpg', 'keyhome'):
|
|
||||||
LOG.error("No valid entry for gpg keyhome. Encryption aborted.")
|
|
||||||
return recipients
|
|
||||||
|
|
||||||
gpg_recipients, cleartext_recipients = _identify_gpg_recipients(recipients, _load_keys())
|
|
||||||
|
|
||||||
LOG.info(f"Got addresses: gpg_to={gpg_recipients!r}, ungpg_to={cleartext_recipients!r}")
|
|
||||||
|
|
||||||
if gpg_recipients:
|
|
||||||
LOG.info("Encrypting email to: %s", gpg_recipients)
|
|
||||||
|
|
||||||
recipients_mime, keys_mime, recipients_inline, keys_inline = \
|
|
||||||
_sort_gpg_recipients(gpg_recipients)
|
|
||||||
|
|
||||||
if recipients_mime:
|
|
||||||
# Encrypt mail with PGP/MIME
|
|
||||||
_gpg_encrypt_and_deliver(raw_message,
|
|
||||||
keys_mime, recipients_mime,
|
|
||||||
_encrypt_all_payloads_mime)
|
|
||||||
|
|
||||||
if recipients_inline:
|
|
||||||
# Encrypt mail with PGP/INLINE
|
|
||||||
_gpg_encrypt_and_deliver(raw_message,
|
|
||||||
keys_inline, recipients_inline,
|
|
||||||
_encrypt_all_payloads_inline)
|
|
||||||
|
|
||||||
LOG.info('Not processed emails: %s', cleartext_recipients)
|
|
||||||
return cleartext_recipients
|
|
||||||
|
|
||||||
|
|
||||||
def _sort_gpg_recipients(gpg_to):
|
|
||||||
mime = RecipientList()
|
|
||||||
inline = RecipientList()
|
|
||||||
|
|
||||||
recipients_mime = list()
|
|
||||||
keys_mime = list()
|
|
||||||
|
|
||||||
recipients_inline = list()
|
|
||||||
keys_inline = list()
|
|
||||||
|
|
||||||
default_to_pgp_mime = conf.flag_enabled('default', 'mime_conversion')
|
|
||||||
|
|
||||||
for rcpt in gpg_to:
|
|
||||||
# Checking pre defined styles in settings first
|
|
||||||
if conf.config_item_equals('pgp_style', rcpt.email(), 'mime'):
|
|
||||||
recipients_mime.append(rcpt.email())
|
|
||||||
keys_mime.extend(rcpt.key().split(','))
|
|
||||||
mime += rcpt
|
|
||||||
elif conf.config_item_equals('pgp_style', rcpt.email(), 'inline'):
|
|
||||||
recipients_inline.append(rcpt.email())
|
|
||||||
keys_inline.extend(rcpt.key().split(','))
|
|
||||||
inline += rcpt
|
|
||||||
else:
|
|
||||||
# Log message only if an unknown style is defined
|
|
||||||
if conf.config_item_set('pgp_style', rcpt.email()):
|
|
||||||
LOG.debug("Style %s for recipient %s is not known. Use default as fallback."
|
|
||||||
% (conf.get_item("pgp_style", rcpt.email()), rcpt.email()))
|
|
||||||
|
|
||||||
# If no style is in settings defined for recipient, use default from settings
|
|
||||||
if default_to_pgp_mime:
|
|
||||||
recipients_mime.append(rcpt.email())
|
|
||||||
keys_mime.extend(rcpt.key().split(','))
|
|
||||||
mime += rcpt
|
|
||||||
else:
|
|
||||||
recipients_inline.append(rcpt.email())
|
|
||||||
keys_inline.extend(rcpt.key().split(','))
|
|
||||||
inline += rcpt
|
|
||||||
|
|
||||||
# mime = RecipientList(recipients_mime, keys_mime)
|
|
||||||
# inline = RecipientList(recipients_inline, keys_inline)
|
|
||||||
|
|
||||||
return recipients_mime, keys_mime, recipients_inline, keys_inline
|
|
||||||
|
|
||||||
|
|
||||||
def _gpg_encrypt_copy(message: EmailMessage, keys, recipients, encrypt_f):
|
|
||||||
msg_copy = copy.deepcopy(message)
|
|
||||||
_customise_headers(msg_copy)
|
|
||||||
encrypted_payloads = encrypt_f(msg_copy, keys)
|
|
||||||
msg_copy.set_payload(encrypted_payloads)
|
|
||||||
return msg_copy
|
|
||||||
|
|
||||||
|
|
||||||
def _gpg_encrypt_to_bytes(message: EmailMessage, keys, recipients, encrypt_f) -> bytes:
|
|
||||||
msg_copy = _gpg_encrypt_copy(message, keys, recipients, encrypt_f)
|
|
||||||
return msg_copy.as_bytes(policy=SMTPUTF8)
|
|
||||||
|
|
||||||
|
|
||||||
def _gpg_encrypt_to_str(message: EmailMessage, keys, recipients, encrypt_f) -> str:
|
|
||||||
msg_copy = _gpg_encrypt_copy(message, keys, recipients, encrypt_f)
|
|
||||||
return msg_copy.as_string(policy=SMTPUTF8)
|
|
||||||
|
|
||||||
|
|
||||||
def _gpg_encrypt_and_deliver(message: EmailMessage, keys, recipients, encrypt_f):
|
|
||||||
out = _gpg_encrypt_to_str(message, keys, recipients, encrypt_f)
|
|
||||||
send_msg(out, recipients)
|
|
||||||
|
|
||||||
|
|
||||||
def _customise_headers(message: EmailMessage):
|
|
||||||
if conf.config_item_equals('default', 'add_header', 'yes'):
|
|
||||||
message['X-GPG-Mailgate'] = 'Encrypted by GPG Mailgate'
|
|
||||||
|
|
||||||
if 'Content-Transfer-Encoding' in message:
|
|
||||||
message.replace_header('Content-Transfer-Encoding', '8BIT')
|
|
||||||
else:
|
|
||||||
message['Content-Transfer-Encoding'] = '8BIT'
|
|
||||||
|
|
||||||
|
|
||||||
def _load_keys():
|
|
||||||
"""Return a map from a key's fingerprint to email address."""
|
|
||||||
keyring = kcache.KeyRing(conf.get_item('gpg', 'keyhome'))
|
|
||||||
return asyncio.run(keyring.freeze_identities())
|
|
||||||
|
|
||||||
|
|
||||||
class GpgRecipient:
|
class GpgRecipient:
|
||||||
"""A tuple-like object that contains GPG recipient data."""
|
"""A tuple-like object that contains GPG recipient data."""
|
||||||
|
|
||||||
|
@ -199,23 +86,151 @@ class RecipientList:
|
||||||
|
|
||||||
def __init__(self, recipients=[], keys=[]):
|
def __init__(self, recipients=[], keys=[]):
|
||||||
"""Initialise lists of recipients and identities."""
|
"""Initialise lists of recipients and identities."""
|
||||||
self._recipients = recipients
|
self._recipients = [GpgRecipient(email, key) for (email, key) in zip(recipients, keys)]
|
||||||
|
|
||||||
def emails(self):
|
def emails(self):
|
||||||
"""Return list of recipients."""
|
"""Return list of recipients."""
|
||||||
for r in self._recipients:
|
LOG.debug('Recipient emails from: %s', self._recipients)
|
||||||
yield r.email()
|
return [r.email() for r in self._recipients]
|
||||||
|
|
||||||
def keys(self):
|
def keys(self):
|
||||||
"""Return list of GPG identities."""
|
"""Return list of GPG identities."""
|
||||||
for r in self._recipients:
|
LOG.debug('Recipient keys from: %s', self._recipients)
|
||||||
yield r.key()
|
return [r.key() for r in self._recipients]
|
||||||
|
|
||||||
def __iadd__(self, recipient: GpgRecipient):
|
def __iadd__(self, recipient: GpgRecipient):
|
||||||
"""Append a recipient."""
|
"""Append a recipient."""
|
||||||
|
LOG.debug('Adding %s to %s', recipient, self._recipients)
|
||||||
self._recipients.append(recipient)
|
self._recipients.append(recipient)
|
||||||
|
LOG.debug('Added; got: %s', self._recipients)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""Provide len().
|
||||||
|
|
||||||
|
With this method, it is possible to write code like:
|
||||||
|
|
||||||
|
rl = RecipientList()
|
||||||
|
if rl:
|
||||||
|
# do something
|
||||||
|
"""
|
||||||
|
return len(self._recipients)
|
||||||
|
|
||||||
|
|
||||||
|
def _gpg_encrypt(raw_message, recipients):
|
||||||
|
if not conf.config_item_set('gpg', 'keyhome'):
|
||||||
|
LOG.error("No valid entry for gpg keyhome. Encryption aborted.")
|
||||||
|
return recipients
|
||||||
|
|
||||||
|
gpg_recipients, cleartext_recipients = _identify_gpg_recipients(recipients, _load_keys())
|
||||||
|
|
||||||
|
LOG.info(f"Got addresses: gpg_to={gpg_recipients!r}, ungpg_to={cleartext_recipients!r}")
|
||||||
|
|
||||||
|
if gpg_recipients:
|
||||||
|
LOG.info("Encrypting email to: %s", gpg_recipients)
|
||||||
|
|
||||||
|
mime, inline = _sort_gpg_recipients(gpg_recipients)
|
||||||
|
|
||||||
|
if mime:
|
||||||
|
# Encrypt mail with PGP/MIME
|
||||||
|
_gpg_encrypt_and_deliver(raw_message,
|
||||||
|
mime.keys(), mime.emails(),
|
||||||
|
_encrypt_all_payloads_mime)
|
||||||
|
|
||||||
|
if inline:
|
||||||
|
# Encrypt mail with PGP/INLINE
|
||||||
|
_gpg_encrypt_and_deliver(raw_message,
|
||||||
|
inline.keys(), inline.emails(),
|
||||||
|
_encrypt_all_payloads_inline)
|
||||||
|
|
||||||
|
LOG.info('Not processed emails: %s', cleartext_recipients)
|
||||||
|
return cleartext_recipients
|
||||||
|
|
||||||
|
|
||||||
|
def _sort_gpg_recipients(gpg_to) -> Tuple[RecipientList, RecipientList]:
|
||||||
|
mime = RecipientList()
|
||||||
|
inline = RecipientList()
|
||||||
|
|
||||||
|
recipients_mime = list()
|
||||||
|
keys_mime = list()
|
||||||
|
|
||||||
|
recipients_inline = list()
|
||||||
|
keys_inline = list()
|
||||||
|
|
||||||
|
default_to_pgp_mime = conf.flag_enabled('default', 'mime_conversion')
|
||||||
|
|
||||||
|
for rcpt in gpg_to:
|
||||||
|
# Checking pre defined styles in settings first
|
||||||
|
if conf.config_item_equals('pgp_style', rcpt.email(), 'mime'):
|
||||||
|
recipients_mime.append(rcpt.email())
|
||||||
|
keys_mime.extend(rcpt.key().split(','))
|
||||||
|
mime += rcpt
|
||||||
|
elif conf.config_item_equals('pgp_style', rcpt.email(), 'inline'):
|
||||||
|
recipients_inline.append(rcpt.email())
|
||||||
|
keys_inline.extend(rcpt.key().split(','))
|
||||||
|
inline += rcpt
|
||||||
|
else:
|
||||||
|
# Log message only if an unknown style is defined
|
||||||
|
if conf.config_item_set('pgp_style', rcpt.email()):
|
||||||
|
LOG.debug("Style %s for recipient %s is not known. Use default as fallback."
|
||||||
|
% (conf.get_item("pgp_style", rcpt.email()), rcpt.email()))
|
||||||
|
|
||||||
|
# If no style is in settings defined for recipient, use default from settings
|
||||||
|
if default_to_pgp_mime:
|
||||||
|
recipients_mime.append(rcpt.email())
|
||||||
|
keys_mime.extend(rcpt.key().split(','))
|
||||||
|
mime += rcpt
|
||||||
|
else:
|
||||||
|
recipients_inline.append(rcpt.email())
|
||||||
|
keys_inline.extend(rcpt.key().split(','))
|
||||||
|
inline += rcpt
|
||||||
|
|
||||||
|
mime = RecipientList(recipients_mime, keys_mime)
|
||||||
|
inline = RecipientList(recipients_inline, keys_inline)
|
||||||
|
|
||||||
|
LOG.debug('Loaded recipients: MIME %s; Inline %s', repr(mime), repr(inline))
|
||||||
|
|
||||||
|
return mime, inline
|
||||||
|
|
||||||
|
|
||||||
|
def _gpg_encrypt_copy(message: EmailMessage, keys, recipients, encrypt_f):
|
||||||
|
msg_copy = copy.deepcopy(message)
|
||||||
|
_customise_headers(msg_copy)
|
||||||
|
encrypted_payloads = encrypt_f(msg_copy, keys)
|
||||||
|
msg_copy.set_payload(encrypted_payloads)
|
||||||
|
return msg_copy
|
||||||
|
|
||||||
|
|
||||||
|
def _gpg_encrypt_to_bytes(message: EmailMessage, keys, recipients, encrypt_f) -> bytes:
|
||||||
|
msg_copy = _gpg_encrypt_copy(message, keys, recipients, encrypt_f)
|
||||||
|
return msg_copy.as_bytes(policy=SMTPUTF8)
|
||||||
|
|
||||||
|
|
||||||
|
def _gpg_encrypt_to_str(message: EmailMessage, keys, recipients, encrypt_f) -> str:
|
||||||
|
msg_copy = _gpg_encrypt_copy(message, keys, recipients, encrypt_f)
|
||||||
|
return msg_copy.as_string(policy=SMTPUTF8)
|
||||||
|
|
||||||
|
|
||||||
|
def _gpg_encrypt_and_deliver(message: EmailMessage, keys, recipients, encrypt_f):
|
||||||
|
out = _gpg_encrypt_to_str(message, keys, recipients, encrypt_f)
|
||||||
|
send_msg(out, recipients)
|
||||||
|
|
||||||
|
|
||||||
|
def _customise_headers(message: EmailMessage):
|
||||||
|
if conf.config_item_equals('default', 'add_header', 'yes'):
|
||||||
|
message['X-GPG-Mailgate'] = 'Encrypted by GPG Mailgate'
|
||||||
|
|
||||||
|
if 'Content-Transfer-Encoding' in message:
|
||||||
|
message.replace_header('Content-Transfer-Encoding', '8BIT')
|
||||||
|
else:
|
||||||
|
message['Content-Transfer-Encoding'] = '8BIT'
|
||||||
|
|
||||||
|
|
||||||
|
def _load_keys():
|
||||||
|
"""Return a map from a key's fingerprint to email address."""
|
||||||
|
keyring = kcache.KeyRing(conf.get_item('gpg', 'keyhome'))
|
||||||
|
return asyncio.run(keyring.freeze_identities())
|
||||||
|
|
||||||
|
|
||||||
def _identify_gpg_recipients(recipients, keys: kcache.KeyCache):
|
def _identify_gpg_recipients(recipients, keys: kcache.KeyCache):
|
||||||
# This list will be filled with pairs (M, N), where M is the destination
|
# This list will be filled with pairs (M, N), where M is the destination
|
||||||
|
@ -609,16 +624,15 @@ def delivery_plan(recipients, message: EmailMessage, key_cache: kcache.KeyCache)
|
||||||
|
|
||||||
gpg_recipients, cleartext_recipients = _identify_gpg_recipients(recipients, key_cache)
|
gpg_recipients, cleartext_recipients = _identify_gpg_recipients(recipients, key_cache)
|
||||||
|
|
||||||
mime_recipients, mime_keys, inline_recipients, inline_keys = \
|
mime, inline = _sort_gpg_recipients(gpg_recipients)
|
||||||
_sort_gpg_recipients(gpg_recipients)
|
|
||||||
|
|
||||||
keyhome = conf.get_item('gpg', 'keyhome')
|
keyhome = conf.get_item('gpg', 'keyhome')
|
||||||
|
|
||||||
plan = []
|
plan = []
|
||||||
if mime_recipients:
|
if mime:
|
||||||
plan.append(MimeOpenPGPEncrypt(mime_recipients, mime_keys, keyhome))
|
plan.append(MimeOpenPGPEncrypt(mime.emails(), mime.keys(), keyhome))
|
||||||
if inline_recipients:
|
if inline:
|
||||||
plan.append(InlineOpenPGPEncrypt(inline_recipients, inline_keys, keyhome))
|
plan.append(InlineOpenPGPEncrypt(inline.emails(), inline.keys(), keyhome))
|
||||||
if cleartext_recipients:
|
if cleartext_recipients:
|
||||||
plan.append(KeepIntact(cleartext_recipients))
|
plan.append(KeepIntact(cleartext_recipients))
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue