diff options
Diffstat (limited to 'Mailman')
-rw-r--r-- | Mailman/Archiver/pipermail.py | 4 | ||||
-rw-r--r-- | Mailman/Bouncers/SimpleWarning.py | 6 | ||||
-rw-r--r-- | Mailman/Cgi/admin.py | 6 | ||||
-rw-r--r-- | Mailman/Cgi/admindb.py | 4 | ||||
-rw-r--r-- | Mailman/Cgi/confirm.py | 7 | ||||
-rw-r--r-- | Mailman/Cgi/edithtml.py | 4 | ||||
-rw-r--r-- | Mailman/Cgi/listinfo.py | 4 | ||||
-rw-r--r-- | Mailman/Cgi/options.py | 4 | ||||
-rwxr-xr-x | Mailman/Cgi/private.py | 4 | ||||
-rw-r--r-- | Mailman/Cgi/rmlist.py | 4 | ||||
-rw-r--r-- | Mailman/Cgi/roster.py | 4 | ||||
-rwxr-xr-x | Mailman/Cgi/subscribe.py | 4 | ||||
-rwxr-xr-x | Mailman/Defaults.py.in | 28 | ||||
-rw-r--r-- | Mailman/Gui/General.py | 42 | ||||
-rw-r--r-- | Mailman/Gui/Privacy.py | 36 | ||||
-rw-r--r-- | Mailman/Handlers/CleanseDKIM.py | 17 | ||||
-rwxr-xr-x | Mailman/Handlers/CookHeaders.py | 22 | ||||
-rw-r--r-- | Mailman/Handlers/Moderate.py | 41 | ||||
-rw-r--r-- | Mailman/Handlers/WrapMessage.py | 5 | ||||
-rwxr-xr-x | Mailman/ListAdmin.py | 18 | ||||
-rw-r--r-- | Mailman/MTA/Postfix.py | 10 | ||||
-rwxr-xr-x | Mailman/MailList.py | 2 | ||||
-rw-r--r-- | Mailman/Mailbox.py | 10 | ||||
-rw-r--r-- | Mailman/SecurityManager.py | 6 | ||||
-rw-r--r-- | Mailman/Utils.py | 45 | ||||
-rw-r--r-- | Mailman/Version.py | 4 | ||||
-rwxr-xr-x | Mailman/versions.py | 2 |
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 |