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