summaryrefslogtreecommitdiff
path: root/src/mailman/mta
diff options
context:
space:
mode:
Diffstat (limited to 'src/mailman/mta')
-rw-r--r--src/mailman/mta/postfix.py139
-rw-r--r--src/mailman/mta/tests/test_aliases.py109
2 files changed, 142 insertions, 106 deletions
diff --git a/src/mailman/mta/postfix.py b/src/mailman/mta/postfix.py
index 4fc097ffc..072581374 100644
--- a/src/mailman/mta/postfix.py
+++ b/src/mailman/mta/postfix.py
@@ -42,6 +42,7 @@ from mailman.utilities.datetime import now
log = logging.getLogger('mailman.error')
ALIASTMPL = '{0:{2}}lmtp:[{1.mta.lmtp_host}]:{1.mta.lmtp_port}'
+NL = '\n'
@@ -64,96 +65,52 @@ class LMTP:
# We can ignore the mlist argument because for LMTP delivery, we just
# generate the entire file every time.
self.regenerate()
- self.regenerate_domain()
delete = create
- def regenerate(self, output=None):
- """See `IMailTransportAgentLifecycle`.
-
- The format for Postfix's LMTP transport map is defined here:
- http://www.postfix.org/transport.5.html
- """
+ def regenerate(self, directory=None):
+ """See `IMailTransportAgentLifecycle`."""
# Acquire a lock file to prevent other processes from racing us here.
+ if directory is None:
+ directory = config.DATA_DIR
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.
- if output is None:
- path = os.path.join(config.DATA_DIR, 'postfix_lmtp')
- path_new = path + '.new'
- elif isinstance(output, basestring):
- path = output
- path_new = output + '.new'
- else:
- path = path_new = None
- if path_new is None:
- self._do_write_file(output)
- # There's nothing to rename, and we can't generate the .db
- # file, so we're done.
- return
- # Write the file.
- with open(path_new, 'w') as fp:
- self._do_write_file(fp)
+ lmtp_path = os.path.join(directory, 'postfix_lmtp')
+ lmtp_path_new = lmtp_path + '.new'
+ with open(lmtp_path_new, 'w') as fp:
+ self._generate_lmtp_file(fp)
# Atomically rename to the intended path.
- os.rename(path + '.new', path)
- # Now that the new file is in place, we must tell Postfix to
- # generate a new .db file.
- command = config.mta.postfix_map_cmd + ' ' + path
- status = (os.system(command) >> 8) & 0xff
- if status:
- msg = 'command failure: %s, %s, %s'
- errstr = os.strerror(status)
- log.error(msg, command, status, errstr)
- raise RuntimeError(msg % (command, status, errstr))
-
- def regenerate_domain(self, output=None):
- """The map for all list domains
-
- 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.
- 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.
- if output is None:
- path = os.path.join(config.DATA_DIR, 'postfix_domains')
- path_new = path + '.new'
- elif isinstance(output, basestring):
- path = output
- path_new = output + '.new'
- else:
- path = path_new = None
- if path_new is None:
- self._do_write_file_domains(output)
- # There's nothing to rename, and we can't generate the .db
- # file, so we're done.
- return
- # Write the file.
- with open(path_new, 'w') as fp:
- self._do_write_file_domains(fp)
+ os.rename(lmtp_path_new, lmtp_path)
+ domains_path = os.path.join(directory, 'postfix_domains')
+ domains_path_new = domains_path + '.new'
+ with open(domains_path_new, 'w') as fp:
+ self._generate_domains_file(fp)
# Atomically rename to the intended path.
- os.rename(path + '.new', path)
- # Now that the new file is in place, we must tell Postfix to
- # generate a new .db file.
- command = config.mta.postfix_map_cmd + ' ' + path
- status = (os.system(command) >> 8) & 0xff
- if status:
- msg = 'command failure: %s, %s, %s'
- errstr = os.strerror(status)
- log.error(msg, command, status, errstr)
- raise RuntimeError(msg % (command, status, errstr))
+ os.rename(domains_path_new, domains_path)
+ # Now, run the postmap command on both newly generated files. If
+ # one files, still try the other one.
+ errors = []
+ for path in (lmtp_path, domains_path):
+ command = config.mta.postfix_map_cmd + ' ' + path
+ status = (os.system(command) >> 8) & 0xff
+ if status:
+ msg = 'command failure: %s, %s, %s'
+ errstr = os.strerror(status)
+ log.error(msg, command, status, errstr)
+ errors.append(msg % (command, status, errstr))
+ if errors:
+ raise RuntimeError(NL.join(errors))
- def _do_write_file(self, fp):
- """Do the actual file writes for list creation."""
- # Sort all existing mailing list names first by domain, then by local
- # part. For postfix we need a dummy entry for the domain.
+ def _generate_lmtp_file(self, fp):
+ # The format for Postfix's LMTP transport map is defined here:
+ # http://www.postfix.org/transport.5.html
+ #
+ # Sort all existing mailing list names first by domain, then by
+ # local part. For Postfix we need a dummy entry for the domain.
list_manager = getUtility(IListManager)
+ utility = getUtility(IMailTransportAgentAliases)
by_domain = {}
+ sort_key = attrgetter('list_name')
for list_name, mail_host in list_manager.name_components:
mlist = _FakeList(list_name, mail_host)
by_domain.setdefault(mlist.mail_host, []).append(mlist)
@@ -164,14 +121,12 @@ class LMTP:
# file. YOU SHOULD NOT MANUALLY EDIT THIS FILE unless you know what you're
# doing, and can keep the two files properly in sync. If you screw it up,
# you're on your own.
-""".format(now().replace(microsecond=0)), file=fp)
- sort_key = attrgetter('list_name')
+ """.format(now().replace(microsecond=0)), file=fp)
for domain in sorted(by_domain):
print("""\
-# Aliases which are visible only in the @{0} domain.""".format(domain),
- file=fp)
+# Aliases which are visible only in the @{0} domain.""".format(domain),
+ file=fp)
for mlist in sorted(by_domain[domain], key=sort_key):
- utility = getUtility(IMailTransportAgentAliases)
aliases = list(utility.aliases(mlist))
width = max(len(alias) for alias in aliases) + 3
print(ALIASTMPL.format(aliases.pop(0), config, width), file=fp)
@@ -179,13 +134,11 @@ class LMTP:
print(ALIASTMPL.format(alias, config, width), file=fp)
print(file=fp)
- def _do_write_file_domains(self, fp):
- """Do the actual file writes of the domain map for list creation."""
- # 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 = []
+ def _generate_domains_file(self, fp):
+ # Uniquify the domains, then sort them alphabetically.
+ domains = set()
for list_name, mail_host in getUtility(IListManager).name_components:
- by_domain.append(mail_host)
+ domains.add(mail_host)
print("""\
# AUTOMATICALLY GENERATED BY MAILMAN ON {0}
#
@@ -194,6 +147,6 @@ class LMTP:
# doing, and can keep the two files properly in sync. If you screw it up,
# you're on your own.
""".format(now().replace(microsecond=0)), file=fp)
- for domain in sorted(by_domain):
- print("""{0} {0}""".format(domain), file=fp)
-
+ for domain in sorted(domains):
+ print('{0} {0}'.format(domain), file=fp)
+ print(file=fp)
diff --git a/src/mailman/mta/tests/test_aliases.py b/src/mailman/mta/tests/test_aliases.py
index b1a60bc95..cc6b677b3 100644
--- a/src/mailman/mta/tests/test_aliases.py
+++ b/src/mailman/mta/tests/test_aliases.py
@@ -15,28 +15,40 @@
# 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."""
+"""Test the MTA file generating utility."""
from __future__ import absolute_import, unicode_literals
__metaclass__ = type
__all__ = [
+ 'TestAliases',
+ 'TestPostfix',
]
+import os
+import shutil
+import tempfile
import unittest
-from cStringIO import StringIO
from zope.component import getUtility
from mailman.app.lifecycle import create_list
+from mailman.interfaces.domain import IDomainManager
from mailman.interfaces.mta import IMailTransportAgentAliases
from mailman.mta.postfix import LMTP
+from mailman.testing.helpers import configuration
from mailman.testing.layers import ConfigLayer
+
NL = '\n'
+def _strip_header(contents):
+ lines = contents.splitlines()
+ return NL.join(lines[7:])
+
+
class TestAliases(unittest.TestCase):
@@ -129,21 +141,35 @@ class TestPostfix(unittest.TestCase):
layer = ConfigLayer
def setUp(self):
+ self.tempdir = tempfile.mkdtemp()
self.utility = getUtility(IMailTransportAgentAliases)
self.mlist = create_list('test@example.com')
- self.output = StringIO()
self.postfix = LMTP()
# Python 2.7 has assertMultiLineEqual. Let this work without bounds.
self.maxDiff = None
self.eq = getattr(self, 'assertMultiLineEqual', self.assertEqual)
+ def tearDown(self):
+ shutil.rmtree(self.tempdir)
+
+ @configuration('mta', postfix_map_cmd='true')
def test_aliases(self):
# Test the format of the Postfix alias generator.
- self.postfix.regenerate(self.output)
- # Strip out the variable and unimportant bits of the output.
- lines = self.output.getvalue().splitlines()
- output = NL.join(lines[7:])
- self.eq(output, """\
+ self.postfix.regenerate(self.tempdir)
+ # There are two files in this directory.
+ self.assertEqual(sorted(os.listdir(self.tempdir)),
+ ['postfix_domains', 'postfix_lmtp'])
+ # The domains file, just contains the example.com domain. We have to
+ # ignore the file header.
+ with open(os.path.join(self.tempdir, 'postfix_domains')) as fp:
+ contents = _strip_header(fp.read())
+ self.eq(contents, """\
+example.com example.com
+""")
+ # The lmtp file contains transport mappings to the lmtp server.
+ with open(os.path.join(self.tempdir, 'postfix_lmtp')) as fp:
+ contents = _strip_header(fp.read())
+ self.eq(contents, """\
# 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
@@ -156,15 +182,26 @@ test-subscribe@example.com lmtp:[127.0.0.1]:9024
test-unsubscribe@example.com lmtp:[127.0.0.1]:9024
""")
+ @configuration('mta', postfix_map_cmd='true')
def test_two_lists(self):
# Both lists need to show up in the aliases file. LP: #874929.
# Create a second list.
create_list('other@example.com')
- self.postfix.regenerate(self.output)
- # Strip out the variable and unimportant bits of the output.
- lines = self.output.getvalue().splitlines()
- output = NL.join(lines[7:])
- self.eq(output, """\
+ self.postfix.regenerate(self.tempdir)
+ # There are two files in this directory.
+ self.assertEqual(sorted(os.listdir(self.tempdir)),
+ ['postfix_domains', 'postfix_lmtp'])
+ # Because both lists are in the same domain, there should be only one
+ # entry in the relays file.
+ with open(os.path.join(self.tempdir, 'postfix_domains')) as fp:
+ contents = _strip_header(fp.read())
+ self.eq(contents, """\
+example.com example.com
+""")
+ # The transport file contains entries for both lists.
+ with open(os.path.join(self.tempdir, 'postfix_lmtp')) as fp:
+ contents = _strip_header(fp.read())
+ self.eq(contents, """\
# Aliases which are visible only in the @example.com domain.
other@example.com lmtp:[127.0.0.1]:9024
other-bounces@example.com lmtp:[127.0.0.1]:9024
@@ -186,3 +223,49 @@ 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
""")
+
+ @configuration('mta', postfix_map_cmd='true')
+ def test_two_lists_two_domains(self):
+ # Now we have two lists in two different domains. Both lists will
+ # show up in the postfix_lmtp file, and both domains will show up in
+ # the postfix_domains file.
+ getUtility(IDomainManager).add('example.net')
+ create_list('other@example.net')
+ self.postfix.regenerate(self.tempdir)
+ # There are two files in this directory.
+ self.assertEqual(sorted(os.listdir(self.tempdir)),
+ ['postfix_domains', 'postfix_lmtp'])
+ # Because both lists are in the same domain, there should be only one
+ # entry in the relays file.
+ with open(os.path.join(self.tempdir, 'postfix_domains')) as fp:
+ contents = _strip_header(fp.read())
+ self.eq(contents, """\
+example.com example.com
+example.net example.net
+""")
+ # The transport file contains entries for both lists.
+ with open(os.path.join(self.tempdir, 'postfix_lmtp')) as fp:
+ contents = _strip_header(fp.read())
+ self.eq(contents, """\
+# 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
+
+# Aliases which are visible only in the @example.net domain.
+other@example.net lmtp:[127.0.0.1]:9024
+other-bounces@example.net lmtp:[127.0.0.1]:9024
+other-confirm@example.net lmtp:[127.0.0.1]:9024
+other-join@example.net lmtp:[127.0.0.1]:9024
+other-leave@example.net lmtp:[127.0.0.1]:9024
+other-owner@example.net lmtp:[127.0.0.1]:9024
+other-request@example.net lmtp:[127.0.0.1]:9024
+other-subscribe@example.net lmtp:[127.0.0.1]:9024
+other-unsubscribe@example.net lmtp:[127.0.0.1]:9024
+""")