"""Bounce queue runner."""
import re
import time
from email.MIMEText import MIMEText
from email.MIMEMessage import MIMEMessage
from email.Utils import parseaddr
from Mailman import mm_cfg
from Mailman import Utils
from Mailman import LockFile
from Mailman.Message import UserNotification
from Mailman.Bouncers import BouncerAPI
from Mailman.Queue.Runner import Runner
from Mailman.Queue.sbcache import get_switchboard
from Mailman.Logging.Syslog import syslog
from Mailman.i18n import _
COMMASPACE = ', '
REGISTER_BOUNCES_EVERY = mm_cfg.minutes(15)
class BounceRunner(Runner):
QDIR = mm_cfg.BOUNCEQUEUE_DIR
def __init__(self, slice=None, numslices=1):
Runner.__init__(self, slice, numslices)
self._bounces = {}
self._bouncecnt = 0
self._next_registration = time.time() + REGISTER_BOUNCES_EVERY
def _dispose(self, mlist, msg, msgdata):
mlist.Load()
outq = get_switchboard(mm_cfg.OUTQUEUE_DIR)
if msg.get('to', '') == Utils.get_site_email(extra='-owner'):
outq.enqueue(msg, msgdata,
recips=[Utils.get_site_email()],
envsender=Utils.get_site_email(extra='loop'),
)
if not mlist.bounce_processing:
return
addrs = verp_bounce(mlist, msg)
if not addrs:
addrs = BouncerAPI.ScanMessages(mlist, msg)
if not addrs:
syslog('bounce', 'bounce message w/no discernable addresses: %s',
msg.get('message-id'))
maybe_forward(mlist, msg)
return
addrs = filter(None, addrs)
today = time.localtime()[:3]
events = [(addr, today, msg) for addr in addrs]
self._bounces.setdefault(mlist.internal_name(), []).extend(events)
self._bouncecnt += len(addrs)
def _doperiodic(self):
now = time.time()
if self._next_registration > now or not self._bounces:
return
self._next_registration = now + REGISTER_BOUNCES_EVERY
self._register_bounces()
def _register_bounces(self):
syslog('bounce', 'Processing %s queued bounces', self._bouncecnt)
sitebounces = self._bounces.get(mm_cfg.MAILMAN_SITE_LIST, [])
if sitebounces:
listnames = Utils.list_names()
else:
listnames = self._bounces.keys()
for listname in listnames:
mlist = self._open_list(listname)
mlist.Lock()
try:
events = self._bounces.get(listname, []) + sitebounces
for addr, day, msg in events:
mlist.registerBounce(addr, msg, day=day)
mlist.Save()
finally:
mlist.Unlock()
self._bounces = {}
self._bouncecnt = 0
def _cleanup(self):
if self._bounces:
self._register_bounces()
Runner._cleanup(self)
def verp_bounce(mlist, msg):
bmailbox, bdomain = Utils.ParseEmail(mlist.GetBouncesEmail())
vals = []
for header in ('to', 'delivered-to', 'envelope-to', 'apparently-to'):
vals.extend(msg.get_all(header, []))
for field in vals:
to = parseaddr(field)[1]
if not to:
continue mo = re.search(mm_cfg.VERP_REGEXP, to)
if not mo:
continue try:
if bmailbox <> mo.group('bounces'):
continue addr = '%s@%s' % mo.group('mailbox', 'host')
except IndexError:
syslog('error',
"VERP_REGEXP doesn't yield the right match groups: %s",
mm_cfg.VERP_REGEXP)
return []
return [addr]
def maybe_forward(mlist, msg):
if mlist.bounce_unrecognized_goes_to_list_owner:
adminurl = mlist.GetScriptURL('admin', absolute=1) + '/bounce'
mlist.ForwardMessage(msg,
text=_("""\
The attached message was received as a bounce, but either the bounce format
was not recognized, or no member addresses could be extracted from it. This
mailing list has been configured to send all unrecognized bounce messages to
the list administrator(s).
For more information see:
%(adminurl)s
"""),
subject=_('Uncaught bounce notification'),
tomoderators=0)
syslog('bounce', 'forwarding unrecognized, message-id: %s',
msg.get('message-id', 'n/a'))
else:
syslog('bounce', 'discarding unrecognized, message-id: %s',
msg.get('message-id', 'n/a'))