# Copyright (C) 2016-2017 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 .
"""Test the DMARC handler."""
import unittest
from mailman.app.lifecycle import create_list
from mailman.app.membership import add_member
from mailman.handlers import dmarc
from mailman.interfaces.mailinglist import DMARCMitigateAction, ReplyToMunging
from mailman.interfaces.subscriptions import RequestRecord
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
maxDiff = None
def setUp(self):
self._mlist = create_list('ant@example.com')
self._mlist.anonymous_list = False
self._mlist.dmarc_policy_mitigate_action = (
DMARCMitigateAction.no_mitigation)
self._mlist.dmarc_wrapped_message_text = ''
self._mlist.dmarc_mitigate_unconditionally = False
self._mlist.reply_goes_to_list = ReplyToMunging.no_munging
# We can use the same message text for most tests.
self._text = """\
From: anne@example.com
To: ant@example.com
Subject: A subject
X-Mailman-Version: X.Y
Message-ID:
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/html; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Some things to say.
--=====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_mitigate_action = DMARCMitigateAction.munge_from
msgdata = {'dmarc': True}
msg = mfs(self._text)
dmarc.process(self._mlist, msg, msgdata)
self.assertMultiLineEqual(msg.as_string(), self._text)
def test_no_mitigation_no_change_1(self):
msg = mfs(self._text)
self._mlist.dmarc_mitigate_unconditionally = True
dmarc.process(self._mlist, msg, {})
self.assertMultiLineEqual(msg.as_string(), self._text)
def test_no_mitigation_no_change_2(self):
msg = mfs(self._text)
msgdata = {'dmarc': True}
dmarc.process(self._mlist, msg, msgdata)
self.assertMultiLineEqual(msg.as_string(), self._text)
def test_action_reject_mitigate_unconditionally(self):
msg = mfs(self._text)
self._mlist.dmarc_mitigate_unconditionally = True
self._mlist.dmarc_mitigate_action = DMARCMitigateAction.reject
dmarc.process(self._mlist, msg, {})
self.assertMultiLineEqual(msg.as_string(), self._text)
def test_action_discard_mitigate_unconditionally(self):
msg = mfs(self._text)
self._mlist.dmarc_mitigate_unconditionally = True
self._mlist.dmarc_mitigate_action = DMARCMitigateAction.discard
dmarc.process(self._mlist, msg, {})
self.assertMultiLineEqual(msg.as_string(), self._text)
def test_action_munge_from(self):
self._mlist.dmarc_mitigate_action = DMARCMitigateAction.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:
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
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/html; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Some things to say.
--=====abc==--
""")
def test_action_munge_from_no_from(self):
self._mlist.dmarc_mitigate_action = DMARCMitigateAction.munge_from
msgdata = dict(
dmarc=True,
original_sender='anne@example.com',
)
msg = mfs(self._text)
del msg['from']
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:
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
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/html; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Some things to say.
--=====abc==--
""")
def test_action_munge_multiple_froms(self):
self._mlist.dmarc_mitigate_action = DMARCMitigateAction.munge_from
msgdata = dict(
dmarc=True,
original_sender='cate@example.com',
)
msg = mfs(self._text)
# Put multiple addresses in the From: header. The msgdata must
# contain a key naming the "original sender" as determined by the
# Message.sender attribute.
del msg['from']
msg['From'] = 'anne@example.com, bart@example.com'
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:
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: cate--- via Ant
Reply-To: cate@example.com
--=====abc==
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Some things to say.
--=====abc==
Content-Type: text/html; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Some things to say.
--=====abc==--
""")
def test_action_munge_from_display_name_in_from(self):
self._mlist.dmarc_mitigate_action = DMARCMitigateAction.munge_from
msgdata = {'dmarc': True}
msg = mfs(self._text)
del msg['from']
msg['From'] = 'Anne Person '
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:
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 Person via Ant
Reply-To: Anne Person
--=====abc==
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Some things to say.
--=====abc==
Content-Type: text/html; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Some things to say.
--=====abc==--
""")
def test_action_munge_from_display_name_in_list(self):
self._mlist.dmarc_mitigate_action = DMARCMitigateAction.munge_from
add_member(
self._mlist,
RequestRecord('anne@example.com', 'Anna Banana')
)
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:
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: Anna Banana via Ant
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/html; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Some things to say.
--=====abc==--
""")
def test_no_action_without_msgdata(self):
self._mlist.dmarc_mitigate_action = DMARCMitigateAction.munge_from
msg = mfs(self._text)
dmarc.process(self._mlist, msg, {})
self.assertMultiLineEqual(msg.as_string(), self._text)
def test_unconditional_no_msgdata(self):
self._mlist.dmarc_mitigate_action = DMARCMitigateAction.munge_from
self._mlist.dmarc_mitigate_unconditionally = True
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:
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
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/html; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Some things to say.
--=====abc==--
""")
def test_from_in_cc(self):
self._mlist.dmarc_mitigate_action = DMARCMitigateAction.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:
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
Cc: anne@example.com
--=====abc==
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Some things to say.
--=====abc==
Content-Type: text/html; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Some things to say.
--=====abc==--
""")
def test_wrap_message_unconditionally(self):
self._mlist.dmarc_mitigate_action = DMARCMitigateAction.wrap_message
self._mlist.dmarc_mitigate_unconditionally = True
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
Reply-To: anne@example.com
Content-Type: message/rfc822
Content-Disposition: inline
""" + self._text)
def test_wrap_message(self):
self._mlist.dmarc_mitigate_action = DMARCMitigateAction.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.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
Reply-To: anne@example.com
Content-Type: message/rfc822
Content-Disposition: inline
""" + self._text)
def test_wrap_message_cc(self):
self._mlist.dmarc_mitigate_action = DMARCMitigateAction.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.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
Cc: anne@example.com
Content-Type: message/rfc822
Content-Disposition: inline
""" + self._text)
def test_rfc2047_encoded_from(self):
self._mlist.dmarc_mitigate_action = DMARCMitigateAction.munge_from
msgdata = {'dmarc': True}
msg = mfs(self._text)
del msg['from']
msg['From'] = '=?iso-8859-1?Q?A_Pers=F3n?= '
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:
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: =?utf-8?q?A_Pers=C3=B3n_via_Ant?=
Reply-To: =?iso-8859-1?Q?A_Pers=F3n?=
--=====abc==
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Some things to say.
--=====abc==
Content-Type: text/html; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Some things to say.
--=====abc==--
""")