summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/mailman/chains/builtin.py2
-rw-r--r--src/mailman/chains/docs/moderation.rst10
-rw-r--r--src/mailman/core/docs/chains.rst4
-rw-r--r--src/mailman/database/alembic/versions/3002bac0c25a_dmarc_attributes.py49
-rw-r--r--src/mailman/handlers/dmarc.py47
-rw-r--r--src/mailman/handlers/docs/dmarc-mitigations.rst42
-rw-r--r--src/mailman/handlers/tests/test_dmarc.py35
-rw-r--r--src/mailman/interfaces/mailinglist.py46
-rw-r--r--src/mailman/model/mailinglist.py14
-rw-r--r--src/mailman/rest/docs/listconf.rst17
-rw-r--r--src/mailman/rest/listconf.py12
-rw-r--r--src/mailman/rest/tests/test_listconf.py6
-rw-r--r--src/mailman/rules/dmarc.py36
-rw-r--r--src/mailman/rules/docs/dmarc-mitigation.rst (renamed from src/mailman/rules/docs/dmarc-moderation.rst)29
-rw-r--r--src/mailman/rules/docs/rules.rst2
-rw-r--r--src/mailman/runners/docs/incoming.rst2
-rw-r--r--src/mailman/styles/base.py9
17 files changed, 141 insertions, 221 deletions
diff --git a/src/mailman/chains/builtin.py b/src/mailman/chains/builtin.py
index 3bf0fd5b5..e26c38470 100644
--- a/src/mailman/chains/builtin.py
+++ b/src/mailman/chains/builtin.py
@@ -41,7 +41,7 @@ class BuiltInChain:
# First check DMARC. For a reject or discard, the rule hits and we
# jump to the moderation chain to do the action. Otherwise, the rule
# misses buts sets msgdata['dmarc'] for the handler.
- ('dmarc-moderation', LinkAction.jump, 'moderation'),
+ ('dmarc-mitigation', LinkAction.jump, 'moderation'),
('approved', LinkAction.jump, 'accept'),
('emergency', LinkAction.jump, 'hold'),
('loop', LinkAction.jump, 'discard'),
diff --git a/src/mailman/chains/docs/moderation.rst b/src/mailman/chains/docs/moderation.rst
index 53c32bc33..e3c9e12f5 100644
--- a/src/mailman/chains/docs/moderation.rst
+++ b/src/mailman/chains/docs/moderation.rst
@@ -87,7 +87,7 @@ built-in chain. No rules hit and so the message is accepted.
Subject: aardvark
Hits:
Misses:
- dmarc-moderation
+ dmarc-mitigation
approved
emergency
loop
@@ -125,7 +125,7 @@ moderator approval.
Hits:
member-moderation
Misses:
- dmarc-moderation
+ dmarc-mitigation
approved
emergency
loop
@@ -152,7 +152,7 @@ Anne's moderation action can also be set to `discard`...
Hits:
member-moderation
Misses:
- dmarc-moderation
+ dmarc-mitigation
approved
emergency
loop
@@ -178,7 +178,7 @@ Anne's moderation action can also be set to `discard`...
Hits:
member-moderation
Misses:
- dmarc-moderation
+ dmarc-mitigation
approved
emergency
loop
@@ -219,7 +219,7 @@ moderator approval.
Hits:
nonmember-moderation
Misses:
- dmarc-moderation
+ dmarc-mitigation
approved
emergency
loop
diff --git a/src/mailman/core/docs/chains.rst b/src/mailman/core/docs/chains.rst
index 43b41bcf5..e3190e364 100644
--- a/src/mailman/core/docs/chains.rst
+++ b/src/mailman/core/docs/chains.rst
@@ -268,7 +268,7 @@ This message will end up in the `pipeline` queue.
Message-ID: <first>
Message-ID-Hash: 4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB
X-Message-ID-Hash: 4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB
- X-Mailman-Rule-Misses: dmarc-moderation; approved; emergency; loop;
+ X-Mailman-Rule-Misses: dmarc-mitigation; approved; emergency; loop;
banned-address; member-moderation; nonmember-moderation; administrivia;
implicit-dest; max-recipients; max-size; news-moderation; no-subject;
suspicious-header
@@ -285,7 +285,7 @@ hit and all rules that have missed.
administrivia
approved
banned-address
- dmarc-moderation
+ dmarc-mitigation
emergency
implicit-dest
loop
diff --git a/src/mailman/database/alembic/versions/3002bac0c25a_dmarc_attributes.py b/src/mailman/database/alembic/versions/3002bac0c25a_dmarc_attributes.py
index 03740c947..aa2d8610d 100644
--- a/src/mailman/database/alembic/versions/3002bac0c25a_dmarc_attributes.py
+++ b/src/mailman/database/alembic/versions/3002bac0c25a_dmarc_attributes.py
@@ -10,8 +10,8 @@ import sqlalchemy as sa
from alembic import op
from mailman.database.helpers import exists_in_db
-from mailman.database.types import Enum, SAUnicode
-from mailman.interfaces.mailinglist import DMARCModerationAction, FromIsList
+from mailman.database.types import Enum, SAUnicodeLarge
+from mailman.interfaces.mailinglist import DMARCMitigateAction
# revision identifiers, used by Alembic.
@@ -22,62 +22,49 @@ down_revision = 'a46993b05703'
def upgrade():
if not exists_in_db(op.get_bind(),
'mailinglist',
- 'dmarc_moderation_action'
+ 'dmarc_mitigate_action'
):
# SQLite may not have removed it when downgrading. It should be OK
# to just test one.
op.add_column('mailinglist', sa.Column(
- 'dmarc_moderation_action',
- Enum(DMARCModerationAction),
+ 'dmarc_mitigate_action',
+ Enum(DMARCMitigateAction),
nullable=True))
op.add_column('mailinglist', sa.Column(
- 'dmarc_quarantine_moderation_action',
- sa.Boolean(),
- nullable=True))
- op.add_column('mailinglist', sa.Column(
- 'dmarc_none_moderation_action',
- sa.Boolean(),
+ 'dmarc_mitigate_unconditionally',
+ sa.Boolean,
nullable=True))
op.add_column('mailinglist', sa.Column(
'dmarc_moderation_notice',
- SAUnicode(),
+ SAUnicodeLarge(),
nullable=True))
op.add_column('mailinglist', sa.Column(
'dmarc_wrapped_message_text',
- SAUnicode(),
- nullable=True))
- op.add_column('mailinglist', sa.Column(
- 'from_is_list',
- Enum(FromIsList),
+ SAUnicodeLarge(),
nullable=True))
# Now migrate the data. Don't import the table definition from the
# models, it may break this migration when the model is updated in the
# future (see the Alembic doc).
mlist = sa.sql.table(
'mailinglist',
- sa.sql.column('dmarc_moderation_action', Enum(DMARCModerationAction)),
- sa.sql.column('dmarc_quarantine_moderation_action', sa.Boolean()),
- sa.sql.column('dmarc_none_moderation_action', sa.Boolean()),
- sa.sql.column('dmarc_moderation_notice', SAUnicode()),
- sa.sql.column('dmarc_wrapped_message_text', SAUnicode()),
- sa.sql.column('from_is_list', Enum(FromIsList))
+ sa.sql.column('dmarc_mitigate_action', Enum(DMARCMitigateAction)),
+ sa.sql.column('dmarc_mitigate_unconditionally', sa.Boolean),
+ sa.sql.column('dmarc_moderation_notice', SAUnicodeLarge()),
+ sa.sql.column('dmarc_wrapped_message_text', SAUnicodeLarge()),
)
# These are all new attributes so just set defaults.
op.execute(mlist.update().values(dict(
- dmarc_moderation_action=op.inline_literal(DMARCModerationAction.none),
- dmarc_quarantine_moderation_action=op.inline_literal(True),
- dmarc_none_moderation_action=op.inline_literal(False),
+ dmarc_mitigate_action=op.inline_literal(
+ DMARCMitigateAction.no_mitigation),
+ dmarc_mitigate_unconditionally=op.inline_literal(False),
dmarc_moderation_notice=op.inline_literal(''),
dmarc_wrapped_message_text=op.inline_literal(''),
- from_is_list=op.inline_literal(FromIsList.none),
)))
def downgrade():
with op.batch_alter_table('mailinglist') as batch_op:
- batch_op.drop_column('dmarc_moderation_action')
- batch_op.drop_column('dmarc_quarantine_moderation_action')
- batch_op.drop_column('dmarc_none_moderation_action')
+ batch_op.drop_column('dmarc_mitigate_action')
+ batch_op.drop_column('dmarc_mitigate_unconditionally')
batch_op.drop_column('dmarc_moderation_notice')
batch_op.drop_column('dmarc_wrapped_message_text')
- batch_op.drop_column('from_is_list')
diff --git a/src/mailman/handlers/dmarc.py b/src/mailman/handlers/dmarc.py
index 21b122990..99d8e08df 100644
--- a/src/mailman/handlers/dmarc.py
+++ b/src/mailman/handlers/dmarc.py
@@ -34,8 +34,7 @@ from email.mime.text import MIMEText
from email.utils import formataddr, getaddresses, make_msgid
from mailman.core.i18n import _
from mailman.interfaces.handler import IHandler
-from mailman.interfaces.mailinglist import (
- DMARCModerationAction, FromIsList, ReplyToMunging)
+from mailman.interfaces.mailinglist import DMARCMitigateAction, ReplyToMunging
from mailman.utilities.string import wrap
from public import public
from zope.interface import implementer
@@ -148,7 +147,7 @@ def munge_from(mlist, msg, msgdata):
return
-def wrap_message(mlist, msg, msgdata, dmarc_wrap=False):
+def wrap_message(mlist, msg, msgdata):
# Create a wrapper message around the original.
# There are various headers in msg that we don't want, so we basically
# make a copy of the msg, then delete almost everything and set/copy
@@ -166,9 +165,8 @@ def wrap_message(mlist, msg, msgdata, dmarc_wrap=False):
msg['Message-ID'] = make_msgid()
for k, v in munged_headers(mlist, omsg, msgdata):
msg[k] = v
- # Are we including dmarc_wrapped_message_text? I.e., do we have text and
- # are we wrapping because of dmarc_moderation_action?
- if len(mlist.dmarc_wrapped_message_text) > 0 and dmarc_wrap:
+ # Are we including dmarc_wrapped_message_text?
+ if len(mlist.dmarc_wrapped_message_text) > 0:
part1 = MIMEText(wrap(mlist.dmarc_wrapped_message_text),
'plain',
mlist.preferred_language.charset)
@@ -185,34 +183,29 @@ def wrap_message(mlist, msg, msgdata, dmarc_wrap=False):
def process(mlist, msg, msgdata):
- """Process DMARC actions."""
- if ((not msgdata.get('dmarc') or
- mlist.dmarc_moderation_action is DMARCModerationAction.none) and
- mlist.from_is_list is FromIsList.none):
+ # If we're mitigating on policy and we have no hit, return.
+ if not msgdata.get('dmarc') and not mlist.dmarc_mitigate_unconditionally:
+ return
+ # if we're not mitigating, return.
+ if mlist.dmarc_mitigate_action is DMARCMitigateAction.no_mitigation:
return
if mlist.anonymous_list:
# DMARC mitigation is not required for anonymous lists.
return
- if (mlist.dmarc_moderation_action is not DMARCModerationAction.none and
- msgdata.get('dmarc')):
- if mlist.dmarc_moderation_action is DMARCModerationAction.munge_from:
+ if msgdata.get('dmarc') or mlist.dmarc_mitigate_unconditionally:
+ if mlist.dmarc_mitigate_action is DMARCMitigateAction.munge_from:
munge_from(mlist, msg, msgdata)
- elif (mlist.dmarc_moderation_action is
- DMARCModerationAction.wrap_message):
- wrap_message(mlist, msg, msgdata, dmarc_wrap=True)
- else:
- raise AssertionError(
- 'handlers/dmarc.py: dmarc_moderation_action = {}'.format(
- mlist.dmarc_moderation_action))
- else:
- if mlist.from_is_list is FromIsList.munge_from:
- munge_from(mlist, msg, msgdata)
- elif mlist.from_is_list is FromIsList.wrap_message:
+ elif mlist.dmarc_mitigate_action is DMARCMitigateAction.wrap_message:
wrap_message(mlist, msg, msgdata)
else:
- raise AssertionError(
- 'handlers/dmarc.py: from_is_list = {}'.format(
- mlist.from_is_list))
+ # We can get here if DMARCMitigateAction is reject or discard
+ # but the From: domain has no reject or quarantine policy and
+ # mlist.dmarc_mitigate_unconditionally is True. We just ignore
+ # this.
+ return
+ else:
+ raise AssertionError(
+ 'handlers/dmarc.py: no hit and unconditional is False')
@public
diff --git a/src/mailman/handlers/docs/dmarc-mitigations.rst b/src/mailman/handlers/docs/dmarc-mitigations.rst
index 2f87ecedb..d26fc6bdd 100644
--- a/src/mailman/handlers/docs/dmarc-mitigations.rst
+++ b/src/mailman/handlers/docs/dmarc-mitigations.rst
@@ -19,30 +19,22 @@ The settings and their effects are:
``anonymous_list``
If True, no mitigations are ever applied because the message
is already From: the list.
-``dmarc_moderation_action``
+``dmarc_mitigate_action``
The action to apply to messages From: a domain
- publishing a DMARC policy of reject and possibly quarantine or none
- depending on the next two settings.
-``dmarc_quarantine_moderation_action``
- A flag to apply the above dmarc_moderation_action
- to messages From: a domain publishing a DMARC policy of quarantine in
- addition to domains publishing a DMARC policy of reject.
-``dmarc_none_moderation_action``
- A flag to apply the above dmarc_moderation_action to
- messages From: a domain publishing a DMARC policy of none, but only when
- dmarc_quarantine_moderation_action is also true.
+ publishing a DMARC policy of reject or quarantine or to all messages
+ depending on the next setting.
+``dmarc_mitigate_unconditionally``
+ A Flag to apply dmarc_mitigate_action to all messages, but only if
+ dmarc_mitigate_action is neither reject or discard.
``dmarc_moderation_notice``
Text to include in any rejection notice to be sent
- when dmarc_moderation_action of reject applies. This overrides the bult-in
+ when dmarc_policy_mitigation of reject applies. This overrides the bult-in
default text.
``dmarc_wrapped_message_text``
Text to be added as a separate text/plain MIME
- part preceding the original message part in the wrapped message when
- dmarc_moderation_action of wrap_message applies. If this is not provided
- the separate text/plain MIME part is not added.
-``from_is_list``
- The action to be applied to all messages for which
- dmarc_moderation_action is none or not applicable.
+ part preceding the original message part in the wrapped message when a
+ wrap_message mitigation applies. If this is not provided the separate
+ text/plain MIME part is not added.
``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:. This is intended to
@@ -50,9 +42,9 @@ The settings and their effects are:
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:
+The possible actions for dmarc_mitigate_action are:
-``none``
+``no_mitigation``
Make no transformation to the message.
``munge_from``
Change the From: header and put the original From: in Reply-To:
@@ -60,10 +52,6 @@ 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 (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.
@@ -72,10 +60,10 @@ dmarc-moderation rule) for dmarc_moderation_action only:
Here's what happens when we munge the From.
- >>> from mailman.interfaces.mailinglist import (DMARCModerationAction,
+ >>> from mailman.interfaces.mailinglist import (DMARCMitigateAction,
... ReplyToMunging)
>>> mlist = create_list('test@example.com')
- >>> mlist.dmarc_moderation_action = DMARCModerationAction.munge_from
+ >>> mlist.dmarc_mitigate_action = DMARCMitigateAction.munge_from
>>> mlist.reply_goes_to_list = ReplyToMunging.no_munging
>>> msg = message_from_string("""\
... From: A Person <aperson@example.com>
@@ -96,7 +84,7 @@ Here's what happens when we munge the From.
Here we wrap the message without adding a text part.
- >>> mlist.dmarc_moderation_action = DMARCModerationAction.wrap_message
+ >>> mlist.dmarc_mitigate_action = DMARCMitigateAction.wrap_message
>>> mlist.dmarc_wrapped_message_text = ''
>>> msg = message_from_string("""\
... From: A Person <aperson@example.com>
diff --git a/src/mailman/handlers/tests/test_dmarc.py b/src/mailman/handlers/tests/test_dmarc.py
index ca1978299..a90fb3618 100644
--- a/src/mailman/handlers/tests/test_dmarc.py
+++ b/src/mailman/handlers/tests/test_dmarc.py
@@ -21,8 +21,7 @@ import unittest
from mailman.app.lifecycle import create_list
from mailman.handlers import dmarc
-from mailman.interfaces.mailinglist import (
- DMARCModerationAction, FromIsList, ReplyToMunging)
+from mailman.interfaces.mailinglist import DMARCMitigateAction, ReplyToMunging
from mailman.testing.helpers import specialized_message_from_string as mfs
from mailman.testing.layers import ConfigLayer
@@ -35,9 +34,10 @@ class TestDMARCMitigations(unittest.TestCase):
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_policy_mitigate_action = (
+ DMARCMitigateAction.no_mitigation)
self._mlist.dmarc_wrapped_message_text = ''
- self._mlist.from_is_list = FromIsList.none
+ self._mlist.dmarc_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 = """\
@@ -71,14 +71,14 @@ Content-Transfer-Encoding: 7bit
def test_anonymous_no_change(self):
self._mlist.anonymous_list = True
- self._mlist.dmarc_moderation_action = DMARCModerationAction.munge_from
+ 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_action_munge_from(self):
- self._mlist.dmarc_moderation_action = DMARCModerationAction.munge_from
+ self._mlist.dmarc_mitigate_action = DMARCMitigateAction.munge_from
msgdata = {'dmarc': True}
msg = mfs(self._text)
dmarc.process(self._mlist, msg, msgdata)
@@ -108,13 +108,14 @@ Content-Transfer-Encoding: 7bit
""")
def test_no_action_without_msgdata(self):
- self._mlist.dmarc_moderation_action = DMARCModerationAction.munge_from
+ 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_from_is_list_no_msgdata(self):
- self._mlist.from_is_list = FromIsList.munge_from
+ 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(), """\
@@ -143,7 +144,7 @@ Content-Transfer-Encoding: 7bit
""")
def test_from_in_cc(self):
- self._mlist.dmarc_moderation_action = DMARCModerationAction.munge_from
+ 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)
@@ -174,7 +175,8 @@ Content-Transfer-Encoding: 7bit
""")
def test_wrap_message_1(self):
- self._mlist.from_is_list = FromIsList.wrap_message
+ 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
@@ -195,8 +197,7 @@ Content-Disposition: inline
""" + self._text)
def test_wrap_message_2(self):
- self._mlist.dmarc_moderation_action = (
- DMARCModerationAction.wrap_message)
+ self._mlist.dmarc_mitigate_action = DMARCMitigateAction.wrap_message
msgdata = {'dmarc': True}
msg = mfs(self._text)
dmarc.process(self._mlist, msg, msgdata)
@@ -219,8 +220,7 @@ Content-Disposition: inline
""" + self._text)
def test_wrap_message_cc(self):
- self._mlist.dmarc_moderation_action = (
- DMARCModerationAction.wrap_message)
+ 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)
@@ -244,11 +244,12 @@ Content-Disposition: inline
""" + self._text)
def test_rfc2047_encoded_from(self):
- self._mlist.from_is_list = FromIsList.munge_from
+ 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?= <anne@example.com>'
- dmarc.process(self._mlist, msg, {})
+ dmarc.process(self._mlist, msg, msgdata)
self.assertMultiLineEqual(msg.as_string(), """\
To: ant@example.com
Subject: A subject
diff --git a/src/mailman/interfaces/mailinglist.py b/src/mailman/interfaces/mailinglist.py
index 09f7d0259..0f1fbace1 100644
--- a/src/mailman/interfaces/mailinglist.py
+++ b/src/mailman/interfaces/mailinglist.py
@@ -24,8 +24,10 @@ from zope.interface import Attribute, Interface
@public
-class DMARCModerationAction(Enum):
- none = 0
+class DMARCMitigateAction(Enum):
+ # Mitigation to apply to messages From: domains publishing an applicable
+ # DMARC policy or unconditionally depending on settings.
+ no_mitigation = 0
# No DMARC mitigations.
munge_from = 1
# Messages From: domains with DMARC policy will have From: replaced by
@@ -41,20 +43,6 @@ class DMARCModerationAction(Enum):
@public
-class FromIsList(Enum):
- none = 0
- # No DMARC transformations will be applied to all messages.
- munge_from = 1
- # All messages will have From: replaced by the list posting address and
- # the original From: added to Reply-To: except DMARCModerationAction
- # wrap_message, reject or discard takes precedence if applicable.
- wrap_message = 2
- # All messages will be wrapped in an outer message From: the list posting
- # address except DMARCModerationAction reject or discard takes
- # precedence if applicable.
-
-
-@public
class Personalization(Enum):
none = 0
# Everyone gets a unique copy of the message, and there are a few more
@@ -249,35 +237,25 @@ class IMailingList(Interface):
# DMARC attributes.
- dmarc_moderation_action = Attribute(
- """The DMARCModerationAction to be applied to messages From: a
- domain publishing DMARC p=reject and possibly quarantine or none.
+ dmarc_mitigate_action = Attribute(
+ """The DMARCMitigateAction to be applied to messages From: a domain
+ publishing DMARC p=reject or quarantine and possibly unconditionally.
""")
- dmarc_quarantine_moderation_action = Attribute(
- """Flag to apply DMARCModerationAction to messages From: a domain
- publishing DMARC p=quarantine.
+ dmarc_mitigate_unconditionally = Attribute(
+ """A Flag to apply dmarc_mitigate_action to all messages but only if
+ dmarc_mitigate_action is other than reject or discard.
""")
- dmarc_none_moderation_action = Attribute(
- """Flag to apply DMARCModerationAction to messages From: a domain
- publishing DMARC p=none, but only when
- dmarc_quarantine_moderation_action is also true.
- """)
dmarc_moderation_notice = Attribute(
"""Text to include in any rejection notice to be sent when
- DMARCModerationAction of reject applies.
+ DMARCMitigateAction of reject applies.
""")
dmarc_wrapped_message_text = Attribute(
"""Text to be added as a separate text/plain MIME part preceding the
original message part in the wrapped message when
- DMARCModerationAction of wrap_message applies.
- """)
-
- from_is_list = Attribute(
- """The FromIsList action to be applied to all messages for which
- DMARCModerationAction is none or not applicable.
+ DMARCMitigateAction of wrap_message applies.
""")
# Rosters and subscriptions.
diff --git a/src/mailman/model/mailinglist.py b/src/mailman/model/mailinglist.py
index 51d322dbc..fe35f6127 100644
--- a/src/mailman/model/mailinglist.py
+++ b/src/mailman/model/mailinglist.py
@@ -22,7 +22,7 @@ import os
from mailman.config import config
from mailman.database.model import Model
from mailman.database.transaction import dbconnection
-from mailman.database.types import Enum, SAUnicode
+from mailman.database.types import Enum, SAUnicode, SAUnicodeLarge
from mailman.interfaces.action import Action, FilterAction
from mailman.interfaces.address import IAddress
from mailman.interfaces.archiver import ArchivePolicy
@@ -32,7 +32,7 @@ from mailman.interfaces.digests import DigestFrequency
from mailman.interfaces.domain import IDomainManager
from mailman.interfaces.languages import ILanguageManager
from mailman.interfaces.mailinglist import (
- DMARCModerationAction, FromIsList, IAcceptableAlias, IAcceptableAliasSet,
+ DMARCMitigateAction, IAcceptableAlias, IAcceptableAliasSet,
IHeaderMatch, IHeaderMatchList, IListArchiver, IListArchiverSet,
IMailingList, Personalization, ReplyToMunging, SubscriptionPolicy)
from mailman.interfaces.member import (
@@ -128,12 +128,10 @@ class MailingList(Model):
Enum(UnrecognizedBounceDisposition))
process_bounces = Column(Boolean)
# DMARC
- dmarc_moderation_action = Column(Enum(DMARCModerationAction))
- dmarc_quarantine_moderation_action = Column(Boolean)
- dmarc_none_moderation_action = Column(Boolean)
- dmarc_moderation_notice = Column(SAUnicode)
- dmarc_wrapped_message_text = Column(SAUnicode)
- from_is_list = Column(Enum(FromIsList))
+ dmarc_mitigate_action = Column(Enum(DMARCMitigateAction))
+ dmarc_mitigate_unconditionally = Column(Boolean)
+ dmarc_moderation_notice = Column(SAUnicodeLarge)
+ dmarc_wrapped_message_text = Column(SAUnicodeLarge)
# Miscellaneous
default_member_action = Column(Enum(Action))
default_nonmember_action = Column(Enum(Action))
diff --git a/src/mailman/rest/docs/listconf.rst b/src/mailman/rest/docs/listconf.rst
index da9f1e299..343e50715 100644
--- a/src/mailman/rest/docs/listconf.rst
+++ b/src/mailman/rest/docs/listconf.rst
@@ -44,16 +44,14 @@ All readable attributes for a list are available on a sub-resource.
digest_volume_frequency: monthly
digests_enabled: True
display_name: Ant
- dmarc_moderation_action: none
+ dmarc_mitigate_action: no_mitigation
+ dmarc_mitigate_unconditionally: False
dmarc_moderation_notice:
- dmarc_none_moderation_action: False
- dmarc_quarantine_moderation_action: True
dmarc_wrapped_message_text:
filter_content: False
first_strip_reply_to: False
footer_uri:
fqdn_listname: ant@example.com
- from_is_list: none
goodbye_message_uri:
header_uri:
http_etag: "..."
@@ -116,15 +114,13 @@ When using ``PUT``, all writable attributes must be included.
... digest_size_threshold=10.5,
... digest_volume_frequency='yearly',
... digests_enabled=False,
- ... dmarc_moderation_action='munge_from',
+ ... dmarc_mitigate_action='munge_from',
+ ... dmarc_mitigate_unconditionally=False,
... dmarc_moderation_notice='Some moderation notice',
- ... dmarc_none_moderation_action=True,
- ... dmarc_quarantine_moderation_action=False,
... dmarc_wrapped_message_text='some message text',
... posting_pipeline='virgin',
... filter_content=True,
... first_strip_reply_to=True,
- ... from_is_list='none',
... convert_html_to_plaintext=True,
... collapse_alternatives=False,
... reply_goes_to_list='point_to_list',
@@ -174,10 +170,9 @@ These values are changed permanently.
digest_volume_frequency: yearly
digests_enabled: False
display_name: Fnords
- dmarc_moderation_action: munge_from
+ dmarc_mitigate_action: munge_from
+ dmarc_mitigate_unconditionally: False
dmarc_moderation_notice: Some moderation notice
- dmarc_none_moderation_action: True
- dmarc_quarantine_moderation_action: False
dmarc_wrapped_message_text: some message text
filter_content: True
first_strip_reply_to: True
diff --git a/src/mailman/rest/listconf.py b/src/mailman/rest/listconf.py
index 416336142..d5a1ca0c1 100644
--- a/src/mailman/rest/listconf.py
+++ b/src/mailman/rest/listconf.py
@@ -24,8 +24,8 @@ from mailman.interfaces.archiver import ArchivePolicy
from mailman.interfaces.autorespond import ResponseAction
from mailman.interfaces.digests import DigestFrequency
from mailman.interfaces.mailinglist import (
- DMARCModerationAction, FromIsList, IAcceptableAliasSet, IMailingList,
- ReplyToMunging, SubscriptionPolicy)
+ DMARCMitigateAction, IAcceptableAliasSet, IMailingList, ReplyToMunging,
+ SubscriptionPolicy)
from mailman.interfaces.template import ITemplateManager
from mailman.rest.helpers import (
GetterSetter, bad_request, etag, no_content, not_found, okay)
@@ -152,16 +152,14 @@ ATTRIBUTES = dict(
digest_size_threshold=GetterSetter(float),
digest_volume_frequency=GetterSetter(enum_validator(DigestFrequency)),
digests_enabled=GetterSetter(as_boolean),
- dmarc_moderation_action=GetterSetter(
- enum_validator(DMARCModerationAction)),
+ dmarc_mitigate_action=GetterSetter(
+ enum_validator(DMARCMitigateAction)),
+ dmarc_mitigate_unconditionally=GetterSetter(as_boolean),
dmarc_moderation_notice=GetterSetter(str),
- dmarc_none_moderation_action=GetterSetter(as_boolean),
- dmarc_quarantine_moderation_action=GetterSetter(as_boolean),
dmarc_wrapped_message_text=GetterSetter(str),
filter_content=GetterSetter(as_boolean),
first_strip_reply_to=GetterSetter(as_boolean),
fqdn_listname=GetterSetter(None),
- from_is_list=GetterSetter(enum_validator(FromIsList)),
include_rfc2369_headers=GetterSetter(as_boolean),
info=GetterSetter(str),
join_address=GetterSetter(None),
diff --git a/src/mailman/rest/tests/test_listconf.py b/src/mailman/rest/tests/test_listconf.py
index d9eb91c02..b9b64464c 100644
--- a/src/mailman/rest/tests/test_listconf.py
+++ b/src/mailman/rest/tests/test_listconf.py
@@ -62,14 +62,12 @@ RESOURCE = dict(
digest_volume_frequency='monthly',
digests_enabled=True,
display_name='Fnords',
- dmarc_moderation_action='munge_from',
+ dmarc_mitigate_action='munge_from',
+ dmarc_mitigate_unconditionally=False,
dmarc_moderation_notice='Some moderation notice',
- dmarc_none_moderation_action=True,
- dmarc_quarantine_moderation_action=False,
dmarc_wrapped_message_text='some message text',
filter_content=True,
first_strip_reply_to=True,
- from_is_list='none',
goodbye_message_uri='mailman:///goodbye.txt',
include_rfc2369_headers=False,
info='This is the mailing list info',
diff --git a/src/mailman/rules/dmarc.py b/src/mailman/rules/dmarc.py
index b9781fd40..dac1cd88c 100644
--- a/src/mailman/rules/dmarc.py
+++ b/src/mailman/rules/dmarc.py
@@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License along with
# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
-"""DMARC moderation rule."""
+"""DMARC mitigation rule."""
import re
import logging
@@ -26,7 +26,7 @@ from email.utils import parseaddr
from lazr.config import as_timedelta
from mailman.config import config
from mailman.core.i18n import _
-from mailman.interfaces.mailinglist import DMARCModerationAction
+from mailman.interfaces.mailinglist import DMARCMitigateAction
from mailman.interfaces.rules import IRule
from mailman.utilities.string import wrap
from public import public
@@ -110,7 +110,7 @@ def _get_org_dom(domain):
def _IsDMARCProhibited(mlist, email):
# This takes an email address, and returns True if DMARC policy is
- # p=reject or possibly quarantine or none.
+ # p=reject or quarantine.
email = email.lower()
# Scan from the right in case quoted local part has an '@'.
at_sign = email.rfind('@')
@@ -205,54 +205,40 @@ def _DMARCProhibited(mlist, email, dmarc_domain, org=False):
found p=reject in %s = %s""",
mlist.list_name, email, dmarc_domain, name, entry)
return True
-
- if (mlist.dmarc_quarantine_moderation_action and
- policy == 'quarantine'):
+ if policy == 'quarantine':
vlog.info(
"""%s: DMARC lookup for %s (%s)
found p=quarantine in %s = %s""",
mlist.list_name, email, dmarc_domain, name, entry)
return True
-
- if (mlist.dmarc_none_moderation_action and
- mlist.dmarc_quarantine_moderation_action and
- mlist.dmarc_moderation_action in (
- DMARCModerationAction.munge_from,
- DMARCModerationAction.wrap_message) and
- policy == 'none'):
- vlog.info(
- '%s: DMARC lookup for %s (%s) found p=none in %s = %s',
- mlist.list_name, email, dmarc_domain, name, entry)
- return True
-
return False
@public
@implementer(IRule)
-class DMARCModeration:
- """The DMARC moderation rule."""
+class DMARCMitigation:
+ """The DMARC mitigation rule."""
- name = 'dmarc-moderation'
+ name = 'dmarc-mitigation'
description = _('Find DMARC policy of From: domain.')
record = True
def check(self, mlist, msg, msgdata):
"""See `IRule`."""
- if mlist.dmarc_moderation_action is DMARCModerationAction.none:
+ if mlist.dmarc_mitigate_action is DMARCMitigateAction.no_mitigation:
# Don't bother to check if we're not going to do anything.
return False
dn, addr = parseaddr(msg.get('from'))
if _IsDMARCProhibited(mlist, addr):
- # If dmarc_moderation_action is discard or reject, this rule fires
+ # If dmarc_mitigate_action is discard or reject, this rule fires
# and jumps to the 'moderation' chain to do the actual discard.
# Otherwise, the rule misses but sets a flag for the dmarc handler
# to do the appropriate action.
msgdata['dmarc'] = True
- if mlist.dmarc_moderation_action is DMARCModerationAction.discard:
+ if mlist.dmarc_mitigate_action is DMARCMitigateAction.discard:
msgdata['moderation_action'] = 'discard'
msgdata['moderation_reasons'] = [_('DMARC moderation')]
- elif mlist.dmarc_moderation_action is DMARCModerationAction.reject:
+ elif mlist.dmarc_mitigate_action is DMARCMitigateAction.reject:
listowner = mlist.owner_address # noqa F841
reason = (mlist.dmarc_moderation_notice or
_('You are not allowed to post to this mailing '
diff --git a/src/mailman/rules/docs/dmarc-moderation.rst b/src/mailman/rules/docs/dmarc-mitigation.rst
index 8aab08161..7118a5be4 100644
--- a/src/mailman/rules/docs/dmarc-moderation.rst
+++ b/src/mailman/rules/docs/dmarc-mitigation.rst
@@ -1,17 +1,18 @@
================
-DMARC moderation
+DMARC mitigation
================
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.
+or discard the message. The rule looks at the list's dmarc_mitigate_action
+and if it is other than 'no_mitigation', 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']
+ >>> rule = config.rules['dmarc-mitigation']
>>> print(rule.name)
- dmarc-moderation
+ dmarc-mitigation
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.
@@ -21,8 +22,8 @@ This returns p=reject for the example.biz domain and not for any others.
A message From: a domain without a DMARC policy does not set any flags.
- >>> from mailman.interfaces.mailinglist import DMARCModerationAction
- >>> mlist.dmarc_moderation_action = DMARCModerationAction.munge_from
+ >>> from mailman.interfaces.mailinglist import DMARCMitigateAction
+ >>> mlist.dmarc_mitigate_action = DMARCMitigateAction.munge_from
>>> msg = message_from_string("""\
... From: aperson@example.org
... To: _xtest@example.com
@@ -37,9 +38,9 @@ A message From: a domain without a DMARC policy does not set any flags.
True
Even if the From: domain publishes p=reject, no flags are set if the list's
-action is none.
+action is no_mitigation.
- >>> mlist.dmarc_moderation_action = DMARCModerationAction.none
+ >>> mlist.dmarc_mitigate_action = DMARCMitigateAction.no_mitigation
>>> msg = message_from_string("""\
... From: aperson@example.biz
... To: _xtest@example.com
@@ -55,7 +56,7 @@ action is none.
But with a different list setting, the message is flagged.
- >>> mlist.dmarc_moderation_action = DMARCModerationAction.munge_from
+ >>> mlist.dmarc_mitigate_action = DMARCMitigateAction.munge_from
>>> msg = message_from_string("""\
... From: aperson@example.biz
... To: _xtest@example.com
@@ -87,7 +88,7 @@ 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.
- >>> mlist.dmarc_moderation_action = DMARCModerationAction.discard
+ >>> mlist.dmarc_mitigate_action = DMARCMitigateAction.discard
>>> msg = message_from_string("""\
... From: aperson@example.biz
... To: _xtest@example.com
@@ -106,7 +107,7 @@ message.
We can reject the message with a default reason.
- >>> mlist.dmarc_moderation_action = DMARCModerationAction.reject
+ >>> mlist.dmarc_mitigate_action = DMARCMitigateAction.reject
>>> msg = message_from_string("""\
... From: aperson@example.biz
... To: _xtest@example.com
diff --git a/src/mailman/rules/docs/rules.rst b/src/mailman/rules/docs/rules.rst
index e00889061..8b351650c 100644
--- a/src/mailman/rules/docs/rules.rst
+++ b/src/mailman/rules/docs/rules.rst
@@ -22,7 +22,7 @@ names to rule objects.
any True
approved True
banned-address True
- dmarc-moderation True
+ dmarc-mitigation True
emergency True
implicit-dest True
loop True
diff --git a/src/mailman/runners/docs/incoming.rst b/src/mailman/runners/docs/incoming.rst
index 18b723ab4..33810d104 100644
--- a/src/mailman/runners/docs/incoming.rst
+++ b/src/mailman/runners/docs/incoming.rst
@@ -128,7 +128,7 @@ Now the message is in the pipeline queue.
Message-ID-Hash: 4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB
X-Message-ID-Hash: 4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB
Date: ...
- X-Mailman-Rule-Misses: dmarc-moderation; approved; emergency; loop;
+ X-Mailman-Rule-Misses: dmarc-mitigation; approved; emergency; loop;
banned-address; member-moderation; nonmember-moderation; administrivia;
implicit-dest; max-recipients; max-size; news-moderation; no-subject;
suspicious-header
diff --git a/src/mailman/styles/base.py b/src/mailman/styles/base.py
index 9530b0ea3..693a2ab14 100644
--- a/src/mailman/styles/base.py
+++ b/src/mailman/styles/base.py
@@ -31,8 +31,7 @@ from mailman.interfaces.autorespond import ResponseAction
from mailman.interfaces.bounce import UnrecognizedBounceDisposition
from mailman.interfaces.digests import DigestFrequency
from mailman.interfaces.mailinglist import (
- DMARCModerationAction, FromIsList, Personalization, ReplyToMunging,
- SubscriptionPolicy)
+ DMARCMitigateAction, Personalization, ReplyToMunging, SubscriptionPolicy)
from mailman.interfaces.nntp import NewsgroupModeration
from public import public
@@ -87,12 +86,10 @@ class BasicOperation:
mlist.digest_volume_frequency = DigestFrequency.monthly
mlist.next_digest_number = 1
# DMARC
- mlist.dmarc_moderation_action = DMARCModerationAction.none
- mlist.dmarc_quarantine_moderation_action = True
- mlist.dmarc_none_moderation_action = False
+ mlist.dmarc_mitigate_action = DMARCMitigateAction.no_mitigation
+ mlist.dmarc_mitigate_unconditionally = False
mlist.dmarc_moderation_notice = ''
mlist.dmarc_wrapped_message_text = ''
- mlist.from_is_list = FromIsList.none
# NNTP gateway
mlist.nntp_host = ''
mlist.linked_newsgroup = ''