diff options
Diffstat (limited to 'src/mailman/queue/command.py')
| -rw-r--r-- | src/mailman/queue/command.py | 219 |
1 files changed, 0 insertions, 219 deletions
diff --git a/src/mailman/queue/command.py b/src/mailman/queue/command.py deleted file mode 100644 index ec3ec0089..000000000 --- a/src/mailman/queue/command.py +++ /dev/null @@ -1,219 +0,0 @@ -# Copyright (C) 1998-2011 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/>. - -"""-request robot command queue runner.""" - -__metaclass__ = type -__all__ = [ - 'CommandRunner', - 'Results', - ] - -# See the delivery diagram in IncomingRunner.py. This module handles all -# email destined for mylist-request, -join, and -leave. It no longer handles -# bounce messages (i.e. -admin or -bounces), nor does it handle mail to -# -owner. - -import re -import logging - -from StringIO import StringIO -from email.errors import HeaderParseError -from email.header import decode_header, make_header -from email.iterators import typed_subpart_iterator -from zope.component import getUtility -from zope.interface import implements - -from mailman.config import config -from mailman.core.i18n import _ -from mailman.email.message import UserNotification -from mailman.interfaces.command import ContinueProcessing, IEmailResults -from mailman.interfaces.languages import ILanguageManager -from mailman.queue import Runner - - -NL = '\n' -log = logging.getLogger('mailman.vette') - - - -class CommandFinder: - """Generate commands from the content of a message.""" - - def __init__(self, msg, msgdata, results): - self.command_lines = [] - self.ignored_lines = [] - self.processed_lines = [] - # Depending on where the message was destined to, add some implicit - # commands. For example, if this was sent to the -join or -leave - # addresses, it's the same as if 'join' or 'leave' commands were sent - # to the -request address. - subaddress = msgdata.get('subaddress') - if subaddress == 'join': - self.command_lines.append('join') - elif subaddress == 'leave': - self.command_lines.append('leave') - elif subaddress == 'confirm': - mo = re.match(config.mta.verp_confirm_regexp, msg.get('to', '')) - if mo: - self.command_lines.append('confirm ' + mo.group('cookie')) - # Extract the subject header and do RFC 2047 decoding. - raw_subject = msg.get('subject', '') - try: - subject = unicode(make_header(decode_header(raw_subject))) - # Mail commands must be ASCII. - self.command_lines.append(subject.encode('us-ascii')) - except (HeaderParseError, UnicodeError, LookupError): - # The Subject header was unparseable or not ASCII, so just ignore - # it. - pass - # Find the first text/plain part of the message. - part = None - for part in typed_subpart_iterator(msg, 'text', 'plain'): - break - if part is None or part is not msg: - # Either there was no text/plain part or we ignored some - # non-text/plain parts. - print >> results, _('Ignoring non-text/plain MIME parts') - if part is None: - # There was no text/plain part to be found. - return - body = part.get_payload(decode=True) - # text/plain parts better have string payloads. - assert isinstance(body, basestring), 'Non-string decoded payload' - lines = body.splitlines() - # Use no more lines than specified - max_lines = int(config.mailman.email_commands_max_lines) - self.command_lines.extend(lines[:max_lines]) - self.ignored_lines.extend(lines[max_lines:]) - - def __iter__(self): - """Return each command line, split into commands and arguments. - - :return: 2-tuples where the first element is the command and the - second element is a tuple of the arguments. - """ - while self.command_lines: - line = self.command_lines.pop(0) - self.processed_lines.append(line) - parts = line.strip().split() - if len(parts) == 0: - continue - command = parts.pop(0) - yield command, tuple(parts) - - - -class Results: - """The email command results.""" - - implements(IEmailResults) - - def __init__(self): - self._output = StringIO() - print >> self._output, _("""\ -The results of your email command are provided below. -""") - - def write(self, text): - self._output.write(text) - - def __unicode__(self): - value = self._output.getvalue() - assert isinstance(value, unicode), 'Not a unicode: %r' % value - return value - - - -class CommandRunner(Runner): - """The email command runner.""" - - def _dispose(self, mlist, msg, msgdata): - message_id = msg.get('message-id', 'n/a') - # The policy here is similar to the Replybot policy. If a message has - # "Precedence: bulk|junk|list" and no "X-Ack: yes" header, we discard - # the command message. - precedence = msg.get('precedence', '').lower() - ack = msg.get('x-ack', '').lower() - if ack <> 'yes' and precedence in ('bulk', 'junk', 'list'): - log.info('%s Precedence: %s message discarded by: %s', - message_id, precedence, mlist.request_address) - return False - # Do replybot for commands. - replybot = config.handlers['replybot'] - replybot.process(mlist, msg, msgdata) - if mlist.autorespond_requests == 1: - # Respond and discard. - log.info('%s -request message replied and discard', message_id) - return False - # Now craft the response and process the command lines. - results = Results() - # Include just a few key pieces of information from the original: the - # sender, date, and message id. - print >> results, _('- Original message details:') - subject = msg.get('subject', 'n/a') - date = msg.get('date', 'n/a') - from_ = msg.get('from', 'n/a') - print >> results, _(' From: $from_') - print >> results, _(' Subject: $subject') - print >> results, _(' Date: $date') - print >> results, _(' Message-ID: $message_id') - print >> results, _('\n- Results:') - finder = CommandFinder(msg, msgdata, results) - for command_name, arguments in finder: - command = config.commands.get(command_name) - if command is None: - print >> results, _('No such command: $command_name') - else: - status = command.process( - mlist, msg, msgdata, arguments, results) - assert status in ContinueProcessing, ( - 'Invalid status: %s' % status) - if status == ContinueProcessing.no: - break - # All done. Strip blank lines and send the response. - lines = filter(None, (line.strip() for line in finder.command_lines)) - if len(lines) > 0: - print >> results, _('\n- Unprocessed:') - for line in lines: - print >> results, line - lines = filter(None, (line.strip() for line in finder.ignored_lines)) - if len(lines) > 0: - print >> results, _('\n- Ignored:') - for line in lines: - print >> results, line - print >> results, _('\n- Done.') - # Send a reply, but do not attach the original message. This is a - # compromise because the original message is often helpful in tracking - # down problems, but it's also a vector for backscatter spam. - language = getUtility(ILanguageManager)[msgdata['lang']] - reply = UserNotification(msg.sender, mlist.bounces_address, - _('The results of your email commands'), - lang=language) - # Find a charset for the response body. Try ascii first, then - # latin-1 and finally falling back to utf-8. - reply_body = unicode(results) - for charset in ('us-ascii', 'latin-1'): - try: - reply_body.encode(charset) - break - except UnicodeError: - pass - else: - charset = 'utf-8' - reply.set_payload(reply_body, charset=charset) - reply.send(mlist) |
