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
First crack at real virtual domain support, i.e. mailing lists with the same
name in more than one domain. - Totally eradicate MAILMAN_SITE_LIST, and in fact the entire need for a site list. The functions that the site list previously performed are either removed or supported in other ways. For example, instead of forwarding owner bounces to the site list, we now have a SITE_OWNER_ADDRESS which should point to a human, and such bounces are sent there instead. There's also a "no reply" email address that should be set up to go to devnull. For any message that never expects a reply, the sender is set to this address. - Remove the Site.py module. It was an experimental approach to trying to support virtual domains, and we're going to do it so much better now that this module is no longer necessary. Site._makedirs() -> Utils.makedir(). - VIRTUAL_HOST_OVERVIEW is completely removed, since now virtual hosts are always enabled. Virtual domains should be added to mailman.cfg by using the new add_domain() function. add_virtualhost() is gone. If no virtual domains are added explicitly, we add the default one that configure guessed (but we never add that if domains are added explicitly). - Utils.get_domain() -> Utils.get_request_domain() - withlist code cleanup and make sure that we load etc/mailman.cfg - A new base exception called MailmanException is added, from which all exceptions defined in Errors.py ultimately derive. MailmanError is retained and derives from MailmanException. - BadDomainSpecificationError is added. - Remove the -V/--virtual-host-overview option from list_lists and add instead -d/--domain and -f/--full. - bin/update probably works but needs more testing. - bin/newlist and bin/rmlist take fqdn list names, but default to the default domain if @whatever isn't given. newlist's -u/--urlhost and -e/--emailhost options are removed. The domain that the list is being added to must already exist. - Minor code cleanup in Message.py - Bump version to 2.2.0a1 - The Configuration object grows a .domain dictionary which maps email hosts to url hosts. The reverse mapping is supported, but not directly; use Configuration.get_email_host() instead. - Mailman/Cgi/create is converted from mm_cfg to config, and some minor code cleanup is performed. Also, convert to __i18n_templates__ = True. - New MailList APIs: + property .fqdn_listname + GetNoReplyEmail() + Create() API changes and refactoring.
-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: