diff options
| -rw-r--r-- | src/mailman/app/bounces.py | 47 | ||||
| -rw-r--r-- | src/mailman/app/membership.py | 3 | ||||
| -rw-r--r-- | src/mailman/app/notifications.py | 2 | ||||
| -rw-r--r-- | src/mailman/app/tests/test_bounces.py | 139 | ||||
| -rw-r--r-- | src/mailman/database/mailman.sql | 2 | ||||
| -rw-r--r-- | src/mailman/email/message.py | 33 | ||||
| -rw-r--r-- | src/mailman/interfaces/bounce.py | 12 | ||||
| -rw-r--r-- | src/mailman/interfaces/mailinglist.py | 10 | ||||
| -rw-r--r-- | src/mailman/model/docs/registration.txt | 2 | ||||
| -rw-r--r-- | src/mailman/model/docs/requests.txt | 20 | ||||
| -rw-r--r-- | src/mailman/model/mailinglist.py | 3 | ||||
| -rw-r--r-- | src/mailman/pipeline/docs/acknowledge.txt | 32 | ||||
| -rw-r--r-- | src/mailman/pipeline/docs/replybot.txt | 4 | ||||
| -rw-r--r-- | src/mailman/queue/bounce.py | 26 | ||||
| -rw-r--r-- | src/mailman/queue/docs/command.txt | 2 | ||||
| -rw-r--r-- | src/mailman/queue/outgoing.py | 2 | ||||
| -rw-r--r-- | src/mailman/queue/tests/test_outgoing.py | 14 | ||||
| -rw-r--r-- | src/mailman/styles/default.py | 5 | ||||
| -rw-r--r-- | src/mailman/templates/en/unrecognized.txt | 7 | ||||
| -rw-r--r-- | src/mailman/testing/helpers.py | 17 |
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() |
