summaryrefslogtreecommitdiff
path: root/Mailman/Queue/CommandRunner.py
diff options
context:
space:
mode:
authorBarry Warsaw2007-09-29 14:55:25 -0400
committerBarry Warsaw2007-09-29 14:55:25 -0400
commit3e9ed398b6a05c69daca14c8226ca7f57c164c21 (patch)
treeb0ae2e9771a80f9e0e6218871bbe2d281120202c /Mailman/Queue/CommandRunner.py
parentcbf2967239163e42cc2b25eece7bb5cb71b197fe (diff)
downloadmailman-3e9ed398b6a05c69daca14c8226ca7f57c164c21.tar.gz
mailman-3e9ed398b6a05c69daca14c8226ca7f57c164c21.tar.zst
mailman-3e9ed398b6a05c69daca14c8226ca7f57c164c21.zip
Diffstat (limited to 'Mailman/Queue/CommandRunner.py')
-rw-r--r--Mailman/Queue/CommandRunner.py242
1 files changed, 0 insertions, 242 deletions
diff --git a/Mailman/Queue/CommandRunner.py b/Mailman/Queue/CommandRunner.py
deleted file mode 100644
index 2df28e312..000000000
--- a/Mailman/Queue/CommandRunner.py
+++ /dev/null
@@ -1,242 +0,0 @@
-# Copyright (C) 1998-2007 by the Free Software Foundation, Inc.
-#
-# This program 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 2
-# of the License, or (at your option) any later version.
-#
-# This program 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 this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-
-"""-request robot command queue runner."""
-
-# 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 sys
-import logging
-
-from email.Errors import HeaderParseError
-from email.Header import decode_header, make_header, Header
-from email.Iterators import typed_subpart_iterator
-from email.MIMEMessage import MIMEMessage
-from email.MIMEText import MIMEText
-
-from Mailman import LockFile
-from Mailman import Message
-from Mailman import Utils
-from Mailman.Handlers import Replybot
-from Mailman.Queue.Runner import Runner
-from Mailman.app.replybot import autorespond_to_sender
-from Mailman.configuration import config
-from Mailman.i18n import _
-
-NL = '\n'
-
-log = logging.getLogger('mailman.vette')
-
-
-
-class Results:
- def __init__(self, mlist, msg, msgdata):
- self.mlist = mlist
- self.msg = msg
- self.msgdata = msgdata
- # Only set returnaddr if the response is to go to someone other than
- # the address specified in the From: header (e.g. for the password
- # command).
- self.returnaddr = None
- self.commands = []
- self.results = []
- self.ignored = []
- self.lineno = 0
- self.subjcmdretried = 0
- self.respond = True
- # Extract the subject header and do RFC 2047 decoding. Note that
- # Python 2.1's unicode() builtin doesn't call obj.__unicode__().
- subj = msg.get('subject', '')
- try:
- subj = make_header(decode_header(subj)).__unicode__()
- # TK: Currently we don't allow 8bit or multibyte in mail command.
- subj = subj.encode('us-ascii')
- # Always process the Subject: header first
- self.commands.append(subj)
- except (HeaderParseError, UnicodeError, LookupError):
- # We couldn't parse it so ignore the Subject header
- pass
- # Find the first text/plain part
- 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.
- self.results.append(_('Ignoring non-text/plain MIME parts'))
- if part is None:
- # E.g the outer Content-Type: was text/html
- return
- body = part.get_payload()
- # text/plain parts better have string payloads
- assert isinstance(body, basestring)
- lines = body.splitlines()
- # Use no more lines than specified
- self.commands.extend(lines[:config.DEFAULT_MAIL_COMMANDS_MAX_LINES])
- self.ignored.extend(lines[config.DEFAULT_MAIL_COMMANDS_MAX_LINES:])
-
- def process(self):
- # Now, process each line until we find an error. The first
- # non-command line found stops processing.
- stop = False
- for line in self.commands:
- if line and line.strip():
- args = line.split()
- cmd = args.pop(0).lower()
- stop = self.do_command(cmd, args)
- self.lineno += 1
- if stop:
- break
-
- def do_command(self, cmd, args=None):
- if args is None:
- args = ()
- # Try to import a command handler module for this command
- modname = 'Mailman.Commands.cmd_' + cmd
- try:
- __import__(modname)
- handler = sys.modules[modname]
- # ValueError can be raised if cmd has dots in it.
- except (ImportError, ValueError):
- # If we're on line zero, it was the Subject: header that didn't
- # contain a command. It's possible there's a Re: prefix (or
- # localized version thereof) on the Subject: line that's messing
- # things up. Pop the prefix off and try again... once.
- #
- # If that still didn't work it isn't enough to stop processing.
- # BAW: should we include a message that the Subject: was ignored?
- if not self.subjcmdretried and args:
- self.subjcmdretried += 1
- cmd = args.pop(0)
- return self.do_command(cmd, args)
- return self.lineno <> 0
- return handler.process(self, args)
-
- def send_response(self):
- # Helper
- def indent(lines):
- return [' ' + line for line in lines]
- # Quick exit for some commands which don't need a response
- if not self.respond:
- return
- resp = [Utils.wrap(_("""\
-The results of your email command are provided below.
-Attached is your original message.
-"""))]
- if self.results:
- resp.append(_('- Results:'))
- resp.extend(indent(self.results))
- # Ignore empty lines
- unprocessed = [line for line in self.commands[self.lineno:]
- if line and line.strip()]
- if unprocessed:
- resp.append(_('\n- Unprocessed:'))
- resp.extend(indent(unprocessed))
- if not unprocessed and not self.results:
- # The user sent an empty message; return a helpful one.
- resp.append(Utils.wrap(_("""\
-No commands were found in this message.
-To obtain instructions, send a message containing just the word "help".
-""")))
- if self.ignored:
- resp.append(_('\n- Ignored:'))
- resp.extend(indent(self.ignored))
- resp.append(_('\n- Done.\n\n'))
- # Encode any unicode strings into the list charset, so we don't try to
- # join unicode strings and invalid ASCII.
- charset = Utils.GetCharSet(self.msgdata['lang'])
- encoded_resp = []
- for item in resp:
- if isinstance(item, unicode):
- item = item.encode(charset, 'replace')
- encoded_resp.append(item)
- results = MIMEText(NL.join(encoded_resp), _charset=charset)
- # Safety valve for mail loops with misconfigured email 'bots. We
- # don't respond to commands sent with "Precedence: bulk|junk|list"
- # unless they explicitly "X-Ack: yes", but not all mail 'bots are
- # correctly configured, so we max out the number of responses we'll
- # give to an address in a single day.
- #
- # BAW: We wait until now to make this decision since our sender may
- # not be self.msg.get_sender(), but I'm not sure this is right.
- recip = self.returnaddr or self.msg.get_sender()
- if not autorespond_to_sender(self.mlist, recip, self.msgdata['lang']):
- return
- msg = Message.UserNotification(
- recip,
- self.mlist.GetBouncesEmail(),
- _('The results of your email commands'),
- lang=self.msgdata['lang'])
- msg.set_type('multipart/mixed')
- msg.attach(results)
- orig = MIMEMessage(self.msg)
- msg.attach(orig)
- msg.send(self.mlist)
-
-
-
-class CommandRunner(Runner):
- QDIR = config.CMDQUEUE_DIR
-
- def _dispose(self, mlist, msg, msgdata):
- # 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
- # it to prevent replybot response storms.
- precedence = msg.get('precedence', '').lower()
- ack = msg.get('x-ack', '').lower()
- if ack <> 'yes' and precedence in ('bulk', 'junk', 'list'):
- log.info('Precedence: %s message discarded by: %s',
- precedence, mlist.GetRequestEmail())
- return False
- # Do replybot for commands
- mlist.Load()
- Replybot.process(mlist, msg, msgdata)
- if mlist.autorespond_requests == 1:
- log.info('replied and discard')
- # w/discard
- return False
- # Now craft the response
- res = Results(mlist, msg, msgdata)
- # BAW: Not all the functions of this qrunner require the list to be
- # locked. Still, it's more convenient to lock it here and now and
- # deal with lock failures in one place.
- try:
- mlist.Lock(timeout=config.LIST_LOCK_TIMEOUT)
- except LockFile.TimeOutError:
- # Oh well, try again later
- return True
- # This message will have been delivered to one of mylist-request,
- # mylist-join, or mylist-leave, and the message metadata will contain
- # a key to which one was used.
- try:
- if msgdata.get('torequest'):
- res.process()
- elif msgdata.get('tojoin'):
- res.do_command('join')
- elif msgdata.get('toleave'):
- res.do_command('leave')
- elif msgdata.get('toconfirm'):
- mo = re.match(config.VERP_CONFIRM_REGEXP, msg.get('to', ''))
- if mo:
- res.do_command('confirm', (mo.group('cookie'),))
- res.send_response()
- mlist.Save()
- finally:
- mlist.Unlock()