diff options
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 @@ -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> </td> </tr> + <mm-digest-question-start> <tr> <td>Souhaitez-vous recevoir les messages de la liste dans un seul courriel quotidien groupé ? 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 |