summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/mailman/handlers/dmarc.py1
-rw-r--r--src/mailman/handlers/docs/dmarc-mitigations.rst11
-rw-r--r--src/mailman/handlers/tests/test_dmarc.py244
-rw-r--r--src/mailman/rules/docs/dmarc-moderation.rst25
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>