summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMark Sapiro2016-11-11 15:58:32 -0800
committerMark Sapiro2016-11-11 15:58:32 -0800
commitfe20103091f5c2853b22190751d9c9f613a625d5 (patch)
treef657cb528f115dde7fb1dac77d8bc3c689b23842 /src
parentf60cb022df4a5c5fca507c103d97638971e63ae2 (diff)
downloadmailman-fe20103091f5c2853b22190751d9c9f613a625d5.tar.gz
mailman-fe20103091f5c2853b22190751d9c9f613a625d5.tar.zst
mailman-fe20103091f5c2853b22190751d9c9f613a625d5.zip
Diffstat (limited to 'src')
-rw-r--r--src/mailman/rules/docs/dmarc-moderation.rst56
-rw-r--r--src/mailman/rules/tests/test_dmarc.py81
2 files changed, 115 insertions, 22 deletions
diff --git a/src/mailman/rules/docs/dmarc-moderation.rst b/src/mailman/rules/docs/dmarc-moderation.rst
index c2210011a..8aab08161 100644
--- a/src/mailman/rules/docs/dmarc-moderation.rst
+++ b/src/mailman/rules/docs/dmarc-moderation.rst
@@ -2,18 +2,23 @@
DMARC moderation
================
-This rule is different from others in that it never matches bucause a match
-would cause the message to be held. The rule looks at the list's
-dmarc_moderation_policy and if it is other than 'none', it checks the domain
-of the From: address for a DMARC policy and depending on settings may reject
-or discard the message or just flag in for the dmarc handler to apply DMARC
-mitigations to the message.
+This rule only matches in order to jump to the moderation chain to reject
+or discard the message. The rule looks at the list's dmarc_moderation_policy
+and if it is other than 'none', it checks the domain of the From: address for
+a DMARC policy and depending on settings may reject or discard the message or
+just flag it for the dmarc handler to apply DMARC mitigations to the message.
>>> mlist = create_list('_xtest@example.com')
>>> rule = config.rules['dmarc-moderation']
>>> print(rule.name)
dmarc-moderation
+First we set up a mock patcher to return predictable responses to DNS lookups.
+This returns p=reject for the example.biz domain and not for any others.
+
+ >>> from mailman.rules.tests.test_dmarc import get_dns_resolver
+ >>> patcher = get_dns_resolver()
+
A message From: a domain without a DMARC policy does not set any flags.
>>> from mailman.interfaces.mailinglist import DMARCModerationAction
@@ -25,7 +30,8 @@ A message From: a domain without a DMARC policy does not set any flags.
...
... """)
>>> msgdata = {}
- >>> rule.check(mlist, msg, msgdata)
+ >>> with patcher as Resolver:
+ ... rule.check(mlist, msg, msgdata)
False
>>> msgdata == {}
True
@@ -35,13 +41,14 @@ action is none.
>>> mlist.dmarc_moderation_action = DMARCModerationAction.none
>>> msg = message_from_string("""\
- ... From: aperson@yahoo.com
+ ... From: aperson@example.biz
... To: _xtest@example.com
... Subject: A posted message
...
... """)
>>> msgdata = {}
- >>> rule.check(mlist, msg, msgdata)
+ >>> with patcher as Resolver:
+ ... rule.check(mlist, msg, msgdata)
False
>>> msgdata == {}
True
@@ -50,13 +57,14 @@ But with a different list setting, the message is flagged.
>>> mlist.dmarc_moderation_action = DMARCModerationAction.munge_from
>>> msg = message_from_string("""\
- ... From: aperson@yahoo.com
+ ... From: aperson@example.biz
... To: _xtest@example.com
... Subject: A posted message
...
... """)
>>> msgdata = {}
- >>> rule.check(mlist, msg, msgdata)
+ >>> with patcher as Resolver:
+ ... rule.check(mlist, msg, msgdata)
False
>>> msgdata['dmarc']
True
@@ -64,13 +72,14 @@ But with a different list setting, the message is flagged.
Subdomains which don't have a policy will check the organizational domain.
>>> msg = message_from_string("""\
- ... From: aperson@sub.domain.yahoo.com
+ ... From: aperson@sub.domain.example.biz
... To: _xtest@example.com
... Subject: A posted message
...
... """)
>>> msgdata = {}
- >>> rule.check(mlist, msg, msgdata)
+ >>> with patcher as Resolver:
+ ... rule.check(mlist, msg, msgdata)
False
>>> msgdata['dmarc']
True
@@ -80,14 +89,15 @@ message.
>>> mlist.dmarc_moderation_action = DMARCModerationAction.discard
>>> msg = message_from_string("""\
- ... From: aperson@yahoo.com
+ ... From: aperson@example.biz
... To: _xtest@example.com
... Subject: A posted message
- ... Message-ID: <xxx_message_id@yahoo.com>
+ ... Message-ID: <xxx_message_id@example.biz>
...
... """)
>>> msgdata = {}
- >>> rule.check(mlist, msg, msgdata)
+ >>> with patcher as Resolver:
+ ... rule.check(mlist, msg, msgdata)
True
>>> msgdata['dmarc']
True
@@ -98,14 +108,15 @@ We can reject the message with a default reason.
>>> mlist.dmarc_moderation_action = DMARCModerationAction.reject
>>> msg = message_from_string("""\
- ... From: aperson@yahoo.com
+ ... From: aperson@example.biz
... To: _xtest@example.com
... Subject: A posted message
- ... Message-ID: <xxx_message_id@yahoo.com>
+ ... Message-ID: <xxx_message_id@example.biz>
...
... """)
>>> msgdata = {}
- >>> rule.check(mlist, msg, msgdata)
+ >>> with patcher as Resolver:
+ ... rule.check(mlist, msg, msgdata)
True
>>> msgdata['dmarc']
True
@@ -118,14 +129,15 @@ And, we can reject with a custom message.
>>> mlist.dmarc_moderation_notice = 'A silly reason'
>>> msg = message_from_string("""\
- ... From: aperson@yahoo.com
+ ... From: aperson@example.biz
... To: _xtest@example.com
... Subject: A posted message
- ... Message-ID: <xxx_message_id@yahoo.com>
+ ... Message-ID: <xxx_message_id@example.biz>
...
... """)
>>> msgdata = {}
- >>> rule.check(mlist, msg, msgdata)
+ >>> with patcher as Resolver:
+ ... rule.check(mlist, msg, msgdata)
True
>>> msgdata['dmarc']
True
diff --git a/src/mailman/rules/tests/test_dmarc.py b/src/mailman/rules/tests/test_dmarc.py
new file mode 100644
index 000000000..49aeea8c6
--- /dev/null
+++ b/src/mailman/rules/tests/test_dmarc.py
@@ -0,0 +1,81 @@
+# 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/>.
+
+"""Support for mocking dnspython calls from dmarc rules."""
+
+from dns.rdatatype import TXT
+from dns.resolver import NXDOMAIN, NoAnswer
+from mailman import public
+from unittest import mock
+
+
+@public
+def get_dns_resolver():
+ """Create a dns.resolver.Resolver mock.
+
+ This is used to return a predictable response to a _dmarc query. It
+ returns p=reject for the example.biz domain and raises either NXDOMAIN
+ or NoAnswer for any other.
+
+ It only implements those classes and attributes used by the dmarc rule.
+ """
+ class Name:
+ # mock answer.name
+ def __init__(self):
+ pass
+
+ def to_text(self):
+ return '_dmarc.example.biz.'
+
+ class Item:
+ # mock answer.items
+ def __init__(self):
+ self.strings = [b'v=DMARC1; p=reject;']
+
+ class Ans_e:
+ # mock answer element
+ def __init__(self):
+ self.rdtype = TXT
+ self.items = [Item()]
+ self.name = Name()
+
+ class Answer:
+ # mock answer
+ def __init__(self):
+ self.answer = [Ans_e()]
+
+ class Resolver:
+ # mock dns.resolver.Resolver class.
+ def __init__(self):
+ pass
+
+ def query(self, domain, data_type):
+ if data_type != TXT:
+ raise NoAnswer
+ dparts = domain.split('.')
+ if len(dparts) < 3:
+ raise NXDOMAIN
+ if len(dparts) > 3:
+ raise NoAnswer
+ if dparts[0] != '_dmarc':
+ raise NoAnswer
+ if dparts[1] != 'example' or dparts[2] != 'biz':
+ raise NXDOMAIN
+ self.response = Answer()
+ return self
+ patcher = mock.patch('dns.resolver.Resolver', Resolver)
+ return patcher