summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Mailman/Handlers/SMTPDirect.py108
1 files changed, 79 insertions, 29 deletions
diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py
index 7507338ab..400d22667 100644
--- a/Mailman/Handlers/SMTPDirect.py
+++ b/Mailman/Handlers/SMTPDirect.py
@@ -24,6 +24,7 @@ isn't locked while delivery occurs synchronously.
"""
import os
+import string
import time
import socket
@@ -39,35 +40,20 @@ def process(mlist, msg, msgdata):
if not recips:
# Nobody to deliver to!
return
- # I want to record how long the SMTP dialog takes because this will help
- # diagnose whether we need to rewrite this module to relinquish the list
- # lock or not.
- t0 = time.time()
+ #
+ # Split the recipient list into SMTP_MAX_RCPTS chunks. Most MTAs have a
+ # limit on the number of recipients they'll swallow in a single
+ # transaction.
+ if mm_cfg.SMTP_MAX_RCPTS <= 0:
+ chunks = [recips]
+ else:
+ chunks = chunkify(recips, mm_cfg.SMTP_MAX_RCPTS)
refused = {}
- try:
- conn = smtplib.SMTP(mm_cfg.SMTPHOST, mm_cfg.SMTPPORT)
- try:
- # make sure the connect happens, which won't be done by the
- # constructor if SMTPHOST is false
- envsender = mlist.GetAdminEmail()
- refused = conn.sendmail(envsender, recips, str(msg))
- finally:
- t1 = time.time()
- mlist.LogMsg('smtp',
- 'smtp for %d recips, completed in %.3f seconds' %
- (len(recips), (t1-t0)))
- conn.quit()
- except smtplib.SMTPRecipientsRefused, e:
- refused = e.recipients
- # MTA not responding, or other socket problems, or any other kind of
- # SMTPException. In that case, nothing got delivered
- except (socket.error, smtplib.SMTPException), e:
- mlist.LogMsg('smtp', 'All recipients refused: %s' % e)
- # no recipients ever received the message
- msgdata['recips'] = recips
- raise HandlerAPI.SomeRecipientsFailed
+ for chunk in chunks:
+ failures = deliver(mlist, msg, chunk)
+ refused.update(failures)
#
- # Go through all refused recipients and deal with them if possible
+ # Process any failed deliveries.
tempfailures = []
for recip, (code, smtpmsg) in refused.items():
# DRUMS is an internet draft, but it says:
@@ -85,10 +71,74 @@ def process(mlist, msg, msgdata):
# it happens around the whole message delivery sequence
mlist.RegisterBounce(recip, msg, saveifdirty=0)
else:
- # deal with persistent transient failures by queuing them up for
- # future delivery.
+ # Deal with persistent transient failures by queuing them up for
+ # future delivery. TBD: this could generate lots of log entries!
mlist.LogMsg('smtp-failure', '%d %s (%s)' % (code, recip, smtpmsg))
tempfailures.append(recip)
if tempfailures:
msgdata['recips'] = tempfailures
raise HandlerAPI.SomeRecipientsFailed
+
+
+
+def chunkify(recips, chunksize):
+ # First do a simple sort on top level domain. It probably doesn't buy us
+ # much to try to sort on MX record -- that's the MTA's job. We're just
+ # trying to avoid getting a max recips error.
+ buckets = {}
+ for r in recips:
+ bin = None
+ i = string.rfind(r, '.')
+ if i >= 0:
+ bin = r[i+1:]
+ bucket = buckets.get(bin, [])
+ bucket.append(r)
+ buckets[bin] = bucket
+ # Now start filling the chunks
+ chunks = []
+ currentchunk = []
+ chunklen = 0
+ for bin in buckets.values():
+ for r in bin:
+ currentchunk.append(r)
+ chunklen = chunklen + 1
+ if chunklen >= chunksize:
+ chunks.append(currentchunk)
+ currentchunk = []
+ chunklen = 0
+ if currentchunk:
+ chunks.append(currentchunk)
+ return chunks
+
+
+
+def deliver(mlist, msg, recips):
+ refused = {}
+ # Gather statistics on how long each SMTP dialog takes.
+ t0 = time.time()
+ try:
+ conn = smtplib.SMTP(mm_cfg.SMTPHOST, mm_cfg.SMTPPORT)
+ try:
+ # make sure the connect happens, which won't be done by the
+ # constructor if SMTPHOST is false
+ envsender = mlist.GetAdminEmail()
+ refused = conn.sendmail(envsender, recips, str(msg))
+ finally:
+ t1 = time.time()
+ mlist.LogMsg('smtp',
+ 'smtp for %d recips, completed in %.3f seconds' %
+ (len(recips), (t1-t0)))
+ conn.quit()
+ except smtplib.SMTPRecipientsRefused, e:
+ refused = e.recipients
+ # MTA not responding, or other socket problems, or any other kind of
+ # SMTPException. In that case, nothing got delivered
+ except (socket.error, smtplib.SMTPException), e:
+ mlist.LogMsg('smtp', 'All recipients refused: %s' % e)
+ # If the exception had an associated error code, use it, otherwise,
+ # fake it with a non-triggering exception code
+ errcode = getattr(e, 'smtp_code', -1)
+ errmsg = getattr(e, 'smtp_error', 'ignore')
+ for r in recips:
+ refused[r] = (errcode, errmsg)
+ return refused