summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBarry Warsaw2011-05-13 10:25:38 +0200
committerBarry Warsaw2011-05-13 10:25:38 +0200
commite116cfac469673fad8446d93cddecdfdf6344039 (patch)
tree306fde9494c1a4fab2173e9242e917bdea84ddab /src
parent091917126e7c58657310524882743e8391166fc3 (diff)
downloadmailman-e116cfac469673fad8446d93cddecdfdf6344039.tar.gz
mailman-e116cfac469673fad8446d93cddecdfdf6344039.tar.zst
mailman-e116cfac469673fad8446d93cddecdfdf6344039.zip
Diffstat (limited to 'src')
-rw-r--r--src/mailman/config/configure.zcml23
-rw-r--r--src/mailman/database/mailman.sql11
-rw-r--r--src/mailman/interfaces/bounce.py48
-rw-r--r--src/mailman/model/bounce.py64
-rw-r--r--src/mailman/model/docs/bounce.rst76
-rw-r--r--src/mailman/model/tests/test_bounce.py46
6 files changed, 257 insertions, 11 deletions
diff --git a/src/mailman/config/configure.zcml b/src/mailman/config/configure.zcml
index 3b4497ab8..299a0ce67 100644
--- a/src/mailman/config/configure.zcml
+++ b/src/mailman/config/configure.zcml
@@ -22,6 +22,11 @@
/>
<utility
+ factory="mailman.model.bounce.BounceProcessor"
+ provides="mailman.interfaces.bounce.IBounceProcessor"
+ />
+
+ <utility
factory="mailman.model.domain.DomainManager"
provides="mailman.interfaces.domain.IDomainManager"
/>
@@ -37,11 +42,6 @@
/>
<utility
- factory="mailman.model.usermanager.UserManager"
- provides="mailman.interfaces.usermanager.IUserManager"
- />
-
- <utility
factory="mailman.model.messagestore.MessageStore"
provides="mailman.interfaces.messages.IMessageStore"
/>
@@ -52,13 +52,13 @@
/>
<utility
- factory="mailman.model.requests.Requests"
- provides="mailman.interfaces.requests.IRequests"
+ factory="mailman.app.registrar.Registrar"
+ provides="mailman.interfaces.registrar.IRegistrar"
/>
<utility
- factory="mailman.app.registrar.Registrar"
- provides="mailman.interfaces.registrar.IRegistrar"
+ factory="mailman.model.requests.Requests"
+ provides="mailman.interfaces.requests.IRequests"
/>
<utility
@@ -67,6 +67,11 @@
/>
<utility
+ factory="mailman.model.usermanager.UserManager"
+ provides="mailman.interfaces.usermanager.IUserManager"
+ />
+
+ <utility
factory="mailman.email.validate.Validator"
provides="mailman.interfaces.address.IEmailValidator"
/>
diff --git a/src/mailman/database/mailman.sql b/src/mailman/database/mailman.sql
index 5d07c607a..016328194 100644
--- a/src/mailman/database/mailman.sql
+++ b/src/mailman/database/mailman.sql
@@ -54,6 +54,17 @@ CREATE INDEX ix_autoresponserecord_address_id
CREATE INDEX ix_autoresponserecord_mailing_list_id
ON autoresponserecord (mailing_list_id);
+CREATE TABLE bounceevent (
+ id INTEGER NOT NULL,
+ list_name TEXT,
+ email TEXT,
+ 'timestamp' TIMESTAMP,
+ message_id TEXT,
+ 'where' TEXT,
+ processed BOOLEAN,
+ PRIMARY KEY (id)
+ );
+
CREATE TABLE contentfilter (
id INTEGER NOT NULL,
mailing_list_id INTEGER,
diff --git a/src/mailman/interfaces/bounce.py b/src/mailman/interfaces/bounce.py
index 22e2467b8..e6d9e8ccd 100644
--- a/src/mailman/interfaces/bounce.py
+++ b/src/mailman/interfaces/bounce.py
@@ -22,12 +22,13 @@ from __future__ import absolute_import, unicode_literals
__metaclass__ = type
__all__ = [
'IBounceDetector',
+ 'IBounceEvent',
+ 'IBounceProcessor',
'Stop',
]
-from flufl.enum import Enum
-from zope.interface import Interface
+from zope.interface import Attribute, Interface
@@ -52,3 +53,46 @@ class IBounceDetector(Interface):
returned to halt any bounce processing pipeline.
:rtype: A set strings, or `Stop`
"""
+
+
+
+class IBounceEvent(Interface):
+ """Registration record for a single bounce event."""
+
+ list_name = Attribute(
+ """The name of the mailing list that received this bounce.""")
+
+ email = Attribute(
+ """The email address that bounced.""")
+
+ timestamp = Attribute(
+ """The timestamp for when the bounce was received.""")
+
+ message_id = Attribute(
+ """The Message-ID of the bounce message.""")
+
+ where = Attribute(
+ """Where was the bounce detected?""")
+
+ processed = Attribute(
+ """Has this bounce event been processed?""")
+
+
+
+class IBounceProcessor(Interface):
+ """Manager/processor of bounce events."""
+
+ def register(mlist, email, msg, where=None):
+ """Register a bounce event.
+
+ :param mlist: The mailing list that the bounce occurred on.
+ :type mlist: IMailingList
+ :param email: The email address that is bouncing.
+ :type email: str
+ :param msg: The bounce message.
+ :type msg: email.message.Message
+ :param where: A description of where the bounce was detected.
+ :type where: str
+ :return: The registered bounce event.
+ :rtype: IBounceEvent
+ """
diff --git a/src/mailman/model/bounce.py b/src/mailman/model/bounce.py
new file mode 100644
index 000000000..855fb472f
--- /dev/null
+++ b/src/mailman/model/bounce.py
@@ -0,0 +1,64 @@
+# Copyright (C) 2011 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
+
+"""Bounce support."""
+
+from __future__ import absolute_import, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+ 'BounceEvent',
+ 'BounceProcessor',
+ ]
+
+
+from storm.locals import Bool, Int, DateTime, Unicode
+from zope.interface import implements
+
+from mailman.interfaces.bounce import IBounceEvent, IBounceProcessor
+from mailman.database.model import Model
+from mailman.utilities.datetime import now
+
+
+
+class BounceEvent(Model):
+ implements(IBounceEvent)
+
+ id = Int(primary=True)
+ list_name = Unicode()
+ email = Unicode()
+ timestamp = DateTime()
+ message_id = Unicode()
+ where = Unicode()
+ processed = Bool()
+
+ def __init__(self, list_name, email, msg, where):
+ self.list_name = list_name
+ self.email = email
+ self.timestamp = now()
+ self.message_id = msg['message-id']
+ self.where = where
+ self.processed = False
+
+
+
+class BounceProcessor:
+ implements(IBounceProcessor)
+
+ def register(self, mlist, email, msg, where=None):
+ """See `IBounceProcessor`."""
+ return BounceEvent(mlist.fqdn_listname, email, msg, where)
diff --git a/src/mailman/model/docs/bounce.rst b/src/mailman/model/docs/bounce.rst
new file mode 100644
index 000000000..6c59a68ee
--- /dev/null
+++ b/src/mailman/model/docs/bounce.rst
@@ -0,0 +1,76 @@
+=======
+Bounces
+=======
+
+When a message to an email address bounces, Mailman's bounce runner will
+register a bounce event. This registration is done through a utility.
+
+ >>> from zope.component import getUtility
+ >>> from zope.interface.verify import verifyObject
+ >>> from mailman.interfaces.bounce import IBounceProcessor
+ >>> processor = getUtility(IBounceProcessor)
+ >>> verifyObject(IBounceProcessor, processor)
+ True
+
+
+Registration
+============
+
+When a bounce occurs, it's always within the context of a specific mailing
+list.
+
+ >>> mlist = create_list('test@example.com')
+
+The bouncing email contains useful information that will be registered as
+well. In particular, the Message-ID is a key piece of data that needs to be
+recorded.
+
+ >>> msg = message_from_string("""\
+ ... From: mail-daemon@example.org
+ ... To: test-bounces@example.com
+ ... Message-ID: <first>
+ ...
+ ... """)
+
+There is a suite of bounce detectors that are used to heuristically extract
+the bouncing email addresses. Various techniques are employed including VERP,
+DSN, and magic. It is the bounce queue's responsibility to extract the set of
+bouncing email addrsses. These are passed one-by-one to the registration
+interface.
+
+ >>> event = processor.register(mlist, 'anne@example.com', msg)
+ >>> print event.list_name
+ test@example.com
+ >>> print event.email
+ anne@example.com
+ >>> print event.message_id
+ <first>
+
+Bounce events have a timestamp.
+
+ >>> print event.timestamp
+ 2005-08-01 07:49:23
+
+Bounce events have a flag indicating whether they've been processed or not.
+
+ >>> event.processed
+ False
+
+When a bounce is registered, you can also include an informative string which
+indicates where the bounce was detected. This is essentially a semantics-free
+field.
+::
+
+ >>> msg = message_from_string("""\
+ ... From: mail-daemon@example.org
+ ... To: test-bounces@example.com
+ ... Message-ID: <second>
+ ...
+ ... """)
+
+ >>> event = processor.register(
+ ... mlist, 'bart@example.com', msg, 'Some place')
+ >>> print event.message_id
+ <second>
+ >>> print event.where
+ Some place
diff --git a/src/mailman/model/tests/test_bounce.py b/src/mailman/model/tests/test_bounce.py
new file mode 100644
index 000000000..fb0bf0875
--- /dev/null
+++ b/src/mailman/model/tests/test_bounce.py
@@ -0,0 +1,46 @@
+# Copyright (C) 2011 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
+
+"""Test bounce model objects."""
+
+from __future__ import absolute_import, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+ 'test_suite',
+ ]
+
+
+import unittest
+
+from mailman.model.bounce import BounceEvent
+from mailman.testing.layers import ConfigLayer
+
+
+
+class TestBounceEvents(unittest.TestCase):
+ layer = ConfigLayer
+
+
+
+
+
+
+def test_suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(TestBounceEvents))
+ return suite