summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbwarsaw2006-07-08 17:37:55 +0000
committerbwarsaw2006-07-08 17:37:55 +0000
commitcbef3114de3e80b9436d909b11568858e3a1cf42 (patch)
treef567fe3fbc331fe399b92e93f80068e8995a7821
parent60b723291e592ff7925e1b15b79161d1cdac5938 (diff)
downloadmailman-cbef3114de3e80b9436d909b11568858e3a1cf42.tar.gz
mailman-cbef3114de3e80b9436d909b11568858e3a1cf42.tar.zst
mailman-cbef3114de3e80b9436d909b11568858e3a1cf42.zip
-rw-r--r--Mailman/Defaults.py.in34
-rw-r--r--Mailman/Handlers/SMTPDirect.py36
-rw-r--r--Mailman/MTA/Utils.py21
-rw-r--r--Mailman/MailList.py150
-rw-r--r--Mailman/Message.py10
-rw-r--r--Mailman/Queue/ArchRunner.py17
-rw-r--r--Mailman/Queue/BounceRunner.py22
-rw-r--r--Mailman/Queue/CommandRunner.py14
-rw-r--r--Mailman/Queue/IncomingRunner.py8
-rw-r--r--Mailman/Queue/MaildirRunner.py22
-rw-r--r--Mailman/Queue/NewsRunner.py15
-rw-r--r--Mailman/Queue/OutgoingRunner.py17
-rw-r--r--Mailman/Queue/RetryRunner.py11
-rw-r--r--Mailman/Queue/Runner.py15
-rw-r--r--Mailman/Queue/Switchboard.py7
-rw-r--r--Mailman/Queue/VirginRunner.py11
-rw-r--r--Mailman/Queue/__init__.py15
-rw-r--r--Mailman/Queue/sbcache.py5
-rw-r--r--Mailman/Site.py13
-rw-r--r--Mailman/Utils.py53
-rw-r--r--Mailman/Version.py4
-rw-r--r--Mailman/bin/list_lists.py11
-rw-r--r--Mailman/bin/mailmanctl.py521
-rw-r--r--Mailman/bin/newlist.py44
-rw-r--r--Mailman/bin/qrunner.py258
-rw-r--r--Mailman/bin/rmlist.py19
-rw-r--r--Mailman/bin/update.py767
-rw-r--r--Mailman/configuration.py103
-rw-r--r--Mailman/i18n.py6
-rw-r--r--Mailman/loginit.py4
-rw-r--r--Mailman/testing/emailbase.py20
-rw-r--r--Makefile.in2
-rw-r--r--bin/Makefile.in9
-rw-r--r--bin/mailmanctl545
-rw-r--r--bin/qrunner278
-rwxr-xr-xbin/update807
-rwxr-xr-xconfigure5
-rw-r--r--configure.in5
-rw-r--r--misc/Makefile.in2
-rw-r--r--misc/mailman.cfg.sample66
40 files changed, 2029 insertions, 1943 deletions
diff --git a/Mailman/Defaults.py.in b/Mailman/Defaults.py.in
index 27eb73ee5..d8248a3cf 100644
--- a/Mailman/Defaults.py.in
+++ b/Mailman/Defaults.py.in
@@ -1264,44 +1264,10 @@ AuthListAdmin = 3 # List Administrator (total control over list)
AuthListModerator = 4 # List Moderator (can only handle held requests)
AuthSiteAdmin = 5 # Site Administrator (total control over everything)
-# Useful directories
-LIST_DATA_DIR = os.path.join(VAR_PREFIX, 'lists')
-LOG_DIR = os.path.join(VAR_PREFIX, 'logs')
-LOCK_DIR = os.path.join(VAR_PREFIX, 'locks')
-DATA_DIR = os.path.join(VAR_PREFIX, 'data')
-SPAM_DIR = os.path.join(VAR_PREFIX, 'spam')
-WRAPPER_DIR = os.path.join(EXEC_PREFIX, 'mail')
-BIN_DIR = os.path.join(PREFIX, 'bin')
-SCRIPTS_DIR = os.path.join(PREFIX, 'scripts')
-TEMPLATE_DIR = os.path.join(PREFIX, 'templates')
-MESSAGES_DIR = os.path.join(PREFIX, 'messages')
-PUBLIC_ARCHIVE_FILE_DIR = os.path.join(VAR_PREFIX, 'archives', 'public')
-PRIVATE_ARCHIVE_FILE_DIR = os.path.join(VAR_PREFIX, 'archives', 'private')
-
-# Directories used by the qrunner subsystem
-QUEUE_DIR = os.path.join(VAR_PREFIX, 'qfiles')
-INQUEUE_DIR = os.path.join(QUEUE_DIR, 'in')
-OUTQUEUE_DIR = os.path.join(QUEUE_DIR, 'out')
-CMDQUEUE_DIR = os.path.join(QUEUE_DIR, 'commands')
-BOUNCEQUEUE_DIR = os.path.join(QUEUE_DIR, 'bounces')
-NEWSQUEUE_DIR = os.path.join(QUEUE_DIR, 'news')
-ARCHQUEUE_DIR = os.path.join(QUEUE_DIR, 'archive')
-SHUNTQUEUE_DIR = os.path.join(QUEUE_DIR, 'shunt')
-VIRGINQUEUE_DIR = os.path.join(QUEUE_DIR, 'virgin')
-BADQUEUE_DIR = os.path.join(QUEUE_DIR, 'bad')
-RETRYQUEUE_DIR = os.path.join(QUEUE_DIR, 'retry')
-MAILDIR_DIR = os.path.join(QUEUE_DIR, 'maildir')
-
-# Other useful files
-PIDFILE = os.path.join(DATA_DIR, 'master-qrunner.pid')
-SITE_PW_FILE = os.path.join(DATA_DIR, 'adm.pw')
-LISTCREATOR_PW_FILE = os.path.join(DATA_DIR, 'creator.pw')
-
# Import a bunch of version numbers
from Version import *
-MAILMAN_VERSION = 'GNU Mailman ' + VERSION
# Vgg: Language descriptions and charsets dictionary, any new supported
# language must have a corresponding entry here. Key is the name of the
diff --git a/Mailman/Handlers/SMTPDirect.py b/Mailman/Handlers/SMTPDirect.py
index e4fa6ec3b..60fb52359 100644
--- a/Mailman/Handlers/SMTPDirect.py
+++ b/Mailman/Handlers/SMTPDirect.py
@@ -38,19 +38,19 @@ from email.Header import Header
from email.Utils import formataddr
from Mailman import Errors
-from Mailman import mm_cfg
from Mailman import Utils
from Mailman.Handlers import Decorate
from Mailman.SafeDict import MsgSafeDict
+from Mailman.configuration import config
DOT = '.'
log = logging.getLogger('mailman.smtp')
flog = logging.getLogger('mailman.smtp-failure')
-every_log = logging.getLogger('mailman.' + mm_cfg.SMTP_LOG_EVERY_MESSAGE[0])
-success_log = logging.getLogger('mailman.' + mm_cfg.SMTP_LOG_SUCCESS[0])
-refused_log = logging.getLogger('mailman.' + mm_cfg.SMTP_LOG_REFUSED[0])
-failure_log = logging.getLogger('mailman.' + mm_cfg.SMTP_LOG_EACH_FAILURE[0])
+every_log = logging.getLogger('mailman.' + config.SMTP_LOG_EVERY_MESSAGE[0])
+success_log = logging.getLogger('mailman.' + config.SMTP_LOG_SUCCESS[0])
+refused_log = logging.getLogger('mailman.' + config.SMTP_LOG_REFUSED[0])
+failure_log = logging.getLogger('mailman.' + config.SMTP_LOG_EACH_FAILURE[0])
@@ -61,8 +61,8 @@ class Connection:
def __connect(self):
self.__conn = smtplib.SMTP()
- self.__conn.connect(mm_cfg.SMTPHOST, mm_cfg.SMTPPORT)
- self.__numsessions = mm_cfg.SMTP_MAX_SESSIONS_PER_CONNECTION
+ self.__conn.connect(config.SMTPHOST, config.SMTPPORT)
+ self.__numsessions = config.SMTP_MAX_SESSIONS_PER_CONNECTION
def sendmail(self, envsender, recips, msgtext):
if self.__conn is None:
@@ -118,10 +118,10 @@ def process(mlist, msg, msgdata):
chunks = [[recip] for recip in recips]
msgdata['personalize'] = 1
deliveryfunc = verpdeliver
- elif mm_cfg.SMTP_MAX_RCPTS <= 0:
+ elif config.SMTP_MAX_RCPTS <= 0:
chunks = [recips]
else:
- chunks = chunkify(recips, mm_cfg.SMTP_MAX_RCPTS)
+ chunks = chunkify(recips, config.SMTP_MAX_RCPTS)
# See if this is an unshunted message for which some were undelivered
if msgdata.has_key('undelivered'):
chunks = msgdata['undelivered']
@@ -181,12 +181,12 @@ def process(mlist, msg, msgdata):
# We have to use the copy() method because extended call syntax requires a
# concrete dictionary object; it does not allow a generic mapping (XXX is
# this still true in Python 2.3?).
- if mm_cfg.SMTP_LOG_EVERY_MESSAGE:
- every_log.info('%s', mm_cfg.SMTP_LOG_EVERY_MESSAGE[1] % d)
+ if config.SMTP_LOG_EVERY_MESSAGE:
+ every_log.info('%s', config.SMTP_LOG_EVERY_MESSAGE[1] % d)
if refused:
- if mm_cfg.SMTP_LOG_REFUSED:
- refused_log.info('%s', mm_cfg.SMTP_LOG_REFUSED[1] % d)
+ if config.SMTP_LOG_REFUSED:
+ refused_log.info('%s', config.SMTP_LOG_REFUSED[1] % d)
elif msgdata.get('tolist'):
# Log the successful post, but only if it really was a post to the
@@ -194,8 +194,8 @@ def process(mlist, msg, msgdata):
# -request addrs should never get here. BAW: it may be useful to log
# the other messages, but in that case, we should probably have a
# separate configuration variable to control that.
- if mm_cfg.SMTP_LOG_SUCCESS:
- success_log.info('%s', mm_cfg.SMTP_LOG_SUCCESS[1] % d)
+ if config.SMTP_LOG_SUCCESS:
+ success_log.info('%s', config.SMTP_LOG_SUCCESS[1] % d)
# Process any failed deliveries.
tempfailures = []
@@ -217,11 +217,11 @@ def process(mlist, msg, msgdata):
# Deal with persistent transient failures by queuing them up for
# future delivery. TBD: this could generate lots of log entries!
tempfailures.append(recip)
- if mm_cfg.SMTP_LOG_EACH_FAILURE:
+ if config.SMTP_LOG_EACH_FAILURE:
d.update({'recipient': recip,
'failcode' : code,
'failmsg' : smtpmsg})
- failure_log.info('%s', mm_cfg.SMTP_LOG_EACH_FAILURE[1] % d)
+ failure_log.info('%s', config.SMTP_LOG_EACH_FAILURE[1] % d)
# Return the results
if tempfailures or permfailures:
raise Errors.SomeRecipientsFailed(tempfailures, permfailures)
@@ -300,7 +300,7 @@ def verpdeliver(mlist, msg, msgdata, envsender, failures, conn):
'mailbox': rmailbox,
'host' : DOT.join(rdomain),
}
- envsender = '%s@%s' % ((mm_cfg.VERP_FORMAT % d), DOT.join(bdomain))
+ envsender = '%s@%s' % ((config.VERP_FORMAT % d), DOT.join(bdomain))
if mlist.personalize == 2:
# When fully personalizing, we want the To address to point to the
# recipient, not to the mailing list
diff --git a/Mailman/MTA/Utils.py b/Mailman/MTA/Utils.py
index 14562de66..ef3df0cb8 100644
--- a/Mailman/MTA/Utils.py
+++ b/Mailman/MTA/Utils.py
@@ -1,25 +1,26 @@
-# Copyright (C) 2001,2002 by the Free Software Foundation, Inc.
+# Copyright (C) 2001-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.
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
"""Utilities for list creation/deletion hooks."""
import os
import pwd
-from Mailman import mm_cfg
+from Mailman.configuration import config
@@ -35,7 +36,7 @@ def getusername():
def _makealiases_mailprog(listname):
- wrapper = os.path.join(mm_cfg.WRAPPER_DIR, 'mailman')
+ wrapper = os.path.join(config.WRAPPER_DIR, 'mailman')
# Most of the list alias extensions are quite regular. I.e. if the
# message is delivered to listname-foobar, it will be filtered to a
# program called foobar. There are two exceptions:
@@ -57,7 +58,7 @@ def _makealiases_mailprog(listname):
def _makealiases_maildir(listname):
- maildir = mm_cfg.MAILDIR_DIR
+ maildir = config.MAILDIR_DIR
if not maildir.endswith('/'):
maildir += '/'
# Deliver everything using maildir style. This way there's no mail
@@ -73,7 +74,9 @@ def _makealiases_maildir(listname):
-if mm_cfg.USE_MAILDIR:
+# XXX This won't work if Mailman.MTA.Utils is imported before the
+# configuration is loaded.
+if config.USE_MAILDIR:
makealiases = _makealiases_maildir
else:
makealiases = _makealiases_mailprog
diff --git a/Mailman/MailList.py b/Mailman/MailList.py
index 04fb0f22c..ecf449246 100644
--- a/Mailman/MailList.py
+++ b/Mailman/MailList.py
@@ -45,8 +45,8 @@ from email.Utils import getaddresses, formataddr, parseaddr
from Mailman import Errors
from Mailman import LockFile
from Mailman import Utils
-from Mailman import mm_cfg
from Mailman.UserDesc import UserDesc
+from Mailman.configuration import config
# Base classes
from Mailman import Pending
@@ -196,19 +196,19 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
return self.getListAddress('owner')
def GetRequestEmail(self, cookie=''):
- if mm_cfg.VERP_CONFIRMATIONS and cookie:
+ if config.VERP_CONFIRMATIONS and cookie:
return self.GetConfirmEmail(cookie)
else:
return self.getListAddress('request')
def GetConfirmEmail(self, cookie):
- return mm_cfg.VERP_CONFIRM_FORMAT % {
+ return config.VERP_CONFIRM_FORMAT % {
'addr' : '%s-confirm' % self.internal_name(),
'cookie': cookie,
} + '@' + self.host_name
def GetConfirmJoinSubject(self, listname, cookie):
- if mm_cfg.VERP_CONFIRMATIONS and cookie:
+ if config.VERP_CONFIRMATIONS and cookie:
cset = i18n.get_translation().charset() or \
Utils.GetCharSet(self.preferred_language)
subj = Header(
@@ -219,7 +219,7 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
return 'confirm ' + cookie
def GetConfirmLeaveSubject(self, listname, cookie):
- if mm_cfg.VERP_CONFIRMATIONS and cookie:
+ if config.VERP_CONFIRMATIONS and cookie:
cset = i18n.get_translation().charset() or \
Utils.GetCharSet(self.preferred_language)
subj = Header(
@@ -269,8 +269,8 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
# need to reload, otherwise... we do.
self.__timestamp = 0
self.__lock = LockFile.LockFile(
- os.path.join(mm_cfg.LOCK_DIR, name or '<site>') + '.lock',
- lifetime=mm_cfg.LIST_LOCK_LIFETIME)
+ os.path.join(config.LOCK_DIR, name or '<site>') + '.lock',
+ lifetime=config.LIST_LOCK_LIFETIME)
self._internal_name = name
if name:
self._full_path = Site.get_listpath(name)
@@ -299,7 +299,7 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
# Must save this state, even though it isn't configurable
self.volume = 1
self.members = {} # self.digest_members is initted in mm_digest
- self.data_version = mm_cfg.DATA_FILE_VERSION
+ self.data_version = config.DATA_FILE_VERSION
self.last_post_time = 0
self.post_id = 1. # A float so it never has a chance to overflow.
@@ -307,76 +307,76 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
self.language = {}
self.usernames = {}
self.passwords = {}
- self.new_member_options = mm_cfg.DEFAULT_NEW_MEMBER_OPTIONS
+ self.new_member_options = config.DEFAULT_NEW_MEMBER_OPTIONS
# This stuff is configurable
self.respond_to_post_requests = 1
- self.advertised = mm_cfg.DEFAULT_LIST_ADVERTISED
- self.max_num_recipients = mm_cfg.DEFAULT_MAX_NUM_RECIPIENTS
- self.max_message_size = mm_cfg.DEFAULT_MAX_MESSAGE_SIZE
+ self.advertised = config.DEFAULT_LIST_ADVERTISED
+ self.max_num_recipients = config.DEFAULT_MAX_NUM_RECIPIENTS
+ self.max_message_size = config.DEFAULT_MAX_MESSAGE_SIZE
# See the note in Defaults.py concerning DEFAULT_HOST_NAME
# vs. DEFAULT_EMAIL_HOST.
- self.host_name = mm_cfg.DEFAULT_HOST_NAME or mm_cfg.DEFAULT_EMAIL_HOST
+ self.host_name = config.DEFAULT_HOST_NAME or config.DEFAULT_EMAIL_HOST
self.web_page_url = (
- mm_cfg.DEFAULT_URL or
- mm_cfg.DEFAULT_URL_PATTERN % mm_cfg.DEFAULT_URL_HOST)
+ config.DEFAULT_URL or
+ config.DEFAULT_URL_PATTERN % config.DEFAULT_URL_HOST)
self.owner = [admin]
self.moderator = []
- self.reply_goes_to_list = mm_cfg.DEFAULT_REPLY_GOES_TO_LIST
+ self.reply_goes_to_list = config.DEFAULT_REPLY_GOES_TO_LIST
self.reply_to_address = ''
- self.first_strip_reply_to = mm_cfg.DEFAULT_FIRST_STRIP_REPLY_TO
- self.admin_immed_notify = mm_cfg.DEFAULT_ADMIN_IMMED_NOTIFY
+ self.first_strip_reply_to = config.DEFAULT_FIRST_STRIP_REPLY_TO
+ self.admin_immed_notify = config.DEFAULT_ADMIN_IMMED_NOTIFY
self.admin_notify_mchanges = \
- mm_cfg.DEFAULT_ADMIN_NOTIFY_MCHANGES
+ config.DEFAULT_ADMIN_NOTIFY_MCHANGES
self.require_explicit_destination = \
- mm_cfg.DEFAULT_REQUIRE_EXPLICIT_DESTINATION
- self.acceptable_aliases = mm_cfg.DEFAULT_ACCEPTABLE_ALIASES
- self.umbrella_list = mm_cfg.DEFAULT_UMBRELLA_LIST
+ config.DEFAULT_REQUIRE_EXPLICIT_DESTINATION
+ self.acceptable_aliases = config.DEFAULT_ACCEPTABLE_ALIASES
+ self.umbrella_list = config.DEFAULT_UMBRELLA_LIST
self.umbrella_member_suffix = \
- mm_cfg.DEFAULT_UMBRELLA_MEMBER_ADMIN_SUFFIX
- self.send_reminders = mm_cfg.DEFAULT_SEND_REMINDERS
- self.send_welcome_msg = mm_cfg.DEFAULT_SEND_WELCOME_MSG
- self.send_goodbye_msg = mm_cfg.DEFAULT_SEND_GOODBYE_MSG
+ config.DEFAULT_UMBRELLA_MEMBER_ADMIN_SUFFIX
+ self.send_reminders = config.DEFAULT_SEND_REMINDERS
+ self.send_welcome_msg = config.DEFAULT_SEND_WELCOME_MSG
+ self.send_goodbye_msg = config.DEFAULT_SEND_GOODBYE_MSG
self.bounce_matching_headers = \
- mm_cfg.DEFAULT_BOUNCE_MATCHING_HEADERS
+ config.DEFAULT_BOUNCE_MATCHING_HEADERS
self.header_filter_rules = []
- self.anonymous_list = mm_cfg.DEFAULT_ANONYMOUS_LIST
+ self.anonymous_list = config.DEFAULT_ANONYMOUS_LIST
internalname = self.internal_name()
self.real_name = internalname[0].upper() + internalname[1:]
self.description = ''
self.info = ''
self.welcome_msg = ''
self.goodbye_msg = ''
- self.subscribe_policy = mm_cfg.DEFAULT_SUBSCRIBE_POLICY
- self.subscribe_auto_approval = mm_cfg.DEFAULT_SUBSCRIBE_AUTO_APPROVAL
- self.unsubscribe_policy = mm_cfg.DEFAULT_UNSUBSCRIBE_POLICY
- self.private_roster = mm_cfg.DEFAULT_PRIVATE_ROSTER
- self.obscure_addresses = mm_cfg.DEFAULT_OBSCURE_ADDRESSES
- self.admin_member_chunksize = mm_cfg.DEFAULT_ADMIN_MEMBER_CHUNKSIZE
- self.administrivia = mm_cfg.DEFAULT_ADMINISTRIVIA
- self.preferred_language = mm_cfg.DEFAULT_SERVER_LANGUAGE
+ self.subscribe_policy = config.DEFAULT_SUBSCRIBE_POLICY
+ self.subscribe_auto_approval = config.DEFAULT_SUBSCRIBE_AUTO_APPROVAL
+ self.unsubscribe_policy = config.DEFAULT_UNSUBSCRIBE_POLICY
+ self.private_roster = config.DEFAULT_PRIVATE_ROSTER
+ self.obscure_addresses = config.DEFAULT_OBSCURE_ADDRESSES
+ self.admin_member_chunksize = config.DEFAULT_ADMIN_MEMBER_CHUNKSIZE
+ self.administrivia = config.DEFAULT_ADMINISTRIVIA
+ self.preferred_language = config.DEFAULT_SERVER_LANGUAGE
self.available_languages = []
self.include_rfc2369_headers = 1
self.include_list_post_header = 1
- self.filter_mime_types = mm_cfg.DEFAULT_FILTER_MIME_TYPES
- self.pass_mime_types = mm_cfg.DEFAULT_PASS_MIME_TYPES
+ self.filter_mime_types = config.DEFAULT_FILTER_MIME_TYPES
+ self.pass_mime_types = config.DEFAULT_PASS_MIME_TYPES
self.filter_filename_extensions = \
- mm_cfg.DEFAULT_FILTER_FILENAME_EXTENSIONS
- self.pass_filename_extensions = mm_cfg.DEFAULT_PASS_FILENAME_EXTENSIONS
- self.filter_content = mm_cfg.DEFAULT_FILTER_CONTENT
- self.collapse_alternatives = mm_cfg.DEFAULT_COLLAPSE_ALTERNATIVES
+ config.DEFAULT_FILTER_FILENAME_EXTENSIONS
+ self.pass_filename_extensions = config.DEFAULT_PASS_FILENAME_EXTENSIONS
+ self.filter_content = config.DEFAULT_FILTER_CONTENT
+ self.collapse_alternatives = config.DEFAULT_COLLAPSE_ALTERNATIVES
self.convert_html_to_plaintext = \
- mm_cfg.DEFAULT_CONVERT_HTML_TO_PLAINTEXT
- self.filter_action = mm_cfg.DEFAULT_FILTER_ACTION
+ config.DEFAULT_CONVERT_HTML_TO_PLAINTEXT
+ self.filter_action = config.DEFAULT_FILTER_ACTION
# Analogs to these are initted in Digester.InitVars
- self.nondigestable = mm_cfg.DEFAULT_NONDIGESTABLE
+ self.nondigestable = config.DEFAULT_NONDIGESTABLE
self.personalize = 0
# New sender-centric moderation (privacy) options
self.default_member_moderation = \
- mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION
+ config.DEFAULT_DEFAULT_MEMBER_MODERATION
# Emergency moderation bit
self.emergency = 0
- # This really ought to default to mm_cfg.HOLD, but that doesn't work
+ # This really ought to default to config.HOLD, but that doesn't work
# with the current GUI description model. So, 0==Hold, 1==Reject,
# 2==Discard
self.member_moderation_action = 0
@@ -385,8 +385,8 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
self.hold_these_nonmembers = []
self.reject_these_nonmembers = []
self.discard_these_nonmembers = []
- self.forward_auto_discards = mm_cfg.DEFAULT_FORWARD_AUTO_DISCARDS
- self.generic_nonmember_action = mm_cfg.DEFAULT_GENERIC_NONMEMBER_ACTION
+ self.forward_auto_discards = config.DEFAULT_FORWARD_AUTO_DISCARDS
+ self.generic_nonmember_action = config.DEFAULT_GENERIC_NONMEMBER_ACTION
self.nonmember_rejection_notice = ''
# Ban lists
self.ban_list = []
@@ -403,9 +403,9 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
# These need to come near the bottom because they're dependent on
# other settings.
- self.subject_prefix = mm_cfg.DEFAULT_SUBJECT_PREFIX % self.__dict__
- self.msg_header = mm_cfg.DEFAULT_MSG_HEADER
- self.msg_footer = mm_cfg.DEFAULT_MSG_FOOTER
+ self.subject_prefix = config.DEFAULT_SUBJECT_PREFIX % self.__dict__
+ self.msg_header = config.DEFAULT_MSG_HEADER
+ self.msg_footer = config.DEFAULT_MSG_FOOTER
# Set this to Never if the list's preferred language uses us-ascii,
# otherwise set it to As Needed
if Utils.GetCharSet(self.preferred_language) == 'us-ascii':
@@ -413,9 +413,9 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
else:
self.encode_ascii_prefixes = 2
# scrub regular delivery
- self.scrub_nondigest = mm_cfg.DEFAULT_SCRUB_NONDIGEST
+ self.scrub_nondigest = config.DEFAULT_SCRUB_NONDIGEST
# automatic discarding
- self.max_days_to_hold = mm_cfg.DEFAULT_MAX_DAYS_TO_HOLD
+ self.max_days_to_hold = config.DEFAULT_MAX_DAYS_TO_HOLD
#
@@ -425,17 +425,17 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
class CategoryDict(UserDict):
def __init__(self):
UserDict.__init__(self)
- self.keysinorder = mm_cfg.ADMIN_CATEGORIES[:]
+ self.keysinorder = config.ADMIN_CATEGORIES[:]
def keys(self):
return self.keysinorder
def items(self):
items = []
- for k in mm_cfg.ADMIN_CATEGORIES:
+ for k in config.ADMIN_CATEGORIES:
items.append((k, self.data[k]))
return items
def values(self):
values = []
- for k in mm_cfg.ADMIN_CATEGORIES:
+ for k in config.ADMIN_CATEGORIES:
values.append(self.data[k])
return values
@@ -476,7 +476,7 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
# However, most scripts already catch MMBadEmailError as exceptions on
# the admin's email address, so transform the exception.
if emailhost is None:
- emailhost = mm_cfg.DEFAULT_EMAIL_HOST
+ emailhost = config.DEFAULT_EMAIL_HOST
postingaddr = '%s@%s' % (name, emailhost)
try:
Utils.ValidateEmail(postingaddr)
@@ -515,7 +515,7 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
# Use a binary format... it's more efficient.
cPickle.dump(dict, fp, 1)
fp.flush()
- if mm_cfg.SYNC_AFTER_WRITE:
+ if config.SYNC_AFTER_WRITE:
os.fsync(fp.fileno())
fp.close()
except IOError, e:
@@ -697,7 +697,7 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
#
def CheckVersion(self, stored_state):
"""Auto-update schema if necessary."""
- if self.data_version >= mm_cfg.DATA_FILE_VERSION:
+ if self.data_version >= config.DATA_FILE_VERSION:
return
# Initialize any new variables
self.InitVars()
@@ -712,7 +712,7 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
try:
from versions import Update
Update(self, stored_state)
- self.data_version = mm_cfg.DATA_FILE_VERSION
+ self.data_version = config.DATA_FILE_VERSION
self.Save()
finally:
if not waslocked:
@@ -725,8 +725,8 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
# URL is empty; substitute faulty value with (hopefully sane)
# default. Note that DEFAULT_URL is obsolete.
self.web_page_url = (
- mm_cfg.DEFAULT_URL or
- mm_cfg.DEFAULT_URL_PATTERN % mm_cfg.DEFAULT_URL_HOST)
+ config.DEFAULT_URL or
+ config.DEFAULT_URL_PATTERN % config.DEFAULT_URL_HOST)
if self.web_page_url and self.web_page_url[-1] <> '/':
self.web_page_url = self.web_page_url + '/'
# Legacy reply_to_address could be an illegal value. We now verify
@@ -958,9 +958,9 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
# Do the actual addition
self.addNewMember(email, realname=name, digest=digest,
password=password, language=lang)
- self.setMemberOption(email, mm_cfg.DisableMime,
+ self.setMemberOption(email, config.DisableMime,
1 - self.mime_is_default_digest)
- self.setMemberOption(email, mm_cfg.Moderate,
+ self.setMemberOption(email, config.Moderate,
self.default_member_moderation)
# Now send and log results
if digest:
@@ -1283,17 +1283,17 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin,
if approved is not None:
# Does it match the list password? Note that we purposefully
# do not allow the site password here.
- if self.Authenticate([mm_cfg.AuthListAdmin,
- mm_cfg.AuthListModerator],
- approved) <> mm_cfg.UnAuthorized:
- action = mm_cfg.APPROVE
+ if self.Authenticate([config.AuthListAdmin,
+ config.AuthListModerator],
+ approved) <> config.UnAuthorized:
+ action = config.APPROVE
else:
# The password didn't match. Re-pend the message and
# inform the list moderators about the problem.
self.pend_repend(cookie, rec)
raise Errors.MMBadPasswordError
else:
- action = mm_cfg.DISCARD
+ action = config.DISCARD
try:
self.HandleRequest(id, action)
except KeyError:
@@ -1459,7 +1459,7 @@ bad regexp in bounce_matching_header line: %s
lang = self.preferred_language
i18n.set_language(lang)
# No limit
- if mm_cfg.MAX_AUTORESPONSES_PER_DAY == 0:
+ if config.MAX_AUTORESPONSES_PER_DAY == 0:
return 1
today = time.localtime()[:3]
info = self.hold_and_cmd_autoresponses.get(sender)
@@ -1473,7 +1473,7 @@ bad regexp in bounce_matching_header line: %s
# They've already hit the limit for today.
vlog.info('-request/hold autoresponse discarded for: %s', sender)
return 0
- if count >= mm_cfg.MAX_AUTORESPONSES_PER_DAY:
+ if count >= config.MAX_AUTORESPONSES_PER_DAY:
vlog.info('-request/hold autoresponse limit hit for: %s', sender)
self.hold_and_cmd_autoresponses[sender] = (today, -1)
# Send this notification message instead
@@ -1544,8 +1544,8 @@ bad regexp in bounce_matching_header line: %s
# language support to the list, then the general admin page may have a
# blank field where the list owner is supposed to chose the list's
# preferred language.
- if mm_cfg.DEFAULT_SERVER_LANGUAGE not in langs:
- langs.append(mm_cfg.DEFAULT_SERVER_LANGUAGE)
+ if config.DEFAULT_SERVER_LANGUAGE not in langs:
+ langs.append(config.DEFAULT_SERVER_LANGUAGE)
# When testing, it's possible we've disabled a language, so just
# filter things out so we don't get tracebacks.
- return [lang for lang in langs if mm_cfg.LC_DESCRIPTIONS.has_key(lang)]
+ return [lang for lang in langs if config.LC_DESCRIPTIONS.has_key(lang)]
diff --git a/Mailman/Message.py b/Mailman/Message.py
index 7216d7c9a..b39bcb3fe 100644
--- a/Mailman/Message.py
+++ b/Mailman/Message.py
@@ -29,8 +29,8 @@ import email.Utils
from email.Charset import Charset
from email.Header import Header
-from Mailman import mm_cfg
from Mailman import Utils
+from Mailman.configuration import config
COMMASPACE = ', '
@@ -113,7 +113,7 @@ class Message(email.Message.Message):
This method differs from get_senders() in that it returns one and only
one address, and uses a different search order.
"""
- senderfirst = mm_cfg.USE_ENVELOPE_SENDER
+ senderfirst = config.USE_ENVELOPE_SENDER
if use_envelope is not None:
senderfirst = use_envelope
if senderfirst:
@@ -165,7 +165,7 @@ class Message(email.Message.Message):
names without the trailing colon.
"""
if headers is None:
- headers = mm_cfg.SENDER_HEADERS
+ headers = config.SENDER_HEADERS
pairs = []
for h in headers:
if h is None:
@@ -245,7 +245,7 @@ class UserNotification(Message):
def _enqueue(self, mlist, **_kws):
# Not imported at module scope to avoid import loop
from Mailman.Queue.sbcache import get_switchboard
- virginq = get_switchboard(mm_cfg.VIRGINQUEUE_DIR)
+ virginq = get_switchboard(config.VIRGINQUEUE_DIR)
# The message metadata better have a `recip' attribute
virginq.enqueue(self,
listname = mlist.internal_name(),
@@ -276,7 +276,7 @@ class OwnerNotification(UserNotification):
def _enqueue(self, mlist, **_kws):
# Not imported at module scope to avoid import loop
from Mailman.Queue.sbcache import get_switchboard
- virginq = get_switchboard(mm_cfg.VIRGINQUEUE_DIR)
+ virginq = get_switchboard(config.VIRGINQUEUE_DIR)
# The message metadata better have a `recip' attribute
virginq.enqueue(self,
listname = mlist.internal_name(),
diff --git a/Mailman/Queue/ArchRunner.py b/Mailman/Queue/ArchRunner.py
index 627145372..f3c90f5c3 100644
--- a/Mailman/Queue/ArchRunner.py
+++ b/Mailman/Queue/ArchRunner.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2000,2001,2002 by the Free Software Foundation, Inc.
+# Copyright (C) 2000-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,21 +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.
"""Archive queue runner."""
import time
from email.Utils import parsedate_tz, mktime_tz, formatdate
-from Mailman import mm_cfg
from Mailman import LockFile
from Mailman.Queue.Runner import Runner
+from Mailman.configuration import config
class ArchRunner(Runner):
- QDIR = mm_cfg.ARCHQUEUE_DIR
+ QDIR = config.ARCHQUEUE_DIR
def _dispose(self, mlist, msg, msgdata):
# Support clobber_date, i.e. setting the date in the archive to the
@@ -37,9 +38,9 @@ class ArchRunner(Runner):
receivedtime = formatdate(msgdata['received_time'])
if not originaldate:
clobber = 1
- elif mm_cfg.ARCHIVER_CLOBBER_DATE_POLICY == 1:
+ elif config.ARCHIVER_CLOBBER_DATE_POLICY == 1:
clobber = 1
- elif mm_cfg.ARCHIVER_CLOBBER_DATE_POLICY == 2:
+ elif config.ARCHIVER_CLOBBER_DATE_POLICY == 2:
# what's the timestamp on the original message?
tup = parsedate_tz(originaldate)
now = time.time()
@@ -47,7 +48,7 @@ class ArchRunner(Runner):
if not tup:
clobber = 1
elif abs(now - mktime_tz(tup)) > \
- mm_cfg.ARCHIVER_ALLOWABLE_SANE_DATE_SKEW:
+ config.ARCHIVER_ALLOWABLE_SANE_DATE_SKEW:
clobber = 1
except (ValueError, OverflowError):
# The likely cause of this is that the year in the Date: field
@@ -65,7 +66,7 @@ class ArchRunner(Runner):
msg['X-List-Received-Date'] = receivedtime
# Now try to get the list lock
try:
- mlist.Lock(timeout=mm_cfg.LIST_LOCK_TIMEOUT)
+ mlist.Lock(timeout=config.LIST_LOCK_TIMEOUT)
except LockFile.TimeOutError:
# oh well, try again later
return 1
diff --git a/Mailman/Queue/BounceRunner.py b/Mailman/Queue/BounceRunner.py
index 0063635ac..93bee2062 100644
--- a/Mailman/Queue/BounceRunner.py
+++ b/Mailman/Queue/BounceRunner.py
@@ -28,13 +28,13 @@ from email.MIMEText import MIMEText
from email.Utils import parseaddr
from Mailman import LockFile
-from Mailman import mm_cfg
from Mailman import Utils
from Mailman.Bouncers import BouncerAPI
-from Mailman.i18n import _
from Mailman.Message import UserNotification
from Mailman.Queue.Runner import Runner
from Mailman.Queue.sbcache import get_switchboard
+from Mailman.configuration import config
+from Mailman.i18n import _
COMMASPACE = ', '
@@ -78,10 +78,10 @@ class BounceMixin:
# their lists. So now we ignore site list bounces. Ce La Vie for
# password reminder bounces.
self._bounce_events_file = os.path.join(
- mm_cfg.DATA_DIR, 'bounce-events-%05d.pck' % os.getpid())
+ config.DATA_DIR, 'bounce-events-%05d.pck' % os.getpid())
self._bounce_events_fp = None
self._bouncecnt = 0
- self._nextaction = time.time() + mm_cfg.REGISTER_BOUNCES_EVERY
+ self._nextaction = time.time() + config.REGISTER_BOUNCES_EVERY
def _queue_bounces(self, listname, addrs, msg):
today = time.localtime()[:3]
@@ -137,7 +137,7 @@ class BounceMixin:
if self._nextaction > now or self._bouncecnt == 0:
return
# Let's go ahead and register the bounces we've got stored up
- self._nextaction = now + mm_cfg.REGISTER_BOUNCES_EVERY
+ self._nextaction = now + config.REGISTER_BOUNCES_EVERY
self._register_bounces()
def _probe_bounce(self, mlist, token):
@@ -158,7 +158,7 @@ class BounceMixin:
class BounceRunner(Runner, BounceMixin):
- QDIR = mm_cfg.BOUNCEQUEUE_DIR
+ QDIR = config.BOUNCEQUEUE_DIR
def __init__(self, slice=None, numslices=1):
Runner.__init__(self, slice, numslices)
@@ -167,7 +167,7 @@ class BounceRunner(Runner, BounceMixin):
def _dispose(self, mlist, msg, msgdata):
# Make sure we have the most up-to-date state
mlist.Load()
- outq = get_switchboard(mm_cfg.OUTQUEUE_DIR)
+ outq = get_switchboard(config.OUTQUEUE_DIR)
# There are a few possibilities here:
#
# - the message could have been VERP'd in which case, we know exactly
@@ -245,7 +245,7 @@ def verp_bounce(mlist, msg):
to = parseaddr(field)[1]
if not to:
continue # empty header
- mo = re.search(mm_cfg.VERP_REGEXP, to)
+ mo = re.search(config.VERP_REGEXP, to)
if not mo:
continue # no match of regexp
try:
@@ -255,7 +255,7 @@ def verp_bounce(mlist, msg):
addr = '%s@%s' % mo.group('mailbox', 'host')
except IndexError:
elog.error("VERP_REGEXP doesn't yield the right match groups: %s",
- mm_cfg.VERP_REGEXP)
+ config.VERP_REGEXP)
return []
return [addr]
@@ -276,7 +276,7 @@ def verp_probe(mlist, msg):
to = parseaddr(field)[1]
if not to:
continue # empty header
- mo = re.search(mm_cfg.VERP_PROBE_REGEXP, to)
+ mo = re.search(config.VERP_PROBE_REGEXP, to)
if not mo:
continue # no match of regexp
try:
@@ -290,7 +290,7 @@ def verp_probe(mlist, msg):
except IndexError:
elog.error(
"VERP_PROBE_REGEXP doesn't yield the right match groups: %s",
- mm_cfg.VERP_PROBE_REGEXP)
+ config.VERP_PROBE_REGEXP)
return None
diff --git a/Mailman/Queue/CommandRunner.py b/Mailman/Queue/CommandRunner.py
index fe30a3cd3..2d1fc1031 100644
--- a/Mailman/Queue/CommandRunner.py
+++ b/Mailman/Queue/CommandRunner.py
@@ -33,11 +33,11 @@ from email.MIMEText import MIMEText
from Mailman import LockFile
from Mailman import Message
-from Mailman import mm_cfg
from Mailman import Utils
from Mailman.Handlers import Replybot
-from Mailman.i18n import _
from Mailman.Queue.Runner import Runner
+from Mailman.configuration import config
+from Mailman.i18n import _
NL = '\n'
@@ -88,8 +88,8 @@ class Results:
assert isinstance(body, basestring)
lines = body.splitlines()
# Use no more lines than specified
- self.commands.extend(lines[:mm_cfg.DEFAULT_MAIL_COMMANDS_MAX_LINES])
- self.ignored.extend(lines[mm_cfg.DEFAULT_MAIL_COMMANDS_MAX_LINES:])
+ self.commands.extend(lines[:config.DEFAULT_MAIL_COMMANDS_MAX_LINES])
+ self.ignored.extend(lines[config.DEFAULT_MAIL_COMMANDS_MAX_LINES:])
def process(self):
# Now, process each line until we find an error. The first
@@ -192,7 +192,7 @@ To obtain instructions, send a message containing just the word "help".
class CommandRunner(Runner):
- QDIR = mm_cfg.CMDQUEUE_DIR
+ QDIR = config.CMDQUEUE_DIR
def _dispose(self, mlist, msg, msgdata):
# The policy here is similar to the Replybot policy. If a message has
@@ -217,7 +217,7 @@ class CommandRunner(Runner):
# locked. Still, it's more convenient to lock it here and now and
# deal with lock failures in one place.
try:
- mlist.Lock(timeout=mm_cfg.LIST_LOCK_TIMEOUT)
+ mlist.Lock(timeout=config.LIST_LOCK_TIMEOUT)
except LockFile.TimeOutError:
# Oh well, try again later
return True
@@ -232,7 +232,7 @@ class CommandRunner(Runner):
elif msgdata.get('toleave'):
res.do_command('leave')
elif msgdata.get('toconfirm'):
- mo = re.match(mm_cfg.VERP_CONFIRM_REGEXP, msg.get('to', ''))
+ mo = re.match(config.VERP_CONFIRM_REGEXP, msg.get('to', ''))
if mo:
res.do_command('confirm', (mo.group('cookie'),))
res.send_response()
diff --git a/Mailman/Queue/IncomingRunner.py b/Mailman/Queue/IncomingRunner.py
index 19a315040..332c9f129 100644
--- a/Mailman/Queue/IncomingRunner.py
+++ b/Mailman/Queue/IncomingRunner.py
@@ -103,8 +103,8 @@ from cStringIO import StringIO
from Mailman import Errors
from Mailman import LockFile
-from Mailman import mm_cfg
from Mailman.Queue.Runner import Runner
+from Mailman.configuration import config
log = logging.getLogger('mailman.error')
vlog = logging.getLogger('mailman.vette')
@@ -112,12 +112,12 @@ vlog = logging.getLogger('mailman.vette')
class IncomingRunner(Runner):
- QDIR = mm_cfg.INQUEUE_DIR
+ QDIR = config.INQUEUE_DIR
def _dispose(self, mlist, msg, msgdata):
# Try to get the list lock.
try:
- mlist.Lock(timeout=mm_cfg.LIST_LOCK_TIMEOUT)
+ mlist.Lock(timeout=config.LIST_LOCK_TIMEOUT)
except LockFile.TimeOutError:
# Oh well, try again later
return 1
@@ -146,7 +146,7 @@ class IncomingRunner(Runner):
# flows through the pipeline will empty it out!
return msgdata.get('pipeline',
getattr(mlist, 'pipeline',
- mm_cfg.GLOBAL_PIPELINE))[:]
+ config.GLOBAL_PIPELINE))[:]
def _dopipeline(self, mlist, msg, msgdata, pipeline):
while pipeline:
diff --git a/Mailman/Queue/MaildirRunner.py b/Mailman/Queue/MaildirRunner.py
index 97b71c7ef..9e5e08be5 100644
--- a/Mailman/Queue/MaildirRunner.py
+++ b/Mailman/Queue/MaildirRunner.py
@@ -57,11 +57,11 @@ import logging
from email.Parser import Parser
from email.Utils import parseaddr
-from Mailman import mm_cfg
from Mailman import Utils
from Mailman.Message import Message
from Mailman.Queue.Runner import Runner
from Mailman.Queue.sbcache import get_switchboard
+from Mailman.configuration import config
# We only care about the listname and the subq as in listname@ or
# listname-request@
@@ -88,8 +88,8 @@ class MaildirRunner(Runner):
# Don't call the base class constructor, but build enough of the
# underlying attributes to use the base class's implementation.
self._stop = 0
- self._dir = os.path.join(mm_cfg.MAILDIR_DIR, 'new')
- self._cur = os.path.join(mm_cfg.MAILDIR_DIR, 'cur')
+ self._dir = os.path.join(config.MAILDIR_DIR, 'new')
+ self._cur = os.path.join(config.MAILDIR_DIR, 'cur')
self._parser = Parser(Message)
def _oneloop(self):
@@ -150,29 +150,29 @@ class MaildirRunner(Runner):
msgdata = {'listname': listname}
# -admin is deprecated
if subq in ('bounces', 'admin'):
- queue = get_switchboard(mm_cfg.BOUNCEQUEUE_DIR)
+ queue = get_switchboard(config.BOUNCEQUEUE_DIR)
elif subq == 'confirm':
msgdata['toconfirm'] = 1
- queue = get_switchboard(mm_cfg.CMDQUEUE_DIR)
+ queue = get_switchboard(config.CMDQUEUE_DIR)
elif subq in ('join', 'subscribe'):
msgdata['tojoin'] = 1
- queue = get_switchboard(mm_cfg.CMDQUEUE_DIR)
+ queue = get_switchboard(config.CMDQUEUE_DIR)
elif subq in ('leave', 'unsubscribe'):
msgdata['toleave'] = 1
- queue = get_switchboard(mm_cfg.CMDQUEUE_DIR)
+ queue = get_switchboard(config.CMDQUEUE_DIR)
elif subq == 'owner':
msgdata.update({
'toowner': 1,
'envsender': Utils.get_site_email(extra='bounces'),
- 'pipeline': mm_cfg.OWNER_PIPELINE,
+ 'pipeline': config.OWNER_PIPELINE,
})
- queue = get_switchboard(mm_cfg.INQUEUE_DIR)
+ queue = get_switchboard(config.INQUEUE_DIR)
elif subq is None:
msgdata['tolist'] = 1
- queue = get_switchboard(mm_cfg.INQUEUE_DIR)
+ queue = get_switchboard(config.INQUEUE_DIR)
elif subq == 'request':
msgdata['torequest'] = 1
- queue = get_switchboard(mm_cfg.CMDQUEUE_DIR)
+ queue = get_switchboard(config.CMDQUEUE_DIR)
else:
log.error('Unknown sub-queue: %s', subq)
os.rename(dstname, xdstname)
diff --git a/Mailman/Queue/NewsRunner.py b/Mailman/Queue/NewsRunner.py
index 7a71f5bf2..31e88da9a 100644
--- a/Mailman/Queue/NewsRunner.py
+++ b/Mailman/Queue/NewsRunner.py
@@ -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.
"""NNTP queue runner."""
@@ -27,9 +28,9 @@ from email.Utils import getaddresses
COMMASPACE = ', '
-from Mailman import mm_cfg
from Mailman import Utils
from Mailman.Queue.Runner import Runner
+from Mailman.configuration import config
log = logging.getLogger('mailman.error')
@@ -48,7 +49,7 @@ mcre = re.compile(r"""
class NewsRunner(Runner):
- QDIR = mm_cfg.NEWSQUEUE_DIR
+ QDIR = config.NEWSQUEUE_DIR
def _dispose(self, mlist, msg, msgdata):
# Make sure we have the most up-to-date state
@@ -64,8 +65,8 @@ class NewsRunner(Runner):
nntp_host, nntp_port = Utils.nntpsplit(mlist.nntp_host)
conn = nntplib.NNTP(nntp_host, nntp_port,
readermode=True,
- user=mm_cfg.NNTP_USERNAME,
- password=mm_cfg.NNTP_PASSWORD)
+ user=config.NNTP_USERNAME,
+ password=config.NNTP_PASSWORD)
conn.post(fp)
except nntplib.error_temp, e:
log.error('(NNTPDirect) NNTP error for list "%s": %s',
@@ -146,9 +147,9 @@ def prepare_message(mlist, msg, msgdata):
# woon't completely sanitize the message, but it will eliminate the bulk
# of the rejections based on message headers. The NNTP server may still
# reject the message because of other problems.
- for header in mm_cfg.NNTP_REMOVE_HEADERS:
+ for header in config.NNTP_REMOVE_HEADERS:
del msg[header]
- for header, rewrite in mm_cfg.NNTP_REWRITE_DUPLICATE_HEADERS:
+ for header, rewrite in config.NNTP_REWRITE_DUPLICATE_HEADERS:
values = msg.get_all(header, [])
if len(values) < 2:
# We only care about duplicates
diff --git a/Mailman/Queue/OutgoingRunner.py b/Mailman/Queue/OutgoingRunner.py
index 3d4575ed2..351f24da6 100644
--- a/Mailman/Queue/OutgoingRunner.py
+++ b/Mailman/Queue/OutgoingRunner.py
@@ -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.
"""Outgoing queue runner."""
@@ -27,10 +28,10 @@ import logging
from Mailman import Errors
from Mailman import LockFile
from Mailman import Message
-from Mailman import mm_cfg
from Mailman.Queue.BounceRunner import BounceMixin
from Mailman.Queue.Runner import Runner
from Mailman.Queue.Switchboard import Switchboard
+from Mailman.configuration import config
# This controls how often _doperiodic() will try to deal with deferred
# permanent failures. It is a count of calls to _doperiodic()
@@ -41,20 +42,20 @@ log = logging.getLogger('mailman.error')
class OutgoingRunner(Runner, BounceMixin):
- QDIR = mm_cfg.OUTQUEUE_DIR
+ QDIR = config.OUTQUEUE_DIR
def __init__(self, slice=None, numslices=1):
Runner.__init__(self, slice, numslices)
BounceMixin.__init__(self)
# We look this function up only at startup time
- modname = 'Mailman.Handlers.' + mm_cfg.DELIVERY_MODULE
+ modname = 'Mailman.Handlers.' + config.DELIVERY_MODULE
mod = __import__(modname)
self._func = getattr(sys.modules[modname], 'process')
# This prevents smtp server connection problems from filling up the
# error log. It gets reset if the message was successfully sent, and
# set if there was a socket.error.
self.__logged = False
- self.__retryq = Switchboard(mm_cfg.RETRYQUEUE_DIR)
+ self.__retryq = Switchboard(config.RETRYQUEUE_DIR)
def _dispose(self, mlist, msg, msgdata):
# See if we should retry delivery of this message again.
@@ -75,13 +76,13 @@ class OutgoingRunner(Runner, BounceMixin):
# There was a problem connecting to the SMTP server. Log this
# once, but crank up our sleep time so we don't fill the error
# log.
- port = mm_cfg.SMTPPORT
+ port = config.SMTPPORT
if port == 0:
port = 'smtp'
# Log this just once.
if not self.__logged:
log.error('Cannot connect to SMTP server %s on port %s',
- mm_cfg.SMTPHOST, port)
+ config.SMTPHOST, port)
self.__logged = True
return True
except Errors.SomeRecipientsFailed, e:
@@ -115,7 +116,7 @@ class OutgoingRunner(Runner, BounceMixin):
return False
else:
# Keep trying to delivery this message for a while
- deliver_until = now + mm_cfg.DELIVERY_RETRY_PERIOD
+ deliver_until = now + config.DELIVERY_RETRY_PERIOD
msgdata['last_recip_count'] = len(recips)
msgdata['deliver_until'] = deliver_until
msgdata['recips'] = recips
diff --git a/Mailman/Queue/RetryRunner.py b/Mailman/Queue/RetryRunner.py
index b6c0eac6b..da87982c1 100644
--- a/Mailman/Queue/RetryRunner.py
+++ b/Mailman/Queue/RetryRunner.py
@@ -12,23 +12,24 @@
#
# 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.
import time
-from Mailman import mm_cfg
from Mailman.Queue.Runner import Runner
from Mailman.Queue.Switchboard import Switchboard
+from Mailman.configuration import config
class RetryRunner(Runner):
- QDIR = mm_cfg.RETRYQUEUE_DIR
- SLEEPTIME = mm_cfg.minutes(15)
+ QDIR = config.RETRYQUEUE_DIR
+ SLEEPTIME = config.minutes(15)
def __init__(self, slice=None, numslices=1):
Runner.__init__(self, slice, numslices)
- self.__outq = Switchboard(mm_cfg.OUTQUEUE_DIR)
+ self.__outq = Switchboard(config.OUTQUEUE_DIR)
def _dispose(self, mlist, msg, msgdata):
# Move it to the out queue for another retry
diff --git a/Mailman/Queue/Runner.py b/Mailman/Queue/Runner.py
index 56baf431b..95cb3fd43 100644
--- a/Mailman/Queue/Runner.py
+++ b/Mailman/Queue/Runner.py
@@ -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.
"""Generic queue runner class."""
@@ -25,11 +26,11 @@ import email.Errors
from cStringIO import StringIO
from Mailman import Errors
-from Mailman import i18n
from Mailman import MailList
-from Mailman import mm_cfg
from Mailman import Utils
+from Mailman import i18n
from Mailman.Queue.Switchboard import Switchboard
+from Mailman.configuration import config
log = logging.getLogger('mailman.error')
@@ -37,7 +38,7 @@ log = logging.getLogger('mailman.error')
class Runner:
QDIR = None
- SLEEPTIME = mm_cfg.QRUNNER_SLEEP_TIME
+ SLEEPTIME = config.QRUNNER_SLEEP_TIME
def __init__(self, slice=None, numslices=1):
self._kids = {}
@@ -45,7 +46,7 @@ class Runner:
# we want to provide slice and numslice arguments.
self._switchboard = Switchboard(self.QDIR, slice, numslices)
# Create the shunt switchboard
- self._shunt = Switchboard(mm_cfg.SHUNTQUEUE_DIR)
+ self._shunt = Switchboard(config.SHUNTQUEUE_DIR)
self._stop = False
def __repr__(self):
@@ -132,7 +133,7 @@ class Runner:
# Find out which mailing list this message is destined for.
listname = msgdata.get('listname')
if not listname:
- listname = mm_cfg.MAILMAN_SITE_LIST
+ listname = config.MAILMAN_SITE_LIST
mlist = self._open_list(listname)
if not mlist:
log.error('Dequeuing message destined for missing list: %s',
@@ -153,7 +154,7 @@ class Runner:
if mlist:
lang = mlist.getMemberLanguage(sender)
else:
- lang = mm_cfg.DEFAULT_SERVER_LANGUAGE
+ lang = config.DEFAULT_SERVER_LANGUAGE
i18n.set_language(lang)
msgdata['lang'] = lang
try:
diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py
index 36b69283e..d9fc5dc27 100644
--- a/Mailman/Queue/Switchboard.py
+++ b/Mailman/Queue/Switchboard.py
@@ -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.
"""Reading and writing message objects and message metadata."""
@@ -41,8 +42,8 @@ import cPickle
import marshal
from Mailman import Message
-from Mailman import mm_cfg
from Mailman import Utils
+from Mailman.configuration import config
# 20 bytes of all bits set, maximum sha.digest() value
shamax = 0xffffffffffffffffffffffffffffffffffffffffL
@@ -104,7 +105,7 @@ class Switchboard:
filename = os.path.join(self.__whichq, filebase + '.pck')
tmpfile = filename + '.tmp'
# Always add the metadata schema version number
- data['version'] = mm_cfg.QFILE_SCHEMA_VERSION
+ data['version'] = config.QFILE_SCHEMA_VERSION
# Filter out volatile entries
for k in data.keys():
if k.startswith('_'):
diff --git a/Mailman/Queue/VirginRunner.py b/Mailman/Queue/VirginRunner.py
index 7d09ef8e9..bdb8a26bf 100644
--- a/Mailman/Queue/VirginRunner.py
+++ b/Mailman/Queue/VirginRunner.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998,1999,2000,2001,2002 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,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.
"""Virgin message queue runner.
@@ -22,14 +23,14 @@ to go through some minimal processing before they can be sent out to the
recipient.
"""
-from Mailman import mm_cfg
-from Mailman.Queue.Runner import Runner
from Mailman.Queue.IncomingRunner import IncomingRunner
+from Mailman.Queue.Runner import Runner
+from Mailman.configuration import config
class VirginRunner(IncomingRunner):
- QDIR = mm_cfg.VIRGINQUEUE_DIR
+ QDIR = config.VIRGINQUEUE_DIR
def _dispose(self, mlist, msg, msgdata):
# We need to fasttrack this message through any handlers that touch
diff --git a/Mailman/Queue/__init__.py b/Mailman/Queue/__init__.py
index 7fef22451..e69de29bb 100644
--- a/Mailman/Queue/__init__.py
+++ b/Mailman/Queue/__init__.py
@@ -1,15 +0,0 @@
-# Copyright (C) 2000,2001,2002 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.
diff --git a/Mailman/Queue/sbcache.py b/Mailman/Queue/sbcache.py
index dee1bca7f..6fe499d0d 100644
--- a/Mailman/Queue/sbcache.py
+++ b/Mailman/Queue/sbcache.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2001,2002 by the Free Software Foundation, Inc.
+# Copyright (C) 2001-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.
"""A factory of Switchboards with caching."""
diff --git a/Mailman/Site.py b/Mailman/Site.py
index 691d23870..72becb224 100644
--- a/Mailman/Site.py
+++ b/Mailman/Site.py
@@ -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.
"""Provide some customization for site-wide behavior.
@@ -23,7 +24,7 @@ implementation should work for standard Mailman.
import os
import errno
-from Mailman import mm_cfg
+from Mailman.configuration import config
@@ -54,7 +55,7 @@ def get_listpath(listname, domain=None, create=0):
should not attempt to create the path heirarchy (and in fact the absence
of the path might be significant).
"""
- path = os.path.join(mm_cfg.LIST_DATA_DIR, listname)
+ path = os.path.join(config.LIST_DATA_DIR, listname)
if create:
_makedir(path)
return path
@@ -79,9 +80,9 @@ def get_archpath(listname, domain=None, create=False, public=False):
is usually a symlink instead of a directory).
"""
if public:
- subdir = mm_cfg.PUBLIC_ARCHIVE_FILE_DIR
+ subdir = config.PUBLIC_ARCHIVE_FILE_DIR
else:
- subdir = mm_cfg.PRIVATE_ARCHIVE_FILE_DIR
+ subdir = config.PRIVATE_ARCHIVE_FILE_DIR
path = os.path.join(subdir, listname)
if create:
_makedir(path)
@@ -101,7 +102,7 @@ def get_listnames(domain=None):
from Mailman.Utils import list_exists
# We don't currently support separate virtual domain directories
got = []
- for fn in os.listdir(mm_cfg.LIST_DATA_DIR):
+ 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 42bacc16a..5e594be8d 100644
--- a/Mailman/Utils.py
+++ b/Mailman/Utils.py
@@ -15,7 +15,6 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# USA.
-
"""Miscellaneous essential routines.
This includes actual message transmission routines, address checking and
@@ -42,8 +41,8 @@ from string import ascii_letters, digits, whitespace
from Mailman import Errors
from Mailman import Site
-from Mailman import mm_cfg
from Mailman.SafeDict import SafeDict
+from Mailman.configuration import config
EMPTYSTRING = ''
UEMPTYSTRING = u''
@@ -228,7 +227,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 = mm_cfg.DEFAULT_URL_PATTERN % get_domain()
+ web_page_url = config.DEFAULT_URL_PATTERN % get_domain()
if web_page_url[-1] <> '/':
web_page_url = web_page_url + '/'
fullpath = os.environ.get('REQUEST_URI')
@@ -247,7 +246,7 @@ def ScriptURL(target, web_page_url=None, absolute=False):
path = ('../' * count) + target
else:
path = web_page_url + target
- return path + mm_cfg.CGIEXT
+ return path + config.CGIEXT
@@ -334,8 +333,10 @@ def Secure_MakeRandomPassword(length):
os.close(fd)
-def MakeRandomPassword(length=mm_cfg.MEMBER_PASSWORD_LENGTH):
- if mm_cfg.USER_FRIENDLY_PASSWORDS:
+def MakeRandomPassword(length=None):
+ if length is None:
+ length = config.MEMBER_PASSWORD_LENGTH
+ if config.USER_FRIENDLY_PASSWORDS:
return UserFriendly_MakeRandomPassword(length)
return Secure_MakeRandomPassword(length)
@@ -356,9 +357,9 @@ def GetRandomSeed():
def set_global_password(pw, siteadmin=True):
if siteadmin:
- filename = mm_cfg.SITE_PW_FILE
+ filename = config.SITE_PW_FILE
else:
- filename = mm_cfg.LISTCREATOR_PW_FILE
+ filename = config.LISTCREATOR_PW_FILE
# rw-r-----
omask = os.umask(026)
try:
@@ -371,9 +372,9 @@ def set_global_password(pw, siteadmin=True):
def get_global_password(siteadmin=True):
if siteadmin:
- filename = mm_cfg.SITE_PW_FILE
+ filename = config.SITE_PW_FILE
else:
- filename = mm_cfg.LISTCREATOR_PW_FILE
+ filename = config.LISTCREATOR_PW_FILE
try:
fp = open(filename)
challenge = fp.read()[:-1] # strip off trailing nl
@@ -486,14 +487,14 @@ def findtext(templatefile, dict=None, raw=False, lang=None, mlist=None):
languages.append(lang)
if mlist is not None:
languages.append(mlist.preferred_language)
- languages.append(mm_cfg.DEFAULT_SERVER_LANGUAGE)
+ languages.append(config.DEFAULT_SERVER_LANGUAGE)
# Calculate the locations to scan
searchdirs = []
if mlist is not None:
searchdirs.append(mlist.fullpath())
- searchdirs.append(os.path.join(mm_cfg.TEMPLATE_DIR, mlist.host_name))
- searchdirs.append(os.path.join(mm_cfg.TEMPLATE_DIR, 'site'))
- searchdirs.append(mm_cfg.TEMPLATE_DIR)
+ searchdirs.append(os.path.join(config.TEMPLATE_DIR, mlist.host_name))
+ searchdirs.append(os.path.join(config.TEMPLATE_DIR, 'site'))
+ searchdirs.append(config.TEMPLATE_DIR)
# Start scanning
quickexit = 'quickexit'
fp = None
@@ -514,7 +515,7 @@ def findtext(templatefile, dict=None, raw=False, lang=None, mlist=None):
# Try one last time with the distro English template, which, unless
# you've got a really broken installation, must be there.
try:
- filename = os.path.join(mm_cfg.TEMPLATE_DIR, 'en', templatefile)
+ filename = os.path.join(config.TEMPLATE_DIR, 'en', templatefile)
fp = open(filename)
except IOError, e:
if e.errno <> errno.ENOENT: raise
@@ -573,7 +574,7 @@ def is_administrivia(msg):
break
if line.strip():
linecnt += 1
- if linecnt > mm_cfg.DEFAULT_MAIL_COMMANDS_MAX_LINES:
+ if linecnt > config.DEFAULT_MAIL_COMMANDS_MAX_LINES:
return False
lines.append(line)
bodytext = NL.join(lines)
@@ -652,14 +653,14 @@ def reap(kids, func=None, once=False):
def GetLanguageDescr(lang):
- return mm_cfg.LC_DESCRIPTIONS[lang][0]
+ return config.LC_DESCRIPTIONS[lang][0]
def GetCharSet(lang):
- return mm_cfg.LC_DESCRIPTIONS[lang][1]
+ return config.LC_DESCRIPTIONS[lang][1]
def IsLanguage(lang):
- return mm_cfg.LC_DESCRIPTIONS.has_key(lang)
+ return config.LC_DESCRIPTIONS.has_key(lang)
@@ -669,23 +670,23 @@ def get_domain():
# Strip off the port if there is one
if port and host.endswith(':' + port):
host = host[:-len(port)-1]
- if mm_cfg.VIRTUAL_HOST_OVERVIEW and host:
+ 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 = ((mm_cfg.DEFAULT_URL
- and urlparse.urlparse(mm_cfg.DEFAULT_URL)[1])
- or mm_cfg.DEFAULT_URL_HOST)
+ hostname = ((config.DEFAULT_URL
+ and urlparse.urlparse(config.DEFAULT_URL)[1])
+ or config.DEFAULT_URL_HOST)
return hostname.lower()
def get_site_email(hostname=None, extra=None):
if hostname is None:
- hostname = mm_cfg.VIRTUAL_HOSTS.get(get_domain(), get_domain())
+ hostname = config.VIRTUAL_HOSTS.get(get_domain(), get_domain())
if extra is None:
- return '%s@%s' % (mm_cfg.MAILMAN_SITE_LIST, hostname)
- return '%s-%s@%s' % (mm_cfg.MAILMAN_SITE_LIST, extra, hostname)
+ return '%s@%s' % (config.MAILMAN_SITE_LIST, hostname)
+ return '%s-%s@%s' % (config.MAILMAN_SITE_LIST, extra, hostname)
diff --git a/Mailman/Version.py b/Mailman/Version.py
index 6c9aad05e..91fb82e65 100644
--- a/Mailman/Version.py
+++ b/Mailman/Version.py
@@ -46,3 +46,7 @@ PENDING_FILE_SCHEMA_VERSION = 2
# version number for the lists/<listname>/request.db file schema
REQUESTS_FILE_SCHEMA_VERSION = 1
+
+# Printable version string used by command line scripts
+MAILMAN_VERSION = 'GNU Mailman ' + VERSION
+
diff --git a/Mailman/bin/list_lists.py b/Mailman/bin/list_lists.py
index a3493c3b8..0feac2845 100644
--- a/Mailman/bin/list_lists.py
+++ b/Mailman/bin/list_lists.py
@@ -18,9 +18,11 @@
import sys
import optparse
+from Mailman import Defaults
from Mailman import MailList
from Mailman import Utils
-from Mailman import mm_cfg
+from Mailman import Version
+from Mailman.configuration import config
from Mailman.i18n import _
__i18n_templates__ = True
@@ -28,7 +30,7 @@ __i18n_templates__ = True
def parseargs():
- parser = optparse.OptionParser(version=mm_cfg.MAILMAN_VERSION,
+ parser = optparse.OptionParser(version=Version.MAILMAN_VERSION,
usage=_("""\
%prog [options]
@@ -46,6 +48,8 @@ Displays only the list name, with no description."""))
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."""))
+ parser.add_option('-C', '--config',
+ help=_('Alternative configuration file to use'))
opts, args = parser.parse_args()
if args:
parser.print_help()
@@ -57,6 +61,7 @@ This only works if the VIRTUAL_HOST_OVERVIEW variable is set."""))
def main():
parser, opts, args = parseargs()
+ config.load(opts.config)
names = Utils.list_names()
names.sort()
@@ -67,7 +72,7 @@ def main():
mlist = MailList.MailList(n, lock=False)
if opts.advertised and not mlist.advertised:
continue
- if opts.vhost and mm_cfg.VIRTUAL_HOST_OVERVIEW and \
+ 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
diff --git a/Mailman/bin/mailmanctl.py b/Mailman/bin/mailmanctl.py
new file mode 100644
index 000000000..a91b5651b
--- /dev/null
+++ b/Mailman/bin/mailmanctl.py
@@ -0,0 +1,521 @@
+# Copyright (C) 2001-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.
+
+import os
+import grp
+import pwd
+import sys
+import errno
+import signal
+import socket
+import logging
+import optparse
+
+from Mailman import Defaults
+from Mailman import Errors
+from Mailman import LockFile
+from Mailman import Utils
+from Mailman import Version
+from Mailman import loginit
+from Mailman.MailList import MailList
+from Mailman.configuration import config
+from Mailman.i18n import _
+
+__i18n_templates__ = True
+
+COMMASPACE = ', '
+DOT = '.'
+
+# Since we wake up once per day and refresh the lock, the LOCK_LIFETIME
+# needn't be (much) longer than SNOOZE. We pad it 6 hours just to be safe.
+LOCK_LIFETIME = Defaults.days(1) + Defaults.hours(6)
+SNOOZE = Defaults.days(1)
+MAX_RESTARTS = 10
+
+
+
+def parseargs():
+ parser = optparse.OptionParser(version=Version.MAILMAN_VERSION,
+ usage=_("""\
+Primary start-up and shutdown script for Mailman's qrunner daemon.
+
+This script starts, stops, and restarts the main Mailman queue runners, making
+sure that the various long-running qrunners are still alive and kicking. It
+does this by forking and exec'ing the qrunners and waiting on their pids.
+When it detects a subprocess has exited, it may restart it.
+
+The qrunners respond to SIGINT, SIGTERM, and SIGHUP. SIGINT and SIGTERM both
+cause the qrunners to exit cleanly, but the master will only restart qrunners
+that have exited due to a SIGINT. SIGHUP causes the master and the qrunners
+to close their log files, and reopen then upon the next printed message.
+
+The master also responds to SIGINT, SIGTERM, and SIGHUP, which it simply
+passes on to the qrunners (note that the master will close and reopen its own
+log files on receipt of a SIGHUP). The master also leaves its own process id
+in the file data/master-qrunner.pid but you normally don't need to use this
+pid directly. The `start', `stop', `restart', and `reopen' commands handle
+everything for you.
+
+Commands:
+
+ start - Start the master daemon and all qrunners. Prints a message and
+ exits if the master daemon is already running.
+
+ stop - Stops the master daemon and all qrunners. After stopping, no
+ more messages will be processed.
+
+ restart - Restarts the qrunners, but not the master process. Use this
+ whenever you upgrade or update Mailman so that the qrunners will
+ use the newly installed code.
+
+ reopen - This will close all log files, causing them to be re-opened the
+ next time a message is written to them
+
+Usage: %prog [options] [ start | stop | restart | reopen ]"""))
+ parser.add_option('-n', '--no-restart',
+ dest='restart', default=True, action='store_false',
+ help=_("""\
+Don't restart the qrunners when they exit because of an error or a SIGINT.
+They are never restarted if they exit in response to a SIGTERM. Use this only
+for debugging. Only useful if the `start' command is given."""))
+ parser.add_option('-u', '--run-as-user',
+ dest='checkprivs', default=True, action='store_false',
+ help=_("""\
+Normally, this script will refuse to run if the user id and group id are not
+set to the `mailman' user and group (as defined when you configured Mailman).
+If run as root, this script will change to this user and group before the
+check is made.
+
+This can be inconvenient for testing and debugging purposes, so the -u flag
+means that the step that sets and checks the uid/gid is skipped, and the
+program is run as the current user and group. This flag is not recommended
+for normal production environments.
+
+Note though, that if you run with -u and are not in the mailman group, you may
+have permission problems, such as begin unable to delete a list's archives
+through the web. Tough luck!"""))
+ parser.add_option('-s', '--stale-lock-cleanup',
+ dest='force', default=False, action='store_true',
+ help=_("""\
+If mailmanctl finds an existing master lock, it will normally exit with an
+error message. With this option, mailmanctl will perform an extra level of
+checking. If a process matching the host/pid described in the lock file is
+running, mailmanctl will still exit, but if no matching process is found,
+mailmanctl will remove the apparently stale lock and make another attempt to
+claim the master lock."""))
+ parser.add_option('-q', '--quiet',
+ default=False, action='store_true',
+ help=_("""\
+Don't print status messages. Error messages are still printed to standard
+error."""))
+ parser.add_option('-C', '--config',
+ help=_('Alternative configuration file to use'))
+ opts, args = parser.parse_args()
+ if not args:
+ parser.print_help()
+ print >> sys.stderr, _('No command given.')
+ sys.exit(1)
+ if len(args) > 1:
+ parse.print_help()
+ commands = COMMASPACE.join(args)
+ print >> sys.stderr, _('Bad command: $commands')
+ sys.exit(1)
+ return parser, opts, args
+
+
+
+def kill_watcher(sig):
+ try:
+ fp = open(config.PIDFILE)
+ pidstr = fp.read()
+ fp.close()
+ pid = int(pidstr.strip())
+ except (IOError, ValueError), e:
+ # For i18n convenience
+ pidfile = config.PIDFILE
+ print >> sys.stderr, _('PID unreadable in: $pidfile')
+ print >> sys.stderr, e
+ print >> sys.stderr, _('Is qrunner even running?')
+ return
+ try:
+ os.kill(pid, sig)
+ except OSError, e:
+ if e.errno <> errno.ESRCH: raise
+ print >> sys.stderr, _('No child with pid: $pid')
+ print >> sys.stderr, e
+ print >> sys.stderr, _('Stale pid file removed.')
+ os.unlink(config.PIDFILE)
+
+
+
+def get_lock_data():
+ # Return the hostname, pid, and tempfile
+ fp = open(config.LOCKFILE)
+ try:
+ filename = os.path.split(fp.read().strip())[1]
+ finally:
+ fp.close()
+ parts = filename.split('.')
+ hostname = DOT.join(parts[1:-1])
+ pid = int(parts[-1])
+ return hostname, int(pid), filename
+
+
+def qrunner_state():
+ # 1 if proc exists on host (but is it qrunner? ;)
+ # 0 if host matches but no proc
+ # hostname if hostname doesn't match
+ hostname, pid, tempfile = get_lock_data()
+ if hostname <> socket.gethostname():
+ return hostname
+ # Find out if the process exists by calling kill with a signal 0.
+ try:
+ os.kill(pid, 0)
+ except OSError, e:
+ if e.errno <> errno.ESRCH:
+ raise
+ return 0
+ return 1
+
+
+def acquire_lock_1(force):
+ # Be sure we can acquire the master qrunner lock. If not, it means some
+ # other master qrunner daemon is already going.
+ lock = LockFile.LockFile(config.LOCK_FILE, LOCK_LIFETIME)
+ try:
+ lock.lock(0.1)
+ return lock
+ except LockFile.TimeOutError:
+ if not force:
+ raise
+ # Force removal of lock first
+ lock._disown()
+ hostname, pid, tempfile = get_lock_data()
+ os.unlink(config.LOCKFILE)
+ os.unlink(os.path.join(config.LOCK_DIR, tempfile))
+ return acquire_lock_1(force=False)
+
+
+def acquire_lock(force):
+ try:
+ lock = acquire_lock_1(force)
+ return lock
+ except LockFile.TimeOutError:
+ status = qrunner_state()
+ if status == 1:
+ # host matches and proc exists
+ print >> sys.stderr, _("""\
+The master qrunner lock could not be acquired because it appears as if another
+master qrunner is already running.
+""")
+ elif status == 0:
+ # host matches but no proc
+ print >> sys.stderr, _("""\
+The master qrunner lock could not be acquired. It appears as though there is
+a stale master qrunner lock. Try re-running mailmanctl with the -s flag.
+""")
+ else:
+ # host doesn't even match
+ print >> sys.stderr, _("""\
+The master qrunner lock could not be acquired, because it appears as if some
+process on some other host may have acquired it. We can't test for stale
+locks across host boundaries, so you'll have to do this manually. Or, if you
+know the lock is stale, re-run mailmanctl with the -s flag.
+
+Lock file: $config.LOCKFILE
+Lock host: $status
+
+Exiting.""")
+
+
+
+def start_runner(qrname, slice, count):
+ pid = os.fork()
+ if pid:
+ # parent
+ return pid
+ # child
+ #
+ # Craft the command line arguments for the exec() call.
+ rswitch = '--runner=%s:%d:%d' % (qrname, slice, count)
+ exe = os.path.join(config.BIN_DIR, 'qrunner')
+ # config.PYTHON, which is the absolute path to the Python interpreter,
+ # must be given as argv[0] due to Python's library search algorithm.
+ os.execl(config.PYTHON, config.PYTHON, exe, rswitch, '-s')
+ # Should never get here
+ raise RuntimeError, 'os.execl() failed'
+
+
+def start_all_runners():
+ kids = {}
+ for qrname, count in config.QRUNNERS:
+ for slice in range(count):
+ # queue runner name, slice, numslices, restart count
+ info = (qrname, slice, count, 0)
+ pid = start_runner(qrname, slice, count)
+ kids[pid] = info
+ return kids
+
+
+
+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
+ # the uid/gid.
+ gid = grp.getgrnam(config.MAILMAN_GROUP)[2]
+ uid = pwd.getpwnam(config.MAILMAN_USER)[2]
+ myuid = os.getuid()
+ if myuid == 0:
+ # Set the process's supplimental groups.
+ groups = [x[2] for x in grp.getgrall() if config.MAILMAN_USER in x[3]]
+ groups.append(gid)
+ os.setgroups(groups)
+ os.setgid(gid)
+ os.setuid(uid)
+ elif myuid <> uid:
+ name = config.MAILMAN_USER
+ usage(1, _(
+ 'Run this program as root or as the $name user, or use -u.'))
+
+
+
+def main():
+ global elog, qlog
+
+ parser, opts, args = parseargs()
+ config.load(opts.config)
+
+ loginit.initialize()
+ elog = logging.getLogger('mailman.error')
+ qlog = logging.getLogger('mailman.qrunner')
+
+ if opts.checkprivs:
+ check_privs()
+ else:
+ print _('Warning! You may encounter permission problems.')
+
+ # Handle the commands
+ command = args[0].lower()
+ if command == 'stop':
+ # Sent the master qrunner process a SIGINT, which is equivalent to
+ # giving cron/qrunner a ctrl-c or KeyboardInterrupt. This will
+ # effectively shut everything down.
+ if not opts.quiet:
+ print _("Shutting down Mailman's master qrunner")
+ kill_watcher(signal.SIGTERM)
+ elif command == 'restart':
+ # Sent the master qrunner process a SIGHUP. This will cause the
+ # master qrunner to kill and restart all the worker qrunners, and to
+ # close and re-open its log files.
+ if not opts.quiet:
+ print _("Restarting Mailman's master qrunner")
+ kill_watcher(signal.SIGINT)
+ elif command == 'reopen':
+ if not opts.quiet:
+ 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.
+ #
+ # The child watcher process simply waits on the pids of the children
+ # qrunners. Unless explicitly disabled by a mailmanctl switch (or the
+ # children are killed with SIGTERM instead of SIGINT), the watcher
+ # will automatically restart any child process that exits. This
+ # allows us to be more robust, and also to implement restart by simply
+ # SIGINT'ing the qrunner children, and letting the watcher restart
+ # them.
+ #
+ # Under normal operation, we have a child per queue. This lets us get
+ # the most out of the available resources, since a qrunner with no
+ # files in its queue directory is pretty cheap, but having a separate
+ # runner process per queue allows for a very responsive system. Some
+ # people want a more traditional (i.e. MM2.0.x) cron-invoked qrunner.
+ # No problem, but using mailmanctl isn't the answer. So while
+ # mailmanctl hard codes some things, others, such as the number of
+ # qrunners per queue, are configurable.
+ #
+ # First, acquire the master mailmanctl lock
+ lock = acquire_lock(opts.force)
+ if not lock:
+ return
+ # Daemon process startup according to Stevens, Advanced Programming in
+ # the UNIX Environment, Chapter 13.
+ pid = os.fork()
+ if pid:
+ # parent
+ if not opts.quiet:
+ print _("Starting Mailman's master qrunner.")
+ # Give up the lock "ownership". This just means the foreground
+ # process won't close/unlock the lock when it finalizes this lock
+ # instance. We'll let the mater watcher subproc own the lock.
+ lock._transfer_to(pid)
+ return
+ # child
+ lock._take_possession()
+ # First, save our pid in a file for "mailmanctl stop" rendezvous. We
+ # want the perms on the .pid file to be rw-rw----
+ omask = os.umask(6)
+ try:
+ fp = open(config.PIDFILE, 'w')
+ print >> fp, os.getpid()
+ fp.close()
+ finally:
+ os.umask(omask)
+ # Create a new session and become the session leader, but since we
+ # won't be opening any terminal devices, don't do the ultra-paranoid
+ # suggestion of doing a second fork after the setsid() call.
+ os.setsid()
+ # Instead of cd'ing to root, cd to the Mailman installation home
+ os.chdir(config.PREFIX)
+ # Set our file mode creation umask
+ os.umask(007)
+ # I don't think we have any unneeded file descriptors.
+ #
+ # Now start all the qrunners. This returns a dictionary where the
+ # keys are qrunner pids and the values are tuples of the following
+ # form: (qrname, slice, count). This does its own fork and exec, and
+ # sets up its own signal handlers.
+ kids = start_all_runners()
+ # Set up a SIGALRM handler to refresh the lock once per day. The lock
+ # lifetime is 1day+6hours so this should be plenty.
+ def sigalrm_handler(signum, frame):
+ lock.refresh()
+ signal.alarm(Defaults.days(1))
+ signal.signal(signal.SIGALRM, sigalrm_handler)
+ signal.alarm(Defaults.days(1))
+ # Set up a SIGHUP handler so that if we get one, we'll pass it along
+ # to all the qrunner children. This will tell them to close and
+ # reopen their log files
+ def sighup_handler(signum, frame):
+ loginit.reopen()
+ for pid in kids.keys():
+ os.kill(pid, signal.SIGHUP)
+ # And just to tweak things...
+ qlog.info('Master watcher caught SIGHUP. Re-opening log files.')
+ signal.signal(signal.SIGHUP, sighup_handler)
+ # We also need to install a SIGTERM handler because that's what init
+ # will kill this process with when changing run levels.
+ def sigterm_handler(signum, frame):
+ for pid in kids.keys():
+ try:
+ os.kill(pid, signal.SIGTERM)
+ except OSError, e:
+ if e.errno <> errno.ESRCH: raise
+ qlog.info('Master watcher caught SIGTERM. Exiting.')
+ signal.signal(signal.SIGTERM, sigterm_handler)
+ # Finally, we need a SIGINT handler which will cause the sub-qrunners
+ # to exit, but the master will restart SIGINT'd sub-processes unless
+ # the -n flag was given.
+ def sigint_handler(signum, frame):
+ for pid in kids.keys():
+ os.kill(pid, signal.SIGINT)
+ qlog.info('Master watcher caught SIGINT. Restarting.')
+ signal.signal(signal.SIGINT, sigint_handler)
+ # Now we're ready to simply do our wait/restart loop. This is the
+ # master qrunner watcher.
+ try:
+ while True:
+ try:
+ pid, status = os.wait()
+ except OSError, e:
+ # No children? We're done
+ if e.errno == errno.ECHILD:
+ break
+ # If the system call got interrupted, just restart it.
+ elif e.errno <> errno.EINTR:
+ raise
+ continue
+ killsig = exitstatus = None
+ if os.WIFSIGNALED(status):
+ killsig = os.WTERMSIG(status)
+ if os.WIFEXITED(status):
+ exitstatus = os.WEXITSTATUS(status)
+ # We'll restart the process unless we were given the
+ # "no-restart" switch, or if the process was SIGTERM'd or
+ # exitted with a SIGTERM exit status. This lets us better
+ # handle runaway restarts (say, if the subproc had a syntax
+ # error!)
+ restarting = ''
+ if opts.restart:
+ if ((exitstatus == None and killsig <> signal.SIGTERM) or
+ (killsig == None and exitstatus <> signal.SIGTERM)):
+ # Then
+ restarting = '[restarting]'
+ qrname, slice, count, restarts = kids[pid]
+ del kids[pid]
+ qlog.info("""\
+Master qrunner detected subprocess exit
+(pid: %d, sig: %s, sts: %s, class: %s, slice: %d/%d) %s""",
+ pid, killsig, exitstatus, qrname,
+ slice+1, count, restarting)
+ # See if we've reached the maximum number of allowable restarts
+ if exitstatus <> signal.SIGINT:
+ restarts += 1
+ if restarts > MAX_RESTARTS:
+ qlog.info("""\
+Qrunner %s reached maximum restart limit of %d, not restarting.""",
+ qrname, MAX_RESTARTS)
+ restarting = ''
+ # Now perhaps restart the process unless it exited with a
+ # SIGTERM or we aren't restarting.
+ if restarting:
+ newpid = start_runner(qrname, slice, count)
+ kids[newpid] = (qrname, slice, count, restarts)
+ finally:
+ # Should we leave the main loop for any reason, we want to be sure
+ # all of our children are exited cleanly. Send SIGTERMs to all
+ # the child processes and wait for them all to exit.
+ for pid in kids.keys():
+ try:
+ os.kill(pid, signal.SIGTERM)
+ except OSError, e:
+ if e.errno == errno.ESRCH:
+ # The child has already exited
+ qlog.info('ESRCH on pid: %d', pid)
+ del kids[pid]
+ # Wait for all the children to go away
+ while True:
+ try:
+ pid, status = os.wait()
+ except OSError, e:
+ if e.errno == errno.ECHILD:
+ break
+ elif e.errno <> errno.EINTR:
+ raise
+ continue
+ # Finally, give up the lock
+ lock.unlock(unconditionally=True)
+ os._exit(0)
+
+
+
+if __name__ == '__main__':
+ main()
diff --git a/Mailman/bin/newlist.py b/Mailman/bin/newlist.py
index 26c7cd42b..0f62f5373 100644
--- a/Mailman/bin/newlist.py
+++ b/Mailman/bin/newlist.py
@@ -25,8 +25,9 @@ from Mailman import Errors
from Mailman import MailList
from Mailman import Message
from Mailman import Utils
+from Mailman import Version
from Mailman import i18n
-from Mailman import mm_cfg
+from Mailman.configuration import config
_ = i18n._
@@ -35,7 +36,7 @@ __i18n_templates__ = True
def parseargs():
- parser = optparse.OptionParser(version=mm_cfg.MAILMAN_VERSION,
+ parser = optparse.OptionParser(version=Version.MAILMAN_VERSION,
usage=_("""\
%%prog [options] [listname [listadmin-addr [admin-password]]]
@@ -87,7 +88,6 @@ defined in your Defaults.py file or overridden by settings in mm_cfg.py).
Note that listnames are forced to lowercase."""))
parser.add_option('-l', '--language',
type='string', action='store',
- default=mm_cfg.DEFAULT_SERVER_LANGUAGE,
help=_("""\
Make the list's preferred language LANGUAGE, which must be a two letter
language code."""))
@@ -110,18 +110,27 @@ This option suppresses the prompt prior to administrator notification but
still sends the notification. It can be used to make newlist totally
non-interactive but still send the notification, assuming listname,
listadmin-addr and admin-password are all specified on the command line."""))
+ parser.add_option('-C', '--config',
+ help=_('Alternative configuration file to use'))
opts, args = parser.parse_args()
- # Is the language known?
- if opts.language not in mm_cfg.LC_DESCRIPTIONS:
- parser.print_help()
- print >> sys.stderr, _('Unknown language: $opts.language')
- sys.exit(1)
+ # Can't verify opts.language here because the configuration isn't loaded
+ # yet.
return parser, opts, args
def main():
parser, opts, args = parseargs()
+ config.load(opts.config)
+
+ # Set up some defaults we couldn't set up in parseargs()
+ if opts.language is None:
+ opts.language = config.DEFAULT_SERVER_LANGUAGE
+ # Is the language known?
+ if opts.language not in config.LC_DESCRIPTIONS:
+ parser.print_help()
+ print >> sys.stderr, _('Unknown language: $opts.language')
+ sys.exit(1)
# Handle variable number of positional arguments
if args:
@@ -134,12 +143,12 @@ def main():
# Note that --urlhost and --emailhost have precedence
listname, domain = listname.split('@', 1)
urlhost = opts.urlhost or domain
- emailhost = opts.emailhost or mm_cfg.VIRTUAL_HOSTS.get(domain, domain)
+ emailhost = opts.emailhost or config.VIRTUAL_HOSTS.get(domain, domain)
- urlhost = opts.urlhost or mm_cfg.DEFAULT_URL_HOST
+ urlhost = opts.urlhost or config.DEFAULT_URL_HOST
host_name = (opts.emailhost or
- mm_cfg.VIRTUAL_HOSTS.get(urlhost, mm_cfg.DEFAULT_EMAIL_HOST))
- web_page_url = mm_cfg.DEFAULT_URL_PATTERN % urlhost
+ config.VIRTUAL_HOSTS.get(urlhost, config.DEFAULT_EMAIL_HOST))
+ web_page_url = config.DEFAULT_URL_PATTERN % urlhost
if Utils.list_exists(listname):
parser.print_help()
@@ -154,7 +163,12 @@ def main():
if args:
listpasswd = args.pop(0)
else:
- listpasswd = getpass.getpass(_('Initial $listname password: '))
+ while True:
+ listpasswd = getpass.getpass(_('Initial $listname password: '))
+ confirm = getpass.getpass(_('Confirm $listname password: '))
+ if listpasswd == confirm:
+ break
+ print _('Passwords did not match, try again (Ctrl-C to quit)')
# List passwords cannot be empty
listpasswd = listpasswd.strip()
@@ -198,8 +212,8 @@ def main():
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)
diff --git a/Mailman/bin/qrunner.py b/Mailman/bin/qrunner.py
new file mode 100644
index 000000000..1fab69d98
--- /dev/null
+++ b/Mailman/bin/qrunner.py
@@ -0,0 +1,258 @@
+# Copyright (C) 2001-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.
+
+import sys
+import signal
+import logging
+import optparse
+
+from Mailman import Version
+from Mailman import loginit
+from Mailman.configuration import config
+from Mailman.i18n import _
+
+__i18n_templates__ = True
+
+COMMASPACE = ', '
+log = None
+
+
+
+def r_callback(option, opt, value, parser):
+ dest = getattr(parser.values, option.dest)
+ parts = value.split(':')
+ if len(parts) == 1:
+ runner = parts[0]
+ rslice = rrange = 1
+ elif len(parts) == 3:
+ runner = parts[0]
+ try:
+ rslice = int(parts[1])
+ rrange = int(parts[2])
+ except ValueError:
+ parser.print_help()
+ print >> sys.stderr, _('Bad runner specification: $value')
+ sys.exit(1)
+ else:
+ parser.print_help()
+ print >> sys.stderr, _('Bad runner specification: $value')
+ sys.exit(1)
+ if runner == 'All':
+ for runnername, slices in config.QRUNNERS:
+ dest.append((runnername, rslice, rrange))
+ elif not runner.endswith('Runner'):
+ runner += 'Runner'
+ dest.append((runner, rslice, rrange))
+
+
+
+def parseargs():
+ parser = optparse.OptionParser(version=Version.MAILMAN_VERSION,
+ usage=_("""\
+Run one or more qrunners, once or repeatedly.
+
+Each named runner class is run in round-robin fashion. In other words, the
+first named runner is run to consume all the files currently in its
+directory. When that qrunner is done, the next one is run to consume all the
+files in /its/ directory, and so on. The number of total iterations can be
+given on the command line.
+
+Usage: %prog [options]
+
+-r is required unless -l or -h is given, and its argument must be one of the
+names displayed by the -l switch.
+
+Normally, this script should be started from mailmanctl. Running it
+separately or with -o is generally useful only for debugging.
+"""))
+ parser.add_option('-r', '--runner',
+ metavar='runner[:slice:range]', dest='runners',
+ type='string', default=[],
+ action='callback', callback=r_callback,
+ help=_("""\
+Run the named qrunner, which must be one of the strings returned by the -l
+option. Optional slice:range if given, is used to assign multiple qrunner
+processes to a queue. range is the total number of qrunners for this queue
+while slice is the number of this qrunner from [0..range).
+
+When using the slice:range form, you must ensure that each qrunner for the
+queue is given the same range value. If slice:runner is not given, then 1:1
+is used.
+
+Multiple -r options may be given, in which case each qrunner will run once in
+round-robin fashion. The special runner `All' is shorthand for a qrunner for
+each listed by the -l option."""))
+ parser.add_option('-o', '--once',
+ default=False, action='store_true', help=_("""\
+Run each named qrunner exactly once through its main loop. Otherwise, each
+qrunner runs indefinitely, until the process receives a SIGTERM or SIGINT."""))
+ parser.add_option('-l', '--list',
+ default=False, action='store_true',
+ help=_('List the available qrunner names and exit.'))
+ parser.add_option('-v', '--verbose',
+ default=0, action='count', help=_("""\
+Display more debugging information to the logs/qrunner log file."""))
+ parser.add_option('-s', '--subproc',
+ default=False, action='store_true', help=_("""\
+This should only be used when running qrunner as a subprocess of the
+mailmanctl startup script. It changes some of the exit-on-error behavior to
+work better with that framework."""))
+ parser.add_option('-C', '--config',
+ help=_('Alternative configuration file to use'))
+ opts, args = parser.parse_args()
+ if args:
+ parser.print_help()
+ print >> sys.stderr, _('Unexpected arguments')
+ sys.exit(1)
+ if not opts.runners and not opts.list:
+ parser.print_help()
+ print >> sys.stderr, _('No runner name given.')
+ sys.exit(1)
+ return parser, opts, args
+
+
+
+def make_qrunner(name, slice, range, once=False):
+ modulename = 'Mailman.Queue.' + name
+ try:
+ __import__(modulename)
+ except ImportError, e:
+ if opts.subproc:
+ # Exit with SIGTERM exit code so mailmanctl won't try to restart us
+ print >> sys.stderr, _('Cannot import runner module: $modulename')
+ print >> sys.stderr, e
+ sys.exit(signal.SIGTERM)
+ else:
+ print >> sys.stderr, e
+ sys.exit(1)
+ qrclass = getattr(sys.modules[modulename], name)
+ if once:
+ # Subclass to hack in the setting of the stop flag in _doperiodic()
+ class Once(qrclass):
+ def _doperiodic(self):
+ self.stop()
+ qrunner = Once(slice, range)
+ else:
+ qrunner = qrclass(slice, range)
+ return qrunner
+
+
+
+def set_signals(loop):
+ # Set up the SIGTERM handler for stopping the loop
+ def sigterm_handler(signum, frame):
+ # Exit the qrunner cleanly
+ loop.stop()
+ loop.status = signal.SIGTERM
+ log.info('%s qrunner caught SIGTERM. Stopping.', loop.name())
+ signal.signal(signal.SIGTERM, sigterm_handler)
+ # Set up the SIGINT handler for stopping the loop. For us, SIGINT is
+ # the same as SIGTERM, but our parent treats the exit statuses
+ # differently (it restarts a SIGINT but not a SIGTERM).
+ def sigint_handler(signum, frame):
+ # Exit the qrunner cleanly
+ loop.stop()
+ loop.status = signal.SIGINT
+ log.info('%s qrunner caught SIGINT. Stopping.', loop.name())
+ signal.signal(signal.SIGINT, sigint_handler)
+ # SIGHUP just tells us to rotate our log files.
+ def sighup_handler(signum, frame):
+ loginit.reopen()
+ log.info('%s qrunner caught SIGHUP. Reopening logs.', loop.name())
+ signal.signal(signal.SIGHUP, sighup_handler)
+
+
+
+def main():
+ global log, opts
+
+ parser, opts, args = parseargs()
+ config.load(opts.config)
+
+ # If we're not running as a subprocess of mailmanctl, then we'll log to
+ # stderr in addition to logging to the log files. We do this by passing a
+ # value of True to propagate, which allows the 'mailman' root logger to
+ # see the log messages.
+ loginit.initialize(propagate=not opts.subproc)
+ log = logging.getLogger('mailman.qrunner')
+
+ if opts.list:
+ for runnername, slices in config.QRUNNERS:
+ if runnername.endswith('Runner'):
+ name = runnername[:-len('Runner')]
+ else:
+ name = runnername
+ print _('$name runs the $runnername qrunner')
+ print _('All runs all the above qrunners')
+ sys.exit(0)
+
+ # Fast track for one infinite runner
+ if len(opts.runners) == 1 and not opts.once:
+ qrunner = make_qrunner(*opts.runners[0])
+ class Loop:
+ status = 0
+ def __init__(self, qrunner):
+ self._qrunner = qrunner
+ def name(self):
+ return self._qrunner.__class__.__name__
+ def stop(self):
+ self._qrunner.stop()
+ loop = Loop(qrunner)
+ set_signals(loop)
+ # Now start up the main loop
+ log.info('%s qrunner started.', loop.name())
+ qrunner.run()
+ log.info('%s qrunner exiting.', loop.name())
+ else:
+ # Anything else we have to handle a bit more specially
+ qrunners = []
+ for runner, rslice, rrange in opts.runners:
+ qrunner = make_qrunner(runner, rslice, rrange, once=True)
+ qrunners.append(qrunner)
+ # This class is used to manage the main loop
+ class Loop:
+ status = 0
+ def __init__(self):
+ self._isdone = False
+ def name(self):
+ return 'Main loop'
+ def stop(self):
+ self._isdone = True
+ def isdone(self):
+ return self._isdone
+ loop = Loop()
+ set_signals(loop)
+ log.info('Main qrunner loop started.')
+ while not loop.isdone():
+ for qrunner in qrunners:
+ # In case the SIGTERM came in the middle of this iteration
+ if loop.isdone():
+ break
+ if opts.verbose:
+ log.info('Now doing a %s qrunner iteration',
+ qrunner.__class__.__bases__[0].__name__)
+ qrunner.run()
+ if opts.once:
+ break
+ log.info('Main qrunner loop exiting.')
+ # All done
+ sys.exit(loop.status)
+
+
+
+if __name__ == '__main__':
+ main()
diff --git a/Mailman/bin/rmlist.py b/Mailman/bin/rmlist.py
index aef927c88..9655327f0 100644
--- a/Mailman/bin/rmlist.py
+++ b/Mailman/bin/rmlist.py
@@ -22,7 +22,8 @@ import optparse
from Mailman import MailList
from Mailman import Utils
-from Mailman import mm_cfg
+from Mailman import Version
+from Mailman.configuration import config
from Mailman.i18n import _
__i18n_templates__ = True
@@ -44,7 +45,7 @@ def remove_it(listname, filename, msg):
def parseargs():
- parser = optparse.OptionParser(version=mm_cfg.MAILMAN_VERSION,
+ parser = optparse.OptionParser(version=Version.MAILMAN_VERSION,
usage=_("""\
%prog [options] listname
@@ -57,6 +58,8 @@ archives are not removed, which is very handy for retiring old lists.
default=False, action='store_true', help=_("""\
Remove the list's archives too, or if the list has already been deleted,
remove any residual archives."""))
+ parser.add_option('-C', '--config',
+ help=_('Alternative configuration file to use'))
opts, args = parser.parse_args()
if not args:
parser.print_help()
@@ -72,6 +75,8 @@ remove any residual archives."""))
def main():
parser, opts, args = parseargs()
+ config.load(opts.config)
+
listname = args[0].lower().strip()
if not Utils.list_exists(listname):
if not opts.archives:
@@ -89,18 +94,18 @@ def main():
if Utils.list_exists(listname):
mlist = MailList.MailList(listname, lock=False)
# Do the MTA-specific list deletion tasks
- if mm_cfg.MTA:
- modname = 'Mailman.MTA.' + mm_cfg.MTA
+ if config.MTA:
+ modname = 'Mailman.MTA.' + config.MTA
__import__(modname)
sys.modules[modname].remove(mlist)
removeables.append((os.path.join('lists', listname), _('list info')))
# Remove any stale locks associated with the list
- for filename in os.listdir(mm_cfg.LOCK_DIR):
+ for filename in os.listdir(config.LOCK_DIR):
fn_listname = filename.split('.')[0]
if fn_listname == listname:
- removeables.append((os.path.join(mm_cfg.LOCK_DIR, filename),
+ removeables.append((os.path.join(config.LOCK_DIR, filename),
_('stale lock file')))
if opts.archives:
@@ -116,7 +121,7 @@ def main():
])
for dirtmpl, msg in removeables:
- path = os.path.join(mm_cfg.VAR_PREFIX, dirtmpl)
+ path = os.path.join(config.VAR_PREFIX, dirtmpl)
remove_it(listname, path, msg)
diff --git a/Mailman/bin/update.py b/Mailman/bin/update.py
new file mode 100644
index 000000000..ea74abfd6
--- /dev/null
+++ b/Mailman/bin/update.py
@@ -0,0 +1,767 @@
+# 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
+# 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.
+
+import os
+import md5
+import sys
+import time
+import email
+import errno
+import shutil
+import cPickle
+import marshal
+import optparse
+
+from Mailman import MailList
+from Mailman import Message
+from Mailman import Pending
+from Mailman import Utils
+from Mailman import Version
+from Mailman.LockFile import TimeOutError
+from Mailman.MemberAdaptor import BYBOUNCE, ENABLED
+from Mailman.OldStyleMemberships import OldStyleMemberships
+from Mailman.Queue.Switchboard import Switchboard
+from Mailman.configuration import config
+from Mailman.i18n import _
+
+__i18n_templates__ = True
+
+FRESH = 0
+NOTFRESH = -1
+
+
+
+def parseargs():
+ parser = optparse.OptionParser(version=Version.MAILMAN_VERSION,
+ usage=_("""\
+Perform all necessary upgrades.
+
+%prog [options]"""))
+ parser.add_option('-f', '--force',
+ default=False, action='store_true', help=_("""\
+Force running the upgrade procedures. Normally, if the version number of the
+installed Mailman matches the current version number (or a 'downgrade' is
+detected), nothing will be done."""))
+ parser.add_option('-C', '--config',
+ help=_('Alternative configuration file to use'))
+ opts, args = parser.parse_args()
+ if args:
+ parser.print_help()
+ print >> sys.stderr, _('Unexpected arguments')
+ sys.exit(1)
+ return parser, opts, args
+
+
+
+def calcversions():
+ # Returns a tuple of (lastversion, thisversion). If the last version
+ # could not be determined, lastversion will be FRESH or NOTFRESH,
+ # depending on whether this installation appears to be fresh or not. The
+ # determining factor is whether there are files in the $var_prefix/logs
+ # subdir or not. The version numbers are HEX_VERSIONs.
+ #
+ # See if we stored the last updated version
+ lastversion = None
+ thisversion = config.HEX_VERSION
+ try:
+ fp = open(os.path.join(config.DATA_DIR, 'last_mailman_version'))
+ data = fp.read()
+ fp.close()
+ lastversion = int(data, 16)
+ except (IOError, ValueError):
+ pass
+ #
+ # try to figure out if this is a fresh install
+ if lastversion is None:
+ lastversion = FRESH
+ try:
+ if os.listdir(config.LOG_DIR):
+ lastversion = NOTFRESH
+ except OSError:
+ pass
+ return (lastversion, thisversion)
+
+
+
+def makeabs(relpath):
+ return os.path.join(config.PREFIX, relpath)
+
+
+def make_varabs(relpath):
+ return os.path.join(config.VAR_PREFIX, relpath)
+
+
+
+def move_language_templates(mlist):
+ listname = mlist.internal_name()
+ print _('Fixing language templates: $listname')
+ # Mailman 2.1 has a new cascading search for its templates, defined and
+ # described in Utils.py:maketext(). Putting templates in the top level
+ # templates/ subdir or the lists/<listname> subdir is deprecated and no
+ # longer searched..
+ #
+ # What this means is that most templates can live in the global templates/
+ # subdirectory, and only needs to be copied into the list-, vhost-, or
+ # site-specific language directories when needed.
+ #
+ # Also, by default all standard (i.e. English) templates must now live in
+ # the templates/en directory. This update cleans up all the templates,
+ # deleting more-specific duplicates (as calculated by md5 checksums) in
+ # favor of more-global locations.
+ #
+ # First, get rid of any lists/<list> template or lists/<list>/en template
+ # that is identical to the global templates/* default.
+ for gtemplate in os.listdir(os.path.join(config.TEMPLATE_DIR, 'en')):
+ # BAW: get rid of old templates, e.g. admlogin.txt and
+ # handle_opts.html
+ try:
+ fp = open(os.path.join(config.TEMPLATE_DIR, gtemplate))
+ except IOError, e:
+ if e.errno <> errno.ENOENT:
+ raise
+ # No global template
+ continue
+ gcksum = md5.new(fp.read()).digest()
+ fp.close()
+ # Match against the lists/<list>/* template
+ try:
+ fp = open(os.path.join(mlist.fullpath(), gtemplate))
+ except IOError, e:
+ if e.errno <> errno.ENOENT:
+ raise
+ else:
+ tcksum = md5.new(fp.read()).digest()
+ fp.close()
+ if gcksum == tcksum:
+ os.unlink(os.path.join(mlist.fullpath(), gtemplate))
+ # Match against the lists/<list>/*.prev template
+ try:
+ fp = open(os.path.join(mlist.fullpath(), gtemplate + '.prev'))
+ except IOError, e:
+ if e.errno <> errno.ENOENT:
+ raise
+ else:
+ tcksum = md5.new(fp.read()).digest()
+ fp.close()
+ if gcksum == tcksum:
+ os.unlink(os.path.join(mlist.fullpath(), gtemplate + '.prev'))
+ # Match against the lists/<list>/en/* templates
+ try:
+ fp = open(os.path.join(mlist.fullpath(), 'en', gtemplate))
+ except IOError, e:
+ if e.errno <> errno.ENOENT:
+ raise
+ else:
+ tcksum = md5.new(fp.read()).digest()
+ fp.close()
+ if gcksum == tcksum:
+ os.unlink(os.path.join(mlist.fullpath(), 'en', gtemplate))
+ # Match against the templates/* template
+ try:
+ fp = open(os.path.join(config.TEMPLATE_DIR, gtemplate))
+ except IOError, e:
+ if e.errno <> errno.ENOENT:
+ raise
+ else:
+ tcksum = md5.new(fp.read()).digest()
+ fp.close()
+ if gcksum == tcksum:
+ os.unlink(os.path.join(config.TEMPLATE_DIR, gtemplate))
+ # Match against the templates/*.prev template
+ try:
+ fp = open(os.path.join(config.TEMPLATE_DIR, gtemplate + '.prev'))
+ except IOError, e:
+ if e.errno <> errno.ENOENT:
+ raise
+ else:
+ tcksum = md5.new(fp.read()).digest()
+ fp.close()
+ if gcksum == tcksum:
+ os.unlink(os.path.join(config.TEMPLATE_DIR,
+ gtemplate + '.prev'))
+
+
+
+def dolist(listname):
+ mlist = MailList.MailList(listname, lock=False)
+ try:
+ mlist.Lock(0.5)
+ except TimeOutError:
+ print >> sys.stderr, _(
+ 'WARNING: could not acquire lock for list: $listname')
+ return 1
+ # Sanity check the invariant that every BYBOUNCE disabled member must have
+ # bounce information. Some earlier betas broke this. BAW: we're
+ # submerging below the MemberAdaptor interface, so skip this if we're not
+ # using OldStyleMemberships.
+ if isinstance(mlist._memberadaptor, OldStyleMemberships):
+ noinfo = {}
+ for addr, (reason, when) in mlist.delivery_status.items():
+ if reason == BYBOUNCE and not mlist.bounce_info.has_key(addr):
+ noinfo[addr] = reason, when
+ # What to do about these folks with a BYBOUNCE delivery status and no
+ # bounce info? This number should be very small, and I think it's
+ # fine to simple re-enable them and let the bounce machinery
+ # re-disable them if necessary.
+ n = len(noinfo)
+ if n > 0:
+ print _(
+ 'Resetting $n BYBOUNCEs disabled addrs with no bounce info')
+ for addr in noinfo.keys():
+ mlist.setDeliveryStatus(addr, ENABLED)
+
+ # Update the held requests database
+ print _("""Updating the held requests database.""")
+ mlist._UpdateRecords()
+
+ mbox_dir = make_varabs('archives/private/%s.mbox' % (listname))
+ mbox_file = make_varabs('archives/private/%s.mbox/%s' % (listname,
+ listname))
+ o_pub_mbox_file = make_varabs('archives/public/%s' % (listname))
+ o_pri_mbox_file = make_varabs('archives/private/%s' % (listname))
+ html_dir = o_pri_mbox_file
+ o_html_dir = makeabs('public_html/archives/%s' % (listname))
+ # Make the mbox directory if it's not there.
+ if not os.path.exists(mbox_dir):
+ ou = os.umask(0)
+ os.mkdir(mbox_dir, 02775)
+ os.umask(ou)
+ else:
+ # This shouldn't happen, but hey, just in case
+ if not os.path.isdir(mbox_dir):
+ print _("""\
+For some reason, $mbox_dir exists as a file. This won't work with b6, so I'm
+renaming it to ${mbox_dir}.tmp and proceeding.""")
+ os.rename(mbox_dir, "%s.tmp" % (mbox_dir))
+ ou = os.umask(0)
+ os.mkdir(mbox_dir, 02775)
+ os.umask(ou)
+ # Move any existing mboxes around, but watch out for both a public and a
+ # private one existing
+ if os.path.isfile(o_pri_mbox_file) and os.path.isfile(o_pub_mbox_file):
+ if mlist.archive_private:
+ print _("""\
+
+$listname has both public and private mbox archives. Since this list
+currently uses private archiving, I'm installing the private mbox archive --
+$o_pri_mbox_file -- as the active archive, and renaming
+ $o_pub_mbox_file
+to
+ ${o_pub_mbox_file}.preb6
+
+You can integrate that into the archives if you want by using the 'arch'
+script.
+""") % (mlist._internal_name, o_pri_mbox_file, o_pub_mbox_file,
+ o_pub_mbox_file)
+ os.rename(o_pub_mbox_file, "%s.preb6" % (o_pub_mbox_file))
+ else:
+ print _("""\
+$mlist._internal_name has both public and private mbox archives. Since this
+list currently uses public archiving, I'm installing the public mbox file
+archive file ($o_pub_mbox_file) as the active one, and renaming
+$o_pri_mbox_file to ${o_pri_mbox_file}.preb6
+
+You can integrate that into the archives if you want by using the 'arch'
+script.
+""")
+ os.rename(o_pri_mbox_file, "%s.preb6" % (o_pri_mbox_file))
+ # Move private archive mbox there if it's around
+ # and take into account all sorts of absurdities
+ print _('- updating old private mbox file')
+ if os.path.exists(o_pri_mbox_file):
+ if os.path.isfile(o_pri_mbox_file):
+ os.rename(o_pri_mbox_file, mbox_file)
+ elif not os.path.isdir(o_pri_mbox_file):
+ newname = "%s.mm_install-dunno_what_this_was_but_its_in_the_way" \
+ % o_pri_mbox_file
+ os.rename(o_pri_mbox_file, newname)
+ print _("""\
+ unknown file in the way, moving
+ $o_pri_mbox_file
+ to
+ $newname""")
+ else:
+ # directory
+ print _("""\
+ looks like you have a really recent CVS installation...
+ you're either one brave soul, or you already ran me""")
+ # Move public archive mbox there if it's around
+ # and take into account all sorts of absurdities.
+ print _('- updating old public mbox file')
+ if os.path.exists(o_pub_mbox_file):
+ if os.path.isfile(o_pub_mbox_file):
+ os.rename(o_pub_mbox_file, mbox_file)
+ elif not os.path.isdir(o_pub_mbox_file):
+ newname = "%s.mm_install-dunno_what_this_was_but_its_in_the_way" \
+ % o_pub_mbox_file
+ os.rename(o_pub_mbox_file, newname)
+ print _("""\
+ unknown file in the way, moving
+ $o_pub_mbox_file
+ to
+ $newname""")
+ else: # directory
+ print _("""\
+ looks like you have a really recent CVS installation...
+ you're either one brave soul, or you already ran me""")
+ # Move the html archives there
+ if os.path.isdir(o_html_dir):
+ os.rename(o_html_dir, html_dir)
+ # chmod the html archives
+ os.chmod(html_dir, 02775)
+ # BAW: Is this still necessary?!
+ mlist.Save()
+ # Check to see if pre-b4 list-specific templates are around
+ # and move them to the new place if there's not already
+ # a new one there
+ tmpl_dir = os.path.join(config.PREFIX, "templates")
+ list_dir = os.path.join(config.PREFIX, "lists")
+ b4_tmpl_dir = os.path.join(tmpl_dir, mlist._internal_name)
+ new_tmpl_dir = os.path.join(list_dir, mlist._internal_name)
+ if os.path.exists(b4_tmpl_dir):
+ print _("""\
+- This list looks like it might have <= b4 list templates around""")
+ for f in os.listdir(b4_tmpl_dir):
+ o_tmpl = os.path.join(b4_tmpl_dir, f)
+ n_tmpl = os.path.join(new_tmpl_dir, f)
+ if os.path.exists(o_tmpl):
+ if not os.path.exists(n_tmpl):
+ os.rename(o_tmpl, n_tmpl)
+ print _('- moved $o_tmpl to $n_tmpl')
+ else:
+ print _("""\
+- both $o_tmpl and $n_tmpl exist, leaving untouched""")
+ else:
+ print _("""\
+- $o_tmpl doesn't exist, leaving untouched""")
+ # Move all the templates to the en language subdirectory as required for
+ # Mailman 2.1
+ move_language_templates(mlist)
+ # Avoid eating filehandles with the list lockfiles
+ mlist.Unlock()
+ return 0
+
+
+
+def archive_path_fixer(unused_arg, dir, files):
+ # Passed to os.path.walk to fix the perms on old html archives.
+ for f in files:
+ abs = os.path.join(dir, f)
+ if os.path.isdir(abs):
+ if f == "database":
+ os.chmod(abs, 02770)
+ else:
+ os.chmod(abs, 02775)
+ elif os.path.isfile(abs):
+ os.chmod(abs, 0664)
+
+
+def remove_old_sources(module):
+ # Also removes old directories.
+ src = '%s/%s' % (config.PREFIX, module)
+ pyc = src + "c"
+ if os.path.isdir(src):
+ print _('removing directory $src and everything underneath')
+ shutil.rmtree(src)
+ elif os.path.exists(src):
+ print _('removing $src')
+ try:
+ os.unlink(src)
+ except os.error, rest:
+ print _("Warning: couldn't remove $src -- $rest")
+ if module.endswith('.py') and os.path.exists(pyc):
+ try:
+ os.unlink(pyc)
+ except OSError, rest:
+ print _("couldn't remove old file $pyc -- $rest")
+
+
+
+def update_qfiles():
+ print _('updating old qfiles')
+ prefix = `time.time()` + '+'
+ # Be sure the qfiles/in directory exists (we don't really need the
+ # switchboard object, but it's convenient for creating the directory).
+ sb = Switchboard(config.INQUEUE_DIR)
+ for filename in os.listdir(config.QUEUE_DIR):
+ # Updating means just moving the .db and .msg files to qfiles/in where
+ # it should be dequeued, converted, and processed normally.
+ if os.path.splitext(filename) == '.msg':
+ oldmsgfile = os.path.join(config.QUEUE_DIR, filename)
+ newmsgfile = os.path.join(config.INQUEUE_DIR, prefix + filename)
+ os.rename(oldmsgfile, newmsgfile)
+ elif os.path.splitext(filename) == '.db':
+ olddbfile = os.path.join(config.QUEUE_DIR, filename)
+ newdbfile = os.path.join(config.INQUEUE_DIR, prefix + filename)
+ os.rename(olddbfile, newdbfile)
+ # Now update for the Mailman 2.1.5 qfile format. For every filebase in
+ # the qfiles/* directories that has both a .pck and a .db file, pull the
+ # data out and re-queue them.
+ for dirname in os.listdir(config.QUEUE_DIR):
+ dirpath = os.path.join(config.QUEUE_DIR, dirname)
+ if dirpath == config.BADQUEUE_DIR:
+ # The files in qfiles/bad can't possibly be pickles
+ continue
+ sb = Switchboard(dirpath)
+ try:
+ for filename in os.listdir(dirpath):
+ filepath = os.path.join(dirpath, filename)
+ filebase, ext = os.path.splitext(filepath)
+ # Handle the .db metadata files as part of the handling of the
+ # .pck or .msg message files.
+ if ext not in ('.pck', '.msg'):
+ continue
+ msg, data = dequeue(filebase)
+ if msg is not None and data is not None:
+ sb.enqueue(msg, data)
+ except EnvironmentError, e:
+ if e.errno <> errno.ENOTDIR:
+ raise
+ print _('Warning! Not a directory: $dirpath')
+
+
+
+# Implementations taken from the pre-2.1.5 Switchboard
+def ext_read(filename):
+ fp = open(filename)
+ d = marshal.load(fp)
+ # Update from version 2 files
+ if d.get('version', 0) == 2:
+ del d['filebase']
+ # Do the reverse conversion (repr -> float)
+ for attr in ['received_time']:
+ try:
+ sval = d[attr]
+ except KeyError:
+ pass
+ else:
+ # Do a safe eval by setting up a restricted execution
+ # environment. This may not be strictly necessary since we
+ # know they are floats, but it can't hurt.
+ d[attr] = eval(sval, {'__builtins__': {}})
+ fp.close()
+ return d
+
+
+def dequeue(filebase):
+ # Calculate the .db and .msg filenames from the given filebase.
+ msgfile = os.path.join(filebase + '.msg')
+ pckfile = os.path.join(filebase + '.pck')
+ dbfile = os.path.join(filebase + '.db')
+ # Now we are going to read the message and metadata for the given
+ # filebase. We want to read things in this order: first, the metadata
+ # file to find out whether the message is stored as a pickle or as
+ # plain text. Second, the actual message file. However, we want to
+ # first unlink the message file and then the .db file, because the
+ # qrunner only cues off of the .db file
+ msg = None
+ try:
+ data = ext_read(dbfile)
+ os.unlink(dbfile)
+ except EnvironmentError, e:
+ if e.errno <> errno.ENOENT:
+ raise
+ data = {}
+ # Between 2.1b4 and 2.1b5, the `rejection-notice' key in the metadata
+ # was renamed to `rejection_notice', since dashes in the keys are not
+ # supported in METAFMT_ASCII.
+ if data.has_key('rejection-notice'):
+ data['rejection_notice'] = data['rejection-notice']
+ del data['rejection-notice']
+ msgfp = None
+ try:
+ try:
+ msgfp = open(pckfile)
+ msg = cPickle.load(msgfp)
+ os.unlink(pckfile)
+ except EnvironmentError, e:
+ if e.errno <> errno.ENOENT: raise
+ msgfp = None
+ try:
+ msgfp = open(msgfile)
+ msg = email.message_from_file(msgfp, Message.Message)
+ os.unlink(msgfile)
+ except EnvironmentError, e:
+ if e.errno <> errno.ENOENT: raise
+ except (email.Errors.MessageParseError, ValueError), e:
+ # This message was unparsable, most likely because its
+ # MIME encapsulation was broken. For now, there's not
+ # much we can do about it.
+ print _('message is unparsable: $filebase')
+ msgfp.close()
+ msgfp = None
+ if config.QRUNNER_SAVE_BAD_MESSAGES:
+ # Cheapo way to ensure the directory exists w/ the
+ # proper permissions.
+ sb = Switchboard(config.BADQUEUE_DIR)
+ os.rename(msgfile, os.path.join(
+ config.BADQUEUE_DIR, filebase + '.txt'))
+ else:
+ os.unlink(msgfile)
+ msg = data = None
+ except EOFError:
+ # For some reason the pckfile was empty. Just delete it.
+ print _('Warning! Deleting empty .pck file: $pckfile')
+ os.unlink(pckfile)
+ finally:
+ if msgfp:
+ msgfp.close()
+ return msg, data
+
+
+
+def update_pending():
+ file20 = os.path.join(config.DATA_DIR, 'pending_subscriptions.db')
+ file214 = os.path.join(config.DATA_DIR, 'pending.pck')
+ db = None
+ # Try to load the Mailman 2.0 file
+ try:
+ fp = open(file20)
+ except IOError, e:
+ if e.errno <> errno.ENOENT:
+ raise
+ else:
+ print _('Updating Mailman 2.0 pending_subscriptions.db database')
+ db = marshal.load(fp)
+ # Convert to the pre-Mailman 2.1.5 format
+ db = Pending._update(db)
+ if db is None:
+ # Try to load the Mailman 2.1.x where x < 5, file
+ try:
+ fp = open(file214)
+ except IOError, e:
+ if e.errno <> errno.ENOENT:
+ raise
+ else:
+ print _('Updating Mailman 2.1.4 pending.pck database')
+ db = cPickle.load(fp)
+ if db is None:
+ print _('Nothing to do.')
+ return
+ # Now upgrade the database to the 2.1.5 format. Each list now has its own
+ # pending.pck file, but only the RE_ENABLE operation actually recorded the
+ # listname in the request. For the SUBSCRIPTION, UNSUBSCRIPTION, and
+ # CHANGE_OF_ADDRESS operations, we know the address of the person making
+ # the request so we can repend this request just for the lists the person
+ # is a member of. For the HELD_MESSAGE operation, we can check the list's
+ # requests.pck file for correlation. Evictions will take care of any
+ # misdirected pendings.
+ reenables_by_list = {}
+ addrops_by_address = {}
+ holds_by_id = {}
+ subs_by_address = {}
+ for key, val in db.items():
+ if key in ('evictions', 'version'):
+ continue
+ try:
+ op = val[0]
+ data = val[1:]
+ except (IndexError, ValueError):
+ print _('Ignoring bad pended data: $key: $val')
+ continue
+ if op in (Pending.UNSUBSCRIPTION, Pending.CHANGE_OF_ADDRESS):
+ # data[0] is the address being unsubscribed
+ addrops_by_address.setdefault(data[0], []).append((key, val))
+ elif op == Pending.SUBSCRIPTION:
+ # data[0] is a UserDesc object
+ addr = data[0].address
+ subs_by_address.setdefault(addr, []).append((key, val))
+ elif op == Pending.RE_ENABLE:
+ # data[0] is the mailing list's internal name
+ reenables_by_list.setdefault(data[0], []).append((key, val))
+ elif op == Pending.HELD_MESSAGE:
+ # data[0] is the hold id. There better only be one entry per id
+ id = data[0]
+ if holds_by_id.has_key(id):
+ print _('WARNING: Ignoring duplicate pending ID: $id.')
+ else:
+ holds_by_id[id] = (key, val)
+ # Now we have to lock every list and re-pend all the appropriate
+ # requests. Note that this will reset all the expiration dates, but that
+ # should be fine.
+ for listname in Utils.list_names():
+ mlist = MailList.MailList(listname)
+ # This is not the most efficient way to do this because it loads and
+ # saves the pending.pck file each time. :(
+ try:
+ for cookie, data in reenables_by_list.get(listname, []):
+ mlist.pend_repend(cookie, data)
+ for id, (cookie, data) in holds_by_id.items():
+ try:
+ rec = mlist.GetRecord(id)
+ except KeyError:
+ # Not for this list
+ pass
+ else:
+ mlist.pend_repend(cookie, data)
+ del holds_by_id[id]
+ for addr, recs in subs_by_address.items():
+ # We shouldn't have a subscription confirmation if the address
+ # is already a member of the mailing list.
+ if mlist.isMember(addr):
+ continue
+ for cookie, data in recs:
+ mlist.pend_repend(cookie, data)
+ for addr, recs in addrops_by_address.items():
+ # We shouldn't have unsubscriptions or change of address
+ # requests for addresses which aren't members of the list.
+ if not mlist.isMember(addr):
+ continue
+ for cookie, data in recs:
+ mlist.pend_repend(cookie, data)
+ mlist.Save()
+ finally:
+ mlist.Unlock()
+ try:
+ os.unlink(file20)
+ except OSError, e:
+ if e.errno <> errno.ENOENT:
+ raise
+ try:
+ os.unlink(file214)
+ except OSError, e:
+ if e.errno <> errno.ENOENT:
+ raise
+
+
+
+def main():
+ parser, opts, args = parseargs()
+ config.load(opts.config)
+
+ # calculate the versions
+ lastversion, thisversion = calcversions()
+ hexlversion = hex(lastversion)
+ hextversion = hex(thisversion)
+ if lastversion == thisversion and not opts.force:
+ # nothing to do
+ print _('No updates are necessary.')
+ sys.exit(0)
+ if lastversion > thisversion and not opts.force:
+ print _("""\
+Downgrade detected, from version $hexlversion to version $hextversion
+This is probably not safe.
+Exiting.""")
+ sys.exit(1)
+ print _('Upgrading from version $hexlversion to $hextversion')
+ errors = 0
+ # get rid of old stuff
+ print _('getting rid of old source files')
+ for mod in ('Mailman/Archiver.py', 'Mailman/HyperArch.py',
+ 'Mailman/HyperDatabase.py', 'Mailman/pipermail.py',
+ 'Mailman/smtplib.py', 'Mailman/Cookie.py',
+ 'bin/update_to_10b6', 'scripts/mailcmd',
+ 'scripts/mailowner', 'mail/wrapper', 'Mailman/pythonlib',
+ 'cgi-bin/archives', 'Mailman/MailCommandHandler'):
+ remove_old_sources(mod)
+ listnames = Utils.list_names()
+ if not listnames:
+ print _('no lists == nothing to do, exiting')
+ return
+ #
+ # 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
+If your archives are big, this could take a minute or two...""")
+ os.path.walk("%s/public_html/archives" % config.PREFIX,
+ archive_path_fixer, "")
+ print _('done')
+ for listname in listnames:
+ print _('Updating mailing list: $listname')
+ errors = errors + dolist(listname)
+ print
+ print _('Updating Usenet watermarks')
+ wmfile = os.path.join(config.DATA_DIR, 'gate_watermarks')
+ try:
+ fp = open(wmfile)
+ except IOError:
+ print _('- nothing to update here')
+ else:
+ d = marshal.load(fp)
+ fp.close()
+ for listname in d.keys():
+ if listname not in listnames:
+ # this list no longer exists
+ continue
+ mlist = MailList.MailList(listname, lock=0)
+ try:
+ mlist.Lock(0.5)
+ except TimeOutError:
+ print >> sys.stderr, _(
+ 'WARNING: could not acquire lock for list: $listname')
+ errors = errors + 1
+ else:
+ # Pre 1.0b7 stored 0 in the gate_watermarks file to indicate
+ # that no gating had been done yet. Without coercing this to
+ # None, the list could now suddenly get flooded.
+ mlist.usenet_watermark = d[listname] or None
+ mlist.Save()
+ mlist.Unlock()
+ os.unlink(wmfile)
+ print _('- usenet watermarks updated and gate_watermarks removed')
+ # In Mailman 2.1, the pending database format and file name changed, but
+ # in Mailman 2.1.5 it changed again. This should update all existing
+ # files to the 2.1.5 format.
+ update_pending()
+ # In Mailman 2.1, the qfiles directory has a different structure and a
+ # different content. Also, in Mailman 2.1.5 we collapsed the message
+ # files from separate .msg (pickled Message objects) and .db (marshalled
+ # dictionaries) to a shared .pck file containing two pickles.
+ update_qfiles()
+ # This warning was necessary for the upgrade from 1.0b9 to 1.0b10.
+ # There's no good way of figuring this out for releases prior to 2.0beta2
+ # :(
+ if lastversion == NOTFRESH:
+ print _("""
+
+NOTE NOTE NOTE NOTE NOTE
+
+ You are upgrading an existing Mailman installation, but I can't tell what
+ version you were previously running.
+
+ If you are upgrading from Mailman 1.0b9 or earlier you will need to
+ manually update your mailing lists. For each mailing list you need to
+ copy the file templates/options.html lists/<listname>/options.html.
+
+ However, if you have edited this file via the Web interface, you will have
+ to merge your changes into this file, otherwise you will lose your
+ changes.
+
+NOTE NOTE NOTE NOTE NOTE
+
+""")
+ if not errors:
+ # Record the version we just upgraded to
+ fp = open(os.path.join(config.DATA_DIR, 'last_mailman_version'), 'w')
+ fp.write(hex(config.HEX_VERSION) + '\n')
+ fp.close()
+ else:
+ lockdir = config.LOCK_DIR
+ print _('''\
+
+ERROR:
+
+The locks for some lists could not be acquired. This means that either
+Mailman was still active when you upgraded, or there were stale locks in the
+$lockdir directory.
+
+You must put Mailman into a quiescent state and remove all stale locks, then
+re-run "make update" manually. See the INSTALL and UPGRADE files for details.
+''')
diff --git a/Mailman/configuration.py b/Mailman/configuration.py
new file mode 100644
index 000000000..cf3af03f3
--- /dev/null
+++ b/Mailman/configuration.py
@@ -0,0 +1,103 @@
+# Copyright (C) 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.
+
+"""Configuration file loading and management."""
+
+import os
+import errno
+
+from Mailman import Defaults
+
+_missing = object()
+
+
+
+class Configuration(object):
+ 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,
+ # use Mailman/mm_cfg.py for backward compatibility.
+ #
+ # Whatever you find, create a namespace and execfile that file in it.
+ # The values in that namespace are exposed as attributes on this
+ # Configuration instance.
+ if filename is None:
+ filename = os.path.join(Defaults.VAR_PREFIX, 'etc', 'mailman.cfg')
+ # Set up the execfile namespace
+ ns = Defaults.__dict__.copy()
+ # Prune a few things
+ del ns['__file__']
+ del ns['__name__']
+ del ns['__doc__']
+ # Attempt our first choice
+ path = os.path.abspath(os.path.expanduser(filename))
+ try:
+ execfile(path, ns, ns)
+ except EnvironmentError, e:
+ if e.errno <> errno.ENOENT:
+ raise
+ # The file didn't exist, so try mm_cfg.py
+ from Mailman import mm_cfg
+ ns = mm_cfg.__dict__.copy()
+ # Pull out the defaults
+ PREFIX = ns['PREFIX']
+ VAR_PREFIX = ns['VAR_PREFIX']
+ EXEC_PREFIX = ns['EXEC_PREFIX']
+ # Now that we've loaded all the configuration files we're going to
+ # load, set up some useful directories.
+ self.LIST_DATA_DIR = os.path.join(VAR_PREFIX, 'lists')
+ self.LOG_DIR = os.path.join(VAR_PREFIX, 'logs')
+ self.LOCK_DIR = lockdir = os.path.join(VAR_PREFIX, 'locks')
+ self.DATA_DIR = datadir = os.path.join(VAR_PREFIX, 'data')
+ self.ETC_DIR = etcdir = os.path.join(VAR_PREFIX, 'etc')
+ self.SPAM_DIR = os.path.join(VAR_PREFIX, 'spam')
+ self.WRAPPER_DIR = os.path.join(EXEC_PREFIX, 'mail')
+ self.BIN_DIR = os.path.join(PREFIX, 'bin')
+ self.SCRIPTS_DIR = os.path.join(PREFIX, 'scripts')
+ self.TEMPLATE_DIR = os.path.join(PREFIX, 'templates')
+ self.MESSAGES_DIR = os.path.join(PREFIX, 'messages')
+ self.PUBLIC_ARCHIVE_FILE_DIR = os.path.join(VAR_PREFIX,
+ 'archives', 'public')
+ self.PRIVATE_ARCHIVE_FILE_DIR = os.path.join(VAR_PREFIX,
+ 'archives', 'private')
+ # Directories used by the qrunner subsystem
+ self.QUEUE_DIR = qdir = os.path.join(VAR_PREFIX, 'qfiles')
+ self.INQUEUE_DIR = os.path.join(qdir, 'in')
+ self.OUTQUEUE_DIR = os.path.join(qdir, 'out')
+ self.CMDQUEUE_DIR = os.path.join(qdir, 'commands')
+ self.BOUNCEQUEUE_DIR = os.path.join(qdir, 'bounces')
+ self.NEWSQUEUE_DIR = os.path.join(qdir, 'news')
+ self.ARCHQUEUE_DIR = os.path.join(qdir, 'archive')
+ self.SHUNTQUEUE_DIR = os.path.join(qdir, 'shunt')
+ self.VIRGINQUEUE_DIR = os.path.join(qdir, 'virgin')
+ self.BADQUEUE_DIR = os.path.join(qdir, 'bad')
+ self.RETRYQUEUE_DIR = os.path.join(qdir, 'retry')
+ self.MAILDIR_DIR = os.path.join(qdir, 'maildir')
+ # Other useful files
+ self.PIDFILE = os.path.join(datadir,
+ 'master-qrunner.pid')
+ self.SITE_PW_FILE = os.path.join(datadir, 'adm.pw')
+ self.LISTCREATOR_PW_FILE = os.path.join(datadir, 'creator.pw')
+ 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
+ self.__dict__.update(ns)
+
+
+
+config = Configuration()
+
diff --git a/Mailman/i18n.py b/Mailman/i18n.py
index 7d36e22d3..69f9801d1 100644
--- a/Mailman/i18n.py
+++ b/Mailman/i18n.py
@@ -20,8 +20,8 @@ import time
import string
import gettext
-from Mailman import mm_cfg
from Mailman.SafeDict import SafeDict
+from Mailman.configuration import config
_translation = None
_missing = object()
@@ -47,7 +47,7 @@ def set_language(language=None):
if language is not None:
language = [language]
try:
- _translation = gettext.translation('mailman', mm_cfg.MESSAGES_DIR,
+ _translation = gettext.translation('mailman', config.MESSAGES_DIR,
language)
except IOError:
# The selected language was not installed in messages, so fall back to
@@ -67,7 +67,7 @@ def set_translation(translation):
# Set up the global translation based on environment variables. Mostly used
# for command line scripts.
if _translation is None:
- set_language()
+ _translation = gettext.NullTranslations()
diff --git a/Mailman/loginit.py b/Mailman/loginit.py
index bcb016f08..fac01c0fb 100644
--- a/Mailman/loginit.py
+++ b/Mailman/loginit.py
@@ -24,7 +24,7 @@ import os
import codecs
import logging
-from Mailman import mm_cfg
+from Mailman.configuration import config
FMT = '%(asctime)s (%(process)d) %(message)s'
DATEFMT = '%b %d %H:%M:%S %Y'
@@ -116,7 +116,7 @@ def initialize(propagate=False):
# Propagation to the root logger is how we handle logging to stderr
# when the qrunners are not run as a subprocess of mailmanctl.
log.propagate = propagate
- handler = ReopenableFileHandler(os.path.join(mm_cfg.LOG_DIR, logger))
+ handler = ReopenableFileHandler(os.path.join(config.LOG_DIR, logger))
_handlers.append(handler)
handler.setFormatter(formatter)
log.addHandler(handler)
diff --git a/Mailman/testing/emailbase.py b/Mailman/testing/emailbase.py
index 115ea6839..6c290c8e5 100644
--- a/Mailman/testing/emailbase.py
+++ b/Mailman/testing/emailbase.py
@@ -17,14 +17,18 @@
"""Base class for tests that email things."""
+import os
import smtpd
import socket
import asyncore
+import tempfile
import subprocess
from Mailman import mm_cfg
from Mailman.testing.base import TestBase
+TESTPORT = 10825
+
MSGTEXT = None
@@ -49,15 +53,23 @@ class SinkServer(smtpd.SMTPServer):
class EmailBase(TestBase):
def setUp(self):
TestBase.setUp(self)
- # Find an unused non-root requiring port to listen on
- oldport = mm_cfg.SMTPPORT
- mm_cfg.SMTPPORT = port = 10825
+ # Find an unused non-root requiring port to listen on. Set up a
+ # configuration file that causes the underlying outgoing runner to use
+ # the same port, then start Mailman.
+ fd, self._configfile = tempfile.mkstemp(suffix='.cfg')
+ print 'config file:', self._configfile
+ fp = os.fdopen(fd, 'w')
+ print >> fp, 'SMTPPORT =', TESTPORT
+ fp.close()
# Second argument is ignored.
- self._server = SinkServer(('localhost', port), None)
+ self._server = SinkServer(('localhost', TESTPORT), None)
+ os.system('bin/mailmanctl start')
def tearDown(self):
+ os.system('bin/mailmanctl stop')
self._server.close()
TestBase.tearDown(self)
+ os.remove(self._configfile)
def _readmsg(self):
global MSGTEXT
diff --git a/Makefile.in b/Makefile.in
index aa49d9310..9c5fe7807 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -48,7 +48,7 @@ VAR_DIRS= \
archives/private archives/public
ARCH_INDEP_DIRS= \
- bin templates scripts cron pythonlib \
+ bin etc templates scripts cron pythonlib \
Mailman Mailman/bin Mailman/Cgi Mailman/Archiver \
Mailman/Handlers Mailman/Queue Mailman/Bouncers \
Mailman/MTA Mailman/Gui Mailman/Commands messages icons \
diff --git a/bin/Makefile.in b/bin/Makefile.in
index 40fba3ed4..37084d1f7 100644
--- a/bin/Makefile.in
+++ b/bin/Makefile.in
@@ -45,17 +45,18 @@ SCRIPTSDIR= $(prefix)/bin
SHELL= /bin/sh
SCRIPTS= mmshell \
- remove_members clone_member update \
+ remove_members clone_member \
sync_members check_db withlist check_perms \
config_list dumpdb cleanarch \
- list_admins genaliases mailmanctl qrunner \
+ list_admins genaliases \
fix_url.py convert.py transcheck \
msgfmt.py discard \
reset_pw.py templ2pot.py po2templ.py
LN_SCRIPTS= add_members arch change_pw find_member inject list_lists \
- list_members list_owners mmsitepass newlist rmlist \
- show_qfiles show_mm_cfg testall unshunt version
+ list_members list_owners mailmanctl mmsitepass newlist \
+ qrunner rmlist show_qfiles show_mm_cfg testall unshunt \
+ update version
BUILDDIR= ../build/bin
diff --git a/bin/mailmanctl b/bin/mailmanctl
index 36c3ab545..e69de29bb 100644
--- a/bin/mailmanctl
+++ b/bin/mailmanctl
@@ -1,545 +0,0 @@
-#! @PYTHON@
-
-# Copyright (C) 2001-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.
-
-"""Primary start-up and shutdown script for Mailman's qrunner daemon.
-
-This script starts, stops, and restarts the main Mailman queue runners, making
-sure that the various long-running qrunners are still alive and kicking. It
-does this by forking and exec'ing the qrunners and waiting on their pids.
-When it detects a subprocess has exited, it may restart it.
-
-The qrunners respond to SIGINT, SIGTERM, and SIGHUP. SIGINT and SIGTERM both
-cause the qrunners to exit cleanly, but the master will only restart qrunners
-that have exited due to a SIGINT. SIGHUP causes the master and the qrunners
-to close their log files, and reopen then upon the next printed message.
-
-The master also responds to SIGINT, SIGTERM, and SIGHUP, which it simply
-passes on to the qrunners (note that the master will close and reopen its own
-log files on receipt of a SIGHUP). The master also leaves its own process id
-in the file data/master-qrunner.pid but you normally don't need to use this
-pid directly. The `start', `stop', `restart', and `reopen' commands handle
-everything for you.
-
-Usage: %(PROGRAM)s [options] [ start | stop | restart | reopen ]
-
-Options:
-
- -n/--no-restart
- Don't restart the qrunners when they exit because of an error or a
- SIGINT. They are never restarted if they exit in response to a
- SIGTERM. Use this only for debugging. Only useful if the `start'
- command is given.
-
- -u/--run-as-user
- Normally, this script will refuse to run if the user id and group id
- are not set to the `mailman' user and group (as defined when you
- configured Mailman). If run as root, this script will change to this
- user and group before the check is made.
-
- This can be inconvenient for testing and debugging purposes, so the -u
- flag means that the step that sets and checks the uid/gid is skipped,
- and the program is run as the current user and group. This flag is
- not recommended for normal production environments.
-
- Note though, that if you run with -u and are not in the mailman group,
- you may have permission problems, such as begin unable to delete a
- list's archives through the web. Tough luck!
-
- -s/--stale-lock-cleanup
- If mailmanctl finds an existing master lock, it will normally exit
- with an error message. With this option, mailmanctl will perform an
- extra level of checking. If a process matching the host/pid described
- in the lock file is running, mailmanctl will still exit, but if no
- matching process is found, mailmanctl will remove the apparently stale
- lock and make another attempt to claim the master lock.
-
- -q/--quiet
- Don't print status messages. Error messages are still printed to
- standard error.
-
- -h/--help
- Print this message and exit.
-
-Commands:
-
- start - Start the master daemon and all qrunners. Prints a message and
- exits if the master daemon is already running.
-
- stop - Stops the master daemon and all qrunners. After stopping, no
- more messages will be processed.
-
- restart - Restarts the qrunners, but not the master process. Use this
- whenever you upgrade or update Mailman so that the qrunners will
- use the newly installed code.
-
- reopen - This will close all log files, causing them to be re-opened the
- next time a message is written to them
-"""
-
-import os
-import grp
-import pwd
-import sys
-import errno
-import getopt
-import signal
-import socket
-import logging
-
-import paths
-from Mailman import Errors
-from Mailman import LockFile
-from Mailman import Utils
-from Mailman import loginit
-from Mailman import mm_cfg
-from Mailman.MailList import MailList
-from Mailman.i18n import _
-
-PROGRAM = sys.argv[0]
-COMMASPACE = ', '
-DOT = '.'
-
-# Locking contantsa
-LOCKFILE = os.path.join(mm_cfg.LOCK_DIR, 'master-qrunner')
-# Since we wake up once per day and refresh the lock, the LOCK_LIFETIME
-# needn't be (much) longer than SNOOZE. We pad it 6 hours just to be safe.
-LOCK_LIFETIME = mm_cfg.days(1) + mm_cfg.hours(6)
-SNOOZE = mm_cfg.days(1)
-MAX_RESTARTS = 10
-
-loginit.initialize()
-elog = logging.getLogger('mailman.error')
-qlog = logging.getLogger('mailman.qrunner')
-
-
-
-def usage(code, msg=''):
- if code:
- fd = sys.stderr
- else:
- fd = sys.stdout
- print >> fd, _(__doc__)
- if msg:
- print >> fd, msg
- sys.exit(code)
-
-
-
-def kill_watcher(sig):
- try:
- fp = open(mm_cfg.PIDFILE)
- pidstr = fp.read()
- fp.close()
- pid = int(pidstr.strip())
- except (IOError, ValueError), e:
- # For i18n convenience
- pidfile = mm_cfg.PIDFILE
- print >> sys.stderr, _('PID unreadable in: %(pidfile)s')
- print >> sys.stderr, e
- print >> sys.stderr, _('Is qrunner even running?')
- return
- try:
- os.kill(pid, sig)
- except OSError, e:
- if e.errno <> errno.ESRCH: raise
- print >> sys.stderr, _('No child with pid: %(pid)s')
- print >> sys.stderr, e
- print >> sys.stderr, _('Stale pid file removed.')
- os.unlink(mm_cfg.PIDFILE)
-
-
-
-def get_lock_data():
- # Return the hostname, pid, and tempfile
- fp = open(LOCKFILE)
- try:
- filename = os.path.split(fp.read().strip())[1]
- finally:
- fp.close()
- parts = filename.split('.')
- hostname = DOT.join(parts[1:-1])
- pid = int(parts[-1])
- return hostname, int(pid), filename
-
-
-def qrunner_state():
- # 1 if proc exists on host (but is it qrunner? ;)
- # 0 if host matches but no proc
- # hostname if hostname doesn't match
- hostname, pid, tempfile = get_lock_data()
- if hostname <> socket.gethostname():
- return hostname
- # Find out if the process exists by calling kill with a signal 0.
- try:
- os.kill(pid, 0)
- except OSError, e:
- if e.errno <> errno.ESRCH:
- raise
- return 0
- return 1
-
-
-def acquire_lock_1(force):
- # Be sure we can acquire the master qrunner lock. If not, it means some
- # other master qrunner daemon is already going.
- lock = LockFile.LockFile(LOCKFILE, LOCK_LIFETIME)
- try:
- lock.lock(0.1)
- return lock
- except LockFile.TimeOutError:
- if not force:
- raise
- # Force removal of lock first
- lock._disown()
- hostname, pid, tempfile = get_lock_data()
- os.unlink(LOCKFILE)
- os.unlink(os.path.join(mm_cfg.LOCK_DIR, tempfile))
- return acquire_lock_1(force=0)
-
-
-def acquire_lock(force):
- try:
- lock = acquire_lock_1(force)
- return lock
- except LockFile.TimeOutError:
- status = qrunner_state()
- if status == 1:
- # host matches and proc exists
- print >> sys.stderr, _("""\
-The master qrunner lock could not be acquired because it appears as if another
-master qrunner is already running.
-""")
- elif status == 0:
- # host matches but no proc
- print >> sys.stderr, _("""\
-The master qrunner lock could not be acquired. It appears as though there is
-a stale master qrunner lock. Try re-running mailmanctl with the -s flag.
-""")
- else:
- # host doesn't even match
- print >> sys.stderr, _("""\
-The master qrunner lock could not be acquired, because it appears as if some
-process on some other host may have acquired it. We can't test for stale
-locks across host boundaries, so you'll have to do this manually. Or, if you
-know the lock is stale, re-run mailmanctl with the -s flag.
-
-Lock file: %(LOCKFILE)s
-Lock host: %(status)s
-
-Exiting.""")
-
-
-
-def start_runner(qrname, slice, count):
- pid = os.fork()
- if pid:
- # parent
- return pid
- # child
- #
- # Craft the command line arguments for the exec() call.
- rswitch = '--runner=%s:%d:%d' % (qrname, slice, count)
- exe = os.path.join(mm_cfg.BIN_DIR, 'qrunner')
- # mm_cfg.PYTHON, which is the absolute path to the Python interpreter,
- # must be given as argv[0] due to Python's library search algorithm.
- os.execl(mm_cfg.PYTHON, mm_cfg.PYTHON, exe, rswitch, '-s')
- # Should never get here
- raise RuntimeError, 'os.execl() failed'
-
-
-def start_all_runners():
- kids = {}
- for qrname, count in mm_cfg.QRUNNERS:
- for slice in range(count):
- # queue runner name, slice, numslices, restart count
- info = (qrname, slice, count, 0)
- pid = start_runner(qrname, slice, count)
- kids[pid] = info
- return kids
-
-
-
-def check_for_site_list():
- sitelistname = mm_cfg.MAILMAN_SITE_LIST
- try:
- sitelist = MailList(sitelistname, lock=False)
- except Errors.MMUnknownListError:
- print >> sys.stderr, _('Site list is missing: %(sitelistname)s')
- elog.error('Site list is missing: %s', mm_cfg.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
- # the uid/gid.
- gid = grp.getgrnam(mm_cfg.MAILMAN_GROUP)[2]
- uid = pwd.getpwnam(mm_cfg.MAILMAN_USER)[2]
- myuid = os.getuid()
- if myuid == 0:
- # Set the process's supplimental groups.
- groups = [x[2] for x in grp.getgrall() if mm_cfg.MAILMAN_USER in x[3]]
- groups.append(gid)
- os.setgroups(groups)
- os.setgid(gid)
- os.setuid(uid)
- elif myuid <> uid:
- name = mm_cfg.MAILMAN_USER
- usage(1, _(
- 'Run this program as root or as the %(name)s user, or use -u.'))
-
-
-
-def main():
- global quiet
- try:
- opts, args = getopt.getopt(sys.argv[1:], 'hnusq',
- ['help', 'no-start', 'run-as-user',
- 'stale-lock-cleanup', 'quiet'])
- except getopt.error, msg:
- usage(1, msg)
-
- restart = 1
- checkprivs = 1
- force = 0
- quiet = 0
- for opt, arg in opts:
- if opt in ('-h', '--help'):
- usage(0)
- elif opt in ('-n', '--no-restart'):
- restart = 0
- elif opt in ('-u', '--run-as-user'):
- checkprivs = 0
- elif opt in ('-s', '--stale-lock-cleanup'):
- force = 1
- elif opt in ('-q', '--quiet'):
- quiet = 1
-
- if len(args) < 1:
- usage(1, _('No command given.'))
- elif len(args) > 1:
- command = COMMASPACE.join(args)
- usage(1, _('Bad command: %(command)s'))
-
- if checkprivs:
- check_privs()
- else:
- print _('Warning! You may encounter permission problems.')
-
- # Handle the commands
- command = args[0].lower()
- if command == 'stop':
- # Sent the master qrunner process a SIGINT, which is equivalent to
- # giving cron/qrunner a ctrl-c or KeyboardInterrupt. This will
- # effectively shut everything down.
- if not quiet:
- print _("Shutting down Mailman's master qrunner")
- kill_watcher(signal.SIGTERM)
- elif command == 'restart':
- # Sent the master qrunner process a SIGHUP. This will cause the
- # master qrunner to kill and restart all the worker qrunners, and to
- # close and re-open its log files.
- if not quiet:
- print _("Restarting Mailman's master qrunner")
- kill_watcher(signal.SIGINT)
- elif command == 'reopen':
- if not quiet:
- 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.
- #
- # The child watcher process simply waits on the pids of the children
- # qrunners. Unless explicitly disabled by a mailmanctl switch (or the
- # children are killed with SIGTERM instead of SIGINT), the watcher
- # will automatically restart any child process that exits. This
- # allows us to be more robust, and also to implement restart by simply
- # SIGINT'ing the qrunner children, and letting the watcher restart
- # them.
- #
- # Under normal operation, we have a child per queue. This lets us get
- # the most out of the available resources, since a qrunner with no
- # files in its queue directory is pretty cheap, but having a separate
- # runner process per queue allows for a very responsive system. Some
- # people want a more traditional (i.e. MM2.0.x) cron-invoked qrunner.
- # No problem, but using mailmanctl isn't the answer. So while
- # mailmanctl hard codes some things, others, such as the number of
- # qrunners per queue, is configurable in mm_cfg.py.
- #
- # First, acquire the master mailmanctl lock
- lock = acquire_lock(force)
- if not lock:
- return
- # Daemon process startup according to Stevens, Advanced Programming in
- # the UNIX Environment, Chapter 13.
- pid = os.fork()
- if pid:
- # parent
- if not quiet:
- print _("Starting Mailman's master qrunner.")
- # Give up the lock "ownership". This just means the foreground
- # process won't close/unlock the lock when it finalizes this lock
- # instance. We'll let the mater watcher subproc own the lock.
- lock._transfer_to(pid)
- return
- # child
- lock._take_possession()
- # First, save our pid in a file for "mailmanctl stop" rendezvous. We
- # want the perms on the .pid file to be rw-rw----
- omask = os.umask(6)
- try:
- fp = open(mm_cfg.PIDFILE, 'w')
- print >> fp, os.getpid()
- fp.close()
- finally:
- os.umask(omask)
- # Create a new session and become the session leader, but since we
- # won't be opening any terminal devices, don't do the ultra-paranoid
- # suggestion of doing a second fork after the setsid() call.
- os.setsid()
- # Instead of cd'ing to root, cd to the Mailman installation home
- os.chdir(mm_cfg.PREFIX)
- # Set our file mode creation umask
- os.umask(007)
- # I don't think we have any unneeded file descriptors.
- #
- # Now start all the qrunners. This returns a dictionary where the
- # keys are qrunner pids and the values are tuples of the following
- # form: (qrname, slice, count). This does its own fork and exec, and
- # sets up its own signal handlers.
- kids = start_all_runners()
- # Set up a SIGALRM handler to refresh the lock once per day. The lock
- # lifetime is 1day+6hours so this should be plenty.
- def sigalrm_handler(signum, frame, lock=lock):
- lock.refresh()
- signal.alarm(mm_cfg.days(1))
- signal.signal(signal.SIGALRM, sigalrm_handler)
- signal.alarm(mm_cfg.days(1))
- # Set up a SIGHUP handler so that if we get one, we'll pass it along
- # to all the qrunner children. This will tell them to close and
- # reopen their log files
- def sighup_handler(signum, frame, kids=kids):
- loginit.reopen()
- for pid in kids.keys():
- os.kill(pid, signal.SIGHUP)
- # And just to tweak things...
- qlog.info('Master watcher caught SIGHUP. Re-opening log files.')
- signal.signal(signal.SIGHUP, sighup_handler)
- # We also need to install a SIGTERM handler because that's what init
- # will kill this process with when changing run levels.
- def sigterm_handler(signum, frame, kids=kids):
- for pid in kids.keys():
- try:
- os.kill(pid, signal.SIGTERM)
- except OSError, e:
- if e.errno <> errno.ESRCH: raise
- qlog.info('Master watcher caught SIGTERM. Exiting.')
- signal.signal(signal.SIGTERM, sigterm_handler)
- # Finally, we need a SIGINT handler which will cause the sub-qrunners
- # to exit, but the master will restart SIGINT'd sub-processes unless
- # the -n flag was given.
- def sigint_handler(signum, frame, kids=kids):
- for pid in kids.keys():
- os.kill(pid, signal.SIGINT)
- qlog.info('Master watcher caught SIGINT. Restarting.')
- signal.signal(signal.SIGINT, sigint_handler)
- # Now we're ready to simply do our wait/restart loop. This is the
- # master qrunner watcher.
- try:
- while True:
- try:
- pid, status = os.wait()
- except OSError, e:
- # No children? We're done
- if e.errno == errno.ECHILD:
- break
- # If the system call got interrupted, just restart it.
- elif e.errno <> errno.EINTR:
- raise
- continue
- killsig = exitstatus = None
- if os.WIFSIGNALED(status):
- killsig = os.WTERMSIG(status)
- if os.WIFEXITED(status):
- exitstatus = os.WEXITSTATUS(status)
- # We'll restart the process unless we were given the
- # "no-restart" switch, or if the process was SIGTERM'd or
- # exitted with a SIGTERM exit status. This lets us better
- # handle runaway restarts (say, if the subproc had a syntax
- # error!)
- restarting = ''
- if restart:
- if (exitstatus == None and killsig <> signal.SIGTERM) or \
- (killsig == None and exitstatus <> signal.SIGTERM):
- # Then
- restarting = '[restarting]'
- qrname, slice, count, restarts = kids[pid]
- del kids[pid]
- qlog.info("""\
-Master qrunner detected subprocess exit
-(pid: %d, sig: %s, sts: %s, class: %s, slice: %d/%d) %s""",
- pid, killsig, exitstatus, qrname,
- slice+1, count, restarting)
- # See if we've reached the maximum number of allowable restarts
- if exitstatus <> signal.SIGINT:
- restarts += 1
- if restarts > MAX_RESTARTS:
- qlog.info("""\
-Qrunner %s reached maximum restart limit of %d, not restarting.""",
- qrname, MAX_RESTARTS)
- restarting = ''
- # Now perhaps restart the process unless it exited with a
- # SIGTERM or we aren't restarting.
- if restarting:
- newpid = start_runner(qrname, slice, count)
- kids[newpid] = (qrname, slice, count, restarts)
- finally:
- # Should we leave the main loop for any reason, we want to be sure
- # all of our children are exited cleanly. Send SIGTERMs to all
- # the child processes and wait for them all to exit.
- for pid in kids.keys():
- try:
- os.kill(pid, signal.SIGTERM)
- except OSError, e:
- if e.errno == errno.ESRCH:
- # The child has already exited
- qlog.info('ESRCH on pid: %d', pid)
- del kids[pid]
- # Wait for all the children to go away
- while True:
- try:
- pid, status = os.wait()
- except OSError, e:
- if e.errno == errno.ECHILD:
- break
- elif e.errno <> errno.EINTR:
- raise
- continue
- # Finally, give up the lock
- lock.unlock(unconditionally=True)
- os._exit(0)
-
-
-
-if __name__ == '__main__':
- main()
diff --git a/bin/qrunner b/bin/qrunner
index 42248e472..e69de29bb 100644
--- a/bin/qrunner
+++ b/bin/qrunner
@@ -1,278 +0,0 @@
-#! @PYTHON@
-
-# Copyright (C) 2001-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.
-
-"""Run one or more qrunners, once or repeatedly.
-
-Each named runner class is run in round-robin fashion. In other words, the
-first named runner is run to consume all the files currently in its
-directory. When that qrunner is done, the next one is run to consume all the
-files in /its/ directory, and so on. The number of total iterations can be
-given on the command line.
-
-Usage: %(PROGRAM)s [options]
-
-Options:
-
- -r runner[:slice:range]
- --runner=runner[:slice:range]
- Run the named qrunner, which must be one of the strings returned by
- the -l option. Optional slice:range if given, is used to assign
- multiple qrunner processes to a queue. range is the total number of
- qrunners for this queue while slice is the number of this qrunner from
- [0..range).
-
- If using the slice:range form, you better make sure that each qrunner
- for the queue is given the same range value. If slice:runner is not
- given, then 1:1 is used.
-
- Multiple -r options may be given, in which case each qrunner will run
- once in round-robin fashion. The special runner `All' is shorthand
- for a qrunner for each listed by the -l option.
-
- --once
- -o
- Run each named qrunner exactly once through its main loop. Otherwise,
- each qrunner runs indefinitely, until the process receives a SIGTERM
- or SIGINT.
-
- -l/--list
- Shows the available qrunner names and exit.
-
- -v/--verbose
- Spit out more debugging information to the logs/qrunner log file.
-
- -s/--subproc
- This should only be used when running qrunner as a subprocess of the
- mailmanctl startup script. It changes some of the exit-on-error
- behavior to work better with that framework.
-
- -h/--help
- Print this message and exit.
-
-runner is required unless -l or -h is given, and it must be one of the names
-displayed by the -l switch.
-
-Note also that this script should be started up from mailmanctl as a normal
-operation. It is only useful for debugging if it is run separately.
-"""
-
-import sys
-import getopt
-import signal
-import logging
-
-import paths
-from Mailman import mm_cfg
-from Mailman import loginit
-from Mailman.i18n import _
-
-PROGRAM = sys.argv[0]
-COMMASPACE = ', '
-
-# Flag which says whether we're running under mailmanctl or not.
-AS_SUBPROC = 0
-
-log = logging.getLogger('mailman.qrunner')
-
-
-
-def usage(code, msg=''):
- if code:
- fd = sys.stderr
- else:
- fd = sys.stdout
- print >> fd, _(__doc__)
- if msg:
- print >> fd, msg
- sys.exit(code)
-
-
-
-def make_qrunner(name, slice, range, once=0):
- modulename = 'Mailman.Queue.' + name
- try:
- __import__(modulename)
- except ImportError, e:
- if AS_SUBPROC:
- # Exit with SIGTERM exit code so mailmanctl won't try to restart us
- print >> sys.stderr, 'Cannot import runner module', modulename
- print >> sys.stderr, e
- sys.exit(signal.SIGTERM)
- else:
- usage(1, e)
- qrclass = getattr(sys.modules[modulename], name)
- if once:
- # Subclass to hack in the setting of the stop flag in _doperiodic()
- class Once(qrclass):
- def _doperiodic(self):
- self.stop()
- qrunner = Once(slice, range)
- else:
- qrunner = qrclass(slice, range)
- return qrunner
-
-
-
-def set_signals(loop):
- # Set up the SIGTERM handler for stopping the loop
- def sigterm_handler(signum, frame, loop=loop):
- # Exit the qrunner cleanly
- loop.stop()
- loop.status = signal.SIGTERM
- log.info('%s qrunner caught SIGTERM. Stopping.', loop.name())
- signal.signal(signal.SIGTERM, sigterm_handler)
- # Set up the SIGINT handler for stopping the loop. For us, SIGINT is
- # the same as SIGTERM, but our parent treats the exit statuses
- # differently (it restarts a SIGINT but not a SIGTERM).
- def sigint_handler(signum, frame, loop=loop):
- # Exit the qrunner cleanly
- loop.stop()
- loop.status = signal.SIGINT
- log.info('%s qrunner caught SIGINT. Stopping.', loop.name())
- signal.signal(signal.SIGINT, sigint_handler)
- # SIGHUP just tells us to rotate our log files.
- def sighup_handler(signum, frame, loop=loop):
- loginit.reopen()
- log.info('%s qrunner caught SIGHUP. Reopening logs.', loop.name())
- signal.signal(signal.SIGHUP, sighup_handler)
-
-
-
-def main():
- global AS_SUBPROC
- try:
- opts, args = getopt.getopt(
- sys.argv[1:], 'hlor:vs',
- ['help', 'list', 'once', 'runner=', 'verbose', 'subproc'])
- except getopt.error, msg:
- usage(1, msg)
-
- once = 0
- runners = []
- verbose = 0
- for opt, arg in opts:
- if opt in ('-h', '--help'):
- usage(0)
- elif opt in ('-l', '--list'):
- for runnername, slices in mm_cfg.QRUNNERS:
- if runnername.endswith('Runner'):
- name = runnername[:-len('Runner')]
- else:
- name = runnername
- print _('%(name)s runs the %(runnername)s qrunner')
- print _('All runs all the above qrunners')
- sys.exit(0)
- elif opt in ('-o', '--once'):
- once = 1
- elif opt in ('-r', '--runner'):
- runnerspec = arg
- parts = runnerspec.split(':')
- if len(parts) == 1:
- runner = parts[0]
- slice = 1
- range = 1
- elif len(parts) == 3:
- runner = parts[0]
- try:
- slice = int(parts[1])
- range = int(parts[2])
- except ValueError:
- usage(1, 'Bad runner specification: %(runnerspec)s')
- else:
- usage(1, 'Bad runner specification: %(runnerspec)s')
- if runner == 'All':
- for runnername, slices in mm_cfg.QRUNNERS:
- runners.append((runnername, slice, range))
- else:
- if runner.endswith('Runner'):
- runners.append((runner, slice, range))
- else:
- runners.append((runner + 'Runner', slice, range))
- elif opt in ('-s', '--subproc'):
- AS_SUBPROC = 1
- elif opt in ('-v', '--verbose'):
- verbose = 1
-
- if len(args) <> 0:
- usage(1)
- if len(runners) == 0:
- usage(1, _('No runner name given.'))
-
- # If we're not running as a subprocess of mailmanctl, then we'll log to
- # stderr in addition to logging to the log files. We do this by passing a
- # value of True to propagate, which allows the 'mailman' root logger to
- # see the log messages.
- loginit.initialize(propagate=not AS_SUBPROC)
-
- # Fast track for one infinite runner
- if len(runners) == 1 and not once:
- qrunner = make_qrunner(*runners[0])
- class Loop:
- status = 0
- def __init__(self, qrunner):
- self.__qrunner = qrunner
- def name(self):
- return self.__qrunner.__class__.__name__
- def stop(self):
- self.__qrunner.stop()
- loop = Loop(qrunner)
- set_signals(loop)
- # Now start up the main loop
- log.info('%s qrunner started.', loop.name())
- qrunner.run()
- log.info('%s qrunner exiting.', loop.name())
- else:
- # Anything else we have to handle a bit more specially
- qrunners = []
- for runner, slice, range in runners:
- qrunner = make_qrunner(runner, slice, range, 1)
- qrunners.append(qrunner)
- # This class is used to manage the main loop
- class Loop:
- status = 0
- def __init__(self):
- self.__isdone = 0
- def name(self):
- return 'Main loop'
- def stop(self):
- self.__isdone = 1
- def isdone(self):
- return self.__isdone
- loop = Loop()
- set_signals(loop)
- log.info('Main qrunner loop started.')
- while not loop.isdone():
- for qrunner in qrunners:
- # In case the SIGTERM came in the middle of this iteration
- if loop.isdone():
- break
- if verbose:
- log.info('Now doing a %s qrunner iteration',
- qrunner.__class__.__bases__[0].__name__)
- qrunner.run()
- if once:
- break
- log.info('Main qrunner loop exiting.')
- # All done
- sys.exit(loop.status)
-
-
-
-if __name__ == '__main__':
- main()
diff --git a/bin/update b/bin/update
index 23429508e..e69de29bb 100755
--- a/bin/update
+++ b/bin/update
@@ -1,807 +0,0 @@
-#! @PYTHON@
-#
-# Copyright (C) 1998-2005 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.
-
-"""Perform all necessary upgrades.
-
-Usage: %(PROGRAM)s [options]
-
-Options:
- -f/--force
- Force running the upgrade procedures. Normally, if the version number
- of the installed Mailman matches the current version number (or a
- `downgrade' is detected), nothing will be done.
-
- -h/--help
- Print this text and exit.
-
-Use this script to help you update to the latest release of Mailman from
-some previous version. It knows about versions back to 1.0b4 (?).
-"""
-
-import os
-import md5
-import sys
-import time
-import errno
-import getopt
-import shutil
-import cPickle
-import marshal
-
-import paths
-import email
-
-from Mailman import mm_cfg
-from Mailman import Utils
-from Mailman import MailList
-from Mailman import Message
-from Mailman import Pending
-from Mailman.LockFile import TimeOutError
-from Mailman.i18n import _
-from Mailman.Queue.Switchboard import Switchboard
-from Mailman.OldStyleMemberships import OldStyleMemberships
-from Mailman.MemberAdaptor import BYBOUNCE, ENABLED
-
-FRESH = 0
-NOTFRESH = -1
-
-LMVFILE = os.path.join(mm_cfg.DATA_DIR, 'last_mailman_version')
-PROGRAM = sys.argv[0]
-
-
-
-def calcversions():
- # Returns a tuple of (lastversion, thisversion). If the last version
- # could not be determined, lastversion will be FRESH or NOTFRESH,
- # depending on whether this installation appears to be fresh or not. The
- # determining factor is whether there are files in the $var_prefix/logs
- # subdir or not. The version numbers are HEX_VERSIONs.
- #
- # See if we stored the last updated version
- lastversion = None
- thisversion = mm_cfg.HEX_VERSION
- try:
- fp = open(LMVFILE)
- data = fp.read()
- fp.close()
- lastversion = int(data, 16)
- except (IOError, ValueError):
- pass
- #
- # try to figure out if this is a fresh install
- if lastversion is None:
- lastversion = FRESH
- try:
- if os.listdir(mm_cfg.LOG_DIR):
- lastversion = NOTFRESH
- except OSError:
- pass
- return (lastversion, thisversion)
-
-
-
-def makeabs(relpath):
- return os.path.join(mm_cfg.PREFIX, relpath)
-
-def make_varabs(relpath):
- return os.path.join(mm_cfg.VAR_PREFIX, relpath)
-
-
-def move_language_templates(mlist):
- listname = mlist.internal_name()
- print _('Fixing language templates: %(listname)s')
- # Mailman 2.1 has a new cascading search for its templates, defined and
- # described in Utils.py:maketext(). Putting templates in the top level
- # templates/ subdir or the lists/<listname> subdir is deprecated and no
- # longer searched..
- #
- # What this means is that most templates can live in the global templates/
- # subdirectory, and only needs to be copied into the list-, vhost-, or
- # site-specific language directories when needed.
- #
- # Also, by default all standard (i.e. English) templates must now live in
- # the templates/en directory. This update cleans up all the templates,
- # deleting more-specific duplicates (as calculated by md5 checksums) in
- # favor of more-global locations.
- #
- # First, get rid of any lists/<list> template or lists/<list>/en template
- # that is identical to the global templates/* default.
- for gtemplate in os.listdir(os.path.join(mm_cfg.TEMPLATE_DIR, 'en')):
- # BAW: get rid of old templates, e.g. admlogin.txt and
- # handle_opts.html
- try:
- fp = open(os.path.join(mm_cfg.TEMPLATE_DIR, gtemplate))
- except IOError, e:
- if e.errno <> errno.ENOENT: raise
- # No global template
- continue
-
- gcksum = md5.new(fp.read()).digest()
- fp.close()
- # Match against the lists/<list>/* template
- try:
- fp = open(os.path.join(mlist.fullpath(), gtemplate))
- except IOError, e:
- if e.errno <> errno.ENOENT: raise
- else:
- tcksum = md5.new(fp.read()).digest()
- fp.close()
- if gcksum == tcksum:
- os.unlink(os.path.join(mlist.fullpath(), gtemplate))
- # Match against the lists/<list>/*.prev template
- try:
- fp = open(os.path.join(mlist.fullpath(), gtemplate + '.prev'))
- except IOError, e:
- if e.errno <> errno.ENOENT: raise
- else:
- tcksum = md5.new(fp.read()).digest()
- fp.close()
- if gcksum == tcksum:
- os.unlink(os.path.join(mlist.fullpath(), gtemplate + '.prev'))
- # Match against the lists/<list>/en/* templates
- try:
- fp = open(os.path.join(mlist.fullpath(), 'en', gtemplate))
- except IOError, e:
- if e.errno <> errno.ENOENT: raise
- else:
- tcksum = md5.new(fp.read()).digest()
- fp.close()
- if gcksum == tcksum:
- os.unlink(os.path.join(mlist.fullpath(), 'en', gtemplate))
- # Match against the templates/* template
- try:
- fp = open(os.path.join(mm_cfg.TEMPLATE_DIR, gtemplate))
- except IOError, e:
- if e.errno <> errno.ENOENT: raise
- else:
- tcksum = md5.new(fp.read()).digest()
- fp.close()
- if gcksum == tcksum:
- os.unlink(os.path.join(mm_cfg.TEMPLATE_DIR, gtemplate))
- # Match against the templates/*.prev template
- try:
- fp = open(os.path.join(mm_cfg.TEMPLATE_DIR, gtemplate + '.prev'))
- except IOError, e:
- if e.errno <> errno.ENOENT: raise
- else:
- tcksum = md5.new(fp.read()).digest()
- fp.close()
- if gcksum == tcksum:
- os.unlink(os.path.join(mm_cfg.TEMPLATE_DIR,
- gtemplate + '.prev'))
-
-
-
-def dolist(listname):
- errors = 0
- mlist = MailList.MailList(listname, lock=0)
- try:
- mlist.Lock(0.5)
- except TimeOutError:
- print >> sys.stderr, _('WARNING: could not acquire lock for list: '
- '%(listname)s')
- return 1
-
- # Sanity check the invariant that every BYBOUNCE disabled member must have
- # bounce information. Some earlier betas broke this. BAW: we're
- # submerging below the MemberAdaptor interface, so skip this if we're not
- # using OldStyleMemberships.
- if isinstance(mlist._memberadaptor, OldStyleMemberships):
- noinfo = {}
- for addr, (reason, when) in mlist.delivery_status.items():
- if reason == BYBOUNCE and not mlist.bounce_info.has_key(addr):
- noinfo[addr] = reason, when
- # What to do about these folks with a BYBOUNCE delivery status and no
- # bounce info? This number should be very small, and I think it's
- # fine to simple re-enable them and let the bounce machinery
- # re-disable them if necessary.
- n = len(noinfo)
- if n > 0:
- print _(
- 'Resetting %(n)s BYBOUNCEs disabled addrs with no bounce info')
- for addr in noinfo.keys():
- mlist.setDeliveryStatus(addr, ENABLED)
-
- # Update the held requests database
- print _("""Updating the held requests database.""")
- mlist._UpdateRecords()
-
- mbox_dir = make_varabs('archives/private/%s.mbox' % (listname))
- mbox_file = make_varabs('archives/private/%s.mbox/%s' % (listname,
- listname))
-
- o_pub_mbox_file = make_varabs('archives/public/%s' % (listname))
- o_pri_mbox_file = make_varabs('archives/private/%s' % (listname))
-
- html_dir = o_pri_mbox_file
- o_html_dir = makeabs('public_html/archives/%s' % (listname))
- #
- # make the mbox directory if it's not there.
- #
- if not os.path.exists(mbox_dir):
- ou = os.umask(0)
- os.mkdir(mbox_dir, 02775)
- os.umask(ou)
- else:
- # this shouldn't happen, but hey, just in case
- if not os.path.isdir(mbox_dir):
- print _("""\
-For some reason, %(mbox_dir)s exists as a file. This won't work with
-b6, so I'm renaming it to %(mbox_dir)s.tmp and proceeding.""")
- os.rename(mbox_dir, "%s.tmp" % (mbox_dir))
- ou = os.umask(0)
- os.mkdir(mbox_dir, 02775)
- os.umask(ou)
-
- # Move any existing mboxes around, but watch out for both a public and a
- # private one existing
- if os.path.isfile(o_pri_mbox_file) and os.path.isfile(o_pub_mbox_file):
- if mlist.archive_private:
- print _("""\
-
-%(listname)s has both public and private mbox archives. Since this list
-currently uses private archiving, I'm installing the private mbox archive
--- %(o_pri_mbox_file)s -- as the active archive, and renaming
- %(o_pub_mbox_file)s
-to
- %(o_pub_mbox_file)s.preb6
-
-You can integrate that into the archives if you want by using the 'arch'
-script.
-""") % (mlist._internal_name, o_pri_mbox_file, o_pub_mbox_file,
- o_pub_mbox_file)
- os.rename(o_pub_mbox_file, "%s.preb6" % (o_pub_mbox_file))
- else:
- print _("""\
-%s has both public and private mbox archives. Since this list
-currently uses public archiving, I'm installing the public mbox file
-archive file (%s) as the active one, and renaming
- %s
- to
- %s.preb6
-
-You can integrate that into the archives if you want by using the 'arch'
-script.
-""") % (mlist._internal_name, o_pub_mbox_file, o_pri_mbox_file,
- o_pri_mbox_file)
- os.rename(o_pri_mbox_file, "%s.preb6" % (o_pri_mbox_file))
- #
- # move private archive mbox there if it's around
- # and take into account all sorts of absurdities
- #
- print _('- updating old private mbox file')
- if os.path.exists(o_pri_mbox_file):
- if os.path.isfile(o_pri_mbox_file):
- os.rename(o_pri_mbox_file, mbox_file)
- elif not os.path.isdir(o_pri_mbox_file):
- newname = "%s.mm_install-dunno_what_this_was_but_its_in_the_way" \
- % o_pri_mbox_file
- os.rename(o_pri_mbox_file, newname)
- print _("""\
- unknown file in the way, moving
- %(o_pri_mbox_file)s
- to
- %(newname)s""")
- else:
- # directory
- print _("""\
- looks like you have a really recent CVS installation...
- you're either one brave soul, or you already ran me""")
-
-
- #
- # move public archive mbox there if it's around
- # and take into account all sorts of absurdities.
- #
- print _('- updating old public mbox file')
- if os.path.exists(o_pub_mbox_file):
- if os.path.isfile(o_pub_mbox_file):
- os.rename(o_pub_mbox_file, mbox_file)
- elif not os.path.isdir(o_pub_mbox_file):
- newname = "%s.mm_install-dunno_what_this_was_but_its_in_the_way" \
- % o_pub_mbox_file
- os.rename(o_pub_mbox_file, newname)
- print _("""\
- unknown file in the way, moving
- %(o_pub_mbox_file)s
- to
- %(newname)s""")
- else: # directory
- print _("""\
- looks like you have a really recent CVS installation...
- you're either one brave soul, or you already ran me""")
-
- #
- # move the html archives there
- #
- if os.path.isdir(o_html_dir):
- os.rename(o_html_dir, html_dir)
- #
- # chmod the html archives
- #
- os.chmod(html_dir, 02775)
- # BAW: Is this still necessary?!
- mlist.Save()
- #
- # check to see if pre-b4 list-specific templates are around
- # and move them to the new place if there's not already
- # a new one there
- #
- tmpl_dir = os.path.join(mm_cfg.PREFIX, "templates")
- list_dir = os.path.join(mm_cfg.PREFIX, "lists")
- b4_tmpl_dir = os.path.join(tmpl_dir, mlist._internal_name)
- new_tmpl_dir = os.path.join(list_dir, mlist._internal_name)
- if os.path.exists(b4_tmpl_dir):
- print _("""\
-- This list looks like it might have <= b4 list templates around""")
- for f in os.listdir(b4_tmpl_dir):
- o_tmpl = os.path.join(b4_tmpl_dir, f)
- n_tmpl = os.path.join(new_tmpl_dir, f)
- if os.path.exists(o_tmpl):
- if not os.path.exists(n_tmpl):
- os.rename(o_tmpl, n_tmpl)
- print _('- moved %(o_tmpl)s to %(n_tmpl)s')
- else:
- print _("""\
-- both %(o_tmpl)s and %(n_tmpl)s exist, leaving untouched""")
- else:
- print _("""\
-- %(o_tmpl)s doesn't exist, leaving untouched""")
- #
- # Move all the templates to the en language subdirectory as required for
- # Mailman 2.1
- #
- move_language_templates(mlist)
- # Avoid eating filehandles with the list lockfiles
- mlist.Unlock()
- return 0
-
-
-
-def archive_path_fixer(unused_arg, dir, files):
- # Passed to os.path.walk to fix the perms on old html archives.
- for f in files:
- abs = os.path.join(dir, f)
- if os.path.isdir(abs):
- if f == "database":
- os.chmod(abs, 02770)
- else:
- os.chmod(abs, 02775)
- elif os.path.isfile(abs):
- os.chmod(abs, 0664)
-
-def remove_old_sources(module):
- # Also removes old directories.
- src = '%s/%s' % (mm_cfg.PREFIX, module)
- pyc = src + "c"
- if os.path.isdir(src):
- print _('removing directory %(src)s and everything underneath')
- shutil.rmtree(src)
- elif os.path.exists(src):
- print _('removing %(src)s')
- try:
- os.unlink(src)
- except os.error, rest:
- print _("Warning: couldn't remove %(src)s -- %(rest)s")
- if module.endswith('.py') and os.path.exists(pyc):
- try:
- os.unlink(pyc)
- except os.error, rest:
- print _("couldn't remove old file %(pyc)s -- %(rest)s")
-
-
-def update_qfiles():
- print _('updating old qfiles')
- prefix = `time.time()` + '+'
- # Be sure the qfiles/in directory exists (we don't really need the
- # switchboard object, but it's convenient for creating the directory).
- sb = Switchboard(mm_cfg.INQUEUE_DIR)
- for filename in os.listdir(mm_cfg.QUEUE_DIR):
- # Updating means just moving the .db and .msg files to qfiles/in where
- # it should be dequeued, converted, and processed normally.
- if filename.endswith('.msg'):
- oldmsgfile = os.path.join(mm_cfg.QUEUE_DIR, filename)
- newmsgfile = os.path.join(mm_cfg.INQUEUE_DIR, prefix + filename)
- os.rename(oldmsgfile, newmsgfile)
- elif filename.endswith('.db'):
- olddbfile = os.path.join(mm_cfg.QUEUE_DIR, filename)
- newdbfile = os.path.join(mm_cfg.INQUEUE_DIR, prefix + filename)
- os.rename(olddbfile, newdbfile)
- # Now update for the Mailman 2.1.5 qfile format. For every filebase in
- # the qfiles/* directories that has both a .pck and a .db file, pull the
- # data out and re-queue them.
- for dirname in os.listdir(mm_cfg.QUEUE_DIR):
- dirpath = os.path.join(mm_cfg.QUEUE_DIR, dirname)
- if dirpath == mm_cfg.BADQUEUE_DIR:
- # The files in qfiles/bad can't possibly be pickles
- continue
- sb = Switchboard(dirpath)
- try:
- for filename in os.listdir(dirpath):
- filepath = os.path.join(dirpath, filename)
- filebase, ext = os.path.splitext(filepath)
- # Handle the .db metadata files as part of the handling of the
- # .pck or .msg message files.
- if ext not in ('.pck', '.msg'):
- continue
- msg, data = dequeue(filebase)
- if msg is not None and data is not None:
- sb.enqueue(msg, data)
- except EnvironmentError, e:
- if e.errno <> errno.ENOTDIR:
- raise
- print _('Warning! Not a directory: %(dirpath)s')
-
-
-
-# Implementations taken from the pre-2.1.5 Switchboard
-def ext_read(filename):
- fp = open(filename)
- d = marshal.load(fp)
- # Update from version 2 files
- if d.get('version', 0) == 2:
- del d['filebase']
- # Do the reverse conversion (repr -> float)
- for attr in ['received_time']:
- try:
- sval = d[attr]
- except KeyError:
- pass
- else:
- # Do a safe eval by setting up a restricted execution
- # environment. This may not be strictly necessary since we
- # know they are floats, but it can't hurt.
- d[attr] = eval(sval, {'__builtins__': {}})
- fp.close()
- return d
-
-
-def dequeue(filebase):
- # Calculate the .db and .msg filenames from the given filebase.
- msgfile = os.path.join(filebase + '.msg')
- pckfile = os.path.join(filebase + '.pck')
- dbfile = os.path.join(filebase + '.db')
- # Now we are going to read the message and metadata for the given
- # filebase. We want to read things in this order: first, the metadata
- # file to find out whether the message is stored as a pickle or as
- # plain text. Second, the actual message file. However, we want to
- # first unlink the message file and then the .db file, because the
- # qrunner only cues off of the .db file
- msg = None
- try:
- data = ext_read(dbfile)
- os.unlink(dbfile)
- except EnvironmentError, e:
- if e.errno <> errno.ENOENT: raise
- data = {}
- # Between 2.1b4 and 2.1b5, the `rejection-notice' key in the metadata
- # was renamed to `rejection_notice', since dashes in the keys are not
- # supported in METAFMT_ASCII.
- if data.has_key('rejection-notice'):
- data['rejection_notice'] = data['rejection-notice']
- del data['rejection-notice']
- msgfp = None
- try:
- try:
- msgfp = open(pckfile)
- msg = cPickle.load(msgfp)
- os.unlink(pckfile)
- except EnvironmentError, e:
- if e.errno <> errno.ENOENT: raise
- msgfp = None
- try:
- msgfp = open(msgfile)
- msg = email.message_from_file(msgfp, Message.Message)
- os.unlink(msgfile)
- except EnvironmentError, e:
- if e.errno <> errno.ENOENT: raise
- except (email.Errors.MessageParseError, ValueError), e:
- # This message was unparsable, most likely because its
- # MIME encapsulation was broken. For now, there's not
- # much we can do about it.
- print _('message is unparsable: %(filebase)s')
- msgfp.close()
- msgfp = None
- if mm_cfg.QRUNNER_SAVE_BAD_MESSAGES:
- # Cheapo way to ensure the directory exists w/ the
- # proper permissions.
- sb = Switchboard(mm_cfg.BADQUEUE_DIR)
- os.rename(msgfile, os.path.join(
- mm_cfg.BADQUEUE_DIR, filebase + '.txt'))
- else:
- os.unlink(msgfile)
- msg = data = None
- except EOFError:
- # For some reason the pckfile was empty. Just delete it.
- print _('Warning! Deleting empty .pck file: %(pckfile)s')
- os.unlink(pckfile)
- finally:
- if msgfp:
- msgfp.close()
- return msg, data
-
-
-
-def update_pending():
- file20 = os.path.join(mm_cfg.DATA_DIR, 'pending_subscriptions.db')
- file214 = os.path.join(mm_cfg.DATA_DIR, 'pending.pck')
- db = None
- # Try to load the Mailman 2.0 file
- try:
- fp = open(file20)
- except IOError, e:
- if e.errno <> errno.ENOENT: raise
- else:
- print _('Updating Mailman 2.0 pending_subscriptions.db database')
- db = marshal.load(fp)
- # Convert to the pre-Mailman 2.1.5 format
- db = Pending._update(db)
- if db is None:
- # Try to load the Mailman 2.1.x where x < 5, file
- try:
- fp = open(file214)
- except IOError, e:
- if e.errno <> errno.ENOENT: raise
- else:
- print _('Updating Mailman 2.1.4 pending.pck database')
- db = cPickle.load(fp)
- if db is None:
- print _('Nothing to do.')
- return
- # Now upgrade the database to the 2.1.5 format. Each list now has its own
- # pending.pck file, but only the RE_ENABLE operation actually recorded the
- # listname in the request. For the SUBSCRIPTION, UNSUBSCRIPTION, and
- # CHANGE_OF_ADDRESS operations, we know the address of the person making
- # the request so we can repend this request just for the lists the person
- # is a member of. For the HELD_MESSAGE operation, we can check the list's
- # requests.pck file for correlation. Evictions will take care of any
- # misdirected pendings.
- reenables_by_list = {}
- addrops_by_address = {}
- holds_by_id = {}
- subs_by_address = {}
- for key, val in db.items():
- if key in ('evictions', 'version'):
- continue
- try:
- op = val[0]
- data = val[1:]
- except (IndexError, ValueError):
- print _('Ignoring bad pended data: %(key)s: %(val)s')
- continue
- if op in (Pending.UNSUBSCRIPTION, Pending.CHANGE_OF_ADDRESS):
- # data[0] is the address being unsubscribed
- addrops_by_address.setdefault(data[0], []).append((key, val))
- elif op == Pending.SUBSCRIPTION:
- # data[0] is a UserDesc object
- addr = data[0].address
- subs_by_address.setdefault(addr, []).append((key, val))
- elif op == Pending.RE_ENABLE:
- # data[0] is the mailing list's internal name
- reenables_by_list.setdefault(data[0], []).append((key, val))
- elif op == Pending.HELD_MESSAGE:
- # data[0] is the hold id. There better only be one entry per id
- id = data[0]
- if holds_by_id.has_key(id):
- print _('WARNING: Ignoring duplicate pending ID: %(id)s.')
- else:
- holds_by_id[id] = (key, val)
- # Now we have to lock every list and re-pend all the appropriate
- # requests. Note that this will reset all the expiration dates, but that
- # should be fine.
- for listname in Utils.list_names():
- mlist = MailList.MailList(listname)
- # This is not the most efficient way to do this because it loads and
- # saves the pending.pck file each time. :(
- try:
- for cookie, data in reenables_by_list.get(listname, []):
- mlist.pend_repend(cookie, data)
- for id, (cookie, data) in holds_by_id.items():
- try:
- rec = mlist.GetRecord(id)
- except KeyError:
- # Not for this list
- pass
- else:
- mlist.pend_repend(cookie, data)
- del holds_by_id[id]
- for addr, recs in subs_by_address.items():
- # We shouldn't have a subscription confirmation if the address
- # is already a member of the mailing list.
- if mlist.isMember(addr):
- continue
- for cookie, data in recs:
- mlist.pend_repend(cookie, data)
- for addr, recs in addrops_by_address.items():
- # We shouldn't have unsubscriptions or change of address
- # requests for addresses which aren't members of the list.
- if not mlist.isMember(addr):
- continue
- for cookie, data in recs:
- mlist.pend_repend(cookie, data)
- mlist.Save()
- finally:
- mlist.Unlock()
- try:
- os.unlink(file20)
- except OSError, e:
- if e.errno <> errno.ENOENT: raise
- try:
- os.unlink(file214)
- except OSError, e:
- if e.errno <> errno.ENOENT: raise
-
-
-
-def main():
- errors = 0
- # get rid of old stuff
- print _('getting rid of old source files')
- for mod in ('Mailman/Archiver.py', 'Mailman/HyperArch.py',
- 'Mailman/HyperDatabase.py', 'Mailman/pipermail.py',
- 'Mailman/smtplib.py', 'Mailman/Cookie.py',
- 'bin/update_to_10b6', 'scripts/mailcmd',
- 'scripts/mailowner', 'mail/wrapper', 'Mailman/pythonlib',
- 'cgi-bin/archives', 'Mailman/MailCommandHandler'):
- remove_old_sources(mod)
- listnames = Utils.list_names()
- if not listnames:
- print _('no lists == nothing to do, exiting')
- return
- #
- # 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" % mm_cfg.PREFIX):
- print _("""\
-fixing all the perms on your old html archives to work with b6
-If your archives are big, this could take a minute or two...""")
- os.path.walk("%s/public_html/archives" % mm_cfg.PREFIX,
- archive_path_fixer, "")
- print _('done')
- for listname in listnames:
- print _('Updating mailing list: %(listname)s')
- errors = errors + dolist(listname)
- print
- print _('Updating Usenet watermarks')
- wmfile = os.path.join(mm_cfg.DATA_DIR, 'gate_watermarks')
- try:
- fp = open(wmfile)
- except IOError:
- print _('- nothing to update here')
- else:
- d = marshal.load(fp)
- fp.close()
- for listname in d.keys():
- if listname not in listnames:
- # this list no longer exists
- continue
- mlist = MailList.MailList(listname, lock=0)
- try:
- mlist.Lock(0.5)
- except TimeOutError:
- print >> sys.stderr, _(
- 'WARNING: could not acquire lock for list: %(listname)s')
- errors = errors + 1
- else:
- # Pre 1.0b7 stored 0 in the gate_watermarks file to indicate
- # that no gating had been done yet. Without coercing this to
- # None, the list could now suddenly get flooded.
- mlist.usenet_watermark = d[listname] or None
- mlist.Save()
- mlist.Unlock()
- os.unlink(wmfile)
- print _('- usenet watermarks updated and gate_watermarks removed')
- # In Mailman 2.1, the pending database format and file name changed, but
- # in Mailman 2.1.5 it changed again. This should update all existing
- # files to the 2.1.5 format.
- update_pending()
- # In Mailman 2.1, the qfiles directory has a different structure and a
- # different content. Also, in Mailman 2.1.5 we collapsed the message
- # files from separate .msg (pickled Message objects) and .db (marshalled
- # dictionaries) to a shared .pck file containing two pickles.
- update_qfiles()
- # This warning was necessary for the upgrade from 1.0b9 to 1.0b10.
- # There's no good way of figuring this out for releases prior to 2.0beta2
- # :(
- if lastversion == NOTFRESH:
- print _("""
-
-NOTE NOTE NOTE NOTE NOTE
-
- You are upgrading an existing Mailman installation, but I can't tell what
- version you were previously running.
-
- If you are upgrading from Mailman 1.0b9 or earlier you will need to
- manually update your mailing lists. For each mailing list you need to
- copy the file templates/options.html lists/<listname>/options.html.
-
- However, if you have edited this file via the Web interface, you will have
- to merge your changes into this file, otherwise you will lose your
- changes.
-
-NOTE NOTE NOTE NOTE NOTE
-
-""")
- return errors
-
-
-
-def usage(code, msg=''):
- if code:
- fd = sys.stderr
- else:
- fd = sys.stdout
- print >> fd, _(__doc__) % globals()
- if msg:
- print >> sys.stderr, msg
- sys.exit(code)
-
-
-
-if __name__ == '__main__':
- try:
- opts, args = getopt.getopt(sys.argv[1:], 'hf',
- ['help', 'force'])
- except getopt.error, msg:
- usage(1, msg)
-
- if args:
- usage(1, 'Unexpected arguments: %s' % args)
-
- force = 0
- for opt, arg in opts:
- if opt in ('-h', '--help'):
- usage(0)
- elif opt in ('-f', '--force'):
- force = 1
-
- # calculate the versions
- lastversion, thisversion = calcversions()
- hexlversion = hex(lastversion)
- hextversion = hex(thisversion)
- if lastversion == thisversion and not force:
- # nothing to do
- print _('No updates are necessary.')
- sys.exit(0)
- if lastversion > thisversion and not force:
- print _("""\
-Downgrade detected, from version %(hexlversion)s to version %(hextversion)s
-This is probably not safe.
-Exiting.""")
- sys.exit(1)
- print _('Upgrading from version %(hexlversion)s to %(hextversion)s')
- errors = main()
- if not errors:
- # Record the version we just upgraded to
- fp = open(LMVFILE, 'w')
- fp.write(hex(mm_cfg.HEX_VERSION) + '\n')
- fp.close()
- else:
- lockdir = mm_cfg.LOCK_DIR
- print _('''\
-
-ERROR:
-
-The locks for some lists could not be acquired. This means that either
-Mailman was still active when you upgraded, or there were stale locks in the
-%(lockdir)s directory.
-
-You must put Mailman into a quiescent state and remove all stale locks, then
-re-run "make update" manually. See the INSTALL and UPGRADE files for details.
-''')
diff --git a/configure b/configure
index aae990544..da03c7318 100755
--- a/configure
+++ b/configure
@@ -1,5 +1,5 @@
#! /bin/sh
-# From configure.in Revision: 7897 .
+# From configure.in Revision: 7899 .
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.59 for GNU Mailman 2.2.0a0.
#
@@ -4295,16 +4295,13 @@ build/bin/dumpdb:bin/dumpdb \
build/bin/fix_url.py:bin/fix_url.py \
build/bin/genaliases:bin/genaliases \
build/bin/list_admins:bin/list_admins \
-build/bin/mailmanctl:bin/mailmanctl \
build/bin/mmshell:bin/mmshell \
build/bin/msgfmt.py:bin/msgfmt.py \
build/bin/pygettext.py:bin/pygettext.py \
-build/bin/qrunner:bin/qrunner \
build/bin/remove_members:bin/remove_members \
build/bin/reset_pw.py:bin/reset_pw.py \
build/bin/sync_members:bin/sync_members \
build/bin/transcheck:bin/transcheck \
-build/bin/update:bin/update \
build/bin/withlist:bin/withlist \
build/bin/templ2pot.py:bin/templ2pot.py \
build/bin/po2templ.py:bin/po2templ.py \
diff --git a/configure.in b/configure.in
index 3cd34d5aa..4cfeba5d3 100644
--- a/configure.in
+++ b/configure.in
@@ -15,7 +15,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
dnl Process this file with autoconf to produce a configure script.
-AC_REVISION($Revision: 7899 $)
+AC_REVISION($Revision: 7926 $)
AC_PREREQ(2.0)
AC_INIT([GNU Mailman], [2.2.0a0])
@@ -602,16 +602,13 @@ bin/dumpdb \
bin/fix_url.py \
bin/genaliases \
bin/list_admins \
-bin/mailmanctl \
bin/mmshell \
bin/msgfmt.py \
bin/pygettext.py \
-bin/qrunner \
bin/remove_members \
bin/reset_pw.py \
bin/sync_members \
bin/transcheck \
-bin/update \
bin/withlist \
bin/templ2pot.py \
bin/po2templ.py \
diff --git a/misc/Makefile.in b/misc/Makefile.in
index 20d90d640..6c1a3a55e 100644
--- a/misc/Makefile.in
+++ b/misc/Makefile.in
@@ -42,6 +42,7 @@ OPT= @OPT@
CFLAGS= $(OPT) $(DEFS)
PACKAGEDIR= $(prefix)/Mailman
DATADIR= $(var_prefix)/data
+ETCDIR= $(var_prefix)/etc
ICONDIR= $(prefix)/icons
SCRIPTSDIR= $(prefix)/scripts
@@ -83,6 +84,7 @@ install-other:
done
$(INSTALL) -m $(EXEMODE) mailman $(DESTDIR)$(SCRIPTSDIR)
$(INSTALL) -m $(FILEMODE) sitelist.cfg $(DESTDIR)$(DATADIR)
+ $(INSTALL) -m $(FILEMODE) mailman.cfg.sample $(DESTDIR)$(ETCDIR)
install-packages:
for p in $(PACKAGES); \
diff --git a/misc/mailman.cfg.sample b/misc/mailman.cfg.sample
new file mode 100644
index 000000000..4cc4ec280
--- /dev/null
+++ b/misc/mailman.cfg.sample
@@ -0,0 +1,66 @@
+# -*- python -*-
+
+# Copyright (C) 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.
+
+"""This module contains your site-specific settings.
+
+Use this to override the default settings in Mailman/Defaults.py. You only
+need to include those settings that you want to change, and unlike the old
+mm_cfg.py file, you do /not/ need to import Defaults. Its variables will
+automatically be available in this module's namespace.
+
+You should consult Defaults.py though for a complete listing of configuration
+variables that you can change.
+
+To use this, copy this file to $VAR_PREFIX/etc/mailman.cfg
+
+Mailman's installation procedure will never overwrite mailman.cfg.
+"""
+# -*- python -*-
+
+# Copyright (C) 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.
+
+"""This module contains your site-specific settings.
+
+Use this to override the default settings in Mailman/Defaults.py. You only
+need to include those settings that you want to change, and unlike the old
+mm_cfg.py file, you do /not/ need to import Defaults. Its variables will
+automatically be available in this module's namespace.
+
+You should consult Defaults.py though for a complete listing of configuration
+variables that you can change.
+
+To use this, copy this file to $VAR_PREFIX/etc/mailman.cfg
+
+Mailman's installation procedure will never overwrite mailman.cfg.
+"""