summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mailman/app/bounces.py47
-rw-r--r--src/mailman/app/membership.py3
-rw-r--r--src/mailman/app/notifications.py2
-rw-r--r--src/mailman/app/tests/test_bounces.py139
-rw-r--r--src/mailman/database/mailman.sql2
-rw-r--r--src/mailman/email/message.py33
-rw-r--r--src/mailman/interfaces/bounce.py12
-rw-r--r--src/mailman/interfaces/mailinglist.py10
-rw-r--r--src/mailman/model/docs/registration.txt2
-rw-r--r--src/mailman/model/docs/requests.txt20
-rw-r--r--src/mailman/model/mailinglist.py3
-rw-r--r--src/mailman/pipeline/docs/acknowledge.txt32
-rw-r--r--src/mailman/pipeline/docs/replybot.txt4
-rw-r--r--src/mailman/queue/bounce.py26
-rw-r--r--src/mailman/queue/docs/command.txt2
-rw-r--r--src/mailman/queue/outgoing.py2
-rw-r--r--src/mailman/queue/tests/test_outgoing.py14
-rw-r--r--src/mailman/styles/default.py5
-rw-r--r--src/mailman/templates/en/unrecognized.txt7
-rw-r--r--src/mailman/testing/helpers.py17
20 files changed, 291 insertions, 91 deletions
diff --git a/src/mailman/app/bounces.py b/src/mailman/app/bounces.py
index f216c959e..46c4515ca 100644
--- a/src/mailman/app/bounces.py
+++ b/src/mailman/app/bounces.py
@@ -24,6 +24,7 @@ __all__ = [
'ProbeVERP',
'StandardVERP',
'bounce_message',
+ 'maybe_forward',
'scan_message',
'send_probe',
]
@@ -42,8 +43,9 @@ from zope.interface import implements
from mailman.app.finder import find_components
from mailman.config import config
from mailman.core.i18n import _
-from mailman.email.message import UserNotification
-from mailman.interfaces.bounce import IBounceDetector
+from mailman.email.message import OwnerNotification, UserNotification
+from mailman.interfaces.bounce import (
+ IBounceDetector, UnrecognizedBounceDisposition)
from mailman.interfaces.listmanager import IListManager
from mailman.interfaces.membership import ISubscriptionService
from mailman.interfaces.pending import IPendable, IPendings
@@ -53,6 +55,7 @@ from mailman.utilities.string import oneline
log = logging.getLogger('mailman.config')
elog = logging.getLogger('mailman.error')
+blog = logging.getLogger('mailman.bounce')
DOT = '.'
@@ -245,3 +248,43 @@ def send_probe(member, msg):
probe.attach(MIMEMessage(msg))
probe.send(mlist, envsender=probe_sender, verp=False, probe_token=token)
return token
+
+
+
+def maybe_forward(mlist, msg):
+ """Possibly forward bounce messages with no recognizable addresses.
+
+ :param mlist: The mailing list.
+ :type mlist: `IMailingList`
+ :param msg: The bounce message to scan.
+ :type msg: `Message`
+ """
+ message_id = msg['message-id']
+ if (mlist.forward_unrecognized_bounces_to
+ == UnrecognizedBounceDisposition.discard):
+ blog.error('Discarding unrecognized bounce: {0}'.format(message_id))
+ return
+ # The notification is either going to go to the list's administrators
+ # (owners and moderators), or to the site administrators. Most of the
+ # notification is exactly the same in either case.
+ adminurl = mlist.script_url('admin') + '/bounce'
+ subject=_('Uncaught bounce notification')
+ text = MIMEText(
+ make('unrecognized.txt', mlist, adminurl=adminurl),
+ _charset=mlist.preferred_language.charset)
+ attachment = MIMEMessage(msg)
+ if (mlist.forward_unrecognized_bounces_to
+ == UnrecognizedBounceDisposition.administrators):
+ keywords = dict(roster=mlist.administrators)
+ elif (mlist.forward_unrecognized_bounces_to
+ == UnrecognizedBounceDisposition.site_owner):
+ keywords = {}
+ else:
+ raise AssertionError('Invalid forwarding disposition: {0}'.format(
+ mlist.forward_unrecognized_bounces_to))
+ # Create the notification and send it.
+ notice = OwnerNotification(mlist, subject, **keywords)
+ notice.set_type('multipart/mixed')
+ notice.attach(text)
+ notice.attach(attachment)
+ notice.send(mlist)
diff --git a/src/mailman/app/membership.py b/src/mailman/app/membership.py
index be2382a7f..e7c2f40e3 100644
--- a/src/mailman/app/membership.py
+++ b/src/mailman/app/membership.py
@@ -154,5 +154,6 @@ def delete_member(mlist, address, admin_notif=None, userack=None):
listname=mlist.real_name,
member=formataddr((realname, address)),
)
- msg = OwnerNotification(mlist, subject, text)
+ msg = OwnerNotification(mlist, subject, text,
+ roster=mlist.administrators)
msg.send(mlist)
diff --git a/src/mailman/app/notifications.py b/src/mailman/app/notifications.py
index 8bfbd0934..72413845c 100644
--- a/src/mailman/app/notifications.py
+++ b/src/mailman/app/notifications.py
@@ -131,5 +131,5 @@ def send_admin_subscription_notice(mlist, address, full_name, language):
listname=mlist.real_name,
member=formataddr((full_name, address)),
)
- msg = OwnerNotification(mlist, subject, text)
+ msg = OwnerNotification(mlist, subject, text, roster=mlist.administrators)
msg.send(mlist)
diff --git a/src/mailman/app/tests/test_bounces.py b/src/mailman/app/tests/test_bounces.py
index a79b1524c..954b5fc05 100644
--- a/src/mailman/app/tests/test_bounces.py
+++ b/src/mailman/app/tests/test_bounces.py
@@ -32,14 +32,18 @@ import unittest
from zope.component import getUtility
-from mailman.app.bounces import StandardVERP, ProbeVERP, send_probe
+from mailman.app.bounces import (
+ ProbeVERP, StandardVERP, maybe_forward, send_probe)
from mailman.app.lifecycle import create_list
from mailman.app.membership import add_member
from mailman.config import config
+from mailman.interfaces.bounce import UnrecognizedBounceDisposition
from mailman.interfaces.languages import ILanguageManager
-from mailman.interfaces.member import DeliveryMode
+from mailman.interfaces.member import DeliveryMode, MemberRole
from mailman.interfaces.pending import IPendings
+from mailman.interfaces.usermanager import IUserManager
from mailman.testing.helpers import (
+ LogFileMark,
get_queue_messages,
specialized_message_from_string as message_from_string)
from mailman.testing.layers import ConfigLayer
@@ -365,8 +369,139 @@ From: mail-daemon@example.com
+class TestMaybeForward(unittest.TestCase):
+ """Test forwarding of unrecognized bounces."""
+
+ layer = ConfigLayer
+
+ def setUp(self):
+ config.push('test config', """
+ [mailman]
+ site_owner: postmaster@example.com
+ """)
+ self._mlist = create_list('test@example.com')
+ self._msg = message_from_string("""\
+From: bouncer@example.com
+To: test-bounces@example.com
+Subject: You bounced
+Message-ID: <first>
+
+""")
+
+ def tearDown(self):
+ config.pop('test config')
+
+ def test_maybe_forward_discard(self):
+ # When forward_unrecognized_bounces_to is set to discard, no bounce
+ # messages are forwarded.
+ self._mlist.forward_unrecognized_bounces_to = (
+ UnrecognizedBounceDisposition.discard)
+ # The only artifact of this call is a log file entry.
+ mark = LogFileMark('mailman.bounce')
+ maybe_forward(self._mlist, self._msg)
+ items = get_queue_messages('virgin')
+ self.assertEqual(len(items), 0)
+ line = mark.readline()
+ self.assertEqual(
+ line[-40:-1],
+ 'Discarding unrecognized bounce: <first>')
+
+ def test_maybe_forward_list_owner(self):
+ # Set up some owner and moderator addresses.
+ user_manager = getUtility(IUserManager)
+ anne = user_manager.create_address('anne@example.com')
+ bart = user_manager.create_address('bart@example.com')
+ cris = user_manager.create_address('cris@example.com')
+ dave = user_manager.create_address('dave@example.com')
+ # Regular members.
+ elle = user_manager.create_address('elle@example.com')
+ fred = user_manager.create_address('fred@example.com')
+ self._mlist.subscribe(anne, MemberRole.owner)
+ self._mlist.subscribe(bart, MemberRole.owner)
+ self._mlist.subscribe(cris, MemberRole.moderator)
+ self._mlist.subscribe(dave, MemberRole.moderator)
+ self._mlist.subscribe(elle, MemberRole.member)
+ self._mlist.subscribe(fred, MemberRole.member)
+ # When forward_unrecognized_bounces_to is set to owners, the
+ # bounce is forwarded to the list owners and moderators.
+ self._mlist.forward_unrecognized_bounces_to = (
+ UnrecognizedBounceDisposition.administrators)
+ maybe_forward(self._mlist, self._msg)
+ items = get_queue_messages('virgin')
+ self.assertEqual(len(items), 1)
+ msg = items[0].msg
+ self.assertEqual(msg['subject'], 'Uncaught bounce notification')
+ self.assertEqual(msg['from'], 'postmaster@example.com')
+ self.assertEqual(msg['to'], 'test-owner@example.com')
+ # The first attachment is a notification message with a url.
+ payload = msg.get_payload(0)
+ self.assertEqual(payload.get_content_type(), 'text/plain')
+ body = payload.get_payload()
+ self.assertEqual(
+ body.splitlines()[-1],
+ 'http://lists.example.com/admin/test@example.com/bounce')
+ # The second attachment should be a message/rfc822 containing the
+ # original bounce message.
+ payload = msg.get_payload(1)
+ self.assertEqual(payload.get_content_type(), 'message/rfc822')
+ bounce = payload.get_payload(0)
+ self.assertEqual(bounce.as_string(), self._msg.as_string())
+ # All of the owners and moderators, but none of the members, should be
+ # recipients of this message.
+ self.assertEqual(items[0].msgdata['recipients'],
+ set(['anne@example.com', 'bart@example.com',
+ 'cris@example.com', 'dave@example.com']))
+
+ def test_maybe_forward_site_owner(self):
+ # Set up some owner and moderator addresses.
+ user_manager = getUtility(IUserManager)
+ anne = user_manager.create_address('anne@example.com')
+ bart = user_manager.create_address('bart@example.com')
+ cris = user_manager.create_address('cris@example.com')
+ dave = user_manager.create_address('dave@example.com')
+ # Regular members.
+ elle = user_manager.create_address('elle@example.com')
+ fred = user_manager.create_address('fred@example.com')
+ self._mlist.subscribe(anne, MemberRole.owner)
+ self._mlist.subscribe(bart, MemberRole.owner)
+ self._mlist.subscribe(cris, MemberRole.moderator)
+ self._mlist.subscribe(dave, MemberRole.moderator)
+ self._mlist.subscribe(elle, MemberRole.member)
+ self._mlist.subscribe(fred, MemberRole.member)
+ # When forward_unrecognized_bounces_to is set to owners, the
+ # bounce is forwarded to the list owners and moderators.
+ self._mlist.forward_unrecognized_bounces_to = (
+ UnrecognizedBounceDisposition.site_owner)
+ maybe_forward(self._mlist, self._msg)
+ items = get_queue_messages('virgin')
+ self.assertEqual(len(items), 1)
+ msg = items[0].msg
+ self.assertEqual(msg['subject'], 'Uncaught bounce notification')
+ self.assertEqual(msg['from'], 'postmaster@example.com')
+ self.assertEqual(msg['to'], 'postmaster@example.com')
+ # The first attachment is a notification message with a url.
+ payload = msg.get_payload(0)
+ self.assertEqual(payload.get_content_type(), 'text/plain')
+ body = payload.get_payload()
+ self.assertEqual(
+ body.splitlines()[-1],
+ 'http://lists.example.com/admin/test@example.com/bounce')
+ # The second attachment should be a message/rfc822 containing the
+ # original bounce message.
+ payload = msg.get_payload(1)
+ self.assertEqual(payload.get_content_type(), 'message/rfc822')
+ bounce = payload.get_payload(0)
+ self.assertEqual(bounce.as_string(), self._msg.as_string())
+ # All of the owners and moderators, but none of the members, should be
+ # recipients of this message.
+ self.assertEqual(items[0].msgdata['recipients'],
+ set(['postmaster@example.com',]))
+
+
+
def test_suite():
suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(TestMaybeForward))
suite.addTest(unittest.makeSuite(TestProbe))
suite.addTest(unittest.makeSuite(TestSendProbe))
suite.addTest(unittest.makeSuite(TestSendProbeNonEnglish))
diff --git a/src/mailman/database/mailman.sql b/src/mailman/database/mailman.sql
index 6312066c0..103bfb8e1 100644
--- a/src/mailman/database/mailman.sql
+++ b/src/mailman/database/mailman.sql
@@ -127,13 +127,13 @@ CREATE TABLE mailinglist (
autoresponse_request_text TEXT,
autoresponse_grace_period TEXT,
-- Bounces.
+ forward_unrecognized_bounces_to TEXT,
bounce_info_stale_after TEXT,
bounce_matching_headers TEXT,
bounce_notify_owner_on_disable BOOLEAN,
bounce_notify_owner_on_removal BOOLEAN,
bounce_processing BOOLEAN,
bounce_score_threshold INTEGER,
- bounce_unrecognized_goes_to_list_owner BOOLEAN,
bounce_you_are_disabled_warnings INTEGER,
bounce_you_are_disabled_warnings_interval TEXT,
-- Content filtering.
diff --git a/src/mailman/email/message.py b/src/mailman/email/message.py
index 4eb049f17..546474fbe 100644
--- a/src/mailman/email/message.py
+++ b/src/mailman/email/message.py
@@ -162,7 +162,7 @@ class Message(email.message.Message):
class UserNotification(Message):
"""Class for internally crafted messages."""
- def __init__(self, recip, sender, subject=None, text=None, lang=None):
+ def __init__(self, recipients, sender, subject=None, text=None, lang=None):
Message.__init__(self)
charset = (lang.charset if lang is not None else 'us-ascii')
subject = ('(no subject)' if subject is None else subject)
@@ -171,12 +171,12 @@ class UserNotification(Message):
self['Subject'] = Header(subject.encode(charset), charset,
header_name='Subject', errors='replace')
self['From'] = sender
- if isinstance(recip, list):
- self['To'] = COMMASPACE.join(recip)
- self.recips = recip
+ if isinstance(recipients, (list, set, tuple)):
+ self['To'] = COMMASPACE.join(recipients)
+ self.recipients = recipients
else:
- self['To'] = recip
- self.recips = [recip]
+ self['To'] = recipients
+ self.recipients = set([recipients])
def send(self, mlist, **_kws):
"""Sends the message by enqueuing it to the 'virgin' queue.
@@ -202,7 +202,7 @@ class UserNotification(Message):
virginq = config.switchboards['virgin']
# The message metadata better have a 'recip' attribute.
enqueue_kws = dict(
- recipients=self.recips,
+ recipients=self.recipients,
nodecorate=True,
reduced_list_headers=True,
)
@@ -218,20 +218,21 @@ class UserNotification(Message):
class OwnerNotification(UserNotification):
- """Like user notifications, but this message goes to the list owners."""
+ """Like user notifications, but this message goes to some owners."""
- def __init__(self, mlist, subject=None, text=None, tomoderators=True):
- if tomoderators:
- roster = mlist.moderators
+ def __init__(self, mlist, subject=None, text=None, roster=None):
+ if roster is None:
+ recipients = set([config.mailman.site_owner])
+ to = config.mailman.site_owner
else:
- roster = mlist.owners
- recips = [address.address for address in roster.addresses]
+ recipients = set(address.email for address in roster.addresses)
+ to = mlist.owner_address
sender = config.mailman.site_owner
- UserNotification.__init__(self, recips, sender, subject,
+ UserNotification.__init__(self, recipients, sender, subject,
text, mlist.preferred_language)
# Hack the To header to look like it's going to the -owner address
del self['to']
- self['To'] = mlist.owner_address
+ self['To'] = to
self._sender = sender
def _enqueue(self, mlist, **_kws):
@@ -240,7 +241,7 @@ class OwnerNotification(UserNotification):
# The message metadata better have a `recip' attribute
virginq.enqueue(self,
listname=mlist.fqdn_listname,
- recipients=self.recips,
+ recipients=self.recipients,
nodecorate=True,
reduced_list_headers=True,
envsender=self._sender,
diff --git a/src/mailman/interfaces/bounce.py b/src/mailman/interfaces/bounce.py
index 0b301aa98..99f6b50b7 100644
--- a/src/mailman/interfaces/bounce.py
+++ b/src/mailman/interfaces/bounce.py
@@ -26,6 +26,7 @@ __all__ = [
'IBounceEvent',
'IBounceProcessor',
'Stop',
+ 'UnrecognizedBounceDisposition',
]
@@ -55,6 +56,17 @@ class BounceContext(Enum):
+class UnrecognizedBounceDisposition(Enum):
+ # Just throw the message away.
+ discard = 0
+ # Forward the message to the list administrators, which includes both the
+ # owners and the moderators.
+ administrators = 1
+ # Forward the message to the site owner.
+ site_owner = 2
+
+
+
class IBounceDetector(Interface):
"""Detect a bounce in an email message."""
diff --git a/src/mailman/interfaces/mailinglist.py b/src/mailman/interfaces/mailinglist.py
index 23d21cd34..7a25544d4 100644
--- a/src/mailman/interfaces/mailinglist.py
+++ b/src/mailman/interfaces/mailinglist.py
@@ -495,6 +495,16 @@ class IMailingList(Interface):
without any other checks.
""")
+ # Bounces.
+
+ forward_unrecognized_bounces_to = Attribute(
+ """What to do when a bounce contains no recognizable addresses.
+
+ This is an enumeration which specifies what to do with such bounce
+ messages. They can be discarded, forward to the list owner, or
+ forwarded to the site owner.
+ """)
+
class IAcceptableAlias(Interface):
diff --git a/src/mailman/model/docs/registration.txt b/src/mailman/model/docs/registration.txt
index d0827d37b..0e80bfa14 100644
--- a/src/mailman/model/docs/registration.txt
+++ b/src/mailman/model/docs/registration.txt
@@ -149,7 +149,7 @@ message is sent to the user in order to verify the registered address.
_parsemsg : False
listname : alpha@example.com
nodecorate : True
- recipients : [u'aperson@example.com']
+ recipients : set([u'aperson@example.com'])
reduced_list_headers: True
version : 3
diff --git a/src/mailman/model/docs/requests.txt b/src/mailman/model/docs/requests.txt
index bebb61259..812d25a43 100644
--- a/src/mailman/model/docs/requests.txt
+++ b/src/mailman/model/docs/requests.txt
@@ -302,7 +302,7 @@ The message can be rejected, meaning it is bounced back to the sender.
_parsemsg : False
listname : alist@example.com
nodecorate : True
- recipients : [u'aperson@example.org']
+ recipients : set([u'aperson@example.org'])
reduced_list_headers: True
version : 3
@@ -479,7 +479,7 @@ queue when the message is held.
_parsemsg : False
listname : alist@example.com
nodecorate : True
- recipients : [u'alist-owner@example.com']
+ recipients : set([u'alist-owner@example.com'])
reduced_list_headers: True
tomoderators : True
version : 3
@@ -534,7 +534,7 @@ subscriber.
_parsemsg : False
listname : alist@example.com
nodecorate : True
- recipients : [u'cperson@example.org']
+ recipients : set([u'cperson@example.org'])
reduced_list_headers: True
version : 3
@@ -578,7 +578,7 @@ subscription and the fact that they may need to approve it.
_parsemsg : False
listname : alist@example.com
nodecorate : True
- recipients : [u'alist-owner@example.com']
+ recipients : set([u'alist-owner@example.com'])
reduced_list_headers: True
tomoderators : True
version : 3
@@ -651,7 +651,7 @@ The welcome message is sent to the person who just subscribed.
_parsemsg : False
listname : alist@example.com
nodecorate : True
- recipients : [u'fperson@example.org']
+ recipients : set([u'fperson@example.org'])
reduced_list_headers: True
verp : False
version : 3
@@ -677,7 +677,7 @@ The admin message is sent to the moderators.
envsender : changeme@example.com
listname : alist@example.com
nodecorate : True
- recipients : []
+ recipients : set([])
reduced_list_headers: True
version : 3
@@ -759,7 +759,7 @@ notification.
_parsemsg : False
listname : alist@example.com
nodecorate : True
- recipients : [u'alist-owner@example.com']
+ recipients : set([u'alist-owner@example.com'])
reduced_list_headers: True
tomoderators : True
version : 3
@@ -818,7 +818,7 @@ and the person remains a member of the mailing list.
_parsemsg : False
listname : alist@example.com
nodecorate : True
- recipients : [u'hperson@example.com']
+ recipients : set([u'hperson@example.com'])
reduced_list_headers: True
version : 3
@@ -873,7 +873,7 @@ The goodbye message...
_parsemsg : False
listname : alist@example.com
nodecorate : True
- recipients : [u'gperson@example.com']
+ recipients : set([u'gperson@example.com'])
reduced_list_headers: True
verp : False
version : 3
@@ -898,6 +898,6 @@ The goodbye message...
envsender : changeme@example.com
listname : alist@example.com
nodecorate : True
- recipients : []
+ recipients : set([])
reduced_list_headers: True
version : 3
diff --git a/src/mailman/model/mailinglist.py b/src/mailman/model/mailinglist.py
index 6952abcf0..e0841f6d8 100644
--- a/src/mailman/model/mailinglist.py
+++ b/src/mailman/model/mailinglist.py
@@ -120,9 +120,10 @@ class MailingList(Model):
bounce_notify_owner_on_removal = Bool() # XXX
bounce_processing = Bool() # XXX
bounce_score_threshold = Int() # XXX
- bounce_unrecognized_goes_to_list_owner = Bool() # XXX
bounce_you_are_disabled_warnings = Int() # XXX
bounce_you_are_disabled_warnings_interval = TimeDelta() # XXX
+ forward_unrecognized_bounces_to = Enum()
+ # Miscellaneous
default_member_action = Enum()
default_nonmember_action = Enum()
description = Unicode()
diff --git a/src/mailman/pipeline/docs/acknowledge.txt b/src/mailman/pipeline/docs/acknowledge.txt
index b2ce11b7f..8c8552190 100644
--- a/src/mailman/pipeline/docs/acknowledge.txt
+++ b/src/mailman/pipeline/docs/acknowledge.txt
@@ -7,7 +7,7 @@ receive acknowledgments of their postings, Mailman will sent them such an
acknowledgment.
::
- >>> mlist = create_list('_xtest@example.com')
+ >>> mlist = create_list('test@example.com')
>>> mlist.real_name = 'XTest'
>>> mlist.preferred_language = 'en'
>>> # XXX This will almost certainly change once we've worked out the web
@@ -30,7 +30,7 @@ Subscribe a user to the mailing list.
>>> user_1 = user_manager.create_user('aperson@example.com')
>>> address_1 = list(user_1.addresses)[0]
>>> mlist.subscribe(address_1, MemberRole.member)
- <Member: aperson@example.com on _xtest@example.com as MemberRole.member>
+ <Member: aperson@example.com on test@example.com as MemberRole.member>
Non-member posts
@@ -83,7 +83,7 @@ will be sent.
>>> user_2 = user_manager.create_user('dperson@example.com')
>>> address_2 = list(user_2.addresses)[0]
>>> mlist.subscribe(address_2, MemberRole.member)
- <Member: dperson@example.com on _xtest@example.com as MemberRole.member>
+ <Member: dperson@example.com on test@example.com as MemberRole.member>
>>> handler.process(mlist, msg,
... dict(original_sender='dperson@example.com'))
@@ -112,14 +112,19 @@ The receipt will include the original message's subject in the response body,
>>> qmsg, qdata = virginq.dequeue(virginq.files[0])
>>> virginq.files
[]
- >>> sorted(qdata.items())
- [..., ('recipients', [u'aperson@example.com']), ...]
+ >>> dump_msgdata(qdata)
+ _parsemsg : False
+ listname : test@example.com
+ nodecorate : True
+ recipients : set([u'aperson@example.com'])
+ reduced_list_headers: True
+ ...
>>> print qmsg.as_string()
...
MIME-Version: 1.0
...
Subject: XTest post acknowledgment
- From: _xtest-bounces@example.com
+ From: test-bounces@example.com
To: aperson@example.com
...
Precedence: bulk
@@ -130,7 +135,7 @@ The receipt will include the original message's subject in the response body,
<BLANKLINE>
was successfully received by the XTest mailing list.
<BLANKLINE>
- List info page: http://lists.example.com/listinfo/_xtest@example.com
+ List info page: http://lists.example.com/listinfo/test@example.com
Your preferences: http://example.com/aperson@example.com
<BLANKLINE>
@@ -146,13 +151,18 @@ If there is no subject, then the receipt will use a generic message.
>>> qmsg, qdata = virginq.dequeue(virginq.files[0])
>>> virginq.files
[]
- >>> sorted(qdata.items())
- [..., ('recipients', [u'aperson@example.com']), ...]
+ >>> dump_msgdata(qdata)
+ _parsemsg : False
+ listname : test@example.com
+ nodecorate : True
+ recipients : set([u'aperson@example.com'])
+ reduced_list_headers: True
+ ...
>>> print qmsg.as_string()
MIME-Version: 1.0
...
Subject: XTest post acknowledgment
- From: _xtest-bounces@example.com
+ From: test-bounces@example.com
To: aperson@example.com
...
Precedence: bulk
@@ -163,6 +173,6 @@ If there is no subject, then the receipt will use a generic message.
<BLANKLINE>
was successfully received by the XTest mailing list.
<BLANKLINE>
- List info page: http://lists.example.com/listinfo/_xtest@example.com
+ List info page: http://lists.example.com/listinfo/test@example.com
Your preferences: http://example.com/aperson@example.com
<BLANKLINE>
diff --git a/src/mailman/pipeline/docs/replybot.txt b/src/mailman/pipeline/docs/replybot.txt
index 3a6d75499..208f6aae9 100644
--- a/src/mailman/pipeline/docs/replybot.txt
+++ b/src/mailman/pipeline/docs/replybot.txt
@@ -51,7 +51,7 @@ response.
_parsemsg : False
listname : _xtest@example.com
nodecorate : True
- recipients : [u'aperson@example.com']
+ recipients : set([u'aperson@example.com'])
reduced_list_headers: True
version : 3
@@ -143,7 +143,7 @@ Unless the ``X-Ack:`` header has a value of ``yes``, in which case, the
_parsemsg : False
listname : _xtest@example.com
nodecorate : True
- recipients : [u'asystem@example.com']
+ recipients : set([u'asystem@example.com'])
reduced_list_headers: True
version : 3
diff --git a/src/mailman/queue/bounce.py b/src/mailman/queue/bounce.py
index fb8b0124a..8787332e7 100644
--- a/src/mailman/queue/bounce.py
+++ b/src/mailman/queue/bounce.py
@@ -222,29 +222,3 @@ class BounceRunner(Runner, BounceMixin):
def _clean_up(self):
BounceMixin._clean_up(self)
Runner._clean_up(self)
-
-
-
-def maybe_forward(mlist, msg):
- # Does the list owner want to get non-matching bounce messages?
- # If not, simply discard it.
- 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)
- log.error('forwarding unrecognized, message-id: %s',
- msg.get('message-id', 'n/a'))
- else:
- log.error('discarding unrecognized, message-id: %s',
- msg.get('message-id', 'n/a'))
diff --git a/src/mailman/queue/docs/command.txt b/src/mailman/queue/docs/command.txt
index dfe6b8c19..c767e6f5f 100644
--- a/src/mailman/queue/docs/command.txt
+++ b/src/mailman/queue/docs/command.txt
@@ -63,7 +63,7 @@ And now the response is in the ``virgin`` queue.
_parsemsg : False
listname : test@example.com
nodecorate : True
- recipients : [u'aperson@example.com']
+ recipients : set([u'aperson@example.com'])
reduced_list_headers: True
version : ...
diff --git a/src/mailman/queue/outgoing.py b/src/mailman/queue/outgoing.py
index 1823b42b5..ed27f014c 100644
--- a/src/mailman/queue/outgoing.py
+++ b/src/mailman/queue/outgoing.py
@@ -156,5 +156,5 @@ class OutgoingRunner(Runner):
msgdata['deliver_until'] = deliver_until
msgdata['recipients'] = recipients
self._retryq.enqueue(msg, msgdata)
- # We've successfully completed handling of this message
+ # We've successfully completed handling of this message.
return False
diff --git a/src/mailman/queue/tests/test_outgoing.py b/src/mailman/queue/tests/test_outgoing.py
index 74850ce75..a0fe407c8 100644
--- a/src/mailman/queue/tests/test_outgoing.py
+++ b/src/mailman/queue/tests/test_outgoing.py
@@ -46,6 +46,7 @@ from mailman.interfaces.pending import IPendings
from mailman.interfaces.usermanager import IUserManager
from mailman.queue.outgoing import OutgoingRunner
from mailman.testing.helpers import (
+ LogFileMark,
get_queue_messages,
make_testable_runner,
specialized_message_from_string as message_from_string)
@@ -54,19 +55,6 @@ from mailman.utilities.datetime import factory, now
-class LogFileMark:
- def __init__(self, log_name):
- self._log = logging.getLogger(log_name)
- self._filename = self._log.handlers[0].filename
- self._filepos = os.stat(self._filename).st_size
-
- def readline(self):
- with open(self._filename) as fp:
- fp.seek(self._filepos)
- return fp.readline()
-
-
-
def run_once(qrunner):
"""Predicate for make_testable_runner().
diff --git a/src/mailman/styles/default.py b/src/mailman/styles/default.py
index 4f2b3d7d5..68934e99c 100644
--- a/src/mailman/styles/default.py
+++ b/src/mailman/styles/default.py
@@ -24,6 +24,7 @@ __all__ = [
'DefaultStyle',
]
+
# XXX Styles need to be reconciled with lazr.config.
import datetime
@@ -32,6 +33,7 @@ from zope.interface import implements
from mailman.core.i18n import _
from mailman.interfaces.action import Action
+from mailman.interfaces.bounce import UnrecognizedBounceDisposition
from mailman.interfaces.digests import DigestFrequency
from mailman.interfaces.autorespond import ResponseAction
from mailman.interfaces.mailinglist import Personalization, ReplyToMunging
@@ -161,13 +163,14 @@ ${listinfo_page}
mlist.autoresponse_request_text = ''
mlist.autoresponse_grace_period = datetime.timedelta(days=90)
# Bounces
+ mlist.forward_unrecognized_bounces_to = (
+ UnrecognizedBounceDisposition.administrators)
mlist.bounce_processing = True
mlist.bounce_score_threshold = 5.0
mlist.bounce_info_stale_after = datetime.timedelta(days=7)
mlist.bounce_you_are_disabled_warnings = 3
mlist.bounce_you_are_disabled_warnings_interval = (
datetime.timedelta(days=7))
- mlist.bounce_unrecognized_goes_to_list_owner = True
mlist.bounce_notify_owner_on_disable = True
mlist.bounce_notify_owner_on_removal = True
# This holds legacy member related information. It's keyed by the
diff --git a/src/mailman/templates/en/unrecognized.txt b/src/mailman/templates/en/unrecognized.txt
new file mode 100644
index 000000000..16bd19e79
--- /dev/null
+++ b/src/mailman/templates/en/unrecognized.txt
@@ -0,0 +1,7 @@
+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
diff --git a/src/mailman/testing/helpers.py b/src/mailman/testing/helpers.py
index 9f9ea6181..ed579e39c 100644
--- a/src/mailman/testing/helpers.py
+++ b/src/mailman/testing/helpers.py
@@ -20,6 +20,7 @@ from __future__ import absolute_import, unicode_literals
__metaclass__ = type
__all__ = [
+ 'LogFileMark',
'TestableMaster',
'call_api',
'digest_mbox',
@@ -39,6 +40,7 @@ import time
import errno
import signal
import socket
+import logging
import smtplib
import datetime
import threading
@@ -371,7 +373,7 @@ def reset_the_world():
config.db.commit()
# Reset the global style manager.
config.style_manager.populate()
-
+
def specialized_message_from_string(unicode_text):
@@ -391,3 +393,16 @@ def specialized_message_from_string(unicode_text):
message = message_from_string(text, Message)
message.original_size = original_size
return message
+
+
+
+class LogFileMark:
+ def __init__(self, log_name):
+ self._log = logging.getLogger(log_name)
+ self._filename = self._log.handlers[0].filename
+ self._filepos = os.stat(self._filename).st_size
+
+ def readline(self):
+ with open(self._filename) as fp:
+ fp.seek(self._filepos)
+ return fp.readline()