diff options
| author | Mark Sapiro | 2016-11-03 18:18:00 -0700 |
|---|---|---|
| committer | Mark Sapiro | 2016-11-03 18:18:00 -0700 |
| commit | fdac1fbf0016d53e59d59bee026c27be77bcaccb (patch) | |
| tree | 0a94fdf8849a7792800afd8386ae86c99871e6b4 /src | |
| parent | 8e0795f728c86ac88fba8894a827081c7852451b (diff) | |
| download | mailman-fdac1fbf0016d53e59d59bee026c27be77bcaccb.tar.gz mailman-fdac1fbf0016d53e59d59bee026c27be77bcaccb.tar.zst mailman-fdac1fbf0016d53e59d59bee026c27be77bcaccb.zip | |
Diffstat (limited to 'src')
| -rw-r--r-- | src/mailman/handlers/dmarc.py | 1 | ||||
| -rw-r--r-- | src/mailman/handlers/docs/dmarc-mitigations.rst | 11 | ||||
| -rw-r--r-- | src/mailman/handlers/tests/test_dmarc.py | 244 | ||||
| -rw-r--r-- | src/mailman/rules/docs/dmarc-moderation.rst | 25 |
4 files changed, 272 insertions, 9 deletions
diff --git a/src/mailman/handlers/dmarc.py b/src/mailman/handlers/dmarc.py index 29a90565b..9b8f1bd30 100644 --- a/src/mailman/handlers/dmarc.py +++ b/src/mailman/handlers/dmarc.py @@ -48,6 +48,7 @@ KEEPERS = ('archived-at', 'list-', 'precedence', 'references', + 'subject', 'to', 'x-mailman-', ) diff --git a/src/mailman/handlers/docs/dmarc-mitigations.rst b/src/mailman/handlers/docs/dmarc-mitigations.rst index f538182ad..ee7dd98e0 100644 --- a/src/mailman/handlers/docs/dmarc-mitigations.rst +++ b/src/mailman/handlers/docs/dmarc-mitigations.rst @@ -33,7 +33,10 @@ The settings and their effects are: * from_is_list: The action to be applied to all messages for which dmarc_moderation_action is none or not applicable. * reply_goes_to_list: If this is set to other than no_munging of Reply-To, - the original From: goes in Cc: rather than Reply-To:. + the original From: goes in Cc: rather than Reply-To:. This is intended to + make MUA functions of reply and reply-all have the same effect with + messages to which mitigations have been applied as they do with other + messages. The possible actions for both dmarc_moderation_action and from_is_list are: @@ -43,14 +46,14 @@ The possible actions for both dmarc_moderation_action and from_is_list are: * wrap_message: Wrap the message in an outer message with headers as in munge_from. -In addition, there are two more possible actions for dmarc_moderation_action -only: +In addition, there are two more possible actions (actually processed by the +dmarc-moderation rule) for dmarc_moderation_action only: * reject: Bounce the message back to the sender with a default reason or one supplied in dmarc_moderation_notice. * discard: Silently discard the message. -Here's what happens when we munge the From: +Here's what happens when we munge the From. >>> from mailman.interfaces.mailinglist import (DMARCModerationAction, ... ReplyToMunging) diff --git a/src/mailman/handlers/tests/test_dmarc.py b/src/mailman/handlers/tests/test_dmarc.py new file mode 100644 index 000000000..648fbab0e --- /dev/null +++ b/src/mailman/handlers/tests/test_dmarc.py @@ -0,0 +1,244 @@ +# Copyright (C) 2016 by the Free Software Foundation, Inc. +# +# This file is part of GNU Mailman. +# +# GNU Mailman 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. +# +# GNU Mailman 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 +# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. + +"""Test the dmarc handler.""" + +import unittest + +from mailman.app.lifecycle import create_list +from mailman.handlers import dmarc +from mailman.interfaces.mailinglist import ( + DMARCModerationAction, FromIsList, ReplyToMunging) +from mailman.testing.helpers import specialized_message_from_string as mfs +from mailman.testing.layers import ConfigLayer + + +class TestDMARCMitigations(unittest.TestCase): + """Test the dmarc handler.""" + + layer = ConfigLayer + + def setUp(self): + self._mlist = create_list('ant@example.com') + self._mlist.anonymous_list = False + self._mlist.dmarc_moderation_action = DMARCModerationAction.none + self._mlist.dmarc_wrapped_message_text = '' + self._mlist.from_is_list = FromIsList.none + self._mlist.reply_goes_to_list = ReplyToMunging.no_munging + # We can use the same message text for all tests. + self._text = """\ +From: anne@example.com +To: ant@example.com +Subject: A subject +X-Mailman-Version: X.Y +Message-ID: <some-id@example.com> +Date: Fri, 1 Jan 2016 00:00:01 +0000 +Another-Header: To test removal in wrapper +MIME-Version: 1.0 +Content-Type: multipart/alternative; boundary="=====abc==" + +--=====abc== +Content-Type: text/plain; charset="us-ascii" +Content-Transfer-Encoding: 7bit + +Some things to say. +--=====abc== +Content-Type: text/plain; charset="us-ascii" +Content-Transfer-Encoding: 7bit + +<html><head></head><body>Some things to say.</body></html> +--=====abc==-- +""" + + def test_default_no_change(self): + msg = mfs(self._text) + dmarc.process(self._mlist, msg, {}) + self.assertMultiLineEqual(msg.as_string(), self._text) + + def test_anonymous_no_change(self): + self._mlist.anonymous_list = True + self._mlist.dmarc_moderation_action = DMARCModerationAction.munge_from + msgdata = {'dmarc': True} + msg = mfs(self._text) + dmarc.process(self._mlist, msg, msgdata) + self.assertMultiLineEqual(msg.as_string(), self._text) + + def test_action_munge_from(self): + self._mlist.dmarc_moderation_action = DMARCModerationAction.munge_from + msgdata = {'dmarc': True} + msg = mfs(self._text) + dmarc.process(self._mlist, msg, msgdata) + self.assertMultiLineEqual(msg.as_string(), """\ +To: ant@example.com +Subject: A subject +X-Mailman-Version: X.Y +Message-ID: <some-id@example.com> +Date: Fri, 1 Jan 2016 00:00:01 +0000 +Another-Header: To test removal in wrapper +MIME-Version: 1.0 +Content-Type: multipart/alternative; boundary="=====abc==" +From: anne--- via Ant <ant@example.com> +Reply-To: anne@example.com + +--=====abc== +Content-Type: text/plain; charset="us-ascii" +Content-Transfer-Encoding: 7bit + +Some things to say. +--=====abc== +Content-Type: text/plain; charset="us-ascii" +Content-Transfer-Encoding: 7bit + +<html><head></head><body>Some things to say.</body></html> +--=====abc==-- +""") + + def test_no_action_without_msgdata(self): + self._mlist.dmarc_moderation_action = DMARCModerationAction.munge_from + msg = mfs(self._text) + dmarc.process(self._mlist, msg, {}) + self.assertMultiLineEqual(msg.as_string(), self._text) + + def test_from_is_list_no_msgdata(self): + self._mlist.from_is_list = FromIsList.munge_from + msg = mfs(self._text) + dmarc.process(self._mlist, msg, {}) + self.assertMultiLineEqual(msg.as_string(), """\ +To: ant@example.com +Subject: A subject +X-Mailman-Version: X.Y +Message-ID: <some-id@example.com> +Date: Fri, 1 Jan 2016 00:00:01 +0000 +Another-Header: To test removal in wrapper +MIME-Version: 1.0 +Content-Type: multipart/alternative; boundary="=====abc==" +From: anne--- via Ant <ant@example.com> +Reply-To: anne@example.com + +--=====abc== +Content-Type: text/plain; charset="us-ascii" +Content-Transfer-Encoding: 7bit + +Some things to say. +--=====abc== +Content-Type: text/plain; charset="us-ascii" +Content-Transfer-Encoding: 7bit + +<html><head></head><body>Some things to say.</body></html> +--=====abc==-- +""") + + def test_from_in_cc(self): + self._mlist.dmarc_moderation_action = DMARCModerationAction.munge_from + self._mlist.reply_goes_to_list = ReplyToMunging.point_to_list + msgdata = {'dmarc': True} + msg = mfs(self._text) + dmarc.process(self._mlist, msg, msgdata) + self.assertMultiLineEqual(msg.as_string(), """\ +To: ant@example.com +Subject: A subject +X-Mailman-Version: X.Y +Message-ID: <some-id@example.com> +Date: Fri, 1 Jan 2016 00:00:01 +0000 +Another-Header: To test removal in wrapper +MIME-Version: 1.0 +Content-Type: multipart/alternative; boundary="=====abc==" +From: anne--- via Ant <ant@example.com> +Cc: anne@example.com + +--=====abc== +Content-Type: text/plain; charset="us-ascii" +Content-Transfer-Encoding: 7bit + +Some things to say. +--=====abc== +Content-Type: text/plain; charset="us-ascii" +Content-Transfer-Encoding: 7bit + +<html><head></head><body>Some things to say.</body></html> +--=====abc==-- +""") + + def test_wrap_message_1(self): + self._mlist.from_is_list = FromIsList.wrap_message + msg = mfs(self._text) + dmarc.process(self._mlist, msg, {}) + # We can't predict the Message-ID in the wrapper so delete it, but + # ensure we have one. + self.assertIsNotNone(msg.get('message-id')) + del msg['message-id'] + self.assertMultiLineEqual(msg.as_string(), """\ +To: ant@example.com +Subject: A subject +X-Mailman-Version: X.Y +Date: Fri, 1 Jan 2016 00:00:01 +0000 +MIME-Version: 1.0 +From: anne--- via Ant <ant@example.com> +Reply-To: anne@example.com +Content-Type: message/rfc822 +Content-Disposition: inline + +""" + self._text) + + def test_wrap_message_2(self): + self._mlist.dmarc_moderation_action = ( + DMARCModerationAction.wrap_message) + msgdata = {'dmarc': True} + msg = mfs(self._text) + dmarc.process(self._mlist, msg, msgdata) + # We can't predict the Message-ID in the wrapper so delete it, but + # ensure we have one. + self.assertIsNotNone(msg.get('message-id')) + del msg['message-id'] + self.maxDiff = None + self.assertMultiLineEqual(msg.as_string(), """\ +To: ant@example.com +Subject: A subject +X-Mailman-Version: X.Y +Date: Fri, 1 Jan 2016 00:00:01 +0000 +MIME-Version: 1.0 +From: anne--- via Ant <ant@example.com> +Reply-To: anne@example.com +Content-Type: message/rfc822 +Content-Disposition: inline + +""" + self._text) + + def test_wrap_message_cc(self): + self._mlist.dmarc_moderation_action = ( + DMARCModerationAction.wrap_message) + self._mlist.reply_goes_to_list = ReplyToMunging.point_to_list + msgdata = {'dmarc': True} + msg = mfs(self._text) + dmarc.process(self._mlist, msg, msgdata) + # We can't predict the Message-ID in the wrapper so delete it, but + # ensure we have one. + self.assertIsNotNone(msg.get('message-id')) + del msg['message-id'] + self.maxDiff = None + self.assertMultiLineEqual(msg.as_string(), """\ +To: ant@example.com +Subject: A subject +X-Mailman-Version: X.Y +Date: Fri, 1 Jan 2016 00:00:01 +0000 +MIME-Version: 1.0 +From: anne--- via Ant <ant@example.com> +Cc: anne@example.com +Content-Type: message/rfc822 +Content-Disposition: inline + +""" + self._text) diff --git a/src/mailman/rules/docs/dmarc-moderation.rst b/src/mailman/rules/docs/dmarc-moderation.rst index 91da762d3..fb9ac3ac5 100644 --- a/src/mailman/rules/docs/dmarc-moderation.rst +++ b/src/mailman/rules/docs/dmarc-moderation.rst @@ -78,21 +78,28 @@ Subdomains which don't have a policy will check the organizational domain. The list's action can also be set to immediately discard or reject the message. + >>> from mailman.interfaces.chain import ChainEvent + >>> from mailman.testing.helpers import event_subscribers + >>> def handler(event): + ... if isinstance(event, ChainEvent): + ... print(event.__class__.__name__, + ... event.chain.name, event.msg['message-id']) >>> mlist.dmarc_moderation_action = DMARCModerationAction.discard >>> msg = message_from_string("""\ ... From: aperson@yahoo.com ... To: _xtest@example.com ... Subject: A posted message + ... Message-ID: <xxx_message_id@yahoo.com> ... ... """) >>> msgdata = {} - >>> rule.check(mlist, msg, msgdata) + >>> with event_subscribers(handler): + ... rule.check(mlist, msg, msgdata) + DiscardEvent discard <xxx_message_id@yahoo.com> False >>> msgdata['dmarc'] True -The above needs to test that the message was discarded. - We can reject the message with a default reason. >>> mlist.dmarc_moderation_action = DMARCModerationAction.reject @@ -100,10 +107,13 @@ We can reject the message with a default reason. ... From: aperson@yahoo.com ... To: _xtest@example.com ... Subject: A posted message + ... Message-ID: <xxx_message_id@yahoo.com> ... ... """) >>> msgdata = {} - >>> rule.check(mlist, msg, msgdata) + >>> with event_subscribers(handler): + ... rule.check(mlist, msg, msgdata) + RejectEvent reject <xxx_message_id@yahoo.com> False >>> msgdata['dmarc'] True @@ -148,6 +158,7 @@ There is now a reject message in the virgin queue. From: aperson@yahoo.com To: _xtest@example.com Subject: A posted message + Message-ID: <xxx_message_id@yahoo.com> X-Mailman-Rule-Hits: dmarc-moderation <BLANKLINE> <BLANKLINE> @@ -161,10 +172,13 @@ And, we can reject with a custom message. ... From: aperson@yahoo.com ... To: _xtest@example.com ... Subject: A posted message + ... Message-ID: <xxx_message_id@yahoo.com> ... ... """) >>> msgdata = {} - >>> rule.check(mlist, msg, msgdata) + >>> with event_subscribers(handler): + ... rule.check(mlist, msg, msgdata) + RejectEvent reject <xxx_message_id@yahoo.com> False >>> msgdata['dmarc'] True @@ -204,6 +218,7 @@ Check the the virgin queue. From: aperson@yahoo.com To: _xtest@example.com Subject: A posted message + Message-ID: <xxx_message_id@yahoo.com> X-Mailman-Rule-Hits: dmarc-moderation <BLANKLINE> <BLANKLINE> |
