diff options
| -rw-r--r-- | Mailman/Queue/BounceRunner.py | 209 |
1 files changed, 97 insertions, 112 deletions
diff --git a/Mailman/Queue/BounceRunner.py b/Mailman/Queue/BounceRunner.py index 5013b8e77..9317df4fe 100644 --- a/Mailman/Queue/BounceRunner.py +++ b/Mailman/Queue/BounceRunner.py @@ -31,50 +31,43 @@ from Mailman.Logging.Syslog import syslog class BounceRunner(Runner): QDIR = mm_cfg.BOUNCEQUEUE_DIR + # We only do bounce processing once per minute. + SLEEPTIME = 60 def _dispose(self, mlist, msg, msgdata): outq = get_switchboard(mm_cfg.OUTQUEUE_DIR) - # 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=mm_cfg.LIST_LOCK_TIMEOUT) - except LockFile.TimeOutError: - # Oh well, try again later - return 1 - try: - # There are a few possibilities here: - # - # - the message could have been VERP'd in which case, we know - # exactly who the message was destined for. That make our job - # easy. - # - the message could have been originally destined for a list - # owner, but a list owner address itself bounced. That's bad, - # and for now we'll simply log the problem and attempt to - # deliver the message to the site owner. - # - # All messages to list-owner@vdom.ain have their envelope sender - # set to site-owner@dom.ain (no virtual domain). Is this a bounce - # for a message to a list owner, coming to the site owner? - if msg.get('to', '') == Utils.get_site_email(extra='-owner'): - # Send it on to the site owners, but craft the envelope sender - # to be the -loop detection address, so if /they/ bounce, we - # won't get stuck in a bounce loop. - outq.enqueue(msg, msgdata, - recips=[Utils.get_site_email()], - envsender=Utils.get_site_email(extra='loop'), - ) - # List isn't doing bounce processing? - if not mlist.bounce_processing: - return - # If VERPing, the method will take care of things. - elif self.__verpbounce(mlist, msg): - mlist.Save() - return - # Otherwise do bounce detection of the original message - elif self.__scanbounce(mlist, msg): - mlist.Save() - return + # There are a few possibilities here: + # + # - the message could have been VERP'd in which case, we know exactly + # who the message was destined for. That make our job easy. + # - the message could have been originally destined for a list owner, + # but a list owner address itself bounced. That's bad, and for now + # we'll simply log the problem and attempt to deliver the message to + # the site owner. + # + # All messages to list-owner@vdom.ain have their envelope sender set + # to site-owner@dom.ain (no virtual domain). Is this a bounce for a + # message to a list owner, coming to the site owner? + if msg.get('to', '') == Utils.get_site_email(extra='-owner'): + # Send it on to the site owners, but craft the envelope sender to + # be the -loop detection address, so if /they/ bounce, we won't + # get stuck in a bounce loop. + outq.enqueue(msg, msgdata, + recips=[Utils.get_site_email()], + envsender=Utils.get_site_email(extra='loop'), + ) + # List isn't doing bounce processing? + if not mlist.bounce_processing: + return + # Try VERP detection first, since it's quick and easy + addrs = verp_bounce(mlist, msg) + if not addrs: + # That didn't give us anything useful, so try the old fashion + # bounce matching modules + addrs = BouncerAPI.ScanMessages(mlist, msg) + # If that still didn't return us any useful addresses, then send it on + # or discard it. + if not addrs: # Does the list owner want to get non-matching bounce messages? # If not, simply discard it. if mlist.bounce_unrecognized_goes_to_list_owner: @@ -91,79 +84,71 @@ class BounceRunner(Runner): else: syslog('bounce', 'discarding unrecognized, message-id: %s', msg.get('message-id', 'n/a')) - finally: - mlist.Unlock() - - def __verpbounce(self, mlist, msg): - bmailbox, bdomain = Utils.ParseEmail(mlist.getListAddress('bounces')) - # Sadly not every MTA bounces VERP messages correctly. Fall back to - # Delivered-to: and Apparently-To:, and then short-circuit if we still - # don't have anything to work with. Note that there can be multiple - # Delivered-To: headers so we need to search them all (and we don't - # worry about false positives for forwarded email, because only one - # should match VERP_REGEXP). - vals = [] - for header in ('to', 'delivered-to', 'apparently-to'): - vals.extend(msg.get_all(header, [])) - for field in vals: - to = parseaddr(field)[1] - if not to: - continue # empty header - mo = re.search(mm_cfg.VERP_REGEXP, to) - if not mo: - continue # no match of regexp - try: - if bmailbox <> mo.group('bounces'): - continue # not a bounce to our list - # All is good - 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 0 - # Now, if this message has come to the site list, then search not - # only it, but all the mailing lists on the system, registering a - # bounce with each for this address. - if mlist.internal_name() == mm_cfg.MAILMAN_SITE_LIST: - found = 0 - for listname in Utils.list_names(): - xlist = self._open_list(listname) - if xlist.isMember(addr): - unlockp = 0 - if not xlist.Locked(): - xlist.Lock() - unlockp = 1 - try: - xlist.registerBounce(addr, msg) - found = 1 - xlist.Save() - finally: - if unlockp: - xlist.Unlock() - return found - elif mlist.isMember(addr): - mlist.registerBounce(addr, msg) - return 1 - return 0 - - def __scanbounce(self, mlist, msg): + return + # Okay, we have some recognized addresses. We now need to register + # the bounces for each of these. If the bounce came to the site list, + # then we'll register the address on every list in the system, but + # note: this could be VERY resource intensive! if mlist.internal_name() == mm_cfg.MAILMAN_SITE_LIST: - found = 0 for listname in Utils.list_names(): xlist = self._open_list(listname) - unlockp = 0 - if not xlist.Locked(): - xlist.Lock() - unlockp = 1 - try: - status = BouncerAPI.ScanMessages(xlist, msg) - if status: + if xlist.isMember(addr): + unlockp = 0 + if not xlist.Locked(): + try: + xlist.Lock(timeout=mm_cfg.LIST_LOCK_TIMEOUT) + except LockFile.TimeOutError: + # Oh well, forget aboutf this list + continue + unlockp = 1 + try: + xlist.registerBounce(addr, msg) found = 1 - xlist.Save() - finally: - if unlockp: - xlist.Unlock() - return found + xlist.Save() + finally: + if unlockp: + xlist.Unlock() else: - return BouncerAPI.ScanMessages(mlist, msg) + try: + mlist.Lock(timeout=mm_cfg.LIST_LOCK_TIMEOUT) + except LockFile.TimeOutError: + # Oh well, forget about this bounce + return + try: + for addr in addrs: + mlist.registerBounce(addr, msg) + mlist.Save() + finally: + mlist.Unlock() + + + +def verp_bounce(mlist, msg): + bmailbox, bdomain = Utils.ParseEmail(mlist.GetBouncesEmail()) + # Sadly not every MTA bounces VERP messages correctly. Fall back to + # Delivered-to: and Apparently-To:, and then short-circuit if we still + # don't have anything to work with. Note that there can be multiple + # Delivered-To: headers so we need to search them all (and we don't + # worry about false positives for forwarded email, because only one + # should match VERP_REGEXP). + vals = [] + for header in ('to', 'delivered-to', 'apparently-to'): + vals.extend(msg.get_all(header, [])) + for field in vals: + to = parseaddr(field)[1] + if not to: + continue # empty header + mo = re.search(mm_cfg.VERP_REGEXP, to) + if not mo: + continue # no match of regexp + try: + if bmailbox <> mo.group('bounces'): + continue # not a bounce to our list + # All is good + 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] |
