diff options
| author | Mark Sapiro | 2016-12-26 14:43:36 -0800 |
|---|---|---|
| committer | Mark Sapiro | 2016-12-26 14:43:36 -0800 |
| commit | 2ead4c9f0f70ac3ebd06105562579f74fa6963f1 (patch) | |
| tree | a59cf040acff390d7b5847cf08f84ff5b51dbcfb /src | |
| parent | 17aa36cf4a5ec2cfce7b1e6af585b0f18a82a28b (diff) | |
| download | mailman-2ead4c9f0f70ac3ebd06105562579f74fa6963f1.tar.gz mailman-2ead4c9f0f70ac3ebd06105562579f74fa6963f1.tar.zst mailman-2ead4c9f0f70ac3ebd06105562579f74fa6963f1.zip | |
Diffstat (limited to 'src')
| -rw-r--r-- | src/mailman/chains/builtin.py | 2 | ||||
| -rw-r--r-- | src/mailman/chains/docs/moderation.rst | 10 | ||||
| -rw-r--r-- | src/mailman/core/docs/chains.rst | 4 | ||||
| -rw-r--r-- | src/mailman/database/alembic/versions/3002bac0c25a_dmarc_attributes.py | 49 | ||||
| -rw-r--r-- | src/mailman/handlers/dmarc.py | 47 | ||||
| -rw-r--r-- | src/mailman/handlers/docs/dmarc-mitigations.rst | 42 | ||||
| -rw-r--r-- | src/mailman/handlers/tests/test_dmarc.py | 35 | ||||
| -rw-r--r-- | src/mailman/interfaces/mailinglist.py | 46 | ||||
| -rw-r--r-- | src/mailman/model/mailinglist.py | 14 | ||||
| -rw-r--r-- | src/mailman/rest/docs/listconf.rst | 17 | ||||
| -rw-r--r-- | src/mailman/rest/listconf.py | 12 | ||||
| -rw-r--r-- | src/mailman/rest/tests/test_listconf.py | 6 | ||||
| -rw-r--r-- | src/mailman/rules/dmarc.py | 36 | ||||
| -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.rst | 2 | ||||
| -rw-r--r-- | src/mailman/runners/docs/incoming.rst | 2 | ||||
| -rw-r--r-- | src/mailman/styles/base.py | 9 |
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 = '' |
