diff options
| author | Barry Warsaw | 2009-01-25 13:01:41 -0500 |
|---|---|---|
| committer | Barry Warsaw | 2009-01-25 13:01:41 -0500 |
| commit | eefd06f1b88b8ecbb23a9013cd223b72ca85c20d (patch) | |
| tree | 72c947fe16fce0e07e996ee74020b26585d7e846 /mailman/attic | |
| parent | 07871212f74498abd56bef3919bf3e029eb8b930 (diff) | |
| download | mailman-eefd06f1b88b8ecbb23a9013cd223b72ca85c20d.tar.gz mailman-eefd06f1b88b8ecbb23a9013cd223b72ca85c20d.tar.zst mailman-eefd06f1b88b8ecbb23a9013cd223b72ca85c20d.zip | |
Diffstat (limited to 'mailman/attic')
| -rw-r--r-- | mailman/attic/Bouncer.py | 250 | ||||
| -rw-r--r-- | mailman/attic/Defaults.py | 1324 | ||||
| -rw-r--r-- | mailman/attic/Deliverer.py | 174 | ||||
| -rw-r--r-- | mailman/attic/Digester.py | 57 | ||||
| -rw-r--r-- | mailman/attic/MailList.py | 731 | ||||
| -rw-r--r-- | mailman/attic/SecurityManager.py | 306 | ||||
| -rwxr-xr-x | mailman/attic/bin/clone_member | 219 | ||||
| -rw-r--r-- | mailman/attic/bin/discard | 120 | ||||
| -rw-r--r-- | mailman/attic/bin/fix_url.py | 93 | ||||
| -rw-r--r-- | mailman/attic/bin/list_admins | 101 | ||||
| -rw-r--r-- | mailman/attic/bin/msgfmt.py | 203 | ||||
| -rw-r--r-- | mailman/attic/bin/po2templ.py | 90 | ||||
| -rw-r--r-- | mailman/attic/bin/pygettext.py | 545 | ||||
| -rwxr-xr-x | mailman/attic/bin/remove_members | 186 | ||||
| -rw-r--r-- | mailman/attic/bin/reset_pw.py | 83 | ||||
| -rwxr-xr-x | mailman/attic/bin/sync_members | 286 | ||||
| -rw-r--r-- | mailman/attic/bin/templ2pot.py | 120 | ||||
| -rwxr-xr-x | mailman/attic/bin/transcheck | 412 |
18 files changed, 0 insertions, 5300 deletions
diff --git a/mailman/attic/Bouncer.py b/mailman/attic/Bouncer.py deleted file mode 100644 index e2de3c915..000000000 --- a/mailman/attic/Bouncer.py +++ /dev/null @@ -1,250 +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/>. - -"""Handle delivery bounces.""" - -import sys -import time -import logging - -from email.MIMEMessage import MIMEMessage -from email.MIMEText import MIMEText - -from mailman import Defaults -from mailman import Message -from mailman import Utils -from mailman import i18n -from mailman.configuration import config -from mailman.interfaces import DeliveryStatus - -EMPTYSTRING = '' - -# This constant is supposed to represent the day containing the first midnight -# after the epoch. We'll add (0,)*6 to this tuple to get a value appropriate -# for time.mktime(). -ZEROHOUR_PLUSONEDAY = time.localtime(60 * 60 * 24)[:3] - -def _(s): return s - -REASONS = { - DeliveryStatus.by_bounces : _('due to excessive bounces'), - DeliveryStatus.by_user : _('by yourself'), - DeliveryStatus.by_moderator : _('by the list administrator'), - DeliveryStatus.unknown : _('for unknown reasons'), - } - -_ = i18n._ - -log = logging.getLogger('mailman.bounce') -slog = logging.getLogger('mailman.subscribe') - - - -class _BounceInfo: - def __init__(self, member, score, date, noticesleft): - self.member = member - self.cookie = None - self.reset(score, date, noticesleft) - - def reset(self, score, date, noticesleft): - self.score = score - self.date = date - self.noticesleft = noticesleft - self.lastnotice = ZEROHOUR_PLUSONEDAY - - def __repr__(self): - # For debugging - return """\ -<bounce info for member %(member)s - current score: %(score)s - last bounce date: %(date)s - email notices left: %(noticesleft)s - last notice date: %(lastnotice)s - confirmation cookie: %(cookie)s - >""" % self.__dict__ - - - -class Bouncer: - def registerBounce(self, member, msg, weight=1.0, day=None): - if not self.isMember(member): - return - info = self.getBounceInfo(member) - if day is None: - # Use today's date - day = time.localtime()[:3] - if not isinstance(info, _BounceInfo): - # This is the first bounce we've seen from this member - info = _BounceInfo(member, weight, day, - self.bounce_you_are_disabled_warnings) - self.setBounceInfo(member, info) - log.info('%s: %s bounce score: %s', self.internal_name(), - member, info.score) - # Continue to the check phase below - elif self.getDeliveryStatus(member) <> DeliveryStatus.enabled: - # The user is already disabled, so we can just ignore subsequent - # bounces. These are likely due to residual messages that were - # sent before disabling the member, but took a while to bounce. - log.info('%s: %s residual bounce received', - self.internal_name(), member) - return - elif info.date == day: - # We've already scored any bounces for this day, so ignore it. - log.info('%s: %s already scored a bounce for date %s', - self.internal_name(), member, - time.strftime('%d-%b-%Y', day + (0,0,0,0,1,0))) - # Continue to check phase below - else: - # See if this member's bounce information is stale. - now = Utils.midnight(day) - lastbounce = Utils.midnight(info.date) - if lastbounce + self.bounce_info_stale_after < now: - # Information is stale, so simply reset it - info.reset(weight, day, self.bounce_you_are_disabled_warnings) - log.info('%s: %s has stale bounce info, resetting', - self.internal_name(), member) - else: - # Nope, the information isn't stale, so add to the bounce - # score and take any necessary action. - info.score += weight - info.date = day - log.info('%s: %s current bounce score: %s', - self.internal_name(), member, info.score) - # Continue to the check phase below - # - # Now that we've adjusted the bounce score for this bounce, let's - # check to see if the disable-by-bounce threshold has been reached. - if info.score >= self.bounce_score_threshold: - if config.VERP_PROBES: - log.info('sending %s list probe to: %s (score %s >= %s)', - self.internal_name(), member, info.score, - self.bounce_score_threshold) - self.sendProbe(member, msg) - info.reset(0, info.date, info.noticesleft) - else: - self.disableBouncingMember(member, info, msg) - - def disableBouncingMember(self, member, info, msg): - # Initialize their confirmation cookie. If we do it when we get the - # first bounce, it'll expire by the time we get the disabling bounce. - cookie = self.pend_new(Pending.RE_ENABLE, self.internal_name(), member) - info.cookie = cookie - # Disable them - if config.VERP_PROBES: - log.info('%s: %s disabling due to probe bounce received', - self.internal_name(), member) - else: - log.info('%s: %s disabling due to bounce score %s >= %s', - self.internal_name(), member, - info.score, self.bounce_score_threshold) - self.setDeliveryStatus(member, DeliveryStatus.by_bounces) - self.sendNextNotification(member) - if self.bounce_notify_owner_on_disable: - self.__sendAdminBounceNotice(member, msg) - - def __sendAdminBounceNotice(self, member, msg): - # BAW: This is a bit kludgey, but we're not providing as much - # information in the new admin bounce notices as we used to (some of - # it was of dubious value). However, we'll provide empty, strange, or - # meaningless strings for the unused %()s fields so that the language - # translators don't have to provide new templates. - text = Utils.maketext( - 'bounce.txt', - {'listname' : self.real_name, - 'addr' : member, - 'negative' : '', - 'did' : _('disabled'), - 'but' : '', - 'reenable' : '', - 'owneraddr': self.no_reply_address, - }, mlist=self) - subject = _('Bounce action notification') - umsg = Message.UserNotification(self.GetOwnerEmail(), - self.no_reply_address, - subject, - lang=self.preferred_language) - # BAW: Be sure you set the type before trying to attach, or you'll get - # a MultipartConversionError. - umsg.set_type('multipart/mixed') - umsg.attach( - MIMEText(text, _charset=Utils.GetCharSet(self.preferred_language))) - if isinstance(msg, str): - umsg.attach(MIMEText(msg)) - else: - umsg.attach(MIMEMessage(msg)) - umsg.send(self) - - def sendNextNotification(self, member): - info = self.getBounceInfo(member) - if info is None: - return - reason = self.getDeliveryStatus(member) - if info.noticesleft <= 0: - # BAW: Remove them now, with a notification message - self.ApprovedDeleteMember( - member, 'disabled address', - admin_notif=self.bounce_notify_owner_on_removal, - userack=1) - # Expunge the pending cookie for the user. We throw away the - # returned data. - self.pend_confirm(info.cookie) - if reason == DeliveryStatus.by_bounces: - log.info('%s: %s deleted after exhausting notices', - self.internal_name(), member) - slog.info('%s: %s auto-unsubscribed [reason: %s]', - self.internal_name(), member, - {DeliveryStatus.by_bounces: 'BYBOUNCE', - DeliveryStatus.by_user: 'BYUSER', - DeliveryStatus.by_moderator: 'BYADMIN', - DeliveryStatus.unknown: 'UNKNOWN'}.get( - reason, 'invalid value')) - return - # Send the next notification - confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1), - info.cookie) - optionsurl = self.GetOptionsURL(member, absolute=1) - reqaddr = self.GetRequestEmail() - lang = self.getMemberLanguage(member) - txtreason = REASONS.get(reason) - if txtreason is None: - txtreason = _('for unknown reasons') - else: - txtreason = _(txtreason) - # Give a little bit more detail on bounce disables - if reason == DeliveryStatus.by_bounces: - date = time.strftime('%d-%b-%Y', - time.localtime(Utils.midnight(info.date))) - extra = _(' The last bounce received from you was dated %(date)s') - txtreason += extra - text = Utils.maketext( - 'disabled.txt', - {'listname' : self.real_name, - 'noticesleft': info.noticesleft, - 'confirmurl' : confirmurl, - 'optionsurl' : optionsurl, - 'password' : self.getMemberPassword(member), - 'owneraddr' : self.GetOwnerEmail(), - 'reason' : txtreason, - }, lang=lang, mlist=self) - msg = Message.UserNotification(member, reqaddr, text=text, lang=lang) - # BAW: See the comment in MailList.py ChangeMemberAddress() for why we - # set the Subject this way. - del msg['subject'] - msg['Subject'] = 'confirm ' + info.cookie - msg.send(self) - info.noticesleft -= 1 - info.lastnotice = time.localtime()[:3] diff --git a/mailman/attic/Defaults.py b/mailman/attic/Defaults.py deleted file mode 100644 index 6f72ed535..000000000 --- a/mailman/attic/Defaults.py +++ /dev/null @@ -1,1324 +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/>. - -"""Distributed default settings for significant Mailman config variables.""" - -from datetime import timedelta - -from mailman.interfaces.mailinglist import ReplyToMunging - - - -class CompatibleTimeDelta(timedelta): - def __float__(self): - # Convert to float seconds. - return (self.days * 24 * 60 * 60 + - self.seconds + self.microseconds / 1.0e6) - - def __int__(self): - return int(float(self)) - - -def seconds(s): - return CompatibleTimeDelta(seconds=s) - -def minutes(m): - return CompatibleTimeDelta(minutes=m) - -def hours(h): - return CompatibleTimeDelta(hours=h) - -def days(d): - return CompatibleTimeDelta(days=d) - - -# Some convenient constants -Yes = yes = On = on = True -No = no = Off = off = False - - - -##### -# General system-wide defaults -##### - -# Should image logos be used? Set this to 0 to disable image logos from "our -# sponsors" and just use textual links instead (this will also disable the -# shortcut "favicon"). Otherwise, this should contain the URL base path to -# the logo images (and must contain the trailing slash).. If you want to -# disable Mailman's logo footer altogther, hack -# mailman/htmlformat.py:MailmanLogo(), which also contains the hardcoded links -# and image names. -IMAGE_LOGOS = '/icons/' - -# The name of the Mailman favicon -SHORTCUT_ICON = 'mm-icon.png' - -# Don't change MAILMAN_URL, unless you want to point it at one of the mirrors. -MAILMAN_URL = 'http://www.gnu.org/software/mailman/index.html' -#MAILMAN_URL = 'http://www.list.org/' -#MAILMAN_URL = 'http://mailman.sf.net/' - -DEFAULT_URL_PATTERN = 'http://%s/mailman/' - -# This address is used as the from address whenever a message comes from some -# entity to which there is no natural reply recipient. Set this to a real -# human or to /dev/null. It will be appended with the hostname of the list -# involved or the DEFAULT_EMAIL_HOST if none is available. Address must not -# bounce and it must not point to a Mailman process. -NO_REPLY_ADDRESS = 'noreply' - -# This address is the "site owner" address. Certain messages which must be -# delivered to a human, but which can't be delivered to a list owner (e.g. a -# bounce from a list owner), will be sent to this address. It should point to -# a human. -SITE_OWNER_ADDRESS = 'changeme@example.com' - -# Normally when a site administrator authenticates to a web page with the site -# password, they get a cookie which authorizes them as the list admin. It -# makes me nervous to hand out site auth cookies because if this cookie is -# cracked or intercepted, the intruder will have access to every list on the -# site. OTOH, it's dang handy to not have to re-authenticate to every list on -# the site. Set this value to Yes to allow site admin cookies. -ALLOW_SITE_ADMIN_COOKIES = No - -# Command that is used to convert text/html parts into plain text. This -# should output results to standard output. %(filename)s will contain the -# name of the temporary file that the program should operate on. -HTML_TO_PLAIN_TEXT_COMMAND = '/usr/bin/lynx -dump %(filename)s' - -# Default password hashing scheme. See 'bin/mmsitepass -P' for a list of -# available schemes. -PASSWORD_SCHEME = 'ssha' - -# Default run-time directory. -DEFAULT_VAR_DIRECTORY = '/var/mailman' - - - -##### -# Database options -##### - -# Use this to set the SQLAlchemy database engine URL. You generally have one -# primary database connection for all of Mailman. List data and most rosters -# will store their data in this database, although external rosters may access -# other databases in their own way. This string support substitutions using -# any variable in the Configuration object. -DEFAULT_DATABASE_URL = 'sqlite:///$DATA_DIR/mailman.db' - - - -##### -# Spam avoidance defaults -##### - -# This variable contains a list of tuple of the format: -# -# (header, pattern[, chain]) -# -# which is used to match against the current message's headers. If the -# pattern matches the given header in the current message, then the named -# chain is jumped to. header is case-insensitive and should not include the -# trailing colon. pattern is always matched with re.IGNORECASE. chain is -# optional; if not given the 'hold' chain is used, but if given it may be any -# existing chain, such as 'discard', 'reject', or 'accept'. -# -# Note that the more searching done, the slower the whole process gets. -# Header matching is run against all messages coming to either the list, or -# the -owners address, unless the message is explicitly approved. -HEADER_MATCHES = [] - - - -##### -# Web UI defaults -##### - -# Almost all the colors used in Mailman's web interface are parameterized via -# the following variables. This lets you easily change the color schemes for -# your preferences without having to do major surgery on the source code. -# Note that in general, the template colors are not included here since it is -# easy enough to override the default template colors via site-wide, -# vdomain-wide, or list-wide specializations. - -WEB_BG_COLOR = 'white' # Page background -WEB_HEADER_COLOR = '#99ccff' # Major section headers -WEB_SUBHEADER_COLOR = '#fff0d0' # Minor section headers -WEB_ADMINITEM_COLOR = '#dddddd' # Option field background -WEB_ADMINPW_COLOR = '#99cccc' # Password box color -WEB_ERROR_COLOR = 'red' # Error message foreground -WEB_LINK_COLOR = '' # If true, forces LINK= -WEB_ALINK_COLOR = '' # If true, forces ALINK= -WEB_VLINK_COLOR = '' # If true, forces VLINK= -WEB_HIGHLIGHT_COLOR = '#dddddd' # If true, alternating rows - # in listinfo & admin display -# CGI file extension. -CGIEXT = '' - - - -##### -# Archive defaults -##### - -# The url template for the public archives. This will be used in several -# places, including the List-Archive: header, links to the archive on the -# list's listinfo page, and on the list's admin page. -# -# This variable supports several substitution variables -# - $hostname -- the host on which the archive resides -# - $listname -- the short name of the list being accessed -# - $fqdn_listname -- the long name of the list being accessed -PUBLIC_ARCHIVE_URL = 'http://$hostname/pipermail/$fqdn_listname' - -# The public Mail-Archive.com service's base url. -MAIL_ARCHIVE_BASEURL = 'http://go.mail-archive.com/' -# The posting address for the Mail-Archive.com service -MAIL_ARCHIVE_RECIPIENT = 'archive@mail-archive.com' - -# The command for archiving to a local MHonArc instance. -MHONARC_COMMAND = """\ -/usr/bin/mhonarc \ --add \ --dbfile $PRIVATE_ARCHIVE_FILE_DIR/${listname}.mbox/mhonarc.db \ --outdir $VAR_DIR/mhonarc/${listname} \ --stderr $LOG_DIR/mhonarc \ --stdout $LOG_DIR/mhonarc \ --spammode \ --umask 022""" - -# Are archives on or off by default? -DEFAULT_ARCHIVE = On - -# Are archives public or private by default? -# 0=public, 1=private -DEFAULT_ARCHIVE_PRIVATE = 0 - -# ARCHIVE_TO_MBOX -#-1 - do not do any archiving -# 0 - do not archive to mbox, use builtin mailman html archiving only -# 1 - do not use builtin mailman html archiving, archive to mbox only -# 2 - archive to both mbox and builtin mailman html archiving. -# See the settings below for PUBLIC_EXTERNAL_ARCHIVER and -# PRIVATE_EXTERNAL_ARCHIVER which can be used to replace mailman's -# builtin html archiving with an external archiver. The flat mail -# mbox file can be useful for searching, and is another way to -# interface external archivers, etc. -ARCHIVE_TO_MBOX = 2 - -# 0 - yearly -# 1 - monthly -# 2 - quarterly -# 3 - weekly -# 4 - daily -DEFAULT_ARCHIVE_VOLUME_FREQUENCY = 1 -DEFAULT_DIGEST_VOLUME_FREQUENCY = 1 - -# These variables control the use of an external archiver. Normally if -# archiving is turned on (see ARCHIVE_TO_MBOX above and the list's archive* -# attributes) the internal Pipermail archiver is used. This is the default if -# both of these variables are set to No. When either is set, the value should -# be a shell command string which will get passed to os.popen(). This string -# can contain the following substitution strings: -# -# $listname -- gets the internal name of the list -# $hostname -- gets the email hostname for the list -# -# being archived will be substituted for this. Please note that os.popen() is -# used. -# -# Note that if you set one of these variables, you should set both of them -# (they can be the same string). This will mean your external archiver will -# be used regardless of whether public or private archives are selected. -PUBLIC_EXTERNAL_ARCHIVER = No -PRIVATE_EXTERNAL_ARCHIVER = No - -# A filter module that converts from multipart messages to "flat" messages -# (i.e. containing a single payload). This is required for Pipermail, and you -# may want to set it to 0 for external archivers. You can also replace it -# with your own module as long as it contains a process() function that takes -# a MailList object and a Message object. It should raise -# Errors.DiscardMessage if it wants to throw the message away. Otherwise it -# should modify the Message object as necessary. -ARCHIVE_SCRUBBER = 'mailman.pipeline.scrubber' - -# Control parameter whether mailman.Handlers.Scrubber should use message -# attachment's filename as is indicated by the filename parameter or use -# 'attachement-xxx' instead. The default is set True because the applications -# on PC and Mac begin to use longer non-ascii filenames. Historically, it -# was set False in 2.1.6 for backward compatiblity but it was reset to True -# for safer operation in mailman-2.1.7. -SCRUBBER_DONT_USE_ATTACHMENT_FILENAME = True - -# Use of attachment filename extension per se is may be dangerous because -# virus fakes it. You can set this True if you filter the attachment by -# filename extension -SCRUBBER_USE_ATTACHMENT_FILENAME_EXTENSION = False - -# This variable defines what happens to text/html subparts. They can be -# stripped completely, escaped, or filtered through an external program. The -# legal values are: -# 0 - Strip out text/html parts completely, leaving a notice of the removal in -# the message. If the outer part is text/html, the entire message is -# discarded. -# 1 - Remove any embedded text/html parts, leaving them as HTML-escaped -# attachments which can be separately viewed. Outer text/html parts are -# simply HTML-escaped. -# 2 - Leave it inline, but HTML-escape it -# 3 - Remove text/html as attachments but don't HTML-escape them. Note: this -# is very dangerous because it essentially means anybody can send an HTML -# email to your site containing evil JavaScript or web bugs, or other -# nasty things, and folks viewing your archives will be susceptible. You -# should only consider this option if you do heavy moderation of your list -# postings. -# -# Note: given the current archiving code, it is not possible to leave -# text/html parts inline and un-escaped. I wouldn't think it'd be a good idea -# to do anyway. -# -# The value can also be a string, in which case it is the name of a command to -# filter the HTML page through. The resulting output is left in an attachment -# or as the entirety of the message when the outer part is text/html. The -# format of the string must include a "%(filename)s" which will contain the -# name of the temporary file that the program should operate on. It should -# write the processed message to stdout. Set this to -# HTML_TO_PLAIN_TEXT_COMMAND to specify an HTML to plain text conversion -# program. -ARCHIVE_HTML_SANITIZER = 1 - -# Set this to Yes to enable gzipping of the downloadable archive .txt file. -# Note that this is /extremely/ inefficient, so an alternative is to just -# collect the messages in the associated .txt file and run a cron job every -# night to generate the txt.gz file. See cron/nightly_gzip for details. -GZIP_ARCHIVE_TXT_FILES = No - -# This sets the default `clobber date' policy for the archiver. When a -# message is to be archived either by Pipermail or an external archiver, -# Mailman can modify the Date: header to be the date the message was received -# instead of the Date: in the original message. This is useful if you -# typically receive messages with outrageous dates. Set this to 0 to retain -# the date of the original message, or to 1 to always clobber the date. Set -# it to 2 to perform `smart overrides' on the date; when the date is outside -# ARCHIVER_ALLOWABLE_SANE_DATE_SKEW (either too early or too late), then the -# received date is substituted instead. -ARCHIVER_CLOBBER_DATE_POLICY = 2 -ARCHIVER_ALLOWABLE_SANE_DATE_SKEW = days(15) - -# Pipermail archives contain the raw email addresses of the posting authors. -# Some view this as a goldmine for spam harvesters. Set this to Yes to -# moderately obscure email addresses, but note that this breaks mailto: URLs -# in the archives too. -ARCHIVER_OBSCURES_EMAILADDRS = Yes - -# Pipermail assumes that messages bodies contain US-ASCII text. -# Change this option to define a different character set to be used as -# the default character set for the archive. The term "character set" -# is used in MIME to refer to a method of converting a sequence of -# octets into a sequence of characters. If you change the default -# charset, you might need to add it to VERBATIM_ENCODING below. -DEFAULT_CHARSET = None - -# Most character set encodings require special HTML entity characters to be -# quoted, otherwise they won't look right in the Pipermail archives. However -# some character sets must not quote these characters so that they can be -# rendered properly in the browsers. The primary issue is multi-byte -# encodings where the octet 0x26 does not always represent the & character. -# This variable contains a list of such characters sets which are not -# HTML-quoted in the archives. -VERBATIM_ENCODING = ['iso-2022-jp'] - -# When the archive is public, should Mailman also make the raw Unix mbox file -# publically available? -PUBLIC_MBOX = No - - - -##### -# Delivery defaults -##### - -# Final delivery module for outgoing mail. This handler is used for message -# delivery to the list via the smtpd, and to an individual user. This value -# must be a string naming an IHandler. -DELIVERY_MODULE = 'smtp-direct' - -# MTA should name a module in mailman/MTA which provides the MTA specific -# functionality for creating and removing lists. Some MTAs like Exim can be -# configured to automatically recognize new lists, in which case the MTA -# variable should be set to None. Use 'Manual' to print new aliases to -# standard out (or send an email to the site list owner) for manual twiddling -# of an /etc/aliases style file. Use 'Postfix' if you are using the Postfix -# MTA -- but then also see POSTFIX_STYLE_VIRTUAL_DOMAINS. -MTA = 'Manual' - -# If you set MTA='Postfix', then you also want to set the following variable, -# depending on whether you're using virtual domains in Postfix, and which -# style of virtual domain you're using. Set this to the empty list if you're -# not using virtual domains in Postfix, or if you're using Sendmail-style -# virtual domains (where all addresses are visible in all domains). If you're -# using Postfix-style virtual domains, where aliases should only show up in -# the virtual domain, set this variable to the list of host_name values to -# write separate virtual entries for. I.e. if you run dom1.ain, dom2.ain, and -# dom3.ain, but only dom2 and dom3 are virtual, set this variable to the list -# ['dom2.ain', 'dom3.ain']. Matches are done against the host_name attribute -# of the mailing lists. See the Postfix section of the installation manual -# for details. -POSTFIX_STYLE_VIRTUAL_DOMAINS = [] - -# We should use a separator in place of '@' for list-etc@dom2.ain in both -# aliases and mailman-virtual files. -POSTFIX_VIRTUAL_SEPARATOR = '_at_' - -# These variables describe the program to use for regenerating the aliases.db -# and virtual-mailman.db files, respectively, from the associated plain text -# files. The file being updated will be appended to this string (with a -# separating space), so it must be appropriate for os.system(). -POSTFIX_ALIAS_CMD = '/usr/sbin/postalias' -POSTFIX_MAP_CMD = '/usr/sbin/postmap' - -# Ceiling on the number of recipients that can be specified in a single SMTP -# transaction. Set to 0 to submit the entire recipient list in one -# transaction. Only used with the SMTPDirect DELIVERY_MODULE. -SMTP_MAX_RCPTS = 500 - -# Ceiling on the number of SMTP sessions to perform on a single socket -# connection. Some MTAs have limits. Set this to 0 to do as many as we like -# (i.e. your MTA has no limits). Set this to some number great than 0 and -# Mailman will close the SMTP connection and re-open it after this number of -# consecutive sessions. -SMTP_MAX_SESSIONS_PER_CONNECTION = 0 - -# Maximum number of simultaneous subthreads that will be used for SMTP -# delivery. After the recipients list is chunked according to SMTP_MAX_RCPTS, -# each chunk is handed off to the smptd by a separate such thread. If your -# Python interpreter was not built for threads, this feature is disabled. You -# can explicitly disable it in all cases by setting MAX_DELIVERY_THREADS to -# 0. This feature is only supported with the SMTPDirect DELIVERY_MODULE. -# -# NOTE: This is an experimental feature and limited testing shows that it may -# in fact degrade performance, possibly due to Python's global interpreter -# lock. Use with caution. -MAX_DELIVERY_THREADS = 0 - -# SMTP host and port, when DELIVERY_MODULE is 'SMTPDirect'. Make sure the -# host exists and is resolvable (i.e., if it's the default of "localhost" be -# sure there's a localhost entry in your /etc/hosts file!) -SMTPHOST = 'localhost' -SMTPPORT = 0 # default from smtplib - -# Command for direct command pipe delivery to sendmail compatible program, -# when DELIVERY_MODULE is 'Sendmail'. -SENDMAIL_CMD = '/usr/lib/sendmail' - -# Set these variables if you need to authenticate to your NNTP server for -# Usenet posting or reading. If no authentication is necessary, specify None -# for both variables. -NNTP_USERNAME = None -NNTP_PASSWORD = None - -# Set this if you have an NNTP server you prefer gatewayed lists to use. -DEFAULT_NNTP_HOST = u'' - -# These variables controls how headers must be cleansed in order to be -# accepted by your NNTP server. Some servers like INN reject messages -# containing prohibited headers, or duplicate headers. The NNTP server may -# reject the message for other reasons, but there's little that can be -# programmatically done about that. See mailman/Queue/NewsRunner.py -# -# First, these headers (case ignored) are removed from the original message. -NNTP_REMOVE_HEADERS = ['nntp-posting-host', 'nntp-posting-date', 'x-trace', - 'x-complaints-to', 'xref', 'date-received', 'posted', - 'posting-version', 'relay-version', 'received'] - -# Next, these headers are left alone, unless there are duplicates in the -# original message. Any second and subsequent headers are rewritten to the -# second named header (case preserved). -NNTP_REWRITE_DUPLICATE_HEADERS = [ - ('To', 'X-Original-To'), - ('CC', 'X-Original-CC'), - ('Content-Transfer-Encoding', 'X-Original-Content-Transfer-Encoding'), - ('MIME-Version', 'X-MIME-Version'), - ] - -# Some list posts and mail to the -owner address may contain DomainKey or -# DomainKeys Identified Mail (DKIM) signature headers <http://www.dkim.org/>. -# Various list transformations to the message such as adding a list header or -# footer or scrubbing attachments or even reply-to munging can break these -# signatures. It is generally felt that these signatures have value, even if -# broken and even if the outgoing message is resigned. However, some sites -# may wish to remove these headers by setting this to Yes. -REMOVE_DKIM_HEADERS = No - -# This is the pipeline which messages sent to the -owner address go through -OWNER_PIPELINE = [ - 'SpamDetect', - 'Replybot', - 'CleanseDKIM', - 'OwnerRecips', - 'ToOutgoing', - ] - - -# This defines a logging subsystem confirmation file, which overrides the -# default log settings. This is a ConfigParser formatted file which can -# contain sections named after the logger name (without the leading 'mailman.' -# common prefix). Each section may contain the following options: -# -# - level -- Overrides the default level; this may be any of the -# standard Python logging levels, case insensitive. -# - format -- Overrides the default format string; see below. -# - datefmt -- Overrides the default date format string; see below. -# - path -- Overrides the default logger path. This may be a relative -# path name, in which case it is relative to Mailman's LOG_DIR, -# or it may be an absolute path name. You cannot change the -# handler class that will be used. -# - propagate -- Boolean specifying whether to propagate log message from this -# logger to the root "mailman" logger. You cannot override -# settings for the root logger. -# -# The file name may be absolute, or relative to Mailman's etc directory. -LOG_CONFIG_FILE = None - -# This defines log format strings for the SMTPDirect delivery module (see -# DELIVERY_MODULE above). Valid %()s string substitutions include: -# -# time -- the time in float seconds that it took to complete the smtp -# hand-off of the message from Mailman to your smtpd. -# -# size -- the size of the entire message, in bytes -# -# #recips -- the number of actual recipients for this message. -# -# #refused -- the number of smtp refused recipients (use this only in -# SMTP_LOG_REFUSED). -# -# listname -- the `internal' name of the mailing list for this posting -# -# msg_<header> -- the value of the delivered message's given header. If -# the message had no such header, then "n/a" will be used. Note though -# that if the message had multiple such headers, then it is undefined -# which will be used. -# -# allmsg_<header> - Same as msg_<header> above, but if there are multiple -# such headers in the message, they will all be printed, separated by -# comma-space. -# -# sender -- the "sender" of the messages, which will be the From: or -# envelope-sender as determeined by the USE_ENVELOPE_SENDER variable -# below. -# -# The format of the entries is a 2-tuple with the first element naming the -# logger (as a child of the root 'mailman' logger) to print the message to, -# and the second being a format string appropriate for Python's %-style string -# interpolation. The file name is arbitrary; qfiles/<name> will be created -# automatically if it does not exist. - -# The format of the message printed for every delivered message, regardless of -# whether the delivery was successful or not. Set to None to disable the -# printing of this log message. -SMTP_LOG_EVERY_MESSAGE = ( - 'smtp', - ('${message-id} smtp to $listname for ${#recips} recips, ' - 'completed in $time seconds')) - -# This will only be printed if there were no immediate smtp failures. -# Mutually exclusive with SMTP_LOG_REFUSED. -SMTP_LOG_SUCCESS = ( - 'post', - '${message-id} post to $listname from $sender, size=$size, success') - -# This will only be printed if there were any addresses which encountered an -# immediate smtp failure. Mutually exclusive with SMTP_LOG_SUCCESS. -SMTP_LOG_REFUSED = ( - 'post', - ('${message-id} post to $listname from $sender, size=$size, ' - '${#refused} failures')) - -# This will be logged for each specific recipient failure. Additional %()s -# keys are: -# -# recipient -- the failing recipient address -# failcode -- the smtp failure code -# failmsg -- the actual smtp message, if available -SMTP_LOG_EACH_FAILURE = ( - 'smtp-failure', - ('${message-id} delivery to $recipient failed with code $failcode: ' - '$failmsg')) - -# These variables control the format and frequency of VERP-like delivery for -# better bounce detection. VERP is Variable Envelope Return Path, defined -# here: -# -# http://cr.yp.to/proto/verp.txt -# -# This involves encoding the address of the recipient as we (Mailman) know it -# into the envelope sender address (i.e. the SMTP `MAIL FROM:' address). -# Thus, no matter what kind of forwarding the recipient has in place, should -# it eventually bounce, we will receive an unambiguous notice of the bouncing -# address. -# -# However, we're technically only "VERP-like" because we're doing the envelope -# sender encoding in Mailman, not in the MTA. We do require cooperation from -# the MTA, so you must be sure your MTA can be configured for extended address -# semantics. -# -# The first variable describes how to encode VERP envelopes. It must contain -# these three string interpolations: -# -# %(bounces)s -- the list-bounces mailbox will be set here -# %(mailbox)s -- the recipient's mailbox will be set here -# %(host)s -- the recipient's host name will be set here -# -# This example uses the default below. -# -# FQDN list address is: mylist@dom.ain -# Recipient is: aperson@a.nother.dom -# -# The envelope sender will be mylist-bounces+aperson=a.nother.dom@dom.ain -# -# Note that your MTA /must/ be configured to deliver such an addressed message -# to mylist-bounces! -VERP_DELIMITER = '+' -VERP_FORMAT = '%(bounces)s+%(mailbox)s=%(host)s' - -# The second describes a regular expression to unambiguously decode such an -# address, which will be placed in the To: header of the bounce message by the -# bouncing MTA. Getting this right is critical -- and tricky. Learn your -# Python regular expressions. It must define exactly three named groups, -# bounces, mailbox and host, with the same definition as above. It will be -# compiled case-insensitively. -VERP_REGEXP = r'^(?P<bounces>[^+]+?)\+(?P<mailbox>[^=]+)=(?P<host>[^@]+)@.*$' - -# VERP format and regexp for probe messages -VERP_PROBE_FORMAT = '%(bounces)s+%(token)s' -VERP_PROBE_REGEXP = r'^(?P<bounces>[^+]+?)\+(?P<token>[^@]+)@.*$' -# Set this Yes to activate VERP probe for disabling by bounce -VERP_PROBES = No - -# A perfect opportunity for doing VERP is the password reminders, which are -# already addressed individually to each recipient. Set this to Yes to enable -# VERPs on all password reminders. -VERP_PASSWORD_REMINDERS = No - -# Another good opportunity is when regular delivery is personalized. Here -# again, we're already incurring the performance hit for addressing each -# individual recipient. Set this to Yes to enable VERPs on all personalized -# regular deliveries (personalized digests aren't supported yet). -VERP_PERSONALIZED_DELIVERIES = No - -# And finally, we can VERP normal, non-personalized deliveries. However, -# because it can be a significant performance hit, we allow you to decide how -# often to VERP regular deliveries. This is the interval, in number of -# messages, to do a VERP recipient address. The same variable controls both -# regular and digest deliveries. Set to 0 to disable occasional VERPs, set to -# 1 to VERP every delivery, or to some number > 1 for only occasional VERPs. -VERP_DELIVERY_INTERVAL = 0 - -# For nicer confirmation emails, use a VERP-like format which encodes the -# confirmation cookie in the reply address. This lets us put a more user -# friendly Subject: on the message, but requires cooperation from the MTA. -# Format is like VERP_FORMAT above, but with the following substitutions: -# -# $address -- the list-confirm address -# $cookie -- the confirmation cookie -VERP_CONFIRM_FORMAT = '$address+$cookie' - -# This is analogous to VERP_REGEXP, but for splitting apart the -# VERP_CONFIRM_FORMAT. MUAs have been observed that mung -# From: local_part@host -# into -# To: "local_part" <local_part@host> -# when replying, so we skip everything up to '<' if any. -VERP_CONFIRM_REGEXP = r'^(.*<)?(?P<addr>[^+]+?)\+(?P<cookie>[^@]+)@.*$' - -# Set this to Yes to enable VERP-like (more user friendly) confirmations -VERP_CONFIRMATIONS = No - -# This is the maximum number of automatic responses sent to an address because -# of -request messages or posting hold messages. This limit prevents response -# loops between Mailman and misconfigured remote email robots. Mailman -# already inhibits automatic replies to any message labeled with a header -# "Precendence: bulk|list|junk". This is a fallback safety valve so it should -# be set fairly high. Set to 0 for no limit (probably useful only for -# debugging). -MAX_AUTORESPONSES_PER_DAY = 10 - - - -##### -# Qrunner defaults -##### - -# Which queues should the qrunner master watchdog spawn? add_qrunner() takes -# one required argument, which is the name of the qrunner to start -# (capitalized and without the 'Runner' suffix). Optional second argument -# specifies the number of parallel processes to fork for each qrunner. If -# more than one process is used, each will take an equal subdivision of the -# hash space, so the number must be a power of 2. -# -# del_qrunners() takes one argument which is the name of the qrunner not to -# start. This is used because by default, Mailman starts the Arch, Bounce, -# Command, Incoming, News, Outgoing, Retry, and Virgin queues. -# -# Set this to Yes to use the `Maildir' delivery option. If you change this -# you will need to re-run bin/genaliases for MTAs that don't use list -# auto-detection. -# -# WARNING: If you want to use Maildir delivery, you /must/ start Mailman's -# qrunner as root, or you will get permission problems. -USE_MAILDIR = No - -# Set this to Yes to use the `LMTP' delivery option. If you change this -# you will need to re-run bin/genaliases for MTAs that don't use list -# auto-detection. -# -# You have to set following line in postfix main.cf: -# transport_maps = hash:<prefix>/data/transport -# Also needed is following line if your list is in $mydestination: -# alias_maps = hash:/etc/aliases, hash:<prefix>/data/aliases -USE_LMTP = No - -# Name of the domains which operate on LMTP Mailman only. Currently valid -# only for Postfix alias generation. -LMTP_ONLY_DOMAINS = [] - -# If the list is not present in LMTP_ONLY_DOMAINS, LMTPRunner would return -# 550 response to the master SMTP agent. This may cause 'bounce spam relay' -# in that a spammer expects to deliver the message as bounce info to the -# 'From:' address. You can override this behavior by setting -# LMTP_ERR_550 = '250 Ok. But, blackholed because mailbox unavailable'. -LMTP_ERR_550 = '550 Requested action not taken: mailbox unavailable' - -# WSGI Server. -# -# You must enable PROXY of Apache httpd server and configure to pass Mailman -# CGI requests to this WSGI Server: -# -# ProxyPass /mailman/ http://localhost:2580/mailman/ -# -# Note that local URI part should be the same. -# XXX If you are running Apache 2.2, you will probably also want to set -# ProxyPassReverseCookiePath -# -# Also you have to add following line to <prefix>/etc/mailman.cfg -# add_qrunner('HTTP') -HTTP_HOST = 'localhost' -HTTP_PORT = 2580 - -# After processing every file in the qrunner's slice, how long should the -# runner sleep for before checking the queue directory again for new files? -# This can be a fraction of a second, or zero to check immediately -# (essentially busy-loop as fast as possible). -QRUNNER_SLEEP_TIME = seconds(1) - -# When a message that is unparsable (by the email package) is received, what -# should we do with it? The most common cause of unparsable messages is -# broken MIME encapsulation, and the most common cause of that is viruses like -# Nimda. Set this variable to No to discard such messages, or to Yes to store -# them in qfiles/bad subdirectory. -QRUNNER_SAVE_BAD_MESSAGES = Yes - -# This flag causes Mailman to fsync() its data files after writing and -# flushing its contents. While this ensures the data is written to disk, -# avoiding data loss, it may be a performance killer. Note that this flag -# affects both message pickles and MailList config.pck files. -SYNC_AFTER_WRITE = No - -# The maximum number of times that the mailmanctl watcher will try to restart -# a qrunner that exits uncleanly. -MAX_RESTARTS = 10 - - - -##### -# General defaults -##### - -# The default language for this server. Whenever we can't figure out the list -# context or user context, we'll fall back to using this language. This code -# must be in the list of available language codes. -DEFAULT_SERVER_LANGUAGE = u'en' - -# When allowing only members to post to a mailing list, how is the sender of -# the message determined? If this variable is set to Yes, then first the -# message's envelope sender is used, with a fallback to the sender if there is -# no envelope sender. Set this variable to No to always use the sender. -# -# The envelope sender is set by the SMTP delivery and is thus less easily -# spoofed than the sender, which is typically just taken from the From: header -# and thus easily spoofed by the end-user. However, sometimes the envelope -# sender isn't set correctly and this will manifest itself by postings being -# held for approval even if they appear to come from a list member. If you -# are having this problem, set this variable to No, but understand that some -# spoofed messages may get through. -USE_ENVELOPE_SENDER = No - -# Membership tests for posting purposes are usually performed by looking at a -# set of headers, passing the test if any of their values match a member of -# the list. Headers are checked in the order given in this variable. The -# value None means use the From_ (envelope sender) header. Field names are -# case insensitive. -SENDER_HEADERS = ('from', None, 'reply-to', 'sender') - -# How many members to display at a time on the admin cgi to unsubscribe them -# or change their options? -DEFAULT_ADMIN_MEMBER_CHUNKSIZE = 30 - -# how many bytes of a held message post should be displayed in the admindb web -# page? Use a negative number to indicate the entire message, regardless of -# size (though this will slow down rendering those pages). -ADMINDB_PAGE_TEXT_LIMIT = 4096 - -# Set this variable to Yes to allow list owners to delete their own mailing -# lists. You may not want to give them this power, in which case, setting -# this variable to No instead requires list removal to be done by the site -# administrator, via the command line script bin/rmlist. -OWNERS_CAN_DELETE_THEIR_OWN_LISTS = No - -# Set this variable to Yes to allow list owners to set the "personalized" -# flags on their mailing lists. Turning these on tells Mailman to send -# separate email messages to each user instead of batching them together for -# delivery to the MTA. This gives each member a more personalized message, -# but can have a heavy impact on the performance of your system. -OWNERS_CAN_ENABLE_PERSONALIZATION = No - -# Should held messages be saved on disk as Python pickles or as plain text? -# The former is more efficient since we don't need to go through the -# parse/generate roundtrip each time, but the latter might be preferred if you -# want to edit the held message on disk. -HOLD_MESSAGES_AS_PICKLES = Yes - -# This variable controls the order in which list-specific category options are -# presented in the admin cgi page. -ADMIN_CATEGORIES = [ - # First column - 'general', 'passwords', 'language', 'members', 'nondigest', 'digest', - # Second column - 'privacy', 'bounce', 'archive', 'gateway', 'autoreply', - 'contentfilter', 'topics', - ] - -# See "Bitfield for user options" below; make this a sum of those options, to -# make all new members of lists start with those options flagged. We assume -# by default that people don't want to receive two copies of posts. Note -# however that the member moderation flag's initial value is controlled by the -# list's config variable default_member_moderation. -DEFAULT_NEW_MEMBER_OPTIONS = 256 - -# Specify the type of passwords to use, when Mailman generates the passwords -# itself, as would be the case for membership requests where the user did not -# fill in a password, or during list creation, when auto-generation of admin -# passwords was selected. -# -# Set this value to Yes for classic Mailman user-friendly(er) passwords. -# These generate semi-pronounceable passwords which are easier to remember. -# Set this value to No to use more cryptographically secure, but harder to -# remember, passwords -- if your operating system and Python version support -# the necessary feature (specifically that /dev/urandom be available). -USER_FRIENDLY_PASSWORDS = Yes -# This value specifies the default lengths of member and list admin passwords -MEMBER_PASSWORD_LENGTH = 8 -ADMIN_PASSWORD_LENGTH = 10 - - - -##### -# List defaults. NOTE: Changing these values does NOT change the -# configuration of an existing list. It only defines the default for new -# lists you subsequently create. -##### - -# Should a list, by default be advertised? What is the default maximum number -# of explicit recipients allowed? What is the default maximum message size -# allowed? -DEFAULT_LIST_ADVERTISED = Yes -DEFAULT_MAX_NUM_RECIPIENTS = 10 -DEFAULT_MAX_MESSAGE_SIZE = 40 # KB - -# These format strings will be expanded w.r.t. the dictionary for the -# mailing list instance. -DEFAULT_SUBJECT_PREFIX = u'[$mlist.real_name] ' -# DEFAULT_SUBJECT_PREFIX = "[$mlist.real_name %%d]" # for numbering -DEFAULT_MSG_HEADER = u'' -DEFAULT_MSG_FOOTER = u"""\ -_______________________________________________ -$real_name mailing list -$fqdn_listname -${listinfo_page} -""" - -# Scrub regular delivery -DEFAULT_SCRUB_NONDIGEST = False - -# Mail command processor will ignore mail command lines after designated max. -EMAIL_COMMANDS_MAX_LINES = 10 - -# Is the list owner notified of admin requests immediately by mail, as well as -# by daily pending-request reminder? -DEFAULT_ADMIN_IMMED_NOTIFY = Yes - -# Is the list owner notified of subscribes/unsubscribes? -DEFAULT_ADMIN_NOTIFY_MCHANGES = No - -# Discard held messages after this days -DEFAULT_MAX_DAYS_TO_HOLD = 0 - -# Should list members, by default, have their posts be moderated? -DEFAULT_DEFAULT_MEMBER_MODERATION = No - -# Should non-member posts which are auto-discarded also be forwarded to the -# moderators? -DEFAULT_FORWARD_AUTO_DISCARDS = Yes - -# What shold happen to non-member posts which are do not match explicit -# non-member actions? -# 0 = Accept -# 1 = Hold -# 2 = Reject -# 3 = Discard -DEFAULT_GENERIC_NONMEMBER_ACTION = 1 - -# Bounce if 'To:', 'Cc:', or 'Resent-To:' fields don't explicitly name list? -# This is an anti-spam measure -DEFAULT_REQUIRE_EXPLICIT_DESTINATION = Yes - -# Alternate names acceptable as explicit destinations for this list. -DEFAULT_ACCEPTABLE_ALIASES = """ -""" -# For mailing lists that have only other mailing lists for members: -DEFAULT_UMBRELLA_LIST = No - -# For umbrella lists, the suffix for the account part of address for -# administrative notices (subscription confirmations, password reminders): -DEFAULT_UMBRELLA_MEMBER_ADMIN_SUFFIX = "-owner" - -# This variable controls whether monthly password reminders are sent. -DEFAULT_SEND_REMINDERS = Yes - -# Send welcome messages to new users? -DEFAULT_SEND_WELCOME_MSG = Yes - -# Send goodbye messages to unsubscribed members? -DEFAULT_SEND_GOODBYE_MSG = Yes - -# Wipe sender information, and make it look like the list-admin -# address sends all messages -DEFAULT_ANONYMOUS_LIST = No - -# {header-name: regexp} spam filtering - we include some for example sake. -DEFAULT_BOUNCE_MATCHING_HEADERS = u""" -# Lines that *start* with a '#' are comments. -to: friend@public.com -message-id: relay.comanche.denmark.eu -from: list@listme.com -from: .*@uplinkpro.com -""" - -# Mailman can be configured to "munge" Reply-To: headers for any passing -# messages. One the one hand, there are a lot of good reasons not to munge -# Reply-To: but on the other, people really seem to want this feature. See -# the help for reply_goes_to_list in the web UI for links discussing the -# issue. -# 0 - Reply-To: not munged -# 1 - Reply-To: set back to the list -# 2 - Reply-To: set to an explicit value (reply_to_address) -DEFAULT_REPLY_GOES_TO_LIST = ReplyToMunging.no_munging - -# Mailman can be configured to strip any existing Reply-To: header, or simply -# extend any existing Reply-To: with one based on the above setting. -DEFAULT_FIRST_STRIP_REPLY_TO = No - -# SUBSCRIBE POLICY -# 0 - open list (only when ALLOW_OPEN_SUBSCRIBE is set to 1) ** -# 1 - confirmation required for subscribes -# 2 - admin approval required for subscribes -# 3 - both confirmation and admin approval required -# -# ** please do not choose option 0 if you are not allowing open -# subscribes (next variable) -DEFAULT_SUBSCRIBE_POLICY = 1 - -# Does this site allow completely unchecked subscriptions? -ALLOW_OPEN_SUBSCRIBE = No - -# This is the default list of addresses and regular expressions (beginning -# with ^) that are exempt from approval if SUBSCRIBE_POLICY is 2 or 3. -DEFAULT_SUBSCRIBE_AUTO_APPROVAL = [] - -# The default policy for unsubscriptions. 0 (unmoderated unsubscribes) is -# highly recommended! -# 0 - unmoderated unsubscribes -# 1 - unsubscribes require approval -DEFAULT_UNSUBSCRIBE_POLICY = 0 - -# Private_roster == 0: anyone can see, 1: members only, 2: admin only. -DEFAULT_PRIVATE_ROSTER = 1 - -# When exposing members, make them unrecognizable as email addrs, so -# web-spiders can't pick up addrs for spam purposes. -DEFAULT_OBSCURE_ADDRESSES = Yes - -# RFC 2369 defines List-* headers which are added to every message sent -# through to the mailing list membership. These are a very useful aid to end -# users and should always be added. However, not all MUAs are compliant and -# if a list's membership has many such users, they may clamor for these -# headers to be suppressed. By setting this variable to Yes, list owners will -# be given the option to suppress these headers. By setting it to No, list -# owners will not be given the option to suppress these headers (although some -# header suppression may still take place, i.e. for announce-only lists, or -# lists with no archives). -ALLOW_RFC2369_OVERRIDES = Yes - -# Defaults for content filtering on mailing lists. DEFAULT_FILTER_CONTENT is -# a flag which if set to true, turns on content filtering. -DEFAULT_FILTER_CONTENT = No - -# DEFAULT_FILTER_MIME_TYPES is a list of MIME types to be removed. This is a -# list of strings of the format "maintype/subtype" or simply "maintype". -# E.g. "text/html" strips all html attachments while "image" strips all image -# types regardless of subtype (jpeg, gif, etc.). -DEFAULT_FILTER_MIME_TYPES = [] - -# DEFAULT_PASS_MIME_TYPES is a list of MIME types to be passed through. -# Format is the same as DEFAULT_FILTER_MIME_TYPES -DEFAULT_PASS_MIME_TYPES = ['multipart/mixed', - 'multipart/alternative', - 'text/plain'] - -# DEFAULT_FILTER_FILENAME_EXTENSIONS is a list of filename extensions to be -# removed. It is useful because many viruses fake their content-type as -# harmless ones while keep their extension as executable and expect to be -# executed when victims 'open' them. -DEFAULT_FILTER_FILENAME_EXTENSIONS = [ - 'exe', 'bat', 'cmd', 'com', 'pif', 'scr', 'vbs', 'cpl' - ] - -# DEFAULT_PASS_FILENAME_EXTENSIONS is a list of filename extensions to be -# passed through. Format is the same as DEFAULT_FILTER_FILENAME_EXTENSIONS. -DEFAULT_PASS_FILENAME_EXTENSIONS = [] - -# Replace multipart/alternative with its first alternative. -DEFAULT_COLLAPSE_ALTERNATIVES = Yes - -# Whether text/html should be converted to text/plain after content filtering -# is performed. Conversion is done according to HTML_TO_PLAIN_TEXT_COMMAND -DEFAULT_CONVERT_HTML_TO_PLAINTEXT = Yes - -# Default action to take on filtered messages. -# 0 = Discard, 1 = Reject, 2 = Forward, 3 = Preserve -DEFAULT_FILTER_ACTION = 0 - -# Whether to allow list owners to preserve content filtered messages to a -# special queue on the disk. -OWNERS_CAN_PRESERVE_FILTERED_MESSAGES = Yes - -# Check for administrivia in messages sent to the main list? -DEFAULT_ADMINISTRIVIA = Yes - - - -##### -# Digestification defaults. Same caveat applies here as with list defaults. -##### - -# Will list be available in non-digested form? -DEFAULT_NONDIGESTABLE = Yes - -# Will list be available in digested form? -DEFAULT_DIGESTABLE = Yes -DEFAULT_DIGEST_HEADER = u'' -DEFAULT_DIGEST_FOOTER = DEFAULT_MSG_FOOTER - -DEFAULT_DIGEST_IS_DEFAULT = No -DEFAULT_MIME_IS_DEFAULT_DIGEST = No -DEFAULT_DIGEST_SIZE_THRESHOLD = 30 # KB -DEFAULT_DIGEST_SEND_PERIODIC = Yes - -# Headers which should be kept in both RFC 1153 (plain) and MIME digests. RFC -# 1153 also specifies these headers in this exact order, so order matters. -MIME_DIGEST_KEEP_HEADERS = [ - 'Date', 'From', 'To', 'Cc', 'Subject', 'Message-ID', 'Keywords', - # I believe we should also keep these headers though. - 'In-Reply-To', 'References', 'Content-Type', 'MIME-Version', - 'Content-Transfer-Encoding', 'Precedence', 'Reply-To', - # Mailman 2.0 adds these headers - 'Message', - ] - -PLAIN_DIGEST_KEEP_HEADERS = [ - 'Message', 'Date', 'From', - 'Subject', 'To', 'Cc', - 'Message-ID', 'Keywords', - 'Content-Type', - ] - - - -##### -# Bounce processing defaults. Same caveat applies here as with list defaults. -##### - -# Should we do any bounced mail response at all? -DEFAULT_BOUNCE_PROCESSING = Yes - -# How often should the bounce qrunner process queued detected bounces? -REGISTER_BOUNCES_EVERY = minutes(15) - -# Bounce processing works like this: when a bounce from a member is received, -# we look up the `bounce info' for this member. If there is no bounce info, -# this is the first bounce we've received from this member. In that case, we -# record today's date, and initialize the bounce score (see below for initial -# value). -# -# If there is existing bounce info for this member, we look at the last bounce -# receive date. If this date is farther away from today than the `bounce -# expiration interval', we throw away all the old data and initialize the -# bounce score as if this were the first bounce from the member. -# -# Otherwise, we increment the bounce score. If we can determine whether the -# bounce was soft or hard (i.e. transient or fatal), then we use a score value -# of 0.5 for soft bounces and 1.0 for hard bounces. Note that we only score -# one bounce per day. If the bounce score is then greater than the `bounce -# threshold' we disable the member's address. -# -# After disabling the address, we can send warning messages to the member, -# providing a confirmation cookie/url for them to use to re-enable their -# delivery. After a configurable period of time, we'll delete the address. -# When we delete the address due to bouncing, we'll send one last message to -# the member. - -# Bounce scores greater than this value get disabled. -DEFAULT_BOUNCE_SCORE_THRESHOLD = 5.0 - -# Bounce information older than this interval is considered stale, and is -# discarded. -DEFAULT_BOUNCE_INFO_STALE_AFTER = days(7) - -# The number of notifications to send to the disabled/removed member before we -# remove them from the list. A value of 0 means we remove the address -# immediately (with one last notification). Note that the first one is sent -# upon change of status to disabled. -DEFAULT_BOUNCE_YOU_ARE_DISABLED_WARNINGS = 3 - -# The interval of time between disabled warnings. -DEFAULT_BOUNCE_YOU_ARE_DISABLED_WARNINGS_INTERVAL = days(7) - -# Does the list owner get messages to the -bounces (and -admin) address that -# failed to match by the bounce detector? -DEFAULT_BOUNCE_UNRECOGNIZED_GOES_TO_LIST_OWNER = Yes - -# Notifications on bounce actions. The first specifies whether the list owner -# should get a notification when a member is disabled due to bouncing, while -# the second specifies whether the owner should get one when the member is -# removed due to bouncing. -DEFAULT_BOUNCE_NOTIFY_OWNER_ON_DISABLE = Yes -DEFAULT_BOUNCE_NOTIFY_OWNER_ON_REMOVAL = Yes - - - -##### -# General time limits -##### - -# Default length of time a pending request is live before it is evicted from -# the pending database. -PENDING_REQUEST_LIFE = days(3) - -# How long should messages which have delivery failures continue to be -# retried? After this period of time, a message that has failed recipients -# will be dequeued and those recipients will never receive the message. -DELIVERY_RETRY_PERIOD = days(5) - -# How long should we wait before we retry a temporary delivery failure? -DELIVERY_RETRY_WAIT = hours(1) - - - -##### -# Lock management defaults -##### - -# These variables control certain aspects of lock acquisition and retention. -# They should be tuned as appropriate for your environment. All variables are -# specified in units of floating point seconds. YOU MAY NEED TO TUNE THESE -# VARIABLES DEPENDING ON THE SIZE OF YOUR LISTS, THE PERFORMANCE OF YOUR -# HARDWARE, NETWORK AND GENERAL MAIL HANDLING CAPABILITIES, ETC. - -# This variable specifies how long the lock will be retained for a specific -# operation on a mailing list. Watch your logs/lock file and if you see a lot -# of lock breakages, you might need to bump this up. However if you set this -# too high, a faulty script (or incorrect use of bin/withlist) can prevent the -# list from being used until the lifetime expires. This is probably one of -# the most crucial tuning variables in the system. -LIST_LOCK_LIFETIME = hours(5) - -# This variable specifies how long an attempt will be made to acquire a list -# lock by the incoming qrunner process. If the lock acquisition times out, -# the message will be re-queued for later delivery. -LIST_LOCK_TIMEOUT = seconds(10) - -# Set this to On to turn on lock debugging messages for the pending requests -# database, which will be written to logs/locks. If you think you're having -# lock problems, or just want to tune the locks for your system, turn on lock -# debugging. -PENDINGDB_LOCK_DEBUGGING = Off - - - -##### -# Nothing below here is user configurable. Most of these values are in this -# file for internal system convenience. Don't change any of them or override -# any of them in your mailman.cfg file! -##### - -# Enumeration for Mailman cgi widget types -Toggle = 1 -Radio = 2 -String = 3 -Text = 4 -Email = 5 -EmailList = 6 -Host = 7 -Number = 8 -FileUpload = 9 -Select = 10 -Topics = 11 -Checkbox = 12 -# An "extended email list". Contents must be an email address or a ^-prefixed -# regular expression. Used in the sender moderation text boxes. -EmailListEx = 13 -# Extended spam filter widget -HeaderFilter = 14 - -# Actions -DEFER = 0 -APPROVE = 1 -REJECT = 2 -DISCARD = 3 -SUBSCRIBE = 4 -UNSUBSCRIBE = 5 -ACCEPT = 6 -HOLD = 7 - -# Standard text field width -TEXTFIELDWIDTH = 40 - -# Bitfield for user options. See DEFAULT_NEW_MEMBER_OPTIONS above to set -# defaults for all new lists. -Digests = 0 # handled by other mechanism, doesn't need a flag. -DisableDelivery = 1 # Obsolete; use set/getDeliveryStatus() -DontReceiveOwnPosts = 2 # Non-digesters only -AcknowledgePosts = 4 -DisableMime = 8 # Digesters only -ConcealSubscription = 16 -SuppressPasswordReminder = 32 -ReceiveNonmatchingTopics = 64 -Moderate = 128 -DontReceiveDuplicates = 256 - - -# A mapping between short option tags and their flag -OPTINFO = {'hide' : ConcealSubscription, - 'nomail' : DisableDelivery, - 'ack' : AcknowledgePosts, - 'notmetoo': DontReceiveOwnPosts, - 'digest' : 0, - 'plain' : DisableMime, - 'nodupes' : DontReceiveDuplicates - } - -# Authentication contexts. -# -# Mailman defines the following roles: - -# - User, a normal user who has no permissions except to change their personal -# option settings -# - List creator, someone who can create and delete lists, but cannot -# (necessarily) configure the list. -# - List moderator, someone who can tend to pending requests such as -# subscription requests, or held messages -# - List administrator, someone who has total control over a list, can -# configure it, modify user options for members of the list, subscribe and -# unsubscribe members, etc. -# - Site administrator, someone who has total control over the entire site and -# can do any of the tasks mentioned above. This person usually also has -# command line access. - -UnAuthorized = 0 -AuthUser = 1 # Joe Shmoe User -AuthCreator = 2 # List Creator / Destroyer -AuthListAdmin = 3 # List Administrator (total control over list) -AuthListModerator = 4 # List Moderator (can only handle held requests) -AuthSiteAdmin = 5 # Site Administrator (total control over everything) - - - -# Vgg: Language descriptions and charsets dictionary, any new supported -# language must have a corresponding entry here. Key is the name of the -# directories that hold the localized texts. Data are tuples with first -# element being the description, as described in the catalogs, and second -# element is the language charset. I have chosen code from /usr/share/locale -# in my GNU/Linux. :-) -# -# TK: Now the site admin can select languages for the installation from those -# in the distribution tarball. We don't touch add_language() function for -# backward compatibility. You may have to add your own language in your -# mailman.cfg file, if it is not included in the distribution even if you had -# put language files in source directory and configured by `--with-languages' -# option. -def _(s): - return s - -_DEFAULT_LANGUAGE_DATA = { - 'ar': (_('Arabic'), 'utf-8'), - 'ca': (_('Catalan'), 'iso-8859-1'), - 'cs': (_('Czech'), 'iso-8859-2'), - 'da': (_('Danish'), 'iso-8859-1'), - 'de': (_('German'), 'iso-8859-1'), - 'en': (_('English (USA)'), 'us-ascii'), - 'es': (_('Spanish (Spain)'), 'iso-8859-1'), - 'et': (_('Estonian'), 'iso-8859-15'), - 'eu': (_('Euskara'), 'iso-8859-15'), # Basque - 'fi': (_('Finnish'), 'iso-8859-1'), - 'fr': (_('French'), 'iso-8859-1'), - 'hr': (_('Croatian'), 'iso-8859-2'), - 'hu': (_('Hungarian'), 'iso-8859-2'), - 'ia': (_('Interlingua'), 'iso-8859-15'), - 'it': (_('Italian'), 'iso-8859-1'), - 'ja': (_('Japanese'), 'euc-jp'), - 'ko': (_('Korean'), 'euc-kr'), - 'lt': (_('Lithuanian'), 'iso-8859-13'), - 'nl': (_('Dutch'), 'iso-8859-1'), - 'no': (_('Norwegian'), 'iso-8859-1'), - 'pl': (_('Polish'), 'iso-8859-2'), - 'pt': (_('Portuguese'), 'iso-8859-1'), - 'pt_BR': (_('Portuguese (Brazil)'), 'iso-8859-1'), - 'ro': (_('Romanian'), 'iso-8859-2'), - 'ru': (_('Russian'), 'koi8-r'), - 'sr': (_('Serbian'), 'utf-8'), - 'sl': (_('Slovenian'), 'iso-8859-2'), - 'sv': (_('Swedish'), 'iso-8859-1'), - 'tr': (_('Turkish'), 'iso-8859-9'), - 'uk': (_('Ukrainian'), 'utf-8'), - 'vi': (_('Vietnamese'), 'utf-8'), - 'zh_CN': (_('Chinese (China)'), 'utf-8'), - 'zh_TW': (_('Chinese (Taiwan)'), 'utf-8'), -} - - -del _ diff --git a/mailman/attic/Deliverer.py b/mailman/attic/Deliverer.py deleted file mode 100644 index 0ba3a01bb..000000000 --- a/mailman/attic/Deliverer.py +++ /dev/null @@ -1,174 +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/>. - - -"""Mixin class with message delivery routines.""" - -from __future__ import with_statement - -import logging - -from email.MIMEMessage import MIMEMessage -from email.MIMEText import MIMEText - -from mailman import Errors -from mailman import Message -from mailman import Utils -from mailman import i18n -from mailman.configuration import config - -_ = i18n._ - -log = logging.getLogger('mailman.error') -mlog = logging.getLogger('mailman.mischief') - - - -class Deliverer: - def MailUserPassword(self, user): - listfullname = self.fqdn_listname - requestaddr = self.GetRequestEmail() - # find the lowercased version of the user's address - adminaddr = self.GetBouncesEmail() - assert self.isMember(user) - if not self.getMemberPassword(user): - # The user's password somehow got corrupted. Generate a new one - # for him, after logging this bogosity. - log.error('User %s had a false password for list %s', - user, self.internal_name()) - waslocked = self.Locked() - if not waslocked: - self.Lock() - try: - self.setMemberPassword(user, Utils.MakeRandomPassword()) - self.Save() - finally: - if not waslocked: - self.Unlock() - # Now send the user his password - cpuser = self.getMemberCPAddress(user) - recipient = self.GetMemberAdminEmail(cpuser) - subject = _('%(listfullname)s mailing list reminder') - # Get user's language and charset - lang = self.getMemberLanguage(user) - cset = Utils.GetCharSet(lang) - password = self.getMemberPassword(user) - # TK: Make unprintables to ? - # The list owner should allow users to set language options if they - # want to use non-us-ascii characters in password and send it back. - password = unicode(password, cset, 'replace').encode(cset, 'replace') - # get the text from the template - text = Utils.maketext( - 'userpass.txt', - {'user' : cpuser, - 'listname' : self.real_name, - 'fqdn_lname' : self.GetListEmail(), - 'password' : password, - 'options_url': self.GetOptionsURL(user, absolute=True), - 'requestaddr': requestaddr, - 'owneraddr' : self.GetOwnerEmail(), - }, lang=lang, mlist=self) - msg = Message.UserNotification(recipient, adminaddr, subject, text, - lang) - msg['X-No-Archive'] = 'yes' - msg.send(self, verp=config.VERP_PERSONALIZED_DELIVERIES) - - def ForwardMessage(self, msg, text=None, subject=None, tomoderators=True): - # Wrap the message as an attachment - if text is None: - text = _('No reason given') - if subject is None: - text = _('(no subject)') - text = MIMEText(Utils.wrap(text), - _charset=Utils.GetCharSet(self.preferred_language)) - attachment = MIMEMessage(msg) - notice = Message.OwnerNotification( - self, subject, tomoderators=tomoderators) - # Make it look like the message is going to the -owner address - notice.set_type('multipart/mixed') - notice.attach(text) - notice.attach(attachment) - notice.send(self) - - def SendHostileSubscriptionNotice(self, listname, address): - # Some one was invited to one list but tried to confirm to a different - # list. We inform both list owners of the bogosity, but be careful - # not to reveal too much information. - selfname = self.internal_name() - mlog.error('%s was invited to %s but confirmed to %s', - address, listname, selfname) - # First send a notice to the attacked list - msg = Message.OwnerNotification( - self, - _('Hostile subscription attempt detected'), - Utils.wrap(_("""%(address)s was invited to a different mailing -list, but in a deliberate malicious attempt they tried to confirm the -invitation to your list. We just thought you'd like to know. No further -action by you is required."""))) - msg.send(self) - # Now send a notice to the invitee list - try: - # Avoid import loops - from mailman.MailList import MailList - mlist = MailList(listname, lock=False) - except Errors.MMListError: - # Oh well - return - with i18n.using_language(mlist.preferred_language): - msg = Message.OwnerNotification( - mlist, - _('Hostile subscription attempt detected'), - Utils.wrap(_("""You invited %(address)s to your list, but in a -deliberate malicious attempt, they tried to confirm the invitation to a -different list. We just thought you'd like to know. No further action by you -is required."""))) - msg.send(mlist) - - def sendProbe(self, member, msg): - listname = self.real_name - # Put together the substitution dictionary. - d = {'listname': listname, - 'address': member, - 'optionsurl': self.GetOptionsURL(member, absolute=True), - 'owneraddr': self.GetOwnerEmail(), - } - text = Utils.maketext('probe.txt', d, - lang=self.getMemberLanguage(member), - mlist=self) - # Calculate the VERP'd sender address for bounce processing of the - # probe message. - token = self.pend_new(Pending.PROBE_BOUNCE, member, msg) - probedict = { - 'bounces': self.internal_name() + '-bounces', - 'token': token, - } - probeaddr = '%s@%s' % ((config.VERP_PROBE_FORMAT % probedict), - self.host_name) - # Calculate the Subject header, in the member's preferred language - ulang = self.getMemberLanguage(member) - with i18n.using_language(ulang): - subject = _('%(listname)s mailing list probe message') - outer = Message.UserNotification(member, probeaddr, subject, - lang=ulang) - outer.set_type('multipart/mixed') - text = MIMEText(text, _charset=Utils.GetCharSet(ulang)) - outer.attach(text) - outer.attach(MIMEMessage(msg)) - # Turn off further VERP'ing in the final delivery step. We set - # probe_token for the OutgoingRunner to more easily handling local - # rejects of probe messages. - outer.send(self, envsender=probeaddr, verp=False, probe_token=token) diff --git a/mailman/attic/Digester.py b/mailman/attic/Digester.py deleted file mode 100644 index a88d08abc..000000000 --- a/mailman/attic/Digester.py +++ /dev/null @@ -1,57 +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/>. - - -"""Mixin class with list-digest handling methods and settings.""" - -import os -import errno - -from mailman import Errors -from mailman import Utils -from mailman.Handlers import ToDigest -from mailman.configuration import config -from mailman.i18n import _ - - - -class Digester: - def send_digest_now(self): - # Note: Handler.ToDigest.send_digests() handles bumping the digest - # volume and issue number. - digestmbox = os.path.join(self.fullpath(), 'digest.mbox') - try: - try: - mboxfp = None - # See if there's a digest pending for this mailing list - if os.stat(digestmbox).st_size > 0: - mboxfp = open(digestmbox) - ToDigest.send_digests(self, mboxfp) - os.unlink(digestmbox) - finally: - if mboxfp: - mboxfp.close() - except OSError, e: - if e.errno <> errno.ENOENT: - raise - # List has no outstanding digests - return False - return True - - def bump_digest_volume(self): - self.volume += 1 - self.next_digest_number = 1 diff --git a/mailman/attic/MailList.py b/mailman/attic/MailList.py deleted file mode 100644 index 2d538f026..000000000 --- a/mailman/attic/MailList.py +++ /dev/null @@ -1,731 +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/>. - - -"""The class representing a Mailman mailing list. - -Mixes in many task-specific classes. -""" - -from __future__ import with_statement - -import os -import re -import sys -import time -import errno -import shutil -import socket -import urllib -import cPickle -import logging -import marshal -import email.Iterators - -from UserDict import UserDict -from cStringIO import StringIO -from string import Template -from types import MethodType -from urlparse import urlparse -from zope.interface import implements - -from email.Header import Header -from email.Utils import getaddresses, formataddr, parseaddr - -from Mailman import Errors -from Mailman import Utils -from Mailman import Version -from Mailman import database -from Mailman.UserDesc import UserDesc -from Mailman.configuration import config -from Mailman.interfaces import * - -# Base classes -from Mailman.Archiver import Archiver -from Mailman.Bouncer import Bouncer -from Mailman.Digester import Digester -from Mailman.SecurityManager import SecurityManager - -# GUI components package -from Mailman import Gui - -# Other useful classes -from Mailman import i18n -from Mailman import MemberAdaptor -from Mailman import Message - -_ = i18n._ - -DOT = '.' -EMPTYSTRING = '' -OR = '|' - -clog = logging.getLogger('mailman.config') -elog = logging.getLogger('mailman.error') -vlog = logging.getLogger('mailman.vette') -slog = logging.getLogger('mailman.subscribe') - - - -# Use mixins here just to avoid having any one chunk be too large. -class MailList(object, Archiver, Digester, SecurityManager, Bouncer): - - implements( - IMailingList, - IMailingListAddresses, - IMailingListIdentity, - IMailingListRosters, - ) - - def __init__(self, data): - self._data = data - # Only one level of mixin inheritance allowed. - for baseclass in self.__class__.__bases__: - if hasattr(baseclass, '__init__'): - baseclass.__init__(self) - # Initialize the web u/i components. - self._gui = [] - for component in dir(Gui): - if component.startswith('_'): - continue - self._gui.append(getattr(Gui, component)()) - # Give the extension mechanism a chance to process this list. - try: - from Mailman.ext import init_mlist - except ImportError: - pass - else: - init_mlist(self) - - def __getattr__(self, name): - missing = object() - if name.startswith('_'): - return getattr(super(MailList, self), name) - # Delegate to the database model object if it has the attribute. - obj = getattr(self._data, name, missing) - if obj is not missing: - return obj - # Finally, delegate to one of the gui components. - for guicomponent in self._gui: - obj = getattr(guicomponent, name, missing) - if obj is not missing: - return obj - # Nothing left to delegate to, so it's got to be an error. - raise AttributeError(name) - - def __repr__(self): - return '<mailing list "%s" at %x>' % (self.fqdn_listname, id(self)) - - - def GetConfirmJoinSubject(self, listname, cookie): - if config.VERP_CONFIRMATIONS and cookie: - cset = i18n.get_translation().charset() or \ - Utils.GetCharSet(self.preferred_language) - subj = Header( - _('Your confirmation is required to join the %(listname)s mailing list'), - cset, header_name='subject') - return subj - else: - return 'confirm ' + cookie - - def GetConfirmLeaveSubject(self, listname, cookie): - if config.VERP_CONFIRMATIONS and cookie: - cset = i18n.get_translation().charset() or \ - Utils.GetCharSet(self.preferred_language) - subj = Header( - _('Your confirmation is required to leave the %(listname)s mailing list'), - cset, header_name='subject') - return subj - else: - return 'confirm ' + cookie - - def GetMemberAdminEmail(self, member): - """Usually the member addr, but modified for umbrella lists. - - Umbrella lists have other mailing lists as members, and so admin stuff - like confirmation requests and passwords must not be sent to the - member addresses - the sublists - but rather to the administrators of - the sublists. This routine picks the right address, considering - regular member address to be their own administrative addresses. - - """ - if not self.umbrella_list: - return member - else: - acct, host = tuple(member.split('@')) - return "%s%s@%s" % (acct, self.umbrella_member_suffix, host) - - def GetScriptURL(self, target, absolute=False): - if absolute: - return self.web_page_url + target + '/' + self.fqdn_listname - else: - return Utils.ScriptURL(target) + '/' + self.fqdn_listname - - def GetOptionsURL(self, user, obscure=False, absolute=False): - url = self.GetScriptURL('options', absolute) - if obscure: - user = Utils.ObscureEmail(user) - return '%s/%s' % (url, urllib.quote(user.lower())) - - - # - # Web API support via administrative categories - # - def GetConfigCategories(self): - class CategoryDict(UserDict): - def __init__(self): - UserDict.__init__(self) - self.keysinorder = config.ADMIN_CATEGORIES[:] - def keys(self): - return self.keysinorder - def items(self): - items = [] - for k in config.ADMIN_CATEGORIES: - items.append((k, self.data[k])) - return items - def values(self): - values = [] - for k in config.ADMIN_CATEGORIES: - values.append(self.data[k]) - return values - - categories = CategoryDict() - # Only one level of mixin inheritance allowed - for gui in self._gui: - k, v = gui.GetConfigCategory() - categories[k] = (v, gui) - return categories - - def GetConfigSubCategories(self, category): - for gui in self._gui: - if hasattr(gui, 'GetConfigSubCategories'): - # Return the first one that knows about the given subcategory - subcat = gui.GetConfigSubCategories(category) - if subcat is not None: - return subcat - return None - - def GetConfigInfo(self, category, subcat=None): - for gui in self._gui: - if hasattr(gui, 'GetConfigInfo'): - value = gui.GetConfigInfo(self, category, subcat) - if value: - return value - - - # - # Membership management front-ends and assertion checks - # - def InviteNewMember(self, userdesc, text=''): - """Invite a new member to the list. - - This is done by creating a subscription pending for the user, and then - crafting a message to the member informing them of the invitation. - """ - invitee = userdesc.address - Utils.ValidateEmail(invitee) - # check for banned address - pattern = Utils.get_pattern(invitee, self.ban_list) - if pattern: - raise Errors.MembershipIsBanned(pattern) - # Hack alert! Squirrel away a flag that only invitations have, so - # that we can do something slightly different when an invitation - # subscription is confirmed. In those cases, we don't need further - # admin approval, even if the list is so configured. The flag is the - # list name to prevent invitees from cross-subscribing. - userdesc.invitation = self.internal_name() - cookie = self.pend_new(Pending.SUBSCRIPTION, userdesc) - requestaddr = self.getListAddress('request') - confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1), - cookie) - listname = self.real_name - text += Utils.maketext( - 'invite.txt', - {'email' : invitee, - 'listname' : listname, - 'hostname' : self.host_name, - 'confirmurl' : confirmurl, - 'requestaddr': requestaddr, - 'cookie' : cookie, - 'listowner' : self.GetOwnerEmail(), - }, mlist=self) - sender = self.GetRequestEmail(cookie) - msg = Message.UserNotification( - invitee, sender, - text=text, lang=self.preferred_language) - subj = self.GetConfirmJoinSubject(listname, cookie) - del msg['subject'] - msg['Subject'] = subj - msg.send(self) - - def AddMember(self, userdesc, remote=None): - """Front end to member subscription. - - This method enforces subscription policy, validates values, sends - notifications, and any other grunt work involved in subscribing a - user. It eventually calls ApprovedAddMember() to do the actual work - of subscribing the user. - - userdesc is an instance with the following public attributes: - - address -- the unvalidated email address of the member - fullname -- the member's full name (i.e. John Smith) - digest -- a flag indicating whether the user wants digests or not - language -- the requested default language for the user - password -- the user's password - - Other attributes may be defined later. Only address is required; the - others all have defaults (fullname='', digests=0, language=list's - preferred language, password=generated). - - remote is a string which describes where this add request came from. - """ - assert self.Locked() - # Suck values out of userdesc, apply defaults, and reset the userdesc - # attributes (for passing on to ApprovedAddMember()). Lowercase the - # addr's domain part. - email = Utils.LCDomain(userdesc.address) - name = getattr(userdesc, 'fullname', '') - lang = getattr(userdesc, 'language', self.preferred_language) - digest = getattr(userdesc, 'digest', None) - password = getattr(userdesc, 'password', Utils.MakeRandomPassword()) - if digest is None: - if self.nondigestable: - digest = 0 - else: - digest = 1 - # Validate the e-mail address to some degree. - Utils.ValidateEmail(email) - if self.isMember(email): - raise Errors.MMAlreadyAMember, email - if email.lower() == self.GetListEmail().lower(): - # Trying to subscribe the list to itself! - raise Errors.InvalidEmailAddress - realname = self.real_name - # Is the subscribing address banned from this list? - pattern = Utils.get_pattern(email, self.ban_list) - if pattern: - vlog.error('%s banned subscription: %s (matched: %s)', - realname, email, pattern) - raise Errors.MembershipIsBanned, pattern - # Sanity check the digest flag - if digest and not self.digestable: - raise Errors.MMCantDigestError - elif not digest and not self.nondigestable: - raise Errors.MMMustDigestError - - userdesc.address = email - userdesc.fullname = name - userdesc.digest = digest - userdesc.language = lang - userdesc.password = password - - # Apply the list's subscription policy. 0 means open subscriptions; 1 - # means the user must confirm; 2 means the admin must approve; 3 means - # the user must confirm and then the admin must approve - if self.subscribe_policy == 0: - self.ApprovedAddMember(userdesc, whence=remote or '') - elif self.subscribe_policy == 1 or self.subscribe_policy == 3: - # User confirmation required. BAW: this should probably just - # accept a userdesc instance. - cookie = self.pend_new(Pending.SUBSCRIPTION, userdesc) - # Send the user the confirmation mailback - if remote is None: - by = remote = '' - else: - by = ' ' + remote - remote = _(' from %(remote)s') - - recipient = self.GetMemberAdminEmail(email) - confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1), - cookie) - text = Utils.maketext( - 'verify.txt', - {'email' : email, - 'listaddr' : self.GetListEmail(), - 'listname' : realname, - 'cookie' : cookie, - 'requestaddr' : self.getListAddress('request'), - 'remote' : remote, - 'listadmin' : self.GetOwnerEmail(), - 'confirmurl' : confirmurl, - }, lang=lang, mlist=self) - msg = Message.UserNotification( - recipient, self.GetRequestEmail(cookie), - text=text, lang=lang) - # BAW: See ChangeMemberAddress() for why we do it this way... - del msg['subject'] - msg['Subject'] = self.GetConfirmJoinSubject(realname, cookie) - msg['Reply-To'] = self.GetRequestEmail(cookie) - msg.send(self) - who = formataddr((name, email)) - slog.info('%s: pending %s %s', self.internal_name(), who, by) - raise Errors.MMSubscribeNeedsConfirmation - elif self.HasAutoApprovedSender(email): - # no approval necessary: - self.ApprovedAddMember(userdesc) - else: - # Subscription approval is required. Add this entry to the admin - # requests database. BAW: this should probably take a userdesc - # just like above. - self.HoldSubscription(email, name, password, digest, lang) - raise Errors.MMNeedApproval, _( - 'subscriptions to %(realname)s require moderator approval') - - def DeleteMember(self, name, whence=None, admin_notif=None, userack=True): - realname, email = parseaddr(name) - if self.unsubscribe_policy == 0: - self.ApprovedDeleteMember(name, whence, admin_notif, userack) - else: - self.HoldUnsubscription(email) - raise Errors.MMNeedApproval, _( - 'unsubscriptions require moderator approval') - - def ChangeMemberAddress(self, oldaddr, newaddr, globally): - # Changing a member address consists of verifying the new address, - # making sure the new address isn't already a member, and optionally - # going through the confirmation process. - # - # Most of these checks are copied from AddMember - newaddr = Utils.LCDomain(newaddr) - Utils.ValidateEmail(newaddr) - # Raise an exception if this email address is already a member of the - # list, but only if the new address is the same case-wise as the old - # address and we're not doing a global change. - if not globally and newaddr == oldaddr and self.isMember(newaddr): - raise Errors.MMAlreadyAMember - if newaddr == self.GetListEmail().lower(): - raise Errors.InvalidEmailAddress - realname = self.real_name - # Don't allow changing to a banned address. MAS: maybe we should - # unsubscribe the oldaddr too just for trying, but that's probably - # too harsh. - pattern = Utils.get_pattern(newaddr, self.ban_list) - if pattern: - vlog.error('%s banned address change: %s -> %s (matched: %s)', - realname, oldaddr, newaddr, pattern) - raise Errors.MembershipIsBanned, pattern - # Pend the subscription change - cookie = self.pend_new(Pending.CHANGE_OF_ADDRESS, - oldaddr, newaddr, globally) - confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1), - cookie) - lang = self.getMemberLanguage(oldaddr) - text = Utils.maketext( - 'verify.txt', - {'email' : newaddr, - 'listaddr' : self.GetListEmail(), - 'listname' : realname, - 'cookie' : cookie, - 'requestaddr': self.getListAddress('request'), - 'remote' : '', - 'listadmin' : self.GetOwnerEmail(), - 'confirmurl' : confirmurl, - }, lang=lang, mlist=self) - # BAW: We don't pass the Subject: into the UserNotification - # constructor because it will encode it in the charset of the language - # being used. For non-us-ascii charsets, this means it will probably - # quopri quote it, and thus replies will also be quopri encoded. But - # CommandRunner doesn't yet grok such headers. So, just set the - # Subject: in a separate step, although we have to delete the one - # UserNotification adds. - msg = Message.UserNotification( - newaddr, self.GetRequestEmail(cookie), - text=text, lang=lang) - del msg['subject'] - msg['Subject'] = self.GetConfirmJoinSubject(realname, cookie) - msg['Reply-To'] = self.GetRequestEmail(cookie) - msg.send(self) - - def ApprovedChangeMemberAddress(self, oldaddr, newaddr, globally): - # Check here for banned address in case address was banned after - # confirmation was mailed. MAS: If it's global change should we just - # skip this list and proceed to the others? For now we'll throw the - # exception. - pattern = Utils.get_pattern(newaddr, self.ban_list) - if pattern: - raise Errors.MembershipIsBanned, pattern - # It's possible they were a member of this list, but choose to change - # their membership globally. In that case, we simply remove the old - # address. - if self.getMemberCPAddress(oldaddr) == newaddr: - self.removeMember(oldaddr) - else: - self.changeMemberAddress(oldaddr, newaddr) - self.log_and_notify_admin(oldaddr, newaddr) - # If globally is true, then we also include every list for which - # oldaddr is a member. - if not globally: - return - for listname in config.list_manager.names: - # Don't bother with ourselves - if listname == self.internal_name(): - continue - mlist = MailList(listname, lock=0) - if mlist.host_name <> self.host_name: - continue - if not mlist.isMember(oldaddr): - continue - # If new address is banned from this list, just skip it. - if Utils.get_pattern(newaddr, mlist.ban_list): - continue - mlist.Lock() - try: - # Same logic as above, re newaddr is already a member - if mlist.getMemberCPAddress(oldaddr) == newaddr: - mlist.removeMember(oldaddr) - else: - mlist.changeMemberAddress(oldaddr, newaddr) - mlist.log_and_notify_admin(oldaddr, newaddr) - mlist.Save() - finally: - mlist.Unlock() - - def log_and_notify_admin(self, oldaddr, newaddr): - """Log member address change and notify admin if requested.""" - slog.info('%s: changed member address from %s to %s', - self.internal_name(), oldaddr, newaddr) - if self.admin_notify_mchanges: - with i18n.using_language(self.preferred_language): - realname = self.real_name - subject = _('%(realname)s address change notification') - name = self.getMemberName(newaddr) - if name is None: - name = '' - if isinstance(name, unicode): - name = name.encode(Utils.GetCharSet(self.preferred_language), - 'replace') - text = Utils.maketext( - 'adminaddrchgack.txt', - {'name' : name, - 'oldaddr' : oldaddr, - 'newaddr' : newaddr, - 'listname': self.real_name, - }, mlist=self) - msg = Message.OwnerNotification(self, subject, text) - msg.send(self) - - - # - # Confirmation processing - # - def ProcessConfirmation(self, cookie, context=None): - rec = self.pend_confirm(cookie) - if rec is None: - raise Errors.MMBadConfirmation, 'No cookie record for %s' % cookie - try: - op = rec[0] - data = rec[1:] - except ValueError: - raise Errors.MMBadConfirmation, 'op-less data %s' % (rec,) - if op == Pending.SUBSCRIPTION: - whence = 'via email confirmation' - try: - userdesc = data[0] - # If confirmation comes from the web, context should be a - # UserDesc instance which contains overrides of the original - # subscription information. If it comes from email, then - # context is a Message and isn't relevant, so ignore it. - if isinstance(context, UserDesc): - userdesc += context - whence = 'via web confirmation' - addr = userdesc.address - fullname = userdesc.fullname - password = userdesc.password - digest = userdesc.digest - lang = userdesc.language - except ValueError: - raise Errors.MMBadConfirmation, 'bad subscr data %s' % (data,) - # Hack alert! Was this a confirmation of an invitation? - invitation = getattr(userdesc, 'invitation', False) - # We check for both 2 (approval required) and 3 (confirm + - # approval) because the policy could have been changed in the - # middle of the confirmation dance. - if invitation: - if invitation <> self.internal_name(): - # Not cool. The invitee was trying to subscribe to a - # different list than they were invited to. Alert both - # list administrators. - self.SendHostileSubscriptionNotice(invitation, addr) - raise Errors.HostileSubscriptionError - elif self.subscribe_policy in (2, 3) and \ - not self.HasAutoApprovedSender(addr): - self.HoldSubscription(addr, fullname, password, digest, lang) - name = self.real_name - raise Errors.MMNeedApproval, _( - 'subscriptions to %(name)s require administrator approval') - self.ApprovedAddMember(userdesc, whence=whence) - return op, addr, password, digest, lang - elif op == Pending.UNSUBSCRIPTION: - addr = data[0] - # Log file messages don't need to be i18n'd - if isinstance(context, Message.Message): - whence = 'email confirmation' - else: - whence = 'web confirmation' - # Can raise NotAMemberError if they unsub'd via other means - self.ApprovedDeleteMember(addr, whence=whence) - return op, addr - elif op == Pending.CHANGE_OF_ADDRESS: - oldaddr, newaddr, globally = data - self.ApprovedChangeMemberAddress(oldaddr, newaddr, globally) - return op, oldaddr, newaddr - elif op == Pending.HELD_MESSAGE: - id = data[0] - approved = None - # Confirmation should be coming from email, where context should - # be the confirming message. If the message does not have an - # Approved: header, this is a discard. If it has an Approved: - # header that does not match the list password, then we'll notify - # the list administrator that they used the wrong password. - # Otherwise it's an approval. - if isinstance(context, Message.Message): - # See if it's got an Approved: header, either in the headers, - # or in the first text/plain section of the response. For - # robustness, we'll accept Approve: as well. - approved = context.get('Approved', context.get('Approve')) - if not approved: - try: - subpart = list(email.Iterators.typed_subpart_iterator( - context, 'text', 'plain'))[0] - except IndexError: - subpart = None - if subpart: - s = StringIO(subpart.get_payload()) - while True: - line = s.readline() - if not line: - break - if not line.strip(): - continue - i = line.find(':') - if i > 0: - if (line[:i].lower() == 'approve' or - line[:i].lower() == 'approved'): - # then - approved = line[i+1:].strip() - break - # Is there an approved header? - if approved is not None: - # Does it match the list password? Note that we purposefully - # do not allow the site password here. - if self.Authenticate([config.AuthListAdmin, - config.AuthListModerator], - approved) <> config.UnAuthorized: - action = config.APPROVE - else: - # The password didn't match. Re-pend the message and - # inform the list moderators about the problem. - self.pend_repend(cookie, rec) - raise Errors.MMBadPasswordError - else: - action = config.DISCARD - try: - self.HandleRequest(id, action) - except KeyError: - # Most likely because the message has already been disposed of - # via the admindb page. - elog.error('Could not process HELD_MESSAGE: %s', id) - return (op,) - elif op == Pending.RE_ENABLE: - member = data[1] - self.setDeliveryStatus(member, MemberAdaptor.ENABLED) - return op, member - else: - assert 0, 'Bad op: %s' % op - - def ConfirmUnsubscription(self, addr, lang=None, remote=None): - if lang is None: - lang = self.getMemberLanguage(addr) - cookie = self.pend_new(Pending.UNSUBSCRIPTION, addr) - confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1), - cookie) - realname = self.real_name - if remote is not None: - by = " " + remote - remote = _(" from %(remote)s") - else: - by = "" - remote = "" - text = Utils.maketext( - 'unsub.txt', - {'email' : addr, - 'listaddr' : self.GetListEmail(), - 'listname' : realname, - 'cookie' : cookie, - 'requestaddr' : self.getListAddress('request'), - 'remote' : remote, - 'listadmin' : self.GetOwnerEmail(), - 'confirmurl' : confirmurl, - }, lang=lang, mlist=self) - msg = Message.UserNotification( - addr, self.GetRequestEmail(cookie), - text=text, lang=lang) - # BAW: See ChangeMemberAddress() for why we do it this way... - del msg['subject'] - msg['Subject'] = self.GetConfirmLeaveSubject(realname, cookie) - msg['Reply-To'] = self.GetRequestEmail(cookie) - msg.send(self) - - - # - # Miscellaneous stuff - # - - def HasAutoApprovedSender(self, sender): - """Returns True and logs if sender matches address or pattern - in subscribe_auto_approval. Otherwise returns False. - """ - auto_approve = False - if Utils.get_pattern(sender, self.subscribe_auto_approval): - auto_approve = True - vlog.info('%s: auto approved subscribe from %s', - self.internal_name(), sender) - return auto_approve - - - # - # Multilingual (i18n) support - # - def set_languages(self, *language_codes): - # XXX FIXME not to use a database entity directly. - from Mailman.database.model import Language - # Don't use the language_codes property because that will add the - # default server language. The effect would be that the default - # server language would never get added to the list's list of - # languages. - requested_codes = set(language_codes) - enabled_codes = set(config.languages.enabled_codes) - self.available_languages = [ - Language(code) for code in requested_codes & enabled_codes] - - def add_language(self, language_code): - self.available_languages.append(Language(language_code)) - - @property - def language_codes(self): - # Callers of this method expect a list of language codes - available_codes = set(self.available_languages) - enabled_codes = set(config.languages.enabled_codes) - codes = available_codes & enabled_codes - # If we don't add this, and the site admin has never added any - # language support to the list, then the general admin page may have a - # blank field where the list owner is supposed to chose the list's - # preferred language. - if config.DEFAULT_SERVER_LANGUAGE not in codes: - codes.add(config.DEFAULT_SERVER_LANGUAGE) - return list(codes) diff --git a/mailman/attic/SecurityManager.py b/mailman/attic/SecurityManager.py deleted file mode 100644 index 8d4a30592..000000000 --- a/mailman/attic/SecurityManager.py +++ /dev/null @@ -1,306 +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/>. - -"""Handle passwords and sanitize approved messages.""" - -# There are current 5 roles defined in Mailman, as codified in Defaults.py: -# user, list-creator, list-moderator, list-admin, site-admin. -# -# Here's how we do cookie based authentication. -# -# Each role (see above) has an associated password, which is currently the -# only way to authenticate a role (in the future, we'll authenticate a -# user and assign users to roles). -# -# Each cookie has the following ingredients: the authorization context's -# secret (i.e. the password, and a timestamp. We generate an SHA1 hex -# digest of these ingredients, which we call the 'mac'. We then marshal -# up a tuple of the timestamp and the mac, hexlify that and return that as -# a cookie keyed off the authcontext. Note that authenticating the user -# also requires the user's email address to be included in the cookie. -# -# The verification process is done in CheckCookie() below. It extracts -# the cookie, unhexlifies and unmarshals the tuple, extracting the -# timestamp. Using this, and the shared secret, the mac is calculated, -# and it must match the mac passed in the cookie. If so, they're golden, -# otherwise, access is denied. -# -# It is still possible for an adversary to attempt to brute force crack -# the password if they obtain the cookie, since they can extract the -# timestamp and create macs based on password guesses. They never get a -# cleartext version of the password though, so security rests on the -# difficulty and expense of retrying the cgi dialog for each attempt. It -# also relies on the security of SHA1. - -import os -import re -import sha -import time -import urllib -import Cookie -import logging -import marshal -import binascii - -from urlparse import urlparse - -from Mailman import Defaults -from Mailman import Errors -from Mailman import Utils -from Mailman import passwords -from Mailman.configuration import config - -log = logging.getLogger('mailman.error') -dlog = logging.getLogger('mailman.debug') - -SLASH = '/' - - - -class SecurityManager: - def AuthContextInfo(self, authcontext, user=None): - # authcontext may be one of AuthUser, AuthListModerator, - # AuthListAdmin, AuthSiteAdmin. Not supported is the AuthCreator - # context. - # - # user is ignored unless authcontext is AuthUser - # - # Return the authcontext's secret and cookie key. If the authcontext - # doesn't exist, return the tuple (None, None). If authcontext is - # AuthUser, but the user isn't a member of this mailing list, a - # NotAMemberError will be raised. If the user's secret is None, raise - # a MMBadUserError. - key = urllib.quote(self.fqdn_listname) + '+' - if authcontext == Defaults.AuthUser: - if user is None: - # A bad system error - raise TypeError('No user supplied for AuthUser context') - secret = self.getMemberPassword(user) - userdata = urllib.quote(Utils.ObscureEmail(user), safe='') - key += 'user+%s' % userdata - elif authcontext == Defaults.AuthListModerator: - secret = self.mod_password - key += 'moderator' - elif authcontext == Defaults.AuthListAdmin: - secret = self.password - key += 'admin' - # BAW: AuthCreator - elif authcontext == Defaults.AuthSiteAdmin: - sitepass = Utils.get_global_password() - if config.ALLOW_SITE_ADMIN_COOKIES and sitepass: - secret = sitepass - key = 'site' - else: - # BAW: this should probably hand out a site password based - # cookie, but that makes me a bit nervous, so just treat site - # admin as a list admin since there is currently no site - # admin-only functionality. - secret = self.password - key += 'admin' - else: - return None, None - return key, secret - - def Authenticate(self, authcontexts, response, user=None): - # Given a list of authentication contexts, check to see if the - # response matches one of the passwords. authcontexts must be a - # sequence, and if it contains the context AuthUser, then the user - # argument must not be None. - # - # Return the authcontext from the argument sequence that matches the - # response, or UnAuthorized. - for ac in authcontexts: - if ac == Defaults.AuthCreator: - ok = Utils.check_global_password(response, siteadmin=False) - if ok: - return Defaults.AuthCreator - elif ac == Defaults.AuthSiteAdmin: - ok = Utils.check_global_password(response) - if ok: - return Defaults.AuthSiteAdmin - elif ac == Defaults.AuthListAdmin: - # The password for the list admin and list moderator are not - # kept as plain text, but instead as an sha hexdigest. The - # response being passed in is plain text, so we need to - # digestify it first. - key, secret = self.AuthContextInfo(ac) - if secret is None: - continue - if passwords.check_response(secret, response): - return ac - elif ac == Defaults.AuthListModerator: - # The list moderator password must be sha'd - key, secret = self.AuthContextInfo(ac) - if secret and passwords.check_response(secret, response): - return ac - elif ac == Defaults.AuthUser: - if user is not None: - try: - if self.authenticateMember(user, response): - return ac - except Errors.NotAMemberError: - pass - else: - # What is this context??? - log.error('Bad authcontext: %s', ac) - raise ValueError('Bad authcontext: %s' % ac) - return Defaults.UnAuthorized - - def WebAuthenticate(self, authcontexts, response, user=None): - # Given a list of authentication contexts, check to see if the cookie - # contains a matching authorization, falling back to checking whether - # the response matches one of the passwords. authcontexts must be a - # sequence, and if it contains the context AuthUser, then the user - # argument should not be None. - # - # Returns a flag indicating whether authentication succeeded or not. - for ac in authcontexts: - ok = self.CheckCookie(ac, user) - if ok: - return True - # Check passwords - ac = self.Authenticate(authcontexts, response, user) - if ac: - print self.MakeCookie(ac, user) - return True - return False - - def _cookie_path(self): - script_name = os.environ.get('SCRIPT_NAME', '') - return SLASH.join(script_name.split(SLASH)[:-1]) + SLASH - - def MakeCookie(self, authcontext, user=None): - key, secret = self.AuthContextInfo(authcontext, user) - if key is None or secret is None or not isinstance(secret, basestring): - raise ValueError - # Timestamp - issued = int(time.time()) - # Get a digest of the secret, plus other information. - mac = sha.new(secret + repr(issued)).hexdigest() - # Create the cookie object. - c = Cookie.SimpleCookie() - c[key] = binascii.hexlify(marshal.dumps((issued, mac))) - c[key]['path'] = self._cookie_path() - # We use session cookies, so don't set 'expires' or 'max-age' keys. - # Set the RFC 2109 required header. - c[key]['version'] = 1 - return c - - def ZapCookie(self, authcontext, user=None): - # We can throw away the secret. - key, secret = self.AuthContextInfo(authcontext, user) - # Logout of the session by zapping the cookie. For safety both set - # max-age=0 (as per RFC2109) and set the cookie data to the empty - # string. - c = Cookie.SimpleCookie() - c[key] = '' - c[key]['path'] = self._cookie_path() - c[key]['max-age'] = 0 - # Don't set expires=0 here otherwise it'll force a persistent cookie - c[key]['version'] = 1 - return c - - def CheckCookie(self, authcontext, user=None): - # Two results can occur: we return 1 meaning the cookie authentication - # succeeded for the authorization context, we return 0 meaning the - # authentication failed. - # - # Dig out the cookie data, which better be passed on this cgi - # environment variable. If there's no cookie data, we reject the - # authentication. - cookiedata = os.environ.get('HTTP_COOKIE') - if not cookiedata: - return False - # We can't use the Cookie module here because it isn't liberal in what - # it accepts. Feed it a MM2.0 cookie along with a MM2.1 cookie and - # you get a CookieError. :(. All we care about is accessing the - # cookie data via getitem, so we'll use our own parser, which returns - # a dictionary. - c = parsecookie(cookiedata) - # If the user was not supplied, but the authcontext is AuthUser, we - # can try to glean the user address from the cookie key. There may be - # more than one matching key (if the user has multiple accounts - # subscribed to this list), but any are okay. - if authcontext == Defaults.AuthUser: - if user: - usernames = [user] - else: - usernames = [] - prefix = urllib.quote(self.fqdn_listname) + '+user+' - for k in c.keys(): - if k.startswith(prefix): - usernames.append(k[len(prefix):]) - # If any check out, we're golden. Note: '@'s are no longer legal - # values in cookie keys. - for user in [Utils.UnobscureEmail(u) for u in usernames]: - ok = self.__checkone(c, authcontext, user) - if ok: - return True - return False - else: - return self.__checkone(c, authcontext, user) - - def __checkone(self, c, authcontext, user): - # Do the guts of the cookie check, for one authcontext/user - # combination. - try: - key, secret = self.AuthContextInfo(authcontext, user) - except Errors.NotAMemberError: - return False - if key not in c or not isinstance(secret, basestring): - return False - # Undo the encoding we performed in MakeCookie() above. BAW: I - # believe this is safe from exploit because marshal can't be forced to - # load recursive data structures, and it can't be forced to execute - # any unexpected code. The worst that can happen is that either the - # client will have provided us bogus data, in which case we'll get one - # of the caught exceptions, or marshal format will have changed, in - # which case, the cookie decoding will fail. In either case, we'll - # simply request reauthorization, resulting in a new cookie being - # returned to the client. - try: - data = marshal.loads(binascii.unhexlify(c[key])) - issued, received_mac = data - except (EOFError, ValueError, TypeError, KeyError): - return False - # Make sure the issued timestamp makes sense - now = time.time() - if now < issued: - return False - # Calculate what the mac ought to be based on the cookie's timestamp - # and the shared secret. - mac = sha.new(secret + repr(issued)).hexdigest() - if mac <> received_mac: - return False - # Authenticated! - return True - - - -splitter = re.compile(';\s*') - -def parsecookie(s): - c = {} - for line in s.splitlines(): - for p in splitter.split(line): - try: - k, v = p.split('=', 1) - except ValueError: - pass - else: - c[k] = v - return c diff --git a/mailman/attic/bin/clone_member b/mailman/attic/bin/clone_member deleted file mode 100755 index 1f2a03aca..000000000 --- a/mailman/attic/bin/clone_member +++ /dev/null @@ -1,219 +0,0 @@ -#! @PYTHON@ -# -# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc. -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -"""Clone a member address. - -Cloning a member address means that a new member will be added who has all the -same options and passwords as the original member address. Note that this -operation is fairly trusting of the user who runs it -- it does no -verification to the new address, it does not send out a welcome message, etc. - -The existing member's subscription is usually not modified in any way. If you -want to remove the old address, use the -r flag. If you also want to change -any list admin addresses, use the -a flag. - -Usage: - clone_member [options] fromoldaddr tonewaddr - -Where: - - --listname=listname - -l listname - Check and modify only the named mailing lists. If -l is not given, - then all mailing lists are scanned from the address. Multiple -l - options can be supplied. - - --remove - -r - Remove the old address from the mailing list after it's been cloned. - - --admin - -a - Scan the list admin addresses for the old address, and clone or change - them too. - - --quiet - -q - Do the modifications quietly. - - --nomodify - -n - Print what would be done, but don't actually do it. Inhibits the - --quiet flag. - - --help - -h - Print this help message and exit. - - fromoldaddr (`from old address') is the old address of the user. tonewaddr - (`to new address') is the new address of the user. - -""" - -import sys -import getopt - -import paths -from Mailman import MailList -from Mailman import Utils -from Mailman import Errors -from Mailman.i18n import _ - - - -def usage(code, msg=''): - if code: - fd = sys.stderr - else: - fd = sys.stdout - print >> fd, _(__doc__) - if msg: - print >> fd, msg - sys.exit(code) - - - -def dolist(mlist, options): - SPACE = ' ' - if not options.quiet: - print _('processing mailing list:'), mlist.internal_name() - - # scan the list owners. TBD: mlist.owner keys should be lowercase? - oldowners = mlist.owner[:] - oldowners.sort() - if options.admintoo: - if not options.quiet: - print _(' scanning list owners:'), SPACE.join(oldowners) - newowners = {} - foundp = 0 - for owner in mlist.owner: - if options.lfromaddr == owner.lower(): - foundp = 1 - if options.remove: - continue - newowners[owner] = 1 - if foundp: - newowners[options.toaddr] = 1 - newowners = newowners.keys() - newowners.sort() - if options.modify: - mlist.owner = newowners - if not options.quiet: - if newowners <> oldowners: - print - print _(' new list owners:'), SPACE.join(newowners) - else: - print _('(no change)') - - # see if the fromaddr is a digest member or regular member - if options.lfromaddr in mlist.getDigestMemberKeys(): - digest = 1 - elif options.lfromaddr in mlist.getRegularMemberKeys(): - digest = 0 - else: - if not options.quiet: - print _(' address not found:'), options.fromaddr - return - - # Now change the membership address - try: - if options.modify: - mlist.changeMemberAddress(options.fromaddr, options.toaddr, - not options.remove) - if not options.quiet: - print _(' clone address added:'), options.toaddr - except Errors.MMAlreadyAMember: - if not options.quiet: - print _(' clone address is already a member:'), options.toaddr - - if options.remove: - print _(' original address removed:'), options.fromaddr - - - -def main(): - # default options - class Options: - listnames = None - remove = 0 - admintoo = 0 - quiet = 0 - modify = 1 - - # scan sysargs - try: - opts, args = getopt.getopt( - sys.argv[1:], 'arl:qnh', - ['admin', 'remove', 'listname=', 'quiet', 'nomodify', 'help']) - except getopt.error, msg: - usage(1, msg) - - options = Options() - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-q', '--quiet'): - options.quiet = 1 - elif opt in ('-n', '--nomodify'): - options.modify = 0 - elif opt in ('-a', '--admin'): - options.admintoo = 1 - elif opt in ('-r', '--remove'): - options.remove = 1 - elif opt in ('-l', '--listname'): - if options.listnames is None: - options.listnames = [] - options.listnames.append(arg.lower()) - - # further options and argument processing - if not options.modify: - options.quiet = 0 - - if len(args) <> 2: - usage(1) - fromaddr = args[0] - toaddr = args[1] - - # validate and normalize the target address - try: - Utils.ValidateEmail(toaddr) - except Errors.EmailAddressError: - usage(1, _('Not a valid email address: %(toaddr)s')) - lfromaddr = fromaddr.lower() - options.toaddr = toaddr - options.fromaddr = fromaddr - options.lfromaddr = lfromaddr - - if options.listnames is None: - options.listnames = Utils.list_names() - - for listname in options.listnames: - try: - mlist = MailList.MailList(listname) - except Errors.MMListError, e: - print _('Error opening list "%(listname)s", skipping.\n%(e)s') - continue - try: - dolist(mlist, options) - finally: - mlist.Save() - mlist.Unlock() - - -if __name__ == '__main__': - main() diff --git a/mailman/attic/bin/discard b/mailman/attic/bin/discard deleted file mode 100644 index c30198441..000000000 --- a/mailman/attic/bin/discard +++ /dev/null @@ -1,120 +0,0 @@ -#! @PYTHON@ -# -# Copyright (C) 2003 by the Free Software Foundation, Inc. -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -"""Discard held messages. - -Usage: - discard [options] file ... - -Options: - --help / -h - Print this help message and exit. - - --quiet / -q - Don't print status messages. -""" - -# TODO: add command line arguments for specifying other actions than DISCARD, -# and also for specifying other __handlepost() arguments, i.e. comment, -# preserve, forward, addr - -import os -import re -import sys -import getopt - -import paths -from Mailman import mm_cfg -from Mailman.MailList import MailList -from Mailman.i18n import _ - -try: - True, False -except NameError: - True = 1 - False = 0 - -cre = re.compile(r'heldmsg-(?P<listname>.*)-(?P<id>[0-9]+)\.(pck|txt)$') - - - -def usage(code, msg=''): - if code: - fd = sys.stderr - else: - fd = sys.stdout - print >> fd, _(__doc__) - if msg: - print >> fd, msg - sys.exit(code) - - - -def main(): - try: - opts, args = getopt.getopt(sys.argv[1:], 'hq', ['help', 'quiet']) - except getopt.error, msg: - usage(1, msg) - - quiet = False - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-q', '--quiet'): - quiet = True - - files = args - if not files: - print _('Nothing to do.') - - # Mapping from listnames to sequence of request ids - discards = {} - - # Cruise through all the named files, collating by mailing list. We'll - # lock the list once, process all holds for that list and move on. - for f in files: - basename = os.path.basename(f) - mo = cre.match(basename) - if not mo: - print >> sys.stderr, _('Ignoring non-held message: %(f)s') - continue - listname, id = mo.group('listname', 'id') - try: - id = int(id) - except (ValueError, TypeError): - print >> sys.stderr, _('Ignoring held msg w/bad id: %(f)s') - continue - discards.setdefault(listname, []).append(id) - - # Now do the discards - for listname, ids in discards.items(): - mlist = MailList(listname) - try: - for id in ids: - # No comment, no preserve, no forward, no forwarding address - mlist.HandleRequest(id, mm_cfg.DISCARD, '', False, False, '') - if not quiet: - print _('Discarded held msg #%(id)s for list %(listname)s') - mlist.Save() - finally: - mlist.Unlock() - - - -if __name__ == '__main__': - main() diff --git a/mailman/attic/bin/fix_url.py b/mailman/attic/bin/fix_url.py deleted file mode 100644 index 30618a1a3..000000000 --- a/mailman/attic/bin/fix_url.py +++ /dev/null @@ -1,93 +0,0 @@ -#! @PYTHON@ -# -# Copyright (C) 2001-2009 by the Free Software Foundation, Inc. -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, -# USA. - -"""Reset a list's web_page_url attribute to the default setting. - -This script is intended to be run as a bin/withlist script, i.e. - -% bin/withlist -l -r fix_url listname [options] - -Options: - -u urlhost - --urlhost=urlhost - Look up urlhost in the virtual host table and set the web_page_url and - host_name attributes of the list to the values found. This - essentially moves the list from one virtual domain to another. - - Without this option, the default web_page_url and host_name values are - used. - - -v / --verbose - Print what the script is doing. - -If run standalone, it prints this help text and exits. -""" - -import sys -import getopt - -import paths -from Mailman.configuration import config -from Mailman.i18n import _ - - - -def usage(code, msg=''): - print _(__doc__.replace('%', '%%')) - if msg: - print msg - sys.exit(code) - - - -def fix_url(mlist, *args): - try: - opts, args = getopt.getopt(args, 'u:v', ['urlhost=', 'verbose']) - except getopt.error, msg: - usage(1, msg) - - verbose = 0 - urlhost = mailhost = None - for opt, arg in opts: - if opt in ('-u', '--urlhost'): - urlhost = arg - elif opt in ('-v', '--verbose'): - verbose = 1 - - if urlhost: - web_page_url = config.DEFAULT_URL_PATTERN % urlhost - mailhost = config.VIRTUAL_HOSTS.get(urlhost.lower(), urlhost) - else: - web_page_url = config.DEFAULT_URL_PATTERN % config.DEFAULT_URL_HOST - mailhost = config.DEFAULT_EMAIL_HOST - - if verbose: - print _('Setting web_page_url to: %(web_page_url)s') - mlist.web_page_url = web_page_url - if verbose: - print _('Setting host_name to: %(mailhost)s') - mlist.host_name = mailhost - print _('Saving list') - mlist.Save() - mlist.Unlock() - - - -if __name__ == '__main__': - usage(0) diff --git a/mailman/attic/bin/list_admins b/mailman/attic/bin/list_admins deleted file mode 100644 index c628a42dc..000000000 --- a/mailman/attic/bin/list_admins +++ /dev/null @@ -1,101 +0,0 @@ -#! @PYTHON@ -# -# Copyright (C) 2001,2002 by the Free Software Foundation, Inc. -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -"""List all the owners of a mailing list. - -Usage: %(program)s [options] listname ... - -Where: - - --all-vhost=vhost - -v=vhost - List the owners of all the mailing lists for the given virtual host. - - --all - -a - List the owners of all the mailing lists on this system. - - --help - -h - Print this help message and exit. - -`listname' is the name of the mailing list to print the owners of. You can -have more than one named list on the command line. -""" - -import sys -import getopt - -import paths -from Mailman import MailList, Utils -from Mailman import Errors -from Mailman.i18n import _ - -COMMASPACE = ', ' - -program = sys.argv[0] - - - -def usage(code, msg=''): - if code: - fd = sys.stderr - else: - fd = sys.stdout - print >> fd, _(__doc__) - if msg: - print >> fd, msg - sys.exit(code) - - - -def main(): - try: - opts, args = getopt.getopt(sys.argv[1:], 'hv:a', - ['help', 'all-vhost=', 'all']) - except getopt.error, msg: - usage(1, msg) - - listnames = args - vhost = None - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-a', '--all'): - listnames = Utils.list_names() - elif opt in ('-v', '--all-vhost'): - listnames = Utils.list_names() - vhost = arg - - for listname in listnames: - try: - mlist = MailList.MailList(listname, lock=0) - except Errors.MMListError, e: - print _('No such list: %(listname)s') - continue - - if vhost and vhost <> mlist.host_name: - continue - - owners = COMMASPACE.join(mlist.owner) - print _('List: %(listname)s, \tOwners: %(owners)s') - - - -if __name__ == '__main__': - main() diff --git a/mailman/attic/bin/msgfmt.py b/mailman/attic/bin/msgfmt.py deleted file mode 100644 index 8a2d4e66e..000000000 --- a/mailman/attic/bin/msgfmt.py +++ /dev/null @@ -1,203 +0,0 @@ -#! /usr/bin/env python -# -*- coding: iso-8859-1 -*- -# Written by Martin v. Löwis <loewis@informatik.hu-berlin.de> - -"""Generate binary message catalog from textual translation description. - -This program converts a textual Uniforum-style message catalog (.po file) into -a binary GNU catalog (.mo file). This is essentially the same function as the -GNU msgfmt program, however, it is a simpler implementation. - -Usage: msgfmt.py [OPTIONS] filename.po - -Options: - -o file - --output-file=file - Specify the output file to write to. If omitted, output will go to a - file named filename.mo (based off the input file name). - - -h - --help - Print this message and exit. - - -V - --version - Display version information and exit. -""" - -import sys -import os -import getopt -import struct -import array - -__version__ = "1.1" - -MESSAGES = {} - - - -def usage(code, msg=''): - print >> sys.stderr, __doc__ - if msg: - print >> sys.stderr, msg - sys.exit(code) - - - -def add(id, str, fuzzy): - "Add a non-fuzzy translation to the dictionary." - global MESSAGES - if not fuzzy and str: - MESSAGES[id] = str - - - -def generate(): - "Return the generated output." - global MESSAGES - keys = MESSAGES.keys() - # the keys are sorted in the .mo file - keys.sort() - offsets = [] - ids = strs = '' - for id in keys: - # For each string, we need size and file offset. Each string is NUL - # terminated; the NUL does not count into the size. - offsets.append((len(ids), len(id), len(strs), len(MESSAGES[id]))) - ids += id + '\0' - strs += MESSAGES[id] + '\0' - output = '' - # The header is 7 32-bit unsigned integers. We don't use hash tables, so - # the keys start right after the index tables. - # translated string. - keystart = 7*4+16*len(keys) - # and the values start after the keys - valuestart = keystart + len(ids) - koffsets = [] - voffsets = [] - # The string table first has the list of keys, then the list of values. - # Each entry has first the size of the string, then the file offset. - for o1, l1, o2, l2 in offsets: - koffsets += [l1, o1+keystart] - voffsets += [l2, o2+valuestart] - offsets = koffsets + voffsets - output = struct.pack("Iiiiiii", - 0x950412deL, # Magic - 0, # Version - len(keys), # # of entries - 7*4, # start of key index - 7*4+len(keys)*8, # start of value index - 0, 0) # size and offset of hash table - output += array.array("i", offsets).tostring() - output += ids - output += strs - return output - - - -def make(filename, outfile): - ID = 1 - STR = 2 - - # Compute .mo name from .po name and arguments - if filename.endswith('.po'): - infile = filename - else: - infile = filename + '.po' - if outfile is None: - outfile = os.path.splitext(infile)[0] + '.mo' - - try: - lines = open(infile).readlines() - except IOError, msg: - print >> sys.stderr, msg - sys.exit(1) - - section = None - fuzzy = 0 - - # Parse the catalog - lno = 0 - for l in lines: - lno += 1 - # If we get a comment line after a msgstr, this is a new entry - if l[0] == '#' and section == STR: - add(msgid, msgstr, fuzzy) - section = None - fuzzy = 0 - # Record a fuzzy mark - if l[:2] == '#,' and l.find('fuzzy'): - fuzzy = 1 - # Skip comments - if l[0] == '#': - continue - # Now we are in a msgid section, output previous section - if l.startswith('msgid'): - if section == STR: - add(msgid, msgstr, fuzzy) - section = ID - l = l[5:] - msgid = msgstr = '' - # Now we are in a msgstr section - elif l.startswith('msgstr'): - section = STR - l = l[6:] - # Skip empty lines - l = l.strip() - if not l: - continue - # XXX: Does this always follow Python escape semantics? - l = eval(l) - if section == ID: - msgid += l - elif section == STR: - msgstr += l - else: - print >> sys.stderr, 'Syntax error on %s:%d' % (infile, lno), \ - 'before:' - print >> sys.stderr, l - sys.exit(1) - # Add last entry - if section == STR: - add(msgid, msgstr, fuzzy) - - # Compute output - output = generate() - - try: - open(outfile,"wb").write(output) - except IOError,msg: - print >> sys.stderr, msg - - - -def main(): - try: - opts, args = getopt.getopt(sys.argv[1:], 'hVo:', - ['help', 'version', 'output-file=']) - except getopt.error, msg: - usage(1, msg) - - outfile = None - # parse options - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-V', '--version'): - print >> sys.stderr, "msgfmt.py", __version__ - sys.exit(0) - elif opt in ('-o', '--output-file'): - outfile = arg - # do it - if not args: - print >> sys.stderr, 'No input file given' - print >> sys.stderr, "Try `msgfmt --help' for more information." - return - - for filename in args: - make(filename, outfile) - - -if __name__ == '__main__': - main() diff --git a/mailman/attic/bin/po2templ.py b/mailman/attic/bin/po2templ.py deleted file mode 100644 index 86eae96b9..000000000 --- a/mailman/attic/bin/po2templ.py +++ /dev/null @@ -1,90 +0,0 @@ -#! @PYTHON@ -# -# Copyright (C) 2005-2009 by the Free Software Foundation, Inc. -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, -# USA. - -# Author: Tokio Kikuchi <tkikuchi@is.kochi-u.ac.jp> - - -"""po2templ.py - -Extract templates from language po file. - -Usage: po2templ.py languages -""" - -import re -import sys - -cre = re.compile('^#:\s*templates/en/(?P<filename>.*?):1') - - - -def do_lang(lang): - in_template = False - in_msg = False - msgstr = '' - fp = file('messages/%s/LC_MESSAGES/mailman.po' % lang) - try: - for line in fp: - m = cre.search(line) - if m: - in_template = True - in_msg = False - filename = m.group('filename') - outfilename = 'templates/%s/%s' % (lang, filename) - continue - if in_template and line.startswith('#,'): - if line.strip() == '#, fuzzy': - in_template = False - continue - if in_template and line.startswith('msgstr'): - line = line[7:] - in_msg = True - if in_msg: - if not line.strip(): - in_template = False - in_msg = False - if len(msgstr) > 1 and outfilename: - # exclude no translation ... 1 is for LF only - outfile = file(outfilename, 'w') - try: - outfile.write(msgstr) - outfile.write('\n') - finally: - outfile.close() - outfilename = '' - msgstr = '' - continue - msgstr += eval(line) - finally: - fp.close() - if len(msgstr) > 1 and outfilename: - # flush remaining msgstr (last template file) - outfile = file(outfilename, 'w') - try: - outfile.write(msgstr) - outfile.write('\n') - finally: - outfile.close() - - - -if __name__ == '__main__': - langs = sys.argv[1:] - for lang in langs: - do_lang(lang) diff --git a/mailman/attic/bin/pygettext.py b/mailman/attic/bin/pygettext.py deleted file mode 100644 index 84421ee8c..000000000 --- a/mailman/attic/bin/pygettext.py +++ /dev/null @@ -1,545 +0,0 @@ -#! @PYTHON@ -# Originally written by Barry Warsaw <barry@zope.com> -# -# Minimally patched to make it even more xgettext compatible -# by Peter Funk <pf@artcom-gmbh.de> - -"""pygettext -- Python equivalent of xgettext(1) - -Many systems (Solaris, Linux, Gnu) provide extensive tools that ease the -internationalization of C programs. Most of these tools are independent of -the programming language and can be used from within Python programs. Martin -von Loewis' work[1] helps considerably in this regard. - -There's one problem though; xgettext is the program that scans source code -looking for message strings, but it groks only C (or C++). Python introduces -a few wrinkles, such as dual quoting characters, triple quoted strings, and -raw strings. xgettext understands none of this. - -Enter pygettext, which uses Python's standard tokenize module to scan Python -source code, generating .pot files identical to what GNU xgettext[2] generates -for C and C++ code. From there, the standard GNU tools can be used. - -A word about marking Python strings as candidates for translation. GNU -xgettext recognizes the following keywords: gettext, dgettext, dcgettext, and -gettext_noop. But those can be a lot of text to include all over your code. -C and C++ have a trick: they use the C preprocessor. Most internationalized C -source includes a #define for gettext() to _() so that what has to be written -in the source is much less. Thus these are both translatable strings: - - gettext("Translatable String") - _("Translatable String") - -Python of course has no preprocessor so this doesn't work so well. Thus, -pygettext searches only for _() by default, but see the -k/--keyword flag -below for how to augment this. - - [1] http://www.python.org/workshops/1997-10/proceedings/loewis.html - [2] http://www.gnu.org/software/gettext/gettext.html - -NOTE: pygettext attempts to be option and feature compatible with GNU xgettext -where ever possible. However some options are still missing or are not fully -implemented. Also, xgettext's use of command line switches with option -arguments is broken, and in these cases, pygettext just defines additional -switches. - -Usage: pygettext [options] inputfile ... - -Options: - - -a - --extract-all - Extract all strings. - - -d name - --default-domain=name - Rename the default output file from messages.pot to name.pot. - - -E - --escape - Replace non-ASCII characters with octal escape sequences. - - -D - --docstrings - Extract module, class, method, and function docstrings. These do not - need to be wrapped in _() markers, and in fact cannot be for Python to - consider them docstrings. (See also the -X option). - - -h - --help - Print this help message and exit. - - -k word - --keyword=word - Keywords to look for in addition to the default set, which are: - %(DEFAULTKEYWORDS)s - - You can have multiple -k flags on the command line. - - -K - --no-default-keywords - Disable the default set of keywords (see above). Any keywords - explicitly added with the -k/--keyword option are still recognized. - - --no-location - Do not write filename/lineno location comments. - - -n - --add-location - Write filename/lineno location comments indicating where each - extracted string is found in the source. These lines appear before - each msgid. The style of comments is controlled by the -S/--style - option. This is the default. - - -o filename - --output=filename - Rename the default output file from messages.pot to filename. If - filename is `-' then the output is sent to standard out. - - -p dir - --output-dir=dir - Output files will be placed in directory dir. - - -S stylename - --style stylename - Specify which style to use for location comments. Two styles are - supported: - - Solaris # File: filename, line: line-number - GNU #: filename:line - - The style name is case insensitive. GNU style is the default. - - -v - --verbose - Print the names of the files being processed. - - -V - --version - Print the version of pygettext and exit. - - -w columns - --width=columns - Set width of output to columns. - - -x filename - --exclude-file=filename - Specify a file that contains a list of strings that are not be - extracted from the input files. Each string to be excluded must - appear on a line by itself in the file. - - -X filename - --no-docstrings=filename - Specify a file that contains a list of files (one per line) that - should not have their docstrings extracted. This is only useful in - conjunction with the -D option above. - -If `inputfile' is -, standard input is read. -""" - -import os -import sys -import time -import getopt -import tokenize -import operator - -# for selftesting -try: - import fintl - _ = fintl.gettext -except ImportError: - def _(s): return s - -__version__ = '1.4' - -default_keywords = ['_'] -DEFAULTKEYWORDS = ', '.join(default_keywords) - -EMPTYSTRING = '' - - - -# The normal pot-file header. msgmerge and Emacs's po-mode work better if it's -# there. -pot_header = _('''\ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR ORGANIZATION -# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. -# -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\\n" -"POT-Creation-Date: %(time)s\\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n" -"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n" -"Language-Team: LANGUAGE <LL@li.org>\\n" -"MIME-Version: 1.0\\n" -"Content-Type: text/plain; charset=CHARSET\\n" -"Content-Transfer-Encoding: ENCODING\\n" -"Generated-By: pygettext.py %(version)s\\n" - -''') - - -def usage(code, msg=''): - if code: - fd = sys.stderr - else: - fd = sys.stdout - print >> fd, _(__doc__) % globals() - if msg: - print >> fd, msg - sys.exit(code) - - - -escapes = [] - -def make_escapes(pass_iso8859): - global escapes - if pass_iso8859: - # Allow iso-8859 characters to pass through so that e.g. 'msgid - # "H[o-umlaut]he"' would result not result in 'msgid "H\366he"'. - # Otherwise we escape any character outside the 32..126 range. - mod = 128 - else: - mod = 256 - for i in range(256): - if 32 <= (i % mod) <= 126: - escapes.append(chr(i)) - else: - escapes.append("\\%03o" % i) - escapes[ord('\\')] = '\\\\' - escapes[ord('\t')] = '\\t' - escapes[ord('\r')] = '\\r' - escapes[ord('\n')] = '\\n' - escapes[ord('\"')] = '\\"' - - -def escape(s): - global escapes - s = list(s) - for i in range(len(s)): - s[i] = escapes[ord(s[i])] - return EMPTYSTRING.join(s) - - -def safe_eval(s): - # unwrap quotes, safely - return eval(s, {'__builtins__':{}}, {}) - - -def normalize(s): - # This converts the various Python string types into a format that is - # appropriate for .po files, namely much closer to C style. - lines = s.split('\n') - if len(lines) == 1: - s = '"' + escape(s) + '"' - else: - if not lines[-1]: - del lines[-1] - lines[-1] = lines[-1] + '\n' - for i in range(len(lines)): - lines[i] = escape(lines[i]) - lineterm = '\\n"\n"' - s = '""\n"' + lineterm.join(lines) + '"' - return s - - - -class TokenEater: - def __init__(self, options): - self.__options = options - self.__messages = {} - self.__state = self.__waiting - self.__data = [] - self.__lineno = -1 - self.__freshmodule = 1 - self.__curfile = None - - def __call__(self, ttype, tstring, stup, etup, line): - # dispatch -## import token -## print >> sys.stderr, 'ttype:', token.tok_name[ttype], \ -## 'tstring:', tstring - self.__state(ttype, tstring, stup[0]) - - def __waiting(self, ttype, tstring, lineno): - opts = self.__options - # Do docstring extractions, if enabled - if opts.docstrings and not opts.nodocstrings.get(self.__curfile): - # module docstring? - if self.__freshmodule: - if ttype == tokenize.STRING: - self.__addentry(safe_eval(tstring), lineno, isdocstring=1) - self.__freshmodule = 0 - elif ttype not in (tokenize.COMMENT, tokenize.NL): - self.__freshmodule = 0 - return - # class docstring? - if ttype == tokenize.NAME and tstring in ('class', 'def'): - self.__state = self.__suiteseen - return - if ttype == tokenize.NAME and tstring in opts.keywords: - self.__state = self.__keywordseen - - def __suiteseen(self, ttype, tstring, lineno): - # ignore anything until we see the colon - if ttype == tokenize.OP and tstring == ':': - self.__state = self.__suitedocstring - - def __suitedocstring(self, ttype, tstring, lineno): - # ignore any intervening noise - if ttype == tokenize.STRING: - self.__addentry(safe_eval(tstring), lineno, isdocstring=1) - self.__state = self.__waiting - elif ttype not in (tokenize.NEWLINE, tokenize.INDENT, - tokenize.COMMENT): - # there was no class docstring - self.__state = self.__waiting - - def __keywordseen(self, ttype, tstring, lineno): - if ttype == tokenize.OP and tstring == '(': - self.__data = [] - self.__lineno = lineno - self.__state = self.__openseen - else: - self.__state = self.__waiting - - def __openseen(self, ttype, tstring, lineno): - if ttype == tokenize.OP and tstring == ')': - # We've seen the last of the translatable strings. Record the - # line number of the first line of the strings and update the list - # of messages seen. Reset state for the next batch. If there - # were no strings inside _(), then just ignore this entry. - if self.__data: - self.__addentry(EMPTYSTRING.join(self.__data)) - self.__state = self.__waiting - elif ttype == tokenize.STRING: - self.__data.append(safe_eval(tstring)) - # TBD: should we warn if we seen anything else? - - def __addentry(self, msg, lineno=None, isdocstring=0): - if lineno is None: - lineno = self.__lineno - if not msg in self.__options.toexclude: - entry = (self.__curfile, lineno) - self.__messages.setdefault(msg, {})[entry] = isdocstring - - def set_filename(self, filename): - self.__curfile = filename - self.__freshmodule = 1 - - def write(self, fp): - options = self.__options - timestamp = time.ctime(time.time()) - # The time stamp in the header doesn't have the same format as that - # generated by xgettext... - print >> fp, pot_header % {'time': timestamp, 'version': __version__} - # Sort the entries. First sort each particular entry's keys, then - # sort all the entries by their first item. - reverse = {} - for k, v in self.__messages.items(): - keys = v.keys() - keys.sort() - reverse.setdefault(tuple(keys), []).append((k, v)) - rkeys = reverse.keys() - rkeys.sort() - for rkey in rkeys: - rentries = reverse[rkey] - rentries.sort() - for k, v in rentries: - isdocstring = 0 - # If the entry was gleaned out of a docstring, then add a - # comment stating so. This is to aid translators who may wish - # to skip translating some unimportant docstrings. - if reduce(operator.__add__, v.values()): - isdocstring = 1 - # k is the message string, v is a dictionary-set of (filename, - # lineno) tuples. We want to sort the entries in v first by - # file name and then by line number. - v = v.keys() - v.sort() - if not options.writelocations: - pass - # location comments are different b/w Solaris and GNU: - elif options.locationstyle == options.SOLARIS: - for filename, lineno in v: - d = {'filename': filename, 'lineno': lineno} - print >>fp, _( - '# File: %(filename)s, line: %(lineno)d') % d - elif options.locationstyle == options.GNU: - # fit as many locations on one line, as long as the - # resulting line length doesn't exceeds 'options.width' - locline = '#:' - for filename, lineno in v: - d = {'filename': filename, 'lineno': lineno} - s = _(' %(filename)s:%(lineno)d') % d - if len(locline) + len(s) <= options.width: - locline = locline + s - else: - print >> fp, locline - locline = "#:" + s - if len(locline) > 2: - print >> fp, locline - if isdocstring: - print >> fp, '#, docstring' - print >> fp, 'msgid', normalize(k) - print >> fp, 'msgstr ""\n' - - - -def main(): - global default_keywords - try: - opts, args = getopt.getopt( - sys.argv[1:], - 'ad:DEhk:Kno:p:S:Vvw:x:X:', - ['extract-all', 'default-domain=', 'escape', 'help', - 'keyword=', 'no-default-keywords', - 'add-location', 'no-location', 'output=', 'output-dir=', - 'style=', 'verbose', 'version', 'width=', 'exclude-file=', - 'docstrings', 'no-docstrings', - ]) - except getopt.error, msg: - usage(1, msg) - - # for holding option values - class Options: - # constants - GNU = 1 - SOLARIS = 2 - # defaults - extractall = 0 # FIXME: currently this option has no effect at all. - escape = 0 - keywords = [] - outpath = '' - outfile = 'messages.pot' - writelocations = 1 - locationstyle = GNU - verbose = 0 - width = 78 - excludefilename = '' - docstrings = 0 - nodocstrings = {} - - options = Options() - locations = {'gnu' : options.GNU, - 'solaris' : options.SOLARIS, - } - - # parse options - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-a', '--extract-all'): - options.extractall = 1 - elif opt in ('-d', '--default-domain'): - options.outfile = arg + '.pot' - elif opt in ('-E', '--escape'): - options.escape = 1 - elif opt in ('-D', '--docstrings'): - options.docstrings = 1 - elif opt in ('-k', '--keyword'): - options.keywords.append(arg) - elif opt in ('-K', '--no-default-keywords'): - default_keywords = [] - elif opt in ('-n', '--add-location'): - options.writelocations = 1 - elif opt in ('--no-location',): - options.writelocations = 0 - elif opt in ('-S', '--style'): - options.locationstyle = locations.get(arg.lower()) - if options.locationstyle is None: - usage(1, _('Invalid value for --style: %s') % arg) - elif opt in ('-o', '--output'): - options.outfile = arg - elif opt in ('-p', '--output-dir'): - options.outpath = arg - elif opt in ('-v', '--verbose'): - options.verbose = 1 - elif opt in ('-V', '--version'): - print _('pygettext.py (xgettext for Python) %s') % __version__ - sys.exit(0) - elif opt in ('-w', '--width'): - try: - options.width = int(arg) - except ValueError: - usage(1, _('--width argument must be an integer: %s') % arg) - elif opt in ('-x', '--exclude-file'): - options.excludefilename = arg - elif opt in ('-X', '--no-docstrings'): - fp = open(arg) - try: - while 1: - line = fp.readline() - if not line: - break - options.nodocstrings[line[:-1]] = 1 - finally: - fp.close() - - # calculate escapes - make_escapes(options.escape) - - # calculate all keywords - options.keywords.extend(default_keywords) - - # initialize list of strings to exclude - if options.excludefilename: - try: - fp = open(options.excludefilename) - options.toexclude = fp.readlines() - fp.close() - except IOError: - print >> sys.stderr, _( - "Can't read --exclude-file: %s") % options.excludefilename - sys.exit(1) - else: - options.toexclude = [] - - # slurp through all the files - eater = TokenEater(options) - for filename in args: - if filename == '-': - if options.verbose: - print _('Reading standard input') - fp = sys.stdin - closep = 0 - else: - if options.verbose: - print _('Working on %s') % filename - fp = open(filename) - closep = 1 - try: - eater.set_filename(filename) - try: - tokenize.tokenize(fp.readline, eater) - except tokenize.TokenError, e: - print >> sys.stderr, '%s: %s, line %d, column %d' % ( - e[0], filename, e[1][0], e[1][1]) - finally: - if closep: - fp.close() - - # write the output - if options.outfile == '-': - fp = sys.stdout - closep = 0 - else: - if options.outpath: - options.outfile = os.path.join(options.outpath, options.outfile) - fp = open(options.outfile, 'w') - closep = 1 - try: - eater.write(fp) - finally: - if closep: - fp.close() - - -if __name__ == '__main__': - main() - # some more test strings - _(u'a unicode string') diff --git a/mailman/attic/bin/remove_members b/mailman/attic/bin/remove_members deleted file mode 100755 index a7b4ebb47..000000000 --- a/mailman/attic/bin/remove_members +++ /dev/null @@ -1,186 +0,0 @@ -#! @PYTHON@ -# -# Copyright (C) 1998-2005 by the Free Software Foundation, Inc. -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, -# USA. - -"""Remove members from a list. - -Usage: - remove_members [options] [listname] [addr1 ...] - -Options: - - --file=file - -f file - Remove member addresses found in the given file. If file is - `-', read stdin. - - --all - -a - Remove all members of the mailing list. - (mutually exclusive with --fromall) - - --fromall - Removes the given addresses from all the lists on this system - regardless of virtual domains if you have any. This option cannot be - used -a/--all. Also, you should not specify a listname when using - this option. - - --nouserack - -n - Don't send the user acknowledgements. If not specified, the list - default value is used. - - --noadminack - -N - Don't send the admin acknowledgements. If not specified, the list - default value is used. - - --help - -h - Print this help message and exit. - - listname is the name of the mailing list to use. - - addr1 ... are additional addresses to remove. -""" - -import sys -import getopt - -import paths -from Mailman import MailList -from Mailman import Utils -from Mailman import Errors -from Mailman.i18n import _ - -try: - True, False -except NameError: - True = 1 - False = 0 - - - -def usage(code, msg=''): - if code: - fd = sys.stderr - else: - fd = sys.stdout - print >> fd, _(__doc__) - if msg: - print >> fd, msg - sys.exit(code) - - -def ReadFile(filename): - lines = [] - if filename == "-": - fp = sys.stdin - closep = False - else: - fp = open(filename) - closep = True - lines = filter(None, [line.strip() for line in fp.readlines()]) - if closep: - fp.close() - return lines - - - -def main(): - try: - opts, args = getopt.getopt( - sys.argv[1:], 'naf:hN', - ['all', 'fromall', 'file=', 'help', 'nouserack', 'noadminack']) - except getopt.error, msg: - usage(1, msg) - - filename = None - all = False - alllists = False - # None means use list default - userack = None - admin_notif = None - - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-f', '--file'): - filename = arg - elif opt in ('-a', '--all'): - all = True - elif opt == '--fromall': - alllists = True - elif opt in ('-n', '--nouserack'): - userack = False - elif opt in ('-N', '--noadminack'): - admin_notif = False - - if len(args) < 1 and not (filename and alllists): - usage(1) - - # You probably don't want to delete all the users of all the lists -- Marc - if all and alllists: - usage(1) - - if alllists: - addresses = args - else: - listname = args[0].lower().strip() - addresses = args[1:] - - if alllists: - listnames = Utils.list_names() - else: - listnames = [listname] - - if filename: - try: - addresses = addresses + ReadFile(filename) - except IOError: - print _('Could not open file for reading: %(filename)s.') - - for listname in listnames: - try: - # open locked - mlist = MailList.MailList(listname) - except Errors.MMListError: - print _('Error opening list %(listname)s... skipping.') - continue - - if all: - addresses = mlist.getMembers() - - try: - for addr in addresses: - if not mlist.isMember(addr): - if not alllists: - print _('No such member: %(addr)s') - continue - mlist.ApprovedDeleteMember(addr, 'bin/remove_members', - admin_notif, userack) - if alllists: - print _("User `%(addr)s' removed from list: %(listname)s.") - mlist.Save() - finally: - mlist.Unlock() - - - -if __name__ == '__main__': - main() diff --git a/mailman/attic/bin/reset_pw.py b/mailman/attic/bin/reset_pw.py deleted file mode 100644 index 453c8b849..000000000 --- a/mailman/attic/bin/reset_pw.py +++ /dev/null @@ -1,83 +0,0 @@ -#! @PYTHON@ -# -# Copyright (C) 2004-2009 by the Free Software Foundation, Inc. -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -# Inspired by Florian Weimer. - -"""Reset the passwords for members of a mailing list. - -This script resets all the passwords of a mailing list's members. It can also -be used to reset the lists of all members of all mailing lists, but it is your -responsibility to let the users know that their passwords have been changed. - -This script is intended to be run as a bin/withlist script, i.e. - -% bin/withlist -l -r reset_pw listname [options] - -Options: - -v / --verbose - Print what the script is doing. -""" - -import sys -import getopt - -import paths -from Mailman import Utils -from Mailman.i18n import _ - - - -def usage(code, msg=''): - if code: - fd = sys.stderr - else: - fd = sys.stdout - print >> fd, _(__doc__.replace('%', '%%')) - if msg: - print >> fd, msg - sys.exit(code) - - - -def reset_pw(mlist, *args): - try: - opts, args = getopt.getopt(args, 'v', ['verbose']) - except getopt.error, msg: - usage(1, msg) - - verbose = False - for opt, args in opts: - if opt in ('-v', '--verbose'): - verbose = True - - listname = mlist.internal_name() - if verbose: - print _('Changing passwords for list: %(listname)s') - - for member in mlist.getMembers(): - randompw = Utils.MakeRandomPassword() - mlist.setMemberPassword(member, randompw) - if verbose: - print _('New password for member %(member)40s: %(randompw)s') - - mlist.Save() - - - -if __name__ == '__main__': - usage(0) diff --git a/mailman/attic/bin/sync_members b/mailman/attic/bin/sync_members deleted file mode 100755 index 4a21624c1..000000000 --- a/mailman/attic/bin/sync_members +++ /dev/null @@ -1,286 +0,0 @@ -#! @PYTHON@ -# -# Copyright (C) 1998-2003 by the Free Software Foundation, Inc. -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -"""Synchronize a mailing list's membership with a flat file. - -This script is useful if you have a Mailman mailing list and a sendmail -:include: style list of addresses (also as is used in Majordomo). For every -address in the file that does not appear in the mailing list, the address is -added. For every address in the mailing list that does not appear in the -file, the address is removed. Other options control what happens when an -address is added or removed. - -Usage: %(PROGRAM)s [options] -f file listname - -Where `options' are: - - --no-change - -n - Don't actually make the changes. Instead, print out what would be - done to the list. - - --welcome-msg[=<yes|no>] - -w[=<yes|no>] - Sets whether or not to send the newly added members a welcome - message, overriding whatever the list's `send_welcome_msg' setting - is. With -w=yes or -w, the welcome message is sent. With -w=no, no - message is sent. - - --goodbye-msg[=<yes|no>] - -g[=<yes|no>] - Sets whether or not to send the goodbye message to removed members, - overriding whatever the list's `send_goodbye_msg' setting is. With - -g=yes or -g, the goodbye message is sent. With -g=no, no message is - sent. - - --digest[=<yes|no>] - -d[=<yes|no>] - Selects whether to make newly added members receive messages in - digests. With -d=yes or -d, they become digest members. With -d=no - (or if no -d option given) they are added as regular members. - - --notifyadmin[=<yes|no>] - -a[=<yes|no>] - Specifies whether the admin should be notified for each subscription - or unsubscription. If you're adding a lot of addresses, you - definitely want to turn this off! With -a=yes or -a, the admin is - notified. With -a=no, the admin is not notified. With no -a option, - the default for the list is used. - - --file <filename | -> - -f <filename | -> - This option is required. It specifies the flat file to synchronize - against. Email addresses must appear one per line. If filename is - `-' then stdin is used. - - --help - -h - Print this message. - - listname - Required. This specifies the list to synchronize. -""" - -import sys - -import paths -# Import this /after/ paths so that the sys.path is properly hacked -import email.Utils - -from Mailman import MailList -from Mailman import Errors -from Mailman import Utils -from Mailman.UserDesc import UserDesc -from Mailman.i18n import _ - - - -PROGRAM = sys.argv[0] - -def usage(code, msg=''): - if code: - fd = sys.stderr - else: - fd = sys.stdout - print >> fd, _(__doc__) - if msg: - print >> fd, msg - sys.exit(code) - - - -def yesno(opt): - i = opt.find('=') - yesno = opt[i+1:].lower() - if yesno in ('y', 'yes'): - return 1 - elif yesno in ('n', 'no'): - return 0 - else: - usage(1, _('Bad choice: %(yesno)s')) - # no return - - -def main(): - dryrun = 0 - digest = 0 - welcome = None - goodbye = None - filename = None - listname = None - notifyadmin = None - - # TBD: can't use getopt with this command line syntax, which is broken and - # should be changed to be getopt compatible. - i = 1 - while i < len(sys.argv): - opt = sys.argv[i] - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-n', '--no-change'): - dryrun = 1 - i += 1 - print _('Dry run mode') - elif opt in ('-d', '--digest'): - digest = 1 - i += 1 - elif opt.startswith('-d=') or opt.startswith('--digest='): - digest = yesno(opt) - i += 1 - elif opt in ('-w', '--welcome-msg'): - welcome = 1 - i += 1 - elif opt.startswith('-w=') or opt.startswith('--welcome-msg='): - welcome = yesno(opt) - i += 1 - elif opt in ('-g', '--goodbye-msg'): - goodbye = 1 - i += 1 - elif opt.startswith('-g=') or opt.startswith('--goodbye-msg='): - goodbye = yesno(opt) - i += 1 - elif opt in ('-f', '--file'): - if filename is not None: - usage(1, _('Only one -f switch allowed')) - try: - filename = sys.argv[i+1] - except IndexError: - usage(1, _('No argument to -f given')) - i += 2 - elif opt in ('-a', '--notifyadmin'): - notifyadmin = 1 - i += 1 - elif opt.startswith('-a=') or opt.startswith('--notifyadmin='): - notifyadmin = yesno(opt) - i += 1 - elif opt[0] == '-': - usage(1, _('Illegal option: %(opt)s')) - else: - try: - listname = sys.argv[i].lower() - i += 1 - except IndexError: - usage(1, _('No listname given')) - break - - if listname is None or filename is None: - usage(1, _('Must have a listname and a filename')) - - # read the list of addresses to sync to from the file - if filename == '-': - filemembers = sys.stdin.readlines() - else: - try: - fp = open(filename) - except IOError, (code, msg): - usage(1, _('Cannot read address file: %(filename)s: %(msg)s')) - try: - filemembers = fp.readlines() - finally: - fp.close() - - # strip out lines we don't care about, they are comments (# in first - # non-whitespace) or are blank - for i in range(len(filemembers)-1, -1, -1): - addr = filemembers[i].strip() - if addr == '' or addr[:1] == '#': - del filemembers[i] - print _('Ignore : %(addr)30s') - - # first filter out any invalid addresses - filemembers = email.Utils.getaddresses(filemembers) - invalid = 0 - for name, addr in filemembers: - try: - Utils.ValidateEmail(addr) - except Errors.EmailAddressError: - print _('Invalid : %(addr)30s') - invalid = 1 - if invalid: - print _('You must fix the preceding invalid addresses first.') - sys.exit(1) - - # get the locked list object - try: - mlist = MailList.MailList(listname) - except Errors.MMListError, e: - print _('No such list: %(listname)s') - sys.exit(1) - - try: - # Get the list of addresses currently subscribed - addrs = {} - needsadding = {} - matches = {} - for addr in mlist.getMemberCPAddresses(mlist.getMembers()): - addrs[addr.lower()] = addr - - for name, addr in filemembers: - # Any address found in the file that is also in the list can be - # ignored. If not found in the list, it must be added later. - laddr = addr.lower() - if addrs.has_key(laddr): - del addrs[laddr] - matches[laddr] = 1 - elif not matches.has_key(laddr): - needsadding[laddr] = (name, addr) - - if not needsadding and not addrs: - print _('Nothing to do.') - sys.exit(0) - - enc = sys.getdefaultencoding() - # addrs contains now all the addresses that need removing - for laddr, (name, addr) in needsadding.items(): - pw = Utils.MakeRandomPassword() - # should not already be subscribed, otherwise our test above is - # broken. Bogosity is if the address is listed in the file more - # than once. Second and subsequent ones trigger an - # MMAlreadyAMember error. Just catch it and go on. - userdesc = UserDesc(addr, name, pw, digest) - try: - if not dryrun: - mlist.ApprovedAddMember(userdesc, welcome, notifyadmin) - s = email.Utils.formataddr((name, addr)).encode(enc, 'replace') - print _('Added : %(s)s') - except Errors.MMAlreadyAMember: - pass - - for laddr, addr in addrs.items(): - # Should be a member, otherwise our test above is broken - name = mlist.getMemberName(laddr) or '' - if not dryrun: - try: - mlist.ApprovedDeleteMember(addr, admin_notif=notifyadmin, - userack=goodbye) - except Errors.NotAMemberError: - # This can happen if the address is illegal (i.e. can't be - # parsed by email.Utils.parseaddr()) but for legacy - # reasons is in the database. Use a lower level remove to - # get rid of this member's entry - mlist.removeMember(addr) - s = email.Utils.formataddr((name, addr)).encode(enc, 'replace') - print _('Removed: %(s)s') - - mlist.Save() - finally: - mlist.Unlock() - - -if __name__ == '__main__': - main() diff --git a/mailman/attic/bin/templ2pot.py b/mailman/attic/bin/templ2pot.py deleted file mode 100644 index 0253cc2cd..000000000 --- a/mailman/attic/bin/templ2pot.py +++ /dev/null @@ -1,120 +0,0 @@ -#! @PYTHON@ -# Code stolen from pygettext.py -# by Tokio Kikuchi <tkikuchi@is.kochi-u.ac.jp> - -"""templ2pot.py -- convert mailman template (en) to pot format. - -Usage: templ2pot.py inputfile ... - -Options: - - -h, --help - -Inputfiles are english templates. Outputs are written to stdout. -""" - -import sys -import getopt - - - -try: - import paths - from Mailman.i18n import _ -except ImportError: - def _(s): return s - -EMPTYSTRING = '' - - - -def usage(code, msg=''): - if code: - fd = sys.stderr - else: - fd = sys.stdout - print >> fd, _(__doc__) % globals() - if msg: - print >> fd, msg - sys.exit(code) - - - -escapes = [] - -def make_escapes(pass_iso8859): - global escapes - if pass_iso8859: - # Allow iso-8859 characters to pass through so that e.g. 'msgid - # "H[o-umlaut]he"' would result not result in 'msgid "H\366he"'. - # Otherwise we escape any character outside the 32..126 range. - mod = 128 - else: - mod = 256 - for i in range(256): - if 32 <= (i % mod) <= 126: - escapes.append(chr(i)) - else: - escapes.append("\\%03o" % i) - escapes[ord('\\')] = '\\\\' - escapes[ord('\t')] = '\\t' - escapes[ord('\r')] = '\\r' - escapes[ord('\n')] = '\\n' - escapes[ord('\"')] = '\\"' - - -def escape(s): - global escapes - s = list(s) - for i in range(len(s)): - s[i] = escapes[ord(s[i])] - return EMPTYSTRING.join(s) - - -def normalize(s): - # This converts the various Python string types into a format that is - # appropriate for .po files, namely much closer to C style. - lines = s.splitlines() - if len(lines) == 1: - s = '"' + escape(s) + '"' - else: - if not lines[-1]: - del lines[-1] - lines[-1] = lines[-1] + '\n' - for i in range(len(lines)): - lines[i] = escape(lines[i]) - lineterm = '\\n"\n"' - s = '""\n"' + lineterm.join(lines) + '"' - return s - - - -def main(): - try: - opts, args = getopt.getopt( - sys.argv[1:], - 'h', - ['help',] - ) - except getopt.error, msg: - usage(1, msg) - - # parse options - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - - # calculate escapes - make_escapes(0) - - for filename in args: - print '#: %s:1' % filename - s = file(filename).read() - print '#, template' - print 'msgid', normalize(s) - print 'msgstr ""\n' - - - -if __name__ == '__main__': - main() diff --git a/mailman/attic/bin/transcheck b/mailman/attic/bin/transcheck deleted file mode 100755 index 73910e771..000000000 --- a/mailman/attic/bin/transcheck +++ /dev/null @@ -1,412 +0,0 @@ -#! @PYTHON@ -# -# transcheck - (c) 2002 by Simone Piunno <pioppo@ferrara.linux.it> -# -# This program is free software; you can redistribute it and/or modify it -# under the terms of the version 2.0 of the GNU General Public License as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -# for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -""" -Check a given Mailman translation, making sure that variables and -tags referenced in translation are the same variables and tags in -the original templates and catalog. - -Usage: - -cd $MAILMAN_DIR -%(program)s [-q] <lang> - -Where <lang> is your country code (e.g. 'it' for Italy) and -q is -to ask for a brief summary. -""" - -import sys -import re -import os -import getopt - -import paths -from Mailman.i18n import _ - -program = sys.argv[0] - - - -def usage(code, msg=''): - if code: - fd = sys.stderr - else: - fd = sys.stdout - print >> fd, _(__doc__) - if msg: - print >> fd, msg - sys.exit(code) - - - -class TransChecker: - "check a translation comparing with the original string" - def __init__(self, regexp, escaped=None): - self.dict = {} - self.errs = [] - self.regexp = re.compile(regexp) - self.escaped = None - if escaped: - self.escaped = re.compile(escaped) - - def checkin(self, string): - "scan a string from the original file" - for key in self.regexp.findall(string): - if self.escaped and self.escaped.match(key): - continue - if self.dict.has_key(key): - self.dict[key] += 1 - else: - self.dict[key] = 1 - - def checkout(self, string): - "scan a translated string" - for key in self.regexp.findall(string): - if self.escaped and self.escaped.match(key): - continue - if self.dict.has_key(key): - self.dict[key] -= 1 - else: - self.errs.append( - "%(key)s was not found" % - { 'key' : key } - ) - - def computeErrors(self): - "check for differences between checked in and checked out" - for key in self.dict.keys(): - if self.dict[key] < 0: - self.errs.append( - "Too much %(key)s" % - { 'key' : key } - ) - if self.dict[key] > 0: - self.errs.append( - "Too few %(key)s" % - { 'key' : key } - ) - return self.errs - - def status(self): - if self.errs: - return "FAILED" - else: - return "OK" - - def errorsAsString(self): - msg = "" - for err in self.errs: - msg += " - %(err)s" % { 'err': err } - return msg - - def reset(self): - self.dict = {} - self.errs = [] - - - -class POParser: - "parse a .po file extracting msgids and msgstrs" - def __init__(self, filename=""): - self.status = 0 - self.files = [] - self.msgid = "" - self.msgstr = "" - self.line = 1 - self.f = None - self.esc = { "n": "\n", "r": "\r", "t": "\t" } - if filename: - self.f = open(filename) - - def open(self, filename): - self.f = open(filename) - - def close(self): - self.f.close() - - def parse(self): - """States table for the finite-states-machine parser: - 0 idle - 1 filename-or-comment - 2 msgid - 3 msgstr - 4 end - """ - # each time we can safely re-initialize those vars - self.files = [] - self.msgid = "" - self.msgstr = "" - - - # can't continue if status == 4, this is a dead status - if self.status == 4: - return 0 - - while 1: - # continue scanning, char-by-char - c = self.f.read(1) - if not c: - # EOF -> maybe we have a msgstr to save? - self.status = 4 - if self.msgstr: - return 1 - else: - return 0 - - # keep the line count up-to-date - if c == "\n": - self.line += 1 - - # a pound was detected the previous char... - if self.status == 1: - if c == ":": - # was a line of filenames - row = self.f.readline() - self.files += row.split() - self.line += 1 - elif c == "\n": - # was a single pount on the line - pass - else: - # was a comment... discard - self.f.readline() - self.line += 1 - # in every case, we switch to idle status - self.status = 0; - continue - - # in idle status we search for a '#' or for a 'm' - if self.status == 0: - if c == "#": - # this could be a comment or a filename - self.status = 1; - continue - elif c == "m": - # this should be a msgid start... - s = self.f.read(4) - assert s == "sgid" - # so now we search for a '"' - self.status = 2 - continue - # in idle only those other chars are possibile - assert c in [ "\n", " ", "\t" ] - - # searching for the msgid string - if self.status == 2: - if c == "\n": - # a double LF is not possible here - c = self.f.read(1) - assert c != "\n" - if c == "\"": - # ok, this is the start of the string, - # now search for the end - while 1: - c = self.f.read(1) - if not c: - # EOF, bailout - self.status = 4 - return 0 - if c == "\\": - # a quoted char... - c = self.f.read(1) - if self.esc.has_key(c): - self.msgid += self.esc[c] - else: - self.msgid += c - continue - if c == "\"": - # end of string found - break - # a normal char, add it - self.msgid += c - if c == "m": - # this should be a msgstr identifier - s = self.f.read(5) - assert s == "sgstr" - # ok, now search for the msgstr string - self.status = 3 - - # searching for the msgstr string - if self.status == 3: - if c == "\n": - # a double LF is the end of the msgstr! - c = self.f.read(1) - if c == "\n": - # ok, time to go idle and return - self.status = 0 - self.line += 1 - return 1 - if c == "\"": - # start of string found - while 1: - c = self.f.read(1) - if not c: - # EOF, bail out - self.status = 4 - return 1 - if c == "\\": - # a quoted char... - c = self.f.read(1) - if self.esc.has_key(c): - self.msgid += self.esc[c] - else: - self.msgid += c - continue - if c == "\"": - # end of string - break - # a normal char, add it - self.msgstr += c - - - - -def check_file(translatedFile, originalFile, html=0, quiet=0): - """check a translated template against the original one - search also <MM-*> tags if html is not zero""" - - if html: - c = TransChecker("(%%|%\([^)]+\)[0-9]*[sd]|</?MM-[^>]+>)", "^%%$") - else: - c = TransChecker("(%%|%\([^)]+\)[0-9]*[sd])", "^%%$") - - try: - f = open(originalFile) - except IOError: - if not quiet: - print " - Can'open original file " + originalFile - return 1 - - while 1: - line = f.readline() - if not line: break - c.checkin(line) - - f.close() - - try: - f = open(translatedFile) - except IOError: - if not quiet: - print " - Can'open translated file " + translatedFile - return 1 - - while 1: - line = f.readline() - if not line: break - c.checkout(line) - - f.close() - - n = 0 - msg = "" - for desc in c.computeErrors(): - n +=1 - if not quiet: - print " - %(desc)s" % { 'desc': desc } - return n - - - -def check_po(file, quiet=0): - "scan the po file comparing msgids with msgstrs" - n = 0 - p = POParser(file) - c = TransChecker("(%%|%\([^)]+\)[0-9]*[sdu]|%[0-9]*[sdu])", "^%%$") - while p.parse(): - if p.msgstr: - c.reset() - c.checkin(p.msgid) - c.checkout(p.msgstr) - for desc in c.computeErrors(): - n += 1 - if not quiet: - print " - near line %(line)d %(file)s: %(desc)s" % { - 'line': p.line, - 'file': p.files, - 'desc': desc - } - p.close() - return n - - -def main(): - try: - opts, args = getopt.getopt(sys.argv[1:], 'qh', ['quiet', 'help']) - except getopt.error, msg: - usage(1, msg) - - quiet = 0 - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-q', '--quiet'): - quiet = 1 - - if len(args) <> 1: - usage(1) - - lang = args[0] - - isHtml = re.compile("\.html$"); - isTxt = re.compile("\.txt$"); - - numerrors = 0 - numfiles = 0 - try: - files = os.listdir("templates/" + lang + "/") - except: - print "can't open templates/%s/" % lang - for file in files: - fileEN = "templates/en/" + file - fileIT = "templates/" + lang + "/" + file - errlist = [] - if isHtml.search(file): - if not quiet: - print "HTML checking " + fileIT + "... " - n = check_file(fileIT, fileEN, html=1, quiet=quiet) - if n: - numerrors += n - numfiles += 1 - elif isTxt.search(file): - if not quiet: - print "TXT checking " + fileIT + "... " - n = check_file(fileIT, fileEN, html=0, quiet=quiet) - if n: - numerrors += n - numfiles += 1 - - else: - continue - - file = "messages/" + lang + "/LC_MESSAGES/mailman.po" - if not quiet: - print "PO checking " + file + "... " - n = check_po(file, quiet=quiet) - if n: - numerrors += n - numfiles += 1 - - if quiet: - print "%(errs)u warnings in %(files)u files" % { - 'errs': numerrors, - 'files': numfiles - } - - -if __name__ == '__main__': - main() |
