From fb9f42cd1ebdded7c3c579fd8046d37ee0a85849 Mon Sep 17 00:00:00 2001 From: J08nY Date: Tue, 18 Jul 2017 23:34:30 +0200 Subject: Add outgoing signing and encryption via custom Delivery classes. --- src/mailman_pgp/config/mailman.cfg | 3 + src/mailman_pgp/model/list.py | 1 + src/mailman_pgp/mta/__init__.py | 0 src/mailman_pgp/mta/bulk.py | 103 +++++++++++++++ src/mailman_pgp/mta/deliver.py | 136 ++++++++++++++++++++ src/mailman_pgp/mta/personalized.py | 86 +++++++++++++ src/mailman_pgp/mta/tests/__init__.py | 0 src/mailman_pgp/mta/tests/test_bulk.py | 168 +++++++++++++++++++++++++ src/mailman_pgp/mta/tests/test_personalized.py | 144 +++++++++++++++++++++ 9 files changed, 641 insertions(+) create mode 100644 src/mailman_pgp/mta/__init__.py create mode 100644 src/mailman_pgp/mta/bulk.py create mode 100644 src/mailman_pgp/mta/deliver.py create mode 100644 src/mailman_pgp/mta/personalized.py create mode 100644 src/mailman_pgp/mta/tests/__init__.py create mode 100644 src/mailman_pgp/mta/tests/test_bulk.py create mode 100644 src/mailman_pgp/mta/tests/test_personalized.py diff --git a/src/mailman_pgp/config/mailman.cfg b/src/mailman_pgp/config/mailman.cfg index 80be4b5..24dc3bc 100644 --- a/src/mailman_pgp/config/mailman.cfg +++ b/src/mailman_pgp/config/mailman.cfg @@ -23,6 +23,9 @@ path: mailman_pgp enable: yes configuration: python:mailman_pgp.config.mailman_pgp +[mta] +outgoing: mailman_pgp.mta.deliver.deliver + [runner.in] class: mailman_pgp.runners.incoming.PGPIncomingRunner diff --git a/src/mailman_pgp/model/list.py b/src/mailman_pgp/model/list.py index da542de..ad031c5 100644 --- a/src/mailman_pgp/model/list.py +++ b/src/mailman_pgp/model/list.py @@ -57,6 +57,7 @@ class PGPMailingList(Base): # Encryption related properties nonencrypted_msg_action = Column(Enum(Action), default=Action.reject) + encrypt_outgoing = Column(Boolean, default=True) def __init__(self, mlist): super().__init__() diff --git a/src/mailman_pgp/mta/__init__.py b/src/mailman_pgp/mta/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/mailman_pgp/mta/bulk.py b/src/mailman_pgp/mta/bulk.py new file mode 100644 index 0000000..d22fec7 --- /dev/null +++ b/src/mailman_pgp/mta/bulk.py @@ -0,0 +1,103 @@ +# Copyright (C) 2017 Jan Jancar +# +# This file is a part of the Mailman PGP plugin. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +"""""" +import copy + +from mailman.mta.bulk import BulkDelivery +from public import public + +from mailman_pgp.model.address import PGPAddress +from mailman_pgp.model.list import PGPMailingList +from mailman_pgp.pgp.mime import MIMEWrapper +from mailman_pgp.utils.email import overwrite_message + + +class CallbackBulkDelivery(BulkDelivery): + """""" + + def __init__(self, max_recipients=None): + super().__init__(max_recipients=max_recipients) + self.callbacks = [] + + def deliver(self, mlist, msg, msgdata): + """See `IMailTransportAgentDelivery`.""" + refused = {} + for recipients in self.chunkify(msgdata.get('recipients', set())): + message_copy = copy.deepcopy(msg) + msgdata_copy = msgdata.copy() + for callback in self.callbacks: + callback(mlist, message_copy, msgdata_copy, recipients) + chunk_refused = self._deliver_to_recipients( + mlist, message_copy, msgdata_copy, recipients) + refused.update(chunk_refused) + return refused + + +class PGPBulkMixin: + """""" + + def sign_encrypt(self, mlist, msg, msgdata, recipients): + """ + + :param mlist: + :type mlist: mailman.model.mailinglist.MailingList + :param msg: + :type msg: mailman.email.message.Message + :param msgdata: + :type msgdata: dict + :param recipients: + :type recipients: list + """ + pgp_list = PGPMailingList.for_list(mlist) + if not pgp_list: + return + + keys = [] + for recipient in recipients: + pgp_address = PGPAddress.for_email(recipient) + if pgp_address is None: + # TODO: log failure here + continue + if pgp_address.key is None or not pgp_address.key_confirmed: + # TODO: log failure here? + continue + keys.append(pgp_address.key) + + wrapped = MIMEWrapper(msg) + out = msg + if pgp_list.sign_outgoing: + if pgp_list.encrypt_outgoing: + out = wrapped.sign_encrypt(pgp_list.key, pgp_list.pubkey, + *keys, throw_keyid=True) + else: + out = wrapped.sign(pgp_list.key) + else: + if pgp_list.encrypt_outgoing: + out = wrapped.encrypt(pgp_list.pubkey, *keys, throw_keyid=True) + + if out is not msg: + overwrite_message(out, msg) + + +@public +class PGPBulkDelivery(CallbackBulkDelivery, PGPBulkMixin): + """""" + + def __init__(self, max_recipients=None): + super().__init__(max_recipients=max_recipients) + self.callbacks.append(self.sign_encrypt) diff --git a/src/mailman_pgp/mta/deliver.py b/src/mailman_pgp/mta/deliver.py new file mode 100644 index 0000000..050a740 --- /dev/null +++ b/src/mailman_pgp/mta/deliver.py @@ -0,0 +1,136 @@ +# Copyright (C) 2017 Jan Jancar +# +# This file is a part of the Mailman PGP plugin. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +"""""" +import logging +import time + +from mailman.config import config +from mailman.interfaces.mailinglist import Personalization +from mailman.interfaces.mta import SomeRecipientsFailed +from mailman.mta.bulk import BulkDelivery +from mailman.mta.deliver import Deliver +from mailman.utilities.string import expand +from public import public + +from mailman_pgp.model.list import PGPMailingList +from mailman_pgp.mta.bulk import PGPBulkDelivery +from mailman_pgp.mta.personalized import PGPPersonalizedDelivery + +COMMA = ',' +log = logging.getLogger('mailman.smtp') + + +@public +def deliver(mlist, msg, msgdata): + """Deliver a message to the outgoing mail server.""" + # If there are no recipients, there's nothing to do. + recipients = msgdata.get('recipients') + if not recipients: + # Could be None, could be an empty sequence. + return + # Which delivery agent should we use? Several situations can cause us to + # use individual delivery. If not specified, use bulk delivery. See the + # to-outgoing handler for when the 'verp' key is set in the metadata. + personalized_agent = Deliver + bulk_agent = BulkDelivery + + pgp_list = PGPMailingList.for_list(mlist) + if pgp_list: + personalized_agent = PGPPersonalizedDelivery + bulk_agent = PGPBulkDelivery + + if msgdata.get('verp', False): + agent = personalized_agent() + elif mlist.personalize != Personalization.none: + agent = personalized_agent() + else: + agent = bulk_agent(int(config.mta.max_recipients)) + log.debug('Using agent: %s', agent) + # Keep track of the original recipients and the original sender for + # logging purposes. + original_recipients = msgdata['recipients'] + original_sender = msgdata.get('original-sender', msg.sender) + # Let the agent attempt to deliver to the recipients. Record all failures + # for re-delivery later. + t0 = time.time() + refused = agent.deliver(mlist, msg, msgdata) + t1 = time.time() + # Log this posting. + size = getattr(msg, 'original_size', msgdata.get('original_size')) + if size is None: + size = len(msg.as_string()) + substitutions = dict( + msgid=msg.get('message-id', 'n/a'), # noqa: E221, E251 + listname=mlist.fqdn_listname, # noqa: E221, E251 + sender=original_sender, # noqa: E221, E251 + recip=len(original_recipients), # noqa: E221, E251 + size=size, # noqa: E221, E251 + time=t1 - t0, # noqa: E221, E251 + refused=len(refused), # noqa: E221, E251 + smtpcode='n/a', # noqa: E221, E251 + smtpmsg='n/a', # noqa: E221, E251 + ) + template = config.logging.smtp.every + if template.lower() != 'no': + log.info('%s', expand(template, mlist, substitutions)) + if refused: + template = config.logging.smtp.refused + if template.lower() != 'no': + log.info('%s', expand(template, mlist, substitutions)) + else: + # Log the successful post, but if it was not destined to the mailing + # list (e.g. to the owner or admin), print the actual recipients + # instead of just the number. + if not msgdata.get('tolist', False): + recips = msg.get_all('to', []) + recips.extend(msg.get_all('cc', [])) + substitutions['recips'] = COMMA.join(recips) + template = config.logging.smtp.success + if template.lower() != 'no': + log.info('%s', expand(template, mlist, substitutions)) + # Process any failed deliveries. + temporary_failures = [] + permanent_failures = [] + for recipient, (code, smtp_message) in refused.items(): + # RFC 5321, $4.5.3.1.10 says: + # + # RFC 821 [1] incorrectly listed the error where an SMTP server + # exhausts its implementation limit on the number of RCPT commands + # ("too many recipients") as having reply code 552. The correct + # reply code for this condition is 452. Clients SHOULD treat a 552 + # code in this case as a temporary, rather than permanent, failure + # so the logic below works. + # + if code >= 500 and code != 552: + # A permanent failure + permanent_failures.append(recipient) + else: + # Deal with persistent transient failures by queuing them up for + # future delivery. TBD: this could generate lots of log entries! + temporary_failures.append(recipient) + template = config.logging.smtp.failure + if template.lower() != 'no': + substitutions.update( + recip=recipient, # noqa: E221, E251 + smtpcode=code, # noqa: E221, E251 + smtpmsg=smtp_message # noqa: E221, E251 + ) + log.info('%s', expand(template, mlist, substitutions)) + # Return the results + if temporary_failures or permanent_failures: + raise SomeRecipientsFailed(temporary_failures, permanent_failures) diff --git a/src/mailman_pgp/mta/personalized.py b/src/mailman_pgp/mta/personalized.py new file mode 100644 index 0000000..0d58328 --- /dev/null +++ b/src/mailman_pgp/mta/personalized.py @@ -0,0 +1,86 @@ +# Copyright (C) 2017 Jan Jancar +# +# This file is a part of the Mailman PGP plugin. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +"""""" +from mailman.mta.base import IndividualDelivery +from mailman.mta.decorating import DecoratingMixin +from mailman.mta.personalized import PersonalizedMixin +from mailman.mta.verp import VERPMixin +from public import public + +from mailman_pgp.model.address import PGPAddress +from mailman_pgp.model.list import PGPMailingList +from mailman_pgp.pgp.mime import MIMEWrapper +from mailman_pgp.utils.email import overwrite_message + + +class PGPIndividualMixin: + def sign_encrypt(self, mlist, msg, msgdata): + """ + + :param mlist: + :type mlist: mailman.model.mailinglist.MailingList + :param msg: + :type msg: mailman.email.message.Message + :param msgdata: + :type msgdata: dict + """ + pgp_list = PGPMailingList.for_list(mlist) + if not pgp_list: + return + if not pgp_list.encrypt_outgoing and not pgp_list.sign_outgoing: + # nothing to do + return + + recipient = msgdata['recipient'] + pgp_address = PGPAddress.for_email(recipient) + if pgp_address is None: + # TODO: log failure here + return + if pgp_address.key is None or not pgp_address.key_confirmed: + # TODO: log failure here? + return + + key = pgp_address.key + wrapped = MIMEWrapper(msg) + out = msg + if pgp_list.sign_outgoing: + if pgp_list.encrypt_outgoing: + out = wrapped.sign_encrypt(pgp_list.key, key, pgp_list.pubkey) + else: + out = wrapped.sign(pgp_list.key) + else: + if pgp_list.encrypt_outgoing: + out = wrapped.encrypt(key, pgp_list.pubkey) + + if out is not msg: + overwrite_message(out, msg) + + +@public +class PGPPersonalizedDelivery(VERPMixin, DecoratingMixin, PersonalizedMixin, + IndividualDelivery, PGPIndividualMixin): + """""" + + def __init__(self): + super().__init__() + self.callbacks.extend([ + self.avoid_duplicates, + self.decorate, + self.personalize_to, + self.sign_encrypt + ]) diff --git a/src/mailman_pgp/mta/tests/__init__.py b/src/mailman_pgp/mta/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/mailman_pgp/mta/tests/test_bulk.py b/src/mailman_pgp/mta/tests/test_bulk.py new file mode 100644 index 0000000..bd0db9e --- /dev/null +++ b/src/mailman_pgp/mta/tests/test_bulk.py @@ -0,0 +1,168 @@ +# Copyright (C) 2017 Jan Jancar +# +# This file is a part of the Mailman PGP plugin. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +"""""" +import unittest + +from mailman.app.lifecycle import create_list +from mailman.interfaces.mailinglist import Personalization +from mailman.testing.helpers import ( + specialized_message_from_string as mfs, subscribe) + +from mailman_pgp.database import transaction +from mailman_pgp.model.address import PGPAddress +from mailman_pgp.model.list import PGPMailingList +from mailman_pgp.mta.bulk import (PGPBulkDelivery) +from mailman_pgp.pgp.tests.base import load_key +from mailman_pgp.pgp.wrapper import PGPWrapper +from mailman_pgp.testing.layers import PGPConfigLayer +from mailman_pgp.utils.pgp import verifies + + +class BulkDeliveryTester(PGPBulkDelivery): + """""" + + def __init__(self, max_recipients=None): + super().__init__(max_recipients=max_recipients) + self.deliveries = [] + + def _deliver_to_recipients(self, mlist, msg, msgdata, recipients): + self.deliveries.append((mlist, msg, msgdata, recipients)) + return [] + + +class TestPGPBulkDelivery(unittest.TestCase): + layer = PGPConfigLayer + + def setUp(self): + self.mlist = create_list('test@example.com', style_name='pgp-default') + self.mlist.personalize = Personalization.none + + self.list_key = load_key('ecc_p256.priv.asc') + self.pgp_list = PGPMailingList.for_list(self.mlist) + self.pgp_list.key = self.list_key + + # Make Anne a member of this mailing list. + self.anne = subscribe(self.mlist, 'Anne', email='anne@example.org') + self.anne_key = load_key('rsa_1024.priv.asc') + + self.bart = subscribe(self.mlist, 'Bart', email='bart@example.org') + self.bart_key = load_key('ecc_secp256k1.priv.asc') + + with transaction() as t: + self.pgp_anne = PGPAddress(self.anne.address) + self.pgp_anne.key = self.anne_key.pubkey + self.pgp_anne.key_confirmed = True + t.add(self.pgp_anne) + + with transaction() as t: + self.pgp_bart = PGPAddress(self.bart.address) + self.pgp_bart.key = self.bart_key.pubkey + self.pgp_bart.key_confirmed = True + t.add(self.pgp_bart) + + # Clear out any results from the previous test. + self.msg = mfs("""\ +From: anne@example.org +To: test@example.com +Subject: test + +""") + + def test_sign_encrypt(self): + with transaction(): + self.pgp_list.sign_outgoing = True + self.pgp_list.encrypt_outgoing = True + + msgdata = dict(recipients=['anne@example.org', 'bart@example.org']) + agent = BulkDeliveryTester(2) + refused = agent.deliver(self.mlist, self.msg, msgdata) + + self.assertEqual(len(refused), 0) + self.assertEqual(len(agent.deliveries), 1) + + out_msg = agent.deliveries[0][1] + out_wrapped = PGPWrapper(out_msg) + self.assertTrue(out_wrapped.is_encrypted()) + + decrypted = out_wrapped.decrypt(self.list_key) + wrapped = PGPWrapper(decrypted) + self.assertTrue(wrapped.is_signed()) + self.assertTrue(verifies(wrapped.verify(self.list_key.pubkey))) + + decrypted = out_wrapped.decrypt(self.anne_key) + wrapped = PGPWrapper(decrypted) + self.assertTrue(wrapped.is_signed()) + self.assertTrue(verifies(wrapped.verify(self.list_key.pubkey))) + + decrypted = out_wrapped.decrypt(self.bart_key) + wrapped = PGPWrapper(decrypted) + self.assertTrue(wrapped.is_signed()) + self.assertTrue(verifies(wrapped.verify(self.list_key.pubkey))) + + def test_encrypt(self): + with transaction(): + self.pgp_list.sign_outgoing = False + self.pgp_list.encrypt_outgoing = True + + msgdata = dict(recipients=['anne@example.org', 'bart@example.org']) + agent = BulkDeliveryTester(2) + refused = agent.deliver(self.mlist, self.msg, msgdata) + + self.assertEqual(len(refused), 0) + self.assertEqual(len(agent.deliveries), 1) + + out_msg = agent.deliveries[0][1] + wrapped = PGPWrapper(out_msg) + self.assertTrue(wrapped.is_encrypted()) + wrapped.decrypt(self.list_key) + wrapped.decrypt(self.anne_key) + wrapped.decrypt(self.bart_key) + + def test_sign(self): + with transaction(): + self.pgp_list.sign_outgoing = True + self.pgp_list.encrypt_outgoing = False + + msgdata = dict(recipients=['anne@example.org', 'bart@example.org']) + agent = BulkDeliveryTester(2) + refused = agent.deliver(self.mlist, self.msg, msgdata) + + self.assertEqual(len(refused), 0) + self.assertEqual(len(agent.deliveries), 1) + + out_msg = agent.deliveries[0][1] + wrapped = PGPWrapper(out_msg) + self.assertTrue(wrapped.is_signed()) + self.assertTrue(verifies(wrapped.verify(self.list_key.pubkey))) + + def test_none(self): + with transaction(): + self.pgp_list.sign_outgoing = False + self.pgp_list.encrypt_outgoing = False + + msgdata = dict(recipients=['anne@example.org', 'bart@example.org']) + agent = BulkDeliveryTester(2) + refused = agent.deliver(self.mlist, self.msg, msgdata) + + self.assertEqual(len(refused), 0) + self.assertEqual(len(agent.deliveries), 1) + + out_msg = agent.deliveries[0][1] + wrapped = PGPWrapper(out_msg) + self.assertFalse(wrapped.is_signed()) + self.assertFalse(wrapped.is_encrypted()) diff --git a/src/mailman_pgp/mta/tests/test_personalized.py b/src/mailman_pgp/mta/tests/test_personalized.py new file mode 100644 index 0000000..5550c4d --- /dev/null +++ b/src/mailman_pgp/mta/tests/test_personalized.py @@ -0,0 +1,144 @@ +# Copyright (C) 2017 Jan Jancar +# +# This file is a part of the Mailman PGP plugin. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +"""""" +import unittest + +from mailman.app.lifecycle import create_list +from mailman.interfaces.mailinglist import Personalization +from mailman.testing.helpers import ( + specialized_message_from_string as mfs, subscribe) + +from mailman_pgp.database import transaction +from mailman_pgp.model.address import PGPAddress +from mailman_pgp.model.list import PGPMailingList +from mailman_pgp.mta.personalized import PGPPersonalizedDelivery +from mailman_pgp.pgp.tests.base import load_key +from mailman_pgp.pgp.wrapper import PGPWrapper +from mailman_pgp.testing.layers import PGPConfigLayer + + +class PersonalizedDeliveryTester(PGPPersonalizedDelivery): + """""" + + def __init__(self): + super().__init__() + self.deliveries = [] + + def _deliver_to_recipients(self, mlist, msg, msgdata, recipients): + self.deliveries.append((mlist, msg, msgdata, recipients)) + return [] + + +class TestPGPPersonalizedDelivery(unittest.TestCase): + layer = PGPConfigLayer + + def setUp(self): + self.mlist = create_list('test@example.com', style_name='pgp-default') + self.mlist.personalize = Personalization.individual + + self.list_key = load_key('ecc_p256.priv.asc') + self.pgp_list = PGPMailingList.for_list(self.mlist) + self.pgp_list.key = self.list_key + + # Make Anne a member of this mailing list. + self.anne = subscribe(self.mlist, 'Anne', email='anne@example.org') + self.anne_key = load_key('rsa_1024.priv.asc') + with transaction() as t: + self.pgp_anne = PGPAddress(self.anne.address) + self.pgp_anne.key = self.anne_key.pubkey + self.pgp_anne.key_confirmed = True + t.add(self.pgp_anne) + + # Clear out any results from the previous test. + self.msg = mfs("""\ +From: anne@example.org +To: test@example.com +Subject: test + +""") + + def test_sign_encrypt(self): + with transaction(): + self.pgp_list.sign_outgoing = True + self.pgp_list.encrypt_outgoing = True + + msgdata = dict(recipients=['anne@example.org']) + agent = PersonalizedDeliveryTester() + refused = agent.deliver(self.mlist, self.msg, msgdata) + + self.assertEqual(len(refused), 0) + self.assertEqual(len(agent.deliveries), 1) + + out_msg = agent.deliveries[0][1] + wrapped = PGPWrapper(out_msg) + self.assertTrue(wrapped.is_encrypted()) + decrypted = wrapped.decrypt(self.anne_key) + wrapped = PGPWrapper(decrypted) + self.assertTrue(wrapped.is_signed()) + + def test_encrypt(self): + with transaction(): + self.pgp_list.sign_outgoing = False + self.pgp_list.encrypt_outgoing = True + + msgdata = dict(recipients=['anne@example.org']) + agent = PersonalizedDeliveryTester() + refused = agent.deliver(self.mlist, self.msg, msgdata) + + self.assertEqual(len(refused), 0) + self.assertEqual(len(agent.deliveries), 1) + + out_msg = agent.deliveries[0][1] + wrapped = PGPWrapper(out_msg) + self.assertTrue(wrapped.is_encrypted()) + decrypted = wrapped.decrypt(self.anne_key) + wrapped = PGPWrapper(decrypted) + self.assertFalse(wrapped.is_signed()) + + def test_sign(self): + with transaction(): + self.pgp_list.sign_outgoing = True + self.pgp_list.encrypt_outgoing = False + + msgdata = dict(recipients=['anne@example.org']) + agent = PersonalizedDeliveryTester() + refused = agent.deliver(self.mlist, self.msg, msgdata) + + self.assertEqual(len(refused), 0) + self.assertEqual(len(agent.deliveries), 1) + + out_msg = agent.deliveries[0][1] + wrapped = PGPWrapper(out_msg) + self.assertTrue(wrapped.is_signed()) + + def test_none(self): + with transaction(): + self.pgp_list.sign_outgoing = False + self.pgp_list.encrypt_outgoing = False + + msgdata = dict(recipients=['anne@example.org']) + agent = PersonalizedDeliveryTester() + refused = agent.deliver(self.mlist, self.msg, msgdata) + + self.assertEqual(len(refused), 0) + self.assertEqual(len(agent.deliveries), 1) + + out_msg = agent.deliveries[0][1] + wrapped = PGPWrapper(out_msg) + self.assertFalse(wrapped.is_signed()) + self.assertFalse(wrapped.is_encrypted()) -- cgit v1.2.3-70-g09d2