aboutsummaryrefslogtreecommitdiffstats
path: root/Mailman
diff options
context:
space:
mode:
Diffstat (limited to 'Mailman')
-rw-r--r--Mailman/Bouncer.py15
-rw-r--r--Mailman/Bouncers/DSN.py9
-rw-r--r--Mailman/Bouncers/GroupWise.py4
-rw-r--r--Mailman/Bouncers/SimpleMatch.py24
-rw-r--r--Mailman/Bouncers/SimpleWarning.py6
-rw-r--r--Mailman/Handlers/CookHeaders.py5
-rw-r--r--Mailman/OldStyleMemberships.py8
-rw-r--r--Mailman/Queue/BounceRunner.py66
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'))