summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mailman/Defaults.py15
-rw-r--r--mailman/Message.py2
-rw-r--r--mailman/SafeDict.py31
-rw-r--r--mailman/Utils.py2
-rw-r--r--mailman/bin/withlist.py5
-rw-r--r--mailman/pipeline/smtp_direct.py62
-rw-r--r--mailman/queue/__init__.py38
-rw-r--r--mailman/tests/test_safedict.py50
8 files changed, 81 insertions, 124 deletions
diff --git a/mailman/Defaults.py b/mailman/Defaults.py
index 04152dd33..43bf7a3c3 100644
--- a/mailman/Defaults.py
+++ b/mailman/Defaults.py
@@ -551,19 +551,21 @@ LOG_CONFIG_FILE = None
# printing of this log message.
SMTP_LOG_EVERY_MESSAGE = (
'smtp',
- '%(msg_message-id)s smtp to %(listname)s for %(#recips)d recips, completed in %(time).3f seconds')
+ ('${message-id} smtp to $listname for ${#recips} recips, '
+ 'completed in $time seconds'))
# This will only be printed if there were no immediate smtp failures.
# Mutually exclusive with SMTP_LOG_REFUSED.
SMTP_LOG_SUCCESS = (
'post',
- 'post to %(listname)s from %(sender)s, size=%(size)d, message-id=%(msg_message-id)s, success')
+ '${message-id} post to $listname from $sender, size=$size, success')
# This will only be printed if there were any addresses which encountered an
# immediate smtp failure. Mutually exclusive with SMTP_LOG_SUCCESS.
SMTP_LOG_REFUSED = (
'post',
- 'post to %(listname)s from %(sender)s, size=%(size)d, message-id=%(msg_message-id)s, %(#refused)d failures')
+ ('${message-id} post to $listname from $sender, size=$size, '
+ '${#refused} failures'))
# This will be logged for each specific recipient failure. Additional %()s
# keys are:
@@ -573,7 +575,8 @@ SMTP_LOG_REFUSED = (
# failmsg -- the actual smtp message, if available
SMTP_LOG_EACH_FAILURE = (
'smtp-failure',
- 'delivery to %(recipient)s failed with code %(failcode)d: %(failmsg)s')
+ ('${message-id} delivery to $recipient failed with code $failcode: '
+ '$failmsg'))
# These variables control the format and frequency of VERP-like delivery for
# better bounce detection. VERP is Variable Envelope Return Path, defined
@@ -878,8 +881,8 @@ DEFAULT_MSG_HEADER = u''
DEFAULT_MSG_FOOTER = u"""\
_______________________________________________
$real_name mailing list
-$fqdn_realname
-${web_page_url}listinfo${cgiext}/${list_name}
+$fqdn_listname
+${web_page_url}listinfo${cgiext}/${fqdn_listname}
"""
# Scrub regular delivery
diff --git a/mailman/Message.py b/mailman/Message.py
index e822dd0ff..42c778894 100644
--- a/mailman/Message.py
+++ b/mailman/Message.py
@@ -270,7 +270,6 @@ 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,5 +306,4 @@ class OwnerNotification(UserNotification):
nodecorate=True,
reduced_list_headers=True,
envsender=self._sender,
- pipeline='virgin',
**_kws)
diff --git a/mailman/SafeDict.py b/mailman/SafeDict.py
index 1db43a760..1549777da 100644
--- a/mailman/SafeDict.py
+++ b/mailman/SafeDict.py
@@ -53,34 +53,3 @@ class SafeDict(dict):
if isinstance(v, str):
self.__setitem__(k, unicode(v, self.cset))
return template % self
-
-
-
-class MsgSafeDict(SafeDict):
- def __init__(self, msg, d=None):
- self.__msg = msg
- if d is None:
- d = {}
- super(MsgSafeDict, self).__init__(d)
-
- def __getitem__(self, key):
- if key.startswith('msg_'):
- return self.__msg.get(key[4:], 'n/a')
- elif key.startswith('allmsg_'):
- missing = []
- all = self.__msg.get_all(key[7:], missing)
- if all is missing:
- return 'n/a'
- return COMMASPACE.join(all)
- else:
- return super(MsgSafeDict, self).__getitem__(key)
-
- def copy(self):
- d = super(MsgSafeDict, self).copy()
- for k in self.__msg.keys():
- vals = self.__msg.get_all(k)
- if len(vals) == 1:
- d['msg_'+k.lower()] = vals[0]
- else:
- d['allmsg_'+k.lower()] = COMMASPACE.join(vals)
- return d
diff --git a/mailman/Utils.py b/mailman/Utils.py
index 621a307d4..273d6af06 100644
--- a/mailman/Utils.py
+++ b/mailman/Utils.py
@@ -858,5 +858,3 @@ def get_pattern(email, pattern_list):
matched = pattern
break
return matched
-
-
diff --git a/mailman/bin/withlist.py b/mailman/bin/withlist.py
index c6a352319..2b312a87c 100644
--- a/mailman/bin/withlist.py
+++ b/mailman/bin/withlist.py
@@ -210,5 +210,8 @@ def main():
"The variable 'm' is the $listname mailing list")
else:
banner = interact.DEFAULT_BANNER
- overrides = dict(m=LAST_MLIST, r=r)
+ overrides = dict(m=LAST_MLIST, r=r,
+ commit=config.db.commit,
+ abort=config.db.abort,
+ config=config)
interact.interact(upframe=False, banner=banner, overrides=overrides)
diff --git a/mailman/pipeline/smtp_direct.py b/mailman/pipeline/smtp_direct.py
index 74eaa5aad..e51a148c3 100644
--- a/mailman/pipeline/smtp_direct.py
+++ b/mailman/pipeline/smtp_direct.py
@@ -34,6 +34,7 @@ import copy
import time
import email
import socket
+import string
import logging
import smtplib
@@ -44,7 +45,6 @@ from zope.interface import implements
from mailman import Errors
from mailman import Utils
-from mailman.SafeDict import MsgSafeDict
from mailman.configuration import config
from mailman.i18n import _
from mailman.interfaces import IHandler, Personalization
@@ -101,6 +101,20 @@ class Connection:
+class MessageDict(dict):
+ def __init__(self, message, extras):
+ super(MessageDict, self).__init__()
+ for key, value in message.items():
+ self[key.lower()] = value
+ self.update(extras)
+
+
+class Template(string.Template):
+ # Allow dashes and # signs, in addition to the standard pattern.
+ idpattern = '[_a-z#-][_a-z0-9#-]*'
+
+
+
def process(mlist, msg, msgdata):
recips = msgdata.get('recips')
if not recips:
@@ -178,23 +192,27 @@ def process(mlist, msg, msgdata):
msgdata['recips'] = origrecips
# Log the successful post
t1 = time.time()
- d = MsgSafeDict(msg, {'time' : t1-t0,
- # BAW: Urg. This seems inefficient.
- 'size' : len(msg.as_string()),
- '#recips' : len(recips),
- '#refused': len(refused),
- 'listname': mlist.fqdn_listname,
- 'sender' : origsender,
- })
+ substitutions = MessageDict(msg, {
+ 'time' : t1-t0,
+ 'size' : msg.original_size,
+ '#recips' : len(recips),
+ '#refused' : len(refused),
+ 'listname' : mlist.fqdn_listname,
+ 'sender' : origsender,
+ })
+ if 'message-id' not in substitutions:
+ substitutions['message-id'] = 'n/a'
# We have to use the copy() method because extended call syntax requires a
# concrete dictionary object; it does not allow a generic mapping (XXX is
# this still true in Python 2.3?).
if config.SMTP_LOG_EVERY_MESSAGE:
- every_log.info('%s', config.SMTP_LOG_EVERY_MESSAGE[1] % d)
+ template = Template(config.SMTP_LOG_EVERY_MESSAGE[1])
+ every_log.info('%s', template.safe_substitute(substitutions))
if refused:
if config.SMTP_LOG_REFUSED:
- refused_log.info('%s', config.SMTP_LOG_REFUSED[1] % d)
+ template = Template(config.SMTP_LOG_REFUSED[1])
+ refused_log.info('%s', template.safe_substitute(substitutions))
elif msgdata.get('tolist'):
# Log the successful post, but only if it really was a post to the
@@ -203,7 +221,8 @@ def process(mlist, msg, msgdata):
# the other messages, but in that case, we should probably have a
# separate configuration variable to control that.
if config.SMTP_LOG_SUCCESS:
- success_log.info('%s', config.SMTP_LOG_SUCCESS[1] % d)
+ template = Template(config.SMTP_LOG_SUCCESS[1])
+ success_log.info('%s', template.safe_substitute(substitutions))
# Process any failed deliveries.
tempfailures = []
@@ -226,10 +245,13 @@ def process(mlist, msg, msgdata):
# future delivery. TBD: this could generate lots of log entries!
tempfailures.append(recip)
if config.SMTP_LOG_EACH_FAILURE:
- d.update({'recipient': recip,
- 'failcode' : code,
- 'failmsg' : smtpmsg})
- failure_log.info('%s', config.SMTP_LOG_EACH_FAILURE[1] % d)
+ substitutions.update({
+ 'recipient' : recip,
+ 'failcode' : code,
+ 'failmsg' : smtpmsg,
+ })
+ template = Template(config.SMTP_LOG_EACH_FAILURE[1])
+ failure_log.info('%s', template.safe_substitute(substitutions))
# Return the results
if tempfailures or permfailures:
raise Errors.SomeRecipientsFailed(tempfailures, permfailures)
@@ -371,11 +393,11 @@ def bulkdeliver(mlist, msg, msgdata, envsender, failures, conn):
# Send the message
refused = conn.sendmail(envsender, recips, msgtext)
except smtplib.SMTPRecipientsRefused, e:
- flog.error('All recipients refused: %s, msgid: %s', e, msgid)
+ flog.error('%s recipients refused: %s', msgid, e)
refused = e.recipients
except smtplib.SMTPResponseException, e:
- flog.error('SMTP session failure: %s, %s, msgid: %s',
- e.smtp_code, e.smtp_error, msgid)
+ flog.error('%s SMTP session failure: %s, %s',
+ msgid, e.smtp_code, e.smtp_error)
# If this was a permanent failure, don't add the recipients to the
# refused, because we don't want them to be added to failures.
# Otherwise, if the MTA rejects the message because of the message
@@ -390,7 +412,7 @@ def bulkdeliver(mlist, msg, msgdata, envsender, failures, conn):
# MTA not responding, or other socket problems, or any other kind of
# SMTPException. In that case, nothing got delivered, so treat this
# as a temporary failure.
- flog.error('Low level smtp error: %s, msgid: %s', e, msgid)
+ flog.error('%s low level smtp error: %s', msgid, e)
error = str(e)
for r in recips:
refused[r] = (-1, error)
diff --git a/mailman/queue/__init__.py b/mailman/queue/__init__.py
index 17386cda6..5748b38b2 100644
--- a/mailman/queue/__init__.py
+++ b/mailman/queue/__init__.py
@@ -60,7 +60,8 @@ shamax = 0xffffffffffffffffffffffffffffffffffffffffL
# prevents skipping one of two entries with the same time until the next pass.
DELTA = .0001
-log = logging.getLogger('mailman.error')
+elog = logging.getLogger('mailman.error')
+dlog = logging.getLogger('mailman.debug')
@@ -168,7 +169,8 @@ class Switchboard:
else:
os.unlink(bakfile)
except EnvironmentError, e:
- log.exception('Failed to unlink/preserve backup file: %s', bakfile)
+ elog.exception(
+ 'Failed to unlink/preserve backup file: %s', bakfile)
@property
def files(self):
@@ -257,12 +259,15 @@ class Runner:
self._cleanup()
def _oneloop(self):
+ me = self.__class__.__name__
+ dlog.debug('[%s] starting oneloop', me)
# First, list all the files in our queue directory.
# Switchboard.files() is guaranteed to hand us the files in FIFO
# order. Return an integer count of the number of files that were
# available for this qrunner to process.
files = self._switchboard.files
for filebase in files:
+ dlog.debug('[%s] processing filebase: %s', me, filebase)
try:
# Ask the switchboard for the message and metadata objects
# associated with this filebase.
@@ -274,13 +279,15 @@ class Runner:
# We don't want the runner to die, so we just log and skip
# this entry, but preserve it for analysis.
self._log(e)
- log.error('Skipping and preserving unparseable message: %s',
- filebase)
+ elog.error('Skipping and preserving unparseable message: %s',
+ filebase)
self._switchboard.finish(filebase, preserve=True)
config.db.abort()
continue
try:
+ dlog.debug('[%s] processing onefile', me)
self._onefile(msg, msgdata)
+ dlog.debug('[%s] finishing filebase: %s', me, filebase)
self._switchboard.finish(filebase)
except Exception, e:
# All runners that implement _dispose() must guarantee that
@@ -297,23 +304,30 @@ class Runner:
# message. Try to be graceful.
try:
new_filebase = self._shunt.enqueue(msg, msgdata)
- log.error('SHUNTING: %s', new_filebase)
+ elog.error('SHUNTING: %s', new_filebase)
self._switchboard.finish(filebase)
except Exception, e:
# The message wasn't successfully shunted. Log the
# exception and try to preserve the original queue entry
# for possible analysis.
self._log(e)
- log.error('SHUNTING FAILED, preserving original entry: %s',
- filebase)
+ elog.error(
+ 'SHUNTING FAILED, preserving original entry: %s',
+ filebase)
self._switchboard.finish(filebase, preserve=True)
- config.db.abort()
+ config.db.abort()
# Other work we want to do each time through the loop.
+ dlog.debug('[%s] reaping', me)
Utils.reap(self._kids, once=True)
+ dlog.debug('[%s] doing periodic', me)
self._doperiodic()
+ dlog.debug('[%s] checking short circuit', me)
if self._shortcircuit():
+ dlog.debug('[%s] short circuiting', me)
break
+ dlog.debug('[%s] commiting', me)
config.db.commit()
+ dlog.debug('[%s] ending oneloop: %s', me, len(files))
return len(files)
def _onefile(self, msg, msgdata):
@@ -327,8 +341,8 @@ class Runner:
listname = msgdata.get('listname')
mlist = config.db.list_manager.get(listname)
if not mlist:
- log.error('Dequeuing message destined for missing list: %s',
- listname)
+ elog.error('Dequeuing message destined for missing list: %s',
+ listname)
self._shunt.enqueue(msg, msgdata)
return
# Now process this message, keeping track of any subprocesses that may
@@ -357,10 +371,10 @@ class Runner:
self._switchboard.enqueue(msg, msgdata)
def _log(self, exc):
- log.error('Uncaught runner exception: %s', exc)
+ elog.error('Uncaught runner exception: %s', exc)
s = StringIO()
traceback.print_exc(file=s)
- log.error('%s', s.getvalue())
+ elog.error('%s', s.getvalue())
#
# Subclasses can override these methods.
diff --git a/mailman/tests/test_safedict.py b/mailman/tests/test_safedict.py
index c1eeb15f2..544824870 100644
--- a/mailman/tests/test_safedict.py
+++ b/mailman/tests/test_safedict.py
@@ -42,57 +42,7 @@ class TestSafeDict(unittest.TestCase):
-class TestMsgSafeDict(unittest.TestCase):
- def setUp(self):
- self._msg = email.message_from_string("""To: foo
-From: bar
-Subject: baz
-Cc: aperson@dom.ain
-Cc: bperson@dom.ain
-
-""")
-
- def test_normal_key(self):
- sd = SafeDict.MsgSafeDict(self._msg, {'key': 'value'})
- si = '%(key)s' % sd
- self.assertEqual(si, 'value')
-
- def test_msg_key(self):
- sd = SafeDict.MsgSafeDict(self._msg, {'to': 'value'})
- si = '%(msg_to)s' % sd
- self.assertEqual(si, 'foo')
-
- def test_allmsg_key(self):
- sd = SafeDict.MsgSafeDict(self._msg, {'cc': 'value'})
- si = '%(allmsg_cc)s' % sd
- self.assertEqual(si, 'aperson@dom.ain, bperson@dom.ain')
-
- def test_msg_no_key(self):
- sd = SafeDict.MsgSafeDict(self._msg)
- si = '%(msg_date)s' % sd
- self.assertEqual(si, 'n/a')
-
- def test_allmsg_no_key(self):
- sd = SafeDict.MsgSafeDict(self._msg)
- si = '%(allmsg_date)s' % sd
- self.assertEqual(si, 'n/a')
-
- def test_copy(self):
- sd = SafeDict.MsgSafeDict(self._msg, {'foo': 'bar'})
- copy = sd.copy()
- items = copy.items()
- items.sort()
- self.assertEqual(items, [
- ('allmsg_cc', 'aperson@dom.ain, bperson@dom.ain'),
- ('foo', 'bar'),
- ('msg_from', 'bar'),
- ('msg_subject', 'baz'),
- ('msg_to', 'foo'),
- ])
-
-
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestSafeDict))
- suite.addTest(unittest.makeSuite(TestMsgSafeDict))
return suite