diff options
| author | Barry Warsaw | 2011-06-10 19:52:25 -0400 |
|---|---|---|
| committer | Barry Warsaw | 2011-06-10 19:52:25 -0400 |
| commit | f8596ce463863dc6defb5dac84f5b226c45cb419 (patch) | |
| tree | 616e2f0d952d1654d3b0b60d661f5349d469acf4 /src/mailman/mta | |
| parent | bf8b285acb8c2500e52ae2582f27513b9842de54 (diff) | |
| download | mailman-f8596ce463863dc6defb5dac84f5b226c45cb419.tar.gz mailman-f8596ce463863dc6defb5dac84f5b226c45cb419.tar.zst mailman-f8596ce463863dc6defb5dac84f5b226c45cb419.zip | |
Diffstat (limited to 'src/mailman/mta')
| -rw-r--r-- | src/mailman/mta/aliases.py | 65 | ||||
| -rw-r--r-- | src/mailman/mta/base.py | 8 | ||||
| -rw-r--r-- | src/mailman/mta/null.py | 10 | ||||
| -rw-r--r-- | src/mailman/mta/postfix.py | 53 | ||||
| -rw-r--r-- | src/mailman/mta/tests/__init__.py | 0 | ||||
| -rw-r--r-- | src/mailman/mta/tests/test_aliases.py | 128 |
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 |
