summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/mailman/app/bounces.py61
-rw-r--r--src/mailman/app/tests/test_bounces.py7
-rw-r--r--src/mailman/config/schema.cfg2
-rw-r--r--src/mailman/model/docs/pending.txt44
-rw-r--r--src/mailman/templates/en/probe.txt23
-rw-r--r--src/mailman/utilities/i18n.py5
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)