summaryrefslogtreecommitdiff
path: root/mailman/rules
diff options
context:
space:
mode:
authorBarry Warsaw2009-01-25 13:01:41 -0500
committerBarry Warsaw2009-01-25 13:01:41 -0500
commiteefd06f1b88b8ecbb23a9013cd223b72ca85c20d (patch)
tree72c947fe16fce0e07e996ee74020b26585d7e846 /mailman/rules
parent07871212f74498abd56bef3919bf3e029eb8b930 (diff)
downloadmailman-eefd06f1b88b8ecbb23a9013cd223b72ca85c20d.tar.gz
mailman-eefd06f1b88b8ecbb23a9013cd223b72ca85c20d.tar.zst
mailman-eefd06f1b88b8ecbb23a9013cd223b72ca85c20d.zip
Diffstat (limited to 'mailman/rules')
-rw-r--r--mailman/rules/__init__.py54
-rw-r--r--mailman/rules/administrivia.py102
-rw-r--r--mailman/rules/any.py45
-rw-r--r--mailman/rules/approved.py121
-rw-r--r--mailman/rules/docs/administrivia.txt99
-rw-r--r--mailman/rules/docs/approve.txt472
-rw-r--r--mailman/rules/docs/emergency.txt72
-rw-r--r--mailman/rules/docs/header-matching.txt144
-rw-r--r--mailman/rules/docs/implicit-dest.txt75
-rw-r--r--mailman/rules/docs/loop.txt48
-rw-r--r--mailman/rules/docs/max-size.txt39
-rw-r--r--mailman/rules/docs/moderation.txt69
-rw-r--r--mailman/rules/docs/news-moderation.txt36
-rw-r--r--mailman/rules/docs/no-subject.txt33
-rw-r--r--mailman/rules/docs/recipients.txt40
-rw-r--r--mailman/rules/docs/rules.txt69
-rw-r--r--mailman/rules/docs/suspicious.txt35
-rw-r--r--mailman/rules/docs/truth.txt9
-rw-r--r--mailman/rules/emergency.py50
-rw-r--r--mailman/rules/implicit_dest.py99
-rw-r--r--mailman/rules/loop.py48
-rw-r--r--mailman/rules/max_recipients.py52
-rw-r--r--mailman/rules/max_size.py50
-rw-r--r--mailman/rules/moderation.py68
-rw-r--r--mailman/rules/news_moderation.py49
-rw-r--r--mailman/rules/no_subject.py46
-rw-r--r--mailman/rules/suspicious.py100
-rw-r--r--mailman/rules/truth.py45
28 files changed, 0 insertions, 2169 deletions
diff --git a/mailman/rules/__init__.py b/mailman/rules/__init__.py
deleted file mode 100644
index 6c7034772..000000000
--- a/mailman/rules/__init__.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# Copyright (C) 2007-2009 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/>.
-
-"""The built in rule set."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'initialize',
- ]
-
-
-import os
-import sys
-
-from mailman.interfaces.rules import IRule
-
-
-
-def initialize():
- """Initialize the built-in rules.
-
- Rules are auto-discovered by searching for IRule implementations in all
- importable modules in this subpackage.
- """
- # Find all rules found in all modules inside our package.
- import mailman.rules
- here = os.path.dirname(mailman.rules.__file__)
- for filename in os.listdir(here):
- basename, extension = os.path.splitext(filename)
- if extension <> '.py':
- continue
- module_name = 'mailman.rules.' + basename
- __import__(module_name, fromlist='*')
- module = sys.modules[module_name]
- for name in module.__all__:
- rule = getattr(module, name)
- if IRule.implementedBy(rule):
- yield rule
diff --git a/mailman/rules/administrivia.py b/mailman/rules/administrivia.py
deleted file mode 100644
index 8807ef952..000000000
--- a/mailman/rules/administrivia.py
+++ /dev/null
@@ -1,102 +0,0 @@
-# Copyright (C) 2007-2009 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/>.
-
-"""The administrivia rule."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'Administrivia',
- ]
-
-
-from email.iterators import typed_subpart_iterator
-from zope.interface import implements
-
-from mailman.config import config
-from mailman.i18n import _
-from mailman.interfaces.rules import IRule
-
-
-# The list of email commands we search for in the Subject header and payload.
-# We probably should get this information from the actual implemented
-# commands.
-EMAIL_COMMANDS = {
- # keyword: (minimum #args, maximum #args)
- 'confirm': (1, 1),
- 'help': (0, 0),
- 'info': (0, 0),
- 'lists': (0, 0),
- 'options': (0, 0),
- 'password': (2, 2),
- 'remove': (0, 0),
- 'set': (3, 3),
- 'subscribe': (0, 3),
- 'unsubscribe': (0, 1),
- 'who': (0, 2),
- }
-
-
-
-class Administrivia:
- """The administrivia rule."""
- implements(IRule)
-
- name = 'administrivia'
- description = _('Catch mis-addressed email commands.')
- record = True
-
- def check(self, mlist, msg, msgdata):
- """See `IRule`."""
- # The list must have the administrivia check enabled.
- if not mlist.administrivia:
- return False
- # First check the Subject text.
- lines_to_check = []
- subject = str(msg.get('subject', ''))
- if subject <> '':
- lines_to_check.append(subject)
- # Search only the first text/plain subpart of the message. There's
- # really no good way to find email commands in any other content type.
- for part in typed_subpart_iterator(msg, 'text', 'plain'):
- payload = part.get_payload(decode=True)
- lines = payload.splitlines()
- # Count lines without using enumerate() because blank lines in the
- # payload don't count against the maximum examined.
- lineno = 0
- for line in lines:
- line = line.strip()
- if line == '':
- continue
- lineno += 1
- if lineno > config.mailman.email_commands_max_lines:
- break
- lines_to_check.append(line)
- # Only look at the first text/plain part.
- break
- # For each line we're checking, split the line into words. Then see
- # if it looks like a command with the min-to-max number of arguments.
- for line in lines_to_check:
- words = [word.lower() for word in line.split()]
- if words[0] not in EMAIL_COMMANDS:
- # This is not an administrivia command.
- continue
- minargs, maxargs = EMAIL_COMMANDS[words[0]]
- if minargs <= len(words) - 1 <= maxargs:
- return True
- return False
diff --git a/mailman/rules/any.py b/mailman/rules/any.py
deleted file mode 100644
index c337df7f1..000000000
--- a/mailman/rules/any.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# Copyright (C) 2007-2009 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/>.
-
-"""Check if any previous rules have matched."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'Any',
- ]
-
-
-from zope.interface import implements
-
-from mailman.i18n import _
-from mailman.interfaces.rules import IRule
-
-
-
-class Any:
- """Look for any previous rule match."""
- implements(IRule)
-
- name = 'any'
- description = _('Look for any previous rule hit.')
- record = False
-
- def check(self, mlist, msg, msgdata):
- """See `IRule`."""
- return len(msgdata.get('rules', [])) > 0
diff --git a/mailman/rules/approved.py b/mailman/rules/approved.py
deleted file mode 100644
index f81c1ad04..000000000
--- a/mailman/rules/approved.py
+++ /dev/null
@@ -1,121 +0,0 @@
-# Copyright (C) 2007-2009 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/>.
-
-"""Look for moderator pre-approval."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'Approved',
- ]
-
-
-import re
-from email.iterators import typed_subpart_iterator
-from zope.interface import implements
-
-from mailman.i18n import _
-from mailman.interfaces.rules import IRule
-
-
-EMPTYSTRING = ''
-
-
-
-class Approved:
- """Look for moderator pre-approval."""
- implements(IRule)
-
- name = 'approved'
- description = _('The message has a matching Approve or Approved header.')
- record = True
-
- def check(self, mlist, msg, msgdata):
- """See `IRule`."""
- # See if the message has an Approved or Approve header with a valid
- # moderator password. Also look at the first non-whitespace line in
- # the file to see if it looks like an Approved header.
- missing = object()
- password = msg.get('approved', msg.get('approve', missing))
- if password is missing:
- # Find the first text/plain part in the message
- part = None
- stripped = False
- for part in typed_subpart_iterator(msg, 'text', 'plain'):
- break
- payload = part.get_payload(decode=True)
- if payload is not None:
- lines = payload.splitlines(True)
- for lineno, line in enumerate(lines):
- if line.strip() <> '':
- break
- if ':' in line:
- header, value = line.split(':', 1)
- if header.lower() in ('approved', 'approve'):
- password = value.strip()
- # Now strip the first line from the payload so the
- # password doesn't leak.
- del lines[lineno]
- reset_payload(part, EMPTYSTRING.join(lines))
- stripped = True
- if stripped:
- # Now try all the text parts in case it's
- # multipart/alternative with the approved line in HTML or
- # other text part. We make a pattern from the Approved line
- # and delete it from all text/* parts in which we find it. It
- # would be better to just iterate forward, but email
- # compatability for pre Python 2.2 returns a list, not a true
- # iterator.
- #
- # This will process all the multipart/alternative parts in the
- # message as well as all other text parts. We shouldn't find
- # the pattern outside the multipart/alternative parts, but if
- # we do, it is probably best to delete it anyway as it does
- # contain the password.
- #
- # Make a pattern to delete. We can't just delete a line
- # because line of HTML or other fancy text may include
- # additional message text. This pattern works with HTML. It
- # may not work with rtf or whatever else is possible.
- pattern = header + ':(\s|&nbsp;)*' + re.escape(password)
- for part in typed_subpart_iterator(msg, 'text'):
- payload = part.get_payload(decode=True)
- if payload is not None:
- if re.search(pattern, payload):
- reset_payload(part, re.sub(pattern, '', payload))
- else:
- del msg['approved']
- del msg['approve']
- return password is not missing and password == mlist.moderator_password
-
-
-
-def reset_payload(part, payload):
- # Set decoded payload maintaining content-type, charset, format and delsp.
- charset = part.get_content_charset() or 'us-ascii'
- content_type = part.get_content_type()
- format = part.get_param('format')
- delsp = part.get_param('delsp')
- del part['content-transfer-encoding']
- del part['content-type']
- part.set_payload(payload, charset)
- part.set_type(content_type)
- if format:
- part.set_param('Format', format)
- if delsp:
- part.set_param('DelSp', delsp)
diff --git a/mailman/rules/docs/administrivia.txt b/mailman/rules/docs/administrivia.txt
deleted file mode 100644
index dba882775..000000000
--- a/mailman/rules/docs/administrivia.txt
+++ /dev/null
@@ -1,99 +0,0 @@
-Administrivia
-=============
-
-The 'administrivia' rule matches when the message contains some common email
-commands in the Subject header or first few lines of the payload. This is
-used to catch messages posted to the list which should have been sent to the
--request robot address.
-
- >>> mlist = config.db.list_manager.create(u'_xtest@example.com')
- >>> mlist.administrivia = True
- >>> rule = config.rules['administrivia']
- >>> print rule.name
- administrivia
-
-For example, if the Subject header contains the word 'unsubscribe', the rule
-matches.
-
- >>> msg_1 = message_from_string("""\
- ... From: aperson@example.com
- ... Subject: unsubscribe
- ...
- ... """)
- >>> rule.check(mlist, msg_1, {})
- True
-
-Similarly, if the body of the message contains the word 'subscribe' in the
-first few lines of text, the rule matches.
-
- >>> msg_2 = message_from_string("""\
- ... From: aperson@example.com
- ... Subject: I wish to join your list
- ...
- ... subscribe
- ... """)
- >>> rule.check(mlist, msg_2, {})
- True
-
-In both cases, administrivia checking can be disabled.
-
- >>> mlist.administrivia = False
- >>> rule.check(mlist, msg_1, {})
- False
- >>> rule.check(mlist, msg_2, {})
- False
-
-To make the administrivia heuristics a little more robust, the rule actually
-looks for a minimum and maximum number of arguments, so that it really does
-seem like a mis-addressed email command. In this case, the 'confirm' command
-requires at least one argument. We don't give that here so the rule will not
-match.
-
- >>> mlist.administrivia = True
- >>> msg = message_from_string("""\
- ... From: aperson@example.com
- ... Subject: confirm
- ...
- ... """)
- >>> rule.check(mlist, msg, {})
- False
-
-But a real 'confirm' message will match.
-
- >>> msg = message_from_string("""\
- ... From: aperson@example.com
- ... Subject: confirm 12345
- ...
- ... """)
- >>> rule.check(mlist, msg, {})
- True
-
-We don't show all the other possible email commands, but you get the idea.
-
-
-Non-administrivia
------------------
-
-Of course, messages that don't contain administrivia, don't match the rule.
-
- >>> msg = message_from_string("""\
- ... From: aperson@example.com
- ... Subject: examine
- ...
- ... persuade
- ... """)
- >>> rule.check(mlist, msg, {})
- False
-
-Also, only text/plain parts are checked for administrivia, so any email
-commands in other content type subparts are ignored.
-
- >>> msg = message_from_string("""\
- ... From: aperson@example.com
- ... Subject: some administrivia
- ... Content-Type: text/x-special
- ...
- ... subscribe
- ... """)
- >>> rule.check(mlist, msg, {})
- False
diff --git a/mailman/rules/docs/approve.txt b/mailman/rules/docs/approve.txt
deleted file mode 100644
index dda531a4c..000000000
--- a/mailman/rules/docs/approve.txt
+++ /dev/null
@@ -1,472 +0,0 @@
-Pre-approved postings
-=====================
-
-Messages can contain a pre-approval, which is used to bypass the message
-approval queue. This has several use cases:
-
-- A list administrator can send an emergency message to the mailing list from
- an unregistered address, say if they are away from their normal email.
-
-- An automated script can be programmed to send a message to an otherwise
- moderated list.
-
-In order to support this, a mailing list can be given a 'moderator password'
-which is shared among all the administrators.
-
- >>> mlist = config.db.list_manager.create(u'_xtest@example.com')
- >>> mlist.moderator_password = u'abcxyz'
-
-The 'approved' rule determines whether the message contains the proper
-approval or not.
-
- >>> rule = config.rules['approved']
- >>> print rule.name
- approved
-
-
-No approval
------------
-
-If the message has no Approve or Approved header, then the rule does not
-match.
-
- >>> msg = message_from_string("""\
- ... From: aperson@example.com
- ...
- ... An important message.
- ... """)
- >>> rule.check(mlist, msg, {})
- False
-
-If the message has an Approve or Approved header with a value that does not
-match the moderator password, then the rule does not match. However, the
-header is still removed.
-
- >>> msg['Approve'] = u'12345'
- >>> rule.check(mlist, msg, {})
- False
- >>> print msg['approve']
- None
-
- >>> del msg['approve']
- >>> msg['Approved'] = u'12345'
- >>> rule.check(mlist, msg, {})
- False
- >>> print msg['approved']
- None
-
- >>> del msg['approved']
-
-
-Using an approval header
-------------------------
-
-If the moderator password is given in an Approve header, then the rule
-matches, and the Approve header is stripped.
-
- >>> msg['Approve'] = u'abcxyz'
- >>> rule.check(mlist, msg, {})
- True
- >>> print msg['approve']
- None
-
-Similarly, for the Approved header.
-
- >>> msg['Approved'] = u'abcxyz'
- >>> rule.check(mlist, msg, {})
- True
- >>> print msg['approved']
- None
-
-
-Using a pseudo-header
----------------------
-
-Different mail user agents have varying degrees to which they support custom
-headers like Approve and Approved. For this reason, Mailman also supports
-using a 'pseudo-header', which is really just the first non-whitespace line in
-the payload of the message. If this pseudo-header looks like a matching
-Approve or Approved header, the message is similarly allowed to pass.
-
- >>> msg = message_from_string("""\
- ... From: aperson@example.com
- ...
- ... Approve: abcxyz
- ... An important message.
- ... """)
- >>> rule.check(mlist, msg, {})
- True
-
-The pseudo-header is removed.
-
- >>> print msg.as_string()
- From: aperson@example.com
- Content-Transfer-Encoding: 7bit
- MIME-Version: 1.0
- Content-Type: text/plain; charset="us-ascii"
- <BLANKLINE>
- An important message.
- <BLANKLINE>
-
-Similarly for the Approved header.
-
- >>> msg = message_from_string("""\
- ... From: aperson@example.com
- ...
- ... Approved: abcxyz
- ... An important message.
- ... """)
- >>> rule.check(mlist, msg, {})
- True
-
- >>> print msg.as_string()
- From: aperson@example.com
- Content-Transfer-Encoding: 7bit
- MIME-Version: 1.0
- Content-Type: text/plain; charset="us-ascii"
- <BLANKLINE>
- An important message.
- <BLANKLINE>
-
-As before, a mismatch in the pseudo-header does not approve the message, but
-the pseudo-header line is still removed.
-
- >>> msg = message_from_string("""\
- ... From: aperson@example.com
- ...
- ... Approve: 123456
- ... An important message.
- ... """)
- >>> rule.check(mlist, msg, {})
- False
-
- >>> print msg.as_string()
- From: aperson@example.com
- Content-Transfer-Encoding: 7bit
- MIME-Version: 1.0
- Content-Type: text/plain; charset="us-ascii"
- <BLANKLINE>
- An important message.
- <BLANKLINE>
-
-Similarly for the Approved header.
-
- >>> msg = message_from_string("""\
- ... From: aperson@example.com
- ...
- ... Approved: 123456
- ... An important message.
- ... """)
- >>> rule.check(mlist, msg, {})
- False
-
- >>> print msg.as_string()
- From: aperson@example.com
- Content-Transfer-Encoding: 7bit
- MIME-Version: 1.0
- Content-Type: text/plain; charset="us-ascii"
- <BLANKLINE>
- An important message.
- <BLANKLINE>
-
-
-MIME multipart support
-----------------------
-
-Mailman searches for the pseudo-header as the first non-whitespace line in the
-first text/plain message part of the message. This allows the feature to be
-used with MIME documents.
-
- >>> msg = message_from_string("""\
- ... From: aperson@example.com
- ... MIME-Version: 1.0
- ... Content-Type: multipart/mixed; boundary="AAA"
- ...
- ... --AAA
- ... Content-Type: application/x-ignore
- ...
- ... Approve: 123456
- ... The above line will be ignored.
- ...
- ... --AAA
- ... Content-Type: text/plain
- ...
- ... Approve: abcxyz
- ... An important message.
- ... --AAA--
- ... """)
- >>> rule.check(mlist, msg, {})
- True
-
-Like before, the pseudo-header is removed, but only from the text parts.
-
- >>> print msg.as_string()
- From: aperson@example.com
- MIME-Version: 1.0
- Content-Type: multipart/mixed; boundary="AAA"
- <BLANKLINE>
- --AAA
- Content-Type: application/x-ignore
- <BLANKLINE>
- Approve: 123456
- The above line will be ignored.
- <BLANKLINE>
- --AAA
- Content-Transfer-Encoding: 7bit
- MIME-Version: 1.0
- Content-Type: text/plain; charset="us-ascii"
- <BLANKLINE>
- An important message.
- --AAA--
- <BLANKLINE>
-
-The same goes for the Approved message.
-
- >>> msg = message_from_string("""\
- ... From: aperson@example.com
- ... MIME-Version: 1.0
- ... Content-Type: multipart/mixed; boundary="AAA"
- ...
- ... --AAA
- ... Content-Type: application/x-ignore
- ...
- ... Approved: 123456
- ... The above line will be ignored.
- ...
- ... --AAA
- ... Content-Type: text/plain
- ...
- ... Approved: abcxyz
- ... An important message.
- ... --AAA--
- ... """)
- >>> rule.check(mlist, msg, {})
- True
-
-And the header is removed.
-
- >>> print msg.as_string()
- From: aperson@example.com
- MIME-Version: 1.0
- Content-Type: multipart/mixed; boundary="AAA"
- <BLANKLINE>
- --AAA
- Content-Type: application/x-ignore
- <BLANKLINE>
- Approved: 123456
- The above line will be ignored.
- <BLANKLINE>
- --AAA
- Content-Transfer-Encoding: 7bit
- MIME-Version: 1.0
- Content-Type: text/plain; charset="us-ascii"
- <BLANKLINE>
- An important message.
- --AAA--
- <BLANKLINE>
-
-Here, the correct password is in the non-text/plain part, so it is ignored.
-
- >>> msg = message_from_string("""\
- ... From: aperson@example.com
- ... MIME-Version: 1.0
- ... Content-Type: multipart/mixed; boundary="AAA"
- ...
- ... --AAA
- ... Content-Type: application/x-ignore
- ...
- ... Approve: abcxyz
- ... The above line will be ignored.
- ...
- ... --AAA
- ... Content-Type: text/plain
- ...
- ... Approve: 123456
- ... An important message.
- ... --AAA--
- ... """)
- >>> rule.check(mlist, msg, {})
- False
-
-And yet the pseudo-header is still stripped.
-
- >>> print msg.as_string()
- From: aperson@example.com
- MIME-Version: 1.0
- Content-Type: multipart/mixed; boundary="AAA"
- <BLANKLINE>
- --AAA
- Content-Type: application/x-ignore
- <BLANKLINE>
- Approve: abcxyz
- The above line will be ignored.
- <BLANKLINE>
- --AAA
- Content-Transfer-Encoding: 7bit
- MIME-Version: 1.0
- Content-Type: text/plain; charset="us-ascii"
- <BLANKLINE>
- An important message.
- --AAA--
-
-As before, the same goes for the Approved header.
-
- >>> msg = message_from_string("""\
- ... From: aperson@example.com
- ... MIME-Version: 1.0
- ... Content-Type: multipart/mixed; boundary="AAA"
- ...
- ... --AAA
- ... Content-Type: application/x-ignore
- ...
- ... Approved: abcxyz
- ... The above line will be ignored.
- ...
- ... --AAA
- ... Content-Type: text/plain
- ...
- ... Approved: 123456
- ... An important message.
- ... --AAA--
- ... """)
- >>> rule.check(mlist, msg, {})
- False
-
-And the pseudo-header is removed.
-
- >>> print msg.as_string()
- From: aperson@example.com
- MIME-Version: 1.0
- Content-Type: multipart/mixed; boundary="AAA"
- <BLANKLINE>
- --AAA
- Content-Type: application/x-ignore
- <BLANKLINE>
- Approved: abcxyz
- The above line will be ignored.
- <BLANKLINE>
- --AAA
- Content-Transfer-Encoding: 7bit
- MIME-Version: 1.0
- Content-Type: text/plain; charset="us-ascii"
- <BLANKLINE>
- An important message.
- --AAA--
-
-
-Stripping text/html parts
--------------------------
-
-Because some mail readers will include both a text/plain part and a text/html
-alternative, the 'approved' rule has to search the alternatives and strip
-anything that looks like an Approve or Approved headers.
-
- >>> msg = message_from_string("""\
- ... From: aperson@example.com
- ... MIME-Version: 1.0
- ... Content-Type: multipart/mixed; boundary="AAA"
- ...
- ... --AAA
- ... Content-Type: text/html
- ...
- ... <html>
- ... <head></head>
- ... <body>
- ... <b>Approved: abcxyz</b>
- ... <p>The above line will be ignored.
- ... </body>
- ... </html>
- ...
- ... --AAA
- ... Content-Type: text/plain
- ...
- ... Approved: abcxyz
- ... An important message.
- ... --AAA--
- ... """)
- >>> rule.check(mlist, msg, {})
- True
-
-And the header-like text in the text/html part was stripped.
-
- >>> print msg.as_string()
- From: aperson@example.com
- MIME-Version: 1.0
- Content-Type: multipart/mixed; boundary="AAA"
- <BLANKLINE>
- --AAA
- Content-Transfer-Encoding: 7bit
- MIME-Version: 1.0
- Content-Type: text/html; charset="us-ascii"
- <BLANKLINE>
- <html>
- <head></head>
- <body>
- <b></b>
- <p>The above line will be ignored.
- </body>
- </html>
- <BLANKLINE>
- --AAA
- Content-Transfer-Encoding: 7bit
- MIME-Version: 1.0
- Content-Type: text/plain; charset="us-ascii"
- <BLANKLINE>
- An important message.
- --AAA--
- <BLANKLINE>
-
-This is true even if the rule does not match.
-
- >>> msg = message_from_string("""\
- ... From: aperson@example.com
- ... MIME-Version: 1.0
- ... Content-Type: multipart/mixed; boundary="AAA"
- ...
- ... --AAA
- ... Content-Type: text/html
- ...
- ... <html>
- ... <head></head>
- ... <body>
- ... <b>Approve: 123456</b>
- ... <p>The above line will be ignored.
- ... </body>
- ... </html>
- ...
- ... --AAA
- ... Content-Type: text/plain
- ...
- ... Approve: 123456
- ... An important message.
- ... --AAA--
- ... """)
- >>> rule.check(mlist, msg, {})
- False
-
- >>> print msg.as_string()
- From: aperson@example.com
- MIME-Version: 1.0
- Content-Type: multipart/mixed; boundary="AAA"
- <BLANKLINE>
- --AAA
- Content-Transfer-Encoding: 7bit
- MIME-Version: 1.0
- Content-Type: text/html; charset="us-ascii"
- <BLANKLINE>
- <html>
- <head></head>
- <body>
- <b></b>
- <p>The above line will be ignored.
- </body>
- </html>
- <BLANKLINE>
- --AAA
- Content-Transfer-Encoding: 7bit
- MIME-Version: 1.0
- Content-Type: text/plain; charset="us-ascii"
- <BLANKLINE>
- An important message.
- --AAA--
- <BLANKLINE>
diff --git a/mailman/rules/docs/emergency.txt b/mailman/rules/docs/emergency.txt
deleted file mode 100644
index 9d80fdb40..000000000
--- a/mailman/rules/docs/emergency.txt
+++ /dev/null
@@ -1,72 +0,0 @@
-Emergency
-=========
-
-When the mailing list has its emergency flag set, all messages posted to the
-list are held for moderator approval.
-
- >>> from mailman.app.lifecycle import create_list
- >>> mlist = create_list(u'_xtest@example.com')
- >>> msg = message_from_string("""\
- ... From: aperson@example.com
- ... To: _xtest@example.com
- ... Subject: My first post
- ... Message-ID: <first>
- ...
- ... An important message.
- ... """)
-
-The emergency rule is matched as part of the built-in chain. The emergency
-rule matches if the flag is set on the mailing list.
-
- >>> from mailman.core.chains import process
- >>> mlist.emergency = True
- >>> process(mlist, msg, {}, 'built-in')
-
-There are two messages in the virgin queue. The one addressed to the original
-sender will contain a token we can use to grab the held message out of the
-pending requests.
-
- >>> virginq = config.switchboards['virgin']
-
- >>> def get_held_message():
- ... import re
- ... qfiles = []
- ... for filebase in virginq.files:
- ... qmsg, qdata = virginq.dequeue(filebase)
- ... virginq.finish(filebase)
- ... qfiles.append(qmsg)
- ... from operator import itemgetter
- ... qfiles.sort(key=itemgetter('to'))
- ... cookie = None
- ... for line in qfiles[1].get_payload().splitlines():
- ... mo = re.search('confirm/[^/]+/(?P<cookie>.*)$', line)
- ... if mo:
- ... cookie = mo.group('cookie')
- ... break
- ... assert cookie is not None, 'No confirmation token found'
- ... data = config.db.pendings.confirm(cookie)
- ... requestdb = config.db.requests.get_list_requests(mlist)
- ... rkey, rdata = requestdb.get_request(data['id'])
- ... return config.db.message_store.get_message_by_id(
- ... rdata['_mod_message_id'])
-
- >>> msg = get_held_message()
- >>> print msg.as_string()
- From: aperson@example.com
- To: _xtest@example.com
- Subject: My first post
- Message-ID: <first>
- X-Mailman-Rule-Hits: emergency
- X-Mailman-Rule-Misses: approved
- X-Message-ID-Hash: RXJU4JL6N2OUN3OYMXXPPSCR7P7JE2BW
- <BLANKLINE>
- An important message.
- <BLANKLINE>
-
-However, if the message metadata has a 'moderator_approved' key set, then even
-if the mailing list has its emergency flag set, the message still goes through
-to the membership.
-
- >>> process(mlist, msg, dict(moderator_approved=True), 'built-in')
- >>> len(virginq.files)
- 0
diff --git a/mailman/rules/docs/header-matching.txt b/mailman/rules/docs/header-matching.txt
deleted file mode 100644
index 417000d67..000000000
--- a/mailman/rules/docs/header-matching.txt
+++ /dev/null
@@ -1,144 +0,0 @@
-Header matching
-===============
-
-Mailman can do pattern based header matching during its normal rule
-processing. There is a set of site-wide default header matches specified in
-the configuration file under the [spam.headers] section.
-
- >>> from mailman.app.lifecycle import create_list
- >>> mlist = create_list(u'_xtest@example.com')
-
-Because the default [spam.headers] section is empty, we'll just extend the
-current header matching chain with a pattern that matches 4 or more stars,
-discarding the message if it hits.
-
- >>> chain = config.chains['header-match']
- >>> chain.extend('x-spam-score', '[*]{4,}', 'discard')
-
-First, if the message has no X-Spam-Score header, the message passes through
-the chain untouched (i.e. no disposition).
-
- >>> msg = message_from_string("""\
- ... From: aperson@example.com
- ... To: _xtest@example.com
- ... Subject: Not spam
- ... Message-ID: <one>
- ...
- ... This is a message.
- ... """)
-
- >>> from mailman.core.chains import process
-
-Pass through is seen as nothing being in the log file after processing.
-
- # XXX This checks the vette log file because there is no other evidence
- # that this chain has done anything.
- >>> import os
- >>> fp = open(os.path.join(config.LOG_DIR, 'vette'))
- >>> fp.seek(0, 2)
- >>> file_pos = fp.tell()
- >>> process(mlist, msg, {}, 'header-match')
- >>> fp.seek(file_pos)
- >>> print 'LOG:', fp.read()
- LOG:
- <BLANKLINE>
-
-Now, if the header exists but does not match, then it also passes through
-untouched.
-
- >>> msg['X-Spam-Score'] = '***'
- >>> del msg['subject']
- >>> msg['Subject'] = 'This is almost spam'
- >>> del msg['message-id']
- >>> msg['Message-ID'] = '<two>'
- >>> file_pos = fp.tell()
- >>> process(mlist, msg, {}, 'header-match')
- >>> fp.seek(file_pos)
- >>> print 'LOG:', fp.read()
- LOG:
- <BLANKLINE>
-
-But now if the header matches, then the message gets discarded.
-
- >>> del msg['x-spam-score']
- >>> msg['X-Spam-Score'] = '****'
- >>> del msg['subject']
- >>> msg['Subject'] = 'This is spam, but barely'
- >>> del msg['message-id']
- >>> msg['Message-ID'] = '<three>'
- >>> file_pos = fp.tell()
- >>> process(mlist, msg, {}, 'header-match')
- >>> fp.seek(file_pos)
- >>> print 'LOG:', fp.read()
- LOG: ... DISCARD: <three>
- <BLANKLINE>
-
-For kicks, let's show a message that's really spammy.
-
- >>> del msg['x-spam-score']
- >>> msg['X-Spam-Score'] = '**********'
- >>> del msg['subject']
- >>> msg['Subject'] = 'This is really spammy'
- >>> del msg['message-id']
- >>> msg['Message-ID'] = '<four>'
- >>> file_pos = fp.tell()
- >>> process(mlist, msg, {}, 'header-match')
- >>> fp.seek(file_pos)
- >>> print 'LOG:', fp.read()
- LOG: ... DISCARD: <four>
- <BLANKLINE>
-
-Flush out the extended header matching rules.
-
- >>> chain.flush()
-
-
-List-specific header matching
------------------------------
-
-Each mailing list can also be configured with a set of header matching regular
-expression rules. These are used to impose list-specific header filtering
-with the same semantics as the global [spam.headers] section.
-
-The list administrator wants to match not on four stars, but on three plus
-signs, but only for the current mailing list.
-
- >>> mlist.header_matches = [('x-spam-score', '[+]{3,}', 'discard')]
-
-A message with a spam score of two pluses does not match.
-
- >>> del msg['x-spam-score']
- >>> msg['X-Spam-Score'] = '++'
- >>> del msg['message-id']
- >>> msg['Message-ID'] = '<five>'
- >>> file_pos = fp.tell()
- >>> process(mlist, msg, {}, 'header-match')
- >>> fp.seek(file_pos)
- >>> print 'LOG:', fp.read()
- LOG:
-
-A message with a spam score of three pluses does match.
-
- >>> del msg['x-spam-score']
- >>> msg['X-Spam-Score'] = '+++'
- >>> del msg['message-id']
- >>> msg['Message-ID'] = '<six>'
- >>> file_pos = fp.tell()
- >>> process(mlist, msg, {}, 'header-match')
- >>> fp.seek(file_pos)
- >>> print 'LOG:', fp.read()
- LOG: ... DISCARD: <six>
- <BLANKLINE>
-
-As does a message with a spam score of four pluses.
-
- >>> del msg['x-spam-score']
- >>> msg['X-Spam-Score'] = '+++'
- >>> del msg['message-id']
- >>> msg['Message-ID'] = '<seven>'
- >>> file_pos = fp.tell()
- >>> process(mlist, msg, {}, 'header-match')
- >>> fp.seek(file_pos)
- >>> print 'LOG:', fp.read()
- LOG: ... DISCARD: <seven>
- <BLANKLINE>
diff --git a/mailman/rules/docs/implicit-dest.txt b/mailman/rules/docs/implicit-dest.txt
deleted file mode 100644
index e5c340dcd..000000000
--- a/mailman/rules/docs/implicit-dest.txt
+++ /dev/null
@@ -1,75 +0,0 @@
-Implicit destination
-====================
-
-The 'implicit-dest' rule matches when the mailing list's posting address is
-not explicitly mentioned in the set of message recipients.
-
- >>> mlist = config.db.list_manager.create(u'_xtest@example.com')
- >>> rule = config.rules['implicit-dest']
- >>> print rule.name
- implicit-dest
-
-This rule matches messages that have implicit destination, meaning that the
-mailing list's posting address isn't included in the explicit recipients.
-
- >>> mlist.require_explicit_destination = True
- >>> mlist.acceptable_aliases = u''
- >>> msg = message_from_string("""\
- ... From: aperson@example.org
- ... Subject: An implicit message
- ...
- ... """)
- >>> rule.check(mlist, msg, {})
- True
-
-You can disable implicit destination checks for the mailing list.
-
- >>> mlist.require_explicit_destination = False
- >>> rule.check(mlist, msg, {})
- False
-
-Even with some recipients, if the posting address is not included, the rule
-will match.
-
- >>> mlist.require_explicit_destination = True
- >>> msg['To'] = 'myfriend@example.com'
- >>> rule.check(mlist, msg, {})
- True
-
-Add the posting address as a recipient and the rule will no longer match.
-
- >>> msg['Cc'] = '_xtest@example.com'
- >>> rule.check(mlist, msg, {})
- False
-
-Alternatively, if one of the acceptable aliases is in the recipients list,
-then the rule will not match.
-
- >>> del msg['cc']
- >>> rule.check(mlist, msg, {})
- True
- >>> mlist.acceptable_aliases = u'myfriend@example.com'
- >>> rule.check(mlist, msg, {})
- False
-
-A message gated from NNTP will obviously have an implicit destination. Such
-gated messages will not be held for implicit destination because it's assumed
-that Mailman pulled it from the appropriate news group.
-
- >>> rule.check(mlist, msg, dict(fromusenet=True))
- False
-
-
-Alias patterns
---------------
-
-It's also possible to specify an alias pattern, i.e. a regular expression to
-match against the recipients. For example, we can say that if there is a
-recipient in the example.net domain, then the rule does not match.
-
- >>> mlist.acceptable_aliases = u'^.*@example.net'
- >>> rule.check(mlist, msg, {})
- True
- >>> msg['To'] = 'you@example.net'
- >>> rule.check(mlist, msg, {})
- False
diff --git a/mailman/rules/docs/loop.txt b/mailman/rules/docs/loop.txt
deleted file mode 100644
index 61612cd75..000000000
--- a/mailman/rules/docs/loop.txt
+++ /dev/null
@@ -1,48 +0,0 @@
-Posting loops
-=============
-
-To avoid a posting loop, Mailman has a rule to check for the existence of an
-X-BeenThere header with the value of the list's posting address.
-
- >>> mlist = config.db.list_manager.create(u'_xtest@example.com')
- >>> rule = config.rules['loop']
- >>> print rule.name
- loop
-
-The header could be missing, in which case the rule does not match.
-
- >>> msg = message_from_string("""\
- ... From: aperson@example.com
- ...
- ... An important message.
- ... """)
- >>> rule.check(mlist, msg, {})
- False
-
-The header could be present, but not match the list's posting address.
-
- >>> msg['X-BeenThere'] = u'not-this-list@example.com'
- >>> rule.check(mlist, msg, {})
- False
-
-If the header is present and does match the posting address, the rule
-matches.
-
- >>> del msg['x-beenthere']
- >>> msg['X-BeenThere'] = mlist.posting_address
- >>> rule.check(mlist, msg, {})
- True
-
-Even if there are multiple X-BeenThere headers, as long as one with the
-posting address exists, the rule matches.
-
- >>> msg = message_from_string("""\
- ... From: aperson@example.com
- ... X-BeenThere: not-this-list@example.com
- ... X-BeenThere: _xtest@example.com
- ... X-BeenThere: foo@example.com
- ...
- ... An important message.
- ... """)
- >>> rule.check(mlist, msg, {})
- True
diff --git a/mailman/rules/docs/max-size.txt b/mailman/rules/docs/max-size.txt
deleted file mode 100644
index 117691e59..000000000
--- a/mailman/rules/docs/max-size.txt
+++ /dev/null
@@ -1,39 +0,0 @@
-Message size
-============
-
-The 'message-size' rule matches when the posted message is bigger than a
-specified maximum. Generally this is used to prevent huge attachments from
-getting posted to the list. This value is calculated in terms of KB (1024
-bytes).
-
- >>> mlist = config.db.list_manager.create(u'_xtest@example.com')
- >>> rule = config.rules['max-size']
- >>> print rule.name
- max-size
-
-For example, setting the maximum message size to 1 means that any message
-bigger than that will match the rule.
-
- >>> mlist.max_message_size = 1 # 1024 bytes
- >>> one_line = u'x' * 79
- >>> big_body = u'\n'.join([one_line] * 15)
- >>> msg = message_from_string("""\
- ... From: aperson@example.com
- ... To: _xtest@example.com
- ...
- ... """ + big_body)
- >>> rule.check(mlist, msg, {})
- True
-
-Setting the maximum message size to zero means no size check is performed.
-
- >>> mlist.max_message_size = 0
- >>> rule.check(mlist, msg, {})
- False
-
-Of course, if the maximum size is larger than the message's size, then it's
-still okay.
-
- >>> mlist.max_message_size = msg.original_size/1024.0 + 1
- >>> rule.check(mlist, msg, {})
- False
diff --git a/mailman/rules/docs/moderation.txt b/mailman/rules/docs/moderation.txt
deleted file mode 100644
index 65be0d7da..000000000
--- a/mailman/rules/docs/moderation.txt
+++ /dev/null
@@ -1,69 +0,0 @@
-Member moderation
-=================
-
-Each user has a moderation flag. When set, and the list is set to moderate
-postings, then only members with a cleared moderation flag will be able to
-email the list without having those messages be held for approval. The
-'moderation' rule determines whether the message should be moderated or not.
-
- >>> mlist = config.db.list_manager.create(u'_xtest@example.com')
- >>> rule = config.rules['moderation']
- >>> print rule.name
- moderation
-
-In the simplest case, the sender is not a member of the mailing list, so the
-moderation rule can't match.
-
- >>> msg = message_from_string("""\
- ... From: aperson@example.org
- ... To: _xtest@example.com
- ... Subject: A posted message
- ...
- ... """)
- >>> rule.check(mlist, msg, {})
- False
-
-Let's add the message author as a non-moderated member.
-
- >>> user = config.db.user_manager.create_user(
- ... u'aperson@example.org', u'Anne Person')
- >>> address = list(user.addresses)[0]
- >>> from mailman.interfaces.member import MemberRole
- >>> member = address.subscribe(mlist, MemberRole.member)
- >>> member.is_moderated
- False
- >>> rule.check(mlist, msg, {})
- False
-
-Once the member's moderation flag is set though, the rule matches.
-
- >>> member.is_moderated = True
- >>> rule.check(mlist, msg, {})
- True
-
-
-Non-members
------------
-
-There is another, related rule for matching non-members, which simply matches
-if the sender is /not/ a member of the mailing list.
-
- >>> rule = config.rules['non-member']
- >>> print rule.name
- non-member
-
-If the sender is a member of this mailing list, the rule does not match.
-
- >>> rule.check(mlist, msg, {})
- False
-
-But if the sender is not a member of this mailing list, the rule matches.
-
- >>> msg = message_from_string("""\
- ... From: bperson@example.org
- ... To: _xtest@example.com
- ... Subject: A posted message
- ...
- ... """)
- >>> rule.check(mlist, msg, {})
- True
diff --git a/mailman/rules/docs/news-moderation.txt b/mailman/rules/docs/news-moderation.txt
deleted file mode 100644
index 4c095cc81..000000000
--- a/mailman/rules/docs/news-moderation.txt
+++ /dev/null
@@ -1,36 +0,0 @@
-Newsgroup moderation
-====================
-
-The 'news-moderation' rule matches all messages posted to mailing lists that
-gateway to a moderated newsgroup. The reason for this is that such messages
-must get forwarded on to the newsgroup moderator. From there it will get
-posted to the newsgroup, and from there, gated to the mailing list. It's a
-circuitous route, but it works nonetheless by holding all messages posted
-directly to the mailing list.
-
- >>> mlist = config.db.list_manager.create(u'_xtest@example.com')
- >>> rule = config.rules['news-moderation']
- >>> print rule.name
- news-moderation
-
-Set the list configuraiton variable to enable newsgroup moderation.
-
- >>> from mailman.interfaces import NewsModeration
- >>> mlist.news_moderation = NewsModeration.moderated
-
-And now all messages will match the rule.
-
- >>> msg = message_from_string("""\
- ... From: aperson@example.org
- ... Subject: An announcment
- ...
- ... Great things are happening.
- ... """)
- >>> rule.check(mlist, msg, {})
- True
-
-When moderation is turned off, the rule does not match.
-
- >>> mlist.news_moderation = NewsModeration.none
- >>> rule.check(mlist, msg, {})
- False
diff --git a/mailman/rules/docs/no-subject.txt b/mailman/rules/docs/no-subject.txt
deleted file mode 100644
index 576111cd7..000000000
--- a/mailman/rules/docs/no-subject.txt
+++ /dev/null
@@ -1,33 +0,0 @@
-No Subject header
-=================
-
-This rule matches if the message has no Subject header, or if the header is
-the empty string when stripped.
-
- >>> mlist = config.db.list_manager.create(u'_xtest@example.com')
- >>> rule = config.rules['no-subject']
- >>> print rule.name
- no-subject
-
-A message with a non-empty subject does not match the rule.
-
- >>> msg = message_from_string("""\
- ... From: aperson@example.org
- ... To: _xtest@example.com
- ... Subject: A posted message
- ...
- ... """)
- >>> rule.check(mlist, msg, {})
- False
-
-Delete the Subject header and the rule matches.
-
- >>> del msg['subject']
- >>> rule.check(mlist, msg, {})
- True
-
-Even a Subject header with only whitespace still matches the rule.
-
- >>> msg['Subject'] = u' '
- >>> rule.check(mlist, msg, {})
- True
diff --git a/mailman/rules/docs/recipients.txt b/mailman/rules/docs/recipients.txt
deleted file mode 100644
index 3cd49d501..000000000
--- a/mailman/rules/docs/recipients.txt
+++ /dev/null
@@ -1,40 +0,0 @@
-Maximum number of recipients
-============================
-
-The 'max-recipients' rule matches when there are more than the maximum allowed
-number of explicit recipients addressed by the message.
-
- >>> mlist = config.db.list_manager.create(u'_xtest@example.com')
- >>> rule = config.rules['max-recipients']
- >>> print rule.name
- max-recipients
-
-In this case, we'll create a message with 5 recipients. These include all
-addresses in the To and CC headers.
-
- >>> msg = message_from_string("""\
- ... From: aperson@example.com
- ... To: _xtest@example.com, bperson@example.com
- ... Cc: cperson@example.com
- ... Cc: dperson@example.com (Dan Person)
- ... To: Elly Q. Person <eperson@example.com>
- ...
- ... Hey folks!
- ... """)
-
-For backward compatibility, the message must have fewer than the maximum
-number of explicit recipients.
-
- >>> mlist.max_num_recipients = 5
- >>> rule.check(mlist, msg, {})
- True
-
- >>> mlist.max_num_recipients = 6
- >>> rule.check(mlist, msg, {})
- False
-
-Zero means any number of recipients are allowed.
-
- >>> mlist.max_num_recipients = 0
- >>> rule.check(mlist, msg, {})
- False
diff --git a/mailman/rules/docs/rules.txt b/mailman/rules/docs/rules.txt
deleted file mode 100644
index 095d11466..000000000
--- a/mailman/rules/docs/rules.txt
+++ /dev/null
@@ -1,69 +0,0 @@
-Rules
-=====
-
-Rules are applied to each message as part of a rule chain. Individual rules
-simply return a boolean specifying whether the rule matches or not. Chain
-links determine what happens when a rule matches.
-
-
-All rules
----------
-
-Rules are maintained in the configuration object as a dictionary mapping rule
-names to rule objects.
-
- >>> from zope.interface.verify import verifyObject
- >>> from mailman.interfaces.rules import IRule
- >>> for rule_name in sorted(config.rules):
- ... rule = config.rules[rule_name]
- ... print rule_name, verifyObject(IRule, rule)
- administrivia True
- any True
- approved True
- emergency True
- implicit-dest True
- loop True
- max-recipients True
- max-size True
- moderation True
- news-moderation True
- no-subject True
- non-member True
- suspicious-header True
- truth True
-
-You can get a rule by name.
-
- >>> rule = config.rules['emergency']
- >>> verifyObject(IRule, rule)
- True
-
-
-Rule checks
------------
-
-Individual rules can be checked to see if they match, by running the rule's
-`check()` method. This returns a boolean indicating whether the rule was
-matched or not.
-
- >>> mlist = config.db.list_manager.create(u'_xtest@example.com')
- >>> msg = message_from_string("""\
- ... From: aperson@example.com
- ...
- ... An important message.
- ... """)
-
-For example, the emergency rule just checks to see if the emergency flag is
-set on the mailing list, and the message has not been pre-approved by the list
-administrator.
-
- >>> print rule.name
- emergency
- >>> mlist.emergency = False
- >>> rule.check(mlist, msg, {})
- False
- >>> mlist.emergency = True
- >>> rule.check(mlist, msg, {})
- True
- >>> rule.check(mlist, msg, dict(moderator_approved=True))
- False
diff --git a/mailman/rules/docs/suspicious.txt b/mailman/rules/docs/suspicious.txt
deleted file mode 100644
index 190a34aca..000000000
--- a/mailman/rules/docs/suspicious.txt
+++ /dev/null
@@ -1,35 +0,0 @@
-Suspicious headers
-==================
-
-Suspicious headers are a way for Mailman to hold messages that match a
-particular regular expression. This mostly historical feature is fairly
-confusing to users, and the list attribute that controls this is misnamed.
-
- >>> mlist = config.db.list_manager.create(u'_xtest@example.com')
- >>> rule = config.rules['suspicious-header']
- >>> print rule.name
- suspicious-header
-
-Set the so-called suspicious header configuration variable.
-
- >>> mlist.bounce_matching_headers = u'From: .*person@(blah.)?example.com'
- >>> msg = message_from_string("""\
- ... From: aperson@example.com
- ... To: _xtest@example.com
- ... Subject: An implicit message
- ...
- ... """)
- >>> rule.check(mlist, msg, {})
- True
-
-But if the header doesn't match the regular expression, the rule won't match.
-This one comes from a .org address.
-
- >>> msg = message_from_string("""\
- ... From: aperson@example.org
- ... To: _xtest@example.com
- ... Subject: An implicit message
- ...
- ... """)
- >>> rule.check(mlist, msg, {})
- False
diff --git a/mailman/rules/docs/truth.txt b/mailman/rules/docs/truth.txt
deleted file mode 100644
index f331e852b..000000000
--- a/mailman/rules/docs/truth.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-Truth
-=====
-
-The 'truth' rule always matches. This makes it useful as a terminus rule for
-unconditionally jumping to another chain.
-
- >>> rule = config.rules['truth']
- >>> rule.check(False, False, False)
- True
diff --git a/mailman/rules/emergency.py b/mailman/rules/emergency.py
deleted file mode 100644
index c2cee06c4..000000000
--- a/mailman/rules/emergency.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# Copyright (C) 2007-2009 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/>.
-
-"""The emergency hold rule."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'Emergency',
- ]
-
-
-from zope.interface import implements
-
-from mailman.i18n import _
-from mailman.interfaces.rules import IRule
-
-
-
-class Emergency:
- """The emergency hold rule."""
- implements(IRule)
-
- name = 'emergency'
-
- description = _(
- """The mailing list is in emergency hold and this message was not
- pre-approved by the list administrator.
- """)
-
- record = True
-
- def check(self, mlist, msg, msgdata):
- """See `IRule`."""
- return mlist.emergency and not msgdata.get('moderator_approved')
diff --git a/mailman/rules/implicit_dest.py b/mailman/rules/implicit_dest.py
deleted file mode 100644
index 3ddffa2cf..000000000
--- a/mailman/rules/implicit_dest.py
+++ /dev/null
@@ -1,99 +0,0 @@
-# Copyright (C) 2007-2009 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/>.
-
-"""The implicit destination rule."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'ImplicitDestination',
- ]
-
-
-import re
-from email.utils import getaddresses
-from zope.interface import implements
-
-from mailman.i18n import _
-from mailman.interfaces.rules import IRule
-
-
-
-class ImplicitDestination:
- """The implicit destination rule."""
- implements(IRule)
-
- name = 'implicit-dest'
- description = _('Catch messages with implicit destination.')
- record = True
-
- def check(self, mlist, msg, msgdata):
- """See `IRule`."""
- # Implicit destination checking must be enabled in the mailing list.
- if not mlist.require_explicit_destination:
- return False
- # Messages gated from NNTP will always have an implicit destination so
- # are never checked.
- if msgdata.get('fromusenet'):
- return False
- # Calculate the list of acceptable aliases. If the alias starts with
- # a caret (i.e. ^), then it's a regular expression to match against.
- aliases = set()
- alias_patterns = set()
- for alias in mlist.acceptable_aliases.splitlines():
- alias = alias.strip().lower()
- if alias.startswith('^'):
- alias_patterns.add(alias)
- elif '@' in alias:
- aliases.add(alias)
- else:
- # This is not a regular expression, nor a fully-qualified
- # email address, so skip it.
- pass
- # Add the list's posting address, i.e. the explicit address, to the
- # set of acceptable aliases.
- aliases.add(mlist.posting_address)
- # Look at all the recipients. If the recipient is any acceptable
- # alias (or the explicit posting address), then this rule does not
- # match. If not, then add it to the set of recipients we'll check
- # against the alias patterns later.
- recipients = set()
- for header in ('to', 'cc', 'resent-to', 'resent-cc'):
- for fullname, address in getaddresses(msg.get_all(header, [])):
- address = address.lower()
- if address in aliases:
- return False
- recipients.add(address)
- # Now for all alias patterns, see if any of the recipients matches a
- # pattern. If so, then this rule does not match.
- for pattern in alias_patterns:
- escaped = re.escape(pattern)
- for recipient in recipients:
- try:
- if re.match(pattern, recipient, re.IGNORECASE):
- return False
- except re.error:
- # The pattern is a malformed regular expression. Try
- # matching again with the pattern escaped.
- try:
- if re.match(escaped, recipient, re.IGNORECASE):
- return False
- except re.error:
- pass
- # Nothing matched.
- return True
diff --git a/mailman/rules/loop.py b/mailman/rules/loop.py
deleted file mode 100644
index 564d20dc6..000000000
--- a/mailman/rules/loop.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# Copyright (C) 2007-2009 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/>.
-
-"""Look for a posting loop."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'Loop',
- ]
-
-
-from zope.interface import implements
-
-from mailman.i18n import _
-from mailman.interfaces.rules import IRule
-
-
-
-class Loop:
- """Look for a posting loop."""
- implements(IRule)
-
- name = 'loop'
- description = _('Look for a posting loop, via the X-BeenThere header.')
- record = True
-
- def check(self, mlist, msg, msgdata):
- """See `IRule`."""
- # Has this message already been posted to this list?
- been_theres = [value.strip().lower()
- for value in msg.get_all('x-beenthere', [])]
- return mlist.posting_address in been_theres
diff --git a/mailman/rules/max_recipients.py b/mailman/rules/max_recipients.py
deleted file mode 100644
index a9cfd4a7f..000000000
--- a/mailman/rules/max_recipients.py
+++ /dev/null
@@ -1,52 +0,0 @@
-# Copyright (C) 2007-2009 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/>.
-
-"""The maximum number of recipients rule."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'MaximumRecipients',
- ]
-
-
-from email.utils import getaddresses
-from zope.interface import implements
-
-from mailman.i18n import _
-from mailman.interfaces.rules import IRule
-
-
-
-class MaximumRecipients:
- """The maximum number of recipients rule."""
- implements(IRule)
-
- name = 'max-recipients'
- description = _('Catch messages with too many explicit recipients.')
- record = True
-
- def check(self, mlist, msg, msgdata):
- """See `IRule`."""
- # Zero means any number of recipients are allowed.
- if mlist.max_num_recipients == 0:
- return False
- # Figure out how many recipients there are
- recipients = getaddresses(msg.get_all('to', []) +
- msg.get_all('cc', []))
- return len(recipients) >= mlist.max_num_recipients
diff --git a/mailman/rules/max_size.py b/mailman/rules/max_size.py
deleted file mode 100644
index bac79bbab..000000000
--- a/mailman/rules/max_size.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# Copyright (C) 2007-2009 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/>.
-
-"""The maximum message size rule."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'MaximumSize',
- ]
-
-
-from zope.interface import implements
-
-from mailman.i18n import _
-from mailman.interfaces.rules import IRule
-
-
-
-class MaximumSize:
- """The implicit destination rule."""
- implements(IRule)
-
- name = 'max-size'
- description = _('Catch messages that are bigger than a specified maximum.')
- record = True
-
- def check(self, mlist, msg, msgdata):
- """See `IRule`."""
- if mlist.max_message_size == 0:
- return False
- assert hasattr(msg, 'original_size'), (
- 'Message was not sized on initial parsing.')
- # The maximum size is specified in 1024 bytes.
- return msg.original_size / 1024.0 > mlist.max_message_size
diff --git a/mailman/rules/moderation.py b/mailman/rules/moderation.py
deleted file mode 100644
index 708983f6b..000000000
--- a/mailman/rules/moderation.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# Copyright (C) 2007-2009 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/>.
-
-"""Membership related rules."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'Moderation',
- 'NonMember',
- ]
-
-
-from zope.interface import implements
-
-from mailman.i18n import _
-from mailman.interfaces.rules import IRule
-
-
-
-class Moderation:
- """The member moderation rule."""
- implements(IRule)
-
- name = 'moderation'
- description = _('Match messages sent by moderated members.')
- record = True
-
- def check(self, mlist, msg, msgdata):
- """See `IRule`."""
- for sender in msg.get_senders():
- member = mlist.members.get_member(sender)
- if member is not None and member.is_moderated:
- return True
- return False
-
-
-
-class NonMember:
- """The non-membership rule."""
- implements(IRule)
-
- name = 'non-member'
- description = _('Match messages sent by non-members.')
- record = True
-
- def check(self, mlist, msg, msgdata):
- """See `IRule`."""
- for sender in msg.get_senders():
- if mlist.members.get_member(sender) is not None:
- # The sender is a member of the mailing list.
- return False
- return True
diff --git a/mailman/rules/news_moderation.py b/mailman/rules/news_moderation.py
deleted file mode 100644
index 3ead80086..000000000
--- a/mailman/rules/news_moderation.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# Copyright (C) 2007-2009 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/>.
-
-"""The news moderation rule."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'ModeratedNewsgroup',
- ]
-
-
-from zope.interface import implements
-
-from mailman.i18n import _
-from mailman.interfaces import NewsModeration
-from mailman.interfaces.rules import IRule
-
-
-
-class ModeratedNewsgroup:
- """The news moderation rule."""
- implements(IRule)
-
- name = 'news-moderation'
- description = _(
- """Match all messages posted to a mailing list that gateways to a
- moderated newsgroup.
- """)
- record = True
-
- def check(self, mlist, msg, msgdata):
- """See `IRule`."""
- return mlist.news_moderation == NewsModeration.moderated
diff --git a/mailman/rules/no_subject.py b/mailman/rules/no_subject.py
deleted file mode 100644
index 2487867e7..000000000
--- a/mailman/rules/no_subject.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# Copyright (C) 2007-2009 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/>.
-
-"""The no-Subject header rule."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'NoSubject',
- ]
-
-
-from zope.interface import implements
-
-from mailman.i18n import _
-from mailman.interfaces.rules import IRule
-
-
-
-class NoSubject:
- """The no-Subject rule."""
- implements(IRule)
-
- name = 'no-subject'
- description = _('Catch messages with no, or empty, Subject headers.')
- record = True
-
- def check(self, mlist, msg, msgdata):
- """See `IRule`."""
- subject = msg.get('subject', '').strip()
- return subject == ''
diff --git a/mailman/rules/suspicious.py b/mailman/rules/suspicious.py
deleted file mode 100644
index 00e9a5e9e..000000000
--- a/mailman/rules/suspicious.py
+++ /dev/null
@@ -1,100 +0,0 @@
-# Copyright (C) 2007-2009 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/>.
-
-"""The historical 'suspicious header' rule."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'SuspiciousHeader',
- ]
-
-
-import re
-import logging
-
-from zope.interface import implements
-
-from mailman.i18n import _
-from mailman.interfaces.rules import IRule
-
-log = logging.getLogger('mailman.error')
-
-
-
-class SuspiciousHeader:
- """The historical 'suspicious header' rule."""
- implements(IRule)
-
- name = 'suspicious-header'
- description = _('Catch messages with suspicious headers.')
- record = True
-
- def check(self, mlist, msg, msgdata):
- """See `IRule`."""
- return (mlist.bounce_matching_headers and
- has_matching_bounce_header(mlist, msg))
-
-
-
-def _parse_matching_header_opt(mlist):
- """Return a list of triples [(field name, regex, line), ...]."""
- # - Blank lines and lines with '#' as first char are skipped.
- # - Leading whitespace in the matchexp is trimmed - you can defeat
- # that by, eg, containing it in gratuitous square brackets.
- all = []
- for line in mlist.bounce_matching_headers.splitlines():
- line = line.strip()
- # Skip blank lines and lines *starting* with a '#'.
- if not line or line.startswith('#'):
- continue
- i = line.find(':')
- if i < 0:
- # This didn't look like a header line. BAW: should do a
- # better job of informing the list admin.
- log.error('bad bounce_matching_header line: %s\n%s',
- mlist.real_name, line)
- else:
- header = line[:i]
- value = line[i+1:].lstrip()
- try:
- cre = re.compile(value, re.IGNORECASE)
- except re.error as error:
- # The regexp was malformed. BAW: should do a better
- # job of informing the list admin.
- log.error("""\
-bad regexp in bounce_matching_header line: %s
-\n%s (cause: %s)""", mlist.real_name, value, error)
- else:
- all.append((header, cre, line))
- return all
-
-
-def has_matching_bounce_header(mlist, msg):
- """Does the message have a matching bounce header?
-
- :param mlist: The mailing list the message is destined for.
- :param msg: The email message object.
- :return: True if a header field matches a regexp in the
- bounce_matching_header mailing list variable.
- """
- for header, cre, line in _parse_matching_header_opt(mlist):
- for value in msg.get_all(header, []):
- if cre.search(value):
- return True
- return False
diff --git a/mailman/rules/truth.py b/mailman/rules/truth.py
deleted file mode 100644
index 45b5560c2..000000000
--- a/mailman/rules/truth.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# Copyright (C) 2008-2009 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/>.
-
-"""A rule which always matches."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'Truth',
- ]
-
-
-from zope.interface import implements
-
-from mailman.i18n import _
-from mailman.interfaces.rules import IRule
-
-
-
-class Truth:
- """Look for any previous rule match."""
- implements(IRule)
-
- name = 'truth'
- description = _('A rule which always matches.')
- record = False
-
- def check(self, mlist, msg, msgdata):
- """See `IRule`."""
- return True