summaryrefslogtreecommitdiff
path: root/src/mailman/mta
diff options
context:
space:
mode:
authorBarry Warsaw2011-06-10 19:52:25 -0400
committerBarry Warsaw2011-06-10 19:52:25 -0400
commitf8596ce463863dc6defb5dac84f5b226c45cb419 (patch)
tree616e2f0d952d1654d3b0b60d661f5349d469acf4 /src/mailman/mta
parentbf8b285acb8c2500e52ae2582f27513b9842de54 (diff)
downloadmailman-f8596ce463863dc6defb5dac84f5b226c45cb419.tar.gz
mailman-f8596ce463863dc6defb5dac84f5b226c45cb419.tar.zst
mailman-f8596ce463863dc6defb5dac84f5b226c45cb419.zip
Diffstat (limited to 'src/mailman/mta')
-rw-r--r--src/mailman/mta/aliases.py65
-rw-r--r--src/mailman/mta/base.py8
-rw-r--r--src/mailman/mta/null.py10
-rw-r--r--src/mailman/mta/postfix.py53
-rw-r--r--src/mailman/mta/tests/__init__.py0
-rw-r--r--src/mailman/mta/tests/test_aliases.py128
6 files changed, 230 insertions, 34 deletions
diff --git a/src/mailman/mta/aliases.py b/src/mailman/mta/aliases.py
new file mode 100644
index 000000000..9f2bd74e3
--- /dev/null
+++ b/src/mailman/mta/aliases.py
@@ -0,0 +1,65 @@
+# 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/>.
+
+"""Utility for generating all the aliases of a mailing list."""
+
+from __future__ import absolute_import, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+ 'MailTransportAgentAliases',
+ ]
+
+
+from zope.interface import implements
+
+from mailman.interfaces.mta import IMailTransportAgentAliases
+
+
+SUBDESTINATIONS = (
+ 'bounces',
+ 'confirm',
+ 'join',
+ 'leave',
+ 'owner',
+ 'request',
+ 'subscribe',
+ 'unsubscribe',
+ )
+
+
+
+class MailTransportAgentAliases:
+ """Utility for generating all the aliases of a mailing list."""
+
+ implements(IMailTransportAgentAliases)
+
+ def aliases(self, mlist):
+ """See `IMailTransportAgentAliases`."""
+ # Always return
+ yield mlist.posting_address
+ for destination in sorted(SUBDESTINATIONS):
+ yield '{0}-{1}@{2}'.format(mlist.list_name,
+ destination,
+ mlist.host_name)
+
+ def destinations(self, mlist):
+ """See `IMailTransportAgentAliases`."""
+ # Always return
+ yield mlist.list_name
+ for destination in sorted(SUBDESTINATIONS):
+ yield '{0}-{1}'.format(mlist.list_name, destination)
diff --git a/src/mailman/mta/base.py b/src/mailman/mta/base.py
index e90fbcf8f..69b1d9c5c 100644
--- a/src/mailman/mta/base.py
+++ b/src/mailman/mta/base.py
@@ -21,6 +21,7 @@ from __future__ import absolute_import, unicode_literals
__metaclass__ = type
__all__ = [
+ 'BaseAliases',
'BaseDelivery',
'IndividualDelivery',
]
@@ -168,3 +169,10 @@ class IndividualDelivery(BaseDelivery):
mlist, message_copy, msgdata_copy, [recipient])
refused.update(status)
return refused
+
+
+
+class BaseAliases:
+ """Common alias generation; use as a mixin."""
+
+
diff --git a/src/mailman/mta/null.py b/src/mailman/mta/null.py
index 0afda5fec..c69fb3207 100644
--- a/src/mailman/mta/null.py
+++ b/src/mailman/mta/null.py
@@ -29,23 +29,23 @@ __all__ = [
from zope.interface import implements
-from mailman.interfaces.mta import IMailTransportAgentAliases
+from mailman.interfaces.mta import IMailTransportAgentLifecycle
class NullMTA:
"""Null MTA that just satisfies the interface."""
- implements(IMailTransportAgentAliases)
+ implements(IMailTransportAgentLifecycle)
def create(self, mlist):
- """See `IMailTransportAgentAliases`."""
+ """See `IMailTransportAgentLifecycle`."""
pass
def delete(self, mlist):
- """See `IMailTransportAgentAliases`."""
+ """See `IMailTransportAgentLifecycle`."""
pass
def regenerate(self, output=None):
- """See `IMailTransportAgentAliases`."""
+ """See `IMailTransportAgentLifecycle`."""
pass
diff --git a/src/mailman/mta/postfix.py b/src/mailman/mta/postfix.py
index 81a2a7945..a4fe0f331 100644
--- a/src/mailman/mta/postfix.py
+++ b/src/mailman/mta/postfix.py
@@ -30,30 +30,28 @@ import logging
import datetime
from flufl.lock import Lock
+from operator import attrgetter
from zope.component import getUtility
from zope.interface import implements
from mailman.config import config
from mailman.interfaces.listmanager import IListManager
-from mailman.interfaces.mta import IMailTransportAgentAliases
+from mailman.interfaces.mta import (
+ IMailTransportAgentAliases, IMailTransportAgentLifecycle)
-log = logging.getLogger('mailman.error')
-LOCKFILE = os.path.join(config.LOCK_DIR, 'mta')
-SUBDESTINATIONS = (
- 'bounces', 'confirm', 'join', 'leave',
- 'owner', 'request', 'subscribe', 'unsubscribe',
- )
+log = logging.getLogger('mailman.error')
+ALIASTMPL = '{0:{2}}lmtp:[{1.mta.lmtp_host}]:{1.mta.lmtp_port}'
class LMTP:
"""Connect Mailman to Postfix via LMTP."""
- implements(IMailTransportAgentAliases)
+ implements(IMailTransportAgentLifecycle)
def create(self, mlist):
- """See `IMailTransportAgentAliases`."""
+ """See `IMailTransportAgentLifecycle`."""
# We can ignore the mlist argument because for LMTP delivery, we just
# generate the entire file every time.
self.regenerate()
@@ -61,13 +59,14 @@ class LMTP:
delete = create
def regenerate(self, output=None):
- """See `IMailTransportAgentAliases`.
+ """See `IMailTransportAgentLifecycle`.
The format for Postfix's LMTP transport map is defined here:
http://www.postfix.org/transport.5.html
"""
# Acquire a lock file to prevent other processes from racing us here.
- with Lock(LOCKFILE):
+ lock_file = os.path.join(config.LOCK_DIR, 'mta')
+ with Lock(lock_file):
# If output is a filename, open up a backing file and write the
# output there, then do the atomic rename dance. First though, if
# it's None, we use a calculated path.
@@ -104,9 +103,8 @@ class LMTP:
# Sort all existing mailing list names first by domain, then my local
# part. For postfix we need a dummy entry for the domain.
by_domain = {}
- for mailing_list in getUtility(IListManager).mailing_lists:
- by_domain.setdefault(mailing_list.host_name, []).append(
- mailing_list.list_name)
+ for mlist in getUtility(IListManager).mailing_lists:
+ by_domain.setdefault(mlist.host_name, []).append(mlist)
print >> fp, """\
# AUTOMATICALLY GENERATED BY MAILMAN ON {0}
#
@@ -115,22 +113,19 @@ class LMTP:
# doing, and can keep the two files properly in sync. If you screw it up,
# you're on your own.
""".format(datetime.datetime.now().replace(microsecond=0))
+ sort_key = attrgetter('list_name')
for domain in sorted(by_domain):
print >> fp, """\
-# Aliases which are visible only in the @{0} domain.
-""".format(domain)
- for list_name in by_domain[domain]:
- # Calculate the field width of the longest alias. 10 ==
- # len('-subscribe') + '@'.
- longest = len(list_name + domain) + 10
- print >> fp, """\
-{0}@{1:{3}}lmtp:[{2.mta.lmtp_host}]:{2.mta.lmtp_port}""".format(
- list_name, domain, config,
+# Aliases which are visible only in the @{0} domain.""".format(domain)
+ for mlist in sorted(by_domain[domain], key=sort_key):
+ utility = getUtility(IMailTransportAgentAliases)
+ aliases = list(utility.aliases(mlist))
+ longest = max(len(alias) for alias in aliases)
+ print >> fp, ALIASTMPL.format(
+ aliases.pop(0), config,
# Add 1 because the bare list name has no dash.
- longest + 1)
- for destination in SUBDESTINATIONS:
- print >> fp, """\
-{0}-{1}@{2:{4}}lmtp:[{3.mta.lmtp_host}]:{3.mta.lmtp_port}""".format(
- list_name, destination, domain, config,
- longest - len(destination))
+ longest + 3)
+ for alias in aliases:
+ print >> fp, ALIASTMPL.format(
+ alias, config, longest + 3)
print >> fp
diff --git a/src/mailman/mta/tests/__init__.py b/src/mailman/mta/tests/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/mailman/mta/tests/__init__.py
diff --git a/src/mailman/mta/tests/test_aliases.py b/src/mailman/mta/tests/test_aliases.py
new file mode 100644
index 000000000..96207c943
--- /dev/null
+++ b/src/mailman/mta/tests/test_aliases.py
@@ -0,0 +1,128 @@
+# 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 the template generating utility."""
+
+from __future__ import absolute_import, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+ 'test_suite',
+ ]
+
+
+import unittest
+
+from cStringIO import StringIO
+from zope.component import getUtility
+
+from mailman.app.lifecycle import create_list
+from mailman.config import config
+from mailman.interfaces.mta import IMailTransportAgentAliases
+from mailman.mta.postfix import LMTP
+from mailman.testing.layers import ConfigLayer
+
+NL = '\n'
+
+
+
+class TestAliases(unittest.TestCase):
+
+ layer = ConfigLayer
+
+ def setUp(self):
+ self.utility = getUtility(IMailTransportAgentAliases)
+ self.mlist = create_list('test@example.com')
+
+ def test_posting_address_first(self):
+ # The posting address is always first.
+ aliases = list(self.utility.aliases(self.mlist))
+ self.assertEqual(aliases[0], self.mlist.posting_address)
+
+ def test_aliases(self):
+ # The aliases are the fully qualified email addresses.
+ aliases = list(self.utility.aliases(self.mlist))
+ self.assertEqual(aliases, [
+ 'test@example.com',
+ 'test-bounces@example.com',
+ 'test-confirm@example.com',
+ 'test-join@example.com',
+ 'test-leave@example.com',
+ 'test-owner@example.com',
+ 'test-request@example.com',
+ 'test-subscribe@example.com',
+ 'test-unsubscribe@example.com',
+ ])
+
+ def test_destinations(self):
+ # The destinations are just the local part.
+ destinations = list(self.utility.destinations(self.mlist))
+ self.assertEqual(destinations, [
+ 'test',
+ 'test-bounces',
+ 'test-confirm',
+ 'test-join',
+ 'test-leave',
+ 'test-owner',
+ 'test-request',
+ 'test-subscribe',
+ 'test-unsubscribe',
+ ])
+
+
+
+class TestPostfix(unittest.TestCase):
+ """Test the Postfix LMTP alias generator."""
+
+ layer = ConfigLayer
+
+ def setUp(self):
+ self.utility = getUtility(IMailTransportAgentAliases)
+ self.mlist = create_list('test@example.com')
+ self.output = StringIO()
+ self.postfix = LMTP()
+ # For Python 2.7's unittest.
+ self.maxDiff = None
+
+ def test_aliases(self):
+ # Test the format of the Postfix alias generator.
+ self.postfix.regenerate(self.output)
+ # Python 2.7 has assertMultiLineEqual but Python 2.6 does not.
+ eq = getattr(self, 'assertMultiLineEqual', self.assertEqual)
+ # Strip out the variable and unimportant bits of the output.
+ lines = self.output.getvalue().splitlines()
+ output = NL.join(lines[7:])
+ eq(output, """\
+# Aliases which are visible only in the @example.com domain.
+test@example.com lmtp:[127.0.0.1]:9024
+test-bounces@example.com lmtp:[127.0.0.1]:9024
+test-confirm@example.com lmtp:[127.0.0.1]:9024
+test-join@example.com lmtp:[127.0.0.1]:9024
+test-leave@example.com lmtp:[127.0.0.1]:9024
+test-owner@example.com lmtp:[127.0.0.1]:9024
+test-request@example.com lmtp:[127.0.0.1]:9024
+test-subscribe@example.com lmtp:[127.0.0.1]:9024
+test-unsubscribe@example.com lmtp:[127.0.0.1]:9024
+""")
+
+
+
+def test_suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(TestAliases))
+ suite.addTest(unittest.makeSuite(TestPostfix))
+ return suite