summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--mailman/Message.py7
-rw-r--r--mailman/app/chains.py2
-rw-r--r--mailman/app/pipelines.py45
-rw-r--r--mailman/bin/dumpdb.py134
-rw-r--r--mailman/bin/inject.py7
-rw-r--r--mailman/inject.py50
-rw-r--r--mailman/queue/__init__.py9
-rw-r--r--mailman/queue/archive.py38
-rw-r--r--mailman/queue/outgoing.py9
-rw-r--r--mailman/queue/virgin.py20
10 files changed, 178 insertions, 143 deletions
diff --git a/mailman/Message.py b/mailman/Message.py
index 5d20fb897..e822dd0ff 100644
--- a/mailman/Message.py
+++ b/mailman/Message.py
@@ -248,11 +248,10 @@ class UserNotification(Message):
This is used for all internally crafted messages.
"""
# Since we're crafting the message from whole cloth, let's make sure
- # this message has a Message-ID. Yes, the MTA would give us one, but
- # this is useful for logging to logs/smtp.
+ # this message has a Message-ID.
if 'message-id' not in self:
self['Message-ID'] = email.utils.make_msgid()
- # Ditto for Date: which is required by RFC 2822
+ # Ditto for Date: as required by RFC 2822.
if 'date' not in self:
self['Date'] = email.utils.formatdate(localtime=True)
# UserNotifications are typically for admin messages, and for messages
@@ -271,6 +270,7 @@ class UserNotification(Message):
recips=self.recips,
nodecorate=True,
reduced_list_headers=True,
+ pipeline='virgin',
)
if mlist is not None:
enqueue_kws['listname'] = mlist.fqdn_listname
@@ -307,4 +307,5 @@ class OwnerNotification(UserNotification):
nodecorate=True,
reduced_list_headers=True,
envsender=self._sender,
+ pipeline='virgin',
**_kws)
diff --git a/mailman/app/chains.py b/mailman/app/chains.py
index 58f940a83..d2f9eab86 100644
--- a/mailman/app/chains.py
+++ b/mailman/app/chains.py
@@ -25,11 +25,11 @@ __metaclass__ = type
from mailman.chains.accept import AcceptChain
+from mailman.chains.builtin import BuiltInChain
from mailman.chains.discard import DiscardChain
from mailman.chains.headers import HeaderMatchChain
from mailman.chains.hold import HoldChain
from mailman.chains.reject import RejectChain
-from mailman.chains.builtin import BuiltInChain
from mailman.configuration import config
from mailman.interfaces import LinkAction
diff --git a/mailman/app/pipelines.py b/mailman/app/pipelines.py
index ab128a2b1..c2b0a6890 100644
--- a/mailman/app/pipelines.py
+++ b/mailman/app/pipelines.py
@@ -48,11 +48,27 @@ def process(mlist, msg, msgdata, pipeline_name='built-in'):
-class BuiltInPipeline:
- """The built-in pipeline."""
+class BasePipeline:
+ """Base pipeline implementation."""
implements(IPipeline)
+ _default_handlers = ()
+
+ def __init__(self):
+ self._handlers = []
+ for handler_name in self._default_handlers:
+ self._handlers.append(config.handlers[handler_name])
+
+ def __iter__(self):
+ """See `IPipeline`."""
+ for handler in self._handlers:
+ yield handler
+
+
+class BuiltInPipeline(BasePipeline):
+ """The built-in pipeline."""
+
name = 'built-in'
description = _('The built-in pipeline.')
@@ -73,15 +89,19 @@ class BuiltInPipeline:
'to-outgoing',
)
- def __init__(self):
- self._handlers = []
- for handler_name in self._default_handlers:
- self._handlers.append(config.handlers[handler_name])
- def __iter__(self):
- """See `IPipeline`."""
- for handler in self._handlers:
- yield handler
+class VirginPipeline(BasePipeline):
+ """The processing pipeline for virgin messages.
+
+ Virgin messages are those that are crafted internally by Mailman.
+ """
+ name = 'virgin'
+ description = _('The virgin queue pipeline.')
+
+ _default_handlers = (
+ 'cook-headers',
+ 'to-outgoing',
+ )
@@ -97,5 +117,6 @@ def initialize():
(handler.name, handler_finder))
config.handlers[handler.name] = handler
# Set up some pipelines.
- pipeline = BuiltInPipeline()
- config.pipelines[pipeline.name] = pipeline
+ for pipeline_class in (BuiltInPipeline, VirginPipeline):
+ pipeline = pipeline_class()
+ config.pipelines[pipeline.name] = pipeline
diff --git a/mailman/bin/dumpdb.py b/mailman/bin/dumpdb.py
index 5ee7ff9dd..508b9d571 100644
--- a/mailman/bin/dumpdb.py
+++ b/mailman/bin/dumpdb.py
@@ -1,5 +1,3 @@
-#! @PYTHON@
-#
# Copyright (C) 1998-2008 by the Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or
@@ -17,110 +15,76 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# USA.
-import sys
+from __future__ import with_statement
+
import pprint
import cPickle
-import marshal
-import optparse
-from mailman import Version
from mailman.configuration import config
from mailman.i18n import _
+from mailman.interact import interact
+from mailman.options import Options
COMMASPACE = ', '
+m = []
-def parseargs():
- parser = optparse.OptionParser(version=Version.MAILMAN_VERSION,
- usage=_("""\
-%%prog [options] filename
+class ScriptOptions(Options):
+ usage=_("""\
+%prog [options] filename
+
+Dump the contents of any Mailman queue file. The queue file is a data file
+with multiple Python pickles in it.""")
-Dump the contents of any Mailman `database' file.
+ def add_options(self):
+ super(ScriptOptions, self).add_options()
+ self.parser.add_option(
+ '-n', '--noprint',
+ dest='doprint', default=True, action='store_false',
+ help=_("""\
+Don't attempt to pretty print the object. This is useful if there is some
+problem with the object and you just want to get an unpickled representation.
+Useful with 'bin/dumpdb -i <file>'. In that case, the list of
+unpickled objects will be left in a variable called 'm'."""))
+ self.parser.add_option(
+ '-i', '--interact',
+ default=False, action='store_true',
+ help=_("""\
+Start an interactive Python session, with a variable called 'm' containing the
+list of unpickled objects."""))
-If the filename ends with `.db', then it is assumed that the file contains a
-Python marshal. If the file ends with `.pck' then it is assumed to contain a
-Python pickle. In either case, if you want to override the default assumption
--- or if the file ends in neither suffix -- use the -p or -m flags."""))
- parser.add_option('-m', '--marshal',
- default=False, action='store_true',
- help=_("""\
-Assume the file contains a Python marshal,
-overridding any automatic guessing."""))
- parser.add_option('-p', '--pickle',
- default=False, action='store_true',
- help=_("""\
-Assume the file contains a Python pickle,
-overridding any automatic guessing."""))
- parser.add_option('-n', '--noprint',
- default=False, action='store_true',
- help=_("""\
-Don't attempt to pretty print the object. This is useful if there's
-some problem with the object and you just want to get an unpickled
-representation. Useful with `python -i bin/dumpdb <file>'. In that
-case, the root of the tree will be left in a global called "msg"."""))
- parser.add_option('-C', '--config',
- help=_('Alternative configuration file to use'))
- opts, args = parser.parse_args()
- # Options.
- # None == guess, 0 == pickle, 1 == marshal
- opts.filetype = None
- if opts.pickle:
- opts.filetype = 0
- if opts.marshal:
- opts.filetype = 1
- opts.doprint = not opts.noprint
- if len(args) < 1:
- parser.error(_('No filename given.'))
- elif len(args) > 1:
- pargs = COMMASPACE.join(args)
- parser.error(_('Bad arguments: $pargs'))
- else:
- opts.filename = args[0]
- if opts.filetype is None:
- if opts.filename.endswith('.db'):
- opts.filetype = 1
- elif opts.filename.endswith('.pck'):
- opts.filetype = 0
+ def sanity_check(self):
+ if len(self.arguments) < 1:
+ self.parser.error(_('No filename given.'))
+ elif len(self.arguments) > 1:
+ self.parser.error(_('Unexpected arguments'))
else:
- parser.error(_('Please specify either -p or -m.'))
- return parser, opts, args
+ self.filename = self.arguments[0]
def main():
- parser, opts, args = parseargs()
- config.load(opts.config)
+ options = ScriptOptions()
+ options.initialize()
- # Handle dbs
pp = pprint.PrettyPrinter(indent=4)
- if opts.filetype == 1:
- load = marshal.load
- typename = 'marshal'
- else:
- load = cPickle.load
- typename = 'pickle'
- fp = open(opts.filename)
- m = []
- try:
- cnt = 1
- if opts.doprint:
- print _('[----- start $typename file -----]')
+ with open(options.filename) as fp:
while True:
try:
- obj = load(fp)
+ m.append(cPickle.load(fp))
except EOFError:
- if opts.doprint:
- print _('[----- end $typename file -----]')
break
- if opts.doprint:
- print _('<----- start object $cnt ----->')
- if isinstance(obj, str):
- print obj
- else:
- pp.pprint(obj)
- cnt += 1
- m.append(obj)
- finally:
- fp.close()
+ if options.options.doprint:
+ print _('[----- start pickle -----]')
+ for i, obj in enumerate(m):
+ count = i + 1
+ print _('<----- start object $count ----->')
+ if isinstance(obj, basestring):
+ print obj
+ else:
+ pp.pprint(obj)
+ print _('[----- end pickle -----]')
+ if options.options.interact:
+ interact()
diff --git a/mailman/bin/inject.py b/mailman/bin/inject.py
index 3e4012baf..8c4613756 100644
--- a/mailman/bin/inject.py
+++ b/mailman/bin/inject.py
@@ -20,11 +20,14 @@ from __future__ import with_statement
import os
import sys
+from email import message_from_string
+
from mailman import Utils
from mailman import Version
+from mailman.Message import Message
from mailman.configuration import config
from mailman.i18n import _
-from mailman.inject import inject
+from mailman.inject import inject_text
from mailman.options import SingleMailingListOptions
@@ -81,7 +84,7 @@ def main():
with open(options.filename) as fp:
message_text = fp.read()
- inject(fqdn_listname, message_text, qdir=qdir)
+ inject_text(mlist, message_text, qdir=qdir)
diff --git a/mailman/inject.py b/mailman/inject.py
index 5796eff5c..7e50513d8 100644
--- a/mailman/inject.py
+++ b/mailman/inject.py
@@ -15,20 +15,64 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# USA.
+__metaclass__ = type
+__all__ = [
+ 'inject_message',
+ 'inject_text',
+ ]
+
+
+from email import message_from_string
+from email.utils import formatdate, make_msgid
+
+from mailman.Message import Message
from mailman.configuration import config
from mailman.queue import Switchboard
-def inject(listname, msg, recips=None, qdir=None):
+def inject_message(mlist, msg, recips=None, qdir=None):
+ """Inject a message into a queue.
+
+ :param mlist: The mailing list this message is destined for.
+ :param msg: The Message object to inject.
+ :param recips: Optional set of recipients to put into the message's
+ metadata.
+ :param qdir: Optional queue directory to inject this message into. If not
+ given, the incoming queue is used.
+ """
if qdir is None:
qdir = config.INQUEUE_DIR
+ # Since we're crafting the message from whole cloth, let's make sure this
+ # message has a Message-ID.
+ if 'message-id' not in msg:
+ msg['Message-ID'] = make_msgid()
+ # Ditto for Date: as required by RFC 2822.
+ if 'date' not in msg:
+ msg['Date'] = formatdate(localtime=True)
queue = Switchboard(qdir)
kws = dict(
- listname=listname,
+ listname=mlist.fqdn_listname,
tolist=True,
- _plaintext=True,
+ original_size=getattr(msg, 'original_size', len(msg.as_string())),
)
if recips is not None:
kws['recips'] = recips
queue.enqueue(msg, **kws)
+
+
+
+def inject_text(mlist, text, recips=None, qdir=None):
+ """Inject a message into a queue.
+
+ :param mlist: The mailing list this message is destined for.
+ :param text: The text of the message to inject. This will be parsed into
+ a Message object.
+ :param recips: Optional set of recipients to put into the message's
+ metadata.
+ :param qdir: Optional queue directory to inject this message into. If not
+ given, the incoming queue is used.
+ """
+ message = message_from_string(text, Message)
+ message.original_size = len(text)
+ inject_message(mlist, message, recips, qdir)
diff --git a/mailman/queue/__init__.py b/mailman/queue/__init__.py
index 4175babaa..17386cda6 100644
--- a/mailman/queue/__init__.py
+++ b/mailman/queue/__init__.py
@@ -38,6 +38,7 @@ import sha
import time
import email
import errno
+import pickle
import cPickle
import logging
import marshal
@@ -95,12 +96,12 @@ class Switchboard:
listname = data.get('listname', '--nolist--')
# Get some data for the input to the sha hash.
now = time.time()
- if not data.get('_plaintext'):
- protocol = 1
- msgsave = cPickle.dumps(_msg, protocol)
- else:
+ if data.get('_plaintext'):
protocol = 0
msgsave = cPickle.dumps(str(_msg), protocol)
+ else:
+ protocol = pickle.HIGHEST_PROTOCOL
+ msgsave = cPickle.dumps(_msg, protocol)
# listname is unicode but the input to the hash function must be an
# 8-bit string (eventually, a bytes object).
hashfood = msgsave + listname.encode('utf-8') + repr(now)
diff --git a/mailman/queue/archive.py b/mailman/queue/archive.py
index 854e4340f..013f8c949 100644
--- a/mailman/queue/archive.py
+++ b/mailman/queue/archive.py
@@ -28,6 +28,7 @@ __all__ = [
import os
import time
+from datetime import datetime
from email.Utils import parsedate_tz, mktime_tz, formatdate
from locknix.lockfile import Lock
@@ -44,37 +45,38 @@ class ArchiveRunner(Runner):
# Support clobber_date, i.e. setting the date in the archive to the
# received date, not the (potentially bogus) Date: header of the
# original message.
- clobber = 0
- originaldate = msg.get('date')
- receivedtime = formatdate(msgdata['received_time'])
- if not originaldate:
- clobber = 1
+ clobber = False
+ original_date = msg.get('date')
+ received_time = formatdate(msgdata['received_time'])
+ if not original_date:
+ clobber = True
elif config.ARCHIVER_CLOBBER_DATE_POLICY == 1:
- clobber = 1
+ clobber = True
elif config.ARCHIVER_CLOBBER_DATE_POLICY == 2:
- # what's the timestamp on the original message?
- tup = parsedate_tz(originaldate)
- now = time.time()
+ # What's the timestamp on the original message?
+ timetup = parsedate_tz(original_date)
+ now = datetime.now()
try:
- if not tup:
- clobber = 1
- elif abs(now - mktime_tz(tup)) > \
- config.ARCHIVER_ALLOWABLE_SANE_DATE_SKEW:
- clobber = 1
+ if not timetup:
+ clobber = True
+ else:
+ utc_timestamp = datetime.fromtimestamp(mktime_tz(timetup))
+ clobber = (abs(now - utc_timestamp) >
+ config.ARCHIVER_ALLOWABLE_SANE_DATE_SKEW)
except (ValueError, OverflowError):
# The likely cause of this is that the year in the Date: field
# is horribly incorrect, e.g. (from SF bug # 571634):
# Date: Tue, 18 Jun 0102 05:12:09 +0500
# Obviously clobber such dates.
- clobber = 1
+ clobber = True
if clobber:
del msg['date']
del msg['x-original-date']
- msg['Date'] = receivedtime
+ msg['Date'] = received_time
if originaldate:
- msg['X-Original-Date'] = originaldate
+ msg['X-Original-Date'] = original_date
# Always put an indication of when we received the message.
- msg['X-List-Received-Date'] = receivedtime
+ msg['X-List-Received-Date'] = received_time
# While a list archiving lock is acquired, archive the message.
with Lock(os.path.join(mlist.data_path, 'archive.lck')):
for archive_factory in get_plugins('mailman.archiver'):
diff --git a/mailman/queue/outgoing.py b/mailman/queue/outgoing.py
index 4c067b8c0..399432e5d 100644
--- a/mailman/queue/outgoing.py
+++ b/mailman/queue/outgoing.py
@@ -23,7 +23,8 @@ import copy
import email
import socket
import logging
-import datetime
+
+from datetime import datetime
from mailman import Errors
from mailman import Message
@@ -56,8 +57,8 @@ class OutgoingRunner(Runner, BounceMixin):
def _dispose(self, mlist, msg, msgdata):
# See if we should retry delivery of this message again.
- deliver_after = msgdata.get('deliver_after', 0)
- if datetime.datetime.now() < deliver_after:
+ deliver_after = msgdata.get('deliver_after', datetime.fromtimestamp(0))
+ if datetime.now() < deliver_after:
return True
# Make sure we have the most up-to-date state
try:
@@ -102,7 +103,7 @@ class OutgoingRunner(Runner, BounceMixin):
# occasionally move them back here for another shot at
# delivery.
if e.tempfailures:
- now = datetime.datetime.now()
+ now = datetime.now()
recips = e.tempfailures
last_recip_count = msgdata.get('last_recip_count', 0)
deliver_until = msgdata.get('deliver_until', now)
diff --git a/mailman/queue/virgin.py b/mailman/queue/virgin.py
index 5534c95f0..0494700ce 100644
--- a/mailman/queue/virgin.py
+++ b/mailman/queue/virgin.py
@@ -23,22 +23,20 @@ to go through some minimal processing before they can be sent out to the
recipient.
"""
+from mailman.app.pipelines import process
from mailman.configuration import config
from mailman.queue import Runner
-from mailman.queue.incoming import IncomingRunner
-class VirginRunner(IncomingRunner):
+class VirginRunner(Runner):
QDIR = config.VIRGINQUEUE_DIR
def _dispose(self, mlist, msg, msgdata):
- # We need to fasttrack this message through any handlers that touch
- # it. E.g. especially CookHeaders.
- msgdata['_fasttrack'] = 1
- return IncomingRunner._dispose(self, mlist, msg, msgdata)
-
- def _get_pipeline(self, mlist, msg, msgdata):
- # It's okay to hardcode this, since it'll be the same for all
- # internally crafted messages.
- return ['CookHeaders', 'ToOutgoing']
+ # We need to fast track this message through any pipeline handlers
+ # that touch it, e.g. especially cook-headers.
+ msgdata['_fasttrack'] = True
+ # Use the 'virgin' pipeline.
+ process(mlist, msg, msgdata, 'virgin')
+ # Do not keep this message queued.
+ return False