diff options
Diffstat (limited to 'mailman/rules')
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| )*' + 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 |
