diff options
| author | Barry Warsaw | 2009-12-13 00:03:43 -0500 |
|---|---|---|
| committer | Barry Warsaw | 2009-12-13 00:03:43 -0500 |
| commit | 80b3d59005333a5fd80f3bb3ba908a5ec84e9e8b (patch) | |
| tree | 5d435cde7f2c76a82322b1daac135eaa851f6d82 /src/attic | |
| parent | 4d9afb3d1d4ae87bda4b833f6e46660722147f18 (diff) | |
| download | mailman-80b3d59005333a5fd80f3bb3ba908a5ec84e9e8b.tar.gz mailman-80b3d59005333a5fd80f3bb3ba908a5ec84e9e8b.tar.zst mailman-80b3d59005333a5fd80f3bb3ba908a5ec84e9e8b.zip | |
Finally remove code we're not going to use.
Diffstat (limited to 'src/attic')
42 files changed, 0 insertions, 11223 deletions
diff --git a/src/attic/Bouncer.py b/src/attic/Bouncer.py deleted file mode 100644 index e2de3c915..000000000 --- a/src/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/src/attic/Defaults.py b/src/attic/Defaults.py deleted file mode 100644 index 6f72ed535..000000000 --- a/src/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/src/attic/Deliverer.py b/src/attic/Deliverer.py deleted file mode 100644 index 0ba3a01bb..000000000 --- a/src/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/src/attic/Digester.py b/src/attic/Digester.py deleted file mode 100644 index a88d08abc..000000000 --- a/src/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/src/attic/MailList.py b/src/attic/MailList.py deleted file mode 100644 index 2d538f026..000000000 --- a/src/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/src/attic/Mailbox.py b/src/attic/Mailbox.py deleted file mode 100644 index 3a2f079c4..000000000 --- a/src/attic/Mailbox.py +++ /dev/null @@ -1,106 +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/>. - -"""Extend mailbox.UnixMailbox. -""" - -import sys -import email -import mailbox - -from email.errors import MessageParseError -from email.generator import Generator - -from mailman.Message import Message -from mailman.config import config - - - -def _safeparser(fp): - try: - return email.message_from_file(fp, Message) - except MessageParseError: - # Don't return None since that will stop a mailbox iterator - return '' - - - -class Mailbox(mailbox.PortableUnixMailbox): - def __init__(self, fp): - mailbox.PortableUnixMailbox.__init__(self, fp, _safeparser) - - # msg should be an rfc822 message or a subclass. - def AppendMessage(self, msg): - # Check the last character of the file and write a newline if it isn't - # a newline (but not at the beginning of an empty file). - try: - self.fp.seek(-1, 2) - except IOError, e: - # Assume the file is empty. We can't portably test the error code - # returned, since it differs per platform. - pass - else: - if self.fp.read(1) <> '\n': - self.fp.write('\n') - # Seek to the last char of the mailbox - self.fp.seek(1, 2) - # Create a Generator instance to write the message to the file - g = Generator(self.fp) - g.flatten(msg, unixfrom=True) - # Add one more trailing newline for separation with the next message - # to be appended to the mbox. - print >> self.fp - - - -# This stuff is used by pipermail.py:processUnixMailbox(). It provides an -# opportunity for the built-in archiver to scrub archived messages of nasty -# things like attachments and such... -def _archfactory(mailbox): - # The factory gets a file object, but it also needs to have a MailList - # object, so the clearest <wink> way to do this is to build a factory - # function that has a reference to the mailbox object, which in turn holds - # a reference to the mailing list. Nested scopes would help here, BTW, - # but we can't rely on them being around (e.g. Python 2.0). - def scrubber(fp, mailbox=mailbox): - msg = _safeparser(fp) - if msg == '': - return msg - return mailbox.scrub(msg) - return scrubber - - -class ArchiverMailbox(Mailbox): - # This is a derived class which is instantiated with a reference to the - # MailList object. It is build such that the factory calls back into its - # scrub() method, giving the scrubber module a chance to do its thing - # before the message is archived. - def __init__(self, fp, mlist): - scrubber_module = config.scrubber.archive_scrubber - if scrubber_module: - __import__(scrubber_module) - self._scrubber = sys.modules[scrubber_module].process - else: - self._scrubber = None - self._mlist = mlist - mailbox.PortableUnixMailbox.__init__(self, fp, _archfactory(self)) - - def scrub(self, msg): - if self._scrubber: - return self._scrubber(self._mlist, msg) - else: - return msg diff --git a/src/attic/SecurityManager.py b/src/attic/SecurityManager.py deleted file mode 100644 index 8d4a30592..000000000 --- a/src/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/src/attic/add_members.py b/src/attic/add_members.py deleted file mode 100644 index 540c0facb..000000000 --- a/src/attic/add_members.py +++ /dev/null @@ -1,186 +0,0 @@ -# Copyright (C) 1998-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -import os -import sys -import codecs - -from cStringIO import StringIO -from email.utils import parseaddr - -from mailman import Utils -from mailman import i18n -from mailman.app.membership import add_member -from mailman.config import config -from mailman.core import errors -from mailman.email.message import UserNotification -from mailman.interfaces.member import AlreadySubscribedError, DeliveryMode -from mailman.options import SingleMailingListOptions - -_ = i18n._ - - - -class ScriptOptions(SingleMailingListOptions): - usage=_("""\ -%prog [options] - -Add members to a list. 'listname' is the name of the Mailman list you are -adding members to; the list must already exist. - -You must supply at least one of -r and -d options. At most one of the -files can be '-'. -""") - - def add_options(self): - super(ScriptOptions, self).add_options() - self.parser.add_option( - '-r', '--regular-members-file', - type='string', dest='regular', help=_("""\ -A file containing addresses of the members to be added, one address per line. -This list of people become non-digest members. If file is '-', read addresses -from stdin.""")) - self.parser.add_option( - '-d', '--digest-members-file', - type='string', dest='digest', help=_("""\ -Similar to -r, but these people become digest members.""")) - self.parser.add_option( - '-w', '--welcome-msg', - type='yesno', metavar='<y|n>', help=_("""\ -Set whether or not to send the list members a welcome message, overriding -whatever the list's 'send_welcome_msg' setting is.""")) - self.parser.add_option( - '-a', '--admin-notify', - type='yesno', metavar='<y|n>', help=_("""\ -Set whether or not to send the list administrators a notification on the -success/failure of these subscriptions, overriding whatever the list's -'admin_notify_mchanges' setting is.""")) - - def sanity_check(self): - if not self.options.listname: - self.parser.error(_('Missing listname')) - if len(self.arguments) > 0: - self.parser.print_error(_('Unexpected arguments')) - if self.options.regular is None and self.options.digest is None: - parser.error(_('At least one of -r or -d is required')) - if self.options.regular == '-' and self.options.digest == '-': - parser.error(_("-r and -d cannot both be '-'")) - - - -def readfile(filename): - if filename == '-': - fp = sys.stdin - else: - # XXX Need to specify other encodings. - fp = codecs.open(filename, encoding='utf-8') - # Strip all the lines of whitespace and discard blank lines - try: - return set(line.strip() for line in fp if line) - finally: - if fp is not sys.stdin: - fp.close() - - - -class Tee: - def __init__(self, outfp): - self._outfp = outfp - - def write(self, msg): - sys.stdout.write(msg) - self._outfp.write(msg) - - - -def addall(mlist, subscribers, delivery_mode, ack, admin_notify, outfp): - tee = Tee(outfp) - for subscriber in subscribers: - try: - fullname, address = parseaddr(subscriber) - # Watch out for the empty 8-bit string. - if not fullname: - fullname = u'' - password = Utils.MakeRandomPassword() - add_member(mlist, address, fullname, password, delivery_mode, - unicode(config.mailman.default_language)) - # XXX Support ack and admin_notify - except AlreadySubscribedError: - print >> tee, _('Already a member: $subscriber') - except errors.InvalidEmailAddress: - if not address: - print >> tee, _('Bad/Invalid email address: blank line') - else: - print >> tee, _('Bad/Invalid email address: $subscriber') - else: - print >> tee, _('Subscribing: $subscriber') - - - -def main(): - options = ScriptOptions() - options.initialize() - - fqdn_listname = options.options.listname - mlist = config.db.list_manager.get(fqdn_listname) - if mlist is None: - parser.error(_('No such list: $fqdn_listname')) - - # Set up defaults. - send_welcome_msg = (options.options.welcome_msg - if options.options.welcome_msg is not None - else mlist.send_welcome_msg) - admin_notify = (options.options.admin_notify - if options.options.admin_notify is not None - else mlist.admin_notify) - - with i18n.using_language(mlist.preferred_language): - if options.options.digest: - dmembers = readfile(options.options.digest) - else: - dmembers = set() - if options.options.regular: - nmembers = readfile(options.options.regular) - else: - nmembers = set() - - if not dmembers and not nmembers: - print _('Nothing to do.') - sys.exit(0) - - outfp = StringIO() - if nmembers: - addall(mlist, nmembers, DeliveryMode.regular, - send_welcome_msg, admin_notify, outfp) - - if dmembers: - addall(mlist, dmembers, DeliveryMode.mime_digests, - send_welcome_msg, admin_notify, outfp) - - config.db.commit() - - if admin_notify: - subject = _('$mlist.real_name subscription notification') - msg = UserNotification( - mlist.owner, mlist.no_reply_address, subject, - outfp.getvalue(), mlist.preferred_language) - msg.send(mlist) - - - -if __name__ == '__main__': - main() diff --git a/src/attic/bin/clone_member b/src/attic/bin/clone_member deleted file mode 100755 index 1f2a03aca..000000000 --- a/src/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/src/attic/bin/discard b/src/attic/bin/discard deleted file mode 100644 index c30198441..000000000 --- a/src/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/src/attic/bin/fix_url.py b/src/attic/bin/fix_url.py deleted file mode 100644 index 30618a1a3..000000000 --- a/src/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/src/attic/bin/list_admins b/src/attic/bin/list_admins deleted file mode 100644 index c628a42dc..000000000 --- a/src/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/src/attic/bin/msgfmt.py b/src/attic/bin/msgfmt.py deleted file mode 100644 index 8a2d4e66e..000000000 --- a/src/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/src/attic/bin/po2templ.py b/src/attic/bin/po2templ.py deleted file mode 100644 index 86eae96b9..000000000 --- a/src/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/src/attic/bin/pygettext.py b/src/attic/bin/pygettext.py deleted file mode 100644 index 84421ee8c..000000000 --- a/src/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/src/attic/bin/remove_members b/src/attic/bin/remove_members deleted file mode 100755 index a7b4ebb47..000000000 --- a/src/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/src/attic/bin/reset_pw.py b/src/attic/bin/reset_pw.py deleted file mode 100644 index 453c8b849..000000000 --- a/src/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/src/attic/bin/sync_members b/src/attic/bin/sync_members deleted file mode 100755 index 4a21624c1..000000000 --- a/src/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/src/attic/bin/templ2pot.py b/src/attic/bin/templ2pot.py deleted file mode 100644 index 0253cc2cd..000000000 --- a/src/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/src/attic/bin/transcheck b/src/attic/bin/transcheck deleted file mode 100755 index 73910e771..000000000 --- a/src/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() diff --git a/src/attic/cmd_confirm.py b/src/attic/cmd_confirm.py deleted file mode 100644 index 0e2b7ad43..000000000 --- a/src/attic/cmd_confirm.py +++ /dev/null @@ -1,98 +0,0 @@ -# Copyright (C) 2002-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -""" - confirm <confirmation-string> - Confirm an action. The confirmation-string is required and should be - supplied by a mailback confirmation notice. -""" - -from mailman import errors -from mailman import Pending -from mailman.config import config -from mailman.i18n import _ - -STOP = 1 - - - -def gethelp(mlist): - return _(__doc__) - - - -def process(res, args): - mlist = res.mlist - if len(args) <> 1: - res.results.append(_('Usage:')) - res.results.append(gethelp(mlist)) - return STOP - cookie = args[0] - try: - results = mlist.ProcessConfirmation(cookie, res.msg) - except Errors.MMBadConfirmation, e: - # Express in approximate days - days = int(config.PENDING_REQUEST_LIFE / config.days(1) + 0.5) - res.results.append(_("""\ -Invalid confirmation string. Note that confirmation strings expire -approximately %(days)s days after the initial subscription request. If your -confirmation has expired, please try to re-submit your original request or -message.""")) - except Errors.MMNeedApproval: - res.results.append(_("""\ -Your request has been forwarded to the list moderator for approval.""")) - except Errors.MMAlreadyAMember: - # Some other subscription request for this address has - # already succeeded. - res.results.append(_('You are already subscribed.')) - except Errors.NotAMemberError: - # They've already been unsubscribed - res.results.append(_("""\ -You are not currently a member. Have you already unsubscribed or changed -your email address?""")) - except Errors.MembershipIsBanned: - owneraddr = mlist.GetOwnerEmail() - res.results.append(_("""\ -You are currently banned from subscribing to this list. If you think this -restriction is erroneous, please contact the list owners at -%(owneraddr)s.""")) - except Errors.HostileSubscriptionError: - res.results.append(_("""\ -You were not invited to this mailing list. The invitation has been discarded, -and both list administrators have been alerted.""")) - except Errors.MMBadPasswordError: - res.results.append(_("""\ -Bad approval password given. Held message is still being held.""")) - else: - if ((results[0] == Pending.SUBSCRIPTION and mlist.send_welcome_msg) - or - (results[0] == Pending.UNSUBSCRIPTION and mlist.send_goodbye_msg)): - # We don't also need to send a confirmation succeeded message - res.respond = 0 - else: - res.results.append(_('Confirmation succeeded')) - # Consume any other confirmation strings with the same cookie so - # the user doesn't get a misleading "unprocessed" message. - match = 'confirm ' + cookie - unprocessed = [] - for line in res.commands: - if line.lstrip() == match: - continue - unprocessed.append(line) - res.commands = unprocessed - # Process just one confirmation string per message - return STOP diff --git a/src/attic/cmd_help.py b/src/attic/cmd_help.py deleted file mode 100644 index 30c8dc4d6..000000000 --- a/src/attic/cmd_help.py +++ /dev/null @@ -1,93 +0,0 @@ -# Copyright (C) 2002-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -""" - help - Print this help message. -""" - -import os -import sys - -from mailman import Utils -from mailman.config import config -from mailman.i18n import _ - -EMPTYSTRING = '' - - - -def gethelp(mlist): - return _(__doc__) - - - -def process(res, args): - # Get the help text introduction - mlist = res.mlist - # Since this message is personalized, add some useful information if the - # address requesting help is a member of the list. - msg = res.msg - for sender in msg.senders: - if mlist.isMember(sender): - memberurl = mlist.GetOptionsURL(sender, absolute=1) - urlhelp = _( - 'You can access your personal options via the following url:') - res.results.append(urlhelp) - res.results.append(memberurl) - # Get a blank line in the output. - res.results.append('') - break - # build the specific command helps from the module docstrings - modhelps = {} - import mailman.Commands - path = os.path.dirname(os.path.abspath(mailman.Commands.__file__)) - for file in os.listdir(path): - if not file.startswith('cmd_') or not file.endswith('.py'): - continue - module = os.path.splitext(file)[0] - modname = 'mailman.Commands.' + module - try: - __import__(modname) - except ImportError: - continue - cmdname = module[4:] - help = None - if hasattr(sys.modules[modname], 'gethelp'): - help = sys.modules[modname].gethelp(mlist) - if help: - modhelps[cmdname] = help - # Now sort the command helps - helptext = [] - keys = modhelps.keys() - keys.sort() - for cmd in keys: - helptext.append(modhelps[cmd]) - commands = EMPTYSTRING.join(helptext) - # Now craft the response - helptext = Utils.maketext( - 'help.txt', - {'listname' : mlist.real_name, - 'version' : config.VERSION, - 'listinfo_url': mlist.GetScriptURL('listinfo', absolute=1), - 'requestaddr' : mlist.GetRequestEmail(), - 'adminaddr' : mlist.GetOwnerEmail(), - 'commands' : commands, - }, mlist=mlist, lang=res.msgdata['lang'], raw=1) - # Now add to the response - res.results.append('help') - res.results.append(helptext) diff --git a/src/attic/cmd_info.py b/src/attic/cmd_info.py deleted file mode 100644 index 3bdea178f..000000000 --- a/src/attic/cmd_info.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright (C) 2002-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -""" - info - Get information about this mailing list. -""" - -from mailman.i18n import _ - -STOP = 1 - - - -def gethelp(mlist): - return _(__doc__) - - - -def process(res, args): - mlist = res.mlist - if args: - res.results.append(gethelp(mlist)) - return STOP - listname = mlist.real_name - description = mlist.description or _('n/a') - postaddr = mlist.posting_address - requestaddr = mlist.request_address - owneraddr = mlist.owner_address - listurl = mlist.script_url('listinfo') - res.results.append(_('List name: %(listname)s')) - res.results.append(_('Description: %(description)s')) - res.results.append(_('Postings to: %(postaddr)s')) - res.results.append(_('List Helpbot: %(requestaddr)s')) - res.results.append(_('List Owners: %(owneraddr)s')) - res.results.append(_('More information: %(listurl)s')) diff --git a/src/attic/cmd_leave.py b/src/attic/cmd_leave.py deleted file mode 100644 index 5844824f7..000000000 --- a/src/attic/cmd_leave.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (C) 2002-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -"""The `leave' command is synonymous with `unsubscribe'. -""" - -from mailman.Commands.cmd_unsubscribe import process diff --git a/src/attic/cmd_lists.py b/src/attic/cmd_lists.py deleted file mode 100644 index 234ef46fc..000000000 --- a/src/attic/cmd_lists.py +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright (C) 2002-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -""" - lists - See a list of the public mailing lists on this GNU Mailman server. -""" - -from mailman.MailList import MailList -from mailman.config import config -from mailman.i18n import _ - - -STOP = 1 - - - -def gethelp(mlist): - return _(__doc__) - - - -def process(res, args): - mlist = res.mlist - if args: - res.results.append(_('Usage:')) - res.results.append(gethelp(mlist)) - return STOP - hostname = mlist.host_name - res.results.append(_('Public mailing lists at %(hostname)s:')) - i = 1 - for listname in sorted(config.list_manager.names): - if listname == mlist.internal_name(): - xlist = mlist - else: - xlist = MailList(listname, lock=0) - # We can mention this list if you already know about it - if not xlist.advertised and xlist is not mlist: - continue - # Skip the list if it isn't in the same virtual domain. - if xlist.host_name <> mlist.host_name: - continue - realname = xlist.real_name - description = xlist.description or _('n/a') - requestaddr = xlist.GetRequestEmail() - if i > 1: - res.results.append('') - res.results.append(_('%(i)3d. List name: %(realname)s')) - res.results.append(_(' Description: %(description)s')) - res.results.append(_(' Requests to: %(requestaddr)s')) - i += 1 diff --git a/src/attic/cmd_password.py b/src/attic/cmd_password.py deleted file mode 100644 index 545da0cb5..000000000 --- a/src/attic/cmd_password.py +++ /dev/null @@ -1,123 +0,0 @@ -# Copyright (C) 2002-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -""" - password [<oldpassword> <newpassword>] [address=<address>] - Retrieve or change your password. With no arguments, this returns - your current password. With arguments <oldpassword> and <newpassword> - you can change your password. - - If you're posting from an address other than your membership address, - specify your membership address with `address=<address>' (no brackets - around the email address, and no quotes!). Note that in this case the - response is always sent to the subscribed address. -""" - -from email.Utils import parseaddr - -from mailman.config import config -from mailman.i18n import _ - -STOP = 1 - - - -def gethelp(mlist): - return _(__doc__) - - - -def process(res, args): - mlist = res.mlist - address = None - if not args: - # They just want to get their existing password - realname, address = parseaddr(res.msg['from']) - if mlist.isMember(address): - password = mlist.getMemberPassword(address) - res.results.append(_('Your password is: %(password)s')) - # Prohibit multiple password retrievals. - return STOP - else: - listname = mlist.real_name - res.results.append( - _('You are not a member of the %(listname)s mailing list')) - return STOP - elif len(args) == 1 and args[0].startswith('address='): - # They want their password, but they're posting from a different - # address. We /must/ return the password to the subscribed address. - address = args[0][8:] - res.returnaddr = address - if mlist.isMember(address): - password = mlist.getMemberPassword(address) - res.results.append(_('Your password is: %(password)s')) - # Prohibit multiple password retrievals. - return STOP - else: - listname = mlist.real_name - res.results.append( - _('You are not a member of the %(listname)s mailing list')) - return STOP - elif len(args) == 2: - # They are changing their password - oldpasswd = args[0] - newpasswd = args[1] - realname, address = parseaddr(res.msg['from']) - if mlist.isMember(address): - if mlist.Authenticate((config.AuthUser, config.AuthListAdmin), - oldpasswd, address): - mlist.setMemberPassword(address, newpasswd) - res.results.append(_('Password successfully changed.')) - else: - res.results.append(_("""\ -You did not give the correct old password, so your password has not been -changed. Use the no argument version of the password command to retrieve your -current password, then try again.""")) - res.results.append(_('\nUsage:')) - res.results.append(gethelp(mlist)) - return STOP - else: - listname = mlist.real_name - res.results.append( - _('You are not a member of the %(listname)s mailing list')) - return STOP - elif len(args) == 3 and args[2].startswith('address='): - # They want to change their password, and they're sending this from a - # different address than what they're subscribed with. Be sure the - # response goes to the subscribed address. - oldpasswd = args[0] - newpasswd = args[1] - address = args[2][8:] - res.returnaddr = address - if mlist.isMember(address): - if mlist.Authenticate((config.AuthUser, config.AuthListAdmin), - oldpasswd, address): - mlist.setMemberPassword(address, newpasswd) - res.results.append(_('Password successfully changed.')) - else: - res.results.append(_("""\ -You did not give the correct old password, so your password has not been -changed. Use the no argument version of the password command to retrieve your -current password, then try again.""")) - res.results.append(_('\nUsage:')) - res.results.append(gethelp(mlist)) - return STOP - else: - listname = mlist.real_name - res.results.append( - _('You are not a member of the %(listname)s mailing list')) - return STOP diff --git a/src/attic/cmd_remove.py b/src/attic/cmd_remove.py deleted file mode 100644 index 8f3ce9669..000000000 --- a/src/attic/cmd_remove.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (C) 2002-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -"""The `remove' command is synonymous with `unsubscribe'. -""" - -from mailman.Commands.cmd_unsubscribe import process diff --git a/src/attic/cmd_set.py b/src/attic/cmd_set.py deleted file mode 100644 index 020bc3636..000000000 --- a/src/attic/cmd_set.py +++ /dev/null @@ -1,360 +0,0 @@ -# Copyright (C) 2002-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -from email.Utils import parseaddr, formatdate - -from mailman import Errors -from mailman import MemberAdaptor -from mailman import i18n -from mailman.config import config - -def _(s): return s - -OVERVIEW = _(""" - set ... - Set or view your membership options. - - Use `set help' (without the quotes) to get a more detailed list of the - options you can change. - - Use `set show' (without the quotes) to view your current option - settings. -""") - -DETAILS = _(""" - set help - Show this detailed help. - - set show [address=<address>] - View your current option settings. If you're posting from an address - other than your membership address, specify your membership address - with `address=<address>' (no brackets around the email address, and no - quotes!). - - set authenticate <password> [address=<address>] - To set any of your options, you must include this command first, along - with your membership password. If you're posting from an address - other than your membership address, specify your membership address - with `address=<address>' (no brackets around the email address, and no - quotes!). - - set ack on - set ack off - When the `ack' option is turned on, you will receive an - acknowledgement message whenever you post a message to the list. - - set digest plain - set digest mime - set digest off - When the `digest' option is turned off, you will receive postings - immediately when they are posted. Use `set digest plain' if instead - you want to receive postings bundled into a plain text digest - (i.e. RFC 1153 digest). Use `set digest mime' if instead you want to - receive postings bundled together into a MIME digest. - - set delivery on - set delivery off - Turn delivery on or off. This does not unsubscribe you, but instead - tells Mailman not to deliver messages to you for now. This is useful - if you're going on vacation. Be sure to use `set delivery on' when - you return from vacation! - - set myposts on - set myposts off - Use `set myposts off' to not receive copies of messages you post to - the list. This has no effect if you're receiving digests. - - set hide on - set hide off - Use `set hide on' to conceal your email address when people request - the membership list. - - set duplicates on - set duplicates off - Use `set duplicates off' if you want Mailman to not send you messages - if your address is explicitly mentioned in the To: or Cc: fields of - the message. This can reduce the number of duplicate postings you - will receive. - - set reminders on - set reminders off - Use `set reminders off' if you want to disable the monthly password - reminder for this mailing list. -""") - -_ = i18n._ - -STOP = 1 - - - -def gethelp(mlist): - return _(OVERVIEW) - - - -class SetCommands: - def __init__(self): - self.__address = None - self.__authok = 0 - - def process(self, res, args): - if not args: - res.results.append(_(DETAILS)) - return STOP - subcmd = args.pop(0) - methname = 'set_' + subcmd - method = getattr(self, methname, None) - if method is None: - res.results.append(_('Bad set command: %(subcmd)s')) - res.results.append(_(DETAILS)) - return STOP - return method(res, args) - - def set_help(self, res, args=1): - res.results.append(_(DETAILS)) - if args: - return STOP - - def _usage(self, res): - res.results.append(_('Usage:')) - return self.set_help(res) - - def set_show(self, res, args): - mlist = res.mlist - if not args: - realname, address = parseaddr(res.msg['from']) - elif len(args) == 1 and args[0].startswith('address='): - # Send the results to the address, not the From: dude - address = args[0][8:] - res.returnaddr = address - else: - return self._usage(res) - if not mlist.isMember(address): - listname = mlist.real_name - res.results.append( - _('You are not a member of the %(listname)s mailing list')) - return STOP - res.results.append(_('Your current option settings:')) - opt = mlist.getMemberOption(address, config.AcknowledgePosts) - onoff = opt and _('on') or _('off') - res.results.append(_(' ack %(onoff)s')) - # Digests are a special ternary value - digestsp = mlist.getMemberOption(address, config.Digests) - if digestsp: - plainp = mlist.getMemberOption(address, config.DisableMime) - if plainp: - res.results.append(_(' digest plain')) - else: - res.results.append(_(' digest mime')) - else: - res.results.append(_(' digest off')) - # If their membership is disabled, let them know why - status = mlist.getDeliveryStatus(address) - how = None - if status == MemberAdaptor.ENABLED: - status = _('delivery on') - elif status == MemberAdaptor.BYUSER: - status = _('delivery off') - how = _('by you') - elif status == MemberAdaptor.BYADMIN: - status = _('delivery off') - how = _('by the admin') - elif status == MemberAdaptor.BYBOUNCE: - status = _('delivery off') - how = _('due to bounces') - else: - assert status == MemberAdaptor.UNKNOWN - status = _('delivery off') - how = _('for unknown reasons') - changetime = mlist.getDeliveryStatusChangeTime(address) - if how and changetime > 0: - date = formatdate(changetime) - res.results.append(_(' %(status)s (%(how)s on %(date)s)')) - else: - res.results.append(' ' + status) - opt = mlist.getMemberOption(address, config.DontReceiveOwnPosts) - # sense is reversed - onoff = (not opt) and _('on') or _('off') - res.results.append(_(' myposts %(onoff)s')) - opt = mlist.getMemberOption(address, config.ConcealSubscription) - onoff = opt and _('on') or _('off') - res.results.append(_(' hide %(onoff)s')) - opt = mlist.getMemberOption(address, config.DontReceiveDuplicates) - # sense is reversed - onoff = (not opt) and _('on') or _('off') - res.results.append(_(' duplicates %(onoff)s')) - opt = mlist.getMemberOption(address, config.SuppressPasswordReminder) - # sense is reversed - onoff = (not opt) and _('on') or _('off') - res.results.append(_(' reminders %(onoff)s')) - - def set_authenticate(self, res, args): - mlist = res.mlist - if len(args) == 1: - realname, address = parseaddr(res.msg['from']) - password = args[0] - elif len(args) == 2 and args[1].startswith('address='): - password = args[0] - address = args[1][8:] - else: - return self._usage(res) - # See if the password matches - if not mlist.isMember(address): - listname = mlist.real_name - res.results.append( - _('You are not a member of the %(listname)s mailing list')) - return STOP - if not mlist.Authenticate((config.AuthUser, - config.AuthListAdmin), - password, address): - res.results.append(_('You did not give the correct password')) - return STOP - self.__authok = 1 - self.__address = address - - def _status(self, res, arg): - status = arg.lower() - if status == 'on': - flag = 1 - elif status == 'off': - flag = 0 - else: - res.results.append(_('Bad argument: %(arg)s')) - self._usage(res) - return -1 - # See if we're authenticated - if not self.__authok: - res.results.append(_('Not authenticated')) - self._usage(res) - return -1 - return flag - - def set_ack(self, res, args): - mlist = res.mlist - if len(args) <> 1: - return self._usage(res) - status = self._status(res, args[0]) - if status < 0: - return STOP - mlist.setMemberOption(self.__address, config.AcknowledgePosts, status) - res.results.append(_('ack option set')) - - def set_digest(self, res, args): - mlist = res.mlist - if len(args) <> 1: - return self._usage(res) - if not self.__authok: - res.results.append(_('Not authenticated')) - self._usage(res) - return STOP - arg = args[0].lower() - if arg == 'off': - try: - mlist.setMemberOption(self.__address, config.Digests, 0) - except Errors.AlreadyReceivingRegularDeliveries: - pass - elif arg == 'plain': - try: - mlist.setMemberOption(self.__address, config.Digests, 1) - except Errors.AlreadyReceivingDigests: - pass - mlist.setMemberOption(self.__address, config.DisableMime, 1) - elif arg == 'mime': - try: - mlist.setMemberOption(self.__address, config.Digests, 1) - except Errors.AlreadyReceivingDigests: - pass - mlist.setMemberOption(self.__address, config.DisableMime, 0) - else: - res.results.append(_('Bad argument: %(arg)s')) - self._usage(res) - return STOP - res.results.append(_('digest option set')) - - def set_delivery(self, res, args): - mlist = res.mlist - if len(args) <> 1: - return self._usage(res) - status = self._status(res, args[0]) - if status < 0: - return STOP - # Delivery status is handled differently than other options. If - # status is true (set delivery on), then we enable delivery. - # Otherwise, we have to use the setDeliveryStatus() interface to - # specify that delivery was disabled by the user. - if status: - mlist.setDeliveryStatus(self.__address, MemberAdaptor.ENABLED) - res.results.append(_('delivery enabled')) - else: - mlist.setDeliveryStatus(self.__address, MemberAdaptor.BYUSER) - res.results.append(_('delivery disabled by user')) - - def set_myposts(self, res, args): - mlist = res.mlist - if len(args) <> 1: - return self._usage(res) - status = self._status(res, args[0]) - if status < 0: - return STOP - # sense is reversed - mlist.setMemberOption(self.__address, config.DontReceiveOwnPosts, - not status) - res.results.append(_('myposts option set')) - - def set_hide(self, res, args): - mlist = res.mlist - if len(args) <> 1: - return self._usage(res) - status = self._status(res, args[0]) - if status < 0: - return STOP - mlist.setMemberOption(self.__address, config.ConcealSubscription, - status) - res.results.append(_('hide option set')) - - def set_duplicates(self, res, args): - mlist = res.mlist - if len(args) <> 1: - return self._usage(res) - status = self._status(res, args[0]) - if status < 0: - return STOP - # sense is reversed - mlist.setMemberOption(self.__address, config.DontReceiveDuplicates, - not status) - res.results.append(_('duplicates option set')) - - def set_reminders(self, res, args): - mlist = res.mlist - if len(args) <> 1: - return self._usage(res) - status = self._status(res, args[0]) - if status < 0: - return STOP - # sense is reversed - mlist.setMemberOption(self.__address, config.SuppressPasswordReminder, - not status) - res.results.append(_('reminder option set')) - - - -def process(res, args): - # We need to keep some state between set commands - if not getattr(res, 'setstate', None): - res.setstate = SetCommands() - res.setstate.process(res, args) diff --git a/src/attic/cmd_unsubscribe.py b/src/attic/cmd_unsubscribe.py deleted file mode 100644 index 456b8089d..000000000 --- a/src/attic/cmd_unsubscribe.py +++ /dev/null @@ -1,88 +0,0 @@ -# Copyright (C) 2002-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -""" - unsubscribe [password] [address=<address>] - Unsubscribe from the mailing list. If given, your password must match - your current password. If omitted, a confirmation email will be sent - to the unsubscribing address. If you wish to unsubscribe an address - other than the address you sent this request from, you may specify - `address=<address>' (no brackets around the email address, and no - quotes!) -""" - -from email.Utils import parseaddr - -from mailman import Errors -from mailman.i18n import _ - -STOP = 1 - - - -def gethelp(mlist): - return _(__doc__) - - - -def process(res, args): - mlist = res.mlist - password = None - address = None - argnum = 0 - for arg in args: - if arg.startswith('address='): - address = arg[8:] - elif argnum == 0: - password = arg - else: - res.results.append(_('Usage:')) - res.results.append(gethelp(mlist)) - return STOP - argnum += 1 - # Fill in empty defaults - if address is None: - realname, address = parseaddr(res.msg['from']) - if not mlist.isMember(address): - listname = mlist.real_name - res.results.append( - _('%(address)s is not a member of the %(listname)s mailing list')) - return STOP - # If we're doing admin-approved unsubs, don't worry about the password - if mlist.unsubscribe_policy: - try: - mlist.DeleteMember(address, 'mailcmd') - except Errors.MMNeedApproval: - res.results.append(_("""\ -Your unsubscription request has been forwarded to the list administrator for -approval.""")) - elif password is None: - # No password was given, so we need to do a mailback confirmation - # instead of unsubscribing them here. - cpaddr = mlist.getMemberCPAddress(address) - mlist.ConfirmUnsubscription(cpaddr) - # We don't also need to send a confirmation to this command - res.respond = 0 - else: - # No admin approval is necessary, so we can just delete them if the - # passwords match. - oldpw = mlist.getMemberPassword(address) - if oldpw <> password: - res.results.append(_('You gave the wrong password')) - return STOP - mlist.ApprovedDeleteMember(address, 'mailcmd') - res.results.append(_('Unsubscription request succeeded.')) diff --git a/src/attic/cmd_who.py b/src/attic/cmd_who.py deleted file mode 100644 index 6c66610b3..000000000 --- a/src/attic/cmd_who.py +++ /dev/null @@ -1,152 +0,0 @@ -# Copyright (C) 2002-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman is free software: you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -from email.Utils import parseaddr - -from mailman import i18n -from mailman.config import config - -STOP = 1 - -def _(s): return s - -PUBLICHELP = _(""" - who - See the non-hidden members of this mailing list. - who password - See everyone who is on this mailing list. The password is the - list's admin or moderator password. -""") - -MEMBERSONLYHELP = _(""" - who password [address=<address>] - See the non-hidden members of this mailing list. The roster is - limited to list members only, and you must supply your membership - password to retrieve it. If you're posting from an address other - than your membership address, specify your membership address with - `address=<address>' (no brackets around the email address, and no - quotes!). If you provide the list's admin or moderator password, - hidden members will be included. -""") - -ADMINONLYHELP = _(""" - who password - See everyone who is on this mailing list. The roster is limited to - list administrators and moderators only; you must supply the list - admin or moderator password to retrieve the roster. -""") - -_ = i18n._ - - - -def gethelp(mlist): - if mlist.private_roster == 0: - return _(PUBLICHELP) - elif mlist.private_roster == 1: - return _(MEMBERSONLYHELP) - elif mlist.private_roster == 2: - return _(ADMINONLYHELP) - - -def usage(res): - res.results.append(_('Usage:')) - res.results.append(gethelp(res.mlist)) - - - -def process(res, args): - mlist = res.mlist - address = None - password = None - ok = False - full = False - if mlist.private_roster == 0: - # Public rosters - if args: - if len(args) == 1: - if mlist.Authenticate((config.AuthListModerator, - config.AuthListAdmin), - args[0]): - full = True - else: - usage(res) - return STOP - else: - usage(res) - return STOP - ok = True - elif mlist.private_roster == 1: - # List members only - if len(args) == 1: - password = args[0] - realname, address = parseaddr(res.msg['from']) - elif len(args) == 2 and args[1].startswith('address='): - password = args[0] - address = args[1][8:] - else: - usage(res) - return STOP - if mlist.isMember(address) and mlist.Authenticate( - (config.AuthUser, - config.AuthListModerator, - config.AuthListAdmin), - password, address): - # Then - ok = True - if mlist.Authenticate( - (config.AuthListModerator, - config.AuthListAdmin), - password): - # Then - ok = full = True - else: - # Admin only - if len(args) <> 1: - usage(res) - return STOP - if mlist.Authenticate((config.AuthListModerator, - config.AuthListAdmin), - args[0]): - ok = full = True - if not ok: - res.results.append( - _('You are not allowed to retrieve the list membership.')) - return STOP - # It's okay for this person to see the list membership - dmembers = mlist.getDigestMemberKeys() - rmembers = mlist.getRegularMemberKeys() - if not dmembers and not rmembers: - res.results.append(_('This list has no members.')) - return - # Convenience function - def addmembers(members): - for member in members: - if not full and mlist.getMemberOption(member, - config.ConcealSubscription): - continue - realname = mlist.getMemberName(member) - if realname: - res.results.append(' %s (%s)' % (member, realname)) - else: - res.results.append(' %s' % member) - if rmembers: - res.results.append(_('Non-digest (regular) members:')) - addmembers(rmembers) - if dmembers: - res.results.append(_('Digest members:')) - addmembers(dmembers) diff --git a/src/attic/digests.py b/src/attic/digests.py deleted file mode 100644 index 812ae1649..000000000 --- a/src/attic/digests.py +++ /dev/null @@ -1,426 +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/>. - -"""Add the message to the list's current digest and possibly send it.""" - -# Messages are accumulated to a Unix mailbox compatible file containing all -# the messages destined for the digest. This file must be parsable by the -# mailbox.UnixMailbox class (i.e. it must be ^From_ quoted). -# -# When the file reaches the size threshold, it is moved to the qfiles/digest -# directory and the DigestRunner will craft the MIME, rfc1153, and -# (eventually) URL-subject linked digests from the mbox. - -from __future__ import absolute_import, unicode_literals - -__metaclass__ = type -__all__ = [ - 'ToDigest', - ] - - -import os -import re -import copy -import time -import logging - -from StringIO import StringIO # cStringIO can't handle unicode. -from email.charset import Charset -from email.generator import Generator -from email.header import decode_header, make_header, Header -from email.mime.base import MIMEBase -from email.mime.message import MIMEMessage -from email.mime.text import MIMEText -from email.parser import Parser -from email.utils import formatdate, getaddresses, make_msgid -from zope.interface import implements - -from mailman import Message -from mailman import Utils -from mailman import i18n -from mailman.Mailbox import Mailbox -from mailman.Mailbox import Mailbox -from mailman.config import config -from mailman.core import errors -from mailman.interfaces.handler import IHandler -from mailman.interfaces.member import DeliveryMode, DeliveryStatus -from mailman.pipeline.decorate import decorate -from mailman.pipeline.scrubber import process as scrubber - - -_ = i18n._ - -UEMPTYSTRING = '' -EMPTYSTRING = '' - -log = logging.getLogger('mailman.error') - - - -def process(mlist, msg, msgdata): - # Short circuit non-digestable lists. - if not mlist.digestable or msgdata.get('isdigest'): - return - mboxfile = os.path.join(mlist.data_path, 'digest.mbox') - mboxfp = open(mboxfile, 'a+') - mbox = Mailbox(mboxfp) - mbox.AppendMessage(msg) - # Calculate the current size of the accumulation file. This will not tell - # us exactly how big the MIME, rfc1153, or any other generated digest - # message will be, but it's the most easily available metric to decide - # whether the size threshold has been reached. - mboxfp.flush() - size = os.path.getsize(mboxfile) - if size / 1024.0 >= mlist.digest_size_threshold: - # This is a bit of a kludge to get the mbox file moved to the digest - # queue directory. - try: - # Enclose in try/except here because a error in send_digest() can - # silently stop regular delivery. Unsuccessful digest delivery - # should be tried again by cron and the site administrator will be - # notified of any error explicitly by the cron error message. - mboxfp.seek(0) - send_digests(mlist, mboxfp) - os.unlink(mboxfile) - except Exception, errmsg: - # Bare except is generally prohibited in Mailman, but we can't - # forecast what exceptions can occur here. - log.exception('send_digests() failed: %s', errmsg) - mboxfp.close() - - - -def send_digests(mlist, mboxfp): - # Set the digest volume and time - if mlist.digest_last_sent_at: - bump = False - # See if we should bump the digest volume number - timetup = time.localtime(mlist.digest_last_sent_at) - now = time.localtime(time.time()) - freq = mlist.digest_volume_frequency - if freq == 0 and timetup[0] < now[0]: - # Yearly - bump = True - elif freq == 1 and timetup[1] <> now[1]: - # Monthly, but we take a cheap way to calculate this. We assume - # that the clock isn't going to be reset backwards. - bump = True - elif freq == 2 and (timetup[1] % 4 <> now[1] % 4): - # Quarterly, same caveat - bump = True - elif freq == 3: - # Once again, take a cheap way of calculating this - weeknum_last = int(time.strftime('%W', timetup)) - weeknum_now = int(time.strftime('%W', now)) - if weeknum_now > weeknum_last or timetup[0] > now[0]: - bump = True - elif freq == 4 and timetup[7] <> now[7]: - # Daily - bump = True - if bump: - mlist.bump_digest_volume() - mlist.digest_last_sent_at = time.time() - # Wrapper around actually digest crafter to set up the language context - # properly. All digests are translated to the list's preferred language. - with i18n.using_language(mlist.preferred_language): - send_i18n_digests(mlist, mboxfp) - - - -def send_i18n_digests(mlist, mboxfp): - mbox = Mailbox(mboxfp) - # Prepare common information (first lang/charset) - lang = mlist.preferred_language - lcset = Utils.GetCharSet(lang) - lcset_out = Charset(lcset).output_charset or lcset - # Common Information (contd) - realname = mlist.real_name - volume = mlist.volume - issue = mlist.next_digest_number - digestid = _('$realname Digest, Vol $volume, Issue $issue') - digestsubj = Header(digestid, lcset, header_name='Subject') - # Set things up for the MIME digest. Only headers not added by - # CookHeaders need be added here. - # Date/Message-ID should be added here also. - mimemsg = Message.Message() - mimemsg['Content-Type'] = 'multipart/mixed' - mimemsg['MIME-Version'] = '1.0' - mimemsg['From'] = mlist.request_address - mimemsg['Subject'] = digestsubj - mimemsg['To'] = mlist.posting_address - mimemsg['Reply-To'] = mlist.posting_address - mimemsg['Date'] = formatdate(localtime=1) - mimemsg['Message-ID'] = make_msgid() - # Set things up for the rfc1153 digest - plainmsg = StringIO() - rfc1153msg = Message.Message() - rfc1153msg['From'] = mlist.request_address - rfc1153msg['Subject'] = digestsubj - rfc1153msg['To'] = mlist.posting_address - rfc1153msg['Reply-To'] = mlist.posting_address - rfc1153msg['Date'] = formatdate(localtime=1) - rfc1153msg['Message-ID'] = make_msgid() - separator70 = '-' * 70 - separator30 = '-' * 30 - # In the rfc1153 digest, the masthead contains the digest boilerplate plus - # any digest header. In the MIME digests, the masthead and digest header - # are separate MIME subobjects. In either case, it's the first thing in - # the digest, and we can calculate it now, so go ahead and add it now. - mastheadtxt = Utils.maketext( - 'masthead.txt', - {'real_name' : mlist.real_name, - 'got_list_email': mlist.posting_address, - 'got_listinfo_url': mlist.script_url('listinfo'), - 'got_request_email': mlist.request_address, - 'got_owner_email': mlist.owner_address, - }, mlist=mlist) - # MIME - masthead = MIMEText(mastheadtxt.encode(lcset), _charset=lcset) - masthead['Content-Description'] = digestid - mimemsg.attach(masthead) - # RFC 1153 - print >> plainmsg, mastheadtxt - print >> plainmsg - # Now add the optional digest header - if mlist.digest_header: - headertxt = decorate(mlist, mlist.digest_header, _('digest header')) - # MIME - header = MIMEText(headertxt.encode(lcset), _charset=lcset) - header['Content-Description'] = _('Digest Header') - mimemsg.attach(header) - # RFC 1153 - print >> plainmsg, headertxt - print >> plainmsg - # Now we have to cruise through all the messages accumulated in the - # mailbox file. We can't add these messages to the plainmsg and mimemsg - # yet, because we first have to calculate the table of contents - # (i.e. grok out all the Subjects). Store the messages in a list until - # we're ready for them. - # - # Meanwhile prepare things for the table of contents - toc = StringIO() - print >> toc, _("Today's Topics:\n") - # Now cruise through all the messages in the mailbox of digest messages, - # building the MIME payload and core of the RFC 1153 digest. We'll also - # accumulate Subject: headers and authors for the table-of-contents. - messages = [] - msgcount = 0 - msg = mbox.next() - while msg is not None: - if msg == '': - # It was an unparseable message - msg = mbox.next() - continue - msgcount += 1 - messages.append(msg) - # Get the Subject header - msgsubj = msg.get('subject', _('(no subject)')) - subject = Utils.oneline(msgsubj, in_unicode=True) - # Don't include the redundant subject prefix in the toc - mo = re.match('(re:? *)?(%s)' % re.escape(mlist.subject_prefix), - subject, re.IGNORECASE) - if mo: - subject = subject[:mo.start(2)] + subject[mo.end(2):] - username = '' - addresses = getaddresses([Utils.oneline(msg.get('from', ''), - in_unicode=True)]) - # Take only the first author we find - if isinstance(addresses, list) and addresses: - username = addresses[0][0] - if not username: - username = addresses[0][1] - if username: - username = ' ({0})'.format(username) - # Put count and Wrap the toc subject line - wrapped = Utils.wrap('{0:2}. {1}'.format(msgcount, subject), 65) - slines = wrapped.split('\n') - # See if the user's name can fit on the last line - if len(slines[-1]) + len(username) > 70: - slines.append(username) - else: - slines[-1] += username - # Add this subject to the accumulating topics - first = True - for line in slines: - if first: - print >> toc, ' ', line - first = False - else: - print >> toc, ' ', line.lstrip() - # We do not want all the headers of the original message to leak - # through in the digest messages. For this phase, we'll leave the - # same set of headers in both digests, i.e. those required in RFC 1153 - # plus a couple of other useful ones. We also need to reorder the - # headers according to RFC 1153. Later, we'll strip out headers for - # for the specific MIME or plain digests. - keeper = {} - all_keepers = set( - header for header in - config.digests.mime_digest_keep_headers.split() + - config.digests.plain_digest_keep_headers.split()) - for keep in all_keepers: - keeper[keep] = msg.get_all(keep, []) - # Now remove all unkempt headers :) - for header in msg.keys(): - del msg[header] - # And add back the kept header in the RFC 1153 designated order - for keep in all_keepers: - for field in keeper[keep]: - msg[keep] = field - # And a bit of extra stuff - msg['Message'] = repr(msgcount) - # Get the next message in the digest mailbox - msg = mbox.next() - # Now we're finished with all the messages in the digest. First do some - # sanity checking and then on to adding the toc. - if msgcount == 0: - # Why did we even get here? - return - toctext = toc.getvalue() - # MIME - try: - tocpart = MIMEText(toctext.encode(lcset), _charset=lcset) - except UnicodeError: - tocpart = MIMEText(toctext.encode('utf-8'), _charset='utf-8') - tocpart['Content-Description']= _("Today's Topics ($msgcount messages)") - mimemsg.attach(tocpart) - # RFC 1153 - print >> plainmsg, toctext - print >> plainmsg - # For RFC 1153 digests, we now need the standard separator - print >> plainmsg, separator70 - print >> plainmsg - # Now go through and add each message - mimedigest = MIMEBase('multipart', 'digest') - mimemsg.attach(mimedigest) - first = True - for msg in messages: - # MIME. Make a copy of the message object since the rfc1153 - # processing scrubs out attachments. - mimedigest.attach(MIMEMessage(copy.deepcopy(msg))) - # rfc1153 - if first: - first = False - else: - print >> plainmsg, separator30 - print >> plainmsg - # Use Mailman.pipeline.scrubber.process() to get plain text - try: - msg = scrubber(mlist, msg) - except errors.DiscardMessage: - print >> plainmsg, _('[Message discarded by content filter]') - continue - # Honor the default setting - for h in config.digests.plain_digest_keep_headers.split(): - if msg[h]: - uh = Utils.wrap('{0}: {1}'.format( - h, Utils.oneline(msg[h], in_unicode=True))) - uh = '\n\t'.join(uh.split('\n')) - print >> plainmsg, uh - print >> plainmsg - # If decoded payload is empty, this may be multipart message. - # -- just stringfy it. - payload = msg.get_payload(decode=True) \ - or msg.as_string().split('\n\n',1)[1] - mcset = msg.get_content_charset('us-ascii') - try: - payload = unicode(payload, mcset, 'replace') - except (LookupError, TypeError): - # unknown or empty charset - payload = unicode(payload, 'us-ascii', 'replace') - print >> plainmsg, payload - if not payload.endswith('\n'): - print >> plainmsg - # Now add the footer - if mlist.digest_footer: - footertxt = decorate(mlist, mlist.digest_footer) - # MIME - footer = MIMEText(footertxt.encode(lcset), _charset=lcset) - footer['Content-Description'] = _('Digest Footer') - mimemsg.attach(footer) - # RFC 1153 - # BAW: This is not strictly conformant RFC 1153. The trailer is only - # supposed to contain two lines, i.e. the "End of ... Digest" line and - # the row of asterisks. If this screws up MUAs, the solution is to - # add the footer as the last message in the RFC 1153 digest. I just - # hate the way that VM does that and I think it's confusing to users, - # so don't do it unless there's a clamor. - print >> plainmsg, separator30 - print >> plainmsg - print >> plainmsg, footertxt - print >> plainmsg - # Do the last bit of stuff for each digest type - signoff = _('End of ') + digestid - # MIME - # BAW: This stuff is outside the normal MIME goo, and it's what the old - # MIME digester did. No one seemed to complain, probably because you - # won't see it in an MUA that can't display the raw message. We've never - # got complaints before, but if we do, just wax this. It's primarily - # included for (marginally useful) backwards compatibility. - mimemsg.postamble = signoff - # rfc1153 - print >> plainmsg, signoff - print >> plainmsg, '*' * len(signoff) - # Do our final bit of housekeeping, and then send each message to the - # outgoing queue for delivery. - mlist.next_digest_number += 1 - virginq = config.switchboards['virgin'] - # Calculate the recipients lists - plainrecips = set() - mimerecips = set() - # When someone turns off digest delivery, they will get one last digest to - # ensure that there will be no gaps in the messages they receive. - # Currently, this dictionary contains the email addresses of those folks - # who should get one last digest. We need to find the corresponding - # IMember records. - digest_members = set(mlist.digest_members.members) - for address in mlist.one_last_digest: - member = mlist.digest_members.get_member(address) - if member: - digest_members.add(member) - for member in digest_members: - if member.delivery_status <> DeliveryStatus.enabled: - continue - # Send the digest to the case-preserved address of the digest members. - email_address = member.address.original_address - if member.delivery_mode == DeliveryMode.plaintext_digests: - plainrecips.add(email_address) - elif member.delivery_mode == DeliveryMode.mime_digests: - mimerecips.add(email_address) - else: - raise AssertionError( - 'Digest member "{0}" unexpected delivery mode: {1}'.format( - email_address, member.delivery_mode)) - # Zap this since we're now delivering the last digest to these folks. - mlist.one_last_digest.clear() - # MIME - virginq.enqueue(mimemsg, - recips=mimerecips, - listname=mlist.fqdn_listname, - isdigest=True) - # RFC 1153 - # If the entire digest message can't be encoded by list charset, fall - # back to 'utf-8'. - try: - rfc1153msg.set_payload(plainmsg.getvalue().encode(lcset), lcset) - except UnicodeError: - rfc1153msg.set_payload(plainmsg.getvalue().encode('utf-8'), 'utf-8') - virginq.enqueue(rfc1153msg, - recips=plainrecips, - listname=mlist.fqdn_listname, - isdigest=True) diff --git a/src/attic/docs/OLD-NEWS.txt b/src/attic/docs/OLD-NEWS.txt deleted file mode 100644 index c87635640..000000000 --- a/src/attic/docs/OLD-NEWS.txt +++ /dev/null @@ -1,2835 +0,0 @@ -Mailman - The GNU Mailing List Management System -Copyright (C) 1998-2007 by the Free Software Foundation, Inc. -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - -Here is a history of user visible changes to Mailman. - -2.1.10b4 (13-Mar-2008) - - Security - - - The 2.1.9 fixes for CVE-2006-3636 were not complete. In particular, - some potential cross-site scripting attacks were not detected in - editing templates and updating the list's info attribute via the web - admin interface. This has been assigned CVE-2008-0564 and has been - fixed. Thanks again to Moritz Naumann for assistance with this. - - New Features - - - Changed cmd_who.py to list all members if authorization is with the - list's admin or moderator password and to accept the password if the - roster is public. Also changed the web roster to show hidden members - when authorization is by site or list's admin or moderator password - (1587651). - - - Added the ability to put a list name in accept_these_nonmembers - to accept posts from members of that list (1220144). - - - Added a new 'sibling list' feature to exclude members of another list - from receiving a post from this list if the other list is in the To: or - Cc: of the post or to include members of the other list if that list is - not in the To: or Cc: of the post (Patch ID 1347962). - - - Added the admin_member_chunksize attribute to the admin General Options - interface (Bug 1072002, Partial RFE 782436). - -Internationalization - - - Added the Hebrew translation from Dov Zamir. This includes addition of - a direction ('ltr', 'rtl') to the LC_DESCRIPTIONS table. The - add_language() function defaults direction to 'ltr' to not break - existing mm_cfg.py files. - - - Added the Slovak translation from Martin Matuska. - - - Added the Galician translation from Frco. Javier Rial Rodríguez. - - Bug fixes and other patches - - - Added bounce recognition for several additional bounce formats. - - - Fixed CommandRunner.py to decode a quoted-printable or base64 encoded - message part (1829061). - - - Fixed Scrubber.py to avoid loss of an implicit text/plain message part - with no Content-* headers in a MIME multipart message (759841). Fixed - several other minor scrubber issues (1242450). - - - Added Date and Message-ID headers to the confirm reply message that - Mailman adds to the admin notification (1471318). - - - Fixed Cgi/options.py to not present the "empty" topic to user. - - - Fixed Handlers/CalcRecips.py to not process topics if topics are - disabled for the list. This caused users who had previously subscribed - to topics and elected to not receive non-matching posts to receive no - messages after topics were disabled for the list. - - - Fixed MaildirRunner.py to handle hyphenated list names. - - - Fixed a bug in MimeDel.py (content filtering) which caused - *_filename_extensions to not match if the extension in the message was - not all lower case. - - - Fixed versions.py to not call a non-existant method when converting held - posts from Mailman 1.0.x lists. - - - Added a test to configure to detect a missing python-devel package on - some RedHat systems. - - - Fixed bin/dumpdb to once again be able to dump marshals (broken since - 2.1.5) (963137). - - - Worked around a bug in the Python email library that could cause Mailman - to not get the correct value for the sender of a message from an RFC - 2231 encoded header causing spurious held messages. - - - Fixed bin/check_perms to detect certain missing permissions on the - archives/private/ and archives/private/<list>/database/ directories. - - - Improved exception handling in cron/senddigests. - - - Changed the admindb page to not show the "Discard all messages marked - Defer" checkbox when there are only (un)subscribes and no held messages. - Also added a separator and heading for "Held Messages" like the ones for - "Subscribe Requests" and "Unsubscribe Requests". Suppressed the - "Database Updated" message when coming from the login page. Also - removed the "Discard all messages marked Defer" checkbox from the - details page where it didn't work (1562922, 1000699). - - - Fixed admin.py so null VARHELP category is handled (1573393). - - - Fixed OldStyleMemberships.py to preserve delivery statuses BYADMIN - and BYUSER on a straight change of address (1642388). Also fixed a - bug that could result in a member key with uppercase in the domain. - - - Fixed bin/withlist so that -r can take a full package path to a - callable. - - - Removal of DomainKey/DKIM signatures is now controlled by Defaults.py - mm_cfg.py variable REMOVE_DKIM_HEADERS (default = No). Also, if - REMOVE_DKIM_HEADERS = Yes, an Authentication-Results: header will be - removed if present. - - - The DeprecationWarning issued by Python 2.5 regarding string exceptions - is supressed. - - - format=flowed and delsp=yes are now preserved for message bodies when - message headers/footers are added and attachments are scrubbed - (1495122). - - - Queue runner processing is improved to log and preserve for analysis in - the shunt queue certain bad queue entries that were previously logged - but lost. Also, entries are preserved when an attempt to shunt throws - an exception (1656289). - - - The admin Membership List pages have been changed in that the email - address which forms a part of the various CGI data keys is now - urllib.quote()ed. This allows changing options for and unsubbing an - address which contains a double-quote character, but it may require - changes to scripts that screen-scrape the web admin interface to - produce a membership list so they will report an unquoted address. - - - The fix for bug 1181161 in 2.1.7 was incomplete. The Approve(d): line - wasn't always found in quoted-printable encoded parts and was never - found in base64 encoded parts. This is now fixed. - - - Fixed a mail loop if a list owner puts the list's -bounces or -admin - address in the list's owner attribute (1834569). - - - Fixed the mailto: link in archived messages to prefix the subject with - Re: and to put the correct message-id in In-Reply-To (1621278, 1834281). - - - Coerced list name arguments to lower case in the change_pw, inject, - list_admins and list_owners command line tools (patch 1842412). - - - Fixed cron/disabled to test if bounce info is stale before disabling - a member when the threshold has been reduced. - - - It wasn't noted here, but in 2.1.9, queue runner processing was made - more robust by making backups of queue entries when they were dequeued - so they could be recovered in the event of a system failure. This - opened the possibility that if a message itself caused a runner to - crash, a loop could result that would endlessly reprocess the message. - This has now been fixed by adding a dequeue count to the entry and - moving the entry aside and logging the fact after the third dequeue of - the same entry. - - - Fixed the command line scripts add_members, sync_members and - clone_member to properly handle banned addresses (1904737). - - - Fixed bin/newlist to add the list's preferred language to the list's - available_languages if it is other than the server's default language - (1906368). - - - Changed the first URL in the RFC 2369 List-Unsubscribe: header to go - to the options login page instead of the listinfo page. - - - Changed the options login page to not issue the "No address given" error - when coming from the List-Unsubscribe and other direct links. Also - changed to remember the user's language selection when redisplaying the - page following an error. - - - Changed cmd_subscribe.py to properly accept (no)digest without a - password and to recognize (no)digest and address= case insensitively. - - Miscellaneous - - - Brad Knowles' mailman daily status report script updated to 0.0.17. - -2.1.9 (12-Sep-2006) - - Security - - - A malicious user could visit a specially crafted URI and inject an - apparent log message into Mailman's error log which might induce an - unsuspecting administrator to visit a phishing site. This has been - blocked. Thanks to Moritz Naumann for its discovery. - - - Fixed denial of service attack which can be caused by some - standards-breaking RFC 2231 formatted headers. CVE-2006-2941. - - - Several cross-site scripting issues have been fixed. Thanks to Moritz - Naumann for their discovery. CVE-2006-3636 - - - Fixed an unexploitable format string vulnerability. Discovery and fix - by Karl Chen. Analysis of non-exploitability by Martin 'Joey' Schulze. - Also thanks go to Lionel Elie Mamane. CVE-2006-2191. - - Internationalization - - - New languages: Arabic, Vietnamese. - - Bug fixes and other patches - - - Fixed Decorate.py so that characters in message header/footer which - are not in the character set of the list's language are ignored rather - than causing shunted messages (1507248). - - - Switchboard.py - Closed very tiny holes at the upper ends of queue - slices that could result in unprocessable queue entries. Improved FIFO - processing when two queue entries have the same timestamp. - -2.1.8 (15-Apr-2006) - - Security - - - A cross-site scripting hole in the private archive script of 2.1.7 - has been closed. Thanks to Moritz Naumann for its discovery. - - Bug fixes and other patches - - - Bouncers support added: 'unknown user', Microsoft SMTPSVC, Prodigy.net - and several others. - - - Updated email library to 2.5.7 which will encode payload into qp/base64 - upon setting. This enabled backing out the scrubber related patches - including 'X-Mailman-Scrubbed' header in 2.1.7. - - - Fix SpamDetect.py potential hold/reject loop problem. - - - A warning message from email package to the stderr can cause error - in Logging because stderr may be detached from the process during - the qrunner run. We chose not to output errors to stderr but to - the logs/error if the process is running under mailmanctl subprocess. - - - DKIM header cleansing was separated from Cleanse.py and added to - -owner messages too. - - - Fixes: Lose Topics when go directly to topics URL (1194419). - UnicodeError running bin/arch (1395683). edithtml.py missing import - (1400128). Bad escape in cleanarch. Wrong timezone in list archive - index pages (1433673). bin/arch fails with TypeError (1430236). - Subscription fails with some Language combinations (1435722). - Postfix delayed notification not recognized (863989). 2.1.7 (VERP) - mistakes delay notice for bounce (1421285). show_qfiles: 'str' - object has no attribute 'as_string' (1444447). Utils.get_domain() - wrong if VIRTUAL_HOST_OVERVIEW off (1275856). - - Miscellaneous - - - Brad Knowles' mailman daily status report script updated to 0.0.16. - -2.1.7 (31-Dec-2005) - - Security - - - The fix for CAN-2005-0202 has been enhanced to issue an appropriate - message instead of just quietly dropping ./ and ../ from URLs. - - - A note on CVE-2005-3573: Although the RFC2231 bug example in the CVE has - been solved in Mailman 2.1.6, there may be more cases where - ToDigest.send_digests() can block regular delivery. We put the - send_digests() calling part in a try/except clause and leave a message - in the error log if something happened in send_digests(). Daily call of - cron/senddigests will provide more detail to the site administrator. - - - List administrators can no longer change the user's option/subscription - globally. Site admin can change these only if - mm_cfg.ALLOW_SITE_ADMIN_COOKIES is set to Yes. - - - <script> tags are HTML-escaped in the edithtml CGI script. - - - Since the probe message for disabled users may reach unintended - recipients, the password is excluded from sendProbe() and probe.txt. - Note that the default value of VERP_PROBE has been set to `No' from - 2.1.6., thus this change doesn't affect the default behavior. - - New Features - - - Always remove DomainKey (and similar) headers from messages sent to the - list. (1287546) - - - List owners can control the content filter behavior when collapsing - multipart/alternative parts to its first subpart. This allows the - option of letting the HTML part pass through after other content - filtering is done. - - Internationalization - - - New language: Interlingua. - - Bug fixes and other patches - - - Defaults.py.in: SCRUBBER_DONT_USE_ATTACHMENT_FILENAME is set to True for - safer operation. - - - Fixed the bug where Scrubber.py munges quoted-printable by introducing - the 'X-Mailman-Scrubbed' header which marks that the payload is - scrubber-munged. The flag is referenced in ToDigest.py, ToArchive.py, - Decorate.py and Archiver. A similar problem in ToDigest.py where the - plain digest is generated is also fixed. - - - Fixed Syslog.py to write quopri encoded messages when it fail to write - 8-bit characters. - - - Fixed MTA/Postfix.py to check aliases group permission in check_perms - and fixed mailman-install document on this matter (1378270). - - - Fixed private.py to go to the original URL after authorization - (1080943). - - - Fixed bounce log score messages to be more consistent. - - - Fixed bin/remove_members to accept no arguments when both --fromall and - --file= options are specified. - - - Changed cgi-bin and mail wrapper "group not found" error message to be - more descriptive of the actual problem. - - - The list's ban_list now applies to address changes, admin mass - subscribes and invites, and to confirmations/approvals of address - changes, subscriptions and invitations. - - - quoted-printable and base64 encoded parts are decoded before passing to - HTML_TO_PLAIN_TEXT_COMMAND (1367783). - - - Approve: header is removed from posts, and treated the same as the - Approved: header. (1355707) - - - Fixed the removal of the line following Approve[d]: line in body of - post. (1318883) - - - The Approve[d]: <password> header is removed from all text/* parts in - addition the initial text/plain part. It must still be the first - non-blank line in the first text/plain part or it won't be found or - removed at all. (1181161) - - - Posts are now logged in post log file with the true sender, not - listname-bounces. (1287921) - - - Correctly initialize and remember the list's default_member_moderation - attribute in the web list creation page. (1263213) - - - PEP263 charset is added to the config_list output. (1343100) - - - Fixed header_filter_rules getting lost if accessed directly and - authentication was needed by login page. (1230865) - - - Obscure email when the poster doesn't set full name in 'From:' header. - - - Preambles and epilogues are taken into account when calculating message - sizes for holding purposes. (Mark Sapiro) - - - Logging/Logger.py unicode transform option. (1235567) - - - bin/update crashes with bogus files. (949117) - - - Bugs and patches: 1212066/1301983 (Date header in create/remove notice) - -2.1.6 (30-May-2005) - - Security - - - Critical security patch for path traversal vulnerability in private - archive script (CAN-2005-0202). - - - Added the ability for Mailman generated passwords (both member and list - admin) to be more cryptographically secure. See new configuration - variables USER_FRIENDLY_PASSWORDS, MEMBER_PASSWORD_LENGTH, and - ADMIN_PASSWORD_LENGTH. Also added a new bin/withlist script called - reset_pw.py which can be used to reset all member passwords. Passwords - generated by Mailman are now 8 characters by default for members, and 10 - characters for list administrators. - - - A potential cross-site scripting hole in the driver script has been - closed. Thanks to Florian Weimer for its discovery. Also, turn - STEALTH_MODE on by default. - - Internationalization - - - Chinese languages are now supported. They have been moved from 'big5' - and 'gb' to 'zh_TW' and 'zh_CN' respectively for compliance to the IANA - spec. Note, however, that the character sets were changed from 'Big5' - or 'GB2312' to 'UTF-8' to cope with the insufficient codecs support in - Python 2.3 and earlier. You may have to install Chinese capable codecs - (like CJKCodecs) separately to handle the incoming messages which are in - local charsets, or upgrade your Python to 2.4 or newer. - - Behavior or defaults changes - - - VERP_PROBES is disabled by default. - - - bin/withlist can be run without a list name, but only if -i is given. - Also, withlist puts the directory it's found in at the end of sys.path, - making it easier to run withlist scripts that live in $prefix/bin. - - - bin/newlist grew two new options: -u/--urlhost and -e/--emailhost which - lets the user provide the web and email hostnames for the new mailing - list. This is a better way to specify the domain for the list, rather - than the old 'mylist@hostname' syntax (which is still supported for - backward compatibility, but deprecated). - - Compatibility - - - Python 2.4 compatibility issue: time.strftime() became strict about the - 'day of year' range. (1078482) - - New Features - - - New feature: automatic discards of held messages. List owners can now - set how many days to hold the messages in the moderator request queue. - cron/checkdb will automatically discard old messages. See the - max_days_to_hold variable in the General Options and - DEFAULT_MAX_DAYS_TO_HOLD in Defaults.py. This defaults to 0 - (i.e. disabled). (790494) - - - New feature: subject_prefix can be configured to include a sequence - number which is taken from the post_id variable. Also, the prefix is - always put at the start of the subject, i.e. "[list-name] Re: original - subject", if mm_cfg.OLD_STYLE_PREFIXING is set No. The default style - is "Re: [list-name]" if numbering is not set, for backward compatibility. - If the list owner is using numbering feature by "%d" directive, the new - style, "[list-name 123] Re:", is always used. - - - List owners can now cusomize the non-member rejection notice from - admin/<listname>/privacy/sender page. (1107169) - - - Allow editing of the welcome message from the admin page (1085501). - - - List owners can now use Scrubber to get the attachments scrubbed (held - in the web archive), if the site admin permits it in mm_cfg.py. New - variables introduced are SCRUBBER_DONT_USE_ATTACHMENT_FILENAME and - SCRUBBER_USE_ATTACHMENT_FILENAME_EXTENSION in Defaults.py for scrubber - behavior. (904850) - - Documentation - - - Most of the installation instructions have been moved to a latex - document. See doc/mailman-install/index.html for details. - - Bug fixes and other patches - - - Mail-to-news gateway now strips subject prefix off from a response - by a mail user if news_prefix_subject_too is not set. - - - Date and Message-Id headers are added for digests. (1116952) - - - Improved mail address sanity check. (1030228) - - - SpamDetect.py now checks attachment header. (1026977) - - - Filter attachments by filename extensions. (1027882) - - - Bugs and patches: 955381 (older Python compatibility), 1020102/1013079/ - 1020013 (fix spam filter removed), 665569 (newer Postfix bounce - detection), 970383 (moderator -1 admin requests pending), 873035 - (subject handling in -request mail), 799166/946554 (makefile - compatibility), 872068 (add header/footer via unicode), 1032434 - (KNOWN_SPAMMERS check for multi-header), 1025372 (empty Cc:), 789015 - (fix pipermail URL), 948152 (Out of date link on Docs), 1099138 - (Scrubber.py breaks on None part), 1099840/1099840 (deprecated % - insertion), 880073/933762 (List-ID RFC compliance), 1090439 (passwd - reminder shunted), 1112349 (case insensitivity in acceptable_aliases), - 1117618 (Don't Cc for personalized anonymous list), 1190404 (wrong - permission after editing html) - -2.1.5 (15-May-2004) - - - The admindb page has a checkbox that allows you to discard all held - messages that are marked Defer. On heavy lists with lots of spam holds, - this makes clearing them much faster. - - - The qrunner system has changed to use only one file per message. - However the configuration variable METADATA_FORMAT has been removed, and - support for SAVE_MSGS_AS_PICKLES has been changed. The latter no longer - writes messages as plain text. Instead, they are stored as pickles of - plain strings, using the text pickle format. This still makes them - non-binary files readable and editable by humans. - - bin/dumpdb also works differently. It will print out the entire pickle - file (with more verbosity) and if used with 'python -i', it binds msg to - a list of all objects found in the pickle file. - - Removed from Defaults.py: PENDINGDB_LOCK_TIMEOUT, - PENDINGDB_LOCK_ATTEMPTS, METAFMT_MARSHAL, METAFMT_BSDDB_NATIVE, - METAFMT_ASCII, METADATA_FORMAT - - - The bounce processor has been redesigned so that now when an address's - bounce score reaches the threshold, that address will be sent a probe - message. Only if the probe bounces will the address be disabled. The - score is reset to zero when the probe is sent. Also, bounce events are - now kept in an event file instead of in memory. This should help - contain the bloat of the BounceRunner. - - New supporting variables in Defaults.py: VERP_PROBE_FORMAT, - VERP_PROBE_REGEXP - - REGISTER_BOUNCES_EVERY is promoted to a Defaults.py variable. - - - The pending database has been changed from a global pickle file, to a - unique pickle file per mailing list. - - - The 'request' database file has changed from a marshal, to the more - secure pickle format. - - - Disallow multiple password retrievals. - - - SF patch #810675 which adds a "Discard all messages marked Defer" button - for faster admindb maintenance. - - - The email package is updated to version 2.5.5. - - - New language: Turkish. - - - Bugs and patches: 869644, 869647 (NotAMemberError for old cookie data), - 878087 (bug in Slovenian catalog), 899263 (ignore duplicate pending - ids), 810675 (discard all defers button) - -2.1.4 (31-Dec-2003) - - - Close some cross-site scripting vulnerabilities in the admin pages - (CAN-2003-0965). - - - New languages: Catalan, Croatian, Romanian, Slovenian. - - - New mm_cfg.py/Defaults.py variable PUBLIC_MBOX which allows the site - administrator to disable public access to all the raw list mbox files - (this is not a per-list configuration). - - - Expanded header filter rules under Privacy -> Spam Filters. Now you can - specify regular expression matches against any header, with specific - actions tied to those matches. - - - Rework the SMTP error handling in SMTPDirect.py to avoid scoring bounces - for all recipients when a permanent error code is returned by the mail - server (e.g. because of content restrictions). - - - Promoted SYNC_AFTER_WRITE to a Default.py/mm_cfg.py variable and - make it control syncing on the config.pck file. Also, we always flush - and sync message files. - - - Reduce archive bloat by not storing the HTML body of Article objects in - the Pipermail database. A new script bin/rb-archfix was added to clean - up older archives. - - - Proper RFC quoting for List-ID descriptions. - - - PKGDIR can be passed to the make command in order to specify a different - directory to unpack the distutils packages in misc. (SF bug 784700). - - - Improved logging of the origin of subscription requests. - - - Bugs and patches: 832748 (unsubscribe_policy ignored for unsub button on - member login page), 846681 (bounce disabled cookie was always out of - date), 835870 (check VIRTUAL_HOST_OVERVIEW on through the web list - creation), 835036 (global address change when the new address is already - a member of one of the lists), 833384 (incorrect admin password on a - hold message confirmation attachment would discard the message), 835012 - (fix permission on empty archive index), 816410 (confirmation page - consistency), 834486 (catch empty charsets in the scrubber), 777444 (set - the process's supplemental groups if possible), 860135 (ignore - DiscardMessage exceptions during digest scrubbing), 828811 (reduce - process size for list and admin overviews), 864674/864676 (problems - accessing private archives and rosters with admin password), 865661 - (Tokio Kikuchi's i18n patches), 862906 (unicode prefix leak in admindb), - 841445 (setting new_member_options via config_list), n/a (fixed email - command 'set delivery') - -2.1.3 (28-Sep-2003) - - Performance, Reliability, Security - - - Closed a cross-site scripting exploit in the create cgi script. - - - Improvements in the performance of the bounce processor. - Now, instead of processing each bounce immediately (which - can cause severe lock contention), bounce events are queued. - Every 15 minutes by default, the queued bounce events are - processed en masse, on a list-per-list basis, so that each - list only needs to be locked once. - - - When some or all of a message's recipients have temporary - delivery failures, the message is moved to a "retry" queue. - This queue wakes up occasionally and moves the file back to - the outgoing queue for attempted redelivery. This should - fix most observed OutgoingRunner 100% cpu consumption, - especially for bounces to local recipients when using the - Postfix MTA. - - - Optional support for fsync()'ing qfile data after writing. - Under some catastrophic system failures (e.g. power lose), - it would be possible to lose messages because the data - wasn't sync'd to disk. By setting SYNC_AFTER_WRITE to True - in Mailman/Queue/Switchboard.py, you can force Mailman to - fsync() queue files after flushing them. The benefits are - debatable for most operating environments, and you must - ensure that your Python has the os.fsync() function defined - before enabling this feature (it isn't, even on all - Unix-like operating systems). - - Internationalization - - - New languages Ukrainian, Serbian, Danish, Euskara/Basque. - - - Fixes to template lookup. Lists with local overriding - templates would find the wrong template. - - - .mo files (for internationalization) are now generated at - build time instead of coming as part of the source - distribution. - - Documentation - - - A first draft of member documentation by Terri Oda. There - is also a Japanese translation of this manual by Ikeda Soji. - - Archiver / Pipermail - - - In the configuration variables PUBLIC_EXTERNAL_ARCHIVER, and - PRIVATE_EXTERNAL_ARCHIVER, %(hostname)s has been added to - the list of allowable substitution variables. - - - The timezone is now taken into account when figuring the - posting date for an article. - - Scripts / Cron - - - Fixes to cron/disabled for NotAMemberError crashes. - - - New script bin/show_qfiles which prints the contents of .pck - message files. New script bin/discard which can be used to - mass discard held messages. - - - Fixes to cron/mailpasswds to account for old password-less - subscriptions. - - - bin/list_members has grown two new options: --invalid/-i - prints only the addresses in the member database that are - invalid (which could have snuck in via old releases); - --unicode/-u prints addresses which are stored as Unicode - objects instead of as normal strings. - - Miscellaneous - - - Fixes to problems in some configurations where Python wouldn't - be able to find its standard library. - - - Fixes to the digest which could cause MIME-losing missing - newlines when parts are scrubbed via the content filters. - - - In the News/Mail gateway admin page, the configuration variable - nntp_host can now be a name:port pair. - - - When messages are pulled from NNTP, the member moderation checks - are short-circuited. - - - email 2.5.4 is included. This fixes an RFC 2231 bug, among - possibly others. - - - Fixed some extra spaces that could appear in the List-ID header. - - - Fixes to ensure that invalid email addresses can't be invited. - - - WEB_LINK_COLOR in Defaults.py/mm_cfg.py should now work. - - - Fixes so that shunted message file names actually match - those logged in log/errors. - - - An improved pending action cookie generation algorithm has - been added. - - - Fixes to the DSN bounce detector. - - - The usual additional u/i, internationalization, unicode, and - other miscellaneous fixes. - -2.1.2 (22-Apr-2003) - - - New languages Portuguese (Portugal) and Polish. - - - Many convenient constants have been added to the Defaults.py - module to (hopefully) make it more readable. - - - Email addresses which contain 8-bit characters in them are now - rejected and won't be subscribed. This is not the same as 8-bit - characters in the realname, which is still allowed. - - - The X-Originating-Email header is removed for anonymous lists. - Hotmail apparently adds this header. - - - When running make to build Mailman, you can specify $DESTDIR to - the install target to specify an alternative location for - installation, without influencing the paths stored in - e.g. Defaults.py. This is useful to package managers. - - - New Defaults.py variable DELIVERY_RETRY_WAIT which controls how - long the outgoing qrunner will wait before it retries a - tempfailure delivery. - - - The semantics for the extend.py hook to MailList objects has - changed slightly. The hook is now called before attempting to - lock and load the database. - - - Mailman now uses the email package version 2.5.1 - - - bin/transcheck now checks for double-%'s - - - bin/genaliases grew a -q / --quiet flag - - - cron/checkdbs grew a -h / --help option. - - - The -c / --change-msg option has been removed from bin/add_members - - - bin/msgfmt.py has been added, taken from Python 2.3's Tools/i18n - directory. The various .mo files are now no longer distributed - with Mailman. They are generated at build time instead. - - - A new file misc/sitelist.cfg which can be used with - bin/config_list provides a small number of recommended settings - for your site list. Be sure to read it over before applying! - sitelist.cfg is installed into the data directory. - - - Many bug fixes, including these SourceForge bugs closed and - patches applied: 677668, 690448, 700538, 700537, 673294, 683906, - 671294, 522080, 521124, 534297, 699900, 697321, 695526, 703941, - 658261, 710678, 707608, 671303, 717096, 694912, 707624, 716755, - 661138, 716754, 716702, 667167, 725369, 726415 - - -2.1.1 (08-Feb-2003) - - Lots of bug fixes and language updates. Also: - - - Closed a cross-site scripting vulnerability in the user options page. - - - Restore the ability to control which headers show up in messages - included in plaintext and MIME digests. See the variables - PLAIN_DIGEST_KEEP_HEADERS and MIME_DIGEST_KEEP_HEADERS in - Defaults.py. - - - Messages included in the plaintext digests are now sent through - the scrubber to remove (and archive) attachments. Otherwise, - attachments would screw up plaintext digests. MIME digests - include the attachments inline. - -2.1 final (30-Dec-2002) - - Last minute bug fixes and language updates. - -2.1 rc 1 (24-Dec-2002) - - Bug fixes and language updates. Also, - - - Lithuanian support has been added. - - - bin/remove_members grew --nouserack and --noadminack switches - - - configure now honors --srcdir - -2.1 beta 6 (09-Dec-2002) - - Lots and lots of bug fixes, and translation updates. Also, - - - ARCHIVER_OBSCURES_EMAILADDRS is now set to true by default. - - - QRUNNER_SAVE_BAD_MESSAGES is now set to true by default. - - - Bounce messages which were recognized, but in which no member - addresses were found are no longer forwarded to the list - administrator. - - - bin/arch grew a --wipe option which first removes the entire old - archive before regenerating the new one. - - - bin/mailmanctl -u now prints a warning that permission problems - could appear, such as when trying to delete a list through the - web that has some archives in it. - - - bin/remove_members grew --nouserack/-n and -noadminack/-N options. - - - A new script bin/list_owners has been added for printing out - list owners and moderators. - - - Dates in the web version of archived messages are now relative - to the local timezone, and include the timezone names, when - available. - -2.1 beta 5 (19-Nov-2002) - - As is typical for a late beta release, this one includes the usual - bug fixes, tweaks, and massive new features (just kidding). - - IMPORTANT: If you are using Pipermail, and you have any archives - that were created or added to in 2.1b4, you will need to run - bin/b4b5-archfix, followed by bin/check_perms to fix some serious - performance problems. From you install directory, run - "bin/b4b5-archfix --help" for details. - - - The personalization options have been tweaked to provide more - control over mail header and decoration personalizations. In - 2.1b4, when personalization was enabled, the To and Cc headers - were always overwritten. But that's usually not appropriate for - anything but announce lists, so now these headers aren't changed - unless "Full personalization" is enabled. - - - You now need to go to the General category to enable emergency - moderation. - - - The order of the hold modules in the GLOBAL_PIPELINE has - changed, again. Now Moderate comes before Hold. - - - Estonian language support has been added. - - - All posted messages should now get decorated with headers and - footers in a MIME-safe way. Previously, some MIME type messages - didn't get decorated at all. - - - bin/arch grew a -q/--quiet option - - - bin/list_lists grew a -b/--bare option - -2.1 beta 4 (26-Oct-2002) - - The usual assortment of bug fixes and language updates, some u/i - tweaks, as well as the following: - - - Configuring / building / installing - o Tightened up some configure checks; it will now bark loudly - if you don't have the Python distutils package available - (some Linux distros only include distutils in their "devel" - packages). - - o Mailman's username/group security assertions are now done by - symbolic name instead of numeric id. This provides a level - of indirection that makes it much easier to move or package - Mailman. --with-mail-gid and --with-cgi-gid are retained, - but they control the group names used instead. - - - Command line scripts - o A new script, bin/transcheck that language teams can use to - check their .po files. - - o bin/list_members grew a --fullnames/-f option to print the - full names along with the addresses. - - o cron/senddigests grew --help/-h and --listname/-l options. - - o bin/fix_url.py grew some command line options to support moving - a list to a specific virtual domain. - - - Pipermail / archiving - o Reworked the directory layout for archive attachments to be - less susceptible to inode overload. Attachments are now - placed in - - archives/private/<listname>/attachments/<YYYYMMDD>/<msgidhash> - - o Internationalization support in the archiver has been improved. - - - Internationalization - o New languages: Swedish. - - - Mail handling - o Content filtering now has a pass_mime_type variable, which - is a whitelist of MIME types to allow in postings. See the - details of the variable in the Content Filtering category - for more information. - - o If a member has enabled their DontReceiveDuplicates option, - we'll also strip their addresses from the Cc headers in the - copy of the message sent to the list. This helps keep the - Cc lines from growing astronomically. - - o Bounce messages are now forwarded to the list administrators - both if they are unrecognized, and if no list member's - address could be extracted. - - o Content filtering now has a filter_action variable which - controls what happens when a message matches the content - filter rules. The default is still to discard the message. - - o When searching for an Approve/Approved header, the first - non-whitespace line of the body of the message is also - checked, if the body has a MIME type of text/plain. - - o If a list is personalized, and the list's posting address is - not included in a Reply-To header, the posting address is - copied into a Cc header, otherwise there was no (easy) way a - recipient could reply back to the list. - - o Added a MS Exchange bounce recognizer. - - o New configuration variable news_moderation which allows the - mail->news gateway to properly post to moderated newsgroups. - - o Messages sent to a list's owners now comes from the site - list to prevent mail loops when list owners or moderators - having bouncing addresses. - - - Miscellaneous - o mailanctl prevents runaway restarts by imposing a maximum - restart value (defaulting to 10) for restarting the - qrunners. If you hit this limit, do "mailmanctl stop" - followed by "mailmanctl start". - - o The Membership Management page's search feature now includes - searching on members real names. - - o The start of a manual for list administrators is given in - Python HOWTO format (LaTeX). It's in doc/mailman-admin.tex - but it still needs lots of fleshing out. - - o More protections against creating a list with an invalid name. - -2.1 beta 3 (09-Aug-2002) - - The usual assortment of bug fixes and language updates. - - - New languages: Dutch, Portuguese (Brazil) - - - New configure script options: --with-mailhost, --with-urlhost, - --without-permcheck. See ./configure --help for details. - - - The encoding of Subject: prefixes is controlled by a new list - option encode_ascii_prefixes. This is useful for languages with - character sets other than us-ascii. See the Languages admin - page for details. - - - A new list option news_prefix_subject_too controls whether - postings gated from mail to news should have the subject prefix - added to their Subject: header. - - - The algorithm for upgrading the moderation controls for a - Mailman 2.0.x list has changed. The change should be - transparent, but you'll want to double check the moderation - controls after upgrading from MM2.0.x. This should have no - effect for upgrades from a previous MM2.1 beta. - - See the UPGRADING file for details. - - - On the Mass Subscribe admin page, a text box has been added so - that the admin can add a custom message to be prepended to the - welcome/invite notification. - - - On the admindb page, a link is included to more easily reload - the page. - - - The Sendmail.py delivery module is sabotaged so that it can't be - used naively. You need to read the comments in the file and - edit the code to use this unsafe module. - - - When a member sends a `help' command to the request address, - the url to their options page is included in the response. - - - Autoresponses, -request command responses, and posting hold - notifications are inhibited for any message that has a - Precedence: {bulk|list|junk} header. This is to avoid mail - loops between email 'bots. If the original message has an - X-Ack: yes header, the response is sent. - - Responses are also limited to a maximum number per day, as - defined in the site variable MAX_AUTORESPONSES_PER_DAY. This is - another guard against 'bot loops, and it defaults to 10. - - - When a Reply-To: header is munged to include both the original - and the list address, the list address is always added last. - - - The cron/mailpasswds script has grown a -l/--listname option. - - - The cron/disabled script has grown options to send out - notifications for reasons other than bounce-disabled. It has - also grown a -f/--force option. See cron/disabled --help for - details. - - - The bin/dumpdb script has grown a -n/--noprint option. - - - An experimental new mechanism for processing incoming messages - has been added. If you can configure your MTA to do qmail-style - Maildir delivery, Mailman now has a MaildirRunner qrunner. This - may turn out to be much more efficient and scalable, but for - MM2.1, it will not be officially supported. See Defaults.py.in - and Mailman/Queue/MaildirRunner.py for details. - -2.1 beta 2 (05-May-2002) - - Lots of bug fixing, and the following new features and changes: - - - A "de-mime" content filter feature has been added. This - oft-requested feature allows you to specify MIME types that - Mailman should strip off of any messages before they're posted - to the list. You can also optionally convert text/html to - text/plain (by default, through lynx if it's available). - - - Changes to the way the RFC 2919 and 2369 headers (i.e. the - List-*: headers) are added: - o List-Id: is always added - o List-Post:, List-Help:, List-Subscribe:, - List-Unsubscribe:, and List-Archive: are only added to - posting messages. - o X-List-Administrivia: is only added to messages Mailman - creates and sends out of its own accord. - - Also, if the site administrator allows it, list owners can - suppress the addition of all the List-*: headers. List owners - can also separately suppress the List-Post: header for - announce-only lists. - - - A new framework for email commands has been added. This allows - you to easily add, delete, or change the email commands that - Mailman understands, on a per-site, per-list, or even per-user - basis. - - - Users can now change their digest delivery type from MIME to - plain text globally, for all lists they are subscribed to. - - - No language select pulldowns are shown if the list only supports - one language. - - - More mylist-admin eradication. - - - Several performance improvements in the bounce qrunner, one of - which is to make it run only once per minute instead of once per - second. - - - Korean language support as been added. - - - Gatewaying from news -> mail uses its connections to the nntpd - more efficiently. - - - In bin/add_members, -n/--non-digest-members-file command line - switch is deprecated in favor of -r/--regular-members-file. - - - bin/sync_members grew a -g/--goodbye-msg switch. - -2.1 beta 1 (16-Mar-2002) - - In addition to the usual bug fixes, performance improvements, and - GUI changes, here are the highlights: - - - MIME and other message handling - o More robustness against badly MIME encapsulated messages: if - a MessageParseError is raised during the initial parse, the - message can either be discarded or saved in qfiles/bad, - depending on the value of the new configuration variable - QRUNNER_SAVE_BAD_MESSAGES. - - o There is a new per-user option that can be used to avoid - receipt of extra copies, when a member of the list is also - explicitly CC'd. - - o Always add an RFC 2822 Date: header if missing, since not - all MTAs insert one automatically. - - o The Sender: and Errors-To: headers are no longer added to - outgoing messages. - - o Headers and footers are always added by concatenation, if - the message is not MIME and if the list's charset is a - superset of us-ascii. - - - List administration - o An `invitation' feature has been added. This is selectable - as a radio button on the mass subscribe page. When - selected, users are invited to join instead of immediately - joined, i.e. they get a confirmation message. - - o You can now enable and disable list owner notifications for - disabled-due-to-bouncing and removal-due-to-bouncing - actions. The site config variables - DEFAULT_BOUNCE_NOTIFY_OWNER_ON_DISABLE and - DEFAULT_BOUNCE_NOTIFY_OWNER_ON_REMOVAL control the default - behavior. - - o List owners can now decide whether they receive unrecognized - bounce messages or not (i.e. messages that the bounce - processor doesn't recognize). Site admins can set the - default value for this flag with the config variable - DEFAULT_BOUNCE_UNRECOGNIZED_GOES_TO_LIST_OWNER. - - o The admindb summary page gives the option of clearing the - moderation flag of members who are on quarantined. - - o The action to take when a moderated member posts to a list - is now configurable. The message can either be held, - rejected (bounced), or discarded. If the message is - rejected, a rejection notice string can be given. - - o In the General admin page, you can now set the default value - for five per-user flags: concealing the user's email - address, acknowledging posts sent by the user, copy - suppression, not-me-too selection, and the default digest - type. Site admins can set the default bit field with the - new DEFAULT_NEW_MEMBER_OPTIONS variable. - - o A new "Emergency brake" feature for turning on moderation of - all list postings. This is useful for when flamewars break - out, and the list needs a cooling off period. Messages - containing an Approved: header with the list owner password - are still allowed through, as are messages approved through - the admindb interface. - - o When a moderated message is approved for the list, add an - X-Mailman-Approved-At: header which contains the timestamp - of the approval action (changed from X-Moderated: with a - different format). - - o Lists can now be converted to using a less error prone - mechanism for variable substitution syntax in headers and - footers. Instead of %(var)s strings, you'd use $var - strings. You must use "bin/withlist -r convert" to enable - this. - - o When moderating held messages, the header text box and the - message excerpt text box are now both read-only. - - o You can't delete the site list through the web. - - o When creating new lists through the web, you have the option - of setting the "default member moderation" flag. - - - Security and privacy - o New feature: banned subscription addresses. Privacy - options/subscription rules now have an additional list box - which can contain addresses or regular expressions. - Subscription requests from any matching address are - automatically rejected. - - o Membership tests which compare message headers against list - rosters are now more robust. They now check, by default - these header in order: From:, unixfrom, Reply-To:, Sender:. - If any match, then the membership test succeeds. - - o ALLOW_SITE_ADMIN_COOKIES is a new configuration variable - which says whether to allow AuthSiteAdmin cookies or not. - Normally, when a list administrator logs into a list with - the site password, they are issued a cookie that only allows - them to do administration for this one list. By setting - ALLOW_SITE_ADMIN_COOKIES to 1, the user only needs to - authenticate to one list with the site password, and they - can administer any mailing list. - - I'm not sure this feature is wise, so the default value for - ALLOW_SITE_ADMIN_COOKIES is 0. - - o Marc MERLIN's new recipes for secure Linuxes have been - updated. - - o DEFAULT_PRIVATE_ROSTER now defaults to 1. - - o Passwords are no longer included in the confirmation pages. - - - Internationalization - o With the approval of Tamito KAJIYAMA, the Japanese codecs - for Python are now included automatically, so you don't need - to download and install these separate. It is installed in - a Mailman-specific place so it won't affect your larger - Python installation. - - o The configure script will produce a warning if the Chinese - codes are not installed. This is not a fatal error. - - o Russian templates and catalogs have been added. - - o Finnish templates and catalogs have been added. - - - Scripts and utilities - o New program bin/unshunt to safely move shunted messages back - into the appropriate processing queue. - - o New program bin/inject for sending a plaintext message into - the incoming queue from the command line. - - o New cron script cron/disabled for periodically culling the - disabled membership. - - o bin/list_members has grown some new command line switches - for filtering on different criteria (digest mode, disable - mode, etc.) - - o bin/remove_members has grown the --fromall switch. - - o You can now do a bin/rmlist -a to remove an archive even - after the list has been deleted. - - o bin/update removes the $prefix/Mailman/pythonlib directory. - - o bin/withlist grows a --all/-a flag so the --run/-r option - can be applied to all the mailing lists. Also, interactive - mode is now the default if -r isn't used. You don't need to - run this script as "python -i bin/withlist" anymore. - - o There is a new script contrib/majordomo2mailman.pl which - should ease the transition from Majordomo to Mailman. - - - MTA integration - o Postfix integration has been made much more robust, but now - you have to set POSTFIX_ALIAS_CMD and POSTFIX_MAP_CMD to - point to the postalias and postmap commands respectively. - - o VERP-ish delivery has been made much more efficient by - eliminating extra disk copies of messages for each recipient - of a VERP delivery. It has also been made more robust in - the face of failures during chunk delivery. This required a - rewrite of SMTPDirect.py and one casualty of that rewrite - was the experimental threaded delivery. It is no longer - supported (but /might/ be resurrected if there's enough - demand -- or a contributed patch :). - - o A new site config variable SMTP_MAX_SESSIONS_PER_CONNECTION - specifies how many consecutive SMTP sessions will be - conducted down the same socket connection. Some MTAs have a - limit on this. - - o Support for VERP-ing confirmation messages. These are less - error prone since the Subject: header doesn't need to be - retained, and they allow a more user friendly (and i18n'd) - Subject: header. VERP_CONFIRM_FORMAT, VERP_CONFIRM_REGEXP, - and VERP_CONFIRMATIONS control this feature (only supported - for invitation confirmations currently, but will be expanded - to the other confirmations). - - o Several new list-centric addresses have been added: - -subscribe and -unsubscribe are synonyms for -join and - -leave, respectively. Also -confirm has been added to - support VERP'd confirmations. - - - Archiver - o There's now a default page for the Pipermail archive link - for when no messages have yet been posted to the list. - - o Just the mere presence of an X-No-Archive: is enough to - inhibit archiving for this message; the value of the header - is now ignored. - - - Configuring, building, installing - o Mailman now has a new favicon, donated by Terry Oda. Not - all web pages are linked to the favicon yet though. - - o The add-on email package is now distributed and installed - automatically, so you don't need to do this. It is - installed in a Mailman-specific place so it won't affect - your larger Python installation. - - o The default value of VERP_REGEXP has changed. - - o New site configuration variables BADQUEUE_DIR and - QRUNNER_SAVE_BAD_MESSAGES which describe where to save - messages which are not properly MIME encoded. - - o configure should be more POSIX-ly conformant. - - o The Mailman/pythonlib directory has been removed, but a new - $prefix/pythonlib directory has been added. - - o Regression tests are now installed. - - o The second argument to add_virtual() calls in mm_cfg.py are - now optional. - - o DEFAULT_FIRST_STRIP_REPLY_TO now defaults to 0. - - o Site administrators can edit the Mailman/Site.py file to - customize some filesystem layout policies. - - -2.1 alpha 4 (31-Dec-2001) - - - The administrative requests database page (admindb) has been - redesigned for better usability when there are lots of held - postings. Changes include: - o A summary page which groups held messages by sender email - address. On this page you can dispose of all the sender's - messages in one action. You can also view the details of - all the sender's messages, or the details of a single - message. You can also add the sender to one of the list's - sender filters. - - o A details page where you can view all messages, just those - for a particular sender, or just a single held message. - This details page is laid out the same as the old admindb - page. - - o The instructions have been shorted on the summary and - details page, with links to more detailed explanations. - - - Bounce processing - o Mailman now keeps track of the reason a member's delivery - has been disabled: explicitly by the administrator, - explicitly by the user, by the system due to excessive - bounces, or for (legacy) unknown reasons. - - o A new bounce processing algorithm has been implemented (we - might actually understand this one ;). When an address - starts bouncing, the member gets a "bounce score". Hard - (fatal) bounces score 1.0, while soft (transient) bounces - score 0.5. - - List administrators can specify a bounce threshold above - which a member gets disabled. They can also specify a time - interval after which, if no bounces are received from the - member, the member's bounce score is considered stale and is - thrown away. - - o A new cron script, cron/disabled, periodically sends - notifications to members who are bounce disabled. After a - certain number of warnings the member is deleted from the - list. List administrators can control both the number of - notifications and the amount of time between notifications. - - Notifications include a confirmation cookie that the member - can use to re-enable their subscription, via email or web. - - o New configuration variables to support the bounce processing - are DEFAULT_BOUNCE_SCORE_THRESHOLD, - DEFAULT_BOUNCE_INFO_STALE_AFTER, - DEFAULT_BOUNCE_YOU_ARE_DISABLED_WARNINGS, - DEFAULT_BOUNCE_YOU_ARE_DISABLED_WARNINGS_INTERVAL. - - - Privacy and security - o Sender filters can now be regular expressions. If a line - starts with ^ it is taken as a (raw string) regular - expression, otherwise it is a literal email address. - - o Fixes in 2.0.8 ported forward: prevent cross-site scripting - exploits. - - - Mail delivery - o Aliases have all been changed so that there's more - consistency between the alias a message gets delivered to, - and the script & queue runner that handles the message. - - I've also renamed the mail wrapper script to `mailman' from - `wrapper' to avoid collisions with other MLM's. You /will/ - need to regenerate your alias files with bin/genaliases, and - you may need to update your smrsh (Sendmail) configs.a - - Bounces always go to listname-bounces now, since - administration has been separated from bounce processing. - listname-admin is obsolete. - - o VERP support! This greatly improves the accuracy of bounce - detection. Configuration variables which control this feature - include VERP_DELIVERY_INTERVAL, VERP_PERSONALIZED_DELIVERIES, - VERP_PASSWORD_REMINDERS, VERP_REGEXP, and VERP_FORMAT. The - latter two must be tuned to your MTA. - - o A new alias mailman-loop@dom.ain is added which directs all - output to the file $prefix/data/owner-bounces.mbox. This is - used when sending messages to the site list owners, as the - final fallback for bouncing messages. - - o New configuration variable POSTFIX_STYLE_VIRTUAL_DOMAINS - which should be set if you are using the Postfix MTA and - want Mailman to play nice with Postfix-style virtual - domains. - - - Miscellaneous - o Better interoperability with Python 2.2. - - o MailList objects now record the date (in seconds since - epoch) that they were created. This is in a hidden - attribute `created_at'. - - o bin/qrunner grows a -s/--subproc switch which is usually - used only when it's started from mailmanctl. - - o bin/newlist grows a -l/--language option so that the list's - preferred language can be set from the command line. - - o cron changes: admin reminders go out at 8am local time instead - of 10pm local time. - - - Pipermail archiver - o MIME attachments are scrubbed out into separate files which - can be viewed by following a link in the original article. - Article contains an indication of the size of the - attachment, its type, and other useful information. - - o New script bin/cleanarch which can be used to `clean' an - .mbox archive file by fixing unescaped embedded Unix From_ - lines. - - o New configuration variable ARCHIVE_SCRUBBER in - Defaults.py.in which names the module that Pipermail should - use to scrub articles of MIME attachments. - - o New configuration variable ARCHIVE_HTML_SANITIZER which - describes how the scrubber should handle text/html - attachments. - - o PUBLIC_ARCHIVE_URL has change its semantics. It is now an - absolute url, with the hostname and listname parts - interpolated into it on a per-list basis. - - o Pipermail should now provide the proper character set in the - Content-Type: header for archived articles. - - - Internationalization - o Czech translations by Dan Ohnesorg. - - o The Hungarian charset has be fixed to be iso-8859-2. - - o The member options login page now has a language selection - widget. - - - Building, configuration - o email-0.96 package is required (see the misc directory). - - o New recipes for integrating Mailman and Sendmail, - contributed by David Champion. - - -2.1 alpha 3 (22-Oct-2001) - - - Realname support - o Mailman now tracks a member's Real Name in addition to their - email address. - - o List members can now supply their Real Names when - subscribing via the web. Their Real Names are parsed from - any thru-email subscriptions. - - o Members can change their Real Names on their options page, - and admins can change members' Real Names on the membership - pages. Mass subscribing accepts "email@dom.ain (Real Name)" - and "Real Name <email@dom.ain>" entries, for both - in-text-box and file-upload mass subscriptions. - - - Filtering and Privacy - o Reply-To: munging has been enhanced to allow a wider range - of list policies. You can now pre-strip any Reply-To: - headers before adding list-specific ones (i.e. you can - override or extend existing Reply-To: headers). If - stripping, the old headers are no longer saved on - X-Reply-To: - - o New sender moderation rules. The old `posters', - `member_only_posting', `moderated' and `forbidden_posters' - options have been removed in favor of a new moderation - scheme. Each member has a personal moderation bit, and - non-member postings can be automatically accepted, held for - approval, rejected (bounced) or discarded. - - o When membership rosters are private, responses to - subscription (and other) requests are made more generic so - that these processes can't be covertly mined for hidden - addresses. If a subscription request comes in for a user - who is already subscribed, the user is notified of potential - membership mining. - - o When a held message is approved via the admindb page, an - X-Moderated: header is added to the message. - - o List admins can now set an unsubscribe policy which requires - them to approve of member unsubscriptions. - - - Web U/I - o All web confirmations now require a two-click procedure, - where the first click gives them a page that allows them to - confirm or cancel their subscription. It is bad form for an - email click (HTTP GET) to have side effects. - - o Lots of improvements for clarity. - - o The Privacy category has grown three subcategories. - - o The General options page as a number of subsection headers. - - o The Passwords and Languages categories are now on separate - admin pages. - - o The admin subcategories are now formated as two columns in - the top and bottom legends. - - o When creating a list through the web, you can now specify - the initial list of supported languages. - - o The U/I for unsubscribing a member on the admin's membership - page should be more intuitive now. - - o There is now a separate configuration option for whether the - goodbye_msg is sent when a member is unsubscribed. - - - Performance - o misc/mailman is a Unix init script, appropriate for - /etc/init.d, and containing chkconfig hooks for systems that - support it. - - o bin/mailmanctl has been rewritten; the `restart' command - actually works now. It now also accepts -s, -q, and -u - options. - - o bin/qrunner has been rewritten too; it can serve the role of - the old cron/qrunner script for those who want classic - cron-invoked mail delivery. - - o Internally, messages are now stored in the qfiles directory - primarily as pickles. List configuration databases are now - stored as pickles too (i.e. config.pck). bin/dumpdb knows - how to display both pickles and marshals. - - - Mail delivery - o If a user's message is held for approval, they are sent a - notification message containing a confirmation cookie. They - can use this confirmation cookie to cancel their own - postings (if they haven't already been approved). - - o When held messages are forwarded to an explicit address - using the admindb page, it is done so in a message/rfc822 - encapsulation. - - o When a message is first held for approval, the notification - sent to the list admin is a 3-part multipart/mixed. The - first part holds the notification message, the second part - hold the original message, and the third part hold a cookie - confirmation message, to which the admin can respond to - approve or discard the message via email. - - o In the mail->news gateway, you can define mail headers that - must be modified or deleted before the message can be posted - to the nntp server. - - o The list admin can send an immediate urgent message to the - entire list membership, bypassing digest delivery. This is - done by adding an Urgent: header with the list password. - Urgent messages with an invalid password are rejected. - - o Lists can now optionally personalize email messages, if the - site admin allows it. Personalized messages mean that the - To: header includes the recipient's address instead of the - list's address, and header and footer messages can contain - user-specific information. Note that only regular - deliveries can currently be personalized. - - o Message that come from Usenet but that have broken MIME - boundaries are ignored. - - o If the site administrator agrees, list owners have the - ability to disable RFC 2369 List-* headers. - - o There is now an API for an external process to post a - message to a list. This posting process can also specify an - explicit list of recipients, in effect turning the mailing - list into a "virtual list" with a fluid membership. See - Mailman/Post.py for details. - - - Building/testing/configuration - o mimelib is no longer required, but you must install the - email package (see the tarball in the misc directory). - - o An (as yet) incomplete test suite has been added. Don't try - running it in a production environment! - - o Better virtual host support by adding a mapping from the - host name given in cgi's HTTP_HOST/SERVER_NAME variable to - the email host used in list addresses. (E.g. www.python.org - maps to @python.org). - - o Specifying urls to external public archivers is more - flexible. - - o The filters/ subdirectory has been removed. - - o There is now a `site list' which is a mailing list that must - be created first, and from which all password reminders - appear to come from. It is recommended that this list be - called "mailman@your.site". - - o bin/move_list is no longer necessary (see the FAQ for - detailed instructions on renaming a list). - - o A new script bin/fix_url.py can be used with bin/withlist to - change a list's web_page_url configuration variable (since - it is no longer modifiable through the web). - - - Internationalization - o Support for German, Hungarian, Italian, Japanese, and - Norwegian have been added. - - - Miscellaneous - o Lots of new bounce detectors. Bounce detectors can now - discard temporary bounce messages by returning a special - Stop value. - - o bin/withlist now sports a -q/--quiet flag. - - o bin/add_members has a new -a/--admin-notify flag which can - be used to inhibit list owner notification for each - subscription. - - - Membership Adaptors - o Internally, mailing list memberships are accessed through a - MemberAdaptor interface. This would allow for integrating - membership databases with external sources (e.g. Zope or - LDAP), although the only MemberAdaptor currently implemented - is a "classic" adaptor which stores the membership - information on the MailList object. - - o There's a new pipeline handler module called FileRecips.py - which could be used to get all regular delivery mailing list - recipients from a Sendmail-style :include: file (see List - Extensibility bullet below). - - This work was sponsored by Control.com - - - List Extensibility - o A framework has been added which can be used to specialize - and extend specific mailing lists. If there is a file - called lists/<yourlist>/extend.py, it is execfile()'d after - the MailList object is instantiated. The file should - contain a function extend() which will be called with the - MailList instance. This function can do all sorts of deep - things, like modify the handler pipeline just for this list, - or even strip out particular admin GUI elements (see below). - - o All the admin page GUI elements are now separate - components. This provides greater flexibility for list - customization. Also, each GUI element will be given an - opportunity to handle admin CGI form data. - - This work was sponsored by Control.com - - - Topic Filters - o A new feature has been added called "Topic Filters". A list - administrator can create topics, which are essentially - regular expression matches against Subject: and Keyword: - headers (including such pseudo-headers if they appear in the - first few lines of the body of a message). - - List members can then `subscribe' to various topics, which - allows them to filter out any messages that don't match a - topic, or to filter out any message that does match a - topic. This can be useful for high volume lists where not - everyone will be interested in every message. - - This work was sponsored by Control.com - -2.1 alpha 2 (11-Jul-2001) - - - Building - o mimelib 0.4 is now required. Get it from - http://mimelib.sf.net. If you've installed an earlier - version of mimelib, you must upgrade. - - o /usr/local/mailman is now the default installation - directory. Use configure's --prefix switch to change it - back to the default (/home/mailman) or any other - installation directory of your choice. - - - Security - o Better definition of authentication domains. The following - roles have been defined: user, list-admin, list-moderator, - creator, site-admin. - - o There is now a separate role of "list moderator", which has - access to the pending requests (admindb) page, but not the - list configuration pages. - - o Subscription confirmations can now be performed via email or - via URL. When a subscription is received, a unique (sha) - confirm URL is generated in the confirmation message. - Simply visiting this URL completes the subscription process. - - o In a similar manner, removal requests (via web or email - command) no longer require the password. If the correct - password is given, the removal is performed immediately. If - no password is given, then a confirmation message is - generated. - - - Internationalization - o More I18N patches. The basic infrastructure should now be - working correctly. Spanish templates and catalogs are - included, and English, French, Hungarian, and Big5 templates - are included. - - o Cascading specializations and internationalization of - templates. Templates are now search for in the following - order: list-specific location, domain-specific location, - site-wide location, global defaults. Each search location - is further qualified by the language being displayed. This - means that you only need to change the templates that are - different from the global defaults. - - Templates renamed: admlogin.txt => admlogin.html - Templates added: private.html - - - Web UI - o Redesigned the user options page. It now sits behind an - authentication so user options cannot be viewed without the - proper password. The other advantage is that the user's - password need not be entered on the options page to - unsubscribe or change option values. The login screen also - provides for password mail-back, and unsubscription w/ - confirmation. - - Other new features accessible from the user options page - include: ability to change email address (with confirmation) - both per-list and globally for all list on virtual domain; - global membership password changing; global mail delivery - disable/enable; ability to suppress password reminders both - per-list and globally; logout button. - - [Note: the handle_opts cgi has gone away] - - o Color schemes for non-template based web pages can be defined - via mm_cfg. - - o Redesign of the membership management page. The page is now - split into three subcategories (Membership List, Mass - Subscription, and Mass Removal). The Membership List - subcategory now supports searching for member addresses by - regular expression, and if necessary, it groups member - addresses first alphabetically, and then by chunks. - - Mass Subscription and Mass Removal now support file upload, - with one address per line. - - o Hyperlinks from the logos in the footers have been removed. - The sponsors got too much "unsubscribe me!" spam from - desperate user of Mailman at other sites. - - o New buttons on the digest admin page to send a digest - immediately (if it's non-empty), to start a new digest - volume with the next digest, and to select the interval with - which to automatically start a new digest volume (yearly, - monthly, quarterly, weekly, daily). - - DEFAULT_DIGEST_VOLUME_FREQUENCY is a new configuration - variable, initially set to give a new digest volume monthly. - - o Through-the-web list creation and removal, using a separate - site-wide authentication role called the "list creator and - destroyer" or simply "list creator". If the configuration - variable OWNERS_CAN_DELETE_THEIR_OWN_LISTS is set to 1 (by - default, it's 0), then list admins can delete their own - lists. - - This feature requires an adaptor for the particular MTA - you're using. An adaptor for Postfix is included, as is a - dumb adaptor that just emails mailman@yoursite with the - necessary Sendmail style /etc/alias file changes. Some MTAs - like Exim can be configured to automatically recognize new - lists. The adaptor is selected via the MTA option in - mm_cfg.py - - - Email UI - o In email commands, "join" is a synonym for - "subscribe". "remove" and "leave" are synonyms for - "unsubscribe". New robot addresses are support to make - subscribing and unsubscribing much easier: - - mylist-join@mysite - mylist-leave@mysite - - o Confirmation messages have a shortened Subject: header, - containing just the word "confirm" and the confirmation - cookie. This should help for MUAs that like to wrap long - Subject: lines, messing up confirmation. - - o Mailman now recognizes an Urgent: header, which, if it - contains the list moderator or list administrator password, - forces the message to be delivered immediately to all - members (i.e. both regular and digest members). The message - is also placed in the digest. If the password is incorrect, - the message will be bounced back to the sender. - - - Performance - o Refinements to the new qrunner subsystem which preserves - FIFO order of messages. - - o The qrunner is no longer started from cron. It is started - by a Un*x init-style script called bin/mailmanctl (see - below). cron/qrunner has been removed. - - - Command line scripts - o bin/mailmanctl script added, which is used to start, stop, - and restart the qrunner daemon. - - o bin/qrunner script added which allows a single sub-qrunner - to run once through its processing loop. - - o bin/change_pw script added (eases mass changing of list - passwords). - - o bin/update grows a -f switch to force an update. - - o bin/newlang renamed to bin/addlang; bin/rmlang removed. - - o bin/mmsitepass has grown a -c option to set the list - creator's password. The site-wide `create' web page is - linked to from the admin overview page. - - o bin/newlist's -o option is removed. This script also grows - a way of spelling the creation of a list in a specific - virtual domain. - - o The `auto' script has been removed. - - o bin/dumpdb has grown -m/--marshal and -p/--pickle options. - - o bin/list_admins can be used to print the owners of a mailing list. - - o bin/genaliases regenerates from scratch the aliases and - aliases.db file for the Postfix MTA. - - - Archiver - o New archiver date clobbering option, which allows dates to - only be clobber if they are outrageously out-of-date - (default setting is 15 days on either side of received - timestamp). New configuration variables: - - ARCHIVER_CLOBBER_DATE_POLICY - ARCHIVER_ALLOWABLE_SANE_DATE_SKEW - - The archived copy of messages grows an X-List-Received-Date: - header indicating the time the message was received by - Mailman. - - o PRIVATE_ARCHIVE_URL configuration variable is removed (this - can be calculated on the fly, and removing it actually makes - site configuration easier). - - - Miscellaneous - o Several new README's have been added. - - o Most syslog entries for the qrunner have been redirected to - logs/error. - - o On SIGHUP, qrunner will re-open all its log files and - restart all child processes. See "bin/mailmanctl restart". - - - Patches and bug fixes - o SF patches and bug fixes applied: 420396, 424389, 227694, - 426002, 401372 (partial), 401452. - - o Fixes in 2.0.5 ported forward: - Fix a lock stagnation problem that can result when the - user hits the `stop' button on their browser during a - write operation that can take a long time (e.g. hitting - the membership management admin page). - - o Fixes in 2.0.4 ported forward: - Python 2.1 compatibility release. There were a few - questionable constructs and uses of deprecated modules - that caused annoying warnings when used with Python 2.1. - This release quiets those warnings. - - o Fixes in 2.0.3 ported forward: - Bug fix release. There was a small typo in 2.0.2 in - ListAdmin.py for approving an already subscribed member - (thanks Thomas!). Also, an update to the OpenWall - security workaround (contrib/securelinux_fix.py) was - included. Thanks to Marc Merlin. - -2.1 alpha 1 (04-Mar-2001) - - - Python 2.0 or newer required. Also required is `mimelib' a new - library for handling MIME documents. This will be bundled in - future releases, but for now, you must download and install it - (using Python's distutils) from - - http://barry.wooz.org/software/Code/mimelib-0.2.tar.gz - - You need mimelib 0.2 or better. - - - Redesigned qrunner subsystem. Now there are multiple message - queues, and considerable flexibility in file formats for - integration with external systems. The current crop of queues - include: - - archive -- for posting messages to an archiver - commands -- for incoming email commands and bounces - in -- for list-destined incoming email - news -- for messages outgoing to a nntp server - out -- for messages outgoing to a smtp server - shunt -- for messages that trigger unexpected exceptions in Mailman - virgin -- for messages that are generated by Mailman - - cron/qrunner is now a long running script that forks off - sub-runners for each of the above queues. qrunner still plays - nice with cron, but it is expected to be started by init at some - point in the future. Some support exists for parallel - processing of messages in the queues. - - - Support for internationalization support merged in. Original - work done by Juan Carlos Rey Anaya and Victoriano Giralt. I've - tested about 90% of the web side, 50% of the email, and 50% of - the command line / cron scripts. - - New scripts: bin/newlang, bin/rmlang - - - New delivery script `auto' for automatic integration with the - Postfix MTA. - - - A bunch of new bounce detectors. - - Changes ported from Mailman 2.0.2 and 2.0.1: - - - A fix for a potential privacy exploit where a clever list - administrator could gain access to user passwords. This doesn't - allow them to do much more harm to the user then they normally - could, but they still shouldn't have access to the passwords. - - - In the admindb page, don't complain when approving a - subscription of someone who's already on the list (SF bug - #222409 - Thomas Wouters). - - Also, quote for HTML the Subject: text printed for held - messages, otherwise messages with e.g. "Subject: </table>" could - royally screw page formatting. - - - Docstring fix bin/newlist to remove mention of "immediate" - argument (Thomas Wouters). - - - Fix for bin/update when PREFIX != VAR_PREFIX (SF bug #229794 -- - Thomas Wouters). - - - Bug fix release, namely fixes a buglet in bin/withlist affecting - the -l and -r flags; also a problem that can cause qrunner to - stop processing mail after disk-full events (SourceForge bug - 127199). - -2.0 final (21-Nov-2000) - - No changes from rc3. - -2.0 release candidate 3 (16-Nov-2000) - - - By popular demand, Reply-To: munging policy is now to always - override any Reply-To: header in the original message, if - reply_goes_to_list is set to "This list" or "Explicit Address" - - - bin/newlist given -q/--quiet flag instead of the <immediate> - positional argument - - - Hopefully last fix to DEFAULT_URL not ending in a slash - sensitivity - - - 2.0rc2 buglets fixed: - o newlist argument parsing - o updating with unlocked lists - o HyperArch.py traceback when there's no - Content-Transfer-Encoding: header - - - SourceForge bugs fixed: - 122358 (qmail-to-mailman.py listname case folding) - - - SourceForge patches applied: - 102373 (qmail-to-mailman.py listname case folding) - -2.0 release candidate 2 (10-Nov-2000) - - - Documentation updates: start in the doc/ directory. - - - bin/withlist accepts additional command line arguments when used - with the --run flag; bin/mmsitepass and bin/newlist accept - -h/--help flags - - - bin/newlist has a -o/--output flag to append /etc/aliases - suggestions to a specified file - - - SourceForge bugs fixed: - 116615 (README.BSD update), 117015 (duplicate messages on - moderated posts), 117548 (exception in HyperArch.py), 117682 - (typos), 121185 (vsnprintf signature), 121591 and 122017 - (bogus link after web unsubscribe), 121811 (`subscribe' in - Subject: doesn't get archived) - - - SourceForge patches applied: - 101812 (securelinux_fix.py contrib), 102097 (fix for bug - 117548), 102211 (additional args for withlist), 102268 (case - insensitive Content-Transfer-Encoding:) - -2.0 release candidate 1 (23-Oct-2000) - - - Bug fixes and security patches. - - - Better html rendition of articles in non us-ascii charsets - (Jeremy Hylton). See VERBATIM_ENCODING variable in - Defaults.py.in for customization. - -2.0 beta 6 (22-Sep-2000) - - - Building - o Tested with Python 1.5.2, Python 1.6, and Python 2.0 beta 1. - Conducted on RH Linux 6.1 only, but should work - cross-platform. - - o Configure now accepts --with-username, --with-groupname, - --with-var-prefix flags. See `configure --help' or the - INSTALL file for details. - - o Setting the CFLAGS environment variable before invoking - configure now works. - - o The icons are now copied into $prefix/icons at install time. - Patch by David Champion. - - - Standards - o Compliance with RFC 2369 (List-*: headers). Patch by - Darrell Fuhriman. List-ID: header is kept for historical - reasons. - - o Fixes by Jeremy Hylton to Pipermail in support of non-ASCII - charsets, based on the Content-Type: and encoded-words in - the original message. Mail headers are now decoded as per - RFC 2047. - - o Many more bounce formats are detected: Microsoft's SMTPSVC, - Compuserve, GroupWise, SMTP32, and the more generic - SimpleMatch (which catches lots of similar but slightly - different formats). - - - Defaults - o Email addresses can now be obscured in Pipermail archives by - setting mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS to 1 (obscuring - is turned off by default). Patch provided by Chris Snell. - - o The default NNTP host can now be set by editing - mm_cfg.DEFAULT_NNTP_HOST. Patch by David Champion. - - o The default archiving mode (public/private) can now be set - by editing mm_cfg.DEFAULT_ARCHIVE. Patch by Ted Cabeen. - - - Web UI - o The variable details pages in the administrators interface - is now `live', i.e. there's a submit button on the details - page. - - o A link to the administrative interface is placed in the - footer of the general user pages (authentication still - required, of course!) - - o The user options change results page has a link back to the - user's main page. - - o In the admindb page (for dealing with held postings), the - default forward address is now listname-owner instead of - listname-admin. This avoids bounce detection on the - forwarded message. - - - Miscellaneous - o Fixed config.db corruption problem when disk-full errors are - encountered. - - o Command line scripts accept list names case-insensitively. - - o bin/remove_members takes a -a flag to remove all members of - a list in one fell swoop. - - o List admin passwords must be non-empty. - - o Mailman generated passwords are slightly more mnemonic, and - shouldn't have confusing character selections (i.e. `i' - only, but no `1' or `l'). - - o Crossposting to two gated mailing lists should be fixed. - - o Many other bug fixes and minor web UI improvements. - -2.0 beta 5 (01-Aug-2000) - - - Bug fix release. This includes a fix for a small security hole - which could be exploited to gain mailman group access by a local - user (not a mail or web user). - - - As part of the fix for the "cookie reauthorization" bug, only - session cookies are used now. This means that administrative - and private archive cookies expire only when the browser session - is quit, however an explicit "Logout" button has been added. - -2.0 beta 4 (06-Jul-2000) - - - Bug fix release. - -2.0 beta 3 (29-Jun-2000) - - - Delivery mechanism (qrunner) refined to support immediate - queuing, queuing directly from MTA, and queuing on any error - along the delivery pipeline. This means 1) that huge lists - can't time out the MTA's program delivery channel; 2) it is much - harder to completely lose messages; 3) eventually, qrunner will - be elaborated to meter delivery to the MTA so as not to swamp - it. The tradeoff is in more disk I/O since every message coming - into the system (and most that are generated by the system) live - on disk for some part of their journey through Mailman. - - For now, see the Default.py variables QRUNNER_PROCESS_LIFETIME - and QRUNNER_MAX_MESSAGES for primitive resource management. - - The API to the pipeline handler modules has changed. See - Mailman/Handlers/HandlerAPI.py for details. - - - Revamped admindb web page: held messages are split into headers - and bodies so they are easier to vette; admins can now also - preserve a held message (for spam evidence gathering) or forward - the message to a specified email address; disposition of held - messages can be deferred; held messages have a more context - meaningful default rejection message. - - - Change to the semantics for `acceptable_aliases' list - configuration variable, based on suggestions by Harald Meland. - - - New mm_cfg.py variables NNTP_USERNAME and NNTP_PASSWORD can be - set on a site-wide basis if connection to your nntpd requires - authentication. - - - The list attribute `num_spawns' has been removed. The mm_cfg.py - variables MAX_SPAWNS, and DEFAULT_NUM_SPAWNS removed too. - - - LIST_LOCK_LIFETIME cranked to 5 hours and LIST_LOCK_TIMEOUT - shortened to 10 seconds. QRUNNER_LOCK_LIFETIME cranked up to 10 - hours. This should decrease the changes for bogus and harmful - lock breaking. - - - Resent-to: is now one of the headers checked for explicit - destinations. - - - Tons more bounce formats are recognized. The API to the bounce - modules has changed. - - - A rewritten LockFile module which should fix most (hopefully all) - bugs in the locking machinery. Many improvements suggested by - Thomas Wouters and Harald Meland. - - - Experimental support (disabled by default) for delivering SMTP - chunks to the MTA via multiple threads. Your Python executable - must have been compiled with thread support enabled, and you - must set MAX_DELIVERY_THREADS in mm_cfg.py. Note that this may - not improve your overall system performance. - - - Some changes and additions to scripts: bin/find_member now - supports a -w/--owner flag to match regexps against mailing list - owners; bin/find_member now supports multiple regexps; - cron/gate_news command line option changes; new script - bin/dumbdb for debugging purposes; bin/clone_member can now also - remove the old address and change change the list owner - addresses. - - - The News/Mail gateway admin page has a button that lets you do - an explicit catchup of the newsgroup. - - - The CVS repository has been moved out to SourceForge. For more - information, see the project summary at - - http://sourceforge.net/project/?group_id=103 - - - Lots 'o bug fixes and some performance improvements. - -2.0 beta 2 (07-Apr-2000) - - - Rewritten gate_news cron script which should be more efficient - and avoid race and locking problems. Each list now maintains - its own watermark, and when you use the admin CGI script to turn - on gating from Usenet->mail, an automatic mass catch up is done - to avoid flooding the mailing list. cron/gate_news's command - line interface has also changed. See its docstring for - details. - - - A new cron script called qrunner has been added to retry message - deliveries that fail because of temporary smtpd problems. - - - New command line script called bin/list_lists which does exactly - that: lists all the mailing lists on the system (much like the - listinfo CGI does). - - - bin/withlist is now directly executable, however if you want to - use python -i, you must still explicitly invoke it. - bin/withlist also now cleans up after itself by unlocking any - locked lists. It does NOT save any dirty lists though - you - must do this explicitly. - - - $prefix permissions (and all subdirs) must now be 02775. - bin/check_perms has been updated to fix all the subdir - permissions. - - - "make update" (a.k.a. bin/update) is run automatically when you - do a "make install" - - - The CGI driver script now puts information about the Python - environment into the logs/error file (but not the diagnostic web - page). - - - Bug fixes and some performance improvements - -2.0 beta 1 (19-Mar-2000) - - - Python 1.5.2 (or newer) is now required. - - - A new bundled auto-responder has been added. You can now - configure an autoresponse text for each list's primary - addresses: - - listname@yourhost.com -- the general posting address - listname-request@... -- the automated "request bot" address - listname-admin@... -- the human administrator address - - - The standard UI now includes three logos at the bottom of the - page: Dragon's Mailman logo, the Python Powered logo, and the - GNU logo. All point to their respective home pages. - - - It is now possible to set the Reply-To: field on lists to an - arbitrary address. NOTE: Reply-To: munging is generally - considered harmful! However for some read-only lists, it is - useful to direct replies to a parallel discussion list. - - - There is a new message delivery architecture which uses a - pipeline processor for incoming and internally generated - messages. Mailman no longer contains a bundled bulk-mailer; - instead message delivery is handled completely by the MTA. Most - MTAs give a high enough priority to connections from the - localhost that mail will not be lost because of system load, but - this is not guaranteed (or handled) by Mailman currently. Be - careful also if your smtpd is on a different host than the - Mailman host. In practice, mail lossage has not be observed. - - For this reason cron/run_queue is no longer needed (see the - UPGRADING file for details). - - Also, you can choose whether you want direct smtp delivery, or - delivery via the command line to a sendmail-compatible daemon. - You can also easily add your own delivery module. See - Mailman/Defaults.py for details. - - - A similar pipeline architecture for the parsing of bounce - messages has been added. Most common bounce formats are now - handled, including Qmail, Postfix, and DSN. It is now much - easier to add new bounce detectors. - - - The approval pending architecture has also been revamped. - Subscription requests and message posts waiting for admin - approval are no longer kept in the config.db file, but in a - separate requests.db file instead. - - - Finally made consistent the use of Sender:/From:/From_ in the - matching of headers for such things as member-post-only. Now, - if USE_ENVELOPE_SENDER is true, Sender: will always be chosen - over From:, however the default has been changed to - USE_ENVELOPE_SENDER false so that From: is always chosen over - Sender:. In both cases, if no header is found, From_ (i.e. the - envelope sender is used). Note that the variable is now - misnamed! Most people want From: matching anyway and any are - easily spoofable. - - - New scripts bin/move_list, bin/config_list - - - cron/upvolumes_yearly, cron/upvolumes_monthly, cron/archive, - cron/run_queue all removed. Edit your crontab if you used these - scripts. Other scripts removed: contact_transport, deliver, - dumb_deliver. - - - Several web UI improvements, especially in the admin page. - - - Remove X-pmrqc: headers to prevent return reciepts for Pegasus - mail users. - - - Security patch when using external archivers. - - - Honor "X-Archive: No" header by not putting this message in the - archive. - - - Changes to the log file format. - - - The usual bug fixes. - -1.1 (05-Nov-1999) - - - All GIFs removed. See http://www.gnu.org/philosophy/gif.html - for the reason why. - - - Improvements to the Pipermail archiver which make things faster. - Primary change is that the .txt files are not gzipped on every - posted message. Instead, use the new cron script `nightly_gzip' - to gzip the .txt file in batches (this means that the .txt file - will lag behind the on-line archives a little). - - - From the C drivers programs, Python is invoked with the -S - option. This tells Python to avoid importing the site module, - which can improve start up time of the Python process - considerably. Note that the command line script invocation has - not been changed. - - - New configuration variables PUBLIC_EXTERNAL_ARCHIVER and - PRIVATE_EXTERNAL_ARCHIVER which can contain a shell command - string for os.popen(). This can be used to invoke an external - archiver instead of the bundled Pipermail archiver. See - Defaults.py for details. - - - new script `bin/find_member' which can be used to search for a - member by regular expression. - - - More child processes are reaped, which should eliminate most - occurrences of zombie processes. - - - A few small miscellaneous bug fixes (including PR#99, PR#107) - and improvements to the file locking algorithms. - -1.0 (30-Jul-1999) - - - Configure script now allows $PREFIX (by default /home/mailman) - to be permissions 02755. Also, configure now tests for - vsnprintf() - - - Workaround, taken from GNU screen, for systems missing - vsnprintf() - - - Return-Receipt-To: and Disposition-Notification-To: headers are - always removed from posted messages (they can be used to troll - for list membership). - - - Workaround for MSIE4.01 (and possibly other versions) bug in the - handling of cookies. - - - A small collection of other bug fixes. - -1.0rc3 (10-Jul-1999) - - - new script bin/check_perms which checks (and optionally fixes) - the permissions and group ownerships of the files in your - Mailman installation. - - - Removed a bottleneck in the archiving code that was causing - performance problems on highly loaded servers. - - - The code that saves a list's state and configuration database - has been made more robust. - - - Additional exception handlers have been added in several places - to alleviate problems with Mailman bombing out when it really - would be better to print/log a helpful message. - - - The "password" mail command will now mail back the sender's - subscription password when given with no arguments. - - - The embarrassing subject-prefixing bug present in rc2 has been - fixed. - - - A small (but nice :) collection of other squashed bugs. - -1.0rc2 (14-Jun-1999) - - - A security flaw in the CGI cookie mechanisms was discovered -- - the Mailman-issued cookies were easily spoofable, implying that - e.g. admin access to all Mailman lists via the web interface - could be compromised. This flaw has now been fixed. - - - Handling of SMTP errors has been improved. - - - Both "Mass Subscription" via web admin interface and - bin/add_members have been greatly sped up. - - - autoconf check for syslog has been revamped, and is now verified - to work on SCO OpenServer 5. If syslog can't be found, the C - wrappers will compile, but without any syslog calls. - - - Various other bug fixes. - -1.0rc1 (04-May-1999) - - - There is a new Mailman logo, contributed by The Dragon De - Monsyne. Please read the INSTALL file for information about - installing the logo in a place your Web server can find it. - - - USE_ENVELOPE_SENDER is now set to 0 by default. Turning this on - caused problems for too many users; lists restricted to - member-only posts were not matching the addresses correctly. - - - A revamped bin/withlist to be a little more useful. - - - A revamped cron/mailpasswds which groups users by virtual hosts. - - - The usual assortment of bug fixes. - -1.0b11 (03-Apr-1999) - - - Bug fixes and improvements for case preservation of subscribed - addresses. The DATA_FILE_VERSION has been bumped to 14. - - - New script bin/withlist, useful for interactive debugging. - -1.0b10 (26-Mar-1999) - - - New script bin/sync_members which can be used to synchronize a - list's membership against a flat (e.g. sendmail :include: style) - file. - - - bin/add_members and bin/remove_members now accept addresses on - the command line with `-' as the value for the -d and -n - options. - - - Added variable USE_ENVELOPE_SENDER to Defaults.py for site-wide - configuration of address matching scheme. With this variable - set to true, the envelope sender (e.g. Unix "From_" header) is - used to match addresses, otherwise the From: header is used. - Envelope sender matching seems not to work on many systems. - This variable is currently defaulted to 1, but may change to 0 - for the final release. - - - Reorganization of the membership management admin page. Also - member addresses are linked to their options page. Only the - `General' category has the admin password change form. - - - Major reorganization of email command handling and responses. - `notmetoo' is the preferred email command instead of `norcv', - although the latter is still accepted as an argument. If more - than 5 errors are found in the message, command processing is - halted. - - - User options page now shows the user their case-preserved - subscribed address as well. - - - The usual assortment of bug fixes. - -1.0b9 (01-Mar-1999) - - - New bin scripts: clone_member, list_members, add_members (a - consolidation of convertlist and populate_new_list which have - been removed). - - - Two new readmes have been added: README.LINUX and README.QMAIL - - - New configure option --with-cgi-ext which can be used if your - Web server requires extensions on CGI scripts. The extension - must include a dot (e.g. --with-cgi-ext=".cgi"). - - - Many bug fixes, including the setgid problem that was causing - mail to be lost on some versions of Linux. - -1.0b8 (14-Jan-1999) - - - Bug fixes and workarounds for certain Linuxes. - - - Illegal addresses are no longer allowed to be subscribed, from - any interface. - -1.0b7 (31-Dec-1998) - - - Many, many bug fixes. Some performance improvements for large - lists. Some improvements in the Web interfaces. Some security - improvements. Improved compatibility with Python 1.5. - - - bin/convert_list and bin/populate_new_list have been replaced - by bin/add_members. - - - Admins can now get notification on subscriptions and - unsubscriptions. Posts are now logged. - - - The username portion of email addresses are now case-preserved - for delivery purposes. All other address comparisions are - case-insensitive. - - - New default SMTP_MAX_RCPTS that limits the number of "RCPT TO" - SMTP commands that can be given for a single message. Most - MTAs have some hard limit. - - - "Precedence: bulk" header and "List-id:" header are now added - to all outgoing messages. The latter is not added if the - message already has a "List-id:" header. See RFC 2046 and - draft-chandhok-listid-02 for details. - - - The standard (as of Python 1.5.2) smtplib.py is now used. - - - The install process now compiles all the .py files in the - installation. - - - Versions of the Mailman papers given at IPC7 and LISA-98 are - now included. - -1.0b6 (07-Nov-1998) - - - Archiving is (finally) back in. - - - Administrivia filter added. - - - Mail queue mechanism revamped with better concurrency control. - - - For recipients that have estmp MTAs, set delivery notification - status so that only delivery failure notices are sent out, - inhibiting 4 hour and N day warning notices. - - - Now expire old unconfirmed subscription requests, rather than - keeping them forever. - - - Added proposed standard List-Id: header, and our own - X-MailmanVersion header. - - - Prevent havoc from attempts to subscribe a list to itself. (!) - - - Refine mail command processing to prevent loops. - - - Pending subscription DB redone with better locking and cleaner - interface. - - - posters functionality expanded. - - - Subscription policy more flexible, sensible, and - site-configurable. - - - Various and sundry bug fixes. - -1.0b5 (27-Jul-1998) - - - New file locking that should be portable and work w/ NFS. - - - Better use of packages. - - - Better error logging and reporting. - - - Less startup overhead. - - - Various and sundry bug fixes. - - -1.0b4 (03-Jun-1998) - - - A configure script for easy installation (Barry Warsaw) - - - The ability to install Mailman to locations other than - /home/mailman (Barry Warsaw) - - - Use cookies on the admin pages (also hides admin pages from - others) (Scott Cotton) - - - Subscription requests send a request for confirmation, which may - be done by simply replying to the message (Scott Cotton) - - - Facilities for gating mail to a newsgroup, and for gating a - newsgroup to a mailing list (John Viega) - - - Contact the SMTP port instead of calling sendmail (primarily for - portability) (John Viega) - - - Changed all links on web pages to relative links where appropriate. - (John Viega) - - - Use MD5 if crypt is not available (John Viega) - - - Lots of fixing up of bounce handling (Ken Manheimer) - - - General UI polishing (Ken Manheimer) - - - mm_html: Make it prominent when the user's delivery is disabled - on his option page. (Ken Manheimer) - - - mallist:DeleteMember() Delete the option setings if any. (Ken - Manheimer) - -1.0b3 (03-May-1998) - - - mm_message:Deliverer.DeliverToList() added missing newline - between the headers and message body. Without it, any sequence - of initial body lines that _looked_ like headers ("Sir: Please - excuse my impertinence, but") got treated like headers. - - - Fixed typo which broke subscription acknowledgement message - (thanks to janne sinkonen for pointing this out promptly after - release). (Anyone who applied my intermediate patch will - probably see this one trigger patch'es reversed-patch - detector...) - - - Fixed cgi-wrapper.c so it doesn't segfault when invoked with - improper uid or gid, and generally wrappers are cleaned up a - bit. - - - Prevented delivery-failure notices for misdirected subscribe- - confirmation requests from bouncing back to the -request addr, - and then being treated as failing requests. - - Implemented two measures. Set the reply-to for the - confirmation- request to the -request addr, and the sender to be - the list admin. This way, bounces go to list admin instead of - to -request addr. (Using the errors-to header wasn't - sufficient. Thanks, barry, for pointing out the use of sender - here.) Second, ignore any mailcommands coming from postmaster - or non-login system type accounts (mailer-daemon, daemon, - postoffice, etc.) - - - Reenabled admin setting of web_page_url - crucial for having - lists use alternate names of a host that occupies multiple - addresses. - - - Fixed and refined admin-options help mechanism. Top-level visit - to general-category (where the "general" isn't in the URL) was - broken. New help presentation shows the same row that shows on - the actual options page. - - - cron/crontab.in crontab template had wrong name for senddigests. - - - Default digest format setting, as distributed, is now non-MIME, - on urging of reasoned voices asserting that there are still - enough bad MIME implementations in the world to be a nuisance to - too many users if MIME is the default. Sigh. - - - MIME digests now preserve the structure of MIME postings, - keeping attachments as attachments, etc. They also are more - structured in general. - - - Added README instructions explaining how to determine the right - UID and GID settings for the wrapper executables, and improved - some of the explanations about exploratory interaction - w/mailman. - - - Removed the constraint that subscribers have their domain - included in a static list in the code. We might want to - eventually reincorporate the check for the sake of a warning - message, to give a heads up to the subscriber, but try delivery - anyway... - - - Added missing titles to error docs. - - - Improved several help details, including particularly explaining - better how real_name setting is used. - - - Strengthened admonition against setting reply_goes_to_list. - - - Added X-BeenThere header to postings for the sake of prevention - of external mail loops. - - - Improved handling of bounced messages to better recognize - members address, and prevent duplicate attempts to react (which - could cause superfluous notices to administrator). - - - Added __delitem__ method to mm_message.OutgoingMessage, to fix - the intermediate patch posted just before this one. - - - Using keyword substitution format for more message text (ie, - "substituting %(such)s into text" % {'such': "something"}) to - make the substitutions less fragile and, presumably, easier to - debug. - - - Removed hardwired (and failure-prone) /tmp file logging from - answer.majordomo_mail, and generally spiffed up following janne - sinkkonen's lead. - -1.0b2 (13-Apr-1998) -1.0b1 (09-Apr-1998) - - Web pages much more polished - - Better organized, text more finely crafted - - Easier, more refined layout - - List info and admin interface overviews, enumerate all public lists - (via, e.g., http://www.python.org/mailman/listinfo - sans the - specific list) - - Admin interface broken into sections, with help elaboration for - complicated configuration options - - Mailing List Archives - - Integrated with a newer, *much* improved, external pipermail - to be - found at http://starship.skyport.net/crew/amk/maintained/pipermail.html - - Private archives protected with mailing list members passwords, - cookie-fied. - - Spam prevention - - New spam prevention measures catch most if not all spam without - operator intervention or general constraints on who can post to - list: - require_explicit_destination option imposes hold of any postings - that do not have the list name in any of the to or cc header - destination addresses. This catches the vast majority of random - spam. - Other options (forbidden_posters, bounce_matching_headers) provide - for filtering of known transgressors. - - Option obscure_addresses (default on) causes mailing list subscriber - lists on the web to be slightly mangled so they're not directly - recognizable as email address by web spiders, which might be - seeking targets for spammers. - - Site configuration arrangement organized - in mailman/mailman/modules: - - When installing, create a mailman/modules/mm_cfg.py (if there's not - one already there), using mm_cfg.py.dist as a template. - mm_default.py contains the distributed defaults, including - descriptions of the values. mm_cfg.py does a 'from mm_defaults.py - import *' to get the distributed defaults. Include settings in - mm_cfg.py for any values in mm_defaults.py that need to be - customized for your site, after the 'from .. import *'. - See mm_cfg.py.dist for more details. - - Logging - - Major operations (subscription, admin approval, bounce, - digestification, cgi script failure tracebacks) logged in files - using a reliable mechanism - - Wrapper executables log authentication complaints via syslog - - Wrappers - - All cgi-script wrapper executables combined in a single source, - easier to configure. (Mail and aliases wrappers separate.) - - List structure version migration - - Provision for automatic update of list structures when moving to a - new version of the system. See modules/versions.py. - - Code cleaning - - Many more module docstrings, __version__ settings, more function - docstrings. - - Most unqualified exception catches have been replaced with more - finely targeted catches, to avoid concealing bugs. - - Lotsa long lines wrapped (pet peeve:). - - Random details (not complete, sorry): - - make archival frequency a list option - - Option for daily digest dispatch, in addition to size threshhold - - make sure users only get one periodic password notifcation message for - all the lists they're on (repaired 1.0b1.1 varying-case mistake) - - Fix rmlist sans-argument bug causing deletion of all lists! - - doubled generated random passwords to four letters - - Cleaned lots and lots of notices - - Lots and lots of html page cleanup, including table-of-contents, etc - - Admin options sections - don't do the "if so" if the ensuing list - is empty - - Prevent list subject-prefix cascade - - Sources under CVS - - Various spam filters - implicit-destination, header-field - - Adjusted permissions for group access - - Prevent redundant subscription from redundant vetted requests - - Instituted centralize, robustish logging - - Wrapper sources use syslog for logging (john viega) - - Sorting of users done on presentation, not in list. - - Edit options - give an error for non-existent users, not an options page. - - Bounce handling - offer 'disable' option, instead of remove, and - never remove without notifying admin - - Moved subscribers off of listinfo (and made private lists visible - modulo authentication) - - Parameterize default digest headers and footers and create some - - Put titles on cgi result pages that do not get titles (all?) - - Option for immediate admin notifcation via email of pending - requests, as well as periodic - - Admin options web-page help - - Enabled grouped and cascading lists despite implicit-name constraint - - Changed subscribers list so it has its own script (roster) - - Welcome pages: http://www.python.org/mailman/{admin,listinfo}/ - -0.95 (25-Jan-1997) - - Fixed a bug in sending out digests added when adding disable mime option. - - Added an option to not notify about bounced posts. - - Added hook for pre-posting filters. These could be used to - auto-strip signatures. I'm using the feature to auto-strip footers - that are auto-generated by mail received from another mailing list. - -0.94 (22-Jan-1997) - - Made admin password work ubiquitously in place of a user password. - - Added an interface for getting / setting user options. - - Added user option to disable mime digests (digested people only) - - Added user option to not receive your own posts (nondigested people only) - - Added user option to ack posts - - Added user option to disable list delivery to their box. - - Added web interface to user options - - Config number of sendmail spawns on a per-list basis - - Fixed extra space at beginning of each message in digests... - - Handled comma separated emails in bounce messages... - - Added a FindUser() function to MailList. Used it where appropriate. - - Added mail interface to setting list options. - - Added name links to the templates options page - - Added an option so people can hide their names from the subscription list. - - Added an answer_majordomo_mail script for people switching... - -0.93 (18/20-Jan-1997) - - When delivering to list, don't call sendmail directly. Write to a file, - and then run the new deliver script, which forks and exits in the parent - immediately to avoid hanging when delivering mail for large lists, so that - large lists don't spend a lot of time locked. - - GetSender() no longer assumes that you don't have an owner-xxx address. - - Fixed unsubscribing via mail. - - Made subscribe via mail generate a password if you don't supply one. - - Added an option to clobber the date in the archives to the date the list - resent the post, so that the archive doesn't get mail from people sending - bad dates clumped up at the beginning or end. - - Added automatic error message processing as an option. Currently - logging to /tmp/bounce.log - - Changed archive to take a list as an argument, (the old way was broken) - - Remove (ignore) spaces in email addresses - - Allow user passwords to be case insensitive. - - Removed the cleanup script since it was now redundant. - - Fixed archives if there were no archives. - - Added a Lock() call to Load() and Create(). This fixes the - problem of loading then locking. - - Removed all occurances of Lock() except for the ones in mailing - list since creating a list - now implicitly locks it. - - Quote single periods in message text. - - Made bounce system handle digest users fairly. - -0.92 (13/16-Jan-1997) - - Added Lock and Unlock methods to list to ensure each operation is atomic - - Added a cmd that rms all files of a mailing list (but not the aliases) - - Fixed subscribing an unknown user@localhost (confirm this) - - Changed the sender to list-admin@... to ensure we avoid mail loops. - - check to make sure there are msgs to archive before calling pipermail. - - started using this w/ real mailing lists. - - Added a cron script that scours the maillog for User/Host unknown errs - - Sort membership lists - - Always display digest_is_default option - - Don't slam the TO list unless you're sending a digest. - - When making digest summaries, if missing sender name, use their email. - - Hacked in some protection against crappy dates in pipermail.py - - Made it so archive/digest volumes can go up monthly for large large lists. - - Number digest messages - - Add headers/footers to each message in digest for braindead mailers - - I removed some forgotten debug statements that caused server errors - when a CGI script sent mail. - - Removed loose_matches flag, since everything used it. - - Fixed a problem in pipermail if there was no From line. - - In upvolume_ scripts, remove INDEX files as we leave a volume. - - Threw a couple of scripts in bin for generating archives from majordomo's - digest-archives. I wouldn't recommend them for the layman, though, they - were meant to do a job quickly, not to be usable. - -0.91 (23-Dec-1996) - - broke code into mixins for managability - - tag parsing instead of lots of gsubs - - tweaked pipermail (see comments on pipermail header) - - templates are now on a per-list basis as intended. - - request over web that your password be emailed to you. - - option so that web subscriptions require email confirmation. - - wrote a first pass at an admin interface to configurable variables. - - made digests mime-compliant. - - added a FakeFile class that simulates enough of a file object on a - string of text to fool rfc822.Message in non-seek mode. - - changed OutgoingMessage not to require its args in constructor. - - added an admin request DB interface. - - clearly separated the internal name from the real name. - - replaced lots of ugly, redundant code w/ nice code. - (added Get...Email() interfaces, GetScriptURL, etc...) - - Wrote a lot of pretty html formatting functions / classes. - - Fleshed out the newlist command a lot. It now mails the new list - admin, and auto-updates the aliases file. - - Made multiple owners acceptable. - - Non-advertised lists, closed lists, max header length, max msg length - - Allowed editing templates from list admin pages. - - You can get to your info page from the web even if the list is closed. - - -Local Variables: -mode: indented-text -indent-tabs-mode: nil -End: diff --git a/src/attic/docs/man/add_members.1 b/src/attic/docs/man/add_members.1 deleted file mode 100644 index d442a2b66..000000000 --- a/src/attic/docs/man/add_members.1 +++ /dev/null @@ -1,60 +0,0 @@ -.\" -.\" GNU Mailman Manual -.\" -.\" add_members -.\" -.\" Documenter: Terri Oda -.\" terri (at) zone12.com -.\" Created: September 12, 2004 -.\" Last Updated: September 12, 2004 -.\" -.TH add_members 1 "September 12, 2004" "Mailman 2.1" "GNU Mailman Manual" -.\"===================================================================== -.SH NAME -add_members \- Remove members from a Mailman mailing list. -.\"===================================================================== -.SH SYNOPSIS -.B add_members -[-r \fIfile\fP] -[-d \fIfile\fP] -[-w <\fIy|n\fP>] -[-a <\fIy|n\fP>] -[-h] -\fIlistname\fP -.\"===================================================================== -.SH DESCRIPTION -.B add_members -adds members to a Mailman mailing list from the command line. -.PP -You must supply at least one of -r and -d options. At most one of the -files can be `-'. -.\"===================================================================== -.SH OPTIONS -.IP "--regular-members-file=\fIfile\fP, -r \fIfile\fP" -A file containing addresses of the members to be added, one -address per line. This list of people become non-digest -members. If file is `-', read addresses from stdin. Note that --n/--non-digest-members-file are deprecated synonyms for this option. -.IP "--digest-members-file=\fIfile\fP, -d \fIfile\fP" -Similar to above, but these people become digest members. -.IP "--welome-msg=<\fIy|n\fP>, -w <\fIy|n\fP>" -Set whether or not to send the list members a welcome message, -overriding whatever the list's `send_welcome_msg' setting is. -.IP "--admin-notify=<\fIy|n\fP>, -a <\fIy|n\fP>" -Set whether or not to send the list administrators a notification on -the success/failure of these subscriptions, overriding whatever the -list's `admin_notify_mchanges' setting is. -.IP \fIlistname\fP -The name of the list to which you wish to add members. -.\"===================================================================== -.SH SEE ALSO -.BR clone_member (1), -.BR find_member (1), -.BR list_members (1), -.BR remove_members (1) -.PP -The Mailman website: http://www.list.org -.\"===================================================================== -.SH AUTHOR -This man page was created by Terri Oda <terri (at) zone12.com>. -Use <mailman-developers@python.org> to contact the developers. diff --git a/src/attic/docs/man/check_db.1 b/src/attic/docs/man/check_db.1 deleted file mode 100644 index 28a3b8149..000000000 --- a/src/attic/docs/man/check_db.1 +++ /dev/null @@ -1,60 +0,0 @@ -.\" -.\" GNU Mailman Manual -.\" -.\" check_db -.\" -.\" Documenter: Terri Oda -.\" terri (at) zone12.com -.\" Created: September 14, 2004 -.\" Last Updated: September 14, 2004 -.\" -.TH check_db 1 "September 14, 2004" "Mailman 2.1" "GNU Mailman Manual" -.\"===================================================================== -.SH NAME -check_db \- Check a Mailman mailing list's config database file for integrity. -.\"===================================================================== -.SH SYNOPSIS -.B check_db -[-a] -[-v] -[-h] -[\fIlistname\fP [\fIlistname\fP ...]] -.\"===================================================================== -.SH DESCRIPTION -.B check_db -checks a list's config database file for integrity. -.PP -All of the following files are checked: -.RS - config.pck - config.pck.last - config.db - config.db.last - config.safety -.RE -.PP -It's okay if any of these are missing. config.pck and config.pck.last are -pickled versions of the config database file for 2.1a3 and beyond. config.db -and config.db.last are used in all earlier versions, and these are Python -marshals. config.safety is a pickle written by 2.1a3 and beyond when the -primary config.pck file could not be read. -.\"===================================================================== -.SH OPTIONS -.IP "--all, -a" -Check the databases for all lists. Otherwise only the lists named on -the command line are checked. -.IP "--verbose, -v" -Verbose output. The state of every tested file is printed. -Otherwise only corrupt files are displayed. -.IP "--help, -h" -Print help text and exit. -.\"===================================================================== -.SH SEE ALSO -.BR check_perms (1), -.BR transcheck (1) -.PP -The Mailman website: http://www.list.org -.\"===================================================================== -.SH AUTHOR -This man page was created by Terri Oda <terri (at) zone12.com>. -Use <mailman-developers@python.org> to contact the developers. diff --git a/src/attic/docs/man/check_perms.1 b/src/attic/docs/man/check_perms.1 deleted file mode 100644 index 76966a87c..000000000 --- a/src/attic/docs/man/check_perms.1 +++ /dev/null @@ -1,46 +0,0 @@ -.\" -.\" GNU Mailman Manual -.\" -.\" check_perms -.\" -.\" Documenter: Terri Oda -.\" terri (at) zone12.com -.\" Created: September 14, 2004 -.\" Last Updated: September 14, 2004 -.\" -.TH check_perms 1 "September 14, 2004" "Mailman 2.1" "GNU Mailman Manual" -.\"===================================================================== -.SH NAME -check_perms \- Check the permissions for the Mailman installation. -.\"===================================================================== -.SH SYNOPSIS -.B check_perms -[-f] -[-v] -[-h] -.\"===================================================================== -.SH DESCRIPTION -.B check_perms -checks the permissions for the Mailman installation. -.PP -With no arguments, just check and report all the files that have bogus -permissions or group ownership. With -f (and run as root), fix all the -permission problems found. With -v be verbose. -.\"===================================================================== -.SH OPTIONS -.IP "-f" -Run as root and fix all the permission problems found. -.IP "-v" -Verbose output. -.IP "--help, -h" -Print help message and exit. -.\"===================================================================== -.SH SEE ALSO -.BR check_db (1), -.BR transcheck (1) -.PP -The Mailman website: http://www.list.org -.\"===================================================================== -.SH AUTHOR -This man page was created by Terri Oda <terri (at) zone12.com>. -Use <mailman-developers@python.org> to contact the developers. diff --git a/src/attic/docs/man/clone_member.1 b/src/attic/docs/man/clone_member.1 deleted file mode 100644 index 35148f6af..000000000 --- a/src/attic/docs/man/clone_member.1 +++ /dev/null @@ -1,71 +0,0 @@ -.\" -.\" GNU Mailman Manual -.\" -.\" clone_member -.\" -.\" Documenter: Terri Oda -.\" terri (at) zone12.com -.\" Created: September 14, 2004 -.\" Last Updated: September 14, 2004 -.\" -.TH clone_member 1 "September 14, 2004" "Mailman 2.1" "GNU Mailman Manual" -.\"===================================================================== -.SH NAME -clone_member \- Clone a Mailman mailing list member address. -.\"===================================================================== -.SH SYNOPSIS -.B clone_member -[-l \fIlistname\fP] -[-r] -[-a] -[-q] -[-n] -[-h] -\fIfromoldaddr\fP \fItonewaddr\fP -.\"===================================================================== -.SH DESCRIPTION -.B clone_member -clones a member address. -.PP -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. -.PP -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. -.\"===================================================================== -.SH OPTIONS -.IP "--listname=\fIlistname\fP, -l \fIlistname\fP" -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. -.IP "--remove, -r" -Remove the old address from the mailing list after it's been cloned. -.IP "--admin, -a" -Scan the list admin addresses for the old address, and clone or change -them too. -.IP "--quiet, -q" -Do the modifications quietly. -.IP "--nomodify, -n" -Print what would be done, but don't actually do it. Inhibits the ---quiet flag. -.IP "--help, -h" -Print help message and exit. -.IP \fIfromoldaddr\fP -(`from old address') is the old address of the user. -.IP \fItonewaddr\fP -(`to new address') is the new address of the user. -.\"===================================================================== -.SH SEE ALSO -.BR add_member (1), -.BR find_member (1), -.BR list_members (1), -.BR remove_members (1) -.PP -The Mailman website: http://www.list.org -.\"===================================================================== -.SH AUTHOR -This man page was created by Terri Oda <terri (at) zone12.com>. -Use <mailman-developers@python.org> to contact the developers. diff --git a/src/attic/docs/man/find_member.1 b/src/attic/docs/man/find_member.1 deleted file mode 100644 index 9d76bf5db..000000000 --- a/src/attic/docs/man/find_member.1 +++ /dev/null @@ -1,64 +0,0 @@ -add.\" -.\" GNU Mailman Manual -.\" -.\" find_member -.\" -.\" Documenter: Terri Oda -.\" terri (at) zone12.com -.\" Created: September 13, 2004 -.\" Last Updated: September 13, 2004 -.\" -.TH find_member 1 "September 13, 2004" "Mailman 2.1" "GNU Mailman Manual" -.\"===================================================================== -.SH NAME -find_member \- Find all Mailman mailing lists to which a given address is -subscribed. -.\"===================================================================== -.SH SYNOPSIS -.B find_member -[-l \fIlistname\fP] -[-x \fIlistname\fP] -[-w] -[-h] -\fIregex\fP -.\"===================================================================== -.SH DESCRIPTION -.B find_member -finds all Mailman mailing lists to which a member's address is subscribed. -.PP -The interaction between -l and -x is as follows. If any -l option is given -then only the named list will be included in the search. If any -x option is -given but no -l option is given, then all lists will be search except those -specifically excluded. -.PP -Regular expression syntax is Perl5-like, using the Python re module. Complete -specifications are at: -.PP -http://www.python.org/doc/current/lib/module-re.html -.PP -Address matches are case-insensitive, but case-preserved addresses are -displayed. -.\"===================================================================== -.SH OPTIONS -.IP "--listname=\fIlistname\fP, -l \fIlistname\fP" -Include only the named list in the search. -.IP "--exclude=\fIlistname\fP, -x \fIlistname\fP" -Exclude the named list from the search. -.IP "--owners, -w" -Search list owners as well as members. -.IP "--help, -h" -Print help message and exit. -.IP \fIregex\fP -A Python regular expression to match. -.\"===================================================================== -.SH SEE ALSO -.BR add_members (1), -.BR clone_member (1), -.BR list_members (1), -.BR remove_members (1) -.PP -The Mailman website: http://www.list.org -.\"===================================================================== -.SH AUTHOR -This man page was created by Terri Oda <terri (at) zone12.com>. -Use <mailman-developers@python.org> to contact the developers. diff --git a/src/attic/docs/man/list_members.1 b/src/attic/docs/man/list_members.1 deleted file mode 100644 index cbf338a40..000000000 --- a/src/attic/docs/man/list_members.1 +++ /dev/null @@ -1,78 +0,0 @@ -add.\" -.\" GNU Mailman Manual -.\" -.\" list_members -.\" -.\" Documenter: Terri Oda -.\" terri (at) zone12.com -.\" Created: September 13, 2004 -.\" Last Updated: September 13, 2004 -.\" -.TH list_member 1 "September 13, 2004" "Mailman 2.1" "GNU Mailman Manual" -.\"===================================================================== -.SH NAME -list_member \- List all the members of a Mailman mailing list. -.\"===================================================================== -.SH SYNOPSIS -.B list_member -[-o \fIfile\fP] -[-r] -[-d [\fIkind\fP]] -[-n [\fIwhy\fI]] -[-f] -[-p] -[-i] -[-u] -[-h] -\fIlistname\fP -.\"===================================================================== -.SH DESCRIPTION -.B list_member -lists all members of a mailing list. -.PP -Note that if neither -r or -d is supplied, both regular members are printed -first, followed by digest members, but no indication is given as to address -status. -.\"===================================================================== -.SH OPTIONS -.IP "--output \fIfile\fP, -o \fIfile\fP" -Write output to specified file instead of standard out. -.IP "--regular, -r" -Print just the regular (non-digest) members. -.IP "--digest[=\fIkind\fP], -d [\fIkind\fP]" -Print just the digest members. Optional argument can be "mime" or -"plain" which prints just the digest members receiving that kind of -digest. -.IP "--nomail[=\fIwhy\fP], -n[\fIwhy\fP]" -Print the members that have delivery disabled. Optional argument can -be "byadmin", "byuser", "bybounce", or "unknown" which prints just the -users who have delivery disabled for that reason. It can also be -"enabled" which prints just those member for whom delivery is -enabled. -.IP "--fullnames, -f" -Include the full names in the output. -.IP "--preserve, -p" -Output member addresses case preserved the way they were added to the -list. Otherwise, addresses are printed in all lowercase. -.IP "--invalid, -i" -Print only the addresses in the membership list that are invalid. -Ignores -r, -d, -n. -.IP "--unicode, -u" -Print addresses which are stored as Unicode objects instead of normal -string objects. Ignores -r, -d, -n. -.IP "--help, -h" -Print help message and exit. -.IP "\fIlistname\fP" -The name of the mailing list to use. -.\"===================================================================== -.SH SEE ALSO -.BR add_members (1), -.BR clone_member (1), -.BR find_member (1), -.BR remove_members (1) -.PP -The Mailman website: http://www.list.org -.\"===================================================================== -.SH AUTHOR -This man page was created by Terri Oda <terri (at) zone12.com>. -Use <mailman-developers@python.org> to contact the developers. diff --git a/src/attic/docs/man/remove_members.1 b/src/attic/docs/man/remove_members.1 deleted file mode 100644 index 69ed545d6..000000000 --- a/src/attic/docs/man/remove_members.1 +++ /dev/null @@ -1,63 +0,0 @@ -add.\" -.\" GNU Mailman Manual -.\" -.\" remove_members -.\" -.\" Documenter: Terri Oda -.\" terri (at) zone12.com -.\" Created: September 13, 2004 -.\" Last Updated: September 13, 2004 -.\" -.TH remove_members 1 "September 13, 2004" "Mailman 2.1" "GNU Mailman Manual" -.\"===================================================================== -.SH NAME -remove_members \- Remove members from a Mailman mailing list. -.\"===================================================================== -.SH SYNOPSIS -.B remove_members -[-f \fIfile\fP] -[-a] -[--fromall] -[-n] -[-N] -[-h] -[\fIlistname\fP] -[\fIaddr1\fP ...] -.\"===================================================================== -.SH DESCRIPTION -.B remove_members -removes members from a Mailman mailing list from the command line. -.\"===================================================================== -.SH OPTIONS -.IP "---file=\fIfile\fP, -f \fIfile\fP" -Remove member addresses found in the given file. If file is -`-', read stdin. -.IP "--all, -a" -Remove all members of the mailing list. -(mutually exclusive with --fromall) -.IP "--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. -.IP "--nouserack, -n" -Don't send the admin acknowledgements. If not specified, the list -default value is used. -.IP "--help, -h" -Print help message and exit. -.IP \fPlistname\fI -The name of the mailing list to use. -.IP \fPaddr1\fI ... -Additional addresses to remove. -.\"===================================================================== -.SH SEE ALSO -.BR add_members (1), -.BR clone_member (1), -.BR find_member (1), -.BR list_members (1) -.PP -The Mailman website: http://www.list.org -.\"===================================================================== -.SH AUTHOR -This man page was created by Terri Oda <terri (at) zone12.com>. -Use <mailman-developers@python.org> to contact the developers. diff --git a/src/attic/docs/man/sync_members.1 b/src/attic/docs/man/sync_members.1 deleted file mode 100644 index b185ae3c1..000000000 --- a/src/attic/docs/man/sync_members.1 +++ /dev/null @@ -1,81 +0,0 @@ -add.\" -.\" GNU Mailman Manual -.\" -.\" list_members -.\" -.\" Documenter: Terri Oda -.\" terri (at) zone12.com -.\" Created: September 13, 2004 -.\" Last Updated: September 13, 2004 -.\" -.TH sync_members 1 "September 13, 2004" "Mailman 2.1" "GNU Mailman Manual" -.\"===================================================================== -.SH NAME -sync_members \- Synchronize a Mailman mailing list's membership with a flat file. -.\"===================================================================== -.SH SYNOPSIS -.B sync_members -[-n] -[-w <\fIyes|no\fP>] -[-g <\fIyes|no\fP>] -[-d <\fIyes|no\fP>] -[-a <\fIyes|no\fP>] -[-h] --f \fIfilename\fP -\fIlistname\fP -.\"===================================================================== -.SH DESCRIPTION -.B sync_members -synchronizes a Mailman mailing list's membership with a flat file. -.PP -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. -.\"===================================================================== -.SH OPTIONS -.IP "--no-change -n" -Don't actually make the changes. Instead, print out what would be -done to the list. -.IP "-welcome-msg[=<\fIyes|no\fP>], -w[=<\fIyes|no\fP>]" -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. -.IP "--goodbye-msg[=<\fIyes|no\fP>], -g[=<\fIyes|no\fP>]" -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. -.IP "--digest[=<\fIyes|no\fP>], -d[=<\fIyes|no\fP>]" -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. -.IP "--notifyadmin[=<\fIyes|no\fP>], -a[=<\fIyes|no\fP>]" -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. -.IP "--file <\fIfilename | -\fp>, -f <\fIfilename | -\fP>" -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. -.IP "--help, -h" -Print help message. -.IP \fIlistname\fP -Required. This specifies the list to synchronize. -.\"===================================================================== -.SH SEE ALSO -.BR add_members (1), -.BR clone_member (1), -.BR list_members (1), -.BR remove_members (1) -.PP -The Mailman website: http://www.list.org -.\"===================================================================== -.SH AUTHOR -This man page was created by Terri Oda <terri (at) zone12.com>. -Use <mailman-developers@python.org> to contact the developers. diff --git a/src/attic/docs/man/transcheck.1 b/src/attic/docs/man/transcheck.1 deleted file mode 100644 index 6e16a10f8..000000000 --- a/src/attic/docs/man/transcheck.1 +++ /dev/null @@ -1,41 +0,0 @@ -.\" -.\" GNU Mailman Manual -.\" -.\" transcheck -.\" -.\" Documenter: Terri Oda -.\" terri (at) zone12.com -.\" Created: September 18, 2004 -.\" Last Updated: September 18, 2004 -.\" -.TH transcheck 1 "September 18, 2004" "Mailman 2.1" "GNU Mailman Manual" -.\"===================================================================== -.SH NAME -transcheck \- Check a given Mailman translation -.\"===================================================================== -.SH SYNOPSIS -.B transcheck -[-q] -\fIlang\fP -.\"===================================================================== -.SH DESCRIPTION -.B transcheck -checks 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. -.\"===================================================================== -.SH OPTIONS -.IP "-q" -Asks for a brief summary. -.IP "\fIlang\fP" -Your country code. (e.g. 'it' for Italy) -.\"===================================================================== -.SH SEE ALSO -.BR check_perms (1), -.BR check_db (1) -.PP -The Mailman website: http://www.list.org -.\"===================================================================== -.SH AUTHOR -This man page was created by Terri Oda <terri (at) zone12.com>. -Use <mailman-developers@python.org> to contact the developers. diff --git a/src/attic/docs/posting-flow-chart.ps b/src/attic/docs/posting-flow-chart.ps deleted file mode 100644 index e8d47e27c..000000000 --- a/src/attic/docs/posting-flow-chart.ps +++ /dev/null @@ -1,735 +0,0 @@ -%!PS-Adobe-2.0 -%%Title: posting-flow-chart -%%Creator: Dia v0.86 -%%CreationDate: Mon Oct 15 13:46:55 2001 -%%For: a user -%%DocumentPaperSizes: A4 -%%Orientation: Landscape -%%BeginSetup -%%EndSetup -%%EndComments -%%BeginProlog -[ /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quoteright -/parenleft /parenright /asterisk /plus /comma /hyphen /period /slash /zero /one -/two /three /four /five /six /seven /eight /nine /colon /semicolon -/less /equal /greater /question /at /A /B /C /D /E -/F /G /H /I /J /K /L /M /N /O -/P /Q /R /S /T /U /V /W /X /Y -/Z /bracketleft /backslash /bracketright /asciicircum /underscore /quoteleft /a /b /c -/d /e /f /g /h /i /j /k /l /m -/n /o /p /q /r /s /t /u /v /w -/x /y /z /braceleft /bar /braceright /asciitilde /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /exclamdown /cent /sterling /currency /yen /brokenbar /section /dieresis /copyright -/ordfeminine /guillemotleft /logicalnot /hyphen /registered /macron /degree /plusminus /twosuperior /threesuperior -/acute /mu /paragraph /periodcentered /cedilla /onesuperior /ordmasculine /guillemotright /onequarter /onehalf -/threequarters /questiondown /Agrave /Aacute /Acircumflex /Atilde /Adieresis /Aring /AE /Ccedilla -/Egrave /Eacute /Ecircumflex /Edieresis /Igrave /Iacute /Icircumflex /Idieresis /Eth /Ntilde -/Ograve /Oacute /Ocircumflex /Otilde /Odieresis /multiply /Oslash /Ugrave /Uacute /Ucircumflex -/Udieresis /Yacute /Thorn /germandbls /agrave /aacute /acircumflex /atilde /adieresis /aring -/ae /ccedilla /egrave /eacute /ecircumflex /edieresis /igrave /iacute /icircumflex /idieresis -/eth /ntilde /ograve /oacute /ocircumflex /otilde /odieresis /divide /oslash /ugrave -/uacute /ucircumflex /udieresis /yacute /thorn /ydieresis] /isolatin1encoding exch def -/Times-Roman-latin1 - /Times-Roman findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/Times-Italic-latin1 - /Times-Italic findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/Times-Bold-latin1 - /Times-Bold findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/Times-BoldItalic-latin1 - /Times-BoldItalic findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/AvantGarde-Book-latin1 - /AvantGarde-Book findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/AvantGarde-BookOblique-latin1 - /AvantGarde-BookOblique findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/AvantGarde-Demi-latin1 - /AvantGarde-Demi findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/AvantGarde-DemiOblique-latin1 - /AvantGarde-DemiOblique findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/Bookman-Light-latin1 - /Bookman-Light findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/Bookman-LightItalic-latin1 - /Bookman-LightItalic findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/Bookman-Demi-latin1 - /Bookman-Demi findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/Bookman-DemiItalic-latin1 - /Bookman-DemiItalic findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/Courier-latin1 - /Courier findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/Courier-Oblique-latin1 - /Courier-Oblique findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/Courier-Bold-latin1 - /Courier-Bold findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/Courier-BoldOblique-latin1 - /Courier-BoldOblique findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/Helvetica-latin1 - /Helvetica findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/Helvetica-Oblique-latin1 - /Helvetica-Oblique findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/Helvetica-Bold-latin1 - /Helvetica-Bold findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/Helvetica-BoldOblique-latin1 - /Helvetica-BoldOblique findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/Helvetica-Narrow-latin1 - /Helvetica-Narrow findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/Helvetica-Narrow-Oblique-latin1 - /Helvetica-Narrow-Oblique findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/Helvetica-Narrow-Bold-latin1 - /Helvetica-Narrow-Bold findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/Helvetica-Narrow-BoldOblique-latin1 - /Helvetica-Narrow-BoldOblique findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/NewCenturySchoolbook-Roman-latin1 - /NewCenturySchoolbook-Roman findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/NewCenturySchoolbook-Italic-latin1 - /NewCenturySchoolbook-Italic findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/NewCenturySchoolbook-Bold-latin1 - /NewCenturySchoolbook-Bold findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/NewCenturySchoolbook-BoldItalic-latin1 - /NewCenturySchoolbook-BoldItalic findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/Palatino-Roman-latin1 - /Palatino-Roman findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/Palatino-Italic-latin1 - /Palatino-Italic findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/Palatino-Bold-latin1 - /Palatino-Bold findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/Palatino-BoldItalic-latin1 - /Palatino-BoldItalic findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/Symbol-latin1 - /Symbol findfont -definefont pop -/ZapfChancery-MediumItalic-latin1 - /ZapfChancery-MediumItalic findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/ZapfDingbats-latin1 - /ZapfDingbats findfont - dup length dict begin - {1 index /FID ne {def} {pop pop} ifelse} forall - /Encoding isolatin1encoding def - currentdict end -definefont pop -/cp {closepath} bind def -/c {curveto} bind def -/f {fill} bind def -/a {arc} bind def -/ef {eofill} bind def -/ex {exch} bind def -/gr {grestore} bind def -/gs {gsave} bind def -/sa {save} bind def -/rs {restore} bind def -/l {lineto} bind def -/m {moveto} bind def -/rm {rmoveto} bind def -/n {newpath} bind def -/s {stroke} bind def -/sh {show} bind def -/slc {setlinecap} bind def -/slj {setlinejoin} bind def -/slw {setlinewidth} bind def -/srgb {setrgbcolor} bind def -/rot {rotate} bind def -/sc {scale} bind def -/sd {setdash} bind def -/ff {findfont} bind def -/sf {setfont} bind def -/scf {scalefont} bind def -/sw {stringwidth pop} bind def -/tr {translate} bind def - -/ellipsedict 8 dict def -ellipsedict /mtrx matrix put -/ellipse -{ ellipsedict begin - /endangle exch def - /startangle exch def - /yrad exch def - /xrad exch def - /y exch def - /x exch def /savematrix mtrx currentmatrix def - x y tr xrad yrad sc - 0 0 1 startangle endangle arc - savematrix setmatrix - end -} def - -/mergeprocs { -dup length -3 -1 roll -dup -length -dup -5 1 roll -3 -1 roll -add -array cvx -dup -3 -1 roll -0 exch -putinterval -dup -4 2 roll -putinterval -} bind def -%%EndProlog - - -%%Page: 1 1 -gs -90 rotate -10.504277 -10.504277 scale --7.390052 13.596868 translate -n 15.000000 -5.986920 m 79.927437 -5.986920 l 79.927437 35.463012 l 15.000000 35.463012 l 15.000000 -5.986920 l clip -0.100000 slw -[] 0 sd -[] 0 sd -0 slc -0 slj -0 slc -0 slj -[] 0 sd -1.000000 1.000000 1.000000 srgb -n 28.288577 -5.936920 m 34.143683 -5.936920 l 34.952105 -5.936920 35.607460 -5.489205 35.607460 -4.936920 c 35.607460 -4.384635 34.952105 -3.936920 34.143683 -3.936920 c 28.288577 -3.936920 l 27.480155 -3.936920 26.824800 -4.384635 26.824800 -4.936920 c 26.824800 -5.489205 27.480155 -5.936920 28.288577 -5.936920 c f -0.000000 0.000000 0.000000 srgb -n 28.288577 -5.936920 m 34.143683 -5.936920 l 34.952105 -5.936920 35.607460 -5.489205 35.607460 -4.936920 c 35.607460 -4.384635 34.952105 -3.936920 34.143683 -3.936920 c 28.288577 -3.936920 l 27.480155 -3.936920 26.824800 -4.384635 26.824800 -4.936920 c 26.824800 -5.489205 27.480155 -5.936920 28.288577 -5.936920 c s -/Courier-latin1 ff 0.800000 scf sf -0.000000 0.000000 0.000000 srgb -(post a msg) dup sw 2 div 31.216130 ex sub -4.742230 m gs 1 -1 sc sh gr -1.000000 1.000000 1.000000 srgb -n 31.228705 0.448229 m 34.632611 3.852135 l 31.228705 7.256041 l 27.824799 3.852135 l f -0.100000 slw -[] 0 sd -[] 0 sd -0 slj -0.000000 0.000000 0.000000 srgb -n 31.228705 0.448229 m 34.632611 3.852135 l 31.228705 7.256041 l 27.824799 3.852135 l cp s -/Courier-latin1 ff 0.800000 scf sf -0.000000 0.000000 0.000000 srgb -(is a) dup sw 2 div 31.228705 ex sub 3.646825 m gs 1 -1 sc sh gr -0.000000 0.000000 0.000000 srgb -(member?) dup sw 2 div 31.228705 ex sub 4.446825 m gs 1 -1 sc sh gr -0.100000 slw -[] 0 sd -[] 0 sd -0 slc -0.000000 0.000000 0.000000 srgb -n 31.228700 0.448231 m 31.216100 -3.936920 l s -0 slj -0.000000 0.000000 0.000000 srgb -n 30.826403 -0.350616 m 31.228700 0.448231 l 31.626400 -0.352915 l f -1.000000 1.000000 1.000000 srgb -n 22.333205 8.807639 m 25.737111 12.211545 l 22.333205 15.615451 l 18.929299 12.211545 l f -0.100000 slw -[] 0 sd -[] 0 sd -0 slj -0.000000 0.000000 0.000000 srgb -n 22.333205 8.807639 m 25.737111 12.211545 l 22.333205 15.615451 l 18.929299 12.211545 l cp s -/Courier-latin1 ff 0.800000 scf sf -0.000000 0.000000 0.000000 srgb -(mod bit) dup sw 2 div 22.333205 ex sub 12.006235 m gs 1 -1 sc sh gr -0.000000 0.000000 0.000000 srgb -(set?) dup sw 2 div 22.333205 ex sub 12.806235 m gs 1 -1 sc sh gr -0.100000 slw -[] 0 sd -[] 0 sd -0 slc -0.000000 0.000000 0.000000 srgb -n 22.333200 8.807640 m 27.824800 3.852140 l s -0 slj -0.000000 0.000000 0.000000 srgb -n 22.659157 7.974722 m 22.333200 8.807640 l 23.195108 8.568655 l f -/Courier-latin1 ff 0.800000 scf sf -0.000000 0.000000 0.000000 srgb -() dup sw 2 div 15.000000 ex sub 5.000000 m gs 1 -1 sc sh gr -/Courier-latin1 ff 0.800000 scf sf -0.000000 0.000000 0.000000 srgb -(yes) dup sw 2 div 28.675700 ex sub 3.001160 m gs 1 -1 sc sh gr -/Courier-latin1 ff 0.800000 scf sf -0.000000 0.000000 0.000000 srgb -(yes) dup sw 2 div 19.780300 ex sub 11.360600 m gs 1 -1 sc sh gr -/Courier-latin1 ff 0.800000 scf sf -0.000000 0.000000 0.000000 srgb -(no) dup sw 2 div 23.184200 ex sub 14.764500 m gs 1 -1 sc sh gr -0.100000 slw -[] 0 sd -[] 0 sd -0 slc -0.000000 0.000000 0.000000 srgb -n 34.632600 3.852140 m 40.261205 8.139600 l s -0 slj -0.000000 0.000000 0.000000 srgb -n 39.382424 7.973037 m 40.261205 8.139600 l 39.867187 7.336637 l f -/Courier-latin1 ff 0.800000 scf sf -0.000000 0.000000 0.000000 srgb -(no) dup sw 2 div 33.781600 ex sub 3.001160 m gs 1 -1 sc sh gr -1.000000 1.000000 1.000000 srgb -n 40.261205 8.139600 m 43.907510 11.959285 l 40.261205 15.778970 l 36.614900 11.959285 l f -0.100000 slw -[] 0 sd -[] 0 sd -0 slj -0.000000 0.000000 0.000000 srgb -n 40.261205 8.139600 m 43.907510 11.959285 l 40.261205 15.778970 l 36.614900 11.959285 l cp s -/Courier-latin1 ff 0.800000 scf sf -0.000000 0.000000 0.000000 srgb -(explicit) dup sw 2 div 40.261205 ex sub 11.753975 m gs 1 -1 sc sh gr -0.000000 0.000000 0.000000 srgb -(accept?) dup sw 2 div 40.261205 ex sub 12.553975 m gs 1 -1 sc sh gr -/Courier-latin1 ff 0.800000 scf sf -0.000000 0.000000 0.000000 srgb -(yes) dup sw 2 div 39.349629 ex sub 14.824049 m gs 1 -1 sc sh gr -1.000000 1.000000 1.000000 srgb -n 59.913105 8.346249 m 63.559411 11.992555 l 59.913105 15.638861 l 56.266799 11.992555 l f -0.100000 slw -[] 0 sd -[] 0 sd -0 slj -0.000000 0.000000 0.000000 srgb -n 59.913105 8.346249 m 63.559411 11.992555 l 59.913105 15.638861 l 56.266799 11.992555 l cp s -/Courier-latin1 ff 0.800000 scf sf -0.000000 0.000000 0.000000 srgb -(explicit) dup sw 2 div 59.913105 ex sub 11.787245 m gs 1 -1 sc sh gr -0.000000 0.000000 0.000000 srgb -(reject?) dup sw 2 div 59.913105 ex sub 12.587245 m gs 1 -1 sc sh gr -/Courier-latin1 ff 0.800000 scf sf -0.000000 0.000000 0.000000 srgb -(yes) dup sw 2 div 59.001528 ex sub 14.727284 m gs 1 -1 sc sh gr -0.100000 slw -[] 0 sd -[] 0 sd -0 slc -0.000000 0.000000 0.000000 srgb -n 54.324011 11.922575 m 56.266799 11.992555 l s -0 slj -0.000000 0.000000 0.000000 srgb -n 55.452919 12.363498 m 56.266799 11.992555 l 55.481716 11.564017 l f -/Courier-latin1 ff 0.800000 scf sf -0.000000 0.000000 0.000000 srgb -(no) dup sw 2 div 42.995934 ex sub 11.004364 m gs 1 -1 sc sh gr -1.000000 1.000000 1.000000 srgb -n 71.066405 8.389389 m 74.712711 12.035695 l 71.066405 15.682001 l 67.420099 12.035695 l f -0.100000 slw -[] 0 sd -[] 0 sd -0 slj -0.000000 0.000000 0.000000 srgb -n 71.066405 8.389389 m 74.712711 12.035695 l 71.066405 15.682001 l 67.420099 12.035695 l cp s -/Courier-latin1 ff 0.800000 scf sf -0.000000 0.000000 0.000000 srgb -(explicit) dup sw 2 div 71.066405 ex sub 11.830385 m gs 1 -1 sc sh gr -0.000000 0.000000 0.000000 srgb -(discard?) dup sw 2 div 71.066405 ex sub 12.630385 m gs 1 -1 sc sh gr -0.100000 slw -[] 0 sd -[] 0 sd -0 slc -0.000000 0.000000 0.000000 srgb -n 63.559411 11.992555 m 67.420100 12.035700 l s -0 slj -0.000000 0.000000 0.000000 srgb -n 66.615680 12.426735 m 67.420100 12.035700 l 66.624620 11.626785 l f -/Courier-latin1 ff 0.800000 scf sf -0.000000 0.000000 0.000000 srgb -(no) dup sw 2 div 62.647834 ex sub 11.080979 m gs 1 -1 sc sh gr -0.100000 slw -[] 0 sd -[] 0 sd -0 slc -0.000000 0.000000 0.000000 srgb -n 71.066400 15.682000 m 71.055000 19.434500 l s -0 slj -0.000000 0.000000 0.000000 srgb -n 70.657432 18.633289 m 71.055000 19.434500 l 71.457429 18.635719 l f -/Courier-latin1 ff 0.800000 scf sf -0.000000 0.000000 0.000000 srgb -(yes) dup sw 2 div 70.154800 ex sub 14.770400 m gs 1 -1 sc sh gr -/Courier-latin1 ff 0.800000 scf sf -0.000000 0.000000 0.000000 srgb -(no) dup sw 2 div 73.801100 ex sub 11.124100 m gs 1 -1 sc sh gr -0.100000 slw -[] 0 sd -[] 0 sd -0 slc -0.000000 0.000000 0.000000 srgb -n 59.913105 15.638861 m 59.907500 19.455400 l s -0 slj -0.000000 0.000000 0.000000 srgb -n 59.508675 18.654813 m 59.907500 19.455400 l 60.308674 18.655988 l f -0.100000 slw -[] 0 sd -[] 0 sd -0 slj -0 slc -0.000000 0.000000 0.000000 srgb -n 22.333200 15.615400 m 22.362400 21.044900 l 27.166900 21.015055 l s -0 slj -0.000000 0.000000 0.000000 srgb -n 26.369400 21.420017 m 27.166900 21.015055 l 26.364431 20.620032 l f -0.100000 slw -[] 0 sd -[] 0 sd -0 slc -0 slj -0 slc -0 slj -[] 0 sd -1.000000 1.000000 1.000000 srgb -n 28.954977 29.425200 m 34.810083 29.425200 l 35.618505 29.425200 36.273860 29.964050 36.273860 30.628755 c 36.273860 31.293460 35.618505 31.832310 34.810083 31.832310 c 28.954977 31.832310 l 28.146555 31.832310 27.491200 31.293460 27.491200 30.628755 c 27.491200 29.964050 28.146555 29.425200 28.954977 29.425200 c f -0.000000 0.000000 0.000000 srgb -n 28.954977 29.425200 m 34.810083 29.425200 l 35.618505 29.425200 36.273860 29.964050 36.273860 30.628755 c 36.273860 31.293460 35.618505 31.832310 34.810083 31.832310 c 28.954977 31.832310 l 28.146555 31.832310 27.491200 31.293460 27.491200 30.628755 c 27.491200 29.964050 28.146555 29.425200 28.954977 29.425200 c s -/Courier-latin1 ff 0.800000 scf sf -0.000000 0.000000 0.000000 srgb -(hold for) dup sw 2 div 31.882530 ex sub 30.423445 m gs 1 -1 sc sh gr -0.000000 0.000000 0.000000 srgb -(moderation) dup sw 2 div 31.882530 ex sub 31.223445 m gs 1 -1 sc sh gr -0.100000 slw -[] 0 sd -[] 0 sd -0 slc -0 slj -0 slc -0 slj -[] 0 sd -1.000000 1.000000 1.000000 srgb -n 28.666900 19.811500 m 34.666900 19.811500 l 35.495328 19.811500 36.166900 20.350350 36.166900 21.015055 c 36.166900 21.679760 35.495328 22.218610 34.666900 22.218610 c 28.666900 22.218610 l 27.838472 22.218610 27.166900 21.679760 27.166900 21.015055 c 27.166900 20.350350 27.838472 19.811500 28.666900 19.811500 c f -0.000000 0.000000 0.000000 srgb -n 28.666900 19.811500 m 34.666900 19.811500 l 35.495328 19.811500 36.166900 20.350350 36.166900 21.015055 c 36.166900 21.679760 35.495328 22.218610 34.666900 22.218610 c 28.666900 22.218610 l 27.838472 22.218610 27.166900 21.679760 27.166900 21.015055 c 27.166900 20.350350 27.838472 19.811500 28.666900 19.811500 c s -/Courier-latin1 ff 0.800000 scf sf -0.000000 0.000000 0.000000 srgb -(pass thru) dup sw 2 div 31.666900 ex sub 21.209745 m gs 1 -1 sc sh gr -0.100000 slw -[] 0 sd -[] 0 sd -0 slc -0 slj -0 slc -0 slj -[] 0 sd -1.000000 1.000000 1.000000 srgb -n 57.222377 19.455400 m 62.592683 19.455400 l 63.334168 19.455400 63.935260 19.903115 63.935260 20.455400 c 63.935260 21.007685 63.334168 21.455400 62.592683 21.455400 c 57.222377 21.455400 l 56.480892 21.455400 55.879800 21.007685 55.879800 20.455400 c 55.879800 19.903115 56.480892 19.455400 57.222377 19.455400 c f -0.000000 0.000000 0.000000 srgb -n 57.222377 19.455400 m 62.592683 19.455400 l 63.334168 19.455400 63.935260 19.903115 63.935260 20.455400 c 63.935260 21.007685 63.334168 21.455400 62.592683 21.455400 c 57.222377 21.455400 l 56.480892 21.455400 55.879800 21.007685 55.879800 20.455400 c 55.879800 19.903115 56.480892 19.455400 57.222377 19.455400 c s -/Courier-latin1 ff 0.800000 scf sf -0.000000 0.000000 0.000000 srgb -(bounce it) dup sw 2 div 59.907530 ex sub 20.650090 m gs 1 -1 sc sh gr -0.100000 slw -[] 0 sd -[] 0 sd -0 slc -0 slj -0 slc -0 slj -[] 0 sd -1.000000 1.000000 1.000000 srgb -n 67.885077 19.434500 m 74.224983 19.434500 l 75.100342 19.434500 75.809960 19.882215 75.809960 20.434500 c 75.809960 20.986785 75.100342 21.434500 74.224983 21.434500 c 67.885077 21.434500 l 67.009718 21.434500 66.300100 20.986785 66.300100 20.434500 c 66.300100 19.882215 67.009718 19.434500 67.885077 19.434500 c f -0.000000 0.000000 0.000000 srgb -n 67.885077 19.434500 m 74.224983 19.434500 l 75.100342 19.434500 75.809960 19.882215 75.809960 20.434500 c 75.809960 20.986785 75.100342 21.434500 74.224983 21.434500 c 67.885077 21.434500 l 67.009718 21.434500 66.300100 20.986785 66.300100 20.434500 c 66.300100 19.882215 67.009718 19.434500 67.885077 19.434500 c s -/Courier-latin1 ff 0.800000 scf sf -0.000000 0.000000 0.000000 srgb -(discard it) dup sw 2 div 71.055030 ex sub 20.629190 m gs 1 -1 sc sh gr -0.100000 slw -[] 0 sd -[] 0 sd -0 slj -0 slc -0.000000 0.000000 0.000000 srgb -n 40.261205 15.778970 m 40.184200 21.044900 l 36.166900 21.015055 l s -0 slj -0.000000 0.000000 0.000000 srgb -n 36.969849 20.621009 m 36.166900 21.015055 l 36.963906 21.420987 l f -1.000000 1.000000 1.000000 srgb -n 65.017305 25.865999 m 69.790811 30.639505 l 65.017305 35.413011 l 60.243799 30.639505 l f -0.100000 slw -[] 0 sd -[] 0 sd -0 slj -0.000000 0.000000 0.000000 srgb -n 65.017305 25.865999 m 69.790811 30.639505 l 65.017305 35.413011 l 60.243799 30.639505 l cp s -/Courier-latin1 ff 0.800000 scf sf -0.000000 0.000000 0.000000 srgb -(generic) dup sw 2 div 65.017305 ex sub 30.034195 m gs 1 -1 sc sh gr -0.000000 0.000000 0.000000 srgb -(non-member) dup sw 2 div 65.017305 ex sub 30.834195 m gs 1 -1 sc sh gr -0.000000 0.000000 0.000000 srgb -(disposition) dup sw 2 div 65.017305 ex sub 31.634195 m gs 1 -1 sc sh gr -0.100000 slw -[] 0 sd -[] 0 sd -0 slj -0 slc -0.000000 0.000000 0.000000 srgb -n 74.712700 12.035700 m 77.310000 12.068400 l 77.310000 30.668100 l 69.790800 30.639500 l s -0 slj -0.000000 0.000000 0.000000 srgb -n 70.592316 30.242546 m 69.790800 30.639500 l 70.589273 31.042540 l f -/Courier-latin1 ff 0.800000 scf sf -0.000000 0.000000 0.000000 srgb -(discard) dup sw 2 div 68.632800 ex sub 24.246900 m gs 1 -1 sc sh gr -0.100000 slw -[] 0 sd -[] 0 sd -0 slc -0.000000 0.000000 0.000000 srgb -n 66.210600 27.059400 m 71.055000 21.434500 l s -0 slj -0.000000 0.000000 0.000000 srgb -n 70.836024 22.301708 m 71.055000 21.434500 l 70.229848 21.779644 l f -0.100000 slw -[] 0 sd -[] 0 sd -0 slc -0.000000 0.000000 0.000000 srgb -n 63.823900 27.059400 m 59.907500 21.455400 l s -0 slj -0.000000 0.000000 0.000000 srgb -n 60.693636 21.882004 m 59.907500 21.455400 l 60.037899 22.340271 l f -/Courier-latin1 ff 0.800000 scf sf -0.000000 0.000000 0.000000 srgb -(reject) dup sw 2 div 61.865700 ex sub 24.257400 m gs 1 -1 sc sh gr -0.100000 slw -[] 0 sd -[] 0 sd -0 slc -0.000000 0.000000 0.000000 srgb -n 60.243800 30.639500 m 36.273860 30.628755 l s -0 slj -0.000000 0.000000 0.000000 srgb -n 37.074039 30.229114 m 36.273860 30.628755 l 37.073681 31.029114 l f -/Courier-latin1 ff 0.800000 scf sf -0.000000 0.000000 0.000000 srgb -(hold) dup sw 2 div 48.258830 ex sub 30.634128 m gs 1 -1 sc sh gr -0.100000 slw -[] 0 sd -[] 0 sd -0 slj -0 slc -0.000000 0.000000 0.000000 srgb -n 18.929299 12.211545 m 17.707519 12.274592 l 17.707519 30.574592 l 27.491200 30.628755 l s -0 slj -0.000000 0.000000 0.000000 srgb -n 26.688998 31.024320 m 27.491200 30.628755 l 26.693427 30.224332 l f -0.100000 slw -[] 0 sd -[] 0 sd -0 slc -0.000000 0.000000 0.000000 srgb -n 61.437100 29.446100 m 35.727550 21.866089 l s -0 slj -0.000000 0.000000 0.000000 srgb -n 36.608013 21.708655 m 35.727550 21.866089 l 36.381775 22.475998 l f -/Courier-latin1 ff 0.800000 scf sf -0.000000 0.000000 0.000000 srgb -(accept) dup sw 2 div 48.582325 ex sub 25.656094 m gs 1 -1 sc sh gr -1.000000 1.000000 1.000000 srgb -n 50.677705 8.276269 m 54.324011 11.922575 l 50.677705 15.568881 l 47.031399 11.922575 l f -0.100000 slw -[] 0 sd -[] 0 sd -0 slj -0.000000 0.000000 0.000000 srgb -n 50.677705 8.276269 m 54.324011 11.922575 l 50.677705 15.568881 l 47.031399 11.922575 l cp s -/Courier-latin1 ff 0.800000 scf sf -0.000000 0.000000 0.000000 srgb -(explicit) dup sw 2 div 50.677705 ex sub 11.717265 m gs 1 -1 sc sh gr -0.000000 0.000000 0.000000 srgb -(hold?) dup sw 2 div 50.677705 ex sub 12.517265 m gs 1 -1 sc sh gr -/Courier-latin1 ff 0.800000 scf sf -0.000000 0.000000 0.000000 srgb -(yes) dup sw 2 div 49.766128 ex sub 14.657304 m gs 1 -1 sc sh gr -/Courier-latin1 ff 0.800000 scf sf -0.000000 0.000000 0.000000 srgb -(no) dup sw 2 div 53.412434 ex sub 11.010998 m gs 1 -1 sc sh gr -0.100000 slw -[] 0 sd -[] 0 sd -0 slj -0 slc -0.000000 0.000000 0.000000 srgb -n 50.677705 15.568881 m 50.723859 19.674592 l 35.845120 29.777721 l s -0 slj -0.000000 0.000000 0.000000 srgb -n 36.282254 28.997392 m 35.845120 29.777721 l 36.731664 29.659231 l f -0.100000 slw -[] 0 sd -[] 0 sd -0 slc -0.000000 0.000000 0.000000 srgb -n 43.907510 11.959285 m 47.031399 11.922575 l s -0 slj -0.000000 0.000000 0.000000 srgb -n 46.236154 12.331948 m 47.031399 11.922575 l 46.226754 11.532003 l f -/Courier-latin1 ff 2.000000 scf sf -0.000000 0.000000 0.000000 srgb -(Sender Moderation Flowchart) dup sw 2 div 57.443403 ex sub 1.624592 m gs 1 -1 sc sh gr -gr -showpage - |
