summaryrefslogtreecommitdiff
path: root/mailman/configuration.py
diff options
context:
space:
mode:
Diffstat (limited to 'mailman/configuration.py')
-rw-r--r--mailman/configuration.py248
1 files changed, 248 insertions, 0 deletions
diff --git a/mailman/configuration.py b/mailman/configuration.py
new file mode 100644
index 000000000..1f82c8304
--- /dev/null
+++ b/mailman/configuration.py
@@ -0,0 +1,248 @@
+# Copyright (C) 2006-2008 by the Free Software Foundation, Inc.
+#
+# This program 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 2
+# of the License, or (at your option) any later version.
+#
+# This program 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 this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
+
+"""Configuration file loading and management."""
+
+import os
+import sys
+import errno
+
+from mailman import Defaults
+from mailman import Errors
+from mailman import Version
+from mailman.languages import LanguageManager
+
+SPACE = ' '
+_missing = object()
+
+DEFAULT_QRUNNERS = (
+ '.archive.ArchiveRunner',
+ '.bounce.BounceRunner',
+ '.command.CommandRunner',
+ '.incoming.IncomingRunner',
+ '.news.NewsRunner',
+ '.outgoing.OutgoingRunner',
+ '.pipeline.PipelineRunner',
+ '.retry.RetryRunner',
+ '.virgin.VirginRunner',
+ )
+
+
+
+class Configuration(object):
+ def __init__(self):
+ self.domains = {} # email host -> web host
+ self._reverse = None
+ self.qrunners = {}
+ self.QFILE_SCHEMA_VERSION = Version.QFILE_SCHEMA_VERSION
+
+ def load(self, filename=None):
+ join = os.path.join
+ # Set up the execfile namespace
+ ns = Defaults.__dict__.copy()
+ # Prune a few things, add a few things
+ del ns['__file__']
+ del ns['__name__']
+ del ns['__doc__']
+ ns['add_domain'] = self.add_domain
+ ns['add_qrunner'] = self.add_qrunner
+ ns['del_qrunner'] = self.del_qrunner
+ self._languages = LanguageManager()
+ ns['add_language'] = self._languages.add_language
+ ns['enable_language'] = self._languages.enable_language
+ # Add all known languages, but don't enable them.
+ for code, (desc, charset) in Defaults._DEFAULT_LANGUAGE_DATA.items():
+ self._languages.add_language(code, desc, charset, False)
+ # Set up the default list of qrunners so that the mailman.cfg file may
+ # add or delete them.
+ for name in DEFAULT_QRUNNERS:
+ self.add_qrunner(name)
+ # Load the configuration from the named file, or if not given, search
+ # around for a mailman.cfg file. Whatever you find, create a
+ # namespace and execfile that file in it. The values in that
+ # namespace are exposed as attributes on this Configuration instance.
+ self.filename = None
+ self.BIN_DIR = os.path.abspath(os.path.dirname(sys.argv[0]))
+ dev_dir = os.path.dirname(self.BIN_DIR)
+ paths = [
+ # Development directories.
+ join(dev_dir, 'var', 'etc', 'mailman.cfg'),
+ join(os.getcwd(), 'var', 'etc', 'mailman.cfg'),
+ join(os.getcwd(), 'etc', 'mailman.cfg'),
+ # Standard installation directories.
+ join('/etc', 'mailman.cfg'),
+ join(Defaults.DEFAULT_VAR_DIRECTORY, 'etc', 'mailman.cfg'),
+ ]
+ if filename is not None:
+ paths.insert(0, filename)
+ for cfg_path in paths:
+ path = os.path.abspath(os.path.expanduser(cfg_path))
+ try:
+ execfile(path, ns, ns)
+ except EnvironmentError, e:
+ if e.errno <> errno.ENOENT:
+ # It's okay if the mailman.cfg file does not exist. This
+ # can happen for example in the test suite.
+ raise
+ else:
+ self.filename = cfg_path
+ break
+ if self.filename is None:
+ # The logging subsystem isn't running yet, so use stderr.
+ print >> sys.stderr, 'No runtime configuration file file. Use -C'
+ sys.exit(-1)
+ # Based on values possibly set in mailman.cfg, add additional qrunners.
+ if ns['USE_MAILDIR']:
+ self.add_qrunner('.maildir.MaildirRunner')
+ if ns['USE_LMTP']:
+ self.add_qrunner('.lmtp.LMTPRunner')
+ # Pull out the defaults.
+ VAR_DIR = os.path.abspath(ns['VAR_DIR'])
+ # Now that we've loaded all the configuration files we're going to
+ # load, set up some useful directories.
+ self.LIST_DATA_DIR = join(VAR_DIR, 'lists')
+ self.LOG_DIR = join(VAR_DIR, 'logs')
+ self.LOCK_DIR = lockdir = join(VAR_DIR, 'locks')
+ self.DATA_DIR = datadir = join(VAR_DIR, 'data')
+ self.ETC_DIR = etcdir = join(VAR_DIR, 'etc')
+ self.SPAM_DIR = join(VAR_DIR, 'spam')
+ self.EXT_DIR = join(VAR_DIR, 'ext')
+ self.PUBLIC_ARCHIVE_FILE_DIR = join(VAR_DIR, 'archives', 'public')
+ self.PRIVATE_ARCHIVE_FILE_DIR = join(VAR_DIR, 'archives', 'private')
+ # Directories used by the qrunner subsystem
+ self.QUEUE_DIR = qdir = join(VAR_DIR, 'qfiles')
+ self.ARCHQUEUE_DIR = join(qdir, 'archive')
+ self.BADQUEUE_DIR = join(qdir, 'bad')
+ self.BOUNCEQUEUE_DIR = join(qdir, 'bounces')
+ self.CMDQUEUE_DIR = join(qdir, 'commands')
+ self.INQUEUE_DIR = join(qdir, 'in')
+ self.MAILDIR_DIR = join(qdir, 'maildir')
+ self.NEWSQUEUE_DIR = join(qdir, 'news')
+ self.OUTQUEUE_DIR = join(qdir, 'out')
+ self.PIPELINEQUEUE_DIR = join(qdir, 'pipeline')
+ self.RETRYQUEUE_DIR = join(qdir, 'retry')
+ self.SHUNTQUEUE_DIR = join(qdir, 'shunt')
+ self.VIRGINQUEUE_DIR = join(qdir, 'virgin')
+ self.MESSAGES_DIR = join(VAR_DIR, 'messages')
+ # Other useful files
+ self.PIDFILE = join(datadir, 'master-qrunner.pid')
+ self.SITE_PW_FILE = join(datadir, 'adm.pw')
+ self.LISTCREATOR_PW_FILE = join(datadir, 'creator.pw')
+ self.CONFIG_FILE = join(etcdir, 'mailman.cfg')
+ self.LOCK_FILE = join(lockdir, 'master-qrunner')
+ # Now update our dict so attribute syntax just works
+ del ns['add_domain']
+ del ns['add_qrunner']
+ del ns['del_qrunner']
+ self.__dict__.update(ns)
+ # Add the default domain if there are no virtual domains currently
+ # defined.
+ if not self.domains:
+ self.add_domain(self.DEFAULT_EMAIL_HOST, self.DEFAULT_URL_HOST)
+ # Enable all specified languages, and promote the language manager to
+ # a public attribute.
+ self.languages = self._languages
+ del self._languages
+ available_languages = set(self._DEFAULT_LANGUAGE_DATA)
+ enabled_languages = set(self.LANGUAGES.split())
+ if 'all' in enabled_languages:
+ languages = available_languages
+ else:
+ unknown_language = enabled_languages - available_languages
+ if unknown_language:
+ print >> sys.stderr, 'Ignoring unknown language codes:', \
+ SPACE.join(unknown_language)
+ languages = available_languages & enabled_languages
+ for code in languages:
+ self.languages.enable_language(code)
+ # Always add and enable the default server language.
+ code = self.DEFAULT_SERVER_LANGUAGE
+ self.languages.enable_language(code)
+ # Create the registry of rules and chains.
+ self.chains = {}
+ self.rules = {}
+ self.handlers = {}
+ self.pipelines = {}
+
+ def add_domain(self, email_host, url_host=None):
+ """Add a virtual domain.
+
+ :param email_host: The host name for the email interface.
+ :param url_host: Optional host name for the web interface. If not
+ given, the email host will be used.
+ """
+ if url_host is None:
+ url_host = email_host
+ if email_host in self.domains:
+ raise Errors.BadDomainSpecificationError(
+ 'Duplicate email host: %s' % email_host)
+ # Make sure there's only one mapping for the url_host
+ if url_host in self.domains.values():
+ raise Errors.BadDomainSpecificationError(
+ 'Duplicate url host: %s' % url_host)
+ # We'll do the reverse mappings on-demand. There shouldn't be too
+ # many virtual hosts that it will really matter that much.
+ self.domains[email_host] = url_host
+ # Invalidate the reverse mapping cache
+ self._reverse = None
+
+ # Given an email host name, the url host name can be looked up directly.
+ # This does the reverse mapping.
+ def get_email_host(self, url_host, default=None):
+ if self._reverse is None:
+ # XXX Can't use a generator comprehension until Python 2.4 is
+ # minimum requirement.
+ self._reverse = dict([(v, k) for k, v in self.domains.items()])
+ return self._reverse.get(url_host, default)
+
+ def add_qrunner(self, name, count=1):
+ """Convenient interface for adding additional qrunners.
+
+ name is the qrunner name and it must not include the 'Runner' suffix.
+ E.g. 'HTTP' or 'LMTP'. count is the number of qrunner slices to
+ create, by default, 1.
+ """
+ if name.startswith('.'):
+ name = 'mailman.queue' + name
+ self.qrunners[name] = count
+
+ def del_qrunner(self, name):
+ """Remove the named qrunner so that it does not start.
+
+ name is the qrunner name and it must not include the 'Runner' suffix.
+ """
+ if name.startswith('.'):
+ name = 'mailman.queue' + name
+ self.qrunners.pop(name)
+
+ @property
+ def paths(self):
+ return dict([(k, self.__dict__[k])
+ for k in self.__dict__
+ if k.endswith('_DIR')])
+
+ def ensure_directories_exist(self):
+ for variable, directory in self.paths.items():
+ try:
+ os.makedirs(directory, 02775)
+ except OSError, e:
+ if e.errno <> errno.EEXIST:
+ raise
+
+
+
+config = Configuration()