summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mailman/app/bounces.py106
-rw-r--r--src/mailman/app/tests/test_bounces.py49
-rw-r--r--src/mailman/queue/bounce.py4
-rw-r--r--src/mailman/utilities/uid.py30
4 files changed, 120 insertions, 69 deletions
diff --git a/src/mailman/app/bounces.py b/src/mailman/app/bounces.py
index 8d2526a66..8c42ede46 100644
--- a/src/mailman/app/bounces.py
+++ b/src/mailman/app/bounces.py
@@ -21,8 +21,8 @@ from __future__ import absolute_import, unicode_literals
__metaclass__ = type
__all__ = [
+ 'StandardVERP',
'bounce_message',
- 'get_verp',
'scan_message',
]
@@ -39,6 +39,7 @@ 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.utilities.email import split_email
from mailman.utilities.string import oneline
@@ -103,46 +104,75 @@ def scan_message(mlist, msg):
-def get_verp(mlist, msg):
- """Extract a set of VERP bounce addresses.
+class _BaseVERPParser:
+ """Base class for parsing VERP messages.
Sadly not every MTA bounces VERP messages correctly, or consistently.
First, the To: header is checked, then Delivered-To: (Postfix),
Envelope-To: (Exim) and Apparently-To:. Note that there can be multiple
- Delivered-To: headers so we need to search them all (and we don't worry
- about false positives for forwarded email, because only one should match
- `verp_regexp`).
-
- :param mlist: The mailing list being checked.
- :type mlist: `IMailingList`
- :param msg: The message being parsed.
- :type msg: `email.message.Message`
- :return: The set of addresses extracted from the VERP headers.
- :rtype: set of strings
+ headers so we need to search them all
"""
- cre = re.compile(config.mta.verp_regexp, re.IGNORECASE)
- blocal, bdomain = split_email(mlist.bounces_address)
- values = set()
- verp_matches = set()
- for header in ('to', 'delivered-to', 'envelope-to', 'apparently-to'):
- values.update(msg.get_all(header, []))
- for field in values:
- address = parseaddr(field)[1]
- if not address:
- # This header was empty.
- continue
- mo = cre.search(address)
- if not mo:
- # This did not match the VERP regexp.
- continue
- try:
- if blocal != mo.group('bounces'):
- # This was not a bounce to our mailing list.
+
+ def __init__(self, pattern):
+ self._pattern = pattern
+ self._cre = re.compile(pattern, re.IGNORECASE)
+
+ def _get_addresses(self, match_object):
+ raise NotImplementedError
+
+ def get_verp(self, mlist, msg):
+ """Extract a set of VERP bounce addresses.
+
+
+ :param mlist: The mailing list being checked.
+ :type mlist: `IMailingList`
+ :param msg: The message being parsed.
+ :type msg: `email.message.Message`
+ :return: The set of addresses extracted from the VERP headers.
+ :rtype: set of strings
+ """
+ blocal, bdomain = split_email(mlist.bounces_address)
+ values = set()
+ verp_matches = set()
+ for header in ('to', 'delivered-to', 'envelope-to', 'apparently-to'):
+ values.update(msg.get_all(header, []))
+ for field in values:
+ address = parseaddr(field)[1]
+ if not address:
+ # This header was empty.
continue
- original_address = '{0}@{1}'.format(*mo.group('local', 'domain'))
- except IndexError:
- elog.error("verp_regexp doesn't yield the right match groups")
- return set()
- else:
- verp_matches.add(original_address)
- return verp_matches
+ mo = self._cre.search(address)
+ if not mo:
+ # This did not match the VERP regexp.
+ continue
+ try:
+ if blocal != mo.group('bounces'):
+ # This was not a bounce to our mailing list.
+ continue
+ original_address = self._get_address(mo)
+ except IndexError:
+ elog.error('Bad VERP pattern: {0}'.format(self._pattern))
+ return set()
+ else:
+ verp_matches.add(original_address)
+ return verp_matches
+
+
+
+class StandardVERP(_BaseVERPParser):
+ def __init__(self):
+ super(StandardVERP, self).__init__(config.mta.verp_regexp)
+
+ def _get_address(self, match_object):
+ return '{0}@{1}'.format(*match_object.group('local', 'domain'))
+
+
+class ProbeVERP(_BaseVERPParser):
+ def __init__(self):
+ super(ProbeVERP, self).__init__(config.mta.verp_probe_regexp)
+
+ def _get_address(self, match_object):
+ # Extract the token and get the matching address.
+ token = match_object.group('token')
+ op, address, bmsg = getUtility(IPendings).confirm(token)
+ return address
diff --git a/src/mailman/app/tests/test_bounces.py b/src/mailman/app/tests/test_bounces.py
index c6a9394ad..024868056 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 get_verp
+from mailman.app.bounces import StandardVERP
from mailman.app.lifecycle import create_list
from mailman.testing.helpers import (
specialized_message_from_string as message_from_string)
@@ -36,10 +36,13 @@ from mailman.testing.layers import ConfigLayer
class TestVERP(unittest.TestCase):
+ """Test header VERP detection."""
+
layer = ConfigLayer
def setUp(self):
self._mlist = create_list('test@example.com')
+ self._verper = StandardVERP()
def test_no_verp(self):
# The empty set is returned when there is no VERP headers.
@@ -48,7 +51,7 @@ From: postmaster@example.com
To: mailman-bounces@example.com
""")
- self.assertEqual(get_verp(self._mlist, msg), set())
+ self.assertEqual(self._verper.get_verp(self._mlist, msg), set())
def test_verp_in_to(self):
# A VERP address is found in the To header.
@@ -57,7 +60,8 @@ From: postmaster@example.com
To: test-bounces+anne=example.org@example.com
""")
- self.assertEqual(get_verp(self._mlist, msg), set(['anne@example.org']))
+ self.assertEqual(self._verper.get_verp(self._mlist, msg),
+ set(['anne@example.org']))
def test_verp_in_delivered_to(self):
# A VERP address is found in the Delivered-To header.
@@ -66,7 +70,8 @@ From: postmaster@example.com
Delivered-To: test-bounces+anne=example.org@example.com
""")
- self.assertEqual(get_verp(self._mlist, msg), set(['anne@example.org']))
+ self.assertEqual(self._verper.get_verp(self._mlist, msg),
+ set(['anne@example.org']))
def test_verp_in_envelope_to(self):
# A VERP address is found in the Envelope-To header.
@@ -75,7 +80,8 @@ From: postmaster@example.com
Envelope-To: test-bounces+anne=example.org@example.com
""")
- self.assertEqual(get_verp(self._mlist, msg), set(['anne@example.org']))
+ self.assertEqual(self._verper.get_verp(self._mlist, msg),
+ set(['anne@example.org']))
def test_verp_in_apparently_to(self):
# A VERP address is found in the Apparently-To header.
@@ -84,7 +90,8 @@ From: postmaster@example.com
Apparently-To: test-bounces+anne=example.org@example.com
""")
- self.assertEqual(get_verp(self._mlist, msg), set(['anne@example.org']))
+ self.assertEqual(self._verper.get_verp(self._mlist, msg),
+ set(['anne@example.org']))
def test_verp_with_empty_header(self):
# A VERP address is found, but there's an empty header.
@@ -94,7 +101,8 @@ To: test-bounces+anne=example.org@example.com
To:
""")
- self.assertEqual(get_verp(self._mlist, msg), set(['anne@example.org']))
+ self.assertEqual(self._verper.get_verp(self._mlist, msg),
+ set(['anne@example.org']))
def test_no_verp_with_empty_header(self):
# There's an empty header, and no VERP address is found.
@@ -103,7 +111,7 @@ From: postmaster@example.com
To:
""")
- self.assertEqual(get_verp(self._mlist, msg), set())
+ self.assertEqual(self._verper.get_verp(self._mlist, msg), set())
def test_verp_with_non_match(self):
# A VERP address is found, but a header had a non-matching pattern.
@@ -113,7 +121,8 @@ To: test-bounces+anne=example.org@example.com
To: test-bounces@example.com
""")
- self.assertEqual(get_verp(self._mlist, msg), set(['anne@example.org']))
+ self.assertEqual(self._verper.get_verp(self._mlist, msg),
+ set(['anne@example.org']))
def test_no_verp_with_non_match(self):
# No VERP address is found, and a header had a non-matching pattern.
@@ -122,7 +131,7 @@ From: postmaster@example.com
To: test-bounces@example.com
""")
- self.assertEqual(get_verp(self._mlist, msg), set())
+ self.assertEqual(self._verper.get_verp(self._mlist, msg), set())
def test_multiple_verps(self):
# More than one VERP address was found in the same header.
@@ -132,7 +141,8 @@ To: test-bounces+anne=example.org@example.com
To: test-bounces+anne=example.org@example.com
""")
- self.assertEqual(get_verp(self._mlist, msg), set(['anne@example.org']))
+ self.assertEqual(self._verper.get_verp(self._mlist, msg),
+ set(['anne@example.org']))
def test_multiple_verps_different_values(self):
# More than one VERP address was found in the same header with
@@ -143,7 +153,7 @@ To: test-bounces+anne=example.org@example.com
To: test-bounces+bart=example.org@example.com
""")
- self.assertEqual(get_verp(self._mlist, msg),
+ self.assertEqual(self._verper.get_verp(self._mlist, msg),
set(['anne@example.org', 'bart@example.org']))
def test_multiple_verps_different_values_different_headers(self):
@@ -155,12 +165,25 @@ To: test-bounces+anne=example.org@example.com
Apparently-To: test-bounces+bart=example.org@example.com
""")
- self.assertEqual(get_verp(self._mlist, msg),
+ self.assertEqual(self._verper.get_verp(self._mlist, msg),
set(['anne@example.org', 'bart@example.org']))
+class TestProbe(unittest.TestCase):
+ """Test VERP probing."""
+
+ layer = ConfigLayer
+
+ def setUp(self):
+ self._mlist = create_list('test@example.com')
+
+
+
+
+
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestVERP))
+ suite.addTest(unittest.makeSuite(TestProbe))
return suite
diff --git a/src/mailman/queue/bounce.py b/src/mailman/queue/bounce.py
index d6b1576a2..35c27c6d6 100644
--- a/src/mailman/queue/bounce.py
+++ b/src/mailman/queue/bounce.py
@@ -26,7 +26,7 @@ import datetime
from email.utils import parseaddr
from lazr.config import as_timedelta
-from mailman.app.bounces import get_verp
+from mailman.app.bounces import StandardVERP
from mailman.config import config
from mailman.core.i18n import _
from mailman.interfaces.bounce import Stop
@@ -190,7 +190,7 @@ class BounceRunner(Runner, BounceMixin):
if not mlist.bounce_processing:
return
# Try VERP detection first, since it's quick and easy
- addrs = get_verp(mlist, msg)
+ addrs = StandardVERP().get_verp(mlist, msg)
if addrs:
# We have an address, but check if the message is non-fatal.
if scan_messages(mlist, msg) is Stop:
diff --git a/src/mailman/utilities/uid.py b/src/mailman/utilities/uid.py
index e44ed2983..7ef50ace0 100644
--- a/src/mailman/utilities/uid.py
+++ b/src/mailman/utilities/uid.py
@@ -31,16 +31,14 @@ __all__ = [
import os
-import time
import errno
-import hashlib
from flufl.lock import Lock
+from uuid import uuid4
from mailman.config import config
from mailman.model.uid import UID
from mailman.testing import layers
-from mailman.utilities.passwords import SALT_LENGTH
@@ -69,7 +67,12 @@ class UniqueIDFactory:
self._lockobj = Lock(self._lock_file)
return self._lockobj
- def new_uid(self, bytes=None):
+ def new_uid(self):
+ """Return a new UID.
+
+ :return: The new uid
+ :rtype: unicode
+ """
if layers.is_testing():
# When in testing mode we want to produce predictable id, but we
# need to coordinate this among separate processes. We could use
@@ -80,19 +83,14 @@ class UniqueIDFactory:
# tests will be serialized enough (and the ids reset between
# tests) that it will not be a problem. Maybe.
return self._next_uid()
- salt = os.urandom(SALT_LENGTH)
while True:
- h = hashlib.sha1(repr(time.time()))
- h.update(salt)
- if bytes is not None:
- h.update(bytes)
- uid = unicode(h.hexdigest(), 'us-ascii')
- try:
- UID.record(uid)
- except ValueError:
- pass
- else:
- return uid
+ uid = unicode(uuid4().hex, 'us-ascii')
+ try:
+ UID.record(uid)
+ except ValueError:
+ pass
+ else:
+ return uid
def _next_uid(self):
with self._lock: