diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/mailman/app/bounces.py | 61 | ||||
| -rw-r--r-- | src/mailman/app/tests/test_bounces.py | 7 | ||||
| -rw-r--r-- | src/mailman/config/schema.cfg | 2 | ||||
| -rw-r--r-- | src/mailman/model/docs/pending.txt | 44 | ||||
| -rw-r--r-- | src/mailman/templates/en/probe.txt | 23 | ||||
| -rw-r--r-- | src/mailman/utilities/i18n.py | 5 |
6 files changed, 97 insertions, 45 deletions
diff --git a/src/mailman/app/bounces.py b/src/mailman/app/bounces.py index 8c42ede46..302750e6c 100644 --- a/src/mailman/app/bounces.py +++ b/src/mailman/app/bounces.py @@ -21,9 +21,11 @@ from __future__ import absolute_import, unicode_literals __metaclass__ = type __all__ = [ + 'ProbeVERP', 'StandardVERP', 'bounce_message', 'scan_message', + 'send_probe', ] @@ -33,19 +35,25 @@ import logging from email.mime.message import MIMEMessage from email.mime.text import MIMEText from email.utils import parseaddr +from zope.component import getUtility +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.interfaces.pending import IPendings +from mailman.interfaces.listmanager import IListManager +from mailman.interfaces.pending import IPendable, IPendings from mailman.utilities.email import split_email +from mailman.utilities.i18n import make from mailman.utilities.string import oneline log = logging.getLogger('mailman.config') elog = logging.getLogger('mailman.error') +DOT = '.' + def bounce_message(mlist, msg, e=None): @@ -176,3 +184,54 @@ class ProbeVERP(_BaseVERPParser): token = match_object.group('token') op, address, bmsg = getUtility(IPendings).confirm(token) return address + + + +class _ProbePendable(dict): + """The pendable dictionary for probe messages.""" + implements(IPendable) + + +def send_probe(member, msg): + """Send a VERP probe to the member. + + :param member: The member to send the probe to. From this object, both + the user and the mailing list can be determined. + :type member: IMember + :param msg: The bouncing message that caused the probe to be sent. + :type msg: + :return: The token representing this probe in the pendings database. + :rtype: string + """ + mlist = getUtility(IListManager).get(member.mailing_list) + text = make('probe.txt', mlist, member.preferred_language, { + 'listname': mlist.fqdn_listname, + 'address': member.address.email, + 'optionsurl': member.options_url, + 'owneraddr': mlist.owner_address, + }) + pendable = _ProbePendable( + member_id=member.member_id, + message_id=msg['message-id'], + ) + token = getUtility(IPendings).add(pendable) + mailbox, domain_parts = split_email(mlist.bounces_address) + probe_recipient = config.mta.verp_probe_format.format( + bounces=mailbox, + token=token, + domain=DOT.join(domain_parts), + ) + # Calculate the Subject header, in the member's preferred language. + with _.using(member.preferred_language): + subject = _('$mlist.real_name mailing list probe message') + # Craft the probe message. This will be a multipart where the first part + # is the probe text and the second part is the message that caused this + # probe to be sent. + probe = UserNotification(probe_recipient, mlist.owner_address, subject, + lang=member.preferred_language) + probe.set_type('multipart/mixed') + notice = MIMEText(text, _charset=mlist.preferred_language.charset) + probe.attach(notice) + probe.attach(MIMEMessage(msg)) + probe.send(mlist, envsender=probe_recipient, verp=False, probe_token=token) + return token diff --git a/src/mailman/app/tests/test_bounces.py b/src/mailman/app/tests/test_bounces.py index 024868056..7211d750e 100644 --- a/src/mailman/app/tests/test_bounces.py +++ b/src/mailman/app/tests/test_bounces.py @@ -27,7 +27,7 @@ __all__ = [ import unittest -from mailman.app.bounces import StandardVERP +from mailman.app.bounces import StandardVERP, send_probe from mailman.app.lifecycle import create_list from mailman.testing.helpers import ( specialized_message_from_string as message_from_string) @@ -170,6 +170,11 @@ Apparently-To: test-bounces+bart=example.org@example.com +class TestSendProbe(unittest.TestCase): + + + + class TestProbe(unittest.TestCase): """Test VERP probing.""" diff --git a/src/mailman/config/schema.cfg b/src/mailman/config/schema.cfg index 53d7d42e0..8d11cc37e 100644 --- a/src/mailman/config/schema.cfg +++ b/src/mailman/config/schema.cfg @@ -472,7 +472,7 @@ verp_personalized_deliveries: no verp_delivery_interval: 0 # VERP format and regexp for probe messages. -verp_probe_format: %(bounces)s+%(token)s +verp_probe_format: $bounces+$token@$domain verp_probe_regexp: ^(?P<bounces>[^+]+?)\+(?P<token>[^@]+)@.*$ # Set this 'yes' to activate VERP probe for disabling by bounce. verp_probes: no diff --git a/src/mailman/model/docs/pending.txt b/src/mailman/model/docs/pending.txt index e85d8e484..707e8a7fc 100644 --- a/src/mailman/model/docs/pending.txt +++ b/src/mailman/model/docs/pending.txt @@ -7,11 +7,7 @@ are stored. These can include email address registration events, held messages (but only for user confirmation), auto-approvals, and probe bounces. This is not where messages held for administrator approval are kept. - >>> from zope.interface import implements - >>> from zope.interface.verify import verifyObject - -In order to pend an event, you first need a pending database, which is -available by adapting the list manager. +In order to pend an event, you first need a pending database. >>> from mailman.interfaces.pending import IPendings >>> from zope.component import getUtility @@ -20,6 +16,7 @@ available by adapting the list manager. The pending database can add any ``IPendable`` to the database, returning a token that can be used in urls and such. + >>> from zope.interface import implements >>> from mailman.interfaces.pending import IPendable >>> class SimplePendable(dict): ... implements(IPendable) @@ -35,24 +32,23 @@ token that can be used in urls and such. There's not much you can do with tokens except to `confirm` them, which basically means returning the ``IPendable`` structure (as a dictionary) from -the database that matches the token. If the token isn't in the database, -``None`` is returned. +the database that matches the token. If the token isn't in the database, None +is returned. >>> pendable = pendingdb.confirm(bytes('missing')) >>> print pendable None >>> pendable = pendingdb.confirm(token) - >>> sorted(pendable.items()) - [(u'address', u'aperson@example.com'), - (u'language', u'en'), - (u'password', u'xyz'), - (u'realname', u'Anne Person'), - (u'type', u'subscription')] + >>> dump_msgdata(pendable) + address : aperson@example.com + language: en + password: xyz + realname: Anne Person + type : subscription After confirmation, the token is no longer in the database. - >>> pendable = pendingdb.confirm(token) - >>> print pendable + >>> print pendingdb.confirm(token) None There are a few other things you can do with the pending database. When you @@ -66,13 +62,12 @@ expunge it. >>> event_3 = SimplePendable(type='three') >>> token_3 = pendingdb.add(event_3) >>> pendable = pendingdb.confirm(token_1, expunge=False) - >>> pendable.items() - [(u'type', u'one')] + >>> dump_msgdata(pendable) + type: one >>> pendable = pendingdb.confirm(token_1, expunge=True) - >>> pendable.items() - [(u'type', u'one')] - >>> pendable = pendingdb.confirm(token_1) - >>> print pendable + >>> dump_msgdata(pendable) + type: one + >>> print pendingdb.confirm(token_1) None An event can be given a lifetime when it is pended, otherwise it just uses a @@ -86,9 +81,8 @@ default lifetime. Every once in a while the pending database is cleared of old records. >>> pendingdb.evict() - >>> pendable = pendingdb.confirm(token_4) - >>> print pendable + >>> print pendingdb.confirm(token_4) None >>> pendable = pendingdb.confirm(token_2) - >>> pendable.items() - [(u'type', u'two')] + >>> dump_msgdata(pendable) + type: two diff --git a/src/mailman/templates/en/probe.txt b/src/mailman/templates/en/probe.txt index e0ae4ff57..98eaa310d 100644 --- a/src/mailman/templates/en/probe.txt +++ b/src/mailman/templates/en/probe.txt @@ -1,25 +1,20 @@ This is a probe message. You can ignore this message. -The %(listname)s mailing list has received a number of bounces from you, -indicating that there may be a problem delivering messages to %(address)s. -A bounce sample is attached below. Please examine this message to make sure -there are no problems with your email address. You may want to check with -your mail administrator for more help. - -If you are reading this, you don't need to do anything to remain an enabled -member of the mailing list. If this message had bounced, you would not be -reading it, and your membership would have been disabled. Normally when you -are disabled, you receive occasional messages asking you to re-enable your -subscription. +The $listname mailing list has received a number of bounces from you, +indicating that there may be a problem delivering messages to $address. A +sample is attached below. Please examine this message to make sure there are +no problems with your email address. You may want to check with your mail +administrator for more help. +You don't need to do anything to remain an enabled member of the mailing list. You can also visit your membership page at - %(optionsurl)s + $optionsurl On your membership page, you can change various delivery options such as your email address and whether you get digests or not. -If you have any questions or problems, you can contact the list owner +If you have any questions or problems, you can contact the mailing list owner at - %(owneraddr)s + $owneraddr diff --git a/src/mailman/utilities/i18n.py b/src/mailman/utilities/i18n.py index 8e769329c..000e74ac6 100644 --- a/src/mailman/utilities/i18n.py +++ b/src/mailman/utilities/i18n.py @@ -174,9 +174,8 @@ def make(template_file, mailing_list=None, language=None, wrap=True, **kw): :param wrap: When True, wrap the text. :type wrap: bool :param **kw: Keyword arguments for template interpolation. - :return: A tuple of the file system path to the first matching template, - and an open file object allowing reading of the file. - :rtype: (string, file) + :return: The interpolated text. + :rtype: string :raises TemplateNotFoundError: when the template could not be found. """ path, fp = find(template_file, mailing_list, language) |
