diff options
Diffstat (limited to 'Mailman')
-rw-r--r-- | Mailman/Bouncer.py | 15 | ||||
-rw-r--r-- | Mailman/Bouncers/DSN.py | 9 | ||||
-rw-r--r-- | Mailman/Bouncers/GroupWise.py | 4 | ||||
-rw-r--r-- | Mailman/Bouncers/SimpleMatch.py | 24 | ||||
-rw-r--r-- | Mailman/Bouncers/SimpleWarning.py | 6 | ||||
-rw-r--r-- | Mailman/Handlers/CookHeaders.py | 5 | ||||
-rw-r--r-- | Mailman/OldStyleMemberships.py | 8 | ||||
-rw-r--r-- | Mailman/Queue/BounceRunner.py | 66 |
8 files changed, 96 insertions, 41 deletions
diff --git a/Mailman/Bouncer.py b/Mailman/Bouncer.py index ce647a1d..07e33e48 100644 --- a/Mailman/Bouncer.py +++ b/Mailman/Bouncer.py @@ -1,4 +1,4 @@ -# Copyright (C) 1998-2005 by the Free Software Foundation, Inc. +# Copyright (C) 1998-2008 by the Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -113,7 +113,7 @@ class Bouncer: # This is the first bounce we've seen from this member info = _BounceInfo(member, weight, day, self.bounce_you_are_disabled_warnings) - self.setBounceInfo(member, info) + # setBounceInfo is now called below after check phase. syslog('bounce', '%s: %s bounce score: %s', self.internal_name(), member, info.score) # Continue to the check phase below @@ -160,12 +160,20 @@ class Bouncer: info.reset(0, info.date, info.noticesleft) else: self.disableBouncingMember(member, info, msg) + # We've set/changed bounce info above. We now need to tell the + # MemberAdaptor to set/update it. We do it here in case the + # MemberAdaptor stores bounce info externally to the list object to + # be sure updated information is stored. + self.setBounceInfo(member, info) def disableBouncingMember(self, member, info, msg): # Initialize their confirmation cookie. If we do it when we get the # first bounce, it'll expire by the time we get the disabling bounce. cookie = self.pend_new(Pending.RE_ENABLE, self.internal_name(), member) info.cookie = cookie + # In case the MemberAdaptor stores bounce info externally to + # the list, we need to tell it to save the cookie + self.setBounceInfo(member, info) # Disable them if mm_cfg.VERP_PROBES: syslog('bounce', '%s: %s disabling due to probe bounce received', @@ -271,6 +279,9 @@ class Bouncer: msg.send(self) info.noticesleft -= 1 info.lastnotice = time.localtime()[:3] + # In case the MemberAdaptor stores bounce info externally to + # the list, we need to tell it to update + self.setBounceInfo(member, info) def BounceMessage(self, msg, msgdata, e=None): # Bounce a message back to the sender, with an error message if diff --git a/Mailman/Bouncers/DSN.py b/Mailman/Bouncers/DSN.py index e4d2f486..2eacd0f4 100644 --- a/Mailman/Bouncers/DSN.py +++ b/Mailman/Bouncers/DSN.py @@ -1,4 +1,4 @@ -# Copyright (C) 1998-2007 by the Free Software Foundation, Inc. +# Copyright (C) 1998-2008 by the Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -89,6 +89,13 @@ def check(msg): def process(msg): + # We've seen some fairly bogus DSNs, allegedly from postfix that are + # multipart/mixed with 3 subparts - a text/plain postfix like part, a + # message/delivery-status part and a message/rfc822 part with the original + # message. Deal with it as follows. + if (msg.is_multipart() and len(msg.get_payload()) == 3 and + msg.get_payload()[1].get_content_type() == 'message/delivery-status'): + return check(msg.get_payload()[1]) # A DSN has been seen wrapped with a "legal disclaimer" by an outgoing MTA # in a multipart/mixed outer part. if msg.is_multipart() and msg.get_content_subtype() == 'mixed': diff --git a/Mailman/Bouncers/GroupWise.py b/Mailman/Bouncers/GroupWise.py index 7ef31256..74116135 100644 --- a/Mailman/Bouncers/GroupWise.py +++ b/Mailman/Bouncers/GroupWise.py @@ -1,4 +1,4 @@ -# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc. +# Copyright (C) 1998-2008 by the Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -46,6 +46,8 @@ def find_textplain(msg): def process(msg): if msg.get_type() <> 'multipart/mixed' or not msg['x-mailer']: return None + if msg['x-mailer'][:3].lower() not in ('nov', 'ntm', 'int'): + return None addrs = {} # find the first text/plain part in the message textplain = find_textplain(msg) diff --git a/Mailman/Bouncers/SimpleMatch.py b/Mailman/Bouncers/SimpleMatch.py index 2e9c23b1..bd7124b6 100644 --- a/Mailman/Bouncers/SimpleMatch.py +++ b/Mailman/Bouncers/SimpleMatch.py @@ -1,4 +1,4 @@ -# Copyright (C) 1998-2007 by the Free Software Foundation, Inc. +# Copyright (C) 1998-2008 by the Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -88,24 +88,24 @@ PATTERNS = [ _c('--- Original message follows\.'), _c('<(?P<addr>[^>]*)>:')), # googlemail.com - (_c('Delivery to the following recipient failed'), + (_c('Delivery to the following recipient(s)? failed'), _c('----- Original message -----'), _c('^\s*(?P<addr>[^\s@]+@[^\s@]+)\s*$')), - # kundenserver.de - (_c('A message that you sent could not be delivered'), + # kundenserver.de, mxlogic.net + (_c('A message that you( have)? sent could not be delivered'), _c('^---'), _c('<(?P<addr>[^>]*)>')), # another kundenserver.de - (_c('A message that you sent could not be delivered'), + (_c('A message that you( have)? sent could not be delivered'), _c('^---'), _c('^(?P<addr>[^\s@]+@[^\s@:]+):')), - # thehartford.com - (_c('Delivery to the following recipients failed'), + # thehartford.com and amenworld.com + (_c('Del(i|e)very to the following recipient(s)? (failed|was aborted)'), # this one may or may not have the original message, but there's nothing # unique to stop on, so stop on the first line of at least 3 characters # that doesn't start with 'D' (to not stop immediately) and has no '@'. _c('^[^D][^@]{2,}$'), - _c('^\s*(?P<addr>[^\s@]+@[^\s@]+)\s*$')), + _c('^\s*(. )?(?P<addr>[^\s@]+@[^\s@]+)\s*$')), # and another thehartfod.com/hartfordlife.com (_c('^Your message\s*$'), _c('^because:'), @@ -126,9 +126,9 @@ PATTERNS = [ (_c('^Invalid final delivery userid:'), _c('^Original message follows.'), _c('\s*(?P<addr>[^\s@]+@[^\s@]+)\s*$')), - # E500_SMTP_Mail_Service@lerctr.org - (_c('------ Failed Recipients ------'), - _c('-------- Returned Mail --------'), + # E500_SMTP_Mail_Service@lerctr.org and similar + (_c('---- Failed Recipients ----'), + _c(' Mail ----'), _c('<(?P<addr>[^>]*)>')), # cynergycom.net (_c('A message that you sent could not be delivered'), @@ -196,7 +196,7 @@ def process(msg, patterns=None): if mo: addr = mo.group('addr') if addr: - addrs[mo.group('addr')] = 1 + addrs[addr.strip('<>')] = 1 elif ecre.search(line): break if addrs: diff --git a/Mailman/Bouncers/SimpleWarning.py b/Mailman/Bouncers/SimpleWarning.py index 45012c16..34690625 100644 --- a/Mailman/Bouncers/SimpleWarning.py +++ b/Mailman/Bouncers/SimpleWarning.py @@ -1,4 +1,4 @@ -# Copyright (C) 2001-2007 by the Free Software Foundation, Inc. +# Copyright (C) 2001-2008 by the Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -49,6 +49,10 @@ patterns = [ (_c('Delivery attempts will continue to be made'), _c('.+'), _c('(?P<addr>.+)')), + # Googlemail + (_c('THIS IS A WARNING MESSAGE ONLY'), + _c('Message will be retried'), + _c(r'\s*(?P<addr>\S+@\S+)\s*')), # Next one goes here... ] diff --git a/Mailman/Handlers/CookHeaders.py b/Mailman/Handlers/CookHeaders.py index 671922ab..3483225e 100644 --- a/Mailman/Handlers/CookHeaders.py +++ b/Mailman/Handlers/CookHeaders.py @@ -298,7 +298,10 @@ def prefix_subject(mlist, msg, msgdata): if old_style: h = u' '.join([recolon, prefix, subject]) else: - h = u' '.join([prefix, recolon, subject]) + if recolon: + h = u' '.join([prefix, recolon, subject]) + else: + h = u' '.join([prefix, subject]) h = h.encode('us-ascii') h = uheader(mlist, h, 'Subject', continuation_ws=ws) del msg['subject'] diff --git a/Mailman/OldStyleMemberships.py b/Mailman/OldStyleMemberships.py index f28c08db..d50bc62f 100644 --- a/Mailman/OldStyleMemberships.py +++ b/Mailman/OldStyleMemberships.py @@ -1,4 +1,4 @@ -# Copyright (C) 2001-2007 by the Free Software Foundation, Inc. +# Copyright (C) 2001-2008 by the Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -292,6 +292,12 @@ class OldStyleMemberships(MemberAdaptor.MemberAdaptor): raise Errors.NotAMemberError, member del self.__mlist.members[memberkey] self.__mlist.digest_members[memberkey] = cpuser + # If we recently turned off digest mode and are now + # turning it back on, the member may be in one_last_digest. + # If so, remove it so the member doesn't get a dup of the + # next digest. + if self.__mlist.one_last_digest.has_key(memberkey): + del self.__mlist.one_last_digest[memberkey] else: # Be sure the list supports regular delivery if not self.__mlist.nondigestable: diff --git a/Mailman/Queue/BounceRunner.py b/Mailman/Queue/BounceRunner.py index 11fe3fcb..6405c091 100644 --- a/Mailman/Queue/BounceRunner.py +++ b/Mailman/Queue/BounceRunner.py @@ -1,4 +1,4 @@ -# Copyright (C) 2001-2006 by the Free Software Foundation, Inc. +# Copyright (C) 2001-2008 by the Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -178,29 +178,45 @@ class BounceRunner(Runner, BounceMixin): # 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. - # - the list owner could have set listname-bounces as the owner - # address. That's really bad as it results in a loop of ever + # we'll simply attempt to deliver the message to the site list + # owner. + # Note that this means that automated bounce processing doesn't work + # for the site list. Because we can't reliably tell to what address + # a non-VERP'd bounce was originally sent, we have to treat all + # bounces sent to the site list as potential list owner bounces. + # - the list owner could have set list-bounces (or list-admin) as the + # owner address. That's really bad as it results in a loop of ever # growing unrecognized bounce messages. We detect this based on the - # X-BeenThere header and handle it like a list owner bounce. No - # real bounce will have an X-BeenThere header for the list. - bts = [s.strip().lower() for s in msg.get_all('x-beenthere', [])] - if mlist.GetListEmail().lower() in bts: - bt = True - else: - bt = False - # 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, or an owner - # notice sent directly to the -bounces address? - if msg.get('to', '') == Utils.get_site_email(extra='owner') or bt: + # fact that this message itself will be from the site bounces + # address. We then send this to the site list owner instead. + # Notices to list-owner have their envelope sender and From: set to + # the site-bounces address. Check if this is this a bounce for a + # message to a list owner, coming to site-bounces, or a looping + # message sent directly to the -bounces address. We have to do these + # cases separately, because sending to site-owner will reset the + # envelope sender. + # Is this a site list bounce? + if (mlist.internal_name().lower() == + mm_cfg.MAILMAN_SITE_LIST.lower()): # 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()], + recips=mlist.owner, + envsender=Utils.get_site_email(extra='loop'), + nodecorate=1, + ) + return + # Is this a possible looping message sent directly to a list-bounces + # address other than the site list? + # Use the From: because message may not have a unix_from. + if msg.get('from') == Utils.get_site_email(extra='bounces'): + # Just send it to the sitelist-owner address. If that bounces + # we'll handle it above. + outq.enqueue(msg, msgdata, + recips=[Utils.get_site_email(extra='owner')], envsender=Utils.get_site_email(extra='loop'), + nodecorate=1, ) return # List isn't doing bounce processing? @@ -227,8 +243,10 @@ class BounceRunner(Runner, BounceMixin): # If that still didn't return us any useful addresses, then send it on # or discard it. if not addrs: - syslog('bounce', 'bounce message w/no discernable addresses: %s', - msg.get('message-id')) + syslog('bounce', + '%s: bounce message w/no discernable addresses: %s', + mlist.internal_name(), + msg.get('message-id', 'n/a')) maybe_forward(mlist, msg) return # BAW: It's possible that there are None's in the list of addresses, @@ -330,8 +348,12 @@ For more information see: """), subject=_('Uncaught bounce notification'), tomoderators=0) - syslog('bounce', 'forwarding unrecognized, message-id: %s', + syslog('bounce', + '%s: forwarding unrecognized, message-id: %s', + mlist.internal_name(), msg.get('message-id', 'n/a')) else: - syslog('bounce', 'discarding unrecognized, message-id: %s', + syslog('bounce', + '%s: discarding unrecognized, message-id: %s', + mlist.internal_name(), msg.get('message-id', 'n/a')) |