diff options
| author | Barry Warsaw | 2009-01-25 13:01:41 -0500 |
|---|---|---|
| committer | Barry Warsaw | 2009-01-25 13:01:41 -0500 |
| commit | eefd06f1b88b8ecbb23a9013cd223b72ca85c20d (patch) | |
| tree | 72c947fe16fce0e07e996ee74020b26585d7e846 /mailman/chains | |
| parent | 07871212f74498abd56bef3919bf3e029eb8b930 (diff) | |
| download | mailman-eefd06f1b88b8ecbb23a9013cd223b72ca85c20d.tar.gz mailman-eefd06f1b88b8ecbb23a9013cd223b72ca85c20d.tar.zst mailman-eefd06f1b88b8ecbb23a9013cd223b72ca85c20d.zip | |
Diffstat (limited to 'mailman/chains')
| -rw-r--r-- | mailman/chains/__init__.py | 0 | ||||
| -rw-r--r-- | mailman/chains/accept.py | 58 | ||||
| -rw-r--r-- | mailman/chains/base.py | 122 | ||||
| -rw-r--r-- | mailman/chains/builtin.py | 86 | ||||
| -rw-r--r-- | mailman/chains/discard.py | 47 | ||||
| -rw-r--r-- | mailman/chains/headers.py | 156 | ||||
| -rw-r--r-- | mailman/chains/hold.py | 178 | ||||
| -rw-r--r-- | mailman/chains/reject.py | 59 |
8 files changed, 0 insertions, 706 deletions
diff --git a/mailman/chains/__init__.py b/mailman/chains/__init__.py deleted file mode 100644 index e69de29bb..000000000 --- a/mailman/chains/__init__.py +++ /dev/null diff --git a/mailman/chains/accept.py b/mailman/chains/accept.py deleted file mode 100644 index bd47f42c8..000000000 --- a/mailman/chains/accept.py +++ /dev/null @@ -1,58 +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 terminal 'accept' chain.""" - -from __future__ import absolute_import, unicode_literals - -__metaclass__ = type -__all__ = [ - 'AcceptChain', - ] - -import logging - -from mailman.chains.base import TerminalChainBase -from mailman.config import config -from mailman.i18n import _ - - -log = logging.getLogger('mailman.vette') -SEMISPACE = '; ' - - - -class AcceptChain(TerminalChainBase): - """Accept the message for posting.""" - - name = 'accept' - description = _('Accept a message.') - - def _process(self, mlist, msg, msgdata): - """See `TerminalChainBase`.""" - # Start by decorating the message with a header that contains a list - # of all the rules that matched. These metadata could be None or an - # empty list. - rule_hits = msgdata.get('rule_hits') - if rule_hits: - msg['X-Mailman-Rule-Hits'] = SEMISPACE.join(rule_hits) - rule_misses = msgdata.get('rule_misses') - if rule_misses: - msg['X-Mailman-Rule-Misses'] = SEMISPACE.join(rule_misses) - accept_queue = config.switchboards['pipeline'] - accept_queue.enqueue(msg, msgdata) - log.info('ACCEPT: %s', msg.get('message-id', 'n/a')) diff --git a/mailman/chains/base.py b/mailman/chains/base.py deleted file mode 100644 index bcd946b40..000000000 --- a/mailman/chains/base.py +++ /dev/null @@ -1,122 +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/>. - -"""Base class for terminal chains.""" - -from __future__ import absolute_import, unicode_literals - -__metaclass__ = type -__all__ = [ - 'Chain', - 'Link', - 'TerminalChainBase', - ] - - -from zope.interface import implements - -from mailman.config import config -from mailman.interfaces.chain import ( - IChain, IChainIterator, IChainLink, IMutableChain, LinkAction) - - - -class Link: - """A chain link.""" - implements(IChainLink) - - def __init__(self, rule, action=None, chain=None, function=None): - self.rule = rule - self.action = (LinkAction.defer if action is None else action) - self.chain = chain - self.function = function - - - -class TerminalChainBase: - """A base chain that always matches and executes a method. - - The method is called 'process' and must be provided by the subclass. - """ - implements(IChain, IChainIterator) - - def _process(self, mlist, msg, msgdata): - """Process the message for the given mailing list. - - This must be overridden by subclasses. - """ - raise NotImplementedError - - def get_links(self, mlist, msg, msgdata): - """See `IChain`.""" - return iter(self) - - def __iter__(self): - """See `IChainIterator`.""" - truth = config.rules['truth'] - # First, yield a link that always runs the process method. - yield Link(truth, LinkAction.run, function=self._process) - # Now yield a rule that stops all processing. - yield Link(truth, LinkAction.stop) - - - -class Chain: - """Generic chain base class.""" - implements(IMutableChain) - - def __init__(self, name, description): - assert name not in config.chains, ( - 'Duplicate chain name: {0}'.format(name)) - self.name = name - self.description = description - self._links = [] - # Register the chain. - config.chains[name] = self - - def append_link(self, link): - """See `IMutableChain`.""" - self._links.append(link) - - def flush(self): - """See `IMutableChain`.""" - self._links = [] - - def get_links(self, mlist, msg, msgdata): - """See `IChain`.""" - return iter(ChainIterator(self)) - - def get_iterator(self): - """Return an iterator over the links.""" - # We do it this way in order to preserve a separation of interfaces, - # and allows .get_links() to be overridden. - for link in self._links: - yield link - - - -class ChainIterator: - """Generic chain iterator.""" - - implements(IChainIterator) - - def __init__(self, chain): - self._chain = chain - - def __iter__(self): - """See `IChainIterator`.""" - return self._chain.get_iterator() diff --git a/mailman/chains/builtin.py b/mailman/chains/builtin.py deleted file mode 100644 index 05912a2f2..000000000 --- a/mailman/chains/builtin.py +++ /dev/null @@ -1,86 +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 default built-in starting chain.""" - -from __future__ import absolute_import, unicode_literals - -__metaclass__ = type -__all__ = [ - 'BuiltInChain', - ] - - -import logging - -from zope.interface import implements - -from mailman.chains.base import Link -from mailman.config import config -from mailman.i18n import _ -from mailman.interfaces.chain import IChain, LinkAction - - -log = logging.getLogger('mailman.vette') - - - -class BuiltInChain: - """Default built-in chain.""" - - implements(IChain) - - name = 'built-in' - description = _('The built-in moderation chain.') - - _link_descriptions = ( - ('approved', LinkAction.jump, 'accept'), - ('emergency', LinkAction.jump, 'hold'), - ('loop', LinkAction.jump, 'discard'), - # Do all of the following before deciding whether to hold the message - # for moderation. - ('administrivia', LinkAction.defer, None), - ('implicit-dest', LinkAction.defer, None), - ('max-recipients', LinkAction.defer, None), - ('max-size', LinkAction.defer, None), - ('news-moderation', LinkAction.defer, None), - ('no-subject', LinkAction.defer, None), - ('suspicious-header', LinkAction.defer, None), - # Now if any of the above hit, jump to the hold chain. - ('any', LinkAction.jump, 'hold'), - # Take a detour through the self header matching chain, which we'll - # create later. - ('truth', LinkAction.detour, 'header-match'), - # Finally, the builtin chain selfs to acceptance. - ('truth', LinkAction.jump, 'accept'), - ) - - def __init__(self): - self._cached_links = None - - def get_links(self, mlist, msg, msgdata): - """See `IChain`.""" - if self._cached_links is None: - self._cached_links = links = [] - for rule_name, action, chain_name in self._link_descriptions: - # Get the named rule. - rule = config.rules[rule_name] - # Get the chain, if one is defined. - chain = (None if chain_name is None - else config.chains[chain_name]) - links.append(Link(rule, action, chain)) - return iter(self._cached_links) diff --git a/mailman/chains/discard.py b/mailman/chains/discard.py deleted file mode 100644 index 1899e0340..000000000 --- a/mailman/chains/discard.py +++ /dev/null @@ -1,47 +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 terminal 'discard' chain.""" - -from __future__ import absolute_import, unicode_literals - -__metaclass__ = type -__all__ = [ - 'DiscardChain', - ] - - -import logging - -from mailman.chains.base import TerminalChainBase -from mailman.i18n import _ - - -log = logging.getLogger('mailman.vette') - - - -class DiscardChain(TerminalChainBase): - """Discard a message.""" - - name = 'discard' - description = _('Discard a message and stop processing.') - - def _process(self, mlist, msg, msgdata): - """See `TerminalChainBase`.""" - log.info('DISCARD: %s', msg.get('message-id', 'n/a')) - # Nothing more needs to happen. diff --git a/mailman/chains/headers.py b/mailman/chains/headers.py deleted file mode 100644 index 2f85d78d0..000000000 --- a/mailman/chains/headers.py +++ /dev/null @@ -1,156 +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 header-matching chain.""" - -from __future__ import absolute_import, unicode_literals - -__metaclass__ = type -__all__ = [ - 'HeaderMatchChain', - ] - - -import re -import logging -import itertools - -from zope.interface import implements - -from mailman.chains.base import Chain, Link -from mailman.config import config -from mailman.i18n import _ -from mailman.interfaces.chain import IChainIterator, LinkAction -from mailman.interfaces.rules import IRule - - -log = logging.getLogger('mailman.vette') - - - -def make_link(entry): - """Create a Link object. - - :param entry: a 2- or 3-tuple describing a link. If a 2-tuple, it is a - header and a pattern, and a default chain of 'hold' will be used. If - a 3-tuple, the third item is the chain name to use. - :return: an ILink. - """ - if len(entry) == 2: - header, pattern = entry - chain_name = 'hold' - elif len(entry) == 3: - header, pattern, chain_name = entry - # We don't assert that the chain exists here because the jump - # chain may not yet have been created. - else: - raise AssertionError('Bad link description: {0}'.format(entry)) - rule = HeaderMatchRule(header, pattern) - chain = config.chains[chain_name] - return Link(rule, LinkAction.jump, chain) - - - -class HeaderMatchRule: - """Header matching rule used by header-match chain.""" - implements(IRule) - - # Sequential rule counter. - _count = 1 - - def __init__(self, header, pattern): - self._header = header - self._pattern = pattern - self.name = 'header-match-{0:02}'.format(HeaderMatchRule._count) - HeaderMatchRule._count += 1 - self.description = '{0}: {1}'.format(header, pattern) - # XXX I think we should do better here, somehow recording that a - # particular header matched a particular pattern, but that gets ugly - # with RFC 2822 headers. It also doesn't match well with the rule - # name concept. For now, we just record the rather useless numeric - # rule name. I suppose we could do the better hit recording in the - # check() method, and set self.record = False. - self.record = True - - def check(self, mlist, msg, msgdata): - """See `IRule`.""" - for value in msg.get_all(self._header, []): - if re.search(self._pattern, value, re.IGNORECASE): - return True - return False - - - -class HeaderMatchChain(Chain): - """Default header matching chain. - - This could be extended by header match rules in the database. - """ - - def __init__(self): - super(HeaderMatchChain, self).__init__( - 'header-match', _('The built-in header matching chain')) - # The header match rules are not global, so don't register them. - # These are the only rules that the header match chain can execute. - self._links = [] - # Initialize header check rules with those from the global - # HEADER_MATCHES variable. - for entry in config.header_matches: - self._links.append(make_link(entry)) - # Keep track of how many global header matching rules we've seen. - # This is so the flush() method will only delete those that were added - # via extend() or append_link(). - self._permanent_link_count = len(self._links) - - def extend(self, header, pattern, chain_name='hold'): - """Extend the existing header matches. - - :param header: The case-insensitive header field name. - :param pattern: The pattern to match the header's value again. The - match is not anchored and is done case-insensitively. - :param chain: Option chain to jump to if the pattern matches any of - the named header values. If not given, the 'hold' chain is used. - """ - self._links.append(make_link((header, pattern, chain_name))) - - def flush(self): - """See `IMutableChain`.""" - del self._links[self._permanent_link_count:] - - def get_links(self, mlist, msg, msgdata): - """See `IChain`.""" - list_iterator = HeaderMatchIterator(mlist) - return itertools.chain(iter(self._links), iter(list_iterator)) - - def __iter__(self): - for link in self._links: - yield link - - - -class HeaderMatchIterator: - """An iterator of both the global and list-specific chain links.""" - - implements(IChainIterator) - - def __init__(self, mlist): - self._mlist = mlist - - def __iter__(self): - """See `IChainIterator`.""" - for entry in self._mlist.header_matches: - yield make_link(entry) diff --git a/mailman/chains/hold.py b/mailman/chains/hold.py deleted file mode 100644 index 16238a541..000000000 --- a/mailman/chains/hold.py +++ /dev/null @@ -1,178 +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 terminal 'hold' chain.""" - -from __future__ import absolute_import, unicode_literals - -__metaclass__ = type -__all__ = [ - 'HoldChain', - ] - - -import logging - -from email.mime.message import MIMEMessage -from email.mime.text import MIMEText -from email.utils import formatdate, make_msgid -from zope.interface import implements - -from mailman import i18n -from mailman.Message import UserNotification -from mailman.Utils import maketext, oneline, wrap, GetCharSet -from mailman.app.moderator import hold_message -from mailman.app.replybot import autorespond_to_sender, can_acknowledge -from mailman.chains.base import TerminalChainBase -from mailman.config import config -from mailman.interfaces.pending import IPendable - - -log = logging.getLogger('mailman.vette') -SEMISPACE = '; ' -_ = i18n._ - - - -class HeldMessagePendable(dict): - implements(IPendable) - PEND_KEY = 'held message' - - - -class HoldChain(TerminalChainBase): - """Hold a message.""" - - name = 'hold' - description = _('Hold a message and stop processing.') - - def _process(self, mlist, msg, msgdata): - """See `TerminalChainBase`.""" - # Start by decorating the message with a header that contains a list - # of all the rules that matched. These metadata could be None or an - # empty list. - rule_hits = msgdata.get('rule_hits') - if rule_hits: - msg['X-Mailman-Rule-Hits'] = SEMISPACE.join(rule_hits) - rule_misses = msgdata.get('rule_misses') - if rule_misses: - msg['X-Mailman-Rule-Misses'] = SEMISPACE.join(rule_misses) - # Hold the message by adding it to the list's request database. - # XXX How to calculate the reason? - request_id = hold_message(mlist, msg, msgdata, None) - # Calculate a confirmation token to send to the author of the - # message. - pendable = HeldMessagePendable(type=HeldMessagePendable.PEND_KEY, - id=request_id) - token = config.db.pendings.add(pendable) - # Get the language to send the response in. If the sender is a - # member, then send it in the member's language, otherwise send it in - # the mailing list's preferred language. - sender = msg.get_sender() - member = mlist.members.get_member(sender) - language = (member.preferred_language - if member else mlist.preferred_language) - # A substitution dictionary for the email templates. - charset = GetCharSet(mlist.preferred_language) - original_subject = msg.get('subject') - if original_subject is None: - original_subject = _('(no subject)') - else: - original_subject = oneline(original_subject, charset) - substitutions = dict( - listname = mlist.fqdn_listname, - subject = original_subject, - sender = sender, - reason = 'XXX', #reason, - confirmurl = '{0}/{1}'.format(mlist.script_url('confirm'), token), - admindb_url = mlist.script_url('admindb'), - ) - # At this point the message is held, but now we have to craft at least - # two responses. The first will go to the original author of the - # message and it will contain the token allowing them to approve or - # discard the message. The second one will go to the moderators of - # the mailing list, if the list is so configured. - # - # Start by possibly sending a response to the message author. There - # are several reasons why we might not go through with this. If the - # message was gated from NNTP, the author may not even know about this - # list, so don't spam them. If the author specifically requested that - # acknowledgments not be sent, or if the message was bulk email, then - # we do not send the response. It's also possible that either the - # mailing list, or the author (if they are a member) have been - # configured to not send such responses. - if (not msgdata.get('fromusenet') and - can_acknowledge(msg) and - mlist.respond_to_post_requests and - autorespond_to_sender(mlist, sender, language)): - # We can respond to the sender with a message indicating their - # posting was held. - subject = _( - 'Your message to $mlist.fqdn_listname awaits moderator approval') - send_language = msgdata.get('lang', language) - text = maketext('postheld.txt', substitutions, - lang=send_language, mlist=mlist) - adminaddr = mlist.bounces_address - nmsg = UserNotification(sender, adminaddr, subject, text, - send_language) - nmsg.send(mlist) - # Now the message for the list moderators. This one should appear to - # come from <list>-owner since we really don't need to do bounce - # processing on it. - if mlist.admin_immed_notify: - # Now let's temporarily set the language context to that which the - # administrators are expecting. - with i18n.using_language(mlist.preferred_language): - language = mlist.preferred_language - charset = GetCharSet(language) - # We need to regenerate or re-translate a few values in the - # substitution dictionary. - #d['reason'] = _(reason) # XXX reason - substitutions['subject'] = original_subject - # craft the admin notification message and deliver it - subject = _( - '$mlist.fqdn_listname post from $sender requires approval') - nmsg = UserNotification(mlist.owner_address, - mlist.owner_address, - subject, lang=language) - nmsg.set_type('multipart/mixed') - text = MIMEText( - maketext('postauth.txt', substitutions, - raw=True, mlist=mlist), - _charset=charset) - dmsg = MIMEText(wrap(_("""\ -If you reply to this message, keeping the Subject: header intact, Mailman will -discard the held message. Do this if the message is spam. If you reply to -this message and include an Approved: header with the list password in it, the -message will be approved for posting to the list. The Approved: header can -also appear in the first line of the body of the reply.""")), - _charset=GetCharSet(language)) - dmsg['Subject'] = 'confirm ' + token - dmsg['Sender'] = mlist.request_address - dmsg['From'] = mlist.request_address - dmsg['Date'] = formatdate(localtime=True) - dmsg['Message-ID'] = make_msgid() - nmsg.attach(text) - nmsg.attach(MIMEMessage(msg)) - nmsg.attach(MIMEMessage(dmsg)) - nmsg.send(mlist, **dict(tomoderators=True)) - # Log the held message - # XXX reason - reason = 'n/a' - log.info('HOLD: %s post from %s held, message-id=%s: %s', - mlist.fqdn_listname, sender, - msg.get('message-id', 'n/a'), reason) diff --git a/mailman/chains/reject.py b/mailman/chains/reject.py deleted file mode 100644 index 3faf563da..000000000 --- a/mailman/chains/reject.py +++ /dev/null @@ -1,59 +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 terminal 'reject' chain.""" - -from __future__ import absolute_import, unicode_literals - -__metaclass__ = type -__all__ = [ - 'RejectChain', - ] - - -import logging - -from mailman.app.bounces import bounce_message -from mailman.chains.base import TerminalChainBase -from mailman.i18n import _ - - -log = logging.getLogger('mailman.vette') -SEMISPACE = '; ' - - - -class RejectChain(TerminalChainBase): - """Reject/bounce a message.""" - - name = 'reject' - description = _('Reject/bounce a message and stop processing.') - - def _process(self, mlist, msg, msgdata): - """See `TerminalChainBase`.""" - # Start by decorating the message with a header that contains a list - # of all the rules that matched. These metadata could be None or an - # empty list. - rule_hits = msgdata.get('rule_hits') - if rule_hits: - msg['X-Mailman-Rule-Hits'] = SEMISPACE.join(rule_hits) - rule_misses = msgdata.get('rule_misses') - if rule_misses: - msg['X-Mailman-Rule-Misses'] = SEMISPACE.join(rule_misses) - # XXX Exception/reason - bounce_message(mlist, msg) - log.info('REJECT: %s', msg.get('message-id', 'n/a')) |
