aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Mailman/Archiver/pipermail.py4
-rw-r--r--Mailman/Bouncers/SimpleWarning.py6
-rw-r--r--Mailman/Cgi/admin.py6
-rw-r--r--Mailman/Cgi/admindb.py4
-rw-r--r--Mailman/Cgi/confirm.py7
-rw-r--r--Mailman/Cgi/edithtml.py4
-rw-r--r--Mailman/Cgi/listinfo.py4
-rw-r--r--Mailman/Cgi/options.py4
-rwxr-xr-xMailman/Cgi/private.py4
-rw-r--r--Mailman/Cgi/rmlist.py4
-rw-r--r--Mailman/Cgi/roster.py4
-rwxr-xr-xMailman/Cgi/subscribe.py4
-rwxr-xr-xMailman/Defaults.py.in28
-rw-r--r--Mailman/Gui/General.py42
-rw-r--r--Mailman/Gui/Privacy.py36
-rw-r--r--Mailman/Handlers/CleanseDKIM.py17
-rwxr-xr-xMailman/Handlers/CookHeaders.py22
-rw-r--r--Mailman/Handlers/Moderate.py41
-rw-r--r--Mailman/Handlers/WrapMessage.py5
-rwxr-xr-xMailman/ListAdmin.py18
-rw-r--r--Mailman/MTA/Postfix.py10
-rwxr-xr-xMailman/MailList.py2
-rw-r--r--Mailman/Mailbox.py10
-rw-r--r--Mailman/SecurityManager.py6
-rw-r--r--Mailman/Utils.py45
-rw-r--r--Mailman/Version.py4
-rwxr-xr-xMailman/versions.py2
-rwxr-xr-xNEWS92
-rwxr-xr-xbin/sync_members8
-rw-r--r--contrib/README.post_count14
-rw-r--r--contrib/mmdsr7
-rwxr-xr-xcontrib/post_count39
-rwxr-xr-xcron/nightly_gzip6
-rw-r--r--templates/fr/listinfo.html1
-rw-r--r--tests/bounces/simple_40.txt29
-rwxr-xr-xtests/onebounce.py6
-rwxr-xr-xtests/test_bounces.py3
-rw-r--r--tests/test_handlers.py9
38 files changed, 423 insertions, 134 deletions
diff --git a/Mailman/Archiver/pipermail.py b/Mailman/Archiver/pipermail.py
index 939602ba..9c54bbd9 100644
--- a/Mailman/Archiver/pipermail.py
+++ b/Mailman/Archiver/pipermail.py
@@ -552,6 +552,8 @@ class T:
if start is None:
start = 0
counter = 0
+ if start:
+ mbox.skipping(True)
while counter < start:
try:
m = mbox.next()
@@ -560,6 +562,8 @@ class T:
if m is None:
return
counter += 1
+ if start:
+ mbox.skipping(False)
while 1:
try:
pos = input.tell()
diff --git a/Mailman/Bouncers/SimpleWarning.py b/Mailman/Bouncers/SimpleWarning.py
index ab8d6aa2..4f5958ea 100644
--- a/Mailman/Bouncers/SimpleWarning.py
+++ b/Mailman/Bouncers/SimpleWarning.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2001-2009 by the Free Software Foundation, Inc.
+# Copyright (C) 2001-2013 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
@@ -57,6 +57,10 @@ patterns = [
(_c('We will continue to try to deliver'),
_c('.+'),
_c('(?P<addr>.+)')),
+ # kundenserver.de
+ (_c('not yet been delivered'),
+ _c('No action is required on your part'),
+ _c(r'\s*<?(?P<addr>\S+@[^>\s]+)>?\s*')),
# Next one goes here...
]
diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py
index b5c19544..6fd57733 100644
--- a/Mailman/Cgi/admin.py
+++ b/Mailman/Cgi/admin.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2012 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2014 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
@@ -77,8 +77,8 @@ def main():
# Send this with a 404 status.
print 'Status: 404 Not Found'
admin_overview(_('No such list <em>%(safelistname)s</em>'))
- syslog('error', 'admin.py access for non-existent list: %s',
- listname)
+ syslog('error', 'admin: No such list "%s": %s\n',
+ listname, e)
return
# Now that we know what list has been requested, all subsequent admin
# pages are shown in that list's preferred language.
diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py
index d3350ea7..67ae7756 100644
--- a/Mailman/Cgi/admindb.py
+++ b/Mailman/Cgi/admindb.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2013 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2014 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
@@ -114,7 +114,7 @@ def main():
# Send this with a 404 status.
print 'Status: 404 Not Found'
handle_no_list(_('No such list <em>%(safelistname)s</em>'))
- syslog('error', 'No such list "%s": %s\n', listname, e)
+ syslog('error', 'admindb: No such list "%s": %s\n', listname, e)
return
# Now that we know which list to use, set the system's language to it.
diff --git a/Mailman/Cgi/confirm.py b/Mailman/Cgi/confirm.py
index 607f1784..bb529318 100644
--- a/Mailman/Cgi/confirm.py
+++ b/Mailman/Cgi/confirm.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2001-2011 by the Free Software Foundation, Inc.
+# Copyright (C) 2001-2014 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
@@ -64,7 +64,7 @@ def main():
# Send this with a 404 status.
print 'Status: 404 Not Found'
print doc.Format()
- syslog('error', 'No such list "%s": %s', listname, e)
+ syslog('error', 'confirm: No such list "%s": %s', listname, e)
return
# Set the language for the list
@@ -258,7 +258,8 @@ def subscription_prompt(mlist, doc, cookie, userdesc):
<p>Or hit <em>Cancel my subscription request</em> if you no longer want to
subscribe to this list.""") + '<p><hr>'
- if mlist.subscribe_policy in (2, 3):
+ if (mlist.subscribe_policy in (2, 3) and
+ not getattr(userdesc, 'invitation', False)):
# Confirmation is required
result = _("""Your confirmation is required in order to continue with
the subscription request to the mailing list <em>%(listname)s</em>.
diff --git a/Mailman/Cgi/edithtml.py b/Mailman/Cgi/edithtml.py
index ee1ccd04..d01ff889 100644
--- a/Mailman/Cgi/edithtml.py
+++ b/Mailman/Cgi/edithtml.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2011 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2014 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
@@ -72,7 +72,7 @@ def main():
# Send this with a 404 status.
print 'Status: 404 Not Found'
print doc.Format()
- syslog('error', 'No such list "%s": %s', listname, e)
+ syslog('error', 'edithtml: No such list "%s": %s', listname, e)
return
# Now that we have a valid list, set the language to its default
diff --git a/Mailman/Cgi/listinfo.py b/Mailman/Cgi/listinfo.py
index 5fbaaaf3..8396b37d 100644
--- a/Mailman/Cgi/listinfo.py
+++ b/Mailman/Cgi/listinfo.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2012 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2014 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
@@ -53,7 +53,7 @@ def main():
# Send this with a 404 status.
print 'Status: 404 Not Found'
listinfo_overview(_('No such list <em>%(safelistname)s</em>'))
- syslog('error', 'No such list "%s": %s', listname, e)
+ syslog('error', 'listinfo: No such list "%s": %s', listname, e)
return
# See if the user want to see this page in other language
diff --git a/Mailman/Cgi/options.py b/Mailman/Cgi/options.py
index 9a2389a9..853a3922 100644
--- a/Mailman/Cgi/options.py
+++ b/Mailman/Cgi/options.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2011 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2014 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
@@ -81,7 +81,7 @@ def main():
# Send this with a 404 status.
print 'Status: 404 Not Found'
print doc.Format()
- syslog('error', 'No such list "%s": %s\n', listname, e)
+ syslog('error', 'options: No such list "%s": %s\n', listname, e)
return
# The total contents of the user's response
diff --git a/Mailman/Cgi/private.py b/Mailman/Cgi/private.py
index 6eb40943..36cacee4 100755
--- a/Mailman/Cgi/private.py
+++ b/Mailman/Cgi/private.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2012 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2014 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
@@ -111,7 +111,7 @@ def main():
# Send this with a 404 status.
print 'Status: 404 Not Found'
print doc.Format()
- syslog('error', 'No such list "%s": %s\n', listname, e)
+ syslog('error', 'private: No such list "%s": %s\n', listname, e)
return
i18n.set_language(mlist.preferred_language)
diff --git a/Mailman/Cgi/rmlist.py b/Mailman/Cgi/rmlist.py
index 8988dc42..da802b99 100644
--- a/Mailman/Cgi/rmlist.py
+++ b/Mailman/Cgi/rmlist.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2001-2010 by the Free Software Foundation, Inc.
+# Copyright (C) 2001-2014 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
@@ -71,7 +71,7 @@ def main():
# Send this with a 404 status.
print 'Status: 404 Not Found'
print doc.Format()
- syslog('error', 'No such list "%s": %s\n', listname, e)
+ syslog('error', 'rmlist: No such list "%s": %s\n', listname, e)
return
# Now that we have a valid mailing list, set the language
diff --git a/Mailman/Cgi/roster.py b/Mailman/Cgi/roster.py
index 6260c973..6c64925b 100644
--- a/Mailman/Cgi/roster.py
+++ b/Mailman/Cgi/roster.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2011 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2014 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
@@ -57,7 +57,7 @@ def main():
# Send this with a 404 status.
print 'Status: 404 Not Found'
error_page(_('No such list <em>%(safelistname)s</em>'))
- syslog('error', 'roster: no such list "%s": %s', listname, e)
+ syslog('error', 'roster: No such list "%s": %s', listname, e)
return
cgidata = cgi.FieldStorage()
diff --git a/Mailman/Cgi/subscribe.py b/Mailman/Cgi/subscribe.py
index d6b1517d..a1b8434f 100755
--- a/Mailman/Cgi/subscribe.py
+++ b/Mailman/Cgi/subscribe.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2012 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2014 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
@@ -64,7 +64,7 @@ def main():
# Send this with a 404 status.
print 'Status: 404 Not Found'
print doc.Format()
- syslog('error', 'No such list "%s": %s\n', listname, e)
+ syslog('error', 'subscribe: No such list "%s": %s\n', listname, e)
return
# See if the form data has a preferred language set, in which case, use it
diff --git a/Mailman/Defaults.py.in b/Mailman/Defaults.py.in
index a7bf31e5..c04ba8fa 100755
--- a/Mailman/Defaults.py.in
+++ b/Mailman/Defaults.py.in
@@ -1,6 +1,6 @@
# -*- python -*-
-# Copyright (C) 1998-2013 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2014 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
@@ -108,10 +108,6 @@ ALLOW_SITE_ADMIN_COOKIES = No
# expire that many seconds following their last use.
AUTHENTICATION_COOKIE_LIFETIME = 0
-# The following must be set to Yes to enable the 'author is list' feature.
-# See DEFAULT_FROM_IS_LIST below.
-ALLOW_FROM_IS_LIST = No
-
# Form lifetime is set against Cross Site Request Forgery.
FORM_LIFETIME = hours(1)
@@ -561,8 +557,8 @@ NNTP_REWRITE_DUPLICATE_HEADERS = [
# broken and even if the outgoing message is resigned. However, some sites
# may wish to remove these headers. Possible values and meanings are:
# No, 0, False -> do not remove headers.
-# 1 -> remove headers only if the list's from_is_list setting is 1.
-# Yes, 2, True -> always remove headers.
+# Yes, 1, True -> remove headers only if the list's from_is_list setting is 1.
+# 2 -> always remove headers.
REMOVE_DKIM_HEADERS = No
# All `normal' messages which are delivered to the entire list membership go
@@ -1064,6 +1060,20 @@ DEFAULT_DEFAULT_MEMBER_MODERATION = No
# moderators?
DEFAULT_FORWARD_AUTO_DISCARDS = Yes
+# Shall dmarc_moderation_action be applied to messages From: domains with
+# a DMARC policy of quarantine as well as reject?
+DMARC_QUARANTINE_MODERATION_ACTION = Yes
+
+# Default action for posts whose From: address domain has a DMARC policy of
+# reject or quarantine. See DEFAULT_FROM_IS_LIST below. Whatever is set as
+# the default here precludes the list owner from setting a lower value.
+# 0 = Accept
+# 1 = Munge From
+# 2 = Wrap Message
+# 3 = Reject
+# 4 = Discard
+DEFAULT_DMARC_MODERATION_ACTION = 0
+
# What shold happen to non-member posts which are do not match explicit
# non-member actions?
# 0 = Accept
@@ -1101,7 +1111,9 @@ DEFAULT_SEND_WELCOME_MSG = Yes
# Send goodbye messages to unsubscribed members?
DEFAULT_SEND_GOODBYE_MSG = Yes
-# The following is a three way setting.
+# The following is a three way setting. It sets the default for the list's
+# from_is_list policy which is applied to all posts except those for which a
+# dmarc_moderation_action other than accept applies.
# 0 -> Do not rewrite the From: or wrap the message.
# 1 -> Rewrite the From: header of posts replacing the posters address with
# that of the list. Also see REMOVE_DKIM_HEADERS above.
diff --git a/Mailman/Gui/General.py b/Mailman/Gui/General.py
index 24bc009a..a917642c 100644
--- a/Mailman/Gui/General.py
+++ b/Mailman/Gui/General.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2001-2013 by the Free Software Foundation, Inc.
+# Copyright (C) 2001-2014 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
@@ -153,25 +153,27 @@ class General(GUIBase):
directive. eg.; [listname %%d] -> [listname 123]
(listname %%05d) -> (listname 00123)
""")),
- ]
- if mm_cfg.ALLOW_FROM_IS_LIST:
- rtn.append(
- ('from_is_list', mm_cfg.Radio,
- (_('No'), _('Mung From'), _('Wrap Message')), 0,
- _("""Replace the sender with the list address to conform with
- policies like ADSP and DMARC. It replaces the poster's
- address in the From: header with the list address and adds the
- poster to the Reply-To: header, but the anonymous_list and
- Reply-To: header munging settings below take priority. If
- setting this to Yes, it is advised to set the MTA to DKIM sign
- all emails.""") +
- _("""<br>If this is set to Wrap Message, just wrap the message
- in an outer message From: the list with Content-Type:
- message/rfc822."""))
- )
-
- rtn.extend([
+ ('from_is_list', mm_cfg.Radio,
+ (_('No'), _('Munge From'), _('Wrap Message')), 0,
+ _("""Replace the sender with the list address to conform with
+ policies like DMARC."""),
+ _("""Replace the sender with the list address to conform with
+ policies like ADSP and DMARC. It replaces the poster's
+ address in the From: header with the list address and adds the
+ poster to the Reply-To: header, but the anonymous_list and
+ Reply-To: header munging settings below take priority. If
+ setting this to Yes, it is advised to set the MTA to DKIM sign
+ all emails.""") +
+ _("""<p>If this is set to Wrap Message, just wrap the message
+ in an outer message From: the list with Content-Type:
+ message/rfc822.""") +
+ _("""<p>If <a
+ href="?VARHELP=privacy/sender/dmarc_moderation_action">
+ dmarc_moderation_action</a> applies to this message with an
+ action other than Accept, that action rather than this is
+ applied""")),
+
('anonymous_list', mm_cfg.Radio, (_('No'), _('Yes')), 0,
_("""Hide the sender of a message, replacing it with the list
address (Removes From, Sender and Reply-To fields)""")),
@@ -392,7 +394,7 @@ class General(GUIBase):
useful for selecting among alternative names of a host that has
multiple addresses.""")),
- ])
+ ]
if mm_cfg.ALLOW_RFC2369_OVERRIDES:
rtn.append(
diff --git a/Mailman/Gui/Privacy.py b/Mailman/Gui/Privacy.py
index 90560bff..5dcc3f48 100644
--- a/Mailman/Gui/Privacy.py
+++ b/Mailman/Gui/Privacy.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2001-2008 by the Free Software Foundation, Inc.
+# Copyright (C) 2001-2014 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
@@ -158,6 +158,11 @@ class Privacy(GUIBase):
]
adminurl = mlist.GetScriptURL('admin', absolute=1)
+
+ if mm_cfg.DMARC_QUARANTINE_MODERATION_ACTION:
+ quarantine = _('/Quarantine')
+ else:
+ quarantine = ''
sender_rtn = [
_("""When a message is posted to the list, a series of
moderation steps are taken to decide whether a moderator must
@@ -236,11 +241,18 @@ class Privacy(GUIBase):
be sent to moderated members who post to this list.""")),
('dmarc_moderation_action', mm_cfg.Radio,
- (_('Accept'), _('Hold'), _('Reject'), _('Discard')), 0,
+ (_('Accept'), _('Wrap Message'), _('Munge From'), _('Reject'),
+ _('Discard')), 0,
_("""Action to take when anyone posts to the
- list from a domain with a DMARC Reject/Quarantine Policy."""),
- _("""<ul><li><b>Hold</b> -- this holds the message for approval
- by the list moderators.
+ list from a domain with a DMARC Reject%(quarantine)s Policy."""),
+
+ _("""<ul><li><b>Wrap Message</b> -- applies the <a
+ href="?VARHELP=general/from_is_list">from_is _list Wrap
+ Message</a> transformation to these messages.
+
+ <p><li><b>Munge From</b> -- applies the <a
+ href="?VARHELP=general/from_is_list">from_is _list Munge From</a>
+ transformation to these messages.
<p><li><b>Reject</b> -- this automatically rejects the message by
sending a bounce notice to the post's author. The text of the
@@ -250,11 +262,16 @@ class Privacy(GUIBase):
<p><li><b>Discard</b> -- this simply discards the message, with
no notice sent to the post's author.
- </ul>""")),
+ </ul>
+
+ <p>This setting takes precedence over the <a
+ href="?VARHELP=general/from_is_list"> from_is_list</a> setting
+ if the message is From: an affected domain and the setting is
+ other than Accept.""")),
('dmarc_moderation_notice', mm_cfg.Text, (10, WIDTH), 1,
_("""Text to include in any
- <a href="?VARHELP/privacy/sender/dmarc_moderation_action"
+ <a href="?VARHELP=privacy/sender/dmarc_moderation_action"
>rejection notice</a> to
be sent to anyone who posts to this list from a domain
with DMARC Reject/Quarantine Policy.""")),
@@ -466,6 +483,11 @@ class Privacy(GUIBase):
# an option.
if property == 'subscribe_policy' and not mm_cfg.ALLOW_OPEN_SUBSCRIBE:
val += 1
+ if (property == 'dmarc_moderation_action' and
+ val < mm_cfg.DEFAULT_DMARC_MODERATION_ACTION):
+ doc.addError(_("""dmarc_moderation_action must be >= the configured
+ default value."""))
+ val = mm_cfg.DEFAULT_DMARC_MODERATION_ACTION
setattr(mlist, property, val)
# We need to handle the header_filter_rules widgets specially, but
diff --git a/Mailman/Handlers/CleanseDKIM.py b/Mailman/Handlers/CleanseDKIM.py
index 0df2d97f..5a83a2e0 100644
--- a/Mailman/Handlers/CleanseDKIM.py
+++ b/Mailman/Handlers/CleanseDKIM.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2006-2013 by the Free Software Foundation, Inc.
+# Copyright (C) 2006-2014 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
@@ -31,11 +31,12 @@ from Mailman import mm_cfg
def process(mlist, msg, msgdata):
if not mm_cfg.REMOVE_DKIM_HEADERS:
return
- if (mm_cfg.ALLOW_FROM_IS_LIST and
- mm_cfg.REMOVE_DKIM_HEADERS == 1 and
- mlist.from_is_list != 1):
- return
- del msg['domainkey-signature']
- del msg['dkim-signature']
- del msg['authentication-results']
+ if (mm_cfg.REMOVE_DKIM_HEADERS == 1 and
+ (msgdata.get('from_is_list') == 1 or
+ (mlist.from_is_list == 1 and msgdata.get('from_is_list') != 2)
+ )
+ ):
+ del msg['domainkey-signature']
+ del msg['dkim-signature']
+ del msg['authentication-results']
diff --git a/Mailman/Handlers/CookHeaders.py b/Mailman/Handlers/CookHeaders.py
index 150b4922..83f278b4 100755
--- a/Mailman/Handlers/CookHeaders.py
+++ b/Mailman/Handlers/CookHeaders.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2013 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2014 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
@@ -65,7 +65,10 @@ def uheader(mlist, s, header_name=None, continuation_ws='\t', maxlinelen=None):
return Header(s, charset, maxlinelen, header_name, continuation_ws)
def change_header(name, value, mlist, msg, msgdata, delete=True, repl=True):
- if mm_cfg.ALLOW_FROM_IS_LIST and mlist.from_is_list == 2:
+ if ((msgdata.get('from_is_list') == 2 or
+ (msgdata.get('from_is_list') == 0 and mlist.from_is_list == 2)) and
+ not msgdata.get('_fasttrack')
+ ):
msgdata.setdefault('add_header', {})[name] = value
elif repl or not msg.has_key(name):
if delete:
@@ -116,8 +119,15 @@ def process(mlist, msg, msgdata):
change_header('Precedence', 'list',
mlist, msg, msgdata, repl=False)
# Do we change the from so the list takes ownership of the email
- if mm_cfg.ALLOW_FROM_IS_LIST and mlist.from_is_list:
+ if (msgdata.get('from_is_list') or mlist.from_is_list) and not fasttrack:
realname, email = parseaddr(msg['from'])
+ if not realname:
+ if mlist.isMember(email):
+ realname = mlist.getMemberName(email) or email
+ else:
+ realname = email
+ # Remove domain from realname if it looks like an email address
+ realname = re.sub(r'@([^ .]+\.)+[^ .]+$', '---', realname)
replies = getaddresses(msg.get('reply-to', ''))
reply_addrs = [x[1].lower() for x in replies]
if reply_addrs:
@@ -188,10 +198,10 @@ def process(mlist, msg, msgdata):
# above code?
# Also skip Cc if this is an anonymous list as list posting address
# is already in From and Reply-To in this case and similarly for
- # an 'author is list' list.
+ # a 'from is list' list.
if mlist.personalize == 2 and mlist.reply_goes_to_list <> 1 \
- and not mlist.anonymous_list and not (mlist.from_is_list and
- mm_cfg.ALLOW_FROM_IS_LIST):
+ and not mlist.anonymous_list and not (mlist.from_is_list or
+ msgdata.get('from_is_list')):
# Watch out for existing Cc headers, merge, and remove dups. Note
# that RFC 2822 says only zero or one Cc header is allowed.
new = []
diff --git a/Mailman/Handlers/Moderate.py b/Mailman/Handlers/Moderate.py
index 884030de..d901eb59 100644
--- a/Mailman/Handlers/Moderate.py
+++ b/Mailman/Handlers/Moderate.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2001-2013 by the Free Software Foundation, Inc.
+# Copyright (C) 2001-2014 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
@@ -21,6 +21,7 @@
import re
from email.MIMEMessage import MIMEMessage
from email.MIMEText import MIMEText
+from email.Utils import parseaddr
from Mailman import mm_cfg
from Mailman import Utils
@@ -49,32 +50,38 @@ class ModeratedMemberPost(Hold.ModeratedPost):
def process(mlist, msg, msgdata):
if msgdata.get('approved'):
return
- # First of all, is the poster a member or not?
- for sender in msg.get_senders():
- if mlist.isMember(sender):
- break
- else:
- sender = None
- if sender:
- if Utils.IsDmarcProhibited(sender):
+ # Before anything else, check DMARC.
+ msgdata['from_is_list'] = 0
+ dn, addr = parseaddr(msg.get('from'))
+ if addr:
+ if Utils.IsDMARCProhibited(addr):
# Note that for dmarc_moderation_action, 0 = Accept,
- # 1 = Hold, 2 = Reject, 3 = Discard
+ # 1 = Wrap, 2 = Munge, 3 = Reject, 4 = Discard
if mlist.dmarc_moderation_action == 1:
- msgdata['sender'] = sender
- Hold.hold_for_approval(mlist, msg, msgdata,
- ModeratedMemberPost)
+ msgdata['from_is_list'] = 2
elif mlist.dmarc_moderation_action == 2:
+ msgdata['from_is_list'] = 1
+ elif mlist.dmarc_moderation_action == 3:
# Reject
text = mlist.dmarc_moderation_notice
if text:
text = Utils.wrap(text)
else:
- # Use the default RejectMessage notice string
- text = None
+ text = Utils.wrap(_(
+"""You are not allowed to post to this mailing list From: a domain which
+publishes a DMARC policy of reject or quarantine, and your message has been
+automatically rejected. If you think that your messages are being rejected in
+error, contact the mailing list owner at %(listowner)s."""))
raise Errors.RejectMessage, text
- elif mlist.dmarc_moderation_action == 3:
+ elif mlist.dmarc_moderation_action == 4:
raise Errors.DiscardMessage
-
+ # Then, is the poster a member or not?
+ for sender in msg.get_senders():
+ if mlist.isMember(sender):
+ break
+ else:
+ sender = None
+ if sender:
# If the member's moderation flag is on, then perform the moderation
# action.
if mlist.getMemberOption(sender, mm_cfg.Moderate):
diff --git a/Mailman/Handlers/WrapMessage.py b/Mailman/Handlers/WrapMessage.py
index 68c89ff2..de981dd6 100644
--- a/Mailman/Handlers/WrapMessage.py
+++ b/Mailman/Handlers/WrapMessage.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013 by the Free Software Foundation, Inc.
+# Copyright (C) 2013-2014 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
@@ -35,7 +35,8 @@ KEEPERS = ('to',
def process(mlist, msg, msgdata):
- if not mm_cfg.ALLOW_FROM_IS_LIST or mlist.from_is_list != 2:
+ if not (msgdata.get('from_is_list') == 2 or
+ (mlist.from_is_list == 2 and msgdata.get('from_is_list') == 0)):
return
# There are various headers in msg that we don't want, so we basically
diff --git a/Mailman/ListAdmin.py b/Mailman/ListAdmin.py
index af579331..a4edfbba 100755
--- a/Mailman/ListAdmin.py
+++ b/Mailman/ListAdmin.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2011 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2014 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
@@ -243,7 +243,11 @@ class ListAdmin:
if e.errno <> errno.ENOENT: raise
return LOST
try:
- msg = cPickle.load(fp)
+ if path.endswith('.pck'):
+ msg = cPickle.load(fp)
+ else:
+ assert path.endswith('.txt'), '%s not .pck or .txt' % path
+ msg = fp.read()
finally:
fp.close()
# Save the plain text to a .msg file, not a .pck file
@@ -252,8 +256,11 @@ class ListAdmin:
outpath = head + '.msg'
outfp = open(outpath, 'w')
try:
- g = Generator(outfp)
- g.flatten(msg, 1)
+ if path.endswith('.pck'):
+ g = Generator(outfp)
+ g.flatten(msg, 1)
+ else:
+ outfp.write(msg)
finally:
outfp.close()
# Now handle updates to the database
@@ -285,7 +292,8 @@ class ListAdmin:
# message directly here can lead to a huge delay in web
# turnaround. Log the moderation and add a header.
msg['X-Mailman-Approved-At'] = email.Utils.formatdate(localtime=1)
- syslog('vette', 'held message approved, message-id: %s',
+ syslog('vette', '%s: held message approved, message-id: %s',
+ self.internal_name(),
msg.get('message-id', 'n/a'))
# Stick the message back in the incoming queue for further
# processing.
diff --git a/Mailman/MTA/Postfix.py b/Mailman/MTA/Postfix.py
index 801ddc0f..9987e4cf 100644
--- a/Mailman/MTA/Postfix.py
+++ b/Mailman/MTA/Postfix.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2001-2011 by the Free Software Foundation, Inc.
+# Copyright (C) 2001-2014 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
@@ -116,6 +116,10 @@ def _addlist(mlist, fp):
+def _isvirtual(mlist):
+ return (mlist and mlist.host_name.lower() in
+ [d.lower() for d in mm_cfg.POSTFIX_STYLE_VIRTUAL_DOMAINS])
+
def _addvirtual(mlist, fp):
listname = mlist.internal_name()
fieldsz = len(listname) + len('-unsubscribe')
@@ -233,7 +237,7 @@ def create(mlist, cgi=False, nolock=False, quiet=False):
# Do the aliases file, which need to be done in any case
try:
_do_create(mlist, ALIASFILE, _addlist)
- if mlist and mlist.host_name in mm_cfg.POSTFIX_STYLE_VIRTUAL_DOMAINS:
+ if _isvirtual(mlist):
_do_create(mlist, VIRTFILE, _addvirtual)
# bin/genaliases is the only one that calls create with nolock = True.
# Use that to only update the maps at the end of genaliases.
@@ -304,7 +308,7 @@ def remove(mlist, cgi=False):
lock.lock()
try:
_do_remove(mlist, ALIASFILE, False)
- if mlist.host_name in mm_cfg.POSTFIX_STYLE_VIRTUAL_DOMAINS:
+ if _isvirtual(mlist):
_do_remove(mlist, VIRTFILE, True)
# Regenerate the alias and map files
_update_maps()
diff --git a/Mailman/MailList.py b/Mailman/MailList.py
index d13ca169..bc771f4c 100755
--- a/Mailman/MailList.py
+++ b/Mailman/MailList.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2013 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2014 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
diff --git a/Mailman/Mailbox.py b/Mailman/Mailbox.py
index a7e8cced..a8fa4d0b 100644
--- a/Mailman/Mailbox.py
+++ b/Mailman/Mailbox.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2011 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2013 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
@@ -110,3 +110,11 @@ class ArchiverMailbox(Mailbox):
return self._scrubber(self._mlist, msg)
else:
return msg
+
+ def skipping(self, flag):
+ """ This method allows the archiver to skip over messages without
+ scrubbing attachments into the attachments directory."""
+ if flag:
+ self.factory = _safeparser
+ else:
+ self.factory = _archfactory(self)
diff --git a/Mailman/SecurityManager.py b/Mailman/SecurityManager.py
index 4f6aa34a..7ca4e084 100644
--- a/Mailman/SecurityManager.py
+++ b/Mailman/SecurityManager.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2011 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2013 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
@@ -319,8 +319,6 @@ class SecurityManager:
for u in usernames]:
ok = self.__checkone(c, authcontext, user)
if ok:
- # Refresh the cookie
- print self.MakeCookie(authcontext, user)
return True
return False
else:
@@ -362,6 +360,8 @@ class SecurityManager:
if mac <> received_mac:
return False
# Authenticated!
+ # Refresh the cookie
+ print self.MakeCookie(authcontext, user)
return True
diff --git a/Mailman/Utils.py b/Mailman/Utils.py
index 37ae940b..89b7975e 100644
--- a/Mailman/Utils.py
+++ b/Mailman/Utils.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2011 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2014 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
@@ -231,7 +231,7 @@ def ValidateEmail(s):
# Pretty minimal, cheesy check. We could do better...
if not s or s.count(' ') > 0:
raise Errors.MMBadEmailError
- if _badchars.search(s) or s[0] == '-':
+ if _badchars.search(s):
raise Errors.MMHostileAddress, s
user, domain_parts = ParseEmail(s)
# This means local, unqualified addresses, are not allowed
@@ -240,8 +240,9 @@ def ValidateEmail(s):
if len(domain_parts) < 2:
raise Errors.MMBadEmailError, s
# domain parts may only contain ascii letters, digits and hyphen
+ # and must not begin with hyphen.
for p in domain_parts:
- if len(_valid_domain.sub('', p)) > 0:
+ if len(p) == 0 or p[0] == '-' or len(_valid_domain.sub('', p)) > 0:
raise Errors.MMHostileAddress, s
@@ -1067,7 +1068,8 @@ def suspiciousHTML(html):
# This takes an email address, and returns True if DMARC policy is p=reject
-def IsDmarcProhibited(email):
+# or possibly quarantine.
+def IsDMARCProhibited(email):
if not dns_resolver:
return False
@@ -1085,7 +1087,8 @@ def IsDmarcProhibited(email):
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
return False
except DNSException, e:
- syslog('error', 'DNSException: Unable to query DMARC policy for %s (%s). %s',
+ syslog('error',
+ 'DNSException: Unable to query DMARC policy for %s (%s). %s',
email, dmarc_domain, e.__class__)
return False
else:
@@ -1098,10 +1101,12 @@ def IsDmarcProhibited(email):
want_names = set([dmarc_domain + '.'])
for txt_rec in txt_recs.response.answer:
if txt_rec.rdtype == dns.rdatatype.CNAME:
- cnames[txt_rec.name.to_text()] = txt_rec.items[0].target.to_text()
+ cnames[txt_rec.name.to_text()] = (
+ txt_rec.items[0].target.to_text())
if txt_rec.rdtype != dns.rdatatype.TXT:
continue
- results_by_name[txt_rec.name.to_text()].append("".join(txt_rec.items[0].strings))
+ results_by_name[txt_rec.name.to_text()].append(
+ "".join(txt_rec.items[0].strings))
expands = list(want_names)
seen = set(expands)
while expands:
@@ -1115,26 +1120,34 @@ def IsDmarcProhibited(email):
want_names.discard(item)
if len(want_names) != 1:
- syslog('error', 'multiple DMARC entries in results for %s, processing each to be strict',
- dmarc_domain)
+ syslog('error',
+ """multiple DMARC entries in results for %s,
+ processing each to be strict""",
+ dmarc_domain)
for name in want_names:
if name not in results_by_name:
continue
- dmarcs = filter(lambda n: n.startswith('v=DMARC1;'), results_by_name[name])
+ dmarcs = filter(lambda n: n.startswith('v=DMARC1;'),
+ results_by_name[name])
if len(dmarcs) == 0:
return False
if len(dmarcs) > 1:
- syslog('error', 'RRset of TXT records for %s has %d v=DMARC1 entries; testing them all',
+ syslog('error',
+ """RRset of TXT records for %s has %d v=DMARC1 entries;
+ testing them all""",
dmarc_domain, len(dmarc))
for entry in dmarcs:
if re.search(r'\bp=reject\b', entry, re.IGNORECASE):
- syslog('info', 'DMARC lookup for %s (%s) found p=reject in %s = %s',
- email, dmarc_domain, name, entry)
+# syslog('info',
+# 'DMARC lookup for %s (%s) found p=reject in %s = %s',
+# email, dmarc_domain, name, entry)
return True
- if re.search(r'\bp=quarantine\b', entry, re.IGNORECASE):
- syslog('info', 'DMARC lookup for %s (%s) found p=quarantine in %s = %s',
- email, dmarc_domain, name, entry)
+ if (mm_cfg.DMARC_QUARANTINE_MODERATION_ACTION and
+ re.search(r'\bp=quarantine\b', entry, re.IGNORECASE)):
+# syslog('info',
+# 'DMARC lookup for %s (%s) found p=quarantine in %s = %s',
+# email, dmarc_domain, name, entry)
return True
return False
diff --git a/Mailman/Version.py b/Mailman/Version.py
index 8897164f..185ff888 100644
--- a/Mailman/Version.py
+++ b/Mailman/Version.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2013 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2014 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
@@ -37,7 +37,7 @@ HEX_VERSION = ((MAJOR_REV << 24) | (MINOR_REV << 16) | (MICRO_REV << 8) |
(REL_LEVEL << 4) | (REL_SERIAL << 0))
# config.pck schema version number
-DATA_FILE_VERSION = 102
+DATA_FILE_VERSION = 103
# qfile/*.db schema version number
QFILE_SCHEMA_VERSION = 3
diff --git a/Mailman/versions.py b/Mailman/versions.py
index db5b2914..a568e8e6 100755
--- a/Mailman/versions.py
+++ b/Mailman/versions.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2013 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2014 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
diff --git a/NEWS b/NEWS
index 0b939972..e3f84bd2 100755
--- a/NEWS
+++ b/NEWS
@@ -1,10 +1,100 @@
-*- coding: iso-8859-1 -*-
Mailman - The GNU Mailing List Management System
-Copyright (C) 1998-2011 by the Free Software Foundation, Inc.
+Copyright (C) 1998-2013 by the Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
Here is a history of user visible changes to Mailman.
+2.1.18 (xx-xxx-xxxx)
+
+ Dependencies
+
+ - There is a new dependency associated with the new Privacy options ->
+ Sender filters -> dmarc_moderation_action feature discussed below.
+ This requires that the dnspython <http://www.dnspython.org/> package
+ be available in Python.
+
+ New Features
+
+ - The from_is_list feature introduced in 2.1.16 is now unconditionally
+ available to list owners. There is also, a new Privacy options ->
+ Sender filters -> dmarc_moderation_action feature which applies to list
+ messages where the From: address is in a domain which publishes a DMARC
+ policy of reject or possibly quarantine. This is a list setting with
+ values of Accept, Wrap Message, Munge From, Reject or Discard. There is
+ a new DEFAULT_DMARC_MODERATION_ACTION configuration setting to set the
+ default for this, and the list admin UI is not able to set an action
+ which is 'less' than the default. The prior ALLOW_FROM_IS_LIST setting
+ has been removed and is effectively always Yes. There is a new
+ DMARC_QUARANTINE_MODERATION_ACTION configuration setting which defaults
+ to Yes but can be set to No to exclude domains with DMARC policy of
+ quarantine from dmarc_moderation_action.
+
+ dmarc_moderation_action and from_is_list interact in the following way.
+ If the message is From: a domain to which dmarc_moderation_action applies
+ and if dmarc_moderation_action is other than Accept,
+ dmarc_moderation_action applies to that message. Otherwise the
+ from_is_list action applies.
+
+ i18n
+
+ - Added missing <mm-digest-question-start> tag to French listinfo template.
+ (LP: #1275964)
+
+ Bug Fixes and other patches
+
+ - For from_is_list feature, use email address from original From: if
+ original From: has no display name and strip domain part from resultant
+ names that look like email addresses. (LP: #1304511)
+
+ - Added the list name to the vette log "held message approved" entry.
+ (LP: 1295875)
+
+ - Added the CGI module name to various "No such list" error log entries.
+ (LP: 1295875)
+
+ - Modified contrib/mmdsr to report module name if present in "No such list
+ error log entries.
+
+ - Fixed a NameError exception in cron/nightly_gzip when it tries to print
+ the usage message. (LP: #1291038)
+
+ - Fixed a bug in ListAdmin._handlepost that would crash when trying to
+ preserve a held message for the site admin if HOLD_MESSAGES_AS_PICKLES
+ is False. (LP: #1282365)
+
+ - The from_is_list header munging feature introduced in Mailman 2.1.16 is
+ no longer erroneously applied to Mailman generated notices.
+ (LP: #1279667)
+
+ - Changed the message from the confirm CGI to not indicate approval is
+ required for an acceptance of an invitation. (LP: #1277744)
+
+ - Fixed POSTFIX_STYLE_VIRTUAL_DOMAINS to be case-insensitiive.
+ (LP: #1267003)
+
+ - Added recognition for another simple warning to bounce processing.
+ (LP: #1263247)
+
+ - Fixed a few failing tests in tests/test_handlers.py. (LP: #1262950)
+
+ - Fixed bin/arch to not create scrubbed attachments for messages skipped
+ when processing the --start= option. (LP: #1260883)
+
+ - Fixed email address validation to do a bit better in obscure cases.
+ (LP: #1258703)
+
+ - Fixed a bug which caused some authentication cookies to expire too soon
+ if AUTHENTICATION_COOKIE_LIFETIME is non-zero. (LP: #1257112)
+
+ - Fixed a possible TypeError in bin/sync_members introduced in 2.1.17.
+ (LP: #1243343)
+
+ Miscellaneous
+
+ - Added to the contrib directory, a script from Alain Williams to count
+ posts in a list's archive.
+
2.1.17 (23-Nov-2013)
New Features
diff --git a/bin/sync_members b/bin/sync_members
index 58262841..0c860d25 100755
--- a/bin/sync_members
+++ b/bin/sync_members
@@ -257,7 +257,9 @@ def main():
if not dryrun:
mlist.ApprovedAddMember(userdesc, welcome, notifyadmin)
# Avoid UnicodeError if name can't be decoded
- name = unicode(name, errors='replace').encode(enc, 'replace')
+ if isinstance(name, str):
+ name = unicode(name, errors='replace')
+ name = name.encode(enc, 'replace')
s = email.Utils.formataddr((name, addr)).encode(enc, 'replace')
print _('Added : %(s)s')
except Errors.MMAlreadyAMember:
@@ -279,7 +281,9 @@ def main():
# get rid of this member's entry
mlist.removeMember(addr)
# Avoid UnicodeError if name can't be decoded
- name = unicode(name, errors='replace').encode(enc, 'replace')
+ if isinstance(name, str):
+ name = unicode(name, errors='replace')
+ name = name.encode(enc, 'replace')
s = email.Utils.formataddr((name, addr)).encode(enc, 'replace')
print _('Removed: %(s)s')
diff --git a/contrib/README.post_count b/contrib/README.post_count
new file mode 100644
index 00000000..c2de8112
--- /dev/null
+++ b/contrib/README.post_count
@@ -0,0 +1,14 @@
+This is a bash script to go through the archives of a list and report
+number of posts by month, year and total as well as a rough report of
+total post size by month.
+
+The script was contributed by Alain Williams <addw@phcomp.co.uk>
+and has been slightly modified by Mark Sapiro <mark@msapiro.net> to
+run with only read access to the archive and to remove the perl
+dependency.
+
+The original from Alain is at
+<https://mail.python.org/pipermail/mailman-users/2014-February/076106.html>.
+
+Both versions work only with archives whose archive_volume_frequency
+is Monthly.
diff --git a/contrib/mmdsr b/contrib/mmdsr
index 1097722c..522f65fa 100644
--- a/contrib/mmdsr
+++ b/contrib/mmdsr
@@ -176,6 +176,11 @@
# 0.0.21 Update by Mark Sapiro <mark@msapiro.net>
# Updated on: Sun Sep 27 12:45:44 PDT 2009
# Refactored to use mktemp to create temp files
+#
+# 0.0.22 Update by Mark Sapiro <mark@msapiro.net>
+# Updated on: Fri Mar 21 19:47:19 PDT 2014
+# Updated the "No such list" error report to include the newly
+# added name of the CGI module.
###############################################################################
# Set up locations of standard commands, directories, etc....
@@ -426,7 +431,7 @@ do
echo "" >> $TMP
echo "No Such List:" >> $TMP
echo "------------------------------" >> $TMP
- $GREP 'No such list' $TMPLOG | $SED -e 's/^.* "//' -e 's/".*$//' | $SORT | $UNIQ -c | $SORT -nr >> $TMP
+ $GREP 'No such list' $TMPLOG | $SED -e 's/^[^)]*) //' -e 's/No such list "//' -e 's/".*$//' | $SORT | $UNIQ -c | $SORT -nr >> $TMP
CNT=`$GREP -i 'shunting' $TMPLOG | $WC -l`
if [ "${CNT}x" != "x" ] ; then
diff --git a/contrib/post_count b/contrib/post_count
new file mode 100755
index 00000000..c0aa446c
--- /dev/null
+++ b/contrib/post_count
@@ -0,0 +1,39 @@
+# Show how much email sent through a mailman mail list
+# Copyright (c) Alain Williams <addw@phcomp.co.uk> January 2009
+# This program is free software and is licenced under the GPL: http://www.gnu.org/copyleft/gpl.html
+
+# Set the path to Mailman's private archives directory.
+# Adjust for your installation.
+ARCHIVES="/usr/local/mailman/archives/private"
+
+if [ "$1x" == "x" ] ; then
+ echo "Usage: $0 <list-name>"
+ exit
+fi
+
+cd $ARCHIVES/$1 || exit
+
+echo "Columns: month number-of-messages total-message-size"
+Months="January February March April May June July August September October November December"
+FilesTot=0
+
+# Work out starting year, look for something like: 2004-December
+startYear=$( ls -d [0-9][0-9][0-9][0-9]-* | sort | sed s/-.*// | head -1 )
+endYear=$( date '+%Y' )
+
+for year in $( seq $startYear $endYear )
+do echo "Year $year"
+ YearTot=0
+ for month in $Months
+ do
+ [[ ! -d $year-$month ]] && printf "$year $month\tnone\n" && continue
+ cd $year-$month || exit
+ files=$( ls -f [0-9]* | wc -l )
+ (( FilesTot += files ))
+ (( YearTot += files ))
+ printf "$year $month\t$files\t$( du -h | cut -f 1 )\n"
+ cd .. || exit
+ done
+ echo "Total for year $year: $YearTot"
+done
+echo "Emails total $FilesTot"
diff --git a/cron/nightly_gzip b/cron/nightly_gzip
index 0a0f4e33..de493d0e 100755
--- a/cron/nightly_gzip
+++ b/cron/nightly_gzip
@@ -1,6 +1,6 @@
#! @PYTHON@
#
-# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2014 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
@@ -56,12 +56,16 @@ import paths
from Mailman import mm_cfg
from Mailman import Utils
from Mailman import MailList
+from Mailman import i18n
program = sys.argv[0]
VERBOSE = 0
+_ = i18n._
+i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)
+
def usage(code, msg=''):
if code:
fd = sys.stderr
diff --git a/templates/fr/listinfo.html b/templates/fr/listinfo.html
index 3ebdb9c4..4c91c35c 100644
--- a/templates/fr/listinfo.html
+++ b/templates/fr/listinfo.html
@@ -108,6 +108,7 @@
<td> <MM-list-langs></td>
<td>&nbsp; </td>
</tr>
+ <mm-digest-question-start>
<tr>
<td>Souhaitez-vous recevoir les messages de la liste dans
un seul courriel quotidien group&eacute; ?
diff --git a/tests/bounces/simple_40.txt b/tests/bounces/simple_40.txt
new file mode 100644
index 00000000..89375151
--- /dev/null
+++ b/tests/bounces/simple_40.txt
@@ -0,0 +1,29 @@
+Return-Path: <>
+Received: from mout by moeu2.kundenserver.de id 0LkyaB-1VJZRH2iiz-00ahag;
+ Fri, 13 Dec 2013 01:07:54 +0100
+Date: Fri, 13 Dec 2013 01:07:54 +0100
+From: Mail Delivery System <mailer-daemon@kundenserver.de>
+To: mailman-users-bounces+user=example.com@python.org
+Subject: Warning: message delayed 2 days
+Message-Id: <0LkyaB-1VJZRH2iiz-00ahag@moeu2.kundenserver.de>
+X-Original-Id: 0LykMh-1VSY0J0JZn-015ri8
+
+This message was created automatically by mail delivery software.
+
+A message that you sent has not yet been delivered to one or more of
+its recipients after 2 days.
+
+The message has not yet been delivered to the following addresses:
+
+ <user@example.com>
+
+host lsvk.de[213.165.78.137]:
+connection to mail exchanger failed
+
+No action is required on your part. Delivery attempts will continue for
+some time, and this warning may be repeated at intervals if the message
+remains undelivered. Eventually the mail delivery software will give up,
+and when that happens, the message will be returned to you.
+
+--- The header of the original message is following. ---
+
diff --git a/tests/onebounce.py b/tests/onebounce.py
index 2b05807c..846c4fa6 100755
--- a/tests/onebounce.py
+++ b/tests/onebounce.py
@@ -1,6 +1,6 @@
#! /usr/bin/env python
-# Copyright (C) 2002 by the Free Software Foundation, Inc.
+# Copyright (C) 2002-2013 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
@@ -14,7 +14,8 @@
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
"""Test the bounce detection for files containing bounces.
@@ -80,6 +81,7 @@ def main():
print module, 'got a Stop'
if not all:
break
+ continue
if not addrs:
if verbose:
print module, 'found no matches'
diff --git a/tests/test_bounces.py b/tests/test_bounces.py
index 731f3bbe..8c33ccfd 100755
--- a/tests/test_bounces.py
+++ b/tests/test_bounces.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2001-2011 by the Free Software Foundation, Inc.
+# Copyright (C) 2001-2013 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
@@ -89,6 +89,7 @@ class BounceTest(unittest.TestCase):
('SimpleWarning', 'simple_22.txt', Stop),
('SimpleWarning', 'simple_28.txt', Stop),
('SimpleWarning', 'simple_35.txt', Stop),
+ ('SimpleWarning', 'simple_40.txt', Stop),
# GroupWise
('GroupWise', 'groupwise_01.txt', ['thoff@MAINEX1.ASU.EDU']),
# This one really sucks 'cause it's text/html. Just make sure it
diff --git a/tests/test_handlers.py b/tests/test_handlers.py
index aa73b3c1..b9529779 100644
--- a/tests/test_handlers.py
+++ b/tests/test_handlers.py
@@ -704,7 +704,7 @@ From: aperson@dom.ain
'<http://www.dom.ain/mailman/listinfo/_xtest>,'
'\n\t<mailto:_xtest-request@dom.ain?subject=subscribe>')
eq(msg['list-post'], '<mailto:_xtest@dom.ain>')
- eq(msg['list-archive'], '<http://www.dom.ain/pipermail/_xtest>')
+ eq(msg['list-archive'], '<http://www.dom.ain/pipermail/_xtest/>')
def test_list_headers_with_description(self):
eq = self.assertEqual
@@ -1221,7 +1221,8 @@ MIME-Version: 1.0
""")
MimeDel.process(self._mlist, msg, {})
eq(msg.get_content_type(), 'text/plain')
- eq(msg.get_payload(), '\n\n\n')
+ #eq(msg.get_payload(), '\n\n\n')
+ eq(msg.get_payload().strip(), '')
def test_deep_structure(self):
eq = self.assertEqual
@@ -1878,7 +1879,9 @@ Here is message %(i)d
mlist = self._mlist
msg = self._makemsg(99)
size = os.path.getsize(self._path) + len(str(msg))
- mlist.digest_size_threshhold = 0
+ # Set digest_size_threshhold to a very small value to force a digest.
+ # Setting to zero no longer works.
+ mlist.digest_size_threshhold = 0.001
ToDigest.process(mlist, msg, {})
files = self._sb.files()
# There should be two files in the queue, one for the MIME digest and