summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mailman/Defaults.py2
-rw-r--r--mailman/app/lifecycle.py14
-rw-r--r--mailman/archiving/__init__.py31
-rw-r--r--mailman/archiving/mailarchive.py5
-rw-r--r--mailman/archiving/mhonarc.py5
-rw-r--r--mailman/archiving/pipermail.py1
-rw-r--r--mailman/archiving/prototype.py5
-rw-r--r--mailman/bin/create_list.py13
-rw-r--r--mailman/bin/dumpdb.py2
-rw-r--r--mailman/bin/genaliases.py72
-rw-r--r--mailman/bin/list_lists.py2
-rw-r--r--mailman/bin/mailmanctl.py4
-rw-r--r--mailman/bin/qrunner.py4
-rw-r--r--mailman/bin/remove_list.py2
-rw-r--r--mailman/bin/unshunt.py59
-rw-r--r--mailman/bin/withlist.py5
-rw-r--r--mailman/commands/docs/join.txt2
-rw-r--r--mailman/config/config.py13
-rw-r--r--mailman/config/schema.cfg34
-rw-r--r--mailman/core/initialize.py3
-rw-r--r--mailman/core/styles.py2
-rw-r--r--mailman/database/mailinglist.py7
-rw-r--r--mailman/database/mailman.sql1
-rw-r--r--mailman/docs/archivers.txt22
-rw-r--r--mailman/docs/chains.txt5
-rw-r--r--mailman/docs/pipelines.txt64
-rw-r--r--mailman/docs/requests.txt9
-rw-r--r--mailman/interfaces/archiver.py2
-rw-r--r--mailman/interfaces/mta.py34
-rw-r--r--mailman/mta/Manual.py139
-rw-r--r--mailman/mta/Postfix.py411
-rw-r--r--mailman/mta/postfix.py122
-rw-r--r--mailman/options.py7
-rw-r--r--mailman/pipeline/cook_headers.py4
-rw-r--r--mailman/pipeline/decorate.py3
-rw-r--r--mailman/pipeline/docs/acknowledge.txt1
-rw-r--r--mailman/pipeline/docs/cook-headers.txt10
-rw-r--r--mailman/pipeline/docs/digests.txt7
-rw-r--r--mailman/pipeline/docs/replybot.txt1
-rw-r--r--mailman/queue/__init__.py2
-rw-r--r--mailman/queue/archive.py15
-rw-r--r--mailman/queue/docs/archiver.txt3
-rw-r--r--mailman/queue/docs/incoming.txt1
-rw-r--r--mailman/queue/lmtp.py6
-rw-r--r--mailman/rules/docs/emergency.txt1
-rw-r--r--mailman/testing/testing.cfg9
-rw-r--r--setup.py14
47 files changed, 378 insertions, 802 deletions
diff --git a/mailman/Defaults.py b/mailman/Defaults.py
index 2cbfe8241..e3a351ad1 100644
--- a/mailman/Defaults.py
+++ b/mailman/Defaults.py
@@ -859,7 +859,7 @@ DEFAULT_MSG_FOOTER = u"""\
_______________________________________________
$real_name mailing list
$fqdn_listname
-${web_page_url}listinfo${cgiext}/${fqdn_listname}
+${listinfo_page}
"""
# Scrub regular delivery
diff --git a/mailman/app/lifecycle.py b/mailman/app/lifecycle.py
index 5401c1e12..8bc316b43 100644
--- a/mailman/app/lifecycle.py
+++ b/mailman/app/lifecycle.py
@@ -33,7 +33,6 @@ from mailman import Utils
from mailman.Utils import ValidateEmail
from mailman.config import config
from mailman.core import errors
-from mailman.core.plugins import get_plugin
from mailman.core.styles import style_manager
from mailman.interfaces import MemberRole
@@ -52,15 +51,12 @@ def create_list(fqdn_listname, owners=None):
raise errors.BadDomainSpecificationError(domain)
mlist = config.db.list_manager.create(fqdn_listname)
for style in style_manager.lookup(mlist):
- # XXX FIXME. When we get rid of the wrapper object, this hack won't
- # be necessary. Until then, setattr on the MailList instance won't
- # set the database column values, so pass the underlying database
- # object to .apply() instead.
style.apply(mlist)
- # Coordinate with the MTA, which should be defined by plugins.
- # XXX FIXME
-## mta_plugin = get_plugin('mailman.mta')
-## mta_plugin().create(mlist)
+ # Coordinate with the MTA, as defined in the configuration file.
+ module_name, class_name = config.mta.incoming.rsplit('.', 1)
+ __import__(module_name)
+ mta = getattr(sys.modules[module_name], class_name)
+ mta().create(mlist)
# Create any owners that don't yet exist, and subscribe all addresses as
# owners of the mailing list.
usermgr = config.db.user_manager
diff --git a/mailman/archiving/__init__.py b/mailman/archiving/__init__.py
index 93b7ae3ab..e69de29bb 100644
--- a/mailman/archiving/__init__.py
+++ b/mailman/archiving/__init__.py
@@ -1,31 +0,0 @@
-# Copyright (C) 2008-2009 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/>.
-
-__metaclass__ = type
-__all__ = [
- 'initialize',
- ]
-
-
-from mailman.config import config
-from mailman.core.plugins import get_plugins
-
-
-def initialize():
- """Initialize archivers."""
- for archiver in get_plugins('mailman.archiver'):
- config.archivers[archiver.name] = archiver
diff --git a/mailman/archiving/mailarchive.py b/mailman/archiving/mailarchive.py
index 20a3ea483..334818204 100644
--- a/mailman/archiving/mailarchive.py
+++ b/mailman/archiving/mailarchive.py
@@ -44,7 +44,6 @@ class MailArchive:
implements(IArchiver)
name = 'mail-archive'
- is_enabled = False
@staticmethod
def list_url(mlist):
@@ -61,7 +60,9 @@ class MailArchive:
return None
message_id = msg.get('message-id')
# It is not the archiver's job to ensure the message has a Message-ID.
- assert message_id is not None, 'No Message-ID found'
+ # If no Message-ID is available, there is no permalink.
+ if message_id is None:
+ return None
# The angle brackets are not part of the Message-ID. See RFC 2822.
if message_id.startswith('<') and message_id.endswith('>'):
message_id = message_id[1:-1]
diff --git a/mailman/archiving/mhonarc.py b/mailman/archiving/mhonarc.py
index 3cc83170c..cc2e0d7e9 100644
--- a/mailman/archiving/mhonarc.py
+++ b/mailman/archiving/mhonarc.py
@@ -47,7 +47,6 @@ class MHonArc:
implements(IArchiver)
name = 'mhonarc'
- is_enabled = False
@staticmethod
def list_url(mlist):
@@ -66,7 +65,9 @@ class MHonArc:
# XXX What about private MHonArc archives?
message_id = msg.get('message-id')
# It is not the archiver's job to ensure the message has a Message-ID.
- assert message_id is not None, 'No Message-ID found'
+ # If no Message-ID is available, there is no permalink.
+ if message_id is None:
+ return None
# The angle brackets are not part of the Message-ID. See RFC 2822.
if message_id.startswith('<') and message_id.endswith('>'):
message_id = message_id[1:-1]
diff --git a/mailman/archiving/pipermail.py b/mailman/archiving/pipermail.py
index d534f1600..d51b80738 100644
--- a/mailman/archiving/pipermail.py
+++ b/mailman/archiving/pipermail.py
@@ -87,7 +87,6 @@ class Pipermail:
implements(IArchiver)
name = 'pipermail'
- is_enabled = False
@staticmethod
def list_url(mlist):
diff --git a/mailman/archiving/prototype.py b/mailman/archiving/prototype.py
index e17a6f177..8fbbe47c4 100644
--- a/mailman/archiving/prototype.py
+++ b/mailman/archiving/prototype.py
@@ -44,7 +44,6 @@ class Prototype:
implements(IArchiver)
name = 'prototype'
- is_enabled = False
@staticmethod
def list_url(mlist):
@@ -56,7 +55,9 @@ class Prototype:
"""See `IArchiver`."""
message_id = msg.get('message-id')
# It is not the archiver's job to ensure the message has a Message-ID.
- assert message_id is not None, 'No Message-ID found'
+ # If this header is missing, there is no permalink.
+ if message_id is None:
+ return None
# The angle brackets are not part of the Message-ID. See RFC 2822.
if message_id.startswith('<') and message_id.endswith('>'):
message_id = message_id[1:-1]
diff --git a/mailman/bin/create_list.py b/mailman/bin/create_list.py
index 36aa5c1d1..0c6039e45 100644
--- a/mailman/bin/create_list.py
+++ b/mailman/bin/create_list.py
@@ -17,12 +17,12 @@
import sys
-from mailman import errors
from mailman import Message
from mailman import Utils
from mailman import i18n
from mailman.app.lifecycle import create_list
-from mailman.configuration import config
+from mailman.config import config
+from mailman.core import errors
from mailman.interfaces import ListAlreadyExistsError
from mailman.options import SingleMailingListOptions
@@ -79,7 +79,7 @@ owner is specified with the -o option.."""))
def sanity_check(self):
"""Set up some defaults we couldn't set up earlier."""
if self.options.language is None:
- self.options.language = config.DEFAULT_SERVER_LANGUAGE
+ self.options.language = unicode(config.mailman.default_language)
# Is the language known?
if self.options.language not in config.languages.enabled_codes:
self.parser.error(_('Unknown language: $opts.language'))
@@ -95,6 +95,8 @@ def main():
# Create the mailing list, applying styles as appropriate.
fqdn_listname = options.options.listname
+ if fqdn_listname is None:
+ options.parser.error(_('--listname is required'))
try:
mlist = create_list(fqdn_listname, options.options.owners)
mlist.preferred_language = options.options.language
@@ -107,11 +109,6 @@ def main():
config.db.commit()
- # Send notices to the list owners. XXX This should also be moved to the
- # Mailman.app.create module.
- if not options.options.quiet and not options.options.automate:
- print _('Hit enter to notify $fqdn_listname owners...'),
- sys.stdin.readline()
if not options.options.quiet:
d = dict(
listname = mlist.fqdn_listname,
diff --git a/mailman/bin/dumpdb.py b/mailman/bin/dumpdb.py
index 4642ac361..6657602e4 100644
--- a/mailman/bin/dumpdb.py
+++ b/mailman/bin/dumpdb.py
@@ -18,7 +18,7 @@
import pprint
import cPickle
-from mailman.configuration import config
+from mailman.config import config
from mailman.i18n import _
from mailman.interact import interact
from mailman.options import Options
diff --git a/mailman/bin/genaliases.py b/mailman/bin/genaliases.py
index a09bc6318..3fbef0f88 100644
--- a/mailman/bin/genaliases.py
+++ b/mailman/bin/genaliases.py
@@ -1,5 +1,3 @@
-#! @PYTHON@
-#
# Copyright (C) 2001-2009 by the Free Software Foundation, Inc.
#
# This file is part of GNU Mailman.
@@ -17,67 +15,49 @@
# You should have received a copy of the GNU General Public License along with
# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
+__metaclass__ = type
+__all__ = [
+ 'main',
+ ]
+
+
import sys
-import optparse
-from mailman import MailList
-from mailman.configuration import config
+from mailman.config import config
+from mailman.core.plugins import get_plugin
from mailman.i18n import _
-from mailman.initialize import initialize
+from mailman.options import Options
from mailman.version import MAILMAN_VERSION
-def parseargs():
- parser = optparse.OptionParser(version=MAILMAN_VERSION,
- usage=_("""\
+class ScriptOptions(Options):
+ """Options for the genaliases script."""
+
+ usage = _("""\
%prog [options]
Regenerate the Mailman specific MTA aliases from scratch. The actual output
-depends on the value of the 'MTA' variable in your etc/mailman.cfg file."""))
- parser.add_option('-q', '--quiet',
- default=False, action='store_true', help=_("""\
+depends on the value of the 'MTA' variable in your etc/mailman.cfg file.""")
+
+ def add_options(self):
+ super(ScriptOptions, self).add_options()
+ self.parser.add_option(
+ '-q', '--quiet',
+ default=False, action='store_true', help=_("""\
Some MTA output can include more verbose help text. Use this to tone down the
verbosity."""))
- parser.add_option('-C', '--config',
- help=_('Alternative configuration file to use'))
- opts, args = parser.parse_args()
- if args:
- parser.print_error(_('Unexpected arguments'))
- return parser, opts, args
+
def main():
- parser, opts, args = parseargs()
- initialize(opts.config)
-
- # Import the MTA specific module
- modulename = 'mailman.MTA.' + config.MTA
- __import__(modulename)
- MTA = sys.modules[modulename]
+ options = ScriptOptions()
+ options.initialize()
- # We need to acquire a lock so nobody tries to update the files while
- # we're doing it.
- lock = MTA.makelock()
- lock.lock()
- # Group lists by virtual hostname
- mlists = {}
- for listname in config.list_manager.names:
- mlist = MailList.MailList(listname, lock=False)
- mlists.setdefault(mlist.host_name, []).append(mlist)
- try:
- MTA.clear()
- if not mlists:
- MTA.create(None, nolock=True, quiet=opts.quiet)
- else:
- for hostname, vlists in mlists.items():
- for mlist in vlists:
- MTA.create(mlist, nolock=True, quiet=opts.quiet)
- # Be verbose for only the first printed list
- quiet = True
- finally:
- lock.unlock(unconditionally=True)
+ # Get the MTA-specific module.
+ mta = get_plugin('mailman.mta')
+ mta().regenerate()
diff --git a/mailman/bin/list_lists.py b/mailman/bin/list_lists.py
index d19044f35..d2aed3c01 100644
--- a/mailman/bin/list_lists.py
+++ b/mailman/bin/list_lists.py
@@ -16,7 +16,7 @@
# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
from mailman import Defaults
-from mailman.configuration import config
+from mailman.config import config
from mailman.i18n import _
from mailman.options import Options
diff --git a/mailman/bin/mailmanctl.py b/mailman/bin/mailmanctl.py
index 6d424442a..667a46a70 100644
--- a/mailman/bin/mailmanctl.py
+++ b/mailman/bin/mailmanctl.py
@@ -27,9 +27,9 @@ import logging
from optparse import OptionParser
-from mailman.configuration import config
+from mailman.config import config
+from mailman.core.initialize import initialize
from mailman.i18n import _
-from mailman.initialize import initialize
from mailman.version import MAILMAN_VERSION
diff --git a/mailman/bin/qrunner.py b/mailman/bin/qrunner.py
index a71bb2ef5..eeb5c286b 100644
--- a/mailman/bin/qrunner.py
+++ b/mailman/bin/qrunner.py
@@ -151,9 +151,9 @@ def make_qrunner(name, slice, range, once=False):
class Once(qrclass):
def _do_periodic(self):
self.stop()
- qrunner = Once(slice, range)
+ qrunner = Once(name, slice)
else:
- qrunner = qrclass(slice, range)
+ qrunner = qrclass(name, slice)
return qrunner
diff --git a/mailman/bin/remove_list.py b/mailman/bin/remove_list.py
index fb5d4cdbf..57e41c3b3 100644
--- a/mailman/bin/remove_list.py
+++ b/mailman/bin/remove_list.py
@@ -21,7 +21,7 @@ import shutil
from mailman import Utils
from mailman.app.lifecycle import remove_list
-from mailman.configuration import config
+from mailman.config import config
from mailman.i18n import _
from mailman.options import MultipleMailingListOptions
diff --git a/mailman/bin/unshunt.py b/mailman/bin/unshunt.py
index 8a1dba081..97369ab17 100644
--- a/mailman/bin/unshunt.py
+++ b/mailman/bin/unshunt.py
@@ -15,51 +15,33 @@
# You should have received a copy of the GNU General Public License along with
# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
+__metaclass__ = type
+__all__ = [
+ 'main',
+ ]
+
+
import sys
-import optparse
-from mailman.configuration import config
+from mailman.config import config
from mailman.i18n import _
-from mailman.queue import Switchboard
from mailman.version import MAILMAN_VERSION
-
-
-
-def parseargs():
- parser = optparse.OptionParser(version=MAILMAN_VERSION,
- usage=_("""\
-%%prog [options] [directory]
-
-Move a message from the shunt queue to the original queue. Optional
-`directory' specifies a directory to dequeue from other than qfiles/shunt.
-"""))
- parser.add_option('-C', '--config',
- help=_('Alternative configuration file to use'))
- opts, args = parser.parse_args()
- if len(args) > 1:
- parser.print_help()
- print >> sys.stderr, _('Unexpected arguments')
- sys.exit(1)
- return parser, opts, args
+from mailman.options import Options
def main():
- parser, opts, args = parseargs()
- config.load(opts.config)
- if args:
- qdir = args[0]
- else:
- qdir = config.SHUNTQUEUE_DIR
+ options = Options()
+ options.initialize()
+
+ switchboard = config.switchboards['shunt']
+ switchboard.recover_backup_files()
- sb = Switchboard(qdir)
- sb.recover_backup_files()
- for filebase in sb.files():
+ for filebase in switchboard.files:
try:
- msg, msgdata = sb.dequeue(filebase)
- whichq = msgdata.get('whichq', config.INQUEUE_DIR)
- tosb = Switchboard(whichq)
- tosb.enqueue(msg, msgdata)
+ msg, msgdata = switchboard.dequeue(filebase)
+ whichq = msgdata.get('whichq', 'in')
+ config.switchboards[whichq].enqueue(msg, msgdata)
except Exception, e:
# If there are any unshunting errors, log them and continue trying
# other shunted messages.
@@ -67,9 +49,4 @@ def main():
'Cannot unshunt message $filebase, skipping:\n$e')
else:
# Unlink the .bak file left by dequeue()
- sb.finish(filebase)
-
-
-
-if __name__ == '__main__':
- main()
+ switchboard.finish(filebase)
diff --git a/mailman/bin/withlist.py b/mailman/bin/withlist.py
index 8862501d5..f180f5525 100644
--- a/mailman/bin/withlist.py
+++ b/mailman/bin/withlist.py
@@ -154,7 +154,10 @@ def main():
global LAST_MLIST, VERBOSE
parser, opts, args = parseargs()
- initialize(opts.config, not opts.quiet)
+ config_file = (os.getenv('MAILMAN_CONFIG_FILE')
+ if opts.config is None
+ else opts.config)
+ initialize(config_file, not opts.quiet)
VERBOSE = not opts.quiet
# The default for interact is true unless -r was given
diff --git a/mailman/commands/docs/join.txt b/mailman/commands/docs/join.txt
index fd382d0f0..9b85e816c 100644
--- a/mailman/commands/docs/join.txt
+++ b/mailman/commands/docs/join.txt
@@ -106,7 +106,7 @@ Mailman has sent her the confirmation message.
this message, keeping the Subject header intact. Or you can visit this
web page
<BLANKLINE>
- http://www.example.com/confirm/...
+ http://lists.example.com/confirm/...
<BLANKLINE>
If you do not wish to register this email address simply disregard this
message. If you think you are being maliciously subscribed to the list, or
diff --git a/mailman/config/config.py b/mailman/config/config.py
index 3f717d3be..cbdea2aea 100644
--- a/mailman/config/config.py
+++ b/mailman/config/config.py
@@ -54,7 +54,6 @@ class Configuration(object):
self._config = None
self.filename = None
# Create various registries.
- self.archivers = {}
self.chains = {}
self.rules = {}
self.handlers = {}
@@ -174,10 +173,22 @@ class Configuration(object):
@property
def qrunner_configs(self):
+ """Iterate over all the qrunner configuration sections."""
for section in self._config.getByCategory('qrunner', []):
yield section
@property
+ def archivers(self):
+ """Iterate over all the enabled archivers."""
+ for section in self._config.getByCategory('archiver', []):
+ if not as_boolean(section.enable):
+ continue
+ class_path = section['class']
+ module_name, class_name = class_path.rsplit('.', 1)
+ __import__(module_name)
+ yield getattr(sys.modules[module_name], class_name)()
+
+ @property
def header_matches(self):
"""Iterate over all spam matching headers.
diff --git a/mailman/config/schema.cfg b/mailman/config/schema.cfg
index 3cc4eab79..f540bfef1 100644
--- a/mailman/config/schema.cfg
+++ b/mailman/config/schema.cfg
@@ -250,6 +250,40 @@ lmtp_port: 8025
[archiver.master]
+# To add new archivers, define a new section based on this one, overriding the
+# following values.
+
+# The class implementing the IArchiver interface.
+class: mailman.archiving.prototype.Prototype
+
+# Set this to 'yes' to enable the archiver.
+enable: no
+
+# The base url for the archiver. This is used to to calculate links to
+# individual messages in the archive.
base_url: http://archive.example.com/
+
+# If the archiver works by getting a copy of the message, this is the address
+# to send the copy to.
recipient: archive@archive.example.com
+
+# If the archiver works by calling a command on the local machine, this is the
+# command to call.
command: /bin/echo
+
+
+[archiver.mhonarc]
+# This is the stock MHonArc archiver.
+class: mailman.archiving.mhonarc.MHonArc
+
+[archiver.mail_archive]
+# This is the stock mail-archive.com archiver.
+class: mailman.archiving.mailarchive.MailArchive
+
+[archiver.pipermail]
+# This is the stock Pipermail archiver.
+class: mailman.archiving.pipermail.Pipermail
+
+[archiver.prototype]
+# This is a prototypical sample archiver.
+class: mailman.archiving.prototype.Prototype
diff --git a/mailman/core/initialize.py b/mailman/core/initialize.py
index d7a2fde5a..bf38303d5 100644
--- a/mailman/core/initialize.py
+++ b/mailman/core/initialize.py
@@ -70,7 +70,6 @@ def initialize_1(config_path=None, propagate_logs=None):
def initialize_2(debug=False):
"""Second initialization step.
- * Archivers
* Rules
* Chains
* Pipelines
@@ -89,12 +88,10 @@ def initialize_2(debug=False):
# Initialize the rules and chains. Do the imports here so as to avoid
# circular imports.
from mailman.app.commands import initialize as initialize_commands
- from mailman.archiving import initialize as initialize_archivers
from mailman.core.chains import initialize as initialize_chains
from mailman.core.pipelines import initialize as initialize_pipelines
from mailman.core.rules import initialize as initialize_rules
# Order here is somewhat important.
- initialize_archivers()
initialize_rules()
initialize_chains()
initialize_pipelines()
diff --git a/mailman/core/styles.py b/mailman/core/styles.py
index 66b8baaf2..aee61790f 100644
--- a/mailman/core/styles.py
+++ b/mailman/core/styles.py
@@ -43,7 +43,7 @@ from mailman.interfaces import (
class DefaultStyle:
- """The defalt (i.e. legacy) style."""
+ """The default (i.e. legacy) style."""
implements(IStyle)
diff --git a/mailman/database/mailinglist.py b/mailman/database/mailinglist.py
index 641245daf..483cc4749 100644
--- a/mailman/database/mailinglist.py
+++ b/mailman/database/mailinglist.py
@@ -19,6 +19,7 @@ import os
import string
from storm.locals import *
+from urlparse import urljoin
from zope.interface import implements
from mailman import Defaults
@@ -45,7 +46,6 @@ class MailingList(Model):
host_name = Unicode()
# Attributes not directly modifiable via the web u/i
created_at = DateTime()
- web_page_url = Unicode()
admin_member_chunksize = Int()
hold_and_cmd_autoresponses = Pickle()
# Attributes which are directly modifiable via the web u/i. The more
@@ -202,9 +202,12 @@ class MailingList(Model):
def script_url(self, target, context=None):
"""See `IMailingList`."""
+ # Find the domain for this mailing list.
+ domain = config.domains[self.host_name]
# XXX Handle the case for when context is not None; those would be
# relative URLs.
- return self.web_page_url + target + '/' + self.fqdn_listname
+ return urljoin(domain.base_url,
+ target + Defaults.CGIEXT + '/' + self.fqdn_listname)
@property
def data_path(self):
diff --git a/mailman/database/mailman.sql b/mailman/database/mailman.sql
index 3cef32e24..b098ed13b 100644
--- a/mailman/database/mailman.sql
+++ b/mailman/database/mailman.sql
@@ -30,7 +30,6 @@ CREATE TABLE mailinglist (
list_name TEXT,
host_name TEXT,
created_at TIMESTAMP,
- web_page_url TEXT,
admin_member_chunksize INTEGER,
hold_and_cmd_autoresponses BLOB,
next_request_id INTEGER,
diff --git a/mailman/docs/archivers.txt b/mailman/docs/archivers.txt
index b8da86809..489e3f15b 100644
--- a/mailman/docs/archivers.txt
+++ b/mailman/docs/archivers.txt
@@ -24,22 +24,25 @@ Pipermail does not support a permalink, so that interface returns None.
Mailman defines a draft spec for how list servers and archivers can
interoperate.
- >>> for archiver_name, archiver in sorted(config.archivers.items()):
+ >>> archivers = {}
+ >>> from operator import attrgetter
+ >>> for archiver in sorted(config.archivers, key=attrgetter('name')):
... print archiver.name
... print ' ', archiver.list_url(mlist)
... print ' ', archiver.permalink(mlist, msg)
+ ... archivers[archiver.name] = archiver
mail-archive
http://go.mail-archive.dev/test%40example.com
http://go.mail-archive.dev/ZaXPPxRMM9_hFZL4vTRlQlBx8pc=
mhonarc
- http://www.example.com/.../test@example.com
- http://www.example.com/.../RSZCG7IGPHFIRW3EMTVMMDNJMNCVCOLE
+ http://lists.example.com/.../test@example.com
+ http://lists.example.com/.../RSZCG7IGPHFIRW3EMTVMMDNJMNCVCOLE
pipermail
http://www.example.com/pipermail/test@example.com
None
prototype
- http://www.example.com
- http://www.example.com/RSZCG7IGPHFIRW3EMTVMMDNJMNCVCOLE
+ http://lists.example.com
+ http://lists.example.com/RSZCG7IGPHFIRW3EMTVMMDNJMNCVCOLE
Sending the message to the archiver
@@ -47,8 +50,7 @@ Sending the message to the archiver
The archiver is also able to archive the message.
- >>> mlist.web_page_url = u'http://lists.example.com/'
- >>> config.archivers['pipermail'].archive_message(mlist, msg)
+ >>> archivers['pipermail'].archive_message(mlist, msg)
>>> import os
>>> from mailman.interfaces.archiver import IPipermailMailingList
@@ -60,7 +62,7 @@ The archiver is also able to archive the message.
Note however that the prototype archiver can't archive messages.
- >>> config.archivers['prototype'].archive_message(mlist, msg)
+ >>> archivers['prototype'].archive_message(mlist, msg)
Traceback (most recent call last):
...
NotImplementedError
@@ -74,7 +76,7 @@ be used to archive message for free. Mailman comes with a plugin for this
archiver; by enabling it messages to public lists will get sent there
automatically.
- >>> archiver = config.archivers['mail-archive']
+ >>> archiver = archivers['mail-archive']
>>> archiver.list_url(mlist)
'http://go.mail-archive.dev/test%40example.com'
>>> archiver.permalink(mlist, msg)
@@ -159,7 +161,7 @@ MHonArc
The MHonArc archiver <http://www.mhonarc.org> is also available.
- >>> archiver = config.archivers['mhonarc']
+ >>> archiver = archivers['mhonarc']
>>> archiver.name
'mhonarc'
diff --git a/mailman/docs/chains.txt b/mailman/docs/chains.txt
index 6c2137e27..b9fe45686 100644
--- a/mailman/docs/chains.txt
+++ b/mailman/docs/chains.txt
@@ -101,7 +101,6 @@ The Hold chain places the message into the admin request database and
depending on the list's settings, sends a notification to both the original
sender and the list moderators.
- >>> mlist.web_page_url = u'http://www.example.com/'
>>> chain = config.chains['hold']
>>> verifyObject(IChain, chain)
True
@@ -149,7 +148,7 @@ This message is addressed to the mailing list moderators.
<BLANKLINE>
At your convenience, visit:
<BLANKLINE>
- http://www.example.com/admindb/_xtest@example.com
+ http://lists.example.com/admindb/_xtest@example.com
<BLANKLINE>
to approve or deny the request.
<BLANKLINE>
@@ -209,7 +208,7 @@ This message is addressed to the sender of the message.
notification of the moderator's decision. If you would like to cancel
this posting, please visit the following URL:
<BLANKLINE>
- http://www.example.com/confirm/_xtest@example.com/...
+ http://lists.example.com/confirm/_xtest@example.com/...
<BLANKLINE>
<BLANKLINE>
diff --git a/mailman/docs/pipelines.txt b/mailman/docs/pipelines.txt
index 94cc792cd..29888ee0b 100644
--- a/mailman/docs/pipelines.txt
+++ b/mailman/docs/pipelines.txt
@@ -10,7 +10,6 @@ message once it's started.
>>> from mailman.app.lifecycle import create_list
>>> mlist = create_list(u'xtest@example.com')
- >>> mlist.web_page_url = u'http://lists.example.com/archives/'
>>> mlist.pipeline
u'built-in'
>>> from mailman.core.pipelines import process
@@ -21,7 +20,6 @@ Processing a message
Messages hit the pipeline after they've been accepted for posting.
- >>> config.archivers['pipermail'].is_enabled = True
>>> msg = message_from_string("""\
... From: aperson@example.com
... To: xtest@example.com
@@ -45,15 +43,18 @@ etc.
X-Mailman-Version: ...
Precedence: list
List-Id: <xtest.example.com>
- List-Unsubscribe:
- <http://lists.example.com/archives/listinfo/xtest@example.com>,
- <mailto:xtest-leave@example.com>
- List-Archive: <http://www.example.com/pipermail/xtest@example.com>
+ X-Message-ID-Hash: 4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB
List-Post: <mailto:xtest@example.com>
- List-Help: <mailto:xtest-request@example.com?subject=help>
List-Subscribe:
- <http://lists.example.com/archives/listinfo/xtest@example.com>,
- <mailto:xtest-join@example.com>
+ <http://lists.example.com/listinfo/xtest@example.com>,
+ <mailto:xtest-join@example.com>
+ Archived-At:
+ http://lists.example.com/pipermail/4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB
+ List-Unsubscribe:
+ <http://lists.example.com/listinfo/xtest@example.com>,
+ <mailto:xtest-leave@example.com>
+ List-Archive: <http://lists.example.com/pipermail/xtest@example.com>
+ List-Help: <mailto:xtest-request@example.com?subject=help>
<BLANKLINE>
First post!
<BLANKLINE>
@@ -82,15 +83,18 @@ And the message is now sitting in various other processing queues.
X-Mailman-Version: ...
Precedence: list
List-Id: <xtest.example.com>
- List-Unsubscribe:
- <http://lists.example.com/archives/listinfo/xtest@example.com>,
- <mailto:xtest-leave@example.com>
- List-Archive: <http://www.example.com/pipermail/xtest@example.com>
+ X-Message-ID-Hash: 4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB
List-Post: <mailto:xtest@example.com>
- List-Help: <mailto:xtest-request@example.com?subject=help>
List-Subscribe:
- <http://lists.example.com/archives/listinfo/xtest@example.com>,
+ <http://lists.example.com/listinfo/xtest@example.com>,
<mailto:xtest-join@example.com>
+ Archived-At:
+ http://lists.example.com/pipermail/4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB
+ List-Unsubscribe:
+ <http://lists.example.com/listinfo/xtest@example.com>,
+ <mailto:xtest-leave@example.com>
+ List-Archive: <http://lists.example.com/pipermail/xtest@example.com>
+ List-Help: <mailto:xtest-request@example.com?subject=help>
<BLANKLINE>
First post!
<BLANKLINE>
@@ -122,15 +126,18 @@ This is the message that will actually get delivered to end recipients.
X-Mailman-Version: ...
Precedence: list
List-Id: <xtest.example.com>
- List-Unsubscribe:
- <http://lists.example.com/archives/listinfo/xtest@example.com>,
- <mailto:xtest-leave@example.com>
- List-Archive: <http://www.example.com/pipermail/xtest@example.com>
+ X-Message-ID-Hash: 4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB
List-Post: <mailto:xtest@example.com>
- List-Help: <mailto:xtest-request@example.com?subject=help>
List-Subscribe:
- <http://lists.example.com/archives/listinfo/xtest@example.com>,
+ <http://lists.example.com/listinfo/xtest@example.com>,
<mailto:xtest-join@example.com>
+ Archived-At:
+ http://lists.example.com/pipermail/4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB
+ List-Unsubscribe:
+ <http://lists.example.com/listinfo/xtest@example.com>,
+ <mailto:xtest-leave@example.com>
+ List-Archive: <http://lists.example.com/pipermail/xtest@example.com>
+ List-Help: <mailto:xtest-request@example.com?subject=help>
<BLANKLINE>
First post!
<BLANKLINE>
@@ -157,15 +164,18 @@ There's now one message in the digest mailbox, getting ready to be sent.
X-Mailman-Version: ...
Precedence: list
List-Id: <xtest.example.com>
- List-Unsubscribe:
- <http://lists.example.com/archives/listinfo/xtest@example.com>,
- <mailto:xtest-leave@example.com>
- List-Archive: <http://www.example.com/pipermail/xtest@example.com>
+ X-Message-ID-Hash: 4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB
List-Post: <mailto:xtest@example.com>
- List-Help: <mailto:xtest-request@example.com?subject=help>
List-Subscribe:
- <http://lists.example.com/archives/listinfo/xtest@example.com>,
+ <http://lists.example.com/listinfo/xtest@example.com>,
<mailto:xtest-join@example.com>
+ Archived-At:
+ http://lists.example.com/pipermail/4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB
+ List-Unsubscribe:
+ <http://lists.example.com/listinfo/xtest@example.com>,
+ <mailto:xtest-leave@example.com>
+ List-Archive: <http://lists.example.com/pipermail/xtest@example.com>
+ List-Help: <mailto:xtest-request@example.com?subject=help>
<BLANKLINE>
First post!
<BLANKLINE>
diff --git a/mailman/docs/requests.txt b/mailman/docs/requests.txt
index c67a7e06e..0ebb9ff8d 100644
--- a/mailman/docs/requests.txt
+++ b/mailman/docs/requests.txt
@@ -424,7 +424,6 @@ queue when the message is held.
>>> mlist.admin_immed_notify = True
>>> # XXX This will almost certainly change once we've worked out the web
>>> # space layout for mailing lists now.
- >>> mlist.web_page_url = u'http://www.example.com/'
>>> id_4 = moderator.hold_subscription(mlist,
... u'cperson@example.org', u'Claire Person',
... u'{NONE}zyxcba', DeliveryMode.regular, u'en')
@@ -451,7 +450,7 @@ queue when the message is held.
<BLANKLINE>
At your convenience, visit:
<BLANKLINE>
- http://www.example.com/admindb/alist@example.com
+ http://lists.example.com/admindb/alist@example.com
<BLANKLINE>
to process the request.
<BLANKLINE>
@@ -548,7 +547,7 @@ subscription and the fact that they may need to approve it.
<BLANKLINE>
At your convenience, visit:
<BLANKLINE>
- http://www.example.com/admindb/alist@example.com
+ http://lists.example.com/admindb/alist@example.com
<BLANKLINE>
to process the request.
<BLANKLINE>
@@ -604,7 +603,7 @@ The welcome message is sent to the person who just subscribed.
<BLANKLINE>
General information about the mailing list is at:
<BLANKLINE>
- http://www.example.com/listinfo/alist@example.com
+ http://lists.example.com/listinfo/alist@example.com
<BLANKLINE>
If you ever want to unsubscribe or change your options (eg, switch to
or from digest mode, change your password, etc.), visit your
@@ -711,7 +710,7 @@ unsubscription holds can send the list's moderators an immediate notification.
<BLANKLINE>
At your convenience, visit:
<BLANKLINE>
- http://www.example.com/admindb/alist@example.com
+ http://lists.example.com/admindb/alist@example.com
<BLANKLINE>
to process the request.
<BLANKLINE>
diff --git a/mailman/interfaces/archiver.py b/mailman/interfaces/archiver.py
index 0159567cc..c69b13427 100644
--- a/mailman/interfaces/archiver.py
+++ b/mailman/interfaces/archiver.py
@@ -33,8 +33,6 @@ class IArchiver(Interface):
name = Attribute('The name of this archiver')
- is_enabled = Attribute('True if this archiver is enabled.')
-
def list_url(mlist):
"""Return the url to the top of the list's archive.
diff --git a/mailman/interfaces/mta.py b/mailman/interfaces/mta.py
new file mode 100644
index 000000000..a8f55f961
--- /dev/null
+++ b/mailman/interfaces/mta.py
@@ -0,0 +1,34 @@
+# Copyright (C) 2009 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/>.
+
+"""Interface for mail transport agent integration."""
+
+from zope.interface import Interface, Attribute
+
+
+
+class IMailTransportAgent(Interface):
+ """Interface to the MTA."""
+
+ def create(mlist):
+ """Tell the MTA that the mailing list was created."""
+
+ def delete(mlist):
+ """Tell the MTA that the mailing list was deleted."""
+
+ def regenerate():
+ """Regenerate the full aliases file."""
diff --git a/mailman/mta/Manual.py b/mailman/mta/Manual.py
deleted file mode 100644
index d0e7c359a..000000000
--- a/mailman/mta/Manual.py
+++ /dev/null
@@ -1,139 +0,0 @@
-# Copyright (C) 2001-2009 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/>.
-
-"""Creation/deletion hooks for manual /etc/aliases files."""
-
-import sys
-import email.Utils
-
-from cStringIO import StringIO
-
-from mailman import Message
-from mailman import Utils
-from mailman.MTA.Utils import makealiases
-from mailman.configuration import config
-from mailman.i18n import _
-from mailman.queue import Switchboard
-
-
-
-# no-ops for interface compliance
-def makelock():
- class Dummy:
- def lock(self):
- pass
- def unlock(self, unconditionally=False):
- pass
- return Dummy()
-
-
-def clear():
- pass
-
-
-
-# nolock argument is ignored, but exists for interface compliance
-def create(mlist, cgi=False, nolock=False, quiet=False):
- if mlist is None:
- return
- listname = mlist.internal_name()
- fieldsz = len(listname) + len('-unsubscribe')
- if cgi:
- # If a list is being created via the CGI, the best we can do is send
- # an email message to mailman-owner requesting that the proper aliases
- # be installed.
- sfp = StringIO()
- if not quiet:
- print >> sfp, _("""\
-The mailing list '$listname' has been created via the through-the-web
-interface. In order to complete the activation of this mailing list, the
-proper /etc/aliases (or equivalent) file must be updated. The program
-'newaliases' may also have to be run.
-
-Here are the entries for the /etc/aliases file:
-""")
- outfp = sfp
- else:
- if not quiet:
- print _("""\
-To finish creating your mailing list, you must edit your /etc/aliases (or
-equivalent) file by adding the following lines, and possibly running the
-'newaliases' program:
-""")
- print _("""\
-## $listname mailing list""")
- outfp = sys.stdout
- # Common path
- for k, v in makealiases(mlist):
- print >> outfp, k + ':', ((fieldsz - len(k)) * ' '), v
- # If we're using the command line interface, we're done. For ttw, we need
- # to actually send the message to mailman-owner now.
- if not cgi:
- print >> outfp
- return
- siteowner = Utils.get_site_noreply()
- # Should this be sent in the site list's preferred language?
- msg = Message.UserNotification(
- siteowner, siteowner,
- _('Mailing list creation request for list $listname'),
- sfp.getvalue(), config.DEFAULT_SERVER_LANGUAGE)
- msg.send(mlist)
-
-
-
-def remove(mlist, cgi=False):
- listname = mlist.fqdn_listname
- fieldsz = len(listname) + len('-unsubscribe')
- if cgi:
- # If a list is being removed via the CGI, the best we can do is send
- # an email message to mailman-owner requesting that the appropriate
- # aliases be deleted.
- sfp = StringIO()
- print >> sfp, _("""\
-The mailing list '$listname' has been removed via the through-the-web
-interface. In order to complete the de-activation of this mailing list, the
-appropriate /etc/aliases (or equivalent) file must be updated. The program
-'newaliases' may also have to be run.
-
-Here are the entries in the /etc/aliases file that should be removed:
-""")
- outfp = sfp
- else:
- print _("""
-To finish removing your mailing list, you must edit your /etc/aliases (or
-equivalent) file by removing the following lines, and possibly running the
-'newaliases' program:
-
-## $listname mailing list""")
- outfp = sys.stdout
- # Common path
- for k, v in makealiases(mlist):
- print >> outfp, k + ':', ((fieldsz - len(k)) * ' '), v
- # If we're using the command line interface, we're done. For ttw, we need
- # to actually send the message to mailman-owner now.
- if not cgi:
- print >> outfp
- return
- siteowner = Utils.get_site_noreply()
- # Should this be sent in the site list's preferred language?
- msg = Message.UserNotification(
- siteowner, siteowner,
- _('Mailing list removal request for list $listname'),
- sfp.getvalue(), config.DEFAULT_SERVER_LANGUAGE)
- msg['Date'] = email.Utils.formatdate(localtime=True)
- outq = Switchboard(config.OUTQUEUE_DIR)
- outq.enqueue(msg, recips=[siteowner], nodecorate=True)
diff --git a/mailman/mta/Postfix.py b/mailman/mta/Postfix.py
deleted file mode 100644
index 901c21089..000000000
--- a/mailman/mta/Postfix.py
+++ /dev/null
@@ -1,411 +0,0 @@
-# Copyright (C) 2001-2009 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/>.
-
-"""Creation/deletion hooks for the Postfix MTA."""
-
-import os
-import grp
-import pwd
-import time
-import errno
-import logging
-
-from locknix.lockfile import Lock
-from stat import *
-
-from mailman import Utils
-from mailman.MTA.Utils import makealiases
-from mailman.configuration import config
-from mailman.i18n import _
-
-LOCKFILE = os.path.join(config.LOCK_DIR, 'creator')
-ALIASFILE = os.path.join(config.DATA_DIR, 'aliases')
-VIRTFILE = os.path.join(config.DATA_DIR, 'virtual-mailman')
-TRPTFILE = os.path.join(config.DATA_DIR, 'transport')
-
-log = logging.getLogger('mailman.error')
-
-
-
-def _update_maps():
- msg = 'command failed: %s (status: %s, %s)'
- if config.USE_LMTP:
- tcmd = config.POSTFIX_MAP_CMD + ' ' + TRPTFILE
- status = (os.system(tcmd) >> 8) & 0xff
- if status:
- errstr = os.strerror(status)
- log.error(msg, tcmd, status, errstr)
- raise RuntimeError(msg % (tcmd, status, errstr))
- acmd = config.POSTFIX_ALIAS_CMD + ' ' + ALIASFILE
- status = (os.system(acmd) >> 8) & 0xff
- if status:
- errstr = os.strerror(status)
- log.error(msg, acmd, status, errstr)
- raise RuntimeError(msg % (acmd, status, errstr))
- if os.path.exists(VIRTFILE):
- vcmd = config.POSTFIX_MAP_CMD + ' ' + VIRTFILE
- status = (os.system(vcmd) >> 8) & 0xff
- if status:
- errstr = os.strerror(status)
- log.error(msg, vcmd, status, errstr)
- raise RuntimeError(msg % (vcmd, status, errstr))
-
-
-
-def _zapfile(filename):
- # Truncate the file w/o messing with the file permissions, but only if it
- # already exists.
- if os.path.exists(filename):
- fp = open(filename, 'w')
- fp.close()
-
-
-def clear():
- _zapfile(ALIASFILE)
- _zapfile(VIRTFILE)
- _zapfile(TRPTFILE)
-
-
-
-def _addlist(mlist, fp):
- # Set up the mailman-loop address
- loopaddr = Utils.ParseEmail(Utils.get_site_noreply())[0]
- loopmbox = os.path.join(config.DATA_DIR, 'owner-bounces.mbox')
- # Seek to the end of the text file, but if it's empty write the standard
- # disclaimer, and the loop catch address.
- fp.seek(0, 2)
- if not fp.tell():
- print >> fp, """\
-# This file is generated by Mailman, and is kept in sync with the
-# binary hash file aliases.db. 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.
-"""
- print >> fp, '# The ultimate loop stopper address'
- print >> fp, '%s: %s' % (loopaddr, loopmbox)
- print >> fp
- # Bootstrapping. bin/genaliases must be run before any lists are created,
- # but if no lists exist yet then mlist is None. The whole point of the
- # exercise is to get the minimal aliases.db file into existance.
- if mlist is None:
- return
- listname = mlist.internal_name()
- hostname = mlist.host_name
- fieldsz = len(listname) + len('-unsubscribe')
- # The text file entries get a little extra info
- print >> fp, '# STANZA START: %s@%s' % (listname, hostname)
- print >> fp, '# CREATED:', time.ctime(time.time())
- # Now add all the standard alias entries
- for k, v in makealiases(mlist):
- l = len(k)
- if hostname in config.POSTFIX_STYLE_VIRTUAL_DOMAINS:
- k += config.POSTFIX_VIRTUAL_SEPARATOR + hostname
- # Format the text file nicely
- print >> fp, k + ':', ((fieldsz - l) * ' ') + v
- # Finish the text file stanza
- print >> fp, '# STANZA END: %s@%s' % (listname, hostname)
- print >> fp
-
-
-
-def _addvirtual(mlist, fp):
- listname = mlist.internal_name()
- fieldsz = len(listname) + len('-unsubscribe')
- hostname = mlist.host_name
- # Set up the mailman-loop address
- loopaddr = mlist.no_reply_address
- loopdest = Utils.ParseEmail(loopaddr)[0]
- # Seek to the end of the text file, but if it's empty write the standard
- # disclaimer, and the loop catch address.
- fp.seek(0, 2)
- if not fp.tell():
- print >> fp, """\
-# This file is generated by Mailman, and is kept in sync with the binary hash
-# file virtual-mailman.db. 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.
-#
-# Note that you should already have this virtual domain set up properly in
-# your Postfix installation. See README.POSTFIX for details.
-
-# LOOP ADDRESSES START
-%s\t%s
-# LOOP ADDRESSES END
-""" % (loopaddr, loopdest)
- # The text file entries get a little extra info
- print >> fp, '# STANZA START: %s@%s' % (listname, hostname)
- print >> fp, '# CREATED:', time.ctime(time.time())
- # Now add all the standard alias entries
- for k, v in makealiases(mlist):
- fqdnaddr = '%s@%s' % (k, hostname)
- l = len(k)
- # Format the text file nicely
- if hostname in config.POSTFIX_STYLE_VIRTUAL_DOMAINS:
- k += config.POSTFIX_VIRTUAL_SEPARATOR + hostname
- print >> fp, fqdnaddr, ((fieldsz - l) * ' '), k
- # Finish the text file stanza
- print >> fp, '# STANZA END: %s@%s' % (listname, hostname)
- print >> fp
-
-
-
-# Blech.
-def _check_for_virtual_loopaddr(mlist, filename, func):
- loopaddr = mlist.no_reply_address
- loopdest = Utils.ParseEmail(loopaddr)[0]
- if func is _addtransport:
- loopdest = 'local:' + loopdest
- infp = open(filename)
- outfp = open(filename + '.tmp', 'w')
- try:
- # Find the start of the loop address block
- while True:
- line = infp.readline()
- if not line:
- break
- outfp.write(line)
- if line.startswith('# LOOP ADDRESSES START'):
- break
- # Now see if our domain has already been written
- while True:
- line = infp.readline()
- if not line:
- break
- if line.startswith('# LOOP ADDRESSES END'):
- # It hasn't
- print >> outfp, '%s\t%s' % (loopaddr, loopdest)
- outfp.write(line)
- break
- elif line.startswith(loopaddr):
- # We just found it
- outfp.write(line)
- break
- else:
- # This isn't our loop address, so spit it out and continue
- outfp.write(line)
- outfp.writelines(infp.readlines())
- finally:
- infp.close()
- outfp.close()
- os.rename(filename + '.tmp', filename)
-
-
-
-def _addtransport(mlist, fp):
- # Set up the mailman-loop address
- loopaddr = mlist.no_reply_address
- loopdest = Utils.ParseEmail(loopaddr)[0]
- # create/add postfix transport file for mailman
- fp.seek(0, 2)
- if not fp.tell():
- print >> fp, """\
-# This file is generated by Mailman, and is kept in sync with the
-# binary hash file transport.db. 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.
-
-# LOOP ADDRESSES START
-%s\tlocal:%s
-# LOOP ADDRESSES END
-""" % (loopaddr, loopdest)
- # List LMTP_ONLY_DOMAINS
- if config.LMTP_ONLY_DOMAINS:
- print >> fp, '# LMTP ONLY DOMAINS START'
- for dom in config.LMTP_ONLY_DOMAINS:
- print >> fp, '%s\tlmtp:%s:%s' % (dom,
- config.LMTP_HOST,
- config.LMTP_PORT)
- print >> fp, '# LMTP ONLY DOMAINS END\n'
- listname = mlist.internal_name()
- hostname = mlist.host_name
- # No need of individual local part if the domain is LMTP only
- if hostname in config.LMTP_ONLY_DOMAINS:
- return
- fieldsz = len(listname) + len(hostname) + len('-unsubscribe') + 1
- # The text file entries get a little extra info
- print >> fp, '# STANZA START: %s@%s' % (listname, hostname)
- print >> fp, '# CREATED:', time.ctime(time.time())
- # Now add transport entries
- for k, v in makealiases(mlist):
- l = len(k + hostname) + 1
- print >> fp, '%s@%s' % (k, hostname), ((fieldsz - l) * ' ')\
- + 'lmtp:%s:%s' % (config.LMTP_HOST, config.LMTP_PORT)
- #
- print >> fp, '# STANZA END: %s@%s' % (listname, hostname)
- print >> fp
-
-
-
-def _do_create(mlist, textfile, func):
- # Crack open the plain text file
- try:
- fp = open(textfile, 'r+')
- except IOError, e:
- if e.errno <> errno.ENOENT:
- raise
- fp = open(textfile, 'w+')
- try:
- func(mlist, fp)
- finally:
- fp.close()
- # Now double check the virtual plain text file
- if func in (_addvirtual, _addtransport):
- _check_for_virtual_loopaddr(mlist, textfile, func)
-
-
-def create(mlist, cgi=False, nolock=False, quiet=False):
- # Acquire the global list database lock. quiet flag is ignored.
- lock = None
- if not nolock:
- # XXX FIXME
- lock = makelock()
- lock.lock()
- # Do the aliases file, which always needs to be done
- try:
- if config.USE_LMTP:
- _do_create(mlist, TRPTFILE, _addtransport)
- _do_create(None, ALIASFILE, _addlist)
- else:
- _do_create(mlist, ALIASFILE, _addlist)
- if mlist.host_name in config.POSTFIX_STYLE_VIRTUAL_DOMAINS:
- _do_create(mlist, VIRTFILE, _addvirtual)
- _update_maps()
- finally:
- if lock:
- lock.unlock(unconditionally=True)
-
-
-
-def _do_remove(mlist, textfile):
- listname = mlist.internal_name()
- hostname = mlist.host_name
- # Now do our best to filter out the proper stanza from the text file.
- # The text file better exist!
- outfp = None
- try:
- infp = open(textfile)
- except IOError, e:
- if e.errno <> errno.ENOENT:
- raise
- # Otherwise, there's no text file to filter so we're done.
- return
- try:
- outfp = open(textfile + '.tmp', 'w')
- filteroutp = False
- start = '# STANZA START: %s@%s' % (listname, hostname)
- end = '# STANZA END: %s@%s' % (listname, hostname)
- while 1:
- line = infp.readline()
- if not line:
- break
- # If we're filtering out a stanza, just look for the end marker and
- # filter out everything in between. If we're not in the middle of
- # filtering out a stanza, we're just looking for the proper begin
- # marker.
- if filteroutp:
- if line.strip() == end:
- filteroutp = False
- # Discard the trailing blank line, but don't worry if
- # we're at the end of the file.
- infp.readline()
- # Otherwise, ignore the line
- else:
- if line.strip() == start:
- # Filter out this stanza
- filteroutp = True
- else:
- outfp.write(line)
- # Close up shop, and rotate the files
- finally:
- infp.close()
- outfp.close()
- os.rename(textfile+'.tmp', textfile)
-
-
-def remove(mlist, cgi=False):
- # Acquire the global list database lock
- with Lock(LOCKFILE):
- if config.USE_LMTP:
- _do_remove(mlist, TRPTFILE)
- else:
- _do_remove(mlist, ALIASFILE)
- if mlist.host_name in config.POSTFIX_STYLE_VIRTUAL_DOMAINS:
- _do_remove(mlist, VIRTFILE)
- # Regenerate the alias and map files
- _update_maps()
- config.db.commit()
-
-
-
-def checkperms(state):
- targetmode = S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP
- for file in ALIASFILE, VIRTFILE, TRPTFILE:
- if state.VERBOSE:
- print _('checking permissions on %(file)s')
- stat = None
- try:
- stat = os.stat(file)
- except OSError, e:
- if e.errno <> errno.ENOENT:
- raise
- if stat and (stat[ST_MODE] & targetmode) <> targetmode:
- state.ERRORS += 1
- octmode = oct(stat[ST_MODE])
- print _('%(file)s permissions must be 066x (got %(octmode)s)'),
- if state.FIX:
- print _('(fixing)')
- os.chmod(file, stat[ST_MODE] | targetmode)
- else:
- print
- # Make sure the corresponding .db files are owned by the Mailman user.
- # We don't need to check the group ownership of the file, since
- # check_perms checks this itself.
- dbfile = file + '.db'
- stat = None
- try:
- stat = os.stat(dbfile)
- except OSError, e:
- if e.errno <> errno.ENOENT:
- raise
- continue
- if state.VERBOSE:
- print _('checking ownership of %(dbfile)s')
- user = config.MAILMAN_USER
- ownerok = stat[ST_UID] == pwd.getpwnam(user)[2]
- if not ownerok:
- try:
- owner = pwd.getpwuid(stat[ST_UID])[0]
- except KeyError:
- owner = 'uid %d' % stat[ST_UID]
- print _('%(dbfile)s owned by %(owner)s (must be owned by %(user)s'),
- state.ERRORS += 1
- if state.FIX:
- print _('(fixing)')
- uid = pwd.getpwnam(user)[2]
- gid = grp.getgrnam(config.MAILMAN_GROUP)[2]
- os.chown(dbfile, uid, gid)
- else:
- print
- if stat and (stat[ST_MODE] & targetmode) <> targetmode:
- state.ERRORS += 1
- octmode = oct(stat[ST_MODE])
- print _('%(dbfile)s permissions must be 066x (got %(octmode)s)'),
- if state.FIX:
- print _('(fixing)')
- os.chmod(dbfile, stat[ST_MODE] | targetmode)
- else:
- print
diff --git a/mailman/mta/postfix.py b/mailman/mta/postfix.py
new file mode 100644
index 000000000..4b92d5789
--- /dev/null
+++ b/mailman/mta/postfix.py
@@ -0,0 +1,122 @@
+# Copyright (C) 2001-2009 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/>.
+
+"""Creation/deletion hooks for the Postfix MTA."""
+
+__metaclass__ = type
+__all__ = [
+ 'LMTP',
+ ]
+
+
+import os
+import grp
+import pwd
+import time
+import errno
+import logging
+import datetime
+
+from locknix.lockfile import Lock
+from zope.interface import implements
+
+from mailman import Defaults
+from mailman import Utils
+from mailman.config import config
+from mailman.interfaces.mta import IMailTransportAgent
+from mailman.i18n import _
+
+log = logging.getLogger('mailman.error')
+
+LOCKFILE = os.path.join(config.LOCK_DIR, 'mta')
+SUBDESTINATIONS = (
+ 'bounces', 'confirm', 'join', 'leave',
+ 'owner', 'request', 'subscribe', 'unsubscribe',
+ )
+
+
+
+class LMTP:
+ """Connect Mailman to Postfix via LMTP."""
+
+ implements(IMailTransportAgent)
+
+ def create(self, mlist):
+ """See `IMailTransportAgent`."""
+ # Acquire a lock file to prevent other processes from racing us here.
+ with Lock(LOCKFILE):
+ # We can ignore the mlist argument because for LMTP delivery, we
+ # just generate the entire file every time.
+ self._do_write_file()
+
+ delete = create
+
+ def regenerate(self):
+ """See `IMailTransportAgent`."""
+ # Acquire a lock file to prevent other processes from racing us here.
+ with Lock(LOCKFILE):
+ self._do_write_file()
+
+ def _do_write_file(self):
+ """Do the actual file writes for list creation."""
+ # Open up the new alias text file.
+ path = os.path.join(config.DATA_DIR, 'postfix_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 config.db.list_manager.mailing_lists:
+ by_domain.setdefault(mailing_list.host_name, []).append(
+ mailing_list.list_name)
+ with open(path + '.new', 'w') as fp:
+ print >> fp, """\
+# AUTOMATICALLY GENERATED BY MAILMAN ON {0}
+#
+# This file is generated by Mailman, and is kept in sync with the binary hash
+# 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(datetime.datetime.now().replace(microsecond=0))
+ 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,
+ # 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))
+ print >> fp
+ # Move the temporary file into place, then generate the new .db file.
+ os.rename(path + '.new', path)
+ # Now that the new aliases file has been written, we must tell Postfix
+ # to generate a new .db file.
+ command = Defaults.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))
diff --git a/mailman/options.py b/mailman/options.py
index 32f80d426..8d752bfb5 100644
--- a/mailman/options.py
+++ b/mailman/options.py
@@ -24,6 +24,7 @@ __all__ = [
]
+import os
import sys
from copy import copy
@@ -108,7 +109,11 @@ class Options:
from the configuration files.
:type propagate_logs: bool or None.
"""
- initialize(self.options.config, propagate_logs=propagate_logs)
+ # Fall back to using the environment variable if -C is not given.
+ config_file = (os.getenv('MAILMAN_CONFIG_FILE')
+ if self.options.config is None
+ else self.options.config)
+ initialize(config_file, propagate_logs=propagate_logs)
self.sanity_check()
diff --git a/mailman/pipeline/cook_headers.py b/mailman/pipeline/cook_headers.py
index a37a92a69..18000e01a 100644
--- a/mailman/pipeline/cook_headers.py
+++ b/mailman/pipeline/cook_headers.py
@@ -216,9 +216,7 @@ def process(mlist, msg, msgdata):
headers['List-Post'] = '<mailto:%s>' % mlist.posting_address
# Add RFC 2369 and 5064 archiving headers, if archiving is enabled.
if mlist.archive:
- for archiver in get_plugins('mailman.archiver'):
- if not archiver.is_enabled:
- continue
+ for archiver in config.archivers:
headers['List-Archive'] = '<%s>' % archiver.list_url(mlist)
permalink = archiver.permalink(mlist, msg)
if permalink is not None:
diff --git a/mailman/pipeline/decorate.py b/mailman/pipeline/decorate.py
index 3059bde38..4e4b1b97b 100644
--- a/mailman/pipeline/decorate.py
+++ b/mailman/pipeline/decorate.py
@@ -202,10 +202,9 @@ def decorate(mlist, template, extradict=None):
list_name = mlist.list_name,
fqdn_listname = mlist.fqdn_listname,
host_name = mlist.host_name,
- web_page_url = mlist.web_page_url,
+ listinfo_page = mlist.script_url('listinfo'),
description = mlist.description,
info = mlist.info,
- cgiext = Defaults.CGIEXT,
)
if extradict is not None:
d.update(extradict)
diff --git a/mailman/pipeline/docs/acknowledge.txt b/mailman/pipeline/docs/acknowledge.txt
index d1206b6f3..aabd8196a 100644
--- a/mailman/pipeline/docs/acknowledge.txt
+++ b/mailman/pipeline/docs/acknowledge.txt
@@ -11,7 +11,6 @@ acknowledgment.
>>> mlist.preferred_language = u'en'
>>> # XXX This will almost certainly change once we've worked out the web
>>> # space layout for mailing lists now.
- >>> mlist.web_page_url = u'http://lists.example.com/'
>>> # Ensure that the virgin queue is empty, since we'll be checking this
>>> # for new auto-response messages.
diff --git a/mailman/pipeline/docs/cook-headers.txt b/mailman/pipeline/docs/cook-headers.txt
index 985214079..eb1a4e6bc 100644
--- a/mailman/pipeline/docs/cook-headers.txt
+++ b/mailman/pipeline/docs/cook-headers.txt
@@ -12,9 +12,6 @@ is getting sent through the system. We'll take things one-by-one.
>>> mlist.subject_prefix = u''
>>> mlist.include_list_post_header = False
>>> mlist.archive = True
- >>> # XXX This will almost certainly change once we've worked out the web
- >>> # space layout for mailing lists now.
- >>> mlist.web_page_url = u'http://lists.example.com/'
Saving the original sender
@@ -183,7 +180,6 @@ But normally, a list will include these headers.
>>> mlist.include_rfc2369_headers = True
>>> mlist.include_list_post_header = True
>>> mlist.preferred_language = u'en'
- >>> config.archivers['pipermail'].is_enabled = True
>>> msg = message_from_string("""\
... From: aperson@example.com
... Message-ID: <12345>
@@ -192,7 +188,7 @@ But normally, a list will include these headers.
>>> process(mlist, msg, {})
>>> list_headers(msg)
---start---
- List-Archive: <http://www.example.com/pipermail/_xtest@example.com>
+ List-Archive: <http://lists.example.com/pipermail/_xtest@example.com>
List-Help: <mailto:_xtest-request@example.com?subject=help>
List-Id: <_xtest.example.com>
List-Post: <mailto:_xtest@example.com>
@@ -213,7 +209,7 @@ header.
>>> process(mlist, msg, {})
>>> list_headers(msg)
---start---
- List-Archive: <http://www.example.com/pipermail/_xtest@example.com>
+ List-Archive: <http://lists.example.com/pipermail/_xtest@example.com>
List-Help: <mailto:_xtest-request@example.com?subject=help>
List-Id: My test mailing list <_xtest.example.com>
List-Post: <mailto:_xtest@example.com>
@@ -252,7 +248,7 @@ List-Post header, which is reasonable for an announce only mailing list.
>>> process(mlist, msg, {})
>>> list_headers(msg)
---start---
- List-Archive: <http://www.example.com/pipermail/_xtest@example.com>
+ List-Archive: <http://lists.example.com/pipermail/_xtest@example.com>
List-Help: <mailto:_xtest-request@example.com?subject=help>
List-Id: My test mailing list <_xtest.example.com>
List-Subscribe: <http://lists.example.com/listinfo/_xtest@example.com>,
diff --git a/mailman/pipeline/docs/digests.txt b/mailman/pipeline/docs/digests.txt
index 7ef82382f..e94f9912f 100644
--- a/mailman/pipeline/docs/digests.txt
+++ b/mailman/pipeline/docs/digests.txt
@@ -9,7 +9,6 @@ digests, although only two are currently supported: MIME digests and RFC 1153
>>> from mailman.pipeline.to_digest import process
>>> mlist = config.db.list_manager.create(u'_xtest@example.com')
>>> mlist.preferred_language = u'en'
- >>> mlist.web_page_url = u'http://www.example.com/'
>>> mlist.real_name = u'XTest'
>>> mlist.subject_prefix = u'[_XTest] '
>>> mlist.one_last_digest = set()
@@ -126,7 +125,7 @@ digest and an RFC 1153 plain text digest. The size threshold is in KB.
_xtest@example.com
<BLANKLINE>
To subscribe or unsubscribe via the World Wide Web, visit
- http://www.example.com/listinfo/_xtest@example.com
+ http://lists.example.com/listinfo/_xtest@example.com
or, via email, send a message with subject or body 'help' to
_xtest-request@example.com
<BLANKLINE>
@@ -276,7 +275,7 @@ digest and an RFC 1153 plain text digest. The size threshold is in KB.
_xtest@example.com
<BLANKLINE>
To subscribe or unsubscribe via the World Wide Web, visit
- http://www.example.com/listinfo/_xtest@example.com
+ http://lists.example.com/listinfo/_xtest@example.com
or, via email, send a message with subject or body 'help' to
_xtest-request@example.com
<BLANKLINE>
@@ -464,7 +463,7 @@ Set the digest threshold to zero so that the digests will be sent immediately.
_xtest@example.com
<BLANKLINE>
Pour vous (d=E9s)abonner par le web, consultez
- http://www.example.com/listinfo/_xtest@example.com
+ http://lists.example.com/listinfo/_xtest@example.com
<BLANKLINE>
ou, par courriel, envoyez un message avec =AB=A0help=A0=BB dans le corps ou
dans le sujet =E0
diff --git a/mailman/pipeline/docs/replybot.txt b/mailman/pipeline/docs/replybot.txt
index f9f824e4e..f3c3281b3 100644
--- a/mailman/pipeline/docs/replybot.txt
+++ b/mailman/pipeline/docs/replybot.txt
@@ -9,7 +9,6 @@ message or the amount of time since the last auto-response.
>>> from mailman.pipeline.replybot import process
>>> mlist = config.db.list_manager.create(u'_xtest@example.com')
>>> mlist.real_name = u'XTest'
- >>> mlist.web_page_url = u'http://www.example.com/'
>>> # Ensure that the virgin queue is empty, since we'll be checking this
>>> # for new auto-response messages.
diff --git a/mailman/queue/__init__.py b/mailman/queue/__init__.py
index 52686b3ab..f83fd46e9 100644
--- a/mailman/queue/__init__.py
+++ b/mailman/queue/__init__.py
@@ -378,7 +378,7 @@ class Runner:
# them out of our sight.
#
# Find out which mailing list this message is destined for.
- listname = msgdata.get('listname')
+ listname = unicode(msgdata.get('listname'))
mlist = config.db.list_manager.get(listname)
if mlist is None:
elog.error('Dequeuing message destined for missing list: %s',
diff --git a/mailman/queue/archive.py b/mailman/queue/archive.py
index 69ec46f4b..c97bd86fb 100644
--- a/mailman/queue/archive.py
+++ b/mailman/queue/archive.py
@@ -24,15 +24,17 @@ __all__ = [
import os
+import sys
import time
import logging
from datetime import datetime
from email.Utils import parsedate_tz, mktime_tz, formatdate
+from lazr.config import as_boolean
from locknix.lockfile import Lock
from mailman import Defaults
-from mailman.core.plugins import get_plugins
+from mailman.config import config
from mailman.queue import Runner
log = logging.getLogger('mailman.error')
@@ -80,11 +82,10 @@ class ArchiveRunner(Runner):
msg['X-List-Received-Date'] = received_time
# While a list archiving lock is acquired, archive the message.
with Lock(os.path.join(mlist.data_path, 'archive.lck')):
- for archive_factory in get_plugins('mailman.archiver'):
- # A problem in one archiver should not prevent any other
- # archiver from running.
+ for archiver in config.archivers:
+ # A problem in one archiver should not prevent other archivers
+ # from running.
try:
- archive = archive_factory()
- archive.archive_message(mlist, msg)
+ archiver.archive_message(mlist, msg)
except Exception:
- log.exception('Broken archiver: %s' % archive.name)
+ log.exception('Broken archiver: %s' % archiver.name)
diff --git a/mailman/queue/docs/archiver.txt b/mailman/queue/docs/archiver.txt
index ed7c26d45..601857cd9 100644
--- a/mailman/queue/docs/archiver.txt
+++ b/mailman/queue/docs/archiver.txt
@@ -6,8 +6,7 @@ interface. By default, there's a Pipermail archiver.
>>> from mailman.app.lifecycle import create_list
>>> mlist = create_list(u'test@example.com')
- >>> mlist.web_page_url = u'http://www.example.com/'
- >>> config.db.commit()
+ >>> commit()
>>> msg = message_from_string("""\
... From: aperson@example.com
diff --git a/mailman/queue/docs/incoming.txt b/mailman/queue/docs/incoming.txt
index 22b32d828..7402d2aaf 100644
--- a/mailman/queue/docs/incoming.txt
+++ b/mailman/queue/docs/incoming.txt
@@ -87,7 +87,6 @@ pipeline queue.
>>> fp.seek(0, 2)
>>> mlist.emergency = True
- >>> mlist.web_page_url = u'http://archives.example.com/'
>>> inject_message(mlist, msg)
>>> file_pos = fp.tell()
>>> incoming.run()
diff --git a/mailman/queue/lmtp.py b/mailman/queue/lmtp.py
index f0895ee1f..1f45732a3 100644
--- a/mailman/queue/lmtp.py
+++ b/mailman/queue/lmtp.py
@@ -143,13 +143,14 @@ class LMTPRunner(Runner, smtpd.SMTPServer):
# Parse the message data. If there are any defects in the
# message, reject it right away; it's probably spam.
msg = email.message_from_string(data, Message)
+ msg.original_size = len(data)
if msg.defects:
return ERR_501
msg['X-MailFrom'] = mailfrom
except Exception, e:
elog.exception('LMTP message parsing')
config.db.abort()
- return CRLF.join([ERR_451 for to in rcpttos])
+ return CRLF.join(ERR_451 for to in rcpttos)
# RFC 2033 requires us to return a status code for every recipient.
status = []
# Now for each address in the recipients, parse the address to first
@@ -169,7 +170,8 @@ class LMTPRunner(Runner, smtpd.SMTPServer):
# The recipient is a valid mailing list; see if it's a valid
# sub-address, and if so, enqueue it.
queue = None
- msgdata = dict(listname=listname)
+ msgdata = dict(listname=listname,
+ original_size=msg.original_size)
if subaddress in ('bounces', 'admin'):
queue = 'bounce'
elif subaddress == 'confirm':
diff --git a/mailman/rules/docs/emergency.txt b/mailman/rules/docs/emergency.txt
index e71566853..9d80fdb40 100644
--- a/mailman/rules/docs/emergency.txt
+++ b/mailman/rules/docs/emergency.txt
@@ -6,7 +6,6 @@ list are held for moderator approval.
>>> from mailman.app.lifecycle import create_list
>>> mlist = create_list(u'_xtest@example.com')
- >>> mlist.web_page_url = u'http://www.example.com/'
>>> msg = message_from_string("""\
... From: aperson@example.com
... To: _xtest@example.com
diff --git a/mailman/testing/testing.cfg b/mailman/testing/testing.cfg
index ff9bb89da..107db86ed 100644
--- a/mailman/testing/testing.cfg
+++ b/mailman/testing/testing.cfg
@@ -19,6 +19,7 @@
[mta]
smtp_port: 9025
+incoming: mailman.testing.mta.FakeMTA
[qrunner.archive]
max_restarts: 1
@@ -56,19 +57,25 @@ max_restarts: 1
[qrunner.virgin]
max_restarts: 1
+[archiver.prototype]
+enable: yes
+
[archiver.mail_archive]
+enable: yes
base_url: http://go.mail-archive.dev/
recipient: archive@mail-archive.dev
[archiver.pipermail]
+enable: yes
base_url: http://www.example.com/pipermail/$listname
[archiver.mhonarc]
+enable: yes
command: /bin/echo "/usr/bin/mhonarc -add -dbfile $PRIVATE_ARCHIVE_FILE_DIR/${listname}.mbox/mhonarc.db -outdir $VAR_DIR/mhonarc/${listname} -stderr $LOG_DIR/mhonarc -stdout $LOG_DIR/mhonarc -spammode -umask 022"
[domain.example_dot_com]
email_host: example.com
-base_url: http://www.example.com
+base_url: http://lists.example.com
contact_address: postmaster@example.com
[language.ja]
diff --git a/setup.py b/setup.py
index cd1da403f..adefd459c 100644
--- a/setup.py
+++ b/setup.py
@@ -90,20 +90,12 @@ case second `m'. Any other spelling is incorrect.""",
include_package_data = True,
entry_points = {
'console_scripts': list(scripts),
- 'mailman.archiver' : [
- 'mail-archive = mailman.archiving.mailarchive:MailArchive',
- 'mhonarc = mailman.archiving.mhonarc:MHonArc',
- 'pipermail = mailman.archiving.pipermail:Pipermail',
- 'prototype = mailman.archiving.prototype:Prototype',
- ],
- 'mailman.scrubber' : 'stock = mailman.archiving.pipermail:Pipermail',
'mailman.commands' : list(commands),
'mailman.database' : 'stock = mailman.database:StockDatabase',
- 'mailman.mta' : 'stock = mailman.MTA:Manual',
- 'mailman.styles' : 'default = mailman.core.styles:DefaultStyle',
- 'mailman.mta' : 'stock = mailman.MTA:Manual',
- 'mailman.rules' : 'default = mailman.rules:initialize',
'mailman.handlers' : 'default = mailman.pipeline:initialize',
+ 'mailman.rules' : 'default = mailman.rules:initialize',
+ 'mailman.scrubber' : 'stock = mailman.archiving.pipermail:Pipermail',
+ 'mailman.styles' : 'default = mailman.core.styles:DefaultStyle',
},
install_requires = [
'lazr.config',