diff options
Diffstat (limited to 'mailman/bin')
35 files changed, 0 insertions, 6263 deletions
diff --git a/mailman/bin/__init__.py b/mailman/bin/__init__.py deleted file mode 100644 index d61693c5e..000000000 --- a/mailman/bin/__init__.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright (C) 2007-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -__all__ = [ - 'add_members', - 'arch', - 'bounces', - 'bumpdigests', - 'check_perms', - 'checkdbs', - 'cleanarch', - 'config_list', - 'confirm', - 'create_list', - 'disabled', - 'dumpdb', - 'export', - 'find_member', - 'gate_news', - 'genaliases', - 'import', - 'inject', - 'join', - 'leave', - 'list_lists', - 'list_members', - 'list_owners', - 'mailmanctl', - 'make_instance', - 'master', - 'mmsitepass', - 'nightly_gzip', - 'owner', - 'post', - 'qrunner', - 'remove_list', - 'request', - 'senddigests', - 'set_members', - 'show_config', - 'show_qfiles', - 'testall', - 'unshunt', - 'update', - 'version', - 'withlist', - ] diff --git a/mailman/bin/add_members.py b/mailman/bin/add_members.py deleted file mode 100644 index 9c87f4af9..000000000 --- a/mailman/bin/add_members.py +++ /dev/null @@ -1,186 +0,0 @@ -# Copyright (C) 1998-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -import os -import sys -import codecs - -from cStringIO import StringIO -from email.utils import parseaddr - -from mailman import Message -from mailman import Utils -from mailman import i18n -from mailman.app.membership import add_member -from mailman.config import config -from mailman.core import errors -from mailman.interfaces.member import AlreadySubscribedError, DeliveryMode -from mailman.options import SingleMailingListOptions - -_ = i18n._ - - - -class ScriptOptions(SingleMailingListOptions): - usage=_("""\ -%prog [options] - -Add members to a list. 'listname' is the name of the Mailman list you are -adding members to; the list must already exist. - -You must supply at least one of -r and -d options. At most one of the -files can be '-'. -""") - - def add_options(self): - super(ScriptOptions, self).add_options() - self.parser.add_option( - '-r', '--regular-members-file', - type='string', dest='regular', help=_("""\ -A file containing addresses of the members to be added, one address per line. -This list of people become non-digest members. If file is '-', read addresses -from stdin.""")) - self.parser.add_option( - '-d', '--digest-members-file', - type='string', dest='digest', help=_("""\ -Similar to -r, but these people become digest members.""")) - self.parser.add_option( - '-w', '--welcome-msg', - type='yesno', metavar='<y|n>', help=_("""\ -Set whether or not to send the list members a welcome message, overriding -whatever the list's 'send_welcome_msg' setting is.""")) - self.parser.add_option( - '-a', '--admin-notify', - type='yesno', metavar='<y|n>', help=_("""\ -Set whether or not to send the list administrators a notification on the -success/failure of these subscriptions, overriding whatever the list's -'admin_notify_mchanges' setting is.""")) - - def sanity_check(self): - if not self.options.listname: - self.parser.error(_('Missing listname')) - if len(self.arguments) > 0: - self.parser.print_error(_('Unexpected arguments')) - if self.options.regular is None and self.options.digest is None: - parser.error(_('At least one of -r or -d is required')) - if self.options.regular == '-' and self.options.digest == '-': - parser.error(_("-r and -d cannot both be '-'")) - - - -def readfile(filename): - if filename == '-': - fp = sys.stdin - else: - # XXX Need to specify other encodings. - fp = codecs.open(filename, encoding='utf-8') - # Strip all the lines of whitespace and discard blank lines - try: - return set(line.strip() for line in fp if line) - finally: - if fp is not sys.stdin: - fp.close() - - - -class Tee: - def __init__(self, outfp): - self._outfp = outfp - - def write(self, msg): - sys.stdout.write(msg) - self._outfp.write(msg) - - - -def addall(mlist, subscribers, delivery_mode, ack, admin_notify, outfp): - tee = Tee(outfp) - for subscriber in subscribers: - try: - fullname, address = parseaddr(subscriber) - # Watch out for the empty 8-bit string. - if not fullname: - fullname = u'' - password = Utils.MakeRandomPassword() - add_member(mlist, address, fullname, password, delivery_mode, - unicode(config.mailman.default_language)) - # XXX Support ack and admin_notify - except AlreadySubscribedError: - print >> tee, _('Already a member: $subscriber') - except errors.InvalidEmailAddress: - if not address: - print >> tee, _('Bad/Invalid email address: blank line') - else: - print >> tee, _('Bad/Invalid email address: $subscriber') - else: - print >> tee, _('Subscribing: $subscriber') - - - -def main(): - options = ScriptOptions() - options.initialize() - - fqdn_listname = options.options.listname - mlist = config.db.list_manager.get(fqdn_listname) - if mlist is None: - parser.error(_('No such list: $fqdn_listname')) - - # Set up defaults. - send_welcome_msg = (options.options.welcome_msg - if options.options.welcome_msg is not None - else mlist.send_welcome_msg) - admin_notify = (options.options.admin_notify - if options.options.admin_notify is not None - else mlist.admin_notify) - - with i18n.using_language(mlist.preferred_language): - if options.options.digest: - dmembers = readfile(options.options.digest) - else: - dmembers = set() - if options.options.regular: - nmembers = readfile(options.options.regular) - else: - nmembers = set() - - if not dmembers and not nmembers: - print _('Nothing to do.') - sys.exit(0) - - outfp = StringIO() - if nmembers: - addall(mlist, nmembers, DeliveryMode.regular, - send_welcome_msg, admin_notify, outfp) - - if dmembers: - addall(mlist, dmembers, DeliveryMode.mime_digests, - send_welcome_msg, admin_notify, outfp) - - config.db.commit() - - if admin_notify: - subject = _('$mlist.real_name subscription notification') - msg = Message.UserNotification( - mlist.owner, mlist.no_reply_address, subject, - outfp.getvalue(), mlist.preferred_language) - msg.send(mlist) - - - -if __name__ == '__main__': - main() diff --git a/mailman/bin/arch.py b/mailman/bin/arch.py deleted file mode 100644 index a27fa8d7f..000000000 --- a/mailman/bin/arch.py +++ /dev/null @@ -1,151 +0,0 @@ -# Copyright (C) 1998-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -import os -import sys -import errno -import shutil -import optparse - -from locknix.lockfile import Lock - -from mailman import i18n -from mailman.Archiver.HyperArch import HyperArchive -from mailman.Defaults import hours -from mailman.configuration import config -from mailman.initialize import initialize -from mailman.version import MAILMAN_VERSION - -_ = i18n._ - - - -def parseargs(): - parser = optparse.OptionParser(version=MAILMAN_VERSION, - usage=_("""\ -%%prog [options] listname [mbox] - -Rebuild a list's archive. - -Use this command to rebuild the archives for a mailing list. You may want to -do this if you edit some messages in an archive, or remove some messages from -an archive. - -Where 'mbox' is the path to a list's complete mbox archive. Usually this will -be some path in the archives/private directory. For example: - -% bin/arch mylist archives/private/mylist.mbox/mylist.mbox - -'mbox' is optional. If it is missing, it is calculated from the listname. -""")) - parser.add_option('-q', '--quiet', - dest='verbose', default=True, action='store_false', - help=_('Make the archiver output less verbose')) - parser.add_option('--wipe', - default=False, action='store_true', - help=_("""\ -First wipe out the original archive before regenerating. You usually want to -specify this argument unless you're generating the archive in chunks.""")) - parser.add_option('-s', '--start', - default=None, type='int', metavar='N', - help=_("""\ -Start indexing at article N, where article 0 is the first in the mbox. -Defaults to 0.""")) - parser.add_option('-e', '--end', - default=None, type='int', metavar='M', - help=_("""\ -End indexing at article M. This script is not very efficient with respect to -memory management, and for large archives, it may not be possible to index the -mbox entirely. For that reason, you can specify the start and end article -numbers.""")) - parser.add_option('-C', '--config', - help=_('Alternative configuration file to use')) - opts, args = parser.parse_args() - if len(args) < 1: - parser.print_help() - print >> sys.stderr, _('listname is required') - sys.exit(1) - if len(args) > 2: - parser.print_help() - print >> sys.stderr, _('Unexpected arguments') - sys.exit(1) - return parser, opts, args - - - -def main(): - parser, opts, args = parseargs() - initialize(opts.config) - - i18n.set_language(config.DEFAULT_SERVER_LANGUAGE) - - listname = args[0].lower().strip() - if len(args) < 2: - mbox = None - else: - mbox = args[1] - - # Open the mailing list object - mlist = config.list_manager.get(listname) - if mlist is None: - parser.error(_('No such list: $listname')) - if mbox is None: - mbox = mlist.ArchiveFileName() - - i18n.set_language(mlist.preferred_language) - # Lay claim to the archive's lock file. This is so no other post can - # mess up the archive while we're processing it. Try to pick a - # suitably long period of time for the lock lifetime even though we - # really don't know how long it will take. - # - # XXX processUnixMailbox() should refresh the lock. - lock_path = os.path.join(mlist.data_path, '.archiver.lck') - with Lock(lock_path, lifetime=int(hours(3))): - # Try to open mbox before wiping old archive. - try: - fp = open(mbox) - except IOError, e: - if e.errno == errno.ENOENT: - print >> sys.stderr, _('Cannot open mbox file: $mbox') - else: - print >> sys.stderr, e - sys.exit(1) - # Maybe wipe the old archives - if opts.wipe: - if mlist.scrub_nondigest: - # TK: save the attachments dir because they are not in mbox - saved = False - atchdir = os.path.join(mlist.archive_dir(), 'attachments') - savedir = os.path.join(mlist.archive_dir() + '.mbox', - 'attachments') - try: - os.rename(atchdir, savedir) - saved = True - except OSError, e: - if e.errno <> errno.ENOENT: - raise - shutil.rmtree(mlist.archive_dir()) - if mlist.scrub_nondigest and saved: - os.renames(savedir, atchdir) - - archiver = HyperArchive(mlist) - archiver.VERBOSE = opts.verbose - try: - archiver.processUnixMailbox(fp, opts.start, opts.end) - finally: - archiver.close() - fp.close() diff --git a/mailman/bin/bumpdigests.py b/mailman/bin/bumpdigests.py deleted file mode 100644 index b1ed37a21..000000000 --- a/mailman/bin/bumpdigests.py +++ /dev/null @@ -1,74 +0,0 @@ -# Copyright (C) 1998-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -import sys -import optparse - -from mailman import errors -from mailman import MailList -from mailman.configuration import config -from mailman.i18n import _ -from mailman.version import MAILMAN_VERSION - -# Work around known problems with some RedHat cron daemons -import signal -signal.signal(signal.SIGCHLD, signal.SIG_DFL) - - - -def parseargs(): - parser = optparse.OptionParser(version=MAILMAN_VERSION, - usage=_("""\ -%prog [options] [listname ...] - -Increment the digest volume number and reset the digest number to one. All -the lists named on the command line are bumped. If no list names are given, -all lists are bumped.""")) - parser.add_option('-C', '--config', - help=_('Alternative configuration file to use')) - opts, args = parser.parse_args() - return opts, args, parser - - - -def main(): - opts, args, parser = parseargs() - config.load(opts.config) - - listnames = set(args or config.list_manager.names) - if not listnames: - print _('Nothing to do.') - sys.exit(0) - - for listname in listnames: - try: - # Be sure the list is locked - mlist = MailList.MailList(listname) - except errors.MMListError, e: - parser.print_help() - print >> sys.stderr, _('No such list: $listname') - sys.exit(1) - try: - mlist.bump_digest_volume() - finally: - mlist.Save() - mlist.Unlock() - - - -if __name__ == '__main__': - main() diff --git a/mailman/bin/check_perms.py b/mailman/bin/check_perms.py deleted file mode 100644 index 4b75aa9f6..000000000 --- a/mailman/bin/check_perms.py +++ /dev/null @@ -1,408 +0,0 @@ -# Copyright (C) 1998-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -import os -import sys -import pwd -import grp -import errno -import optparse - -from stat import * - -from mailman.configuration import config -from mailman.i18n import _ -from mailman.version import MAILMAN_VERSION - - -# XXX Need to check the archives/private/*/database/* files - - - -class State: - FIX = False - VERBOSE = False - ERRORS = 0 - -STATE = State() - -DIRPERMS = S_ISGID | S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH -QFILEPERMS = S_ISGID | S_IRWXU | S_IRWXG -PYFILEPERMS = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH -ARTICLEFILEPERMS = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP -MBOXPERMS = S_IRGRP | S_IWGRP | S_IRUSR | S_IWUSR -PRIVATEPERMS = QFILEPERMS - - - -def statmode(path): - return os.stat(path).st_mode - - -def statgidmode(path): - stat = os.stat(path) - return stat.st_mode, stat.st_gid - - -seen = {} - -# libc's getgrgid re-opens /etc/group each time :( -_gidcache = {} - -def getgrgid(gid): - data = _gidcache.get(gid) - if data is None: - data = grp.getgrgid(gid) - _gidcache[gid] = data - return data - - - -def checkwalk(arg, dirname, names): - # Short-circuit duplicates - if seen.has_key(dirname): - return - seen[dirname] = True - for name in names: - path = os.path.join(dirname, name) - if arg.VERBOSE: - print _(' checking gid and mode for $path') - try: - mode, gid = statgidmode(path) - except OSError, e: - if e.errno <> errno.ENOENT: raise - continue - if gid <> MAILMAN_GID: - try: - groupname = getgrgid(gid)[0] - except KeyError: - groupname = '<anon gid %d>' % gid - arg.ERRORS += 1 - print _( - '$path bad group (has: $groupname, expected $MAILMAN_GROUP)'), - if STATE.FIX: - print _('(fixing)') - os.chown(path, -1, MAILMAN_GID) - else: - print - # Most directories must be at least rwxrwsr-x. - # The private archive directory and database directory must be at - # least rwxrws---. Their 'other' permissions are checked in - # checkarchives() and checkarchivedbs() below. Their 'user' and - # 'group' permissions are checked here. - # The directories under qfiles should be rwxrws---. Their 'user' and - # 'group' permissions are checked here. Their 'other' permissions - # aren't checked. - private = config.PRIVATE_ARCHIVE_FILE_DIR - if path == private or ( - os.path.commonprefix((path, private)) == private - and os.path.split(path)[1] == 'database'): - # then... - targetperms = PRIVATEPERMS - elif (os.path.commonprefix((path, config.QUEUE_DIR)) - == config.QUEUE_DIR): - targetperms = QFILEPERMS - else: - targetperms = DIRPERMS - octperms = oct(targetperms) - if S_ISDIR(mode) and (mode & targetperms) <> targetperms: - arg.ERRORS += 1 - print _('directory permissions must be $octperms: $path'), - if STATE.FIX: - print _('(fixing)') - os.chmod(path, mode | targetperms) - else: - print - elif os.path.splitext(path)[1] in ('.py', '.pyc', '.pyo'): - octperms = oct(PYFILEPERMS) - if mode & PYFILEPERMS <> PYFILEPERMS: - print _('source perms must be $octperms: $path'), - arg.ERRORS += 1 - if STATE.FIX: - print _('(fixing)') - os.chmod(path, mode | PYFILEPERMS) - else: - print - elif path.endswith('-article'): - # Article files must be group writeable - octperms = oct(ARTICLEFILEPERMS) - if mode & ARTICLEFILEPERMS <> ARTICLEFILEPERMS: - print _('article db files must be $octperms: $path'), - arg.ERRORS += 1 - if STATE.FIX: - print _('(fixing)') - os.chmod(path, mode | ARTICLEFILEPERMS) - else: - print - - - -def checkall(): - # first check PREFIX - if STATE.VERBOSE: - prefix = config.PREFIX - print _('checking mode for $prefix') - dirs = {} - for d in (config.PREFIX, config.EXEC_PREFIX, config.VAR_PREFIX, - config.LOG_DIR): - dirs[d] = True - for d in dirs.keys(): - try: - mode = statmode(d) - except OSError, e: - if e.errno <> errno.ENOENT: raise - print _('WARNING: directory does not exist: $d') - continue - if (mode & DIRPERMS) <> DIRPERMS: - STATE.ERRORS += 1 - print _('directory must be at least 02775: $d'), - if STATE.FIX: - print _('(fixing)') - os.chmod(d, mode | DIRPERMS) - else: - print - # check all subdirs - os.path.walk(d, checkwalk, STATE) - - - -def checkarchives(): - private = config.PRIVATE_ARCHIVE_FILE_DIR - if STATE.VERBOSE: - print _('checking perms on $private') - # private archives must not be other readable - mode = statmode(private) - if mode & S_IROTH: - STATE.ERRORS += 1 - print _('$private must not be other-readable'), - if STATE.FIX: - print _('(fixing)') - os.chmod(private, mode & ~S_IROTH) - else: - print - # In addition, on a multiuser system you may want to hide the private - # archives so other users can't read them. - if mode & S_IXOTH: - print _("""\ -Warning: Private archive directory is other-executable (o+x). - This could allow other users on your system to read private archives. - If you're on a shared multiuser system, you should consult the - installation manual on how to fix this.""") - - - -def checkmboxfile(mboxdir): - absdir = os.path.join(config.PRIVATE_ARCHIVE_FILE_DIR, mboxdir) - for f in os.listdir(absdir): - if not f.endswith('.mbox'): - continue - mboxfile = os.path.join(absdir, f) - mode = statmode(mboxfile) - if (mode & MBOXPERMS) <> MBOXPERMS: - STATE.ERRORS = STATE.ERRORS + 1 - print _('mbox file must be at least 0660:'), mboxfile - if STATE.FIX: - print _('(fixing)') - os.chmod(mboxfile, mode | MBOXPERMS) - else: - print - - - -def checkarchivedbs(): - # The archives/private/listname/database file must not be other readable - # or executable otherwise those files will be accessible when the archives - # are public. That may not be a horrible breach, but let's close this off - # anyway. - for dir in os.listdir(config.PRIVATE_ARCHIVE_FILE_DIR): - if dir.endswith('.mbox'): - checkmboxfile(dir) - dbdir = os.path.join(config.PRIVATE_ARCHIVE_FILE_DIR, dir, 'database') - try: - mode = statmode(dbdir) - except OSError, e: - if e.errno not in (errno.ENOENT, errno.ENOTDIR): raise - continue - if mode & S_IRWXO: - STATE.ERRORS += 1 - print _('$dbdir "other" perms must be 000'), - if STATE.FIX: - print _('(fixing)') - os.chmod(dbdir, mode & ~S_IRWXO) - else: - print - - - -def checkcgi(): - cgidir = os.path.join(config.EXEC_PREFIX, 'cgi-bin') - if STATE.VERBOSE: - print _('checking cgi-bin permissions') - exes = os.listdir(cgidir) - for f in exes: - path = os.path.join(cgidir, f) - if STATE.VERBOSE: - print _(' checking set-gid for $path') - mode = statmode(path) - if mode & S_IXGRP and not mode & S_ISGID: - STATE.ERRORS += 1 - print _('$path must be set-gid'), - if STATE.FIX: - print _('(fixing)') - os.chmod(path, mode | S_ISGID) - else: - print - - - -def checkmail(): - wrapper = os.path.join(config.WRAPPER_DIR, 'mailman') - if STATE.VERBOSE: - print _('checking set-gid for $wrapper') - mode = statmode(wrapper) - if not mode & S_ISGID: - STATE.ERRORS += 1 - print _('$wrapper must be set-gid'), - if STATE.FIX: - print _('(fixing)') - os.chmod(wrapper, mode | S_ISGID) - - - -def checkadminpw(): - for pwfile in (os.path.join(config.DATA_DIR, 'adm.pw'), - os.path.join(config.DATA_DIR, 'creator.pw')): - targetmode = S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP - if STATE.VERBOSE: - print _('checking permissions on $pwfile') - try: - mode = statmode(pwfile) - except OSError, e: - if e.errno <> errno.ENOENT: - raise - return - if mode <> targetmode: - STATE.ERRORS += 1 - octmode = oct(mode) - print _('$pwfile permissions must be exactly 0640 (got $octmode)'), - if STATE.FIX: - print _('(fixing)') - os.chmod(pwfile, targetmode) - else: - print - - -def checkmta(): - if config.MTA: - modname = 'mailman.MTA.' + config.MTA - __import__(modname) - try: - sys.modules[modname].checkperms(STATE) - except AttributeError: - pass - - - -def checkdata(): - targetmode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP - checkfiles = ('config.pck', 'config.pck.last', - 'config.db', 'config.db.last', - 'next-digest', 'next-digest-topics', - 'digest.mbox', 'pending.pck', - 'request.db', 'request.db.tmp') - if STATE.VERBOSE: - print _('checking permissions on list data') - for dir in os.listdir(config.LIST_DATA_DIR): - for file in checkfiles: - path = os.path.join(config.LIST_DATA_DIR, dir, file) - if STATE.VERBOSE: - print _(' checking permissions on: $path') - try: - mode = statmode(path) - except OSError, e: - if e.errno <> errno.ENOENT: - raise - continue - if (mode & targetmode) <> targetmode: - STATE.ERRORS += 1 - print _('file permissions must be at least 660: $path'), - if STATE.FIX: - print _('(fixing)') - os.chmod(path, mode | targetmode) - else: - print - - - -def parseargs(): - parser = optparse.OptionParser(version=MAILMAN_VERSION, - usage=_("""\ -%prog [options] - -Check the permissions of all Mailman files. With no options, just report the -permission and ownership problems found.""")) - parser.add_option('-f', '--fix', - default=False, action='store_true', help=_("""\ -Fix all permission and ownership problems found. With this option, you must -run check_perms as root.""")) - parser.add_option('-v', '--verbose', - default=False, action='store_true', - help=_('Produce more verbose output')) - 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 main(): - global MAILMAN_USER, MAILMAN_GROUP, MAILMAN_UID, MAILMAN_GID - - parser, opts, args = parseargs() - STATE.FIX = opts.fix - STATE.VERBOSE = opts.verbose - - config.load(opts.config) - - MAILMAN_USER = config.MAILMAN_USER - MAILMAN_GROUP = config.MAILMAN_GROUP - # Let KeyErrors percolate - MAILMAN_GID = grp.getgrnam(MAILMAN_GROUP).gr_gid - MAILMAN_UID = pwd.getpwnam(MAILMAN_USER).pw_uid - - checkall() - checkarchives() - checkarchivedbs() - checkcgi() - checkmail() - checkdata() - checkadminpw() - checkmta() - - if not STATE.ERRORS: - print _('No problems found') - else: - print _('Problems found:'), STATE.ERRORS - print _('Re-run as $MAILMAN_USER (or root) with -f flag to fix') - - -if __name__ == '__main__': - main() diff --git a/mailman/bin/checkdbs.py b/mailman/bin/checkdbs.py deleted file mode 100644 index 2ce08aab7..000000000 --- a/mailman/bin/checkdbs.py +++ /dev/null @@ -1,199 +0,0 @@ -# Copyright (C) 1998-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -import sys -import time -import optparse - -from email.Charset import Charset - -from mailman import MailList -from mailman import Message -from mailman import Utils -from mailman import i18n -from mailman.app.requests import handle_request -from mailman.configuration import config -from mailman.version import MAILMAN_VERSION - -_ = i18n._ - -# Work around known problems with some RedHat cron daemons -import signal -signal.signal(signal.SIGCHLD, signal.SIG_DFL) - -NL = u'\n' -now = time.time() - - - -def parseargs(): - parser = optparse.OptionParser(version=MAILMAN_VERSION, - usage=_("""\ -%prog [options] - -Check for pending admin requests and mail the list owners if necessary.""")) - 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 opts, args, parser - - - -def pending_requests(mlist): - # Must return a byte string - lcset = Utils.GetCharSet(mlist.preferred_language) - pending = [] - first = True - requestsdb = config.db.get_list_requests(mlist) - for request in requestsdb.of_type(RequestType.subscription): - if first: - pending.append(_('Pending subscriptions:')) - first = False - key, data = requestsdb.get_request(request.id) - when = data['when'] - addr = data['addr'] - fullname = data['fullname'] - passwd = data['passwd'] - digest = data['digest'] - lang = data['lang'] - if fullname: - if isinstance(fullname, unicode): - fullname = fullname.encode(lcset, 'replace') - fullname = ' (%s)' % fullname - pending.append(' %s%s %s' % (addr, fullname, time.ctime(when))) - first = True - for request in requestsdb.of_type(RequestType.held_message): - if first: - pending.append(_('\nPending posts:')) - first = False - key, data = requestsdb.get_request(request.id) - when = data['when'] - sender = data['sender'] - subject = data['subject'] - reason = data['reason'] - text = data['text'] - msgdata = data['msgdata'] - subject = Utils.oneline(subject, lcset) - date = time.ctime(when) - reason = _(reason) - pending.append(_("""\ -From: $sender on $date -Subject: $subject -Cause: $reason""")) - pending.append('') - # Coerce all items in pending to a Unicode so we can join them - upending = [] - charset = Utils.GetCharSet(mlist.preferred_language) - for s in pending: - if isinstance(s, unicode): - upending.append(s) - else: - upending.append(unicode(s, charset, 'replace')) - # Make sure that the text we return from here can be encoded to a byte - # string in the charset of the list's language. This could fail if for - # example, the request was pended while the list's language was French, - # but then it was changed to English before checkdbs ran. - text = NL.join(upending) - charset = Charset(Utils.GetCharSet(mlist.preferred_language)) - incodec = charset.input_codec or 'ascii' - outcodec = charset.output_codec or 'ascii' - if isinstance(text, unicode): - return text.encode(outcodec, 'replace') - # Be sure this is a byte string encodeable in the list's charset - utext = unicode(text, incodec, 'replace') - return utext.encode(outcodec, 'replace') - - - -def auto_discard(mlist): - # Discard old held messages - discard_count = 0 - expire = config.days(mlist.max_days_to_hold) - requestsdb = config.db.get_list_requests(mlist) - heldmsgs = list(requestsdb.of_type(RequestType.held_message)) - if expire and heldmsgs: - for request in heldmsgs: - key, data = requestsdb.get_request(request.id) - if now - data['date'] > expire: - handle_request(mlist, request.id, config.DISCARD) - discard_count += 1 - mlist.Save() - return discard_count - - - -def main(): - opts, args, parser = parseargs() - config.load(opts.config) - - i18n.set_language(config.DEFAULT_SERVER_LANGUAGE) - - for name in config.list_manager.names: - # The list must be locked in order to open the requests database - mlist = MailList.MailList(name) - try: - count = config.db.requests.get_list_requests(mlist).count - # While we're at it, let's evict yesterday's autoresponse data - midnight_today = Utils.midnight() - evictions = [] - for sender in mlist.hold_and_cmd_autoresponses.keys(): - date, respcount = mlist.hold_and_cmd_autoresponses[sender] - if Utils.midnight(date) < midnight_today: - evictions.append(sender) - if evictions: - for sender in evictions: - del mlist.hold_and_cmd_autoresponses[sender] - # This is the only place we've changed the list's database - mlist.Save() - if count: - i18n.set_language(mlist.preferred_language) - realname = mlist.real_name - discarded = auto_discard(mlist) - if discarded: - count = count - discarded - text = _( - 'Notice: $discarded old request(s) automatically expired.\n\n') - else: - text = '' - if count: - text += Utils.maketext( - 'checkdbs.txt', - {'count' : count, - 'host_name': mlist.host_name, - 'adminDB' : mlist.GetScriptURL('admindb', absolute=1), - 'real_name': realname, - }, mlist=mlist) - text += '\n' + pending_requests(mlist) - subject = _('$count $realname moderator request(s) waiting') - else: - subject = _('$realname moderator request check result') - msg = Message.UserNotification(mlist.GetOwnerEmail(), - mlist.GetBouncesEmail(), - subject, text, - mlist.preferred_language) - msg.send(mlist, **{'tomoderators': True}) - finally: - mlist.Unlock() - - - -if __name__ == '__main__': - main() diff --git a/mailman/bin/cleanarch.py b/mailman/bin/cleanarch.py deleted file mode 100644 index 325fad91a..000000000 --- a/mailman/bin/cleanarch.py +++ /dev/null @@ -1,133 +0,0 @@ -# Copyright (C) 2001-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -"""Clean up an .mbox archive file.""" - -import re -import sys -import mailbox -import optparse - -from mailman.i18n import _ -from mailman.version import MAILMAN_VERSION - - -cre = re.compile(mailbox.UnixMailbox._fromlinepattern) -# From RFC 2822, a header field name must contain only characters from 33-126 -# inclusive, excluding colon. I.e. from oct 41 to oct 176 less oct 072. Must -# use re.match() so that it's anchored at the beginning of the line. -fre = re.compile(r'[\041-\071\073-\176]+') - - - -def parseargs(): - parser = optparse.OptionParser(version=MAILMAN_VERSION, - usage=_("""\ -%prog [options] < inputfile > outputfile - -The archiver looks for Unix-From lines separating messages in an mbox archive -file. For compatibility, it specifically looks for lines that start with -'From ' -- i.e. the letters capital-F, lowercase-r, o, m, space, ignoring -everything else on the line. - -Normally, any lines that start 'From ' in the body of a message should be -escaped such that a > character is actually the first on a line. It is -possible though that body lines are not actually escaped. This script -attempts to fix these by doing a stricter test of the Unix-From lines. Any -lines that start From ' but do not pass this stricter test are escaped with a -'>' character.""")) - parser.add_option('-q', '--quiet', - default=False, action='store_true', help=_("""\ -Don't print changed line information to standard error.""")) - parser.add_option('-s', '--status', - default=-1, type='int', help=_("""\ -Print a '#' character for every n lines processed. With a number less than or -equal to zero, suppress the '#' characters.""")) - parser.add_option('-n', '--dry-run', - default=False, action='store_true', help=_("""\ -Don't actually output anything.""")) - opts, args = parser.parser_args() - if args: - parser.print_error(_('Unexpected arguments')) - return parser, opts, args - - - -def escape_line(line, lineno, quiet, output): - if output: - sys.stdout.write('>' + line) - if not quiet: - print >> sys.stderr, _('Unix-From line changed: $lineno') - print >> sys.stderr, line[:-1] - - - -def main(): - parser, opts, args = parseargs() - - lineno = 0 - statuscnt = 0 - messages = 0 - prevline = None - while True: - lineno += 1 - line = sys.stdin.readline() - if not line: - break - if line.startswith('From '): - if cre.match(line): - # This is a real Unix-From line. But it could be a message - # /about/ Unix-From lines, so as a second order test, make - # sure there's at least one RFC 2822 header following - nextline = sys.stdin.readline() - lineno += 1 - if not nextline: - # It was the last line of the mbox, so it couldn't have - # been a Unix-From - escape_line(line, lineno, quiet, output) - break - fieldname = nextline.split(':', 1) - if len(fieldname) < 2 or not fre.match(nextline): - # The following line was not a header, so this wasn't a - # valid Unix-From - escape_line(line, lineno, quiet, output) - if output: - sys.stdout.write(nextline) - else: - # It's a valid Unix-From line - messages += 1 - if output: - # Before we spit out the From_ line, make sure the - # previous line was blank. - if prevline is not None and prevline <> '\n': - sys.stdout.write('\n') - sys.stdout.write(line) - sys.stdout.write(nextline) - else: - # This is a bogus Unix-From line - escape_line(line, lineno, quiet, output) - elif output: - # Any old line - sys.stdout.write(line) - if status > 0 and (lineno % status) == 0: - sys.stderr.write('#') - statuscnt += 1 - if statuscnt > 50: - print >> sys.stderr - statuscnt = 0 - prevline = line - print >> sys.stderr, _('%(messages)d messages found') diff --git a/mailman/bin/config_list.py b/mailman/bin/config_list.py deleted file mode 100644 index a5cec9480..000000000 --- a/mailman/bin/config_list.py +++ /dev/null @@ -1,332 +0,0 @@ -# Copyright (C) 1998-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -import re -import sys -import time -import optparse - -from mailman import errors -from mailman import MailList -from mailman import Utils -from mailman import i18n -from mailman.configuration import config -from mailman.version import MAILMAN_VERSION - -_ = i18n._ - -NL = '\n' -nonasciipat = re.compile(r'[\x80-\xff]') - - - -def parseargs(): - parser = optparse.OptionParser(version=MAILMAN_VERSION, - usage=_("""\ -%prog [options] listname - -Configure a list from a text file description, or dump a list's configuration -settings.""")) - parser.add_option('-i', '--inputfile', - metavar='FILENAME', default=None, type='string', - help=_("""\ -Configure the list by assigning each module-global variable in the file to an -attribute on the mailing list object, then save the list. The named file is -loaded with execfile() and must be legal Python code. Any variable that isn't -already an attribute of the list object is ignored (a warning message is -printed). See also the -c option. - -A special variable named 'mlist' is put into the globals during the execfile, -which is bound to the actual MailList object. This lets you do all manner of -bizarre thing to the list object, but BEWARE! Using this can severely (and -possibly irreparably) damage your mailing list! - -The may not be used with the -o option.""")) - parser.add_option('-o', '--outputfile', - metavar='FILENAME', default=None, type='string', - help=_("""\ -Instead of configuring the list, print out a mailing list's configuration -variables in a format suitable for input using this script. In this way, you -can easily capture the configuration settings for a particular list and -imprint those settings on another list. FILENAME is the file to output the -settings to. If FILENAME is `-', standard out is used. - -This may not be used with the -i option.""")) - parser.add_option('-c', '--checkonly', - default=False, action='store_true', help=_("""\ -With this option, the modified list is not actually changed. This is only -useful with the -i option.""")) - parser.add_option('-v', '--verbose', - default=False, action='store_true', help=_("""\ -Print the name of each attribute as it is being changed. This is only useful -with the -i option.""")) - parser.add_option('-C', '--config', - help=_('Alternative configuration file to use')) - opts, args = parser.parse_args() - if len(args) > 1: - parser.print_help() - parser.error(_('Unexpected arguments')) - if not args: - parser.error(_('List name is required')) - return parser, opts, args - - - -def do_output(listname, outfile, parser): - closep = False - try: - if outfile == '-': - outfp = sys.stdout - else: - outfp = open(outfile, 'w') - closep = True - # Open the specified list unlocked, since we're only reading it. - try: - mlist = MailList.MailList(listname, lock=False) - except errors.MMListError: - parser.error(_('No such list: $listname')) - # Preamble for the config info. PEP 263 charset and capture time. - language = mlist.preferred_language - charset = Utils.GetCharSet(language) - i18n.set_language(language) - if not charset: - charset = 'us-ascii' - when = time.ctime(time.time()) - print >> outfp, _('''\ -# -*- python -*- -# -*- coding: $charset -*- -## "$listname" mailing list configuration settings -## captured on $when -''') - # Get all the list config info. All this stuff is accessible via the - # web interface. - for k in config.ADMIN_CATEGORIES: - subcats = mlist.GetConfigSubCategories(k) - if subcats is None: - do_list_categories(mlist, k, None, outfp) - else: - for subcat in [t[0] for t in subcats]: - do_list_categories(mlist, k, subcat, outfp) - finally: - if closep: - outfp.close() - - - -def do_list_categories(mlist, k, subcat, outfp): - info = mlist.GetConfigInfo(k, subcat) - label, gui = mlist.GetConfigCategories()[k] - if info is None: - return - charset = Utils.GetCharSet(mlist.preferred_language) - print >> outfp, '##', k.capitalize(), _('options') - print >> outfp, '#' - # First, massage the descripton text, which could have obnoxious - # leading whitespace on second and subsequent lines due to - # triple-quoted string nonsense in the source code. - desc = NL.join([s.lstrip() for s in info[0].splitlines()]) - # Print out the category description - desc = Utils.wrap(desc) - for line in desc.splitlines(): - print >> outfp, '#', line - print >> outfp - for data in info[1:]: - if not isinstance(data, tuple): - continue - varname = data[0] - # Variable could be volatile - if varname[0] == '_': - continue - vtype = data[1] - # First, massage the descripton text, which could have - # obnoxious leading whitespace on second and subsequent lines - # due to triple-quoted string nonsense in the source code. - desc = NL.join([s.lstrip() for s in data[-1].splitlines()]) - # Now strip out all HTML tags - desc = re.sub('<.*?>', '', desc) - # And convert </> to <> - desc = re.sub('<', '<', desc) - desc = re.sub('>', '>', desc) - # Print out the variable description. - desc = Utils.wrap(desc) - for line in desc.split('\n'): - print >> outfp, '#', line - # munge the value based on its type - value = None - if hasattr(gui, 'getValue'): - value = gui.getValue(mlist, vtype, varname, data[2]) - if value is None and not varname.startswith('_'): - value = getattr(mlist, varname) - if vtype in (config.String, config.Text, config.FileUpload): - print >> outfp, varname, '=', - lines = value.splitlines() - if not lines: - print >> outfp, "''" - elif len(lines) == 1: - if charset <> 'us-ascii' and nonasciipat.search(lines[0]): - # This is more readable for non-english list. - print >> outfp, '"' + lines[0].replace('"', '\\"') + '"' - else: - print >> outfp, repr(lines[0]) - else: - if charset == 'us-ascii' and nonasciipat.search(value): - # Normally, an english list should not have non-ascii char. - print >> outfp, repr(NL.join(lines)) - else: - outfp.write(' """') - outfp.write(NL.join(lines).replace('"', '\\"')) - outfp.write('"""\n') - elif vtype in (config.Radio, config.Toggle): - print >> outfp, '#' - print >> outfp, '#', _('legal values are:') - # TBD: This is disgusting, but it's special cased - # everywhere else anyway... - if varname == 'subscribe_policy' and \ - not config.ALLOW_OPEN_SUBSCRIBE: - i = 1 - else: - i = 0 - for choice in data[2]: - print >> outfp, '# ', i, '= "%s"' % choice - i += 1 - print >> outfp, varname, '=', repr(value) - else: - print >> outfp, varname, '=', repr(value) - print >> outfp - - - -def getPropertyMap(mlist): - guibyprop = {} - categories = mlist.GetConfigCategories() - for category, (label, gui) in categories.items(): - if not hasattr(gui, 'GetConfigInfo'): - continue - subcats = mlist.GetConfigSubCategories(category) - if subcats is None: - subcats = [(None, None)] - for subcat, sclabel in subcats: - for element in gui.GetConfigInfo(mlist, category, subcat): - if not isinstance(element, tuple): - continue - propname = element[0] - wtype = element[1] - guibyprop[propname] = (gui, wtype) - return guibyprop - - -class FakeDoc: - # Fake the error reporting API for the htmlformat.Document class - def addError(self, s, tag=None, *args): - if tag: - print >> sys.stderr, tag - print >> sys.stderr, s % args - - def set_language(self, val): - pass - - - -def do_input(listname, infile, checkonly, verbose, parser): - fakedoc = FakeDoc() - # Open the specified list locked, unless checkonly is set - try: - mlist = MailList.MailList(listname, lock=not checkonly) - except errors.MMListError, e: - parser.error(_('No such list "$listname"\n$e')) - savelist = False - guibyprop = getPropertyMap(mlist) - try: - globals = {'mlist': mlist} - # Any exception that occurs in execfile() will cause the list to not - # be saved, but any other problems are not save-fatal. - execfile(infile, globals) - savelist = True - for k, v in globals.items(): - if k in ('mlist', '__builtins__'): - continue - if not hasattr(mlist, k): - print >> sys.stderr, _('attribute "$k" ignored') - continue - if verbose: - print >> sys.stderr, _('attribute "$k" changed') - missing = [] - gui, wtype = guibyprop.get(k, (missing, missing)) - if gui is missing: - # This isn't an official property of the list, but that's - # okay, we'll just restore it the old fashioned way - print >> sys.stderr, _('Non-standard property restored: $k') - setattr(mlist, k, v) - else: - # BAW: This uses non-public methods. This logic taken from - # the guts of GUIBase.handleForm(). - try: - validval = gui._getValidValue(mlist, k, wtype, v) - except ValueError: - print >> sys.stderr, _('Invalid value for property: $k') - except errors.EmailAddressError: - print >> sys.stderr, _( - 'Bad email address for option $k: $v') - else: - # BAW: Horrible hack, but then this is special cased - # everywhere anyway. :( Privacy._setValue() knows that - # when ALLOW_OPEN_SUBSCRIBE is false, the web values are - # 0, 1, 2 but these really should be 1, 2, 3, so it adds - # one. But we really do provide [0..3] so we need to undo - # the hack that _setValue adds. :( :( - if k == 'subscribe_policy' and \ - not config.ALLOW_OPEN_SUBSCRIBE: - validval -= 1 - # BAW: Another horrible hack. This one is just too hard - # to fix in a principled way in Mailman 2.1 - elif k == 'new_member_options': - # Because this is a Checkbox, _getValidValue() - # transforms the value into a list of one item. - validval = validval[0] - validval = [bitfield for bitfield, bitval - in config.OPTINFO.items() - if validval & bitval] - gui._setValue(mlist, k, validval, fakedoc) - # BAW: when to do gui._postValidate()??? - finally: - if savelist and not checkonly: - mlist.Save() - mlist.Unlock() - - - -def main(): - parser, opts, args = parseargs() - config.load(opts.config) - listname = args[0] - - # Sanity check - if opts.inputfile and opts.outputfile: - parser.error(_('Only one of -i or -o is allowed')) - if not opts.inputfile and not opts.outputfile: - parser.error(_('One of -i or -o is required')) - - if opts.outputfile: - do_output(listname, opts.outputfile, parser) - else: - do_input(listname, opts.inputfile, opts.checkonly, - opts.verbose, parser) - - - -if __name__ == '__main__': - main() diff --git a/mailman/bin/create_list.py b/mailman/bin/create_list.py deleted file mode 100644 index 8058a7d67..000000000 --- a/mailman/bin/create_list.py +++ /dev/null @@ -1,129 +0,0 @@ -# Copyright (C) 1998-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -import sys - -from mailman import Message -from mailman import Utils -from mailman import i18n -from mailman.app.lifecycle import create_list -from mailman.config import config -from mailman.core import errors -from mailman.interfaces.listmanager import ListAlreadyExistsError -from mailman.options import SingleMailingListOptions - - -_ = i18n._ - - - -class ScriptOptions(SingleMailingListOptions): - usage = _("""\ -%prog [options] - -Create a new mailing list. - -fqdn_listname is the 'fully qualified list name', basically the posting -address of the list. It must be a valid email address and the domain must be -registered with Mailman. - -Note that listnames are forced to lowercase.""") - - def add_options(self): - super(ScriptOptions, self).add_options() - self.parser.add_option( - '--language', - type='unicode', action='store', - help=_("""\ -Make the list's preferred language LANGUAGE, which must be a two letter -language code.""")) - self.parser.add_option( - '-o', '--owner', - type='unicode', action='append', default=[], - dest='owners', help=_("""\ -Specific a listowner email address. If the address is not currently -registered with Mailman, the address is registered and linked to a user. -Mailman will send a confirmation message to the address, but it will also send -a list creation notice to the address. More than one owner can be -specified.""")) - self.parser.add_option( - '-q', '--quiet', - default=False, action='store_true', - help=_("""\ -Normally the administrator is notified by email (after a prompt) that their -list has been created. This option suppresses the prompt and -notification.""")) - self.parser.add_option( - '-a', '--automate', - default=False, action='store_true', - help=_("""\ -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 at least one list -owner is specified with the -o option..""")) - - def sanity_check(self): - """Set up some defaults we couldn't set up earlier.""" - if self.options.language is None: - self.options.language = unicode(config.mailman.default_language) - # Is the language known? - if self.options.language not in config.languages.enabled_codes: - self.parser.error(_('Unknown language: $opts.language')) - # Handle variable number of positional arguments - if len(self.arguments) > 0: - parser.error(_('Unexpected arguments')) - - - -def main(): - options = ScriptOptions() - options.initialize() - - # Create the mailing list, applying styles as appropriate. - fqdn_listname = options.options.listname - if fqdn_listname is None: - options.parser.error(_('--listname is required')) - try: - mlist = create_list(fqdn_listname, options.options.owners) - mlist.preferred_language = options.options.language - except errors.InvalidEmailAddress: - options.parser.error(_('Illegal list name: $fqdn_listname')) - except ListAlreadyExistsError: - options.parser.error(_('List already exists: $fqdn_listname')) - except errors.BadDomainSpecificationError, domain: - options.parser.error(_('Undefined domain: $domain')) - - config.db.commit() - - if not options.options.quiet: - d = dict( - listname = mlist.fqdn_listname, - admin_url = mlist.script_url('admin'), - listinfo_url = mlist.script_url('listinfo'), - requestaddr = mlist.request_address, - siteowner = mlist.no_reply_address, - ) - text = Utils.maketext('newlist.txt', d, mlist=mlist) - # Set the I18N language to the list's preferred language so the header - # will match the template language. Stashing and restoring the old - # translation context is just (healthy? :) paranoia. - with i18n.using_language(mlist.preferred_language): - msg = Message.UserNotification( - owner_mail, mlist.no_reply_address, - _('Your new mailing list: $fqdn_listname'), - text, mlist.preferred_language) - msg.send(mlist) diff --git a/mailman/bin/disabled.py b/mailman/bin/disabled.py deleted file mode 100644 index cc8eb2c69..000000000 --- a/mailman/bin/disabled.py +++ /dev/null @@ -1,201 +0,0 @@ -# Copyright (C) 2001-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -import time -import logging -import optparse - -from mailman import errors -from mailman import MailList -from mailman import MemberAdaptor -from mailman import Pending -from mailman import loginit -from mailman.Bouncer import _BounceInfo -from mailman.configuration import config -from mailman.i18n import _ -from mailman.version import MAILMAN_VERSION - - -# Work around known problems with some RedHat cron daemons -import signal -signal.signal(signal.SIGCHLD, signal.SIG_DFL) - -ALL = (MemberAdaptor.BYBOUNCE, - MemberAdaptor.BYADMIN, - MemberAdaptor.BYUSER, - MemberAdaptor.UNKNOWN, - ) - - - -def who_callback(option, opt, value, parser): - dest = getattr(parser.values, option.dest) - if opt in ('-o', '--byadmin'): - dest.add(MemberAdaptor.BYADMIN) - elif opt in ('-m', '--byuser'): - dest.add(MemberAdaptor.BYUSER) - elif opt in ('-u', '--unknown'): - dest.add(MemberAdaptor.UNKNOWN) - elif opt in ('-b', '--notbybounce'): - dest.discard(MemberAdaptor.BYBOUNCE) - elif opt in ('-a', '--all'): - dest.update(ALL) - - -def parseargs(): - parser = optparse.OptionParser(version=MAILMAN_VERSION, - usage=_("""\ -%prog [options] - -Process disabled members, recommended once per day. - -This script iterates through every mailing list looking for members whose -delivery is disabled. If they have been disabled due to bounces, they will -receive another notification, or they may be removed if they've received the -maximum number of notifications. - -Use the --byadmin, --byuser, and --unknown flags to also send notifications to -members whose accounts have been disabled for those reasons. Use --all to -send the notification to all disabled members.""")) - # This is the set of working flags for who to send notifications to. By - # default, we notify anybody who has been disable due to bounces. - parser.set_defaults(who=set([MemberAdaptor.BYBOUNCE])) - parser.add_option('-o', '--byadmin', - callback=who_callback, action='callback', dest='who', - help=_("""\ -Also send notifications to any member disabled by the list -owner/administrator.""")) - parser.add_option('-m', '--byuser', - callback=who_callback, action='callback', dest='who', - help=_("""\ -Also send notifications to any member who has disabled themself.""")) - parser.add_option('-u', '--unknown', - callback=who_callback, action='callback', dest='who', - help=_("""\ -Also send notifications to any member disabled for unknown reasons -(usually a legacy disabled address).""")) - parser.add_option('-b', '--notbybounce', - callback=who_callback, action='callback', dest='who', - help=_("""\ -Don't send notifications to members disabled because of bounces (the -default is to notify bounce disabled members).""")) - parser.add_option('-a', '--all', - callback=who_callback, action='callback', dest='who', - help=_('Send notifications to all disabled members')) - parser.add_option('-f', '--force', - default=False, action='store_true', - help=_("""\ -Send notifications to disabled members even if they're not due a new -notification yet.""")) - parser.add_option('-l', '--listname', - dest='listnames', action='append', default=[], - type='string', help=_("""\ -Process only the given list, otherwise do all lists.""")) - parser.add_option('-C', '--config', - help=_('Alternative configuration file to use')) - opts, args = parser.parse_args() - return opts, args, parser - - - -def main(): - opts, args, parser = parseargs() - config.load(opts.config) - - loginit.initialize(propagate=True) - elog = logging.getLogger('mailman.error') - blog = logging.getLogger('mailman.bounce') - - listnames = set(opts.listnames or config.list_manager.names) - who = tuple(opts.who) - - msg = _('[disabled by periodic sweep and cull, no message available]') - today = time.mktime(time.localtime()[:3] + (0,) * 6) - for listname in listnames: - # List of members to notify - notify = [] - mlist = MailList.MailList(listname) - try: - interval = mlist.bounce_you_are_disabled_warnings_interval - # Find all the members who are currently bouncing and see if - # they've reached the disable threshold but haven't yet been - # disabled. This is a sweep through the membership catching - # situations where they've bounced a bunch, then the list admin - # lowered the threshold, but we haven't (yet) seen more bounces - # from the member. Note: we won't worry about stale information - # or anything else since the normal bounce processing code will - # handle that. - disables = [] - for member in mlist.getBouncingMembers(): - if mlist.getDeliveryStatus(member) <> MemberAdaptor.ENABLED: - continue - info = mlist.getBounceInfo(member) - if info.score >= mlist.bounce_score_threshold: - disables.append((member, info)) - if disables: - for member, info in disables: - mlist.disableBouncingMember(member, info, msg) - # Go through all the members who have delivery disabled, and find - # those that are due to have another notification. If they are - # disabled for another reason than bouncing, and we're processing - # them (because of the command line switch) then they won't have a - # bounce info record. We can piggyback on that for all disable - # purposes. - members = mlist.getDeliveryStatusMembers(who) - for member in members: - info = mlist.getBounceInfo(member) - if not info: - # See if they are bounce disabled, or disabled for some - # other reason. - status = mlist.getDeliveryStatus(member) - if status == MemberAdaptor.BYBOUNCE: - elog.error( - '%s disabled BYBOUNCE lacks bounce info, list: %s', - member, mlist.internal_name()) - continue - info = _BounceInfo( - member, 0, today, - mlist.bounce_you_are_disabled_warnings, - mlist.pend_new(Pending.RE_ENABLE, - mlist.internal_name(), - member)) - mlist.setBounceInfo(member, info) - lastnotice = time.mktime(info.lastnotice + (0,) * 6) - if opts.force or today >= lastnotice + interval: - notify.append(member) - # Now, send notifications to anyone who is due - for member in notify: - blog.info('Notifying disabled member %s for list: %s', - member, mlist.internal_name()) - try: - mlist.sendNextNotification(member) - except errors.NotAMemberError: - # There must have been some problem with the data we have - # on this member. Most likely it's that they don't have a - # password assigned. Log this and delete the member. - blog.info( - 'NotAMemberError when sending disabled notice: %s', - member) - mlist.ApprovedDeleteMember(member, 'cron/disabled') - mlist.Save() - finally: - mlist.Unlock() - - - -if __name__ == '__main__': - main() diff --git a/mailman/bin/docs/master.txt b/mailman/bin/docs/master.txt deleted file mode 100644 index 0d3cade77..000000000 --- a/mailman/bin/docs/master.txt +++ /dev/null @@ -1,49 +0,0 @@ -Mailman queue runner control -============================ - -Mailman has a number of queue runners which process messages in its queue file -directories. In normal operation, a command line script called 'mailmanctl' -is used to start, stop and manage the queue runners. mailmanctl actually is -just a wrapper around the real queue runner watcher script called master.py. - - >>> from mailman.testing.helpers import TestableMaster - -Start the master in a subthread. - - >>> master = TestableMaster() - >>> master.start() - -There should be a process id for every qrunner that claims to be startable. - - >>> from lazr.config import as_boolean - >>> startable_qrunners = [qconf for qconf in config.qrunner_configs - ... if as_boolean(qconf.start)] - >>> len(list(master.qrunner_pids)) == len(startable_qrunners) - True - -Now verify that all the qrunners are running. - - >>> import os - - # This should produce no output. - >>> for pid in master.qrunner_pids: - ... os.kill(pid, 0) - -Stop the master process, which should also kill (and not restart) the child -queue runner processes. - - >>> master.stop() - -None of the children are running now. - - >>> import errno - >>> for pid in master.qrunner_pids: - ... try: - ... os.kill(pid, 0) - ... print 'Process did not exit:', pid - ... except OSError, error: - ... if error.errno == errno.ESRCH: - ... # The child process exited. - ... pass - ... else: - ... raise diff --git a/mailman/bin/dumpdb.py b/mailman/bin/dumpdb.py deleted file mode 100644 index 6657602e4..000000000 --- a/mailman/bin/dumpdb.py +++ /dev/null @@ -1,88 +0,0 @@ -# Copyright (C) 1998-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -import pprint -import cPickle - -from mailman.config import config -from mailman.i18n import _ -from mailman.interact import interact -from mailman.options import Options - - -COMMASPACE = ', ' -m = [] - - - -class ScriptOptions(Options): - usage=_("""\ -%prog [options] filename - -Dump the contents of any Mailman queue file. The queue file is a data file -with multiple Python pickles in it.""") - - def add_options(self): - super(ScriptOptions, self).add_options() - self.parser.add_option( - '-n', '--noprint', - dest='doprint', default=True, action='store_false', - help=_("""\ -Don't attempt to pretty print the object. This is useful if there is some -problem with the object and you just want to get an unpickled representation. -Useful with 'bin/dumpdb -i <file>'. In that case, the list of -unpickled objects will be left in a variable called 'm'.""")) - self.parser.add_option( - '-i', '--interact', - default=False, action='store_true', - help=_("""\ -Start an interactive Python session, with a variable called 'm' containing the -list of unpickled objects.""")) - - def sanity_check(self): - if len(self.arguments) < 1: - self.parser.error(_('No filename given.')) - elif len(self.arguments) > 1: - self.parser.error(_('Unexpected arguments')) - else: - self.filename = self.arguments[0] - - - -def main(): - options = ScriptOptions() - options.initialize() - - pp = pprint.PrettyPrinter(indent=4) - with open(options.filename) as fp: - while True: - try: - m.append(cPickle.load(fp)) - except EOFError: - break - if options.options.doprint: - print _('[----- start pickle -----]') - for i, obj in enumerate(m): - count = i + 1 - print _('<----- start object $count ----->') - if isinstance(obj, basestring): - print obj - else: - pp.pprint(obj) - print _('[----- end pickle -----]') - if options.options.interact: - interact() diff --git a/mailman/bin/export.py b/mailman/bin/export.py deleted file mode 100644 index d1992b4b4..000000000 --- a/mailman/bin/export.py +++ /dev/null @@ -1,310 +0,0 @@ -# Copyright (C) 2006-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -"""Export an XML representation of a mailing list.""" - -import sys -import codecs -import datetime -import optparse - -from xml.sax.saxutils import escape - -from mailman import Defaults -from mailman import errors -from mailman import MemberAdaptor -from mailman.MailList import MailList -from mailman.configuration import config -from mailman.i18n import _ -from mailman.initialize import initialize -from mailman.version import MAILMAN_VERSION - - -SPACE = ' ' - -TYPES = { - Defaults.Toggle : 'bool', - Defaults.Radio : 'radio', - Defaults.String : 'string', - Defaults.Text : 'text', - Defaults.Email : 'email', - Defaults.EmailList : 'email_list', - Defaults.Host : 'host', - Defaults.Number : 'number', - Defaults.FileUpload : 'upload', - Defaults.Select : 'select', - Defaults.Topics : 'topics', - Defaults.Checkbox : 'checkbox', - Defaults.EmailListEx : 'email_list_ex', - Defaults.HeaderFilter : 'header_filter', - } - - - -class Indenter: - def __init__(self, fp, indentwidth=4): - self._fp = fp - self._indent = 0 - self._width = indentwidth - - def indent(self): - self._indent += 1 - - def dedent(self): - self._indent -= 1 - assert self._indent >= 0 - - def write(self, s): - if s <> '\n': - self._fp.write(self._indent * self._width * ' ') - self._fp.write(s) - - - -class XMLDumper(object): - def __init__(self, fp): - self._fp = Indenter(fp) - self._tagbuffer = None - self._stack = [] - - def _makeattrs(self, tagattrs): - # The attribute values might contain angle brackets. They might also - # be None. - attrs = [] - for k, v in tagattrs.items(): - if v is None: - v = '' - else: - v = escape(str(v)) - attrs.append('%s="%s"' % (k, v)) - return SPACE.join(attrs) - - def _flush(self, more=True): - if not self._tagbuffer: - return - name, attributes = self._tagbuffer - self._tagbuffer = None - if attributes: - attrstr = ' ' + self._makeattrs(attributes) - else: - attrstr = '' - if more: - print >> self._fp, '<%s%s>' % (name, attrstr) - self._fp.indent() - self._stack.append(name) - else: - print >> self._fp, '<%s%s/>' % (name, attrstr) - - # Use this method when you know you have sub-elements. - def _push_element(self, _name, **_tagattrs): - self._flush() - self._tagbuffer = (_name, _tagattrs) - - def _pop_element(self, _name): - buffered = bool(self._tagbuffer) - self._flush(more=False) - if not buffered: - name = self._stack.pop() - assert name == _name, 'got: %s, expected: %s' % (_name, name) - self._fp.dedent() - print >> self._fp, '</%s>' % name - - # Use this method when you do not have sub-elements - def _element(self, _name, _value=None, **_attributes): - self._flush() - if _attributes: - attrs = ' ' + self._makeattrs(_attributes) - else: - attrs = '' - if _value is None: - print >> self._fp, '<%s%s/>' % (_name, attrs) - else: - # The value might contain angle brackets. - value = escape(unicode(_value)) - print >> self._fp, '<%s%s>%s</%s>' % (_name, attrs, value, _name) - - def _do_list_categories(self, mlist, k, subcat=None): - info = mlist.GetConfigInfo(k, subcat) - label, gui = mlist.GetConfigCategories()[k] - if info is None: - return - for data in info[1:]: - if not isinstance(data, tuple): - continue - varname = data[0] - # Variable could be volatile - if varname.startswith('_'): - continue - vtype = data[1] - # Munge the value based on its type - value = None - if hasattr(gui, 'getValue'): - value = gui.getValue(mlist, vtype, varname, data[2]) - if value is None: - value = getattr(mlist, varname) - widget_type = TYPES[vtype] - if isinstance(value, list): - self._push_element('option', name=varname, type=widget_type) - for v in value: - self._element('value', v) - self._pop_element('option') - else: - self._element('option', value, name=varname, type=widget_type) - - def _dump_list(self, mlist): - # Write list configuration values - self._push_element('list', name=mlist.fqdn_listname) - self._push_element('configuration') - self._element('option', - mlist.preferred_language, - name='preferred_language') - for k in config.ADMIN_CATEGORIES: - subcats = mlist.GetConfigSubCategories(k) - if subcats is None: - self._do_list_categories(mlist, k) - else: - for subcat in [t[0] for t in subcats]: - self._do_list_categories(mlist, k, subcat) - self._pop_element('configuration') - # Write membership - self._push_element('roster') - digesters = set(mlist.getDigestMemberKeys()) - for member in sorted(mlist.getMembers()): - attrs = dict(id=member) - cased = mlist.getMemberCPAddress(member) - if cased <> member: - attrs['original'] = cased - self._push_element('member', **attrs) - self._element('realname', mlist.getMemberName(member)) - self._element('password', mlist.getMemberPassword(member)) - self._element('language', mlist.getMemberLanguage(member)) - # Delivery status, combined with the type of delivery - attrs = {} - status = mlist.getDeliveryStatus(member) - if status == MemberAdaptor.ENABLED: - attrs['status'] = 'enabled' - else: - attrs['status'] = 'disabled' - attrs['reason'] = {MemberAdaptor.BYUSER : 'byuser', - MemberAdaptor.BYADMIN : 'byadmin', - MemberAdaptor.BYBOUNCE : 'bybounce', - }.get(mlist.getDeliveryStatus(member), - 'unknown') - if member in digesters: - if mlist.getMemberOption(member, Defaults.DisableMime): - attrs['delivery'] = 'plain' - else: - attrs['delivery'] = 'mime' - else: - attrs['delivery'] = 'regular' - changed = mlist.getDeliveryStatusChangeTime(member) - if changed: - when = datetime.datetime.fromtimestamp(changed) - attrs['changed'] = when.isoformat() - self._element('delivery', **attrs) - for option, flag in Defaults.OPTINFO.items(): - # Digest/Regular delivery flag must be handled separately - if option in ('digest', 'plain'): - continue - value = mlist.getMemberOption(member, flag) - self._element(option, value) - topics = mlist.getMemberTopics(member) - if not topics: - self._element('topics') - else: - self._push_element('topics') - for topic in topics: - self._element('topic', topic) - self._pop_element('topics') - self._pop_element('member') - self._pop_element('roster') - self._pop_element('list') - - def dump(self, listnames): - print >> self._fp, '<?xml version="1.0" encoding="UTF-8"?>' - self._push_element('mailman', **{ - 'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance', - 'xsi:noNamespaceSchemaLocation': 'ssi-1.0.xsd', - }) - for listname in sorted(listnames): - try: - mlist = MailList(listname, lock=False) - except errors.MMUnknownListError: - print >> sys.stderr, _('No such list: $listname') - continue - self._dump_list(mlist) - self._pop_element('mailman') - - def close(self): - while self._stack: - self._pop_element() - - - -def parseargs(): - parser = optparse.OptionParser(version=MAILMAN_VERSION, - usage=_("""\ -%prog [options] - -Export the configuration and members of a mailing list in XML format.""")) - parser.add_option('-o', '--outputfile', - metavar='FILENAME', default=None, type='string', - help=_("""\ -Output XML to FILENAME. If not given, or if FILENAME is '-', standard out is -used.""")) - parser.add_option('-l', '--listname', - default=[], action='append', type='string', - metavar='LISTNAME', dest='listnames', help=_("""\ -The list to include in the output. If not given, then all mailing lists are -included in the XML output. Multiple -l flags may be given.""")) - parser.add_option('-C', '--config', - help=_('Alternative configuration file to use')) - opts, args = parser.parse_args() - if args: - parser.print_help() - parser.error(_('Unexpected arguments')) - return parser, opts, args - - - -def main(): - parser, opts, args = parseargs() - initialize(opts.config) - - close = False - if opts.outputfile in (None, '-'): - writer = codecs.getwriter('utf-8') - fp = writer(sys.stdout) - else: - fp = codecs.open(opts.outputfile, 'w', 'utf-8') - close = True - - try: - dumper = XMLDumper(fp) - if opts.listnames: - listnames = [] - for listname in opts.listnames: - if '@' not in listname: - listname = '%s@%s' % (listname, config.DEFAULT_EMAIL_HOST) - listnames.append(listname) - else: - listnames = config.list_manager.names - dumper.dump(listnames) - dumper.close() - finally: - if close: - fp.close() diff --git a/mailman/bin/find_member.py b/mailman/bin/find_member.py deleted file mode 100644 index 0982724a0..000000000 --- a/mailman/bin/find_member.py +++ /dev/null @@ -1,135 +0,0 @@ -# Copyright (C) 1998-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -import re -import sys -import optparse - -from mailman import errors -from mailman import MailList -from mailman.configuration import config -from mailman.i18n import _ -from mailman.version import MAILMAN_VERSION - - -AS_MEMBER = 0x01 -AS_OWNER = 0x02 - - - -def parseargs(): - parser = optparse.OptionParser(version=MAILMAN_VERSION, - usage=_("""\ -%prog [options] regex [regex ...] - -Find all lists that a member's address is on. - -The interaction between -l and -x (see below) is as follows. If any -l option -is given then only the named list will be included in the search. If any -x -option is given but no -l option is given, then all lists will be search -except those specifically excluded. - -Regular expression syntax uses the Python 're' module. Complete -specifications are at: - -http://www.python.org/doc/current/lib/module-re.html - -Address matches are case-insensitive, but case-preserved addresses are -displayed.""")) - parser.add_option('-l', '--listname', - type='string', default=[], action='append', - dest='listnames', - help=_('Include only the named list in the search')) - parser.add_option('-x', '--exclude', - type='string', default=[], action='append', - dest='excludes', - help=_('Exclude the named list from the search')) - parser.add_option('-w', '--owners', - default=False, action='store_true', - help=_('Search list owners as well as members')) - 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, _('Search regular expression required') - sys.exit(1) - return parser, opts, args - - - -def main(): - parser, opts, args = parseargs() - config.load(opts.config) - - listnames = opts.listnames or config.list_manager.names - includes = set(listname.lower() for listname in listnames) - excludes = set(listname.lower() for listname in opts.excludes) - listnames = includes - excludes - - if not listnames: - print _('No lists to search') - return - - cres = [] - for r in args: - cres.append(re.compile(r, re.IGNORECASE)) - # dictionary of {address, (listname, ownerp)} - matches = {} - for listname in listnames: - try: - mlist = MailList.MailList(listname, lock=False) - except errors.MMListError: - print _('No such list: $listname') - continue - if opts.owners: - owners = mlist.owner - else: - owners = [] - for cre in cres: - for member in mlist.getMembers(): - if cre.search(member): - addr = mlist.getMemberCPAddress(member) - entries = matches.get(addr, {}) - aswhat = entries.get(listname, 0) - aswhat |= AS_MEMBER - entries[listname] = aswhat - matches[addr] = entries - for owner in owners: - if cre.search(owner): - entries = matches.get(owner, {}) - aswhat = entries.get(listname, 0) - aswhat |= AS_OWNER - entries[listname] = aswhat - matches[owner] = entries - addrs = matches.keys() - addrs.sort() - for k in addrs: - hits = matches[k] - lists = hits.keys() - print k, _('found in:') - for name in lists: - aswhat = hits[name] - if aswhat & AS_MEMBER: - print ' ', name - if aswhat & AS_OWNER: - print ' ', name, _('(as owner)') - - - -if __name__ == '__main__': - main() diff --git a/mailman/bin/gate_news.py b/mailman/bin/gate_news.py deleted file mode 100644 index eac30422d..000000000 --- a/mailman/bin/gate_news.py +++ /dev/null @@ -1,243 +0,0 @@ -# Copyright (C) 1998-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -import os -import sys -import time -import socket -import logging -import nntplib -import optparse -import email.Errors - -from email.Parser import Parser -from locknix import lockfile - -from mailman import MailList -from mailman import Message -from mailman import Utils -from mailman import loginit -from mailman.configuration import config -from mailman.i18n import _ -from mailman.queue import Switchboard -from mailman.version import MAILMAN_VERSION - -# Work around known problems with some RedHat cron daemons -import signal -signal.signal(signal.SIGCHLD, signal.SIG_DFL) - -NL = '\n' - -log = None - -class _ContinueLoop(Exception): - pass - - - -def parseargs(): - parser = optparse.OptionParser(version=MAILMAN_VERSION, - usage=_("""\ -%prog [options] - -Poll the NNTP servers for messages to be gatewayed to mailing lists.""")) - 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 opts, args, parser - - - -_hostcache = {} - -def open_newsgroup(mlist): - # Split host:port if given - nntp_host, nntp_port = Utils.nntpsplit(mlist.nntp_host) - # Open up a "mode reader" connection to nntp server. This will be shared - # for all the gated lists having the same nntp_host. - conn = _hostcache.get(mlist.nntp_host) - if conn is None: - try: - conn = nntplib.NNTP(nntp_host, nntp_port, - readermode=True, - user=config.NNTP_USERNAME, - password=config.NNTP_PASSWORD) - except (socket.error, nntplib.NNTPError, IOError), e: - log.error('error opening connection to nntp_host: %s\n%s', - mlist.nntp_host, e) - raise - _hostcache[mlist.nntp_host] = conn - # Get the GROUP information for the list, but we're only really interested - # in the first article number and the last article number - r, c, f, l, n = conn.group(mlist.linked_newsgroup) - return conn, int(f), int(l) - - -def clearcache(): - for conn in set(_hostcache.values()): - conn.quit() - _hostcache.clear() - - - -# This function requires the list to be locked. -def poll_newsgroup(mlist, conn, first, last, glock): - listname = mlist.internal_name() - # NEWNEWS is not portable and has synchronization issues. - for num in range(first, last): - glock.refresh() - try: - headers = conn.head(repr(num))[3] - found_to = False - beenthere = False - for header in headers: - i = header.find(':') - value = header[:i].lower() - if i > 0 and value == 'to': - found_to = True - if value <> 'x-beenthere': - continue - if header[i:] == ': %s' % mlist.posting_address: - beenthere = True - break - if not beenthere: - body = conn.body(repr(num))[3] - # Usenet originated messages will not have a Unix envelope - # (i.e. "From " header). This breaks Pipermail archiving, so - # we will synthesize one. Be sure to use the format searched - # for by mailbox.UnixMailbox._isrealfromline(). BAW: We use - # the -bounces address here in case any downstream clients use - # the envelope sender for bounces; I'm not sure about this, - # but it's the closest to the old semantics. - lines = ['From %s %s' % (mlist.GetBouncesEmail(), - time.ctime(time.time()))] - lines.extend(headers) - lines.append('') - lines.extend(body) - lines.append('') - p = Parser(Message.Message) - try: - msg = p.parsestr(NL.join(lines)) - except email.Errors.MessageError, e: - log.error('email package exception for %s:%d\n%s', - mlist.linked_newsgroup, num, e) - raise _ContinueLoop - if found_to: - del msg['X-Originally-To'] - msg['X-Originally-To'] = msg['To'] - del msg['To'] - msg['To'] = mlist.posting_address - # Post the message to the locked list - inq = Switchboard(config.INQUEUE_DIR) - inq.enqueue(msg, - listname=mlist.internal_name(), - fromusenet=True) - log.info('posted to list %s: %7d', listname, num) - except nntplib.NNTPError, e: - log.exception('NNTP error for list %s: %7d', listname, num) - except _ContinueLoop: - continue - # Even if we don't post the message because it was seen on the - # list already, update the watermark - mlist.usenet_watermark = num - - - -def process_lists(glock): - for listname in config.list_manager.names: - glock.refresh() - # Open the list unlocked just to check to see if it is gating news to - # mail. If not, we're done with the list. Otherwise, lock the list - # and gate the group. - mlist = MailList.MailList(listname, lock=False) - if not mlist.gateway_to_mail: - continue - # Get the list's watermark, i.e. the last article number that we gated - # from news to mail. None means that this list has never polled its - # newsgroup and that we should do a catch up. - watermark = getattr(mlist, 'usenet_watermark', None) - # Open the newsgroup, but let most exceptions percolate up. - try: - conn, first, last = open_newsgroup(mlist) - except (socket.error, nntplib.NNTPError): - break - log.info('%s: [%d..%d]', listname, first, last) - try: - try: - if watermark is None: - mlist.Lock(timeout=config.LIST_LOCK_TIMEOUT) - # This is the first time we've tried to gate this - # newsgroup. We essentially do a mass catch-up, otherwise - # we'd flood the mailing list. - mlist.usenet_watermark = last - log.info('%s caught up to article %d', listname, last) - else: - # The list has been polled previously, so now we simply - # grab all the messages on the newsgroup that have not - # been seen by the mailing list. The first such article - # is the maximum of the lowest article available in the - # newsgroup and the watermark. It's possible that some - # articles have been expired since the last time gate_news - # has run. Not much we can do about that. - start = max(watermark + 1, first) - if start > last: - log.info('nothing new for list %s', listname) - else: - mlist.Lock(timeout=config.LIST_LOCK_TIMEOUT) - log.info('gating %s articles [%d..%d]', - listname, start, last) - # Use last+1 because poll_newsgroup() employes a for - # loop over range, and this will not include the last - # element in the list. - poll_newsgroup(mlist, conn, start, last + 1, glock) - except lockfile.TimeOutError: - log.error('Could not acquire list lock: %s', listname) - finally: - if mlist.Locked(): - mlist.Save() - mlist.Unlock() - log.info('%s watermark: %d', listname, mlist.usenet_watermark) - - - -def main(): - opts, args, parser = parseargs() - config.load(opts.config) - - GATENEWS_LOCK_FILE = os.path.join(config.LOCK_DIR, 'gate_news.lock') - LOCK_LIFETIME = config.hours(2) - - loginit.initialize(propagate=True) - log = logging.getLogger('mailman.fromusenet') - - try: - with lockfile.Lock(GATENEWS_LOCK_FILE, - # It's okay to hijack this - lifetime=LOCK_LIFETIME) as lock: - process_lists(lock) - clearcache() - except lockfile.TimeOutError: - log.error('Could not acquire gate_news lock') - - - -if __name__ == '__main__': - main() diff --git a/mailman/bin/genaliases.py b/mailman/bin/genaliases.py deleted file mode 100644 index e8916d030..000000000 --- a/mailman/bin/genaliases.py +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright (C) 2001-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -__metaclass__ = type -__all__ = [ - 'main', - ] - - -import sys - -from mailman.config import config -from mailman.i18n import _ -from mailman.options import Options - - - -class ScriptOptions(Options): - """Options for the genaliases script.""" - - usage = _("""\ -%prog [options] - -Regenerate the Mailman specific MTA aliases from scratch. The actual output -depends on the value of the 'MTA' variable in your etc/mailman.cfg file.""") - - def add_options(self): - super(ScriptOptions, self).add_options() - self.parser.add_option( - '-q', '--quiet', - default=False, action='store_true', help=_("""\ -Some MTA output can include more verbose help text. Use this to tone down the -verbosity.""")) - - - - -def main(): - options = ScriptOptions() - options.initialize() - - # Get the MTA-specific module. - module_path, class_path = config.mta.incoming.rsplit('.', 1) - __import__(module_path) - getattr(sys.modules[module_path], class_path)().regenerate() - - - -if __name__ == '__main__': - main() diff --git a/mailman/bin/import.py b/mailman/bin/import.py deleted file mode 100644 index d2361e808..000000000 --- a/mailman/bin/import.py +++ /dev/null @@ -1,315 +0,0 @@ -# Copyright (C) 2006-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -"""Import the XML representation of a mailing list.""" - -import sys -import codecs -import optparse -import traceback - -from xml.dom import minidom -from xml.parsers.expat import ExpatError - -from mailman import Defaults -from mailman import errors -from mailman import MemberAdaptor -from mailman import Utils -from mailman import passwords -from mailman.MailList import MailList -from mailman.i18n import _ -from mailman.initialize import initialize -from mailman.version import MAILMAN_VERSION - - -OPTS = None - - - -def nodetext(node): - # Expect only one TEXT_NODE in the list of children - for child in node.childNodes: - if child.nodeType == node.TEXT_NODE: - return child.data - return u'' - - -def nodegen(node, *elements): - for child in node.childNodes: - if child.nodeType <> minidom.Node.ELEMENT_NODE: - continue - if elements and child.tagName not in elements: - print _('Ignoring unexpected element: $node.tagName') - else: - yield child - - - -def parse_config(node): - config = dict() - for child in nodegen(node, 'option'): - name = child.getAttribute('name') - if not name: - print _('Skipping unnamed option') - continue - vtype = child.getAttribute('type') or 'string' - if vtype in ('email_list', 'email_list_ex', 'checkbox'): - value = [] - for subnode in nodegen(child): - value.append(nodetext(subnode)) - elif vtype == 'bool': - value = nodetext(child) - try: - value = bool(int(value)) - except ValueError: - value = {'true' : True, - 'false': False, - }.get(value.lower()) - if value is None: - print _('Skipping bad boolean value: $value') - continue - elif vtype == 'radio': - value = nodetext(child).lower() - boolval = {'true' : True, - 'false': False, - }.get(value) - if boolval is None: - value = int(value) - else: - value = boolval - elif vtype == 'number': - value = nodetext(child) - # First try int then float - try: - value = int(value) - except ValueError: - value = float(value) - elif vtype in ('header_filter', 'topics'): - value = [] - fakebltins = dict(__builtins__ = dict(True=True, False=False)) - for subnode in nodegen(child): - reprstr = nodetext(subnode) - # Turn the reprs back into tuples, in a safe way - tupleval = eval(reprstr, fakebltins) - value.append(tupleval) - else: - value = nodetext(child) - # And now some special casing :( - if name == 'new_member_options': - value = int(nodetext(child)) - config[name] = value - return config - - - - -def parse_roster(node): - members = [] - for child in nodegen(node, 'member'): - member = dict() - member['id'] = mid = child.getAttribute('id') - if not mid: - print _('Skipping member with no id') - continue - if OPTS.verbose: - print _('* Processing member: $mid') - for subnode in nodegen(child): - attr = subnode.tagName - if attr == 'delivery': - value = (subnode.getAttribute('status'), - subnode.getAttribute('delivery')) - elif attr in ('hide', 'ack', 'notmetoo', 'nodupes', 'nomail'): - value = {'true' : True, - 'false': False, - }.get(nodetext(subnode).lower(), False) - elif attr == 'topics': - value = [] - for subsubnode in nodegen(subnode): - value.append(nodetext(subsubnode)) - elif attr == 'password': - value = nodetext(subnode) - if OPTS.reset_passwords or value == '{NONE}' or not value: - value = passwords.make_secret(Utils.MakeRandomPassword()) - else: - value = nodetext(subnode) - member[attr] = value - members.append(member) - return members - - - -def load(fp): - try: - doc = minidom.parse(fp) - except ExpatError: - print _('Expat error in file: $fp.name') - traceback.print_exc() - sys.exit(1) - doc.normalize() - # Make sure there's only one top-level <mailman> node - gen = nodegen(doc, 'mailman') - top = gen.next() - try: - gen.next() - except StopIteration: - pass - else: - print _('Malformed XML; duplicate <mailman> nodes') - sys.exit(1) - all_listdata = [] - for listnode in nodegen(top, 'list'): - listdata = dict() - name = listnode.getAttribute('name') - if OPTS.verbose: - print _('Processing list: $name') - if not name: - print _('Ignoring malformed <list> node') - continue - for child in nodegen(listnode, 'configuration', 'roster'): - if child.tagName == 'configuration': - list_config = parse_config(child) - else: - assert(child.tagName == 'roster') - list_roster = parse_roster(child) - all_listdata.append((name, list_config, list_roster)) - return all_listdata - - - -def create(all_listdata): - for name, list_config, list_roster in all_listdata: - fqdn_listname = '%s@%s' % (name, list_config['host_name']) - if Utils.list_exists(fqdn_listname): - print _('Skipping already existing list: $fqdn_listname') - continue - mlist = MailList() - try: - if OPTS.verbose: - print _('Creating mailing list: $fqdn_listname') - mlist.Create(fqdn_listname, list_config['owner'][0], - list_config['password']) - except errors.BadDomainSpecificationError: - print _('List is not in a supported domain: $fqdn_listname') - continue - # Save the list creation, then unlock and relock the list. This is so - # that we use normal SQLAlchemy transactions to manage all the - # attribute and membership updates. Without this, no transaction will - # get committed in the second Save() below and we'll lose all our - # updates. - mlist.Save() - mlist.Unlock() - mlist.Lock() - try: - for option, value in list_config.items(): - # XXX Here's what sucks. Some properties need to have - # _setValue() called on the gui component, because those - # methods do some pre-processing on the values before they're - # applied to the MailList instance. But we don't have a good - # way to find a category and sub-category that a particular - # property belongs to. Plus this will probably change. So - # for now, we'll just hard code the extra post-processing - # here. The good news is that not all _setValue() munging - # needs to be done -- for example, we've already converted - # everything to dollar strings. - if option in ('filter_mime_types', 'pass_mime_types', - 'filter_filename_extensions', - 'pass_filename_extensions'): - value = value.splitlines() - if option == 'available_languages': - mlist.set_languages(*value) - else: - setattr(mlist, option, value) - for member in list_roster: - mid = member['id'] - if OPTS.verbose: - print _('* Adding member: $mid') - status, delivery = member['delivery'] - kws = {'password' : member['password'], - 'language' : member['language'], - 'realname' : member['realname'], - 'digest' : delivery <> 'regular', - } - mlist.addNewMember(mid, **kws) - status = {'enabled' : MemberAdaptor.ENABLED, - 'byuser' : MemberAdaptor.BYUSER, - 'byadmin' : MemberAdaptor.BYADMIN, - 'bybounce' : MemberAdaptor.BYBOUNCE, - }.get(status, MemberAdaptor.UNKNOWN) - mlist.setDeliveryStatus(mid, status) - for opt in ('hide', 'ack', 'notmetoo', 'nodupes', 'nomail'): - mlist.setMemberOption(mid, - Defaults.OPTINFO[opt], - member[opt]) - topics = member.get('topics') - if topics: - mlist.setMemberTopics(mid, topics) - mlist.Save() - finally: - mlist.Unlock() - - - -def parseargs(): - parser = optparse.OptionParser(version=MAILMAN_VERSION, - usage=_("""\ -%prog [options] - -Import the configuration and/or members of a mailing list in XML format. The -imported mailing list must not already exist. All mailing lists named in the -XML file are imported, but those that already exist are skipped unless --error -is given.""")) - parser.add_option('-i', '--inputfile', - metavar='FILENAME', default=None, type='string', - help=_("""\ -Input XML from FILENAME. If not given, or if FILENAME is '-', standard input -is used.""")) - parser.add_option('-p', '--reset-passwords', - default=False, action='store_true', help=_("""\ -With this option, user passwords in the XML are ignored and are reset to a -random password. If the generated passwords were not included in the input -XML, they will always be randomly generated.""")) - parser.add_option('-v', '--verbose', - default=False, action='store_true', - help=_('Produce more verbose output')) - parser.add_option('-C', '--config', - help=_('Alternative configuration file to use')) - opts, args = parser.parse_args() - if args: - parser.print_help() - parser.error(_('Unexpected arguments')) - return parser, opts, args - - - -def main(): - global OPTS - - parser, opts, args = parseargs() - initialize(opts.config) - OPTS = opts - - if opts.inputfile in (None, '-'): - fp = sys.stdin - else: - fp = open(opts.inputfile, 'r') - - try: - listbags = load(fp) - create(listbags) - finally: - if fp is not sys.stdin: - fp.close() diff --git a/mailman/bin/inject.py b/mailman/bin/inject.py deleted file mode 100644 index 2bc8a49e3..000000000 --- a/mailman/bin/inject.py +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright (C) 2002-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -import os -import sys - -from email import message_from_string - -from mailman import Utils -from mailman.Message import Message -from mailman.configuration import config -from mailman.i18n import _ -from mailman.inject import inject_text -from mailman.options import SingleMailingListOptions - - - -class ScriptOptions(SingleMailingListOptions): - usage=_("""\ -%prog [options] [filename] - -Inject a message from a file into Mailman's incoming queue. 'filename' is the -name of the plaintext message file to inject. If omitted, or the string '-', -standard input is used. -""") - - def add_options(self): - super(ScriptOptions, self).add_options() - self.parser.add_option( - '-q', '--queue', - type='string', help=_("""\ -The name of the queue to inject the message to. The queuename must be one of -the directories inside the qfiles directory. If omitted, the incoming queue -is used.""")) - - def sanity_check(self): - if not self.options.listname: - self.parser.error(_('Missing listname')) - if len(self.arguments) == 0: - self.filename = '-' - elif len(self.arguments) > 1: - self.parser.print_error(_('Unexpected arguments')) - else: - self.filename = self.arguments[0] - - - -def main(): - options = ScriptOptions() - options.initialize() - - if options.options.queue is None: - qdir = config.INQUEUE_DIR - else: - qdir = os.path.join(config.QUEUE_DIR, options.options.queue) - if not os.path.isdir(qdir): - options.parser.error(_('Bad queue directory: $qdir')) - - fqdn_listname = options.options.listname - mlist = config.db.list_manager.get(fqdn_listname) - if mlist is None: - options.parser.error(_('No such list: $fqdn_listname')) - - if options.filename == '-': - message_text = sys.stdin.read() - else: - with open(options.filename) as fp: - message_text = fp.read() - - inject_text(mlist, message_text, qdir=qdir) - - - -if __name__ == '__main__': - main() diff --git a/mailman/bin/list_lists.py b/mailman/bin/list_lists.py deleted file mode 100644 index ea1640910..000000000 --- a/mailman/bin/list_lists.py +++ /dev/null @@ -1,104 +0,0 @@ -# Copyright (C) 1998-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -from mailman.config import config -from mailman.i18n import _ -from mailman.options import Options - - - -class ScriptOptions(Options): - usage = _("""\ -%prog [options] - -List all mailing lists.""") - - def add_options(self): - super(ScriptOptions, self).add_options() - self.parser.add_option( - '-a', '--advertised', - default=False, action='store_true', - help=_("""\ -List only those mailing lists that are publicly advertised""")) - self.parser.add_option( - '-b', '--bare', - default=False, action='store_true', - help=_("""\ -Displays only the list name, with no description.""")) - self.parser.add_option( - '-d', '--domain', - default=[], type='string', action='append', - dest='domains', help=_("""\ -List only those mailing lists that match the given virtual domain, which may -be either the email host or the url host name. Multiple -d options may be -given.""")) - self.parser.add_option( - '-f', '--full', - default=False, action='store_true', - help=_("""\ -Print the full list name, including the posting address.""")) - - def sanity_check(self): - if len(self.arguments) > 0: - self.parser.error(_('Unexpected arguments')) - - - -def main(): - options = ScriptOptions() - options.initialize() - - mlists = [] - longest = 0 - - listmgr = config.db.list_manager - for fqdn_name in sorted(listmgr.names): - mlist = listmgr.get(fqdn_name) - if options.options.advertised and not mlist.advertised: - continue - if options.options.domains: - for domain in options.options.domains: - if domain in mlist.web_page_url or domain == mlist.host_name: - mlists.append(mlist) - break - else: - mlists.append(mlist) - if options.options.full: - name = mlist.fqdn_listname - else: - name = mlist.real_name - longest = max(len(name), longest) - - if not mlists and not options.options.bare: - print _('No matching mailing lists found') - return - - if not options.options.bare: - num_mlists = len(mlists) - print _('$num_mlists matching mailing lists found:') - - format = '%%%ds - %%.%ds' % (longest, 77 - longest) - for mlist in mlists: - if options.options.full: - name = mlist.fqdn_listname - else: - name = mlist.real_name - if options.options.bare: - print name - else: - description = mlist.description or _('[no description available]') - print ' ', format % (name, description) diff --git a/mailman/bin/list_members.py b/mailman/bin/list_members.py deleted file mode 100644 index 443f764d6..000000000 --- a/mailman/bin/list_members.py +++ /dev/null @@ -1,201 +0,0 @@ -# Copyright (C) 1998-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -import sys - -from email.Utils import formataddr - -from mailman import Utils -from mailman.config import config -from mailman.core import errors -from mailman.i18n import _ -from mailman.interfaces import DeliveryStatus -from mailman.options import SingleMailingListOptions - - -COMMASPACE = ', ' - -WHYCHOICES = { - 'enabled' : DeliveryStatus.enabled, - 'byuser' : DeliveryStatus.by_user, - 'byadmin' : DeliveryStatus.by_moderator, - 'bybounce': DeliveryStatus.by_bounces, - } - -KINDCHOICES = set(('mime', 'plain', 'any')) - - - -class ScriptOptions(SingleMailingListOptions): - usage = _("""\ -%prog [options] - -List all the members of a mailing list. Note that with the options below, if -neither -r or -d is supplied, regular members are printed first, followed by -digest members, but no indication is given as to address status. - -listname is the name of the mailing list to use.""") - - def add_options(self): - super(ScriptOptions, self).add_options() - self.parser.add_option( - '-o', '--output', - type='string', help=_("""\ -Write output to specified file instead of standard out.""")) - self.parser.add_option( - '-r', '--regular', - default=None, action='store_true', - help=_('Print just the regular (non-digest) members.')) - self.parser.add_option( - '-d', '--digest', - default=None, type='string', metavar='KIND', - help=_("""\ -Print just the digest members. KIND can be 'mime', 'plain', or -'any'. 'mime' prints just the members receiving MIME digests, while 'plain' -prints just the members receiving plain text digests. 'any' prints all -members receiving any kind of digest.""")) - self.parser.add_option( - '-n', '--nomail', - type='string', metavar='WHY', help=_("""\ -Print the members that have delivery disabled. WHY selects just the subset of -members with delivery disabled for a particular reason, where 'any' prints all -disabled members. 'byadmin', 'byuser', 'bybounce', and 'unknown' prints just -the users who are disabled for that particular reason. WHY can also be -'enabled' which prints just those members for whom delivery is enabled.""")) - self.parser.add_option( - '-f', '--fullnames', - default=False, action='store_true', - help=_('Include the full names in the output')) - self.parser.add_option( - '-i', '--invalid', - default=False, action='store_true', help=_("""\ -Print only the addresses in the membership list that are invalid. Ignores -r, --d, -n.""")) - - def sanity_check(self): - if not self.options.listname: - self.parser.error(_('Missing listname')) - if len(self.arguments) > 0: - self.parser.print_error(_('Unexpected arguments')) - if self.options.digest is not None: - self.options.kind = self.options.digest.lower() - if self.options.kind not in KINDCHOICES: - self.parser.error( - _('Invalid value for -d: $self.options.digest')) - if self.options.nomail is not None: - why = self.options.nomail.lower() - if why == 'any': - self.options.why = 'any' - elif why not in WHYCHOICES: - self.parser.error( - _('Invalid value for -n: $self.options.nomail')) - self.options.why = why - if self.options.regular is None and self.options.digest is None: - self.options.regular = self.options.digest = True - self.options.kind = 'any' - - - -def safe(string): - if not string: - return '' - return string.encode(sys.getdefaultencoding(), 'replace') - - -def isinvalid(addr): - try: - Utils.ValidateEmail(addr) - return False - except errors.EmailAddressError: - return True - - - -def whymatches(mlist, addr, why): - # Return true if the `why' matches the reason the address is enabled, or - # in the case of why is None, that they are disabled for any reason - # (i.e. not enabled). - status = mlist.getDeliveryStatus(addr) - if why in (None, 'any'): - return status <> DeliveryStatus.enabled - return status == WHYCHOICES[why] - - - -def main(): - options = ScriptOptions() - options.initialize() - - fqdn_listname = options.options.listname - if options.options.output: - try: - fp = open(options.output, 'w') - except IOError: - options.parser.error( - _('Could not open file for writing: $options.options.output')) - else: - fp = sys.stdout - - mlist = config.db.list_manager.get(fqdn_listname) - if mlist is None: - options.parser.error(_('No such list: $fqdn_listname')) - - # The regular delivery and digest members. - rmembers = set(mlist.regular_members.members) - dmembers = set(mlist.digest_members.members) - - fullnames = options.options.fullnames - if options.options.invalid: - all = sorted(member.address.address for member in rmembers + dmembers) - for address in all: - user = config.db.user_manager.get_user(address) - name = (user.real_name if fullnames and user else u'') - if options.options.invalid and isinvalid(address): - print >> fp, formataddr((safe(name), address)) - return - if options.options.regular: - for address in sorted(member.address.address for member in rmembers): - user = config.db.user_manager.get_user(address) - name = (user.real_name if fullnames and user else u'') - # Filter out nomails - if (options.options.nomail and - not whymatches(mlist, address, options.options.why)): - continue - print >> fp, formataddr((safe(name), address)) - if options.options.digest: - for address in sorted(member.address.address for member in dmembers): - user = config.db.user_manager.get_user(address) - name = (user.real_name if fullnames and user else u'') - # Filter out nomails - if (options.options.nomail and - not whymatches(mlist, address, options.options.why)): - continue - # Filter out digest kinds -## if mlist.getMemberOption(addr, config.DisableMime): -## # They're getting plain text digests -## if opts.kind == 'mime': -## continue -## else: -## # They're getting MIME digests -## if opts.kind == 'plain': -## continue - print >> fp, formataddr((safe(name), address)) - - - -if __name__ == '__main__': - main() diff --git a/mailman/bin/list_owners.py b/mailman/bin/list_owners.py deleted file mode 100644 index 953fb8941..000000000 --- a/mailman/bin/list_owners.py +++ /dev/null @@ -1,88 +0,0 @@ -# Copyright (C) 2002-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -import sys -import optparse - -from mailman.MailList import MailList -from mailman.configuration import config -from mailman.i18n import _ -from mailman.initialize import initialize -from mailman.version import MAILMAN_VERSION - - - -def parseargs(): - parser = optparse.OptionParser(version=MAILMAN_VERSION, - usage=_("""\ -%prog [options] [listname ...] - -List the owners of a mailing list, or all mailing lists if no list names are -given.""")) - parser.add_option('-w', '--with-listnames', - default=False, action='store_true', - help=_("""\ -Group the owners by list names and include the list names in the output. -Otherwise, the owners will be sorted and uniquified based on the email -address.""")) - parser.add_option('-m', '--moderators', - default=False, action='store_true', - help=_('Include the list moderators in the output.')) - parser.add_option('-C', '--config', - help=_('Alternative configuration file to use')) - opts, args = parser.parse_args() - return parser, opts, args - - - -def main(): - parser, opts, args = parseargs() - initialize(opts.config) - - listmgr = config.db.list_manager - listnames = set(args or listmgr.names) - bylist = {} - - for listname in listnames: - mlist = listmgr.get(listname) - addrs = [addr.address for addr in mlist.owners.addresses] - if opts.moderators: - addrs.extend([addr.address for addr in mlist.moderators.addresses]) - bylist[listname] = addrs - - if opts.with_listnames: - for listname in listnames: - unique = set() - for addr in bylist[listname]: - unique.add(addr) - keys = list(unique) - keys.sort() - print listname - for k in keys: - print '\t', k - else: - unique = set() - for listname in listnames: - for addr in bylist[listname]: - unique.add(addr) - for k in sorted(unique): - print k - - - -if __name__ == '__main__': - main() diff --git a/mailman/bin/mailmanctl.py b/mailman/bin/mailmanctl.py deleted file mode 100644 index 667a46a70..000000000 --- a/mailman/bin/mailmanctl.py +++ /dev/null @@ -1,232 +0,0 @@ -# Copyright (C) 2001-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -"""Mailman start/stop script.""" - -import os -import grp -import pwd -import sys -import errno -import signal -import logging - -from optparse import OptionParser - -from mailman.config import config -from mailman.core.initialize import initialize -from mailman.i18n import _ -from mailman.version import MAILMAN_VERSION - - -COMMASPACE = ', ' - -log = None -parser = None - - - -def parseargs(): - parser = OptionParser(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, SIGUSR1 and SIGHUP. SIGINT, SIGTERM -and SIGUSR1 all cause the qrunners to exit cleanly, but the master will only -restart qrunners that have exited due to a SIGUSR1. 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, SIGUSR1 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('-u', '--run-as-user', - 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('-f', '--force', - default=False, action='store_true', - help=_("""\ -If the master watcher finds an existing master lock, it will normally exit -with an error message. With this option,the master will perform an extra -level of checking. If a process matching the host/pid described in the lock -file is running, the master will still exit, requiring you to manually clean -up the lock. But if no matching process is found, the master 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')) - options, arguments = parser.parse_args() - if not arguments: - parser.error(_('No command given.')) - if len(arguments) > 1: - commands = COMMASPACE.join(arguments) - parser.error(_('Bad command: $commands')) - parser.options = options - parser.arguments = arguments - return parser - - - -def kill_watcher(sig): - try: - with open(config.PIDFILE) as f: - pid = int(f.read().strip()) - except (IOError, ValueError), e: - # For i18n convenience - print >> sys.stderr, _('PID unreadable in: $config.PIDFILE') - print >> sys.stderr, e - print >> sys.stderr, _('Is qrunner even running?') - return - try: - os.kill(pid, sig) - except OSError, error: - 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 check_privileges(): - # 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).gr_gid - uid = pwd.getpwnam(config.MAILMAN_USER).pw_uid - myuid = os.getuid() - if myuid == 0: - # Set the process's supplimental groups. - groups = [group.gr_gid for group in grp.getgrall() - if config.MAILMAN_USER in group.gr_mem] - groups.append(gid) - os.setgroups(groups) - os.setgid(gid) - os.setuid(uid) - elif myuid <> uid: - name = config.MAILMAN_USER - parser.error( - _('Run this program as root or as the $name user, or use -u.')) - - - -def main(): - global log, parser - - parser = parseargs() - initialize(parser.options.config) - - log = logging.getLogger('mailman.qrunner') - - if not parser.options.run_as_user: - check_privileges() - else: - if not parser.options.quiet: - print _('Warning! You may encounter permission problems.') - - # Handle the commands - command = parser.arguments[0].lower() - if command == 'stop': - if not parser.options.quiet: - print _("Shutting down Mailman's master qrunner") - kill_watcher(signal.SIGTERM) - elif command == 'restart': - if not parser.options.quiet: - print _("Restarting Mailman's master qrunner") - kill_watcher(signal.SIGUSR1) - elif command == 'reopen': - if not parser.options.quiet: - print _('Re-opening all log files') - kill_watcher(signal.SIGHUP) - elif command == 'start': - # Start the master qrunner watcher process. - # - # Daemon process startup according to Stevens, Advanced Programming in - # the UNIX Environment, Chapter 13. - pid = os.fork() - if pid: - # parent - if not parser.options.quiet: - print _("Starting Mailman's master qrunner.") - return - # child - # - # 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 runtime directory. - os.chdir(config.VAR_DIR) - # Exec the master watcher. - args = [sys.executable, sys.executable, - os.path.join(config.BIN_DIR, 'master')] - if parser.options.force: - args.append('--force') - if parser.options.config: - args.extend(['-C', parser.options.config]) - log.debug('starting: %s', args) - os.execl(*args) - # We should never get here. - raise RuntimeError('os.execl() failed') - - - -if __name__ == '__main__': - main() diff --git a/mailman/bin/master.py b/mailman/bin/master.py deleted file mode 100644 index d954bc865..000000000 --- a/mailman/bin/master.py +++ /dev/null @@ -1,452 +0,0 @@ -# Copyright (C) 2001-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -"""Master sub-process watcher.""" - -__metaclass__ = type -__all__ = [ - 'Loop', - 'get_lock_data', - ] - - -import os -import sys -import errno -import signal -import socket -import logging - -from datetime import timedelta -from lazr.config import as_boolean -from locknix import lockfile -from munepy import Enum - -from mailman.config import config -from mailman.core.logging import reopen -from mailman.i18n import _ -from mailman.options import Options - - -DOT = '.' -LOCK_LIFETIME = timedelta(days=1, hours=6) -SECONDS_IN_A_DAY = 86400 - - - -class ScriptOptions(Options): - """Options for the master watcher.""" - - usage = _("""\ -Master sub-process watcher. - -Start and watch the configured queue runners and ensure that they stay alive -and kicking. Each are fork and exec'd in turn, with the master waiting on -their process ids. When it detects a child queue runner has exited, it may -restart it. - -The queue runners respond to SIGINT, SIGTERM, SIGUSR1 and SIGHUP. SIGINT, -SIGTERM and SIGUSR1 all cause the qrunners to exit cleanly. The master will -restart qrunners that have exited due to a SIGUSR1 or some kind of other exit -condition (say because of an exception). 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, SIGUSR1 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. - -Usage: %prog [options]""") - - def add_options(self): - self.parser.add_option( - '-n', '--no-restart', - dest='restartable', default=True, action='store_false', - help=_("""\ -Don't restart the qrunners when they exit because of an error or a SIGUSR1. -Use this only for debugging.""")) - self.parser.add_option( - '-f', '--force', - default=False, action='store_true', - help=_("""\ -If the master watcher finds an existing master lock, it will normally exit -with an error message. With this option,the master will perform an extra -level of checking. If a process matching the host/pid described in the lock -file is running, the master will still exit, requiring you to manually clean -up the lock. But if no matching process is found, the master will remove the -apparently stale lock and make another attempt to claim the master lock.""")) - self.parser.add_option( - '-r', '--runner', - dest='runners', action='append', default=[], - help=_("""\ -Override the default set of queue runners that the master watch will invoke -instead of the default set. Multiple -r options may be given. The values for --r are passed straight through to bin/qrunner.""")) - - def sanity_check(self): - if len(self.arguments) > 0: - self.parser.error(_('Too many arguments')) - - - -def get_lock_data(): - """Get information from the master lock file. - - :return: A 3-tuple of the hostname, integer process id, and file name of - the lock file. - """ - with open(config.LOCK_FILE) as fp: - filename = os.path.split(fp.read().strip())[1] - parts = filename.split('.') - hostname = DOT.join(parts[1:-2]) - pid = int(parts[-2]) - return hostname, int(pid), filename - - -class WatcherState(Enum): - # Another master watcher is running. - conflict = 1 - # No conflicting process exists. - stale_lock = 2 - # Hostname from lock file doesn't match. - host_mismatch = 3 - - -def master_state(): - """Get the state of the master watcher. - - :return: WatcherState describing the state of the lock file. - """ - - # 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 WatcherState.host_mismatch - # Find out if the process exists by calling kill with a signal 0. - try: - os.kill(pid, 0) - return WatcherState.conflict - except OSError, e: - if e.errno == errno.ESRCH: - # No matching process id. - return WatcherState.stale_lock - # Some other error occurred. - raise - - -def acquire_lock_1(force): - """Try to acquire the master queue runner lock. - - :param force: Flag that controls whether to force acquisition of the lock. - :return: The master queue runner lock. - :raises: `TimeOutError` if the lock could not be acquired. - """ - lock = lockfile.Lock(config.LOCK_FILE, LOCK_LIFETIME) - try: - lock.lock(timedelta(seconds=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.LOCK_FILE) - os.unlink(os.path.join(config.LOCK_DIR, tempfile)) - return acquire_lock_1(force=False) - - -def acquire_lock(force): - """Acquire the master queue runner lock. - - :return: The master queue runner lock or None if the lock couldn't be - acquired. In that case, an error messages is also printed to standard - error. - """ - try: - lock = acquire_lock_1(force) - return lock - except lockfile.TimeOutError: - status = master_state() - if status == WatcherState.conflict: - # Hostname matches and process exists. - message = _("""\ -The master qrunner lock could not be acquired because it appears -as though another master qrunner is already running. -""") - elif status == WatcherState.stale_lock: - # Hostname matches but the process does not exist. - message = _("""\ -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: - assert status == WatcherState.host_mismatch, ( - 'Invalid enum value: %s' % status) - # Hostname doesn't even match. - hostname, pid, tempfile = get_lock_data() - message = _("""\ -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 clean this up manually. - -Lock file: $config.LOCK_FILE -Lock host: $hostname - -Exiting.""") - config.options.parser.error(message) - - - -class Loop: - """Main control loop class.""" - - def __init__(self, lock=None, restartable=None, config_file=None): - self._lock = lock - self._restartable = restartable - self._config_file = config_file - self._kids = {} - - def install_signal_handlers(self): - """Install various signals handlers for control from mailmanctl.""" - log = logging.getLogger('mailman.qrunner') - # Set up our signal handlers. Also set up a SIGALRM handler to - # refresh the lock once per day. The lock lifetime is 1 day + 6 hours - # so this should be plenty. - def sigalrm_handler(signum, frame): - self._lock.refresh() - signal.alarm(SECONDS_IN_A_DAY) - signal.signal(signal.SIGALRM, sigalrm_handler) - signal.alarm(SECONDS_IN_A_DAY) - # SIGHUP tells the qrunners to close and reopen their log files. - def sighup_handler(signum, frame): - reopen() - for pid in self._kids: - os.kill(pid, signal.SIGHUP) - log.info('Master watcher caught SIGHUP. Re-opening log files.') - signal.signal(signal.SIGHUP, sighup_handler) - # SIGUSR1 is used by 'mailman restart'. - def sigusr1_handler(signum, frame): - for pid in self._kids: - os.kill(pid, signal.SIGUSR1) - log.info('Master watcher caught SIGUSR1. Exiting.') - signal.signal(signal.SIGUSR1, sigusr1_handler) - # SIGTERM is what init will kill this process with when changing run - # levels. It's also the signal 'mailmanctl stop' uses. - def sigterm_handler(signum, frame): - for pid in self._kids: - os.kill(pid, signal.SIGTERM) - log.info('Master watcher caught SIGTERM. Exiting.') - signal.signal(signal.SIGTERM, sigterm_handler) - # SIGINT is what control-C gives. - def sigint_handler(signum, frame): - for pid in self._kids: - os.kill(pid, signal.SIGINT) - log.info('Master watcher caught SIGINT. Restarting.') - signal.signal(signal.SIGINT, sigint_handler) - - def _start_runner(self, spec): - """Start a queue runner. - - All arguments are passed to the qrunner process. - - :param spec: A queue runner spec, in a format acceptable to - bin/qrunner's --runner argument, e.g. name:slice:count - :type spec: string - :return: The process id of the child queue runner. - :rtype: int - """ - pid = os.fork() - if pid: - # Parent. - return pid - # Child. - # - # Craft the command line arguments for the exec() call. - rswitch = '--runner=' + spec - # Wherever mailmanctl lives, so too must live the qrunner script. - 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. - args = [sys.executable, sys.executable, exe, rswitch, '-s'] - if self._config_file is not None: - args.extend(['-C', self._config_file]) - log = logging.getLogger('mailman.qrunner') - log.debug('starting: %s', args) - os.execl(*args) - # We should never get here. - raise RuntimeError('os.execl() failed') - - def start_qrunners(self, qrunner_names=None): - """Start all the configured qrunners. - - :param qrunners: If given, a sequence of queue runner names to start. - If not given, this sequence is taken from the configuration file. - :type qrunners: a sequence of strings - """ - if not qrunner_names: - qrunner_names = [] - for qrunner_config in config.qrunner_configs: - # Strip off the 'qrunner.' prefix. - assert qrunner_config.name.startswith('qrunner.'), ( - 'Unexpected qrunner configuration section name: %s', - qrunner_config.name) - qrunner_names.append(qrunner_config.name[8:]) - # For each qrunner we want to start, find their config section, which - # will tell us the name of the class to instantiate, along with the - # number of hash space slices to manage. - for name in qrunner_names: - section_name = 'qrunner.' + name - # Let AttributeError propagate. - qrunner_config = getattr(config, section_name) - if not as_boolean(qrunner_config.start): - continue - package, class_name = qrunner_config['class'].rsplit(DOT, 1) - __import__(package) - # Let AttributeError propagate. - class_ = getattr(sys.modules[package], class_name) - # Find out how many qrunners to instantiate. This must be a power - # of 2. - count = int(qrunner_config.instances) - assert (count & (count - 1)) == 0, ( - 'Queue runner "%s", not a power of 2: %s', name, count) - for slice_number in range(count): - # qrunner name, slice #, # of slices, restart count - info = (name, slice_number, count, 0) - spec = '%s:%d:%d' % (name, slice_number, count) - pid = self._start_runner(spec) - log = logging.getLogger('mailman.qrunner') - log.debug('[%d] %s', pid, spec) - self._kids[pid] = info - - def loop(self): - """Main loop. - - Wait until all the qrunners have exited, restarting them if necessary - and configured to do so. - """ - log = logging.getLogger('mailman.qrunner') - while True: - try: - pid, status = os.wait() - except OSError, error: - # No children? We're done. - if error.errno == errno.ECHILD: - break - # If the system call got interrupted, just restart it. - elif error.errno == errno.EINTR: - continue - else: - raise - # Find out why the subprocess exited by getting the signal - # received or exit status. - if os.WIFSIGNALED(status): - why = os.WTERMSIG(status) - elif os.WIFEXITED(status): - why = os.WEXITSTATUS(status) - else: - why = None - # We'll restart the subprocess if it exited with a SIGUSR1 or - # because of a failure (i.e. no exit signal), and the no-restart - # command line switch was not given. This lets us better handle - # runaway restarts (e.g. if the subprocess had a syntax error!) - qrname, slice_number, count, restarts = self._kids.pop(pid) - config_name = 'qrunner.' + qrname - restart = False - if why == signal.SIGUSR1 and self._restartable: - restart = True - # Have we hit the maximum number of restarts? - restarts += 1 - max_restarts = int(getattr(config, config_name).max_restarts) - if restarts > max_restarts: - restart = False - # Are we permanently non-restartable? - log.debug("""\ -Master detected subprocess exit -(pid: %d, why: %s, class: %s, slice: %d/%d) %s""", - pid, why, qrname, slice_number + 1, count, - ('[restarting]' if restart else '')) - # See if we've reached the maximum number of allowable restarts - if restarts > max_restarts: - log.info("""\ -qrunner %s reached maximum restart limit of %d, not restarting.""", - qrname, max_restarts) - # Now perhaps restart the process unless it exited with a - # SIGTERM or we aren't restarting. - if restart: - spec = '%s:%d:%d' % (qrname, slice_number, count) - newpid = self._start_runner(spec) - self._kids[newpid] = (qrname, slice_number, count, restarts) - - def cleanup(self): - """Ensure that all children have exited.""" - log = logging.getLogger('mailman.qrunner') - # Send SIGTERMs to all the child processes and wait for them all to - # exit. - for pid in self._kids: - try: - os.kill(pid, signal.SIGTERM) - except OSError, error: - if error.errno == errno.ESRCH: - # The child has already exited. - log.info('ESRCH on pid: %d', pid) - # Wait for all the children to go away. - while self._kids: - try: - pid, status = os.wait() - del self._kids[pid] - except OSError, e: - if e.errno == errno.ECHILD: - break - elif e.errno == errno.EINTR: - continue - raise - - - -def main(): - """Main process.""" - - options = ScriptOptions() - options.initialize() - - # Acquire the master lock, exiting if we can't acquire it. We'll let the - # caller handle any clean up or lock breaking. No with statement here - # because Lock's constructor doesn't support a timeout. - lock = acquire_lock(options.options.force) - try: - with open(config.PIDFILE, 'w') as fp: - print >> fp, os.getpid() - loop = Loop(lock, options.options.restartable, options.options.config) - loop.install_signal_handlers() - try: - loop.start_qrunners(options.options.runners) - loop.loop() - finally: - loop.cleanup() - os.remove(config.PIDFILE) - finally: - lock.unlock() - - - -if __name__ == '__main__': - main() diff --git a/mailman/bin/mmsitepass.py b/mailman/bin/mmsitepass.py deleted file mode 100644 index 132803fc9..000000000 --- a/mailman/bin/mmsitepass.py +++ /dev/null @@ -1,113 +0,0 @@ -# Copyright (C) 1998-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -import sys -import getpass -import optparse - -from mailman import Utils -from mailman import passwords -from mailman.configuration import config -from mailman.i18n import _ -from mailman.initialize import initialize -from mailman.version import MAILMAN_VERSION - - - -def parseargs(): - parser = optparse.OptionParser(version=MAILMAN_VERSION, - usage=_("""\ -%prog [options] [password] - -Set the site or list creator password. - -The site password can be used in most if not all places that the list -administrator's password can be used, which in turn can be used in most places -that a list user's password can be used. The list creator password is a -separate password that can be given to non-site administrators to delegate the -ability to create new mailing lists. - -If password is not given on the command line, it will be prompted for. -""")) - parser.add_option('-c', '--listcreator', - default=False, action='store_true', - help=_("""\ -Set the list creator password instead of the site password. The list -creator is authorized to create and remove lists, but does not have -the total power of the site administrator.""")) - parser.add_option('-p', '--password-scheme', - default='', type='string', - help=_("""\ -Specify the RFC 2307 style hashing scheme for passwords included in the -output. Use -P to get a list of supported schemes, which are -case-insensitive.""")) - parser.add_option('-P', '--list-hash-schemes', - default=False, action='store_true', help=_("""\ -List the supported password hashing schemes and exit. The scheme labels are -case-insensitive.""")) - parser.add_option('-C', '--config', - help=_('Alternative configuration file to use')) - opts, args = parser.parse_args() - if len(args) > 1: - parser.error(_('Unexpected arguments')) - if opts.list_hash_schemes: - for label in passwords.Schemes: - print str(label).upper() - sys.exit(0) - return parser, opts, args - - -def check_password_scheme(parser, password_scheme): - # shoule be checked after config is loaded. - if password_scheme == '': - password_scheme = config.PASSWORD_SCHEME - scheme = passwords.lookup_scheme(password_scheme.lower()) - if not scheme: - parser.error(_('Invalid password scheme')) - return scheme - - - -def main(): - parser, opts, args = parseargs() - initialize(opts.config) - opts.password_scheme = check_password_scheme(parser, opts.password_scheme) - if args: - password = args[0] - else: - # Prompt for the password - if opts.listcreator: - prompt_1 = _('New list creator password: ') - else: - prompt_1 = _('New site administrator password: ') - pw1 = getpass.getpass(prompt_1) - pw2 = getpass.getpass(_('Enter password again to confirm: ')) - if pw1 <> pw2: - print _('Passwords do not match; no changes made.') - sys.exit(1) - password = pw1 - Utils.set_global_password(password, - not opts.listcreator, opts.password_scheme) - if Utils.check_global_password(password, not opts.listcreator): - print _('Password changed.') - else: - print _('Password change failed.') - - - -if __name__ == '__main__': - main() diff --git a/mailman/bin/nightly_gzip.py b/mailman/bin/nightly_gzip.py deleted file mode 100644 index f886e5801..000000000 --- a/mailman/bin/nightly_gzip.py +++ /dev/null @@ -1,117 +0,0 @@ -# Copyright (C) 1998-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -import os -import sys -import optparse - -try: - import gzip -except ImportError: - sys.exit(0) - -from mailman import MailList -from mailman.configuration import config -from mailman.i18n import _ -from mailman.initialize import initialize -from mailman.version import MAILMAN_VERSION - - - -def parseargs(): - parser = optparse.OptionParser(version=MAILMAN_VERSION, - usage=_("""\ -%prog [options] [listname ...] - -Re-generate the Pipermail gzip'd archive flat files.""")) - parser.add_option('-v', '--verbose', - default=False, action='store_true', - help=_("Print each file as it's being gzip'd")) - parser.add_option('-z', '--level', - default=6, type='int', - help=_('Specifies the compression level')) - parser.add_option('-C', '--config', - help=_('Alternative configuration file to use')) - opts, args = parser.parse_args() - if opts.level < 1 or opts.level > 9: - parser.print_help() - print >> sys.stderr, _('Illegal compression level: $opts.level') - sys.exit(1) - return opts, args, parser - - - -def compress(txtfile, opts): - if opts.verbose: - print _("gzip'ing: $txtfile") - infp = outfp = None - try: - infp = open(txtfile) - outfp = gzip.open(txtfile + '.gz', 'wb', opts.level) - outfp.write(infp.read()) - finally: - if outfp: - outfp.close() - if infp: - infp.close() - - - -def main(): - opts, args, parser = parseargs() - initialize(opts.config) - - if config.ARCHIVE_TO_MBOX not in (1, 2) or config.GZIP_ARCHIVE_TXT_FILES: - # We're only going to run the nightly archiver if messages are - # archived to the mbox, and the gzip file is not created on demand - # (i.e. for every individual post). This is the normal mode of - # operation. - return - - # Process all the specified lists - for listname in set(args or config.list_manager.names): - mlist = MailList.MailList(listname, lock=False) - if not mlist.archive: - continue - dir = mlist.archive_dir() - try: - allfiles = os.listdir(dir) - except OSError: - # Has the list received any messages? If not, last_post_time will - # be zero, so it's not really a bogus archive dir. - if mlist.last_post_time > 0: - print _('List $listname has a bogus archive_directory: $dir') - continue - if opts.verbose: - print _('Processing list: $listname') - files = [] - for f in allfiles: - if os.path.splitext(f)[1] <> '.txt': - continue - # stat both the .txt and .txt.gz files and append them only if - # the former is newer than the latter. - txtfile = os.path.join(dir, f) - gzpfile = txtfile + '.gz' - txt_mtime = os.path.getmtime(txtfile) - try: - gzp_mtime = os.path.getmtime(gzpfile) - except OSError: - gzp_mtime = -1 - if txt_mtime > gzp_mtime: - files.append(txtfile) - for f in files: - compress(f, opts) diff --git a/mailman/bin/qrunner.py b/mailman/bin/qrunner.py deleted file mode 100644 index 62e943aad..000000000 --- a/mailman/bin/qrunner.py +++ /dev/null @@ -1,269 +0,0 @@ -# Copyright (C) 2001-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -import sys -import signal -import logging - -from mailman.config import config -from mailman.core.logging import reopen -from mailman.i18n import _ -from mailman.options import Options - - -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) - dest.append((runner, rslice, rrange)) - - - -class ScriptOptions(Options): - - 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. -""") - - def add_options(self): - self.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.""")) - self.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 signal.""")) - self.parser.add_option( - '-l', '--list', - default=False, action='store_true', - help=_('List the available qrunner names and exit.')) - self.parser.add_option( - '-v', '--verbose', - default=0, action='count', help=_("""\ -Display more debugging information to the logs/qrunner log file.""")) - self.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.""")) - - def sanity_check(self): - if self.arguments: - self.parser.error(_('Unexpected arguments')) - if not self.options.runners and not self.options.list: - self.parser.error(_('No runner name given.')) - - - -def make_qrunner(name, slice, range, once=False): - # Several conventions for specifying the runner name are supported. It - # could be one of the shortcut names. If the name is a full module path, - # use it explicitly. If the name starts with a dot, it's a class name - # relative to the Mailman.queue package. - qrunner_config = getattr(config, 'qrunner.' + name, None) - if qrunner_config is not None: - # It was a shortcut name. - class_path = qrunner_config['class'] - elif name.startswith('.'): - class_path = 'mailman.queue' + name - else: - class_path = name - module_name, class_name = class_path.rsplit('.', 1) - try: - __import__(module_name) - except ImportError, e: - if config.options.options.subproc: - # Exit with SIGTERM exit code so the master watcher won't try to - # restart us. - print >> sys.stderr, _('Cannot import runner module: $module_name') - print >> sys.stderr, e - sys.exit(signal.SIGTERM) - else: - raise - qrclass = getattr(sys.modules[module_name], class_name) - if once: - # Subclass to hack in the setting of the stop flag in _do_periodic() - class Once(qrclass): - def _do_periodic(self): - self.stop() - qrunner = Once(name, slice) - else: - qrunner = qrclass(name, slice) - return qrunner - - - -def set_signals(loop): - """Set up the signal handlers. - - Signals caught are: SIGTERM, SIGINT, SIGUSR1 and SIGHUP. The latter is - used to re-open the log files. SIGTERM and SIGINT are treated exactly the - same -- they cause qrunner to exit with no restart from the master. - SIGUSR1 also causes qrunner to exit, but the master watcher will restart - it in that case. - - :param loop: A loop queue runner instance. - """ - 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) - 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) - def sigusr1_handler(signum, frame): - # Exit the qrunner cleanly - loop.stop() - loop.status = signal.SIGUSR1 - log.info('%s qrunner caught SIGUSR1. Stopping.', loop.name()) - signal.signal(signal.SIGUSR1, sigusr1_handler) - # SIGHUP just tells us to rotate our log files. - def sighup_handler(signum, frame): - reopen() - log.info('%s qrunner caught SIGHUP. Reopening logs.', loop.name()) - signal.signal(signal.SIGHUP, sighup_handler) - - - -def main(): - global log - - options = ScriptOptions() - options.initialize() - - if options.options.list: - prefixlen = max(len(shortname) - for shortname in config.qrunner_shortcuts) - for shortname in sorted(config.qrunner_shortcuts): - runnername = config.qrunner_shortcuts[shortname] - shortname = (' ' * (prefixlen - len(shortname))) + shortname - print _('$shortname runs $runnername') - sys.exit(0) - - # Fast track for one infinite runner - if len(options.options.runners) == 1 and not options.options.once: - qrunner = make_qrunner(*options.options.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 = logging.getLogger('mailman.qrunner') - 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 options.options.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 options.options.verbose: - log.info('Now doing a %s qrunner iteration', - qrunner.__class__.__bases__[0].__name__) - qrunner.run() - if options.options.once: - break - log.info('Main qrunner loop exiting.') - # All done - sys.exit(loop.status) - - - -if __name__ == '__main__': - main() diff --git a/mailman/bin/remove_list.py b/mailman/bin/remove_list.py deleted file mode 100644 index 05211b200..000000000 --- a/mailman/bin/remove_list.py +++ /dev/null @@ -1,83 +0,0 @@ -# Copyright (C) 1998-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -import sys - -from mailman.app.lifecycle import remove_list -from mailman.config import config -from mailman.i18n import _ -from mailman.options import MultipleMailingListOptions - - - -class ScriptOptions(MultipleMailingListOptions): - usage = _("""\ -%prog [options] - -Remove the components of a mailing list with impunity - beware! - -This removes (almost) all traces of a mailing list. By default, the lists -archives are not removed, which is very handy for retiring old lists. -""") - - def add_options(self): - super(ScriptOptions, self).add_options() - self.parser.add_option( - '-a', '--archives', - default=False, action='store_true', - help=_("""\ -Remove the list's archives too, or if the list has already been deleted, -remove any residual archives.""")) - self.parser.add_option( - '-q', '--quiet', - default=False, action='store_true', - help=_('Suppress status messages')) - - def sanity_check(self): - if len(self.options.listnames) == 0: - self.parser.error(_('Nothing to do')) - if len(self.arguments) > 0: - self.parser.error(_('Unexpected arguments')) - - - -def main(): - options = ScriptOptions() - options.initialize() - - for fqdn_listname in options.options.listnames: - if not options.options.quiet: - print _('Removing list: $fqdn_listname') - mlist = config.db.list_manager.get(fqdn_listname) - if mlist is None: - if options.options.archives: - print _("""\ -No such list: ${fqdn_listname}. Removing its residual archives.""") - else: - print >> sys.stderr, _( - 'No such list (or list already deleted): $fqdn_listname') - - if not options.options.archives: - print _('Not removing archives. Reinvoke with -a to remove them.') - - remove_list(fqdn_listname, mlist, options.options.archives) - config.db.commit() - - - -if __name__ == '__main__': - main() diff --git a/mailman/bin/senddigests.py b/mailman/bin/senddigests.py deleted file mode 100644 index fb057d6b9..000000000 --- a/mailman/bin/senddigests.py +++ /dev/null @@ -1,83 +0,0 @@ -# Copyright (C) 1998-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -import os -import sys -import optparse - -from mailman import MailList -from mailman.i18n import _ -from mailman.initialize import initialize -from mailman.version import MAILMAN_VERSION - -# Work around known problems with some RedHat cron daemons -import signal -signal.signal(signal.SIGCHLD, signal.SIG_DFL) - - - -def parseargs(): - parser = optparse.OptionParser(version=MAILMAN_VERSION, - usage=_("""\ -%prog [options] - -Dispatch digests for lists w/pending messages and digest_send_periodic -set.""")) - parser.add_option('-l', '--listname', - type='string', default=[], action='append', - dest='listnames', help=_("""\ -Send the digest for the given list only, otherwise the digests for all -lists are sent out. Multiple -l options may be given.""")) - 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 opts, args, parser - - - -def main(): - opts, args, parser = parseargs() - initialize(opts.config) - - for listname in set(opts.listnames or config.list_manager.names): - mlist = MailList.MailList(listname, lock=False) - if mlist.digest_send_periodic: - mlist.Lock() - try: - try: - mlist.send_digest_now() - mlist.Save() - # We are unable to predict what exception may occur in digest - # processing and we don't want to lose the other digests, so - # we catch everything. - except Exception, errmsg: - print >> sys.stderr, \ - 'List: %s: problem processing %s:\n%s' % \ - (listname, - os.path.join(mlist.data_path, 'digest.mbox'), - errmsg) - finally: - mlist.Unlock() - - - -if __name__ == '__main__': - main() diff --git a/mailman/bin/set_members.py b/mailman/bin/set_members.py deleted file mode 100644 index cdd11c56f..000000000 --- a/mailman/bin/set_members.py +++ /dev/null @@ -1,189 +0,0 @@ -# Copyright (C) 2007-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -import csv -import optparse - -from mailman import Message -from mailman import Utils -from mailman import i18n -from mailman import passwords -from mailman.app.membership import add_member -from mailman.app.notifications import ( - send_admin_subscription_notice, send_welcome_message) -from mailman.configuration import config -from mailman.initialize import initialize -from mailman.interfaces import DeliveryMode -from mailman.version import MAILMAN_VERSION - - -_ = i18n._ - -DELIVERY_MODES = { - 'regular': DeliveryMode.regular, - 'plain': DeliveryMode.plaintext_digests, - 'mime': DeliveryMode.mime_digests, - } - - - -def parseargs(): - parser = optparse.OptionParser(version=MAILMAN_VERSION, - usage=_("""\ -%prog [options] csv-file - -Set the membership of a mailing list to that described in a CSV file. Each -row of the CSV file has the following format. Only the address column is -required. - - - email address - - full name (default: the empty string) - - delivery mode (default: regular delivery) [1] - -[1] The delivery mode is a case insensitive string of the following values: - - regular - regular, i.e. immediate delivery - mime - MIME digest delivery - plain - plain text (RFC 1153) digest delivery - -Any address not included in the CSV file is removed from the list membership. -""")) - parser.add_option('-l', '--listname', - type='string', help=_("""\ -Mailng list to set the membership for.""")) - parser.add_option('-w', '--welcome-msg', - type='string', metavar='<y|n>', help=_("""\ -Set whether or not to send the list members a welcome message, overriding -whatever the list's 'send_welcome_msg' setting is.""")) - parser.add_option('-a', '--admin-notify', - type='string', metavar='<y|n>', help=_("""\ -Set whether or not to send the list administrators a notification on the -success/failure of these subscriptions, overriding whatever the list's -'admin_notify_mchanges' setting is.""")) - parser.add_option('-v', '--verbose', action='store_true', - help=_('Increase verbosity')) - parser.add_option('-C', '--config', - help=_('Alternative configuration file to use')) - opts, args = parser.parse_args() - if opts.welcome_msg is not None: - ch = opts.welcome_msg[0].lower() - if ch == 'y': - opts.welcome_msg = True - elif ch == 'n': - opts.welcome_msg = False - else: - parser.error(_('Illegal value for -w: $opts.welcome_msg')) - if opts.admin_notify is not None: - ch = opts.admin_notify[0].lower() - if ch == 'y': - opts.admin_notify = True - elif ch == 'n': - opts.admin_notify = False - else: - parser.error(_('Illegal value for -a: $opts.admin_notify')) - return parser, opts, args - - - -def parse_file(filename): - members = {} - with open(filename) as fp: - for row in csv.reader(fp): - if len(row) == 0: - continue - elif len(row) == 1: - address = row[0] - real_name = None - delivery_mode = DeliveryMode.regular - elif len(row) == 2: - address, real_name = row - delivery_mode = DeliveryMode.regular - else: - # Ignore extra columns - address, real_name = row[0:2] - delivery_mode = DELIVERY_MODES.get(row[2].lower()) - if delivery_mode is None: - delivery_mode = DeliveryMode.regular - members[address] = real_name, delivery_mode - return members - - - -def main(): - parser, opts, args = parseargs() - initialize(opts.config) - - mlist = config.db.list_manager.get(opts.listname) - if mlist is None: - parser.error(_('No such list: $opts.listname')) - - # Set up defaults. - if opts.welcome_msg is None: - send_welcome_msg = mlist.send_welcome_msg - else: - send_welcome_msg = opts.welcome_msg - if opts.admin_notify is None: - admin_notify = mlist.admin_notify_mchanges - else: - admin_notify = opts.admin_notify - - # Parse the csv files. - member_data = {} - for filename in args: - member_data.update(parse_file(filename)) - - future_members = set(member_data) - current_members = set(obj.address for obj in mlist.members.addresses) - add_members = future_members - current_members - delete_members = current_members - future_members - change_members = current_members & future_members - - with i18n.using_language(mlist.preferred_language): - # Start by removing all the delete members. - for address in delete_members: - print _('deleting address: $address') - member = mlist.members.get_member(address) - member.unsubscribe() - # For all members that are in both lists, update their full name and - # delivery mode. - for address in change_members: - print _('updating address: $address') - real_name, delivery_mode = member_data[address] - member = mlist.members.get_member(address) - member.preferences.delivery_mode = delivery_mode - user = config.db.user_manager.get_user(address) - user.real_name = real_name - for address in add_members: - print _('adding address: $address') - real_name, delivery_mode = member_data[address] - password = passwords.make_secret( - Utils.MakeRandomPassword(), - passwords.lookup_scheme(config.PASSWORD_SCHEME)) - add_member(mlist, address, real_name, password, delivery_mode, - mlist.preferred_language, send_welcome_msg, - admin_notify) - if send_welcome_msg: - send_welcome_message(mlist, address, language, delivery_mode) - if admin_notify: - send_admin_subscription_notice(mlist, address, real_name) - - config.db.flush() - - - -if __name__ == '__main__': - main() diff --git a/mailman/bin/show_config.py b/mailman/bin/show_config.py deleted file mode 100644 index 8d26c5c97..000000000 --- a/mailman/bin/show_config.py +++ /dev/null @@ -1,97 +0,0 @@ -# Copyright (C) 2006-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -import re -import sys -import pprint -import optparse - -from mailman.configuration import config -from mailman.i18n import _ -from mailman.version import MAILMAN_VERSION - - -# List of names never to show even if --verbose -NEVER_SHOW = ['__builtins__', '__doc__'] - - - -def parseargs(): - parser = optparse.OptionParser(version=MAILMAN_VERSION, - usage=_("""\ -%%prog [options] [pattern ...] - -Show the values of various Defaults.py/mailman.cfg variables. -If one or more patterns are given, show only those variables -whose names match a pattern""")) - parser.add_option('-v', '--verbose', - default=False, action='store_true', - help=_( -"Show all configuration names, not just 'settings'.")) - parser.add_option('-i', '--ignorecase', - default=False, action='store_true', - help=_("Match patterns case-insensitively.")) - parser.add_option('-C', '--config', - help=_('Alternative configuration file to use')) - opts, args = parser.parse_args() - return parser, opts, args - - - -def main(): - parser, opts, args = parseargs() - - patterns = [] - if opts.ignorecase: - flag = re.IGNORECASE - else: - flag = 0 - for pattern in args: - patterns.append(re.compile(pattern, flag)) - - pp = pprint.PrettyPrinter(indent=4) - config.load(opts.config) - names = config.__dict__.keys() - names.sort() - for name in names: - if name in NEVER_SHOW: - continue - if not opts.verbose: - if name.startswith('_') or re.search('[a-z]', name): - continue - if patterns: - hit = False - for pattern in patterns: - if pattern.search(name): - hit = True - break - if not hit: - continue - value = config.__dict__[name] - if isinstance(value, str): - if re.search('\n', value): - print '%s = """%s"""' %(name, value) - else: - print "%s = '%s'" % (name, value) - else: - print '%s = ' % name, - pp.pprint(value) - - - -if __name__ == '__main__': - main() diff --git a/mailman/bin/show_qfiles.py b/mailman/bin/show_qfiles.py deleted file mode 100644 index e4b64e0cd..000000000 --- a/mailman/bin/show_qfiles.py +++ /dev/null @@ -1,91 +0,0 @@ -# Copyright (C) 2006-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -import os -import sys - -from cPickle import load - -from mailman.config import config -from mailman.i18n import _ -from mailman.options import Options - - - -class ScriptOptions(Options): - usage = _(""" -%%prog [options] qfiles ... - -Show the contents of one or more Mailman queue files.""") - - def add_options(self): - super(ScriptOptions, self).add_options() - self.parser.add_option( - '-q', '--quiet', - default=False, action='store_true', - help=_("Don't print 'helpful' message delimiters.")) - self.parser.add_option( - '-s', '--summary', - default=False, action='store_true', - help=_('Show a summary of queue files.')) - - - -def main(): - options = ScriptOptions() - options.initialize() - - if options.options.summary: - queue_totals = {} - files_by_queue = {} - for switchboard in config.switchboards.values(): - total = 0 - file_mappings = {} - for filename in os.listdir(switchboard.queue_directory): - base, ext = os.path.splitext(filename) - file_mappings[ext] = file_mappings.get(ext, 0) + 1 - total += 1 - files_by_queue[switchboard.queue_directory] = file_mappings - queue_totals[switchboard.queue_directory] = total - # Sort by queue name. - for queue_directory in sorted(files_by_queue): - total = queue_totals[queue_directory] - print queue_directory - print _('\tfile count: $total') - file_mappings = files_by_queue[queue_directory] - for ext in sorted(file_mappings): - print '\t{0}: {1}'.format(ext, file_mappings[ext]) - return - # No summary. - for filename in options.arguments: - if not options.options.quiet: - print '====================>', filename - with open(filename) as fp: - if filename.endswith('.pck'): - msg = load(fp) - data = load(fp) - if data.get('_parsemsg'): - sys.stdout.write(msg) - else: - sys.stdout.write(msg.as_string()) - else: - sys.stdout.write(fp.read()) - - - -if __name__ == '__main__': - main() diff --git a/mailman/bin/unshunt.py b/mailman/bin/unshunt.py deleted file mode 100644 index fc889377c..000000000 --- a/mailman/bin/unshunt.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright (C) 2002-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -__metaclass__ = type -__all__ = [ - 'main', - ] - - -import sys - -from mailman.config import config -from mailman.i18n import _ -from mailman.options import Options - - - -def main(): - options = Options() - options.initialize() - - switchboard = config.switchboards['shunt'] - switchboard.recover_backup_files() - - for filebase in switchboard.files: - try: - msg, msgdata = switchboard.dequeue(filebase) - whichq = msgdata.get('whichq', 'in') - config.switchboards[whichq].enqueue(msg, msgdata) - except Exception, e: - # If there are any unshunting errors, log them and continue trying - # other shunted messages. - print >> sys.stderr, _( - 'Cannot unshunt message $filebase, skipping:\n$e') - else: - # Unlink the .bak file left by dequeue() - switchboard.finish(filebase) diff --git a/mailman/bin/update.py b/mailman/bin/update.py deleted file mode 100644 index 34ea6cda3..000000000 --- a/mailman/bin/update.py +++ /dev/null @@ -1,660 +0,0 @@ -# Copyright (C) 1998-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -import os -import md5 -import sys -import time -import email -import errno -import shutil -import cPickle -import marshal -import optparse - -from locknix.lockfile import TimeOutError - -from mailman import MailList -from mailman import Message -from mailman import Pending -from mailman import Utils -from mailman import version -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 _ -from mailman.initialize import initialize -from mailman.utilities.filesystem import makedirs - - -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 = version.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 situate_list(listname): - # This turns the directory called 'listname' into a directory called - # 'listname@domain'. Start by finding out what the domain should be. - # A list's domain is its email host. - mlist = MailList.MailList(listname, lock=False, check_version=False) - fullname = mlist.fqdn_listname - oldpath = os.path.join(config.VAR_PREFIX, 'lists', listname) - newpath = os.path.join(config.VAR_PREFIX, 'lists', fullname) - if os.path.exists(newpath): - print >> sys.stderr, _('WARNING: could not situate list: $listname') - else: - os.rename(oldpath, newpath) - print _('situated list $listname to $fullname') - return fullname - - - -def dolist(listname): - mlist = MailList.MailList(listname, lock=False) - try: - 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) - - 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): - makedirs(mbox_dir) - 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)) - makedirs(mbox_dir) - # 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 development 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 development 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 main(): - parser, opts, args = parseargs() - initialize(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) - if not config.list_manager.names: - 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 config.list_manager.names: - # With 2.2.0a0, all list names grew an @domain suffix. If you find a - # list without that, move it now. - if not '@' in listname: - listname = situate_list(listname) - print _('Updating mailing list: $listname') - errors += 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 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/bin/version.py b/mailman/bin/version.py deleted file mode 100644 index 0fb2c5a5b..000000000 --- a/mailman/bin/version.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright (C) 1998-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -import optparse - -from mailman import version -from mailman.i18n import _ - - - -def parseargs(): - parser = optparse.OptionParser(version=version.MAILMAN_VERSION, - usage=_("""\ -%prog - -Print the Mailman version and exit.""")) - opts, args = parser.parse_args() - if args: - parser.error(_('Unexpected arguments')) - return parser, opts, args - - - -def main(): - parser, opts, args = parseargs() - # Yes, this is kind of silly - print _('Using $version.MAILMAN_VERSION ($version.CODENAME)') - - - -if __name__ == '__main__': - main() diff --git a/mailman/bin/withlist.py b/mailman/bin/withlist.py deleted file mode 100644 index 8f2d8a2b5..000000000 --- a/mailman/bin/withlist.py +++ /dev/null @@ -1,220 +0,0 @@ -# Copyright (C) 1998-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -import os -import sys -import optparse - -from mailman import interact -from mailman.config import config -from mailman.core.initialize import initialize -from mailman.i18n import _ -from mailman.version import MAILMAN_VERSION - - -LAST_MLIST = None -VERBOSE = True - - - -def do_list(listname, args, func): - global LAST_MLIST - - if '@' not in listname: - listname += '@' + config.DEFAULT_EMAIL_HOST - - # XXX FIXME Remove this when this script is converted to - # MultipleMailingListOptions. - listname = listname.decode(sys.getdefaultencoding()) - mlist = config.db.list_manager.get(listname) - if mlist is None: - print >> sys.stderr, _('Unknown list: $listname') - else: - if VERBOSE: - print >> sys.stderr, _('Loaded list: $listname') - LAST_MLIST = mlist - # Try to import the module and run the callable. - if func: - return func(mlist, *args) - return None - - - -def parseargs(): - parser = optparse.OptionParser(version=MAILMAN_VERSION, - usage=_("""\ -%prog [options] listname [args ...] - -General framework for interacting with a mailing list object. - -There are two ways to use this script: interactively or programmatically. -Using it interactively allows you to play with, examine and modify a -IMailinglist object from Python's interactive interpreter. When running -interactively, a IMailingList object called 'm' will be available in the -global namespace. - -Programmatically, you can write a function to operate on a IMailingList -object, and this script will take care of the housekeeping (see below for -examples). In that case, the general usage syntax is: - - % bin/withlist [options] listname [args ...] - -Here's an example of how to use the -r option. Say you have a file in the -Mailman installation directory called 'listaddr.py', with the following -two functions: - - def listaddr(mlist): - print mlist.posting_address - - def requestaddr(mlist): - print mlist.request_address - -Now, from the command line you can print the list's posting address by running -the following from the command line: - - % bin/withlist -r listaddr mylist - Loading list: mylist - Importing listaddr ... - Running listaddr.listaddr() ... - mylist@myhost.com - -And you can print the list's request address by running: - - % bin/withlist -r listaddr.requestaddr mylist - Loading list: mylist - Importing listaddr ... - Running listaddr.requestaddr() ... - mylist-request@myhost.com - -As another example, say you wanted to change the password for a particular -user on a particular list. You could put the following function in a file -called 'changepw.py': - - from mailman.errors import NotAMemberError - - def changepw(mlist, addr, newpasswd): - try: - mlist.setMemberPassword(addr, newpasswd) - mlist.Save() - except NotAMemberError: - print 'No address matched:', addr - -and run this from the command line: - - % bin/withlist -l -r changepw mylist somebody@somewhere.org foobar""")) - parser.add_option('-i', '--interactive', - default=None, action='store_true', help=_("""\ -Leaves you at an interactive prompt after all other processing is complete. -This is the default unless the -r option is given.""")) - parser.add_option('-r', '--run', - type='string', help=_("""\ -This can be used to run a script with the opened IMailingList object. This -works by attempting to import'module' (which must be in the directory -containing withlist, or already be accessible on your sys.path), and then -calling 'callable' from the module. callable can be a class or function; it -is called with the IMailingList object as the first argument. If additional -args are given on the command line, they are passed as subsequent positional -args to the callable. - -Note that 'module.' is optional; if it is omitted then a module with the name -'callable' will be imported. - -The global variable 'r' will be set to the results of this call.""")) - parser.add_option('-a', '--all', - default=False, action='store_true', help=_("""\ -This option only works with the -r option. Use this if you want to execute -the script on all mailing lists. When you use -a you should not include a -listname argument on the command line. The variable 'r' will be a list of all -the results.""")) - parser.add_option('-q', '--quiet', - default=False, action='store_true', - help=_('Suppress all status messages.')) - parser.add_option('-C', '--config', - help=_('Alternative configuration file to use')) - opts, args = parser.parse_args() - return parser, opts, args - - - -def main(): - global VERBOSE - - parser, opts, args = parseargs() - config_file = (os.getenv('MAILMAN_CONFIG_FILE') - if opts.config is None - else opts.config) - initialize(config_file, not opts.quiet) - - VERBOSE = not opts.quiet - # The default for interact is true unless -r was given - if opts.interactive is None: - if not opts.run: - opts.interactive = True - else: - opts.interactive = False - - dolist = True - if len(args) < 1 and not opts.all: - warning = _('No list name supplied.') - if opts.interactive: - # Let them keep going - print >> sys.stderr, warning - dolist = False - else: - parser.error(warning) - - if opts.all and not opts.run: - parser.error(_('--all requires --run')) - - # Try to import the module for the callable - func = None - if opts.run: - i = opts.run.rfind('.') - if i < 0: - module = opts.run - callable = opts.run - else: - module = opts.run[:i] - callable = opts.run[i+1:] - if VERBOSE: - print >> sys.stderr, _('Importing $module ...') - __import__(module) - mod = sys.modules[module] - if VERBOSE: - print >> sys.stderr, _('Running ${module}.${callable}() ...') - func = getattr(mod, callable) - - r = None - if opts.all: - r = [do_list(listname, args, func) - for listname in config.list_manager.names] - elif dolist: - listname = args.pop(0).lower().strip() - r = do_list(listname, args, func) - - # Now go to interactive mode, perhaps - if opts.interactive: - if dolist: - banner = _( - "The variable 'm' is the $listname mailing list") - else: - banner = interact.DEFAULT_BANNER - overrides = dict(m=LAST_MLIST, r=r, - commit=config.db.commit, - abort=config.db.abort, - config=config) - interact.interact(upframe=False, banner=banner, overrides=overrides) |
