summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbwarsaw2006-07-08 17:58:13 +0000
committerbwarsaw2006-07-08 17:58:13 +0000
commitf321ff8f419284c32f7eea4e06c83212bccef6b0 (patch)
tree7e1d1e1a1b8b81a48d86afb5c47eb039529993ac
parent7a94dcd001240e0c06cc4b50017b8bfd097d9ff4 (diff)
downloadmailman-f321ff8f419284c32f7eea4e06c83212bccef6b0.tar.gz
mailman-f321ff8f419284c32f7eea4e06c83212bccef6b0.tar.zst
mailman-f321ff8f419284c32f7eea4e06c83212bccef6b0.zip
-rw-r--r--Mailman/Archiver/Archiver.py17
-rw-r--r--Mailman/Bouncer.py6
-rw-r--r--Mailman/Cgi/admin.py15
-rw-r--r--Mailman/Cgi/create.py150
-rw-r--r--Mailman/Cgi/listinfo.py11
-rw-r--r--Mailman/Cgi/rmlist.py2
-rw-r--r--Mailman/Commands/cmd_lists.py11
-rw-r--r--Mailman/Defaults.py.in84
-rw-r--r--Mailman/Deliverer.py2
-rw-r--r--Mailman/Errors.py27
-rw-r--r--Mailman/Handlers/SMTPDirect.py2
-rw-r--r--Mailman/MTA/Manual.py6
-rw-r--r--Mailman/MTA/Postfix.py9
-rw-r--r--Mailman/MailList.py60
-rw-r--r--Mailman/Message.py6
-rw-r--r--Mailman/Queue/BounceRunner.py13
-rw-r--r--Mailman/Queue/IncomingRunner.py2
-rw-r--r--Mailman/Queue/MaildirRunner.py4
-rw-r--r--Mailman/Queue/Runner.py2
-rw-r--r--Mailman/Site.py108
-rw-r--r--Mailman/Utils.py54
-rw-r--r--Mailman/Version.py10
-rw-r--r--Mailman/bin/add_members.py2
-rw-r--r--Mailman/bin/change_pw.py2
-rw-r--r--Mailman/bin/list_lists.py38
-rw-r--r--Mailman/bin/mailmanctl.py12
-rw-r--r--Mailman/bin/newlist.py96
-rw-r--r--Mailman/bin/owner.py1
-rw-r--r--Mailman/bin/rmlist.py5
-rw-r--r--Mailman/bin/update.py27
-rw-r--r--Mailman/configuration.py42
-rw-r--r--Mailman/i18n.py2
-rw-r--r--bin/withlist10
33 files changed, 370 insertions, 468 deletions
diff --git a/Mailman/Archiver/Archiver.py b/Mailman/Archiver/Archiver.py
index 0089ee4dc..8c8dcd238 100644
--- a/Mailman/Archiver/Archiver.py
+++ b/Mailman/Archiver/Archiver.py
@@ -12,8 +12,8 @@
#
# 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.
-
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ # USA.
"""Mixin class for putting new messages in the right place for archival.
@@ -30,12 +30,12 @@ import traceback
from cStringIO import StringIO
-from Mailman import Site
+from Mailman import Mailbox
from Mailman import Utils
from Mailman import mm_cfg
-from Mailman import Mailbox
-from Mailman.i18n import _
from Mailman.SafeDict import SafeDict
+from Mailman.configuration import config
+from Mailman.i18n import _
log = logging.getLogger('mailman.error')
@@ -125,7 +125,9 @@ class Archiver:
os.umask(omask)
def archive_dir(self):
- return Site.get_archpath(self.internal_name())
+ # Return the private archive directory
+ return os.path.join(config.PRIVATE_ARCHIVE_FILE_DIR,
+ self.fqdn_listname)
def ArchiveFileName(self):
"""The mbox name where messages are left for archive construction."""
@@ -225,7 +227,8 @@ class Archiver:
if mm_cfg.ARCHIVE_TO_MBOX == -1:
# Archiving is completely disabled, don't require the skeleton.
return
- pubdir = Site.get_archpath(self.internal_name(), public=True)
+ pubdir = os.path.join(config.PUBLIC_ARCHIVE_FILE_DIR,
+ self.fqdn_listname)
privdir = self.archive_dir()
pubmbox = pubdir + '.mbox'
privmbox = privdir + '.mbox'
diff --git a/Mailman/Bouncer.py b/Mailman/Bouncer.py
index 99774b398..1a5d10a8a 100644
--- a/Mailman/Bouncer.py
+++ b/Mailman/Bouncer.py
@@ -186,7 +186,6 @@ class Bouncer:
# it was of dubious value). However, we'll provide empty, strange, or
# meaningless strings for the unused %()s fields so that the language
# translators don't have to provide new templates.
- siteowner = Utils.get_site_email(self.host_name)
text = Utils.maketext(
'bounce.txt',
{'listname' : self.real_name,
@@ -195,11 +194,12 @@ class Bouncer:
'did' : _('disabled'),
'but' : '',
'reenable' : '',
- 'owneraddr': siteowner,
+ 'owneraddr': self.GetNoReplyEmail(),
}, mlist=self)
subject = _('Bounce action notification')
umsg = Message.UserNotification(self.GetOwnerEmail(),
- siteowner, subject,
+ self.GetNoReplyEmail(),
+ subject,
lang=self.preferred_language)
# BAW: Be sure you set the type before trying to attach, or you'll get
# a MultipartConversionError.
diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py
index aa1cfc846..3ba876340 100644
--- a/Mailman/Cgi/admin.py
+++ b/Mailman/Cgi/admin.py
@@ -205,7 +205,7 @@ def admin_overview(msg=''):
#
# This page should be displayed in the server's default language, which
# should have already been set.
- hostname = Utils.get_domain()
+ hostname = Utils.get_request_domain()
legend = _('%(hostname)s mailing lists - Admin Links')
# The html `document'
doc = Document()
@@ -222,11 +222,10 @@ def admin_overview(msg=''):
listnames.sort()
for name in listnames:
- mlist = MailList.MailList(name, lock=0)
+ mlist = MailList.MailList(name, lock=False)
if mlist.advertised:
- if mm_cfg.VIRTUAL_HOST_OVERVIEW and \
- mlist.web_page_url.find(hostname) == -1:
- # List is for different identity of this host - skip it.
+ if hostname not in mlist.web_page_url:
+ # This list is situated in a different virtual domain
continue
else:
advertised.append((mlist.GetScriptURL('admin'),
@@ -255,7 +254,7 @@ def admin_overview(msg=''):
])
creatorurl = Utils.ScriptURL('create')
- mailman_owner = Utils.get_site_email()
+ mailman_owner = Utils.get_site_noreply()
extra = msg and _('right ') or ''
welcome.extend([
_('''To visit the administrators configuration page for an
@@ -408,9 +407,7 @@ def show_results(mlist, doc, category, subcat, cgidata):
otherlinks.AddItem(Link(mlist.GetBaseArchiveURL(),
_('Go to list archives')).Format() +
'<br>&nbsp;<br>')
- # We do not allow through-the-web deletion of the site list!
- if mm_cfg.OWNERS_CAN_DELETE_THEIR_OWN_LISTS and \
- mlist.internal_name() <> mm_cfg.MAILMAN_SITE_LIST:
+ if mm_cfg.OWNERS_CAN_DELETE_THEIR_OWN_LISTS:
otherlinks.AddItem(Link(mlist.GetScriptURL('rmlist'),
_('Delete this mailing list')).Format() +
_(' (requires confirmation)<br>&nbsp;<br>'))
diff --git a/Mailman/Cgi/create.py b/Mailman/Cgi/create.py
index 767400569..da66c2f7e 100644
--- a/Mailman/Cgi/create.py
+++ b/Mailman/Cgi/create.py
@@ -25,16 +25,16 @@ import signal
import logging
from Mailman import Errors
-from Mailman import i18n
from Mailman import MailList
from Mailman import Message
-from Mailman import mm_cfg
-
+from Mailman import i18n
+from Mailman.configuration import config
from Mailman.htmlformat import *
# Set up i18n
_ = i18n._
-i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)
+i18n.set_language(config.DEFAULT_SERVER_LANGUAGE)
+__i18n_templates__ = True
log = logging.getLogger('mailman.error')
@@ -42,7 +42,7 @@ log = logging.getLogger('mailman.error')
def main():
doc = Document()
- doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)
+ doc.set_language(config.DEFAULT_SERVER_LANGUAGE)
cgidata = cgi.FieldStorage()
parts = Utils.GetPathPieces()
@@ -75,42 +75,35 @@ def main():
def process_request(doc, cgidata):
- # Lowercase the listname since this is treated as the "internal" name.
+ # Lowercase the listname since this is treated as the 'internal' name.
listname = cgidata.getvalue('listname', '').strip().lower()
owner = cgidata.getvalue('owner', '').strip()
try:
- autogen = int(cgidata.getvalue('autogen', '0'))
+ autogen = bool(cgidata.getvalue('autogen', '0'))
except ValueError:
- autogen = 0
+ autogen = False
try:
- notify = int(cgidata.getvalue('notify', '0'))
+ notify = bool(cgidata.getvalue('notify', '0'))
except ValueError:
- notify = 0
+ notify = False
try:
- moderate = int(cgidata.getvalue('moderate',
- mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION))
+ moderate = bool(cgidata.getvalue('moderate',
+ config.DEFAULT_DEFAULT_MEMBER_MODERATION))
except ValueError:
- moderate = mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION
+ moderate = config.DEFAULT_DEFAULT_MEMBER_MODERATION
password = cgidata.getvalue('password', '').strip()
confirm = cgidata.getvalue('confirm', '').strip()
auth = cgidata.getvalue('auth', '').strip()
- langs = cgidata.getvalue('langs', [mm_cfg.DEFAULT_SERVER_LANGUAGE])
+ langs = cgidata.getvalue('langs', [config.DEFAULT_SERVER_LANGUAGE])
if not isinstance(langs, list):
langs = [langs]
- # Sanity check
+ # Sanity checks
safelistname = Utils.websafe(listname)
if '@' in listname:
request_creation(doc, cgidata,
- _('List name must not include "@": %(safelistname)s'))
- return
- if Utils.list_exists(listname):
- # BAW: should we tell them the list already exists? This could be
- # used to mine/guess the existance of non-advertised lists. Then
- # again, that can be done in other ways already, so oh well.
- request_creation(doc, cgidata,
- _('List already exists: %(safelistname)s'))
+ _('List name must not include "@": $safelistname'))
return
if not listname:
request_creation(doc, cgidata,
@@ -120,17 +113,16 @@ def process_request(doc, cgidata):
request_creation(doc, cgidata,
_('You forgot to specify the list owner'))
return
-
if autogen:
if password or confirm:
request_creation(
doc, cgidata,
- _('''Leave the initial password (and confirmation) fields
+ _("""Leave the initial password (and confirmation) fields
blank if you want Mailman to autogenerate the list
- passwords.'''))
+ passwords."""))
return
password = confirm = Utils.MakeRandomPassword(
- mm_cfg.ADMIN_PASSWORD_LENGTH)
+ config.ADMIN_PASSWORD_LENGTH)
else:
if password <> confirm:
request_creation(doc, cgidata,
@@ -147,9 +139,9 @@ def process_request(doc, cgidata):
return
# The authorization password must be non-empty, and it must match either
# the list creation password or the site admin password
- ok = 0
+ ok = False
if auth:
- ok = Utils.check_global_password(auth, 0)
+ ok = Utils.check_global_password(auth, False)
if not ok:
ok = Utils.check_global_password(auth)
if not ok:
@@ -157,27 +149,26 @@ def process_request(doc, cgidata):
doc, cgidata,
_('You are not authorized to create new mailing lists'))
return
- # Make sure the web hostname matches one of our virtual domains
- hostname = Utils.get_domain()
- if mm_cfg.VIRTUAL_HOST_OVERVIEW and \
- not mm_cfg.VIRTUAL_HOSTS.has_key(hostname):
- safehostname = Utils.websafe(hostname)
+ # Make sure the url host name matches one of our virtual domains. Then
+ # calculate the list's posting address.
+ url_host = Utils.get_request_domain()
+ email_host = config.get_email_host(url_host)
+ if not email_host:
+ safehostname = Utils.websafe(email_host)
request_creation(doc, cgidata,
- _('Unknown virtual host: %(safehostname)s'))
+ _('Unknown virtual host: $safehostname'))
return
- emailhost = mm_cfg.VIRTUAL_HOSTS.get(hostname, mm_cfg.DEFAULT_EMAIL_HOST)
+ fqdn_listname = '%s@%s' % (listname, email_host)
# We've got all the data we need, so go ahead and try to create the list
- # See admin.py for why we need to set up the signal handler.
mlist = MailList.MailList()
-
- def sigterm_handler(signum, frame, mlist=mlist):
+ # See admin.py for why we need to set up the signal handler.
+ def sigterm_handler(signum, frame):
# Make sure the list gets unlocked...
mlist.Unlock()
# ...and ensure we exit, otherwise race conditions could cause us to
# enter MailList.Save() while we're in the unlocked state, and that
# could be bad!
sys.exit(0)
-
try:
# Install the emergency shutdown signal handler
signal.signal(signal.SIGTERM, sigterm_handler)
@@ -189,33 +180,29 @@ def process_request(doc, cgidata):
oldmask = os.umask(002)
try:
try:
- mlist.Create(listname, owner, pw, langs, emailhost)
+ mlist.Create(fqdn_listname, owner, pw, langs)
finally:
os.umask(oldmask)
except Errors.EmailAddressError, s:
- request_creation(doc, cgidata,
- _('Bad owner email address: %(s)s'))
+ request_creation(doc, cgidata, _('Bad owner email address: $s'))
return
except Errors.MMListAlreadyExistsError:
+ safelistname = Utils.websafe(listname)
request_creation(doc, cgidata,
- _('List already exists: %(listname)s'))
+ _('List already exists: $safelistname'))
return
except Errors.BadListNameError, s:
- request_creation(doc, cgidata,
- _('Illegal list name: %(s)s'))
+ request_creation(doc, cgidata, _('Illegal list name: $s'))
return
except Errors.MMListError:
request_creation(
doc, cgidata,
- _('''Some unknown error occurred while creating the list.
- Please contact the site administrator for assistance.'''))
+ _("""Some unknown error occurred while creating the list.
+ Please contact the site administrator for assistance."""))
return
-
# Initialize the host_name and web_page_url attributes, based on
# virtual hosting settings and the request environment variables.
mlist.default_member_moderation = moderate
- mlist.web_page_url = mm_cfg.DEFAULT_URL_PATTERN % hostname
- mlist.host_name = emailhost
mlist.Save()
finally:
# Now be sure to unlock the list. It's okay if we get a signal here
@@ -223,34 +210,31 @@ def process_request(doc, cgidata):
# unlocking is unconditional, so it's not an error if we unlock while
# we're already unlocked.
mlist.Unlock()
-
# Now do the MTA-specific list creation tasks
- if mm_cfg.MTA:
- modname = 'Mailman.MTA.' + mm_cfg.MTA
+ if config.MTA:
+ modname = 'Mailman.MTA.' + config.MTA
__import__(modname)
- sys.modules[modname].create(mlist, cgi=1)
-
+ sys.modules[modname].create(mlist, cgi=True)
# And send the notice to the list owner.
if notify:
- siteowner = Utils.get_site_email(mlist.host_name, 'owner')
+ siteowner = mlist.GetNoReplyEmail()
text = Utils.maketext(
'newlist.txt',
{'listname' : listname,
'password' : password,
- 'admin_url' : mlist.GetScriptURL('admin', absolute=1),
- 'listinfo_url': mlist.GetScriptURL('listinfo', absolute=1),
+ 'admin_url' : mlist.GetScriptURL('admin', absolute=True),
+ 'listinfo_url': mlist.GetScriptURL('listinfo', absolute=True),
'requestaddr' : mlist.GetRequestEmail(),
'siteowner' : siteowner,
}, mlist=mlist)
msg = Message.UserNotification(
owner, siteowner,
- _('Your new mailing list: %(listname)s'),
+ _('Your new mailing list: $listname'),
text, mlist.preferred_language)
msg.send(mlist)
-
# Success!
- listinfo_url = mlist.GetScriptURL('listinfo', absolute=1)
- admin_url = mlist.GetScriptURL('admin', absolute=1)
+ listinfo_url = mlist.GetScriptURL('listinfo', absolute=True)
+ admin_url = mlist.GetScriptURL('admin', absolute=True)
create_url = Utils.ScriptURL('create')
title = _('Mailing list creation results')
@@ -258,10 +242,10 @@ def process_request(doc, cgidata):
table = Table(border=0, width='100%')
table.AddRow([Center(Bold(FontAttr(title, size='+1')))])
table.AddCellInfo(table.GetCurrentRowIndex(), 0,
- bgcolor=mm_cfg.WEB_HEADER_COLOR)
- table.AddRow([_('''You have successfully created the mailing list
- <b>%(listname)s</b> and notification has been sent to the list owner
- <b>%(owner)s</b>. You can now:''')])
+ bgcolor=config.WEB_HEADER_COLOR)
+ table.AddRow([_("""You have successfully created the mailing list
+ <b>$listname</b> and notification has been sent to the list owner
+ <b>$owner</b>. You can now:""")])
ullist = UnorderedList()
ullist.AddItem(Link(listinfo_url, _("Visit the list's info page")))
ullist.AddItem(Link(admin_url, _("Visit the list's admin page")))
@@ -281,14 +265,14 @@ dummy = Dummy()
def request_creation(doc, cgidata=dummy, errmsg=None):
# What virtual domain are we using?
- hostname = Utils.get_domain()
+ hostname = Utils.get_request_domain()
# Set up the document
- title = _('Create a %(hostname)s Mailing List')
+ title = _('Create a $hostname Mailing List')
doc.SetTitle(title)
table = Table(border=0, width='100%')
table.AddRow([Center(Bold(FontAttr(title, size='+1')))])
table.AddCellInfo(table.GetCurrentRowIndex(), 0,
- bgcolor=mm_cfg.WEB_HEADER_COLOR)
+ bgcolor=config.WEB_HEADER_COLOR)
# Add any error message
if errmsg:
table.AddRow([Header(3, Bold(
@@ -315,7 +299,7 @@ def request_creation(doc, cgidata=dummy, errmsg=None):
password can also be used for authentication.
""")])
# Build the form for the necessary input
- GREY = mm_cfg.WEB_ADMINITEM_COLOR
+ GREY = config.WEB_ADMINITEM_COLOR
form = Form(Utils.ScriptURL('create'))
ftable = Table(border=0, cols='2', width='100%',
cellspacing=3, cellpadding=4)
@@ -336,9 +320,9 @@ def request_creation(doc, cgidata=dummy, errmsg=None):
ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY)
try:
- autogen = int(cgidata.getvalue('autogen', '0'))
+ autogen = bool(cgidata.getvalue('autogen', '0'))
except ValueError:
- autogen = 0
+ autogen = False
ftable.AddRow([Label(_('Auto-generate initial list password?')),
RadioButtonArray('autogen', (_('No'), _('Yes')),
checked=autogen,
@@ -359,14 +343,14 @@ def request_creation(doc, cgidata=dummy, errmsg=None):
ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 1, bgcolor=GREY)
try:
- notify = int(cgidata.getvalue('notify', '1'))
+ notify = bool(cgidata.getvalue('notify', '1'))
except ValueError:
- notify = 1
+ notify = True
try:
- moderate = int(cgidata.getvalue('moderate',
- mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION))
+ moderate = bool(cgidata.getvalue('moderate',
+ config.DEFAULT_DEFAULT_MEMBER_MODERATION))
except ValueError:
- moderate = mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION
+ moderate = config.DEFAULT_DEFAULT_MEMBER_MODERATION
ftable.AddRow([Center(Italic(_('List Characteristics')))])
ftable.AddCellInfo(ftable.GetCurrentRowIndex(), 0, colspan=2)
@@ -383,7 +367,7 @@ def request_creation(doc, cgidata=dummy, errmsg=None):
# Create the table of initially supported languages, sorted on the long
# name of the language.
revmap = {}
- for key, (name, charset) in mm_cfg.LC_DESCRIPTIONS.items():
+ for key, (name, charset) in config.LC_DESCRIPTIONS.items():
revmap[_(name)] = key
langnames = revmap.keys()
langnames.sort()
@@ -391,7 +375,7 @@ def request_creation(doc, cgidata=dummy, errmsg=None):
for name in langnames:
langs.append(revmap[name])
try:
- langi = langs.index(mm_cfg.DEFAULT_SERVER_LANGUAGE)
+ langi = langs.index(config.DEFAULT_SERVER_LANGUAGE)
except ValueError:
# Someone must have deleted the servers's preferred language. Could
# be other trouble lurking!
@@ -400,11 +384,11 @@ def request_creation(doc, cgidata=dummy, errmsg=None):
# invocations.
checked = [0] * len(langs)
checked[langi] = 1
- deflang = _(Utils.GetLanguageDescr(mm_cfg.DEFAULT_SERVER_LANGUAGE))
+ deflang = _(Utils.GetLanguageDescr(config.DEFAULT_SERVER_LANGUAGE))
ftable.AddRow([Label(_(
- '''Initial list of supported languages. <p>Note that if you do not
+ """Initial list of supported languages. <p>Note that if you do not
select at least one initial language, the list will use the server
- default language of %(deflang)s''')),
+ default language of $deflang""")),
CheckBoxArray('langs',
[_(Utils.GetLanguageDescr(L)) for L in langs],
checked=checked,
diff --git a/Mailman/Cgi/listinfo.py b/Mailman/Cgi/listinfo.py
index fc96f5d29..d8281ccea 100644
--- a/Mailman/Cgi/listinfo.py
+++ b/Mailman/Cgi/listinfo.py
@@ -66,7 +66,7 @@ def main():
def listinfo_overview(msg=''):
# Present the general listinfo overview
- hostname = Utils.get_domain()
+ hostname = Utils.get_request_domain()
# Set up the document and assign it the correct language. The only one we
# know about at the moment is the server's default.
doc = Document()
@@ -86,11 +86,10 @@ def listinfo_overview(msg=''):
listnames.sort()
for name in listnames:
- mlist = MailList.MailList(name, lock=0)
+ mlist = MailList.MailList(name, lock=False)
if mlist.advertised:
- if mm_cfg.VIRTUAL_HOST_OVERVIEW and \
- mlist.web_page_url.find(hostname) == -1:
- # List is for different identity of this host - skip it.
+ if hostname not in mlist.web_page_url:
+ # This list is situated in a different virtual domain
continue
else:
advertised.append((mlist.GetScriptURL('listinfo'),
@@ -116,7 +115,7 @@ def listinfo_overview(msg=''):
# set up some local variables
adj = msg and _('right') or ''
- siteowner = Utils.get_site_email()
+ siteowner = Utils.get_site_noreply()
welcome.extend(
(_(''' To visit the general information page for an unadvertised list,
open a URL similar to this one, but with a '/' and the %(adj)s
diff --git a/Mailman/Cgi/rmlist.py b/Mailman/Cgi/rmlist.py
index 78a3e0a12..e10e77b6e 100644
--- a/Mailman/Cgi/rmlist.py
+++ b/Mailman/Cgi/rmlist.py
@@ -168,7 +168,7 @@ def process_request(doc, cgidata, mlist):
table.AddRow([_('''You have successfully deleted the mailing list
<b>%(listname)s</b>.''')])
else:
- sitelist = Utils.get_site_email(mlist.host_name)
+ sitelist = mlist.GetNoReplyEmail()
table.AddRow([_('''There were some problems deleting the mailing list
<b>%(listname)s</b>. Contact your site administrator at %(sitelist)s
for details.''')])
diff --git a/Mailman/Commands/cmd_lists.py b/Mailman/Commands/cmd_lists.py
index ff5dd355a..b137efc57 100644
--- a/Mailman/Commands/cmd_lists.py
+++ b/Mailman/Commands/cmd_lists.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2002 by the Free Software Foundation, Inc.
+# Copyright (C) 2002-2006 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
@@ -12,7 +12,8 @@
#
# 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.
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
"""
lists
@@ -53,10 +54,8 @@ def process(res, args):
# We can mention this list if you already know about it
if not xlist.advertised and xlist is not mlist:
continue
- # Skip the list if it isn't in the same virtual domain. BAW: should a
- # message to the site list include everything regardless of domain?
- if mm_cfg.VIRTUAL_HOST_OVERVIEW and \
- xlist.host_name <> mlist.host_name:
+ # Skip the list if it isn't in the same virtual domain.
+ if xlist.host_name <> mlist.host_name:
continue
realname = xlist.real_name
description = xlist.description or _('n/a')
diff --git a/Mailman/Defaults.py.in b/Mailman/Defaults.py.in
index d8248a3cf..e843bc249 100644
--- a/Mailman/Defaults.py.in
+++ b/Mailman/Defaults.py.in
@@ -58,29 +58,21 @@ MAILMAN_URL = 'http://www.gnu.org/software/mailman/index.html'
#MAILMAN_URL = 'http://www.list.org/'
#MAILMAN_URL = 'http://mailman.sf.net/'
-# Mailman needs to know about (at least) two fully-qualified domain names
-# (fqdn); 1) the hostname used in your urls, and 2) the hostname used in email
-# addresses for your domain. For example, if people visit your Mailman system
-# with "http://www.dom.ain/mailman" then your url fqdn is "www.dom.ain", and
-# if people send mail to your system via "yourlist@dom.ain" then your email
-# fqdn is "dom.ain". DEFAULT_URL_HOST controls the former, and
-# DEFAULT_EMAIL_HOST controls the latter. Mailman also needs to know how to
-# map from one to the other (this is especially important if you're running
-# with virtual domains). You use "add_virtualhost(urlfqdn, emailfqdn)" to add
-# new mappings.
-#
-# If you don't need to change DEFAULT_EMAIL_HOST and DEFAULT_URL_HOST in your
-# mm_cfg.py, then you're done; the default mapping is added automatically. If
-# however you change either variable in your mm_cfg.py, then be sure to also
-# include the following:
-#
-# add_virtualhost(DEFAULT_URL_HOST, DEFAULT_EMAIL_HOST)
-#
-# because otherwise the default mappings won't be correct.
-DEFAULT_EMAIL_HOST = '@MAILHOST@'
-DEFAULT_URL_HOST = '@URLHOST@'
DEFAULT_URL_PATTERN = 'http://%s/mailman/'
+# This address is used as the from address whenever a message comes from some
+# entity to which there is no natural reply recipient. Set this to a real
+# human or to /dev/null. It will be appended with the hostname of the list
+# involved or the DEFAULT_EMAIL_HOST if none is available. Address must not
+# bounce and it must not point to a Mailman process.
+NO_REPLY_ADDRESS = 'noreply'
+
+# This address is the "site owner" address. Certain messages which must be
+# delivered to a human, but which can't be delivered to a list owner (e.g. a
+# bounce from a list owner), will be sent to this address. It should point to
+# a human.
+SITE_OWNER_ADDRESS = 'changeme@example.com'
+
# DEFAULT_HOST_NAME has been replaced with DEFAULT_EMAIL_HOST, however some
# sites may have the former in their mm_cfg.py files. If so, we'll believe
# that, otherwise we'll believe DEFAULT_EMAIL_HOST. Same for DEFAULT_URL.
@@ -88,7 +80,6 @@ DEFAULT_HOST_NAME = None
DEFAULT_URL = None
HOME_PAGE = 'index.html'
-MAILMAN_SITE_LIST = 'mailman'
# Normally when a site administrator authenticates to a web page with the site
# password, they get a cookie which authorizes them as the list admin. It
@@ -109,36 +100,29 @@ HTML_TO_PLAIN_TEXT_COMMAND = '/usr/bin/lynx -dump %(filename)s'
# Virtual domains
#####
-# Set up your virtual host mappings here. This is primarily used for the
-# thru-the-web list creation, so its effects are currently fairly limited.
-# Use add_virtualhost() call to add new mappings. The keys are strings as
-# determined by Utils.get_domain(), the values are as appropriate for
-# DEFAULT_HOST_NAME.
-VIRTUAL_HOSTS = {}
-
-# When set to Yes, the listinfo and admin overviews of lists on the machine
-# will be confined to only those lists whose web_page_url configuration option
-# host is included within the URL by which the page is visited - only those
-# "on the virtual host". When set to No, all advertised (i.e. public) lists
-# are included in the overview.
-VIRTUAL_HOST_OVERVIEW = On
-
-
-# Helper function; use this in your mm_cfg.py files. If optional emailhost is
-# omitted it defaults to urlhost with the first name stripped off, e.g.
+# Mailman needs to know about at least one domain, called the 'site default
+# domain'. If you run only one domain with Mailman, this will generally be
+# calculated automatically when you configured Mailman. You can always change
+# this in your mailman.cfg file. You can also add additional virtual domains
+# in your mailman.cfg file to enable multiple virtual domains. Every mailing
+# list will be situated in exactly one virtual domain.
#
-# add_virtualhost('www.dom.ain')
-# VIRTUAL_HOST['www.dom.ain']
-# ==> 'dom.ain'
+# For Mailman's purposes, a virtual domain associates an email host name with
+# a web host name. These may be the same, but often they are different, and
+# the list is always referred to by its fully-qualified posting address. For
+# example, if you created 'mylist' in the example.com domain, people can post
+# to your list via 'mylist@example.com'. They may refer to the web pages via
+# 'www.example.com'. So your email host name is 'example.com' and your web
+# host name is 'www.example.com'.
#
-def add_virtualhost(urlhost, emailhost=None):
- DOT = '.'
- if emailhost is None:
- emailhost = DOT.join(urlhost.split(DOT)[1:])
- VIRTUAL_HOSTS[urlhost.lower()] = emailhost.lower()
-
-# And set the default
-add_virtualhost(DEFAULT_URL_HOST, DEFAULT_EMAIL_HOST)
+# To add a virtual domain, put a call to add_domain(email_host, url_host) in
+# your mailman.cfg file. If no add_domain() calls are found, Mailman will
+# automatically add a virtual domain for the following defaults. However if
+# you explicit add domains, you will need to add these defaults as well.
+#
+# These defaults will be filled in by configure.
+DEFAULT_EMAIL_HOST = '@MAILHOST@'
+DEFAULT_URL_HOST = '@URLHOST@'
# Note that you will want to run bin/fix_url.py to change the domain of an
# existing list. bin/fix_url.py must be run within the bin/withlist script,
diff --git a/Mailman/Deliverer.py b/Mailman/Deliverer.py
index e1007e135..8f93bf48e 100644
--- a/Mailman/Deliverer.py
+++ b/Mailman/Deliverer.py
@@ -88,7 +88,7 @@ your membership administrative address, %(addr)s.'''))
msg.send(self, verp=mm_cfg.VERP_PERSONALIZED_DELIVERIES)
def MailUserPassword(self, user):
- listfullname = '%s@%s' % (self.real_name, self.host_name)
+ listfullname = self.fqdn_listname
requestaddr = self.GetRequestEmail()
# find the lowercased version of the user's address
adminaddr = self.GetBouncesEmail()
diff --git a/Mailman/Errors.py b/Mailman/Errors.py
index a0aacf72a..a0fdd8c52 100644
--- a/Mailman/Errors.py
+++ b/Mailman/Errors.py
@@ -12,15 +12,22 @@
#
# 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.
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
+"""Mailman errors."""
-"""Shared Mailman errors and messages."""
+
+
+# Base class for all exceptions raised in Mailman (XXX except legacy string
+# exceptions).
+class MailmanException(Exception):
+ pass
-# exceptions for problems related to opening a list
-class MMListError(Exception): pass
+# Exceptions for problems related to opening a list
+class MMListError(MailmanException): pass
class MMUnknownListError(MMListError): pass
class MMCorruptListDatabaseError(MMListError): pass
class MMListNotReadyError(MMListError): pass
@@ -28,12 +35,12 @@ class MMListAlreadyExistsError(MMListError): pass
class BadListNameError(MMListError): pass
# Membership exceptions
-class MMMemberError(Exception): pass
+class MMMemberError(MailmanException): pass
class MMBadUserError(MMMemberError): pass
class MMAlreadyAMember(MMMemberError): pass
# "New" style membership exceptions (new w/ MM2.1)
-class MemberError(Exception): pass
+class MemberError(MailmanException): pass
class NotAMemberError(MemberError): pass
class AlreadyReceivingDigests(MemberError): pass
class AlreadyReceivingRegularDeliveries(MemberError): pass
@@ -43,7 +50,7 @@ class MembershipIsBanned(MemberError): pass
# Exception hierarchy for various authentication failures, can be
# raised from functions in SecurityManager.py
-class MMAuthenticationError(Exception): pass
+class MMAuthenticationError(MailmanException): pass
class MMBadPasswordError(MMAuthenticationError): pass
class MMPasswordsMustMatch(MMAuthenticationError): pass
class MMCookieError(MMAuthenticationError): pass
@@ -69,10 +76,12 @@ FORBIDDEN_SENDER_MSG = "Forbidden sender"
# New style class based exceptions. All the above errors should eventually be
# converted.
-class MailmanError(Exception):
- """Base class for all Mailman exceptions."""
+class MailmanError(MailmanException):
+ """Base class for all Mailman errors."""
pass
+class BadDomainSpecificationError(MailmanError):
+ """The specification of a virtual domain is invalid or duplicated."""
class MMLoopingPost(MailmanError):
"""Post already went through this list!"""
diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py
index 60fb52359..b71902e2d 100644
--- a/Mailman/Handlers/SMTPDirect.py
+++ b/Mailman/Handlers/SMTPDirect.py
@@ -105,7 +105,7 @@ def process(mlist, msg, msgdata):
if mlist:
envsender = mlist.GetBouncesEmail()
else:
- envsender = Utils.get_site_email(extra='bounces')
+ envsender = Utils.get_site_noreply()
# Time to split up the recipient list. If we're personalizing or VERPing
# then each chunk will have exactly one recipient. We'll then hand craft
# an envelope sender and stitch a message together in memory for each one
diff --git a/Mailman/MTA/Manual.py b/Mailman/MTA/Manual.py
index cb25e9fba..4a3d6aec4 100644
--- a/Mailman/MTA/Manual.py
+++ b/Mailman/MTA/Manual.py
@@ -85,9 +85,7 @@ equivalent) file by adding the following lines, and possibly running the
if not cgi:
print >> outfp
return
- # Send the message to the site -owner so someone can do something about
- # this request.
- siteowner = Utils.get_site_email(extra='owner')
+ siteowner = Utils.get_site_noreply()
# Should this be sent in the site list's preferred language?
msg = Message.UserNotification(
siteowner, siteowner,
@@ -130,7 +128,7 @@ equivalent) file by removing the following lines, and possibly running the
if not cgi:
print >> outfp
return
- siteowner = Utils.get_site_email(extra='owner')
+ siteowner = Utils.get_site_noreply()
# Should this be sent in the site list's preferred language?
msg = Message.UserNotification(
siteowner, siteowner,
diff --git a/Mailman/MTA/Postfix.py b/Mailman/MTA/Postfix.py
index 46143c549..4301321e7 100644
--- a/Mailman/MTA/Postfix.py
+++ b/Mailman/MTA/Postfix.py
@@ -78,7 +78,7 @@ def clear():
def _addlist(mlist, fp):
# Set up the mailman-loop address
- loopaddr = Utils.ParseEmail(Utils.get_site_email(extra='loop'))[0]
+ loopaddr = Utils.ParseEmail(Utils.get_site_noreply())[0]
loopmbox = os.path.join(mm_cfg.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.
@@ -118,7 +118,7 @@ def _addvirtual(mlist, fp):
fieldsz = len(listname) + len('-unsubscribe')
hostname = mlist.host_name
# Set up the mailman-loop address
- loopaddr = Utils.get_site_email(mlist.host_name, extra='loop')
+ loopaddr = mlist.GetNoReplyEmail()
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.
@@ -142,9 +142,8 @@ def _addvirtual(mlist, fp):
print >> fp, '# CREATED:', time.ctime(time.time())
# Now add all the standard alias entries
for k, v in makealiases(listname):
- fqdnaddr = '%s@%s' % (k, hostname)
# Format the text file nicely
- print >> fp, fqdnaddr, ((fieldsz - len(k)) * ' '), k
+ print >> fp, mlist.fqdn_listname, ((fieldsz - len(k)) * ' '), k
# Finish the text file stanza
print >> fp, '# STANZA END:', listname
print >> fp
@@ -153,7 +152,7 @@ def _addvirtual(mlist, fp):
# Blech.
def _check_for_virtual_loopaddr(mlist, filename):
- loopaddr = Utils.get_site_email(mlist.host_name, extra='loop')
+ loopaddr = mlist.GetNoReplyEmail()
loopdest = Utils.ParseEmail(loopaddr)[0]
infp = open(filename)
omask = os.umask(007)
diff --git a/Mailman/MailList.py b/Mailman/MailList.py
index ecf449246..069a0b397 100644
--- a/Mailman/MailList.py
+++ b/Mailman/MailList.py
@@ -68,7 +68,6 @@ from Mailman import Gui
from Mailman import i18n
from Mailman import MemberAdaptor
from Mailman import Message
-from Mailman import Site
from Mailman.OldStyleMemberships import OldStyleMemberships
_ = i18n._
@@ -183,9 +182,13 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
def fullpath(self):
return self._full_path
+ @property
+ def fqdn_listname(self):
+ return '%s@%s' % (self._internal_name, self.host_name)
+
def getListAddress(self, extra=None):
if extra is None:
- return '%s@%s' % (self.internal_name(), self.host_name)
+ return self.fqdn_listname
return '%s-%s@%s' % (self.internal_name(), extra, self.host_name)
# For backwards compatibility
@@ -195,6 +198,9 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
def GetOwnerEmail(self):
return self.getListAddress('owner')
+ def GetNoReplyEmail(self):
+ return '%s@%s' % (config.NO_REPLY_ADDRESS, self.host_name)
+
def GetRequestEmail(self, cookie=''):
if config.VERP_CONFIRMATIONS and cookie:
return self.GetConfirmEmail(cookie)
@@ -273,7 +279,7 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
lifetime=config.LIST_LOCK_LIFETIME)
self._internal_name = name
if name:
- self._full_path = Site.get_listpath(name)
+ self._full_path = os.path.join(config.LIST_DATA_DIR, name)
else:
self._full_path = ''
# Only one level of mixin inheritance allowed
@@ -466,34 +472,41 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
#
# List creation
#
- def Create(self, name, admin, crypted_password,
- langs=None, emailhost=None):
- if Utils.list_exists(name):
- raise Errors.MMListAlreadyExistsError, name
- # Validate what will be the list's posting address. If that's
- # invalid, we don't want to create the mailing list. The hostname
- # part doesn't really matter, since that better already be valid.
- # However, most scripts already catch MMBadEmailError as exceptions on
- # the admin's email address, so transform the exception.
- if emailhost is None:
- emailhost = config.DEFAULT_EMAIL_HOST
- postingaddr = '%s@%s' % (name, emailhost)
+ def Create(self, fqdn_listname, admin_email, crypted_password, langs=None):
+ # Validate the list's posting address, which should be fqdn_listname.
+ # If that's invalid, do not create any of the mailing list artifacts
+ # (the subdir in lists/ and the subdirs in archives/public and
+ # archives/private. Most scripts already catch MMBadEmailError as
+ # exceptions on the admin's email address, so transform the exception.
+ if '@' not in fqdn_listname:
+ raise Errors.BadListNameError(fqdn_listname)
+ listname, email_host = fqdn_listname.split('@', 1)
+ if email_host not in config.domains:
+ raise Errors.BadDomainSpecificationError(email_host)
try:
- Utils.ValidateEmail(postingaddr)
+ Utils.ValidateEmail(fqdn_listname)
except Errors.MMBadEmailError:
- raise Errors.BadListNameError, postingaddr
+ raise Errors.BadListNameError(fqdn_listname)
+ # See if the mailing list already exists.
+ if Utils.list_exists(fqdn_listname):
+ raise Errors.MMListAlreadyExistsError(fqdn_listname)
# Validate the admin's email address
- Utils.ValidateEmail(admin)
- self._internal_name = name
- self._full_path = Site.get_listpath(name, create=1)
+ Utils.ValidateEmail(admin_email)
+ self._internal_name = listname
+ self._full_path = os.path.join(config.LIST_DATA_DIR, fqdn_listname)
+ Utils.makedirs(self._full_path)
# Don't use Lock() since that tries to load the non-existant config.pck
self.__lock.lock()
- self.InitVars(name, admin, crypted_password)
+ self.InitVars(listname, admin_email, crypted_password)
self.CheckValues()
if langs is None:
self.available_languages = [self.preferred_language]
else:
self.available_languages = langs
+ # Set the various host names
+ self.host_name = email_host
+ url_host = config.domains[email_host]
+ self.web_page_url = config.DEFAULT_URL_PATTERN % url_host
@@ -1350,7 +1363,6 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
addresses in the recipient headers.
"""
# This is the list's full address.
- listfullname = '%s@%s' % (self.internal_name(), self.host_name)
recips = []
# Check all recipient addresses against the list's explicit addresses,
# specifically To: Cc: and Resent-to:
@@ -1367,7 +1379,7 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
if (# TBD: backwards compatibility: deprecated
localpart == self.internal_name() or
# exact match against the complete list address
- addr == listfullname):
+ addr == self.fqdn_listname):
return True
recips.append((addr, localpart))
# Helper function used to match a pattern against an address.
@@ -1480,7 +1492,7 @@ bad regexp in bounce_matching_header line: %s
text = Utils.maketext(
'nomoretoday.txt',
{'sender' : sender,
- 'listname': '%s@%s' % (self.real_name, self.host_name),
+ 'listname': self.fqdn_listname,
'num' : count,
'owneremail': self.GetOwnerEmail(),
},
diff --git a/Mailman/Message.py b/Mailman/Message.py
index 147ac073d..acad25680 100644
--- a/Mailman/Message.py
+++ b/Mailman/Message.py
@@ -259,13 +259,11 @@ class UserNotification(Message):
class OwnerNotification(UserNotification):
"""Like user notifications, but this message goes to the list owners."""
- def __init__(self, mlist, subject=None, text=None, tomoderators=1):
+ def __init__(self, mlist, subject=None, text=None, tomoderators=True):
recips = mlist.owner[:]
if tomoderators:
recips.extend(mlist.moderator)
- # We have to set the owner to the site's -bounces address, otherwise
- # we'll get a mail loop if an owner's address bounces.
- sender = Utils.get_site_email(mlist.host_name, 'bounces')
+ sender = config.SITE_OWNER_ADDRESS
lang = mlist.preferred_language
UserNotification.__init__(self, recips, sender, subject, text, lang)
# Hack the To header to look like it's going to the -owner address
diff --git a/Mailman/Queue/BounceRunner.py b/Mailman/Queue/BounceRunner.py
index 93bee2062..9e1a34bf5 100644
--- a/Mailman/Queue/BounceRunner.py
+++ b/Mailman/Queue/BounceRunner.py
@@ -177,16 +177,17 @@ class BounceRunner(Runner, BounceMixin):
# we'll simply log the problem and attempt to deliver the message to
# the site owner.
#
- # All messages to list-owner@vdom.ain have their envelope sender set
- # to site-owner@dom.ain (no virtual domain). Is this a bounce for a
+ # All messages sent to list owners have their sender set to the site
+ # owner address. That way, if a list owner address bounces, at least
+ # some human has a chance to deal with it. Is this a bounce for a
# message to a list owner, coming to the site owner?
- if msg.get('to', '') == Utils.get_site_email(extra='owner'):
+ if msg.get('to', '') == config.SITE_OWNER_ADDRESS:
# Send it on to the site owners, but craft the envelope sender to
- # be the -loop detection address, so if /they/ bounce, we won't
+ # be the noreply address, so if the site owner bounce, we won't
# get stuck in a bounce loop.
outq.enqueue(msg, msgdata,
- recips=[Utils.get_site_email()],
- envsender=Utils.get_site_email(extra='loop'),
+ recips=[config.SITE_OWNER_ADDRESS],
+ envsender=config.NO_REPLY_ADDRESS,
)
# List isn't doing bounce processing?
if not mlist.bounce_processing:
diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py
index 332c9f129..efd7072d8 100644
--- a/Mailman/Queue/IncomingRunner.py
+++ b/Mailman/Queue/IncomingRunner.py
@@ -115,6 +115,8 @@ class IncomingRunner(Runner):
QDIR = config.INQUEUE_DIR
def _dispose(self, mlist, msg, msgdata):
+ if msgdata.get('envsender') is None:
+ msg['envsender'] = mlist.GetNoReplyEmail()
# Try to get the list lock.
try:
mlist.Lock(timeout=config.LIST_LOCK_TIMEOUT)
diff --git a/Mailman/Queue/MaildirRunner.py b/Mailman/Queue/MaildirRunner.py
index 9e5e08be5..e253f3506 100644
--- a/Mailman/Queue/MaildirRunner.py
+++ b/Mailman/Queue/MaildirRunner.py
@@ -162,8 +162,8 @@ class MaildirRunner(Runner):
queue = get_switchboard(config.CMDQUEUE_DIR)
elif subq == 'owner':
msgdata.update({
- 'toowner': 1,
- 'envsender': Utils.get_site_email(extra='bounces'),
+ 'toowner': True,
+ 'envsender': config.SITE_OWNER_ADDRESS,
'pipeline': config.OWNER_PIPELINE,
})
queue = get_switchboard(config.INQUEUE_DIR)
diff --git a/Mailman/Queue/Runner.py b/Mailman/Queue/Runner.py
index 95cb3fd43..bdb811a3a 100644
--- a/Mailman/Queue/Runner.py
+++ b/Mailman/Queue/Runner.py
@@ -132,8 +132,6 @@ class Runner:
#
# Find out which mailing list this message is destined for.
listname = msgdata.get('listname')
- if not listname:
- listname = config.MAILMAN_SITE_LIST
mlist = self._open_list(listname)
if not mlist:
log.error('Dequeuing message destined for missing list: %s',
diff --git a/Mailman/Site.py b/Mailman/Site.py
index 72becb224..e69de29bb 100644
--- a/Mailman/Site.py
+++ b/Mailman/Site.py
@@ -1,108 +0,0 @@
-# Copyright (C) 2002-2006 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.
-
-"""Provide some customization for site-wide behavior.
-
-This should be considered experimental for Mailman 2.1. The default
-implementation should work for standard Mailman.
-"""
-
-import os
-import errno
-
-from Mailman.configuration import config
-
-
-
-def _makedir(path):
- try:
- omask = os.umask(0)
- try:
- os.makedirs(path, 02775)
- finally:
- os.umask(omask)
- except OSError, e:
- # Ignore the exceptions if the directory already exists
- if e.errno <> errno.EEXIST:
- raise
-
-
-
-# BAW: We don't really support domain<>None yet. This will be added in a
-# future version. By default, Mailman will never pass in a domain argument.
-def get_listpath(listname, domain=None, create=0):
- """Return the file system path to the list directory for the named list.
-
- If domain is given, it is the virtual domain for the named list. The
- default is to not distinguish list paths on the basis of virtual domains.
-
- If the create flag is true, then this method should create the path
- hierarchy if necessary. If the create flag is false, then this function
- should not attempt to create the path heirarchy (and in fact the absence
- of the path might be significant).
- """
- path = os.path.join(config.LIST_DATA_DIR, listname)
- if create:
- _makedir(path)
- return path
-
-
-
-# BAW: We don't really support domain<>None yet. This will be added in a
-# future version. By default, Mailman will never pass in a domain argument.
-def get_archpath(listname, domain=None, create=False, public=False):
- """Return the file system path to the list's archive directory for the
- named list in the named virtual domain.
-
- If domain is given, it is the virtual domain for the named list. The
- default is to not distinguish list paths on the basis of virtual domains.
-
- If the create flag is true, then this method should create the path
- hierarchy if necessary. If the create flag is false, then this function
- should not attempt to create the path heirarchy (and in fact the absence
- of the path might be significant).
-
- If public is true, then the path points to the public archive path (which
- is usually a symlink instead of a directory).
- """
- if public:
- subdir = config.PUBLIC_ARCHIVE_FILE_DIR
- else:
- subdir = config.PRIVATE_ARCHIVE_FILE_DIR
- path = os.path.join(subdir, listname)
- if create:
- _makedir(path)
- return path
-
-
-
-# BAW: We don't really support domain<>None yet. This will be added in a
-# future version. By default, Mailman will never pass in a domain argument.
-def get_listnames(domain=None):
- """Return the names of all the known lists for the given domain.
-
- If domain is given, it is the virtual domain for the named list. The
- default is to not distinguish list paths on the basis of virtual domains.
- """
- # Import this here to avoid circular imports
- from Mailman.Utils import list_exists
- # We don't currently support separate virtual domain directories
- got = []
- for fn in os.listdir(config.LIST_DATA_DIR):
- if list_exists(fn):
- got.append(fn)
- return got
diff --git a/Mailman/Utils.py b/Mailman/Utils.py
index 5e594be8d..b190e2ded 100644
--- a/Mailman/Utils.py
+++ b/Mailman/Utils.py
@@ -40,7 +40,6 @@ from email.Errors import HeaderParseError
from string import ascii_letters, digits, whitespace
from Mailman import Errors
-from Mailman import Site
from Mailman.SafeDict import SafeDict
from Mailman.configuration import config
@@ -68,7 +67,7 @@ def list_exists(listname):
#
# The former two are for 2.1alpha3 and beyond, while the latter two are
# for all earlier versions.
- basepath = Site.get_listpath(listname)
+ basepath = os.path.join(config.LIST_DATA_DIR, listname)
for ext in ('.pck', '.pck.last', '.db', '.db.last'):
dbfile = os.path.join(basepath, 'config' + ext)
if os.path.exists(dbfile):
@@ -78,8 +77,11 @@ def list_exists(listname):
def list_names():
"""Return the names of all lists in default list directory."""
- # We don't currently support separate listings of virtual domains
- return Site.get_listnames()
+ got = set()
+ for fn in os.listdir(config.LIST_DATA_DIR):
+ if list_exists(fn):
+ got.add(fn)
+ return got
@@ -227,7 +229,7 @@ def ScriptURL(target, web_page_url=None, absolute=False):
absolute - a flag which if set, generates an absolute url
"""
if web_page_url is None:
- web_page_url = config.DEFAULT_URL_PATTERN % get_domain()
+ web_page_url = config.DEFAULT_URL_PATTERN % get_request_domain()
if web_page_url[-1] <> '/':
web_page_url = web_page_url + '/'
fullpath = os.environ.get('REQUEST_URI')
@@ -389,7 +391,7 @@ def get_global_password(siteadmin=True):
def check_global_password(response, siteadmin=True):
challenge = get_global_password(siteadmin)
if challenge is None:
- return None
+ return False
return challenge == sha.new(response).hexdigest()
@@ -651,6 +653,21 @@ def reap(kids, func=None, once=False):
if once:
break
+
+
+def makedirs(path):
+ try:
+ omask = os.umask(0)
+ try:
+ os.makedirs(path, 02775)
+ finally:
+ os.umask(omask)
+ except OSError, e:
+ # Ignore the exceptions if the directory already exists
+ if e.errno <> errno.EEXIST:
+ raise
+
+
def GetLanguageDescr(lang):
return config.LC_DESCRIPTIONS[lang][0]
@@ -664,29 +681,17 @@ def IsLanguage(lang):
-def get_domain():
+def get_request_domain():
host = os.environ.get('HTTP_HOST', os.environ.get('SERVER_NAME'))
port = os.environ.get('SERVER_PORT')
# Strip off the port if there is one
if port and host.endswith(':' + port):
host = host[:-len(port)-1]
- if config.VIRTUAL_HOST_OVERVIEW and host:
- return host.lower()
- else:
- # See the note in Defaults.py concerning DEFAULT_URL
- # vs. DEFAULT_URL_HOST.
- hostname = ((config.DEFAULT_URL
- and urlparse.urlparse(config.DEFAULT_URL)[1])
- or config.DEFAULT_URL_HOST)
- return hostname.lower()
+ return host.lower()
-def get_site_email(hostname=None, extra=None):
- if hostname is None:
- hostname = config.VIRTUAL_HOSTS.get(get_domain(), get_domain())
- if extra is None:
- return '%s@%s' % (config.MAILMAN_SITE_LIST, hostname)
- return '%s-%s@%s' % (config.MAILMAN_SITE_LIST, extra, hostname)
+def get_site_noreply():
+ return '%s@%s' % (config.NO_REPLY_ADDRESS, config.DEFAULT_EMAIL_HOST)
@@ -700,9 +705,8 @@ def get_site_email(hostname=None, extra=None):
_serial = 0
def unique_message_id(mlist):
global _serial
- msgid = '<mailman.%d.%d.%d.%s@%s>' % (
- _serial, time.time(), os.getpid(),
- mlist.internal_name(), mlist.host_name)
+ msgid = '<mailman.%d.%d.%d.%s>' % (
+ _serial, time.time(), os.getpid(), mlist.fqdn_listname)
_serial += 1
return msgid
diff --git a/Mailman/Version.py b/Mailman/Version.py
index 91fb82e65..7a61ba545 100644
--- a/Mailman/Version.py
+++ b/Mailman/Version.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2005 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2006 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
@@ -12,10 +12,11 @@
#
# 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.
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
# Mailman version
-VERSION = "2.2.0a0"
+VERSION = "2.2.0a1"
# And as a hex number in the manner of PY_VERSION_HEX
ALPHA = 0xa
@@ -30,7 +31,7 @@ MINOR_REV = 2
MICRO_REV = 0
REL_LEVEL = ALPHA
# at most 15 beta releases!
-REL_SERIAL = 0
+REL_SERIAL = 1
HEX_VERSION = ((MAJOR_REV << 24) | (MINOR_REV << 16) | (MICRO_REV << 8) |
(REL_LEVEL << 4) | (REL_SERIAL << 0))
@@ -49,4 +50,3 @@ REQUESTS_FILE_SCHEMA_VERSION = 1
# Printable version string used by command line scripts
MAILMAN_VERSION = 'GNU Mailman ' + VERSION
-
diff --git a/Mailman/bin/add_members.py b/Mailman/bin/add_members.py
index 0adcda518..932e4f77d 100644
--- a/Mailman/bin/add_members.py
+++ b/Mailman/bin/add_members.py
@@ -202,7 +202,7 @@ def main():
if admin_notify:
subject = _('$mlist.real_name subscription notification')
msg = Message.UserNotification(
- mlist.owner, Utils.get_site_email(), subject, s.getvalue(),
+ mlist.owner, mlist.GetNoReplyEmail(), subject, s.getvalue(),
mlist.preferred_language)
msg.send(mlist)
diff --git a/Mailman/bin/change_pw.py b/Mailman/bin/change_pw.py
index afa932160..6c2beaf13 100644
--- a/Mailman/bin/change_pw.py
+++ b/Mailman/bin/change_pw.py
@@ -155,7 +155,7 @@ def main():
hostname = mlist.host_name
adminurl = mlist.GetScriptURL('admin', absolute=True)
msg = Message.UserNotification(
- mlist.owner[:], Utils.get_site_email(),
+ mlist.owner[:], mlist.GetNoReplyEmail(),
_('Your new $listname list password'),
_('''\
The site administrator at $hostname has changed the password for your
diff --git a/Mailman/bin/list_lists.py b/Mailman/bin/list_lists.py
index 0feac2845..f4a193f18 100644
--- a/Mailman/bin/list_lists.py
+++ b/Mailman/bin/list_lists.py
@@ -43,11 +43,17 @@ List only those mailing lists that are publicly advertised"""))
default=False, action='store_true',
help=_("""\
Displays only the list name, with no description."""))
- parser.add_option('-V', '--virtual-host-overview',
- default=None, type='string', dest='vhost',
+ parser.add_option('-d', '--domain',
+ default=[], type='string', action='append',
+ dest='domains', help=_("""\
+List only those mailing lists that match the given virtual domain, which may
+be either the email host or the url host name. Multiple -d options may be
+given."""))
+ parser.add_option('-f', '--full',
+ default=False, action='store_true',
help=_("""\
-List only those mailing lists that are homed to the given virtual domain.
-This only works if the VIRTUAL_HOST_OVERVIEW variable is set."""))
+Print the full list name, including the posting address. This option is
+ignored when -b is given."""))
parser.add_option('-C', '--config',
help=_('Alternative configuration file to use'))
opts, args = parser.parse_args()
@@ -72,12 +78,18 @@ def main():
mlist = MailList.MailList(n, lock=False)
if opts.advertised and not mlist.advertised:
continue
- if opts.vhost and config.VIRTUAL_HOST_OVERVIEW and \
- opts.vhost.find(mlist.web_page_url) == -1 and \
- mlist.web_page_url.find(opts.vhost) == -1:
- continue
- mlists.append(mlist)
- longest = max(len(mlist.real_name), longest)
+ if opts.domains:
+ for domain in opts.domains:
+ if domain in mlist.web_page_url or domain == mlist.host_name:
+ mlists.append(mlist)
+ break
+ else:
+ mlists.append(mlist)
+ if opts.full:
+ name = mlist.internal_name()
+ else:
+ name = mlist.real_name
+ longest = max(len(name), longest)
if not mlists and not opts.bare:
print _('No matching mailing lists found')
@@ -92,8 +104,12 @@ def main():
if opts.bare:
print mlist.internal_name()
else:
+ if opts.full:
+ name = mlist.internal_name()
+ else:
+ name = mlist.real_name
description = mlist.description or _('[no description available]')
- print ' ', format % (mlist.real_name, description)
+ print ' ', format % (name, description)
diff --git a/Mailman/bin/mailmanctl.py b/Mailman/bin/mailmanctl.py
index 0c83e3324..e3a3ec866 100644
--- a/Mailman/bin/mailmanctl.py
+++ b/Mailman/bin/mailmanctl.py
@@ -275,16 +275,6 @@ def start_all_runners():
-def check_for_site_list():
- sitelistname = config.MAILMAN_SITE_LIST
- try:
- sitelist = MailList(sitelistname, lock=False)
- except Errors.MMUnknownListError:
- print >> sys.stderr, _('Site list is missing: $sitelistname')
- elog.error('Site list is missing: %s', config.MAILMAN_SITE_LIST)
- sys.exit(1)
-
-
def check_privs():
# If we're running as root (uid == 0), coerce the uid and gid to that
# which Mailman was configured for, and refuse to run if we didn't coerce
@@ -342,8 +332,6 @@ def main():
print _('Re-opening all log files')
kill_watcher(signal.SIGHUP)
elif command == 'start':
- # First, complain loudly if there's no site list.
- check_for_site_list()
# Here's the scoop on the processes we're about to create. We'll need
# one for each qrunner, and one for a master child process watcher /
# lock refresher process.
diff --git a/Mailman/bin/newlist.py b/Mailman/bin/newlist.py
index 0f62f5373..3136ef425 100644
--- a/Mailman/bin/newlist.py
+++ b/Mailman/bin/newlist.py
@@ -38,52 +38,20 @@ __i18n_templates__ = True
def parseargs():
parser = optparse.OptionParser(version=Version.MAILMAN_VERSION,
usage=_("""\
-%%prog [options] [listname [listadmin-addr [admin-password]]]
+%prog [options] [listname [listadmin-addr [admin-password]]]
-Create a new, unpopulated mailing list.
+Create a new empty mailing list.
You can specify as many of the arguments as you want on the command line:
you will be prompted for the missing ones.
-Every Mailman list has two parameters which define the default host name for
-outgoing email, and the default URL for all web interfaces. When you
-configured Mailman, certain defaults were calculated, but if you are running
-multiple virtual Mailman sites, then the defaults may not be appropriate for
-the list you are creating.
+Every Mailman mailing list is situated in a domain, and Mailman supports
+multiple virtual domains. 'listname' is required, and if it contains an '@',
+it should specify the posting address for your mailing list and the right-hand
+side of the email address must be an existing domain.
-You also specify the domain to create your new list in by typing the command
-like so:
-
- newlist --urlhost=www.mydom.ain mylist
-
-where `www.mydom.ain' should be the base hostname for the URL to this virtual
-hosts's lists. E.g. with this setting people will view the general list
-overviews at http://www.mydom.ain/mailman/listinfo. Also, www.mydom.ain
-should be a key in the VIRTUAL_HOSTS mapping in mm_cfg.py/Defaults.py if
-the email hostname to be automatically determined.
-
-If you want the email hostname to be different from the one looked up by the
-VIRTUAL_HOSTS or if urlhost is not registered in VIRTUAL_HOSTS, you can specify
-`emailhost' like so:
-
- newlist --urlhost=www.mydom.ain --emailhost=mydom.ain mylist
-
-where `mydom.ain' is the mail domain name. If you don't specify emailhost but
-urlhost is not in the virtual host list, then mm_cfg.DEFAULT_EMAIL_HOST will
-be used for the email interface.
-
-For backward compatibility, you can also specify the domain to create your
-new list in by spelling the listname like so:
-
- mylist@www.mydom.ain
-
-where www.mydom.ain is used for `urlhost' but it will also be used for
-`emailhost' if it is not found in the virtual host table. Note that
-'--urlhost' and '--emailhost' have precedence to this notation.
-
-If you spell the list name as just `mylist', then the email hostname will be
-taken from DEFAULT_EMAIL_HOST and the url will be taken from DEFAULT_URL (as
-defined in your Defaults.py file or overridden by settings in mm_cfg.py).
+If 'listname' does not have an '@', the list will be situated in the default
+domain, which Mailman created when you configured the system.
Note that listnames are forced to lowercase."""))
parser.add_option('-l', '--language',
@@ -91,12 +59,6 @@ Note that listnames are forced to lowercase."""))
help=_("""\
Make the list's preferred language LANGUAGE, which must be a two letter
language code."""))
- parser.add_option('-u', '--urlhost',
- type='string', action='store',
- help=_('The hostname for the web interface'))
- parser.add_option('-e', '--emailhost',
- type='string', action='store',
- help=_('The hostname for the email server'))
parser.add_option('-q', '--quiet',
default=False, action='store_true',
help=_("""\
@@ -140,17 +102,20 @@ def main():
listname = listname.lower()
if '@' in listname:
- # Note that --urlhost and --emailhost have precedence
- listname, domain = listname.split('@', 1)
- urlhost = opts.urlhost or domain
- emailhost = opts.emailhost or config.VIRTUAL_HOSTS.get(domain, domain)
-
- urlhost = opts.urlhost or config.DEFAULT_URL_HOST
- host_name = (opts.emailhost or
- config.VIRTUAL_HOSTS.get(urlhost, config.DEFAULT_EMAIL_HOST))
- web_page_url = config.DEFAULT_URL_PATTERN % urlhost
-
- if Utils.list_exists(listname):
+ fqdn_listname = listname
+ listname, email_host = listname.split('@', 1)
+ url_host = config.domains.get(email_host)
+ if not url_host:
+ print >> sys.stderr, _('Undefined domain: $email_host')
+ sys.exit(1)
+ else:
+ email_host = config.DEFAULT_EMAIL_HOST
+ url_host = config.DEFAULT_URL_HOST
+ fqdn_listname = '%s@%s' % (listname, email_host)
+ web_page_url = config.DEFAULT_URL_PATTERN % url_host
+ # Even though MailList.Create() will check to make sure the list doesn't
+ # yet exist, do it now for better usability.
+ if Utils.list_exists(fqdn_listname):
parser.print_help()
print >> sys.stderr, _('List already exists: $listname')
@@ -177,6 +142,9 @@ def main():
print >> sys.stderr, _('The list password cannot be empty')
mlist = MailList.MailList()
+ # Assign the preferred language before Create() since that will use it to
+ # set available_languages.
+ mlist.preferred_language = opts.language
try:
pw = sha.new(listpasswd).hexdigest()
# Guarantee that all newly created files have the proper permission.
@@ -185,7 +153,7 @@ def main():
oldmask = os.umask(002)
try:
try:
- mlist.Create(listname, owner_mail, pw)
+ mlist.Create(fqdn_listname, owner_mail, pw)
except Errors.BadListNameError, s:
parser.print_help()
print >> sys.stderr, _('Illegal list name: $s')
@@ -200,13 +168,6 @@ def main():
sys.exit(1)
finally:
os.umask(oldmask)
-
- # Assign domain-specific attributes
- mlist.host_name = host_name
- mlist.web_page_url = web_page_url
-
- # And assign the preferred language
- mlist.preferred_language = opts.language
mlist.Save()
finally:
mlist.Unlock()
@@ -222,14 +183,13 @@ def main():
print _('Hit enter to notify $listname owner...'),
sys.stdin.readline()
if not opts.quiet:
- siteowner = Utils.get_site_email(mlist.host_name, 'owner')
d = dict(
listname = listname,
password = listpasswd,
admin_url = mlist.GetScriptURL('admin', absolute=True),
listinfo_url = mlist.GetScriptURL('listinfo', absolute=True),
requestaddr = mlist.GetRequestEmail(),
- siteowner = siteowner,
+ siteowner = mlist.GetNoReplyEmail(),
)
text = Utils.maketext('newlist.txt', d, mlist=mlist)
# Set the I18N language to the list's preferred language so the header
@@ -239,7 +199,7 @@ def main():
i18n.set_language(mlist.preferred_language)
try:
msg = Message.UserNotification(
- owner_mail, siteowner,
+ owner_mail, mlist.GetNoReplyEmail(),
_('Your new mailing list: $listname'),
text, mlist.preferred_language)
msg.send(mlist)
diff --git a/Mailman/bin/owner.py b/Mailman/bin/owner.py
index 34f796ad3..f1eb7ce62 100644
--- a/Mailman/bin/owner.py
+++ b/Mailman/bin/owner.py
@@ -60,7 +60,6 @@ def main():
inq.enqueue(sys.stdin.read(),
listname=listname,
_plaintext=True,
- envsender=Utils.get_site_email(extra='bounces'),
pipeline=mm_cfg.OWNER_PIPELINE,
toowner=True)
diff --git a/Mailman/bin/rmlist.py b/Mailman/bin/rmlist.py
index 9655327f0..861e79226 100644
--- a/Mailman/bin/rmlist.py
+++ b/Mailman/bin/rmlist.py
@@ -78,6 +78,9 @@ def main():
config.load(opts.config)
listname = args[0].lower().strip()
+ if '@' not in listname:
+ listname = '%s@%s' % (listname, config.DEFAULT_EMAIL_HOST)
+
if not Utils.list_exists(listname):
if not opts.archives:
print >> sys.stderr, _(
@@ -85,7 +88,7 @@ def main():
sys.exit(1)
else:
print _(
- 'No such list: $listname. Removing its residual archives.')
+ 'No such list: ${listname}. Removing its residual archives.')
if not opts.archives:
print _('Not removing archives. Reinvoke with -a to remove them.')
diff --git a/Mailman/bin/update.py b/Mailman/bin/update.py
index ea74abfd6..36812dcf8 100644
--- a/Mailman/bin/update.py
+++ b/Mailman/bin/update.py
@@ -196,6 +196,23 @@ def move_language_templates(mlist):
+def situate_list(listname):
+ # This turns the directory called 'listname' into a directory called
+ # 'listname@domain'. Start by finding out what the domain should be.
+ # A list's domain is its email host.
+ mlist = MailList.MailList(listname, lock=False)
+ fullname = mlist.fqdn_listname
+ oldpath = os.path.join(config.VAR_PREFIX, 'lists', listname)
+ newpath = os.path.join(config.VAR_PREFIX, 'lists', fullname)
+ if os.path.exists(newpath):
+ print >> sys.stderr, _('WARNING: could not situate list: $listname')
+ else:
+ os.rename(oldpath, newpath)
+ print _('situated list $listname to $fullname')
+ return fullname
+
+
+
def dolist(listname):
mlist = MailList.MailList(listname, lock=False)
try:
@@ -672,10 +689,8 @@ Exiting.""")
if not listnames:
print _('no lists == nothing to do, exiting')
return
- #
- # for people with web archiving, make sure the directories
+ # For people with web archiving, make sure the directories
# in the archiving are set with proper perms for b6.
- #
if os.path.isdir("%s/public_html/archives" % config.PREFIX):
print _("""\
fixing all the perms on your old html archives to work with b6
@@ -684,8 +699,12 @@ If your archives are big, this could take a minute or two...""")
archive_path_fixer, "")
print _('done')
for listname in listnames:
+ # With 2.2.0a0, all list names grew an @domain suffix. If you find a
+ # list without that, move it now.
+ if not '@' in listname:
+ listname = situate_list(listname)
print _('Updating mailing list: $listname')
- errors = errors + dolist(listname)
+ errors += dolist(listname)
print
print _('Updating Usenet watermarks')
wmfile = os.path.join(config.DATA_DIR, 'gate_watermarks')
diff --git a/Mailman/configuration.py b/Mailman/configuration.py
index cf3af03f3..1f1c52f19 100644
--- a/Mailman/configuration.py
+++ b/Mailman/configuration.py
@@ -21,12 +21,17 @@ import os
import errno
from Mailman import Defaults
+from Mailman import Errors
_missing = object()
class Configuration(object):
+ def __init__(self):
+ self.domains = {}
+ self._reverse = None
+
def load(self, filename=None):
# Load the configuration from the named file, or if not given, search
# in VAR_PREFIX for an etc/mailman.cfg file. If that file is missing,
@@ -39,10 +44,11 @@ class Configuration(object):
filename = os.path.join(Defaults.VAR_PREFIX, 'etc', 'mailman.cfg')
# Set up the execfile namespace
ns = Defaults.__dict__.copy()
- # Prune a few things
+ # Prune a few things, add a few things
del ns['__file__']
del ns['__name__']
del ns['__doc__']
+ ns['add_domain'] = self.add_domain
# Attempt our first choice
path = os.path.abspath(os.path.expanduser(filename))
try:
@@ -95,7 +101,41 @@ class Configuration(object):
self.CONFIG_FILE = os.path.join(etcdir, 'mailman.cfg')
self.LOCK_FILE = os.path.join(lockdir, 'master-qrunner')
# Now update our dict so attribute syntax just works
+ if 'add_domain' in ns:
+ del ns['add_domain']
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)
+
+ def add_domain(self, email_host, url_host):
+ """Add the definition of a virtual domain.
+
+ email_host is the right-hand side of the posting email address,
+ e.g. 'example.com' in 'mylist@example.com'. url_host is the host name
+ part of the exposed web pages, e.g. 'www.example.com'."""
+ 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)
diff --git a/Mailman/i18n.py b/Mailman/i18n.py
index 69f9801d1..4a32ce3e3 100644
--- a/Mailman/i18n.py
+++ b/Mailman/i18n.py
@@ -110,7 +110,7 @@ def _(s):
if isinstance(v, unicode):
d[k] = v.encode(charset, 'replace')
# Are we using $-strings or %-strings?
- if d.get('__i18n_templates__', False):
+ if use_templates:
return Template(tns).safe_substitute(attrdict(d))
return tns % SafeDict(d)
diff --git a/bin/withlist b/bin/withlist
index 4209032d6..b6bc8911f 100644
--- a/bin/withlist
+++ b/bin/withlist
@@ -130,12 +130,7 @@ from Mailman import MailList
from Mailman import Utils
from Mailman import loginit
from Mailman.i18n import _
-
-try:
- True, False
-except NameError:
- True = 1
- False = 0
+from Mailman.configuration import config
# `m' will be the MailList object and `r' will be the results of the callable
@@ -246,6 +241,9 @@ def main():
if all and not run:
usage(1, _('--all requires --run'))
+ # XXX Allow for specifying the configuration file via -C/--config
+ config.load()
+
# The default for interact is 1 unless -r was given
if interact is None:
if run is None: