aboutsummaryrefslogtreecommitdiffstats
path: root/Mailman
diff options
context:
space:
mode:
Diffstat (limited to 'Mailman')
-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
27 files changed, 220 insertions, 123 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