From b056d1850e44ab2cb6827e2b1a4f0d451459e9eb Mon Sep 17 00:00:00 2001 From: Mark Sapiro Date: Sun, 10 Mar 2013 20:27:18 -0700 Subject: Setting digest_size_threshhold to zero now means no digests will be sent based on size instead of a digest being sent with every post. --- Mailman/Gui/Digest.py | 6 +++--- Mailman/Handlers/ToDigest.py | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'Mailman') diff --git a/Mailman/Gui/Digest.py b/Mailman/Gui/Digest.py index f7722019..77691aee 100644 --- a/Mailman/Gui/Digest.py +++ b/Mailman/Gui/Digest.py @@ -1,4 +1,4 @@ -# Copyright (C) 1998,1999,2000,2001,2002 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 @@ -56,8 +56,8 @@ class Digest(GUIBase): _('When receiving digests, which format is default?')), ('digest_size_threshhold', mm_cfg.Number, 3, 0, - _('How big in Kb should a digest be before it gets sent out?')), - # Should offer a 'set to 0' for no size threshhold. + _('How big in Kb should a digest be before it gets sent out?' + ' 0 implies no maximum size.')), ('digest_send_periodic', mm_cfg.Radio, (_('No'), _('Yes')), 1, _('Should a digest be dispatched daily when the size threshold ' diff --git a/Mailman/Handlers/ToDigest.py b/Mailman/Handlers/ToDigest.py index edbf40dc..2027a46c 100644 --- a/Mailman/Handlers/ToDigest.py +++ b/Mailman/Handlers/ToDigest.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 @@ -86,7 +86,8 @@ def process(mlist, msg, msgdata): # whether the size threshold has been reached. mboxfp.flush() size = os.path.getsize(mboxfile) - if size / 1024.0 >= mlist.digest_size_threshhold: + if (mlist.digest_size_threshhold > 0 and + size / 1024.0 >= mlist.digest_size_threshhold): # This is a bit of a kludge to get the mbox file moved to the digest # queue directory. try: -- cgit v1.2.3 From 2e1198e3bb113ca718e144c3e37a5b8d40b555da Mon Sep 17 00:00:00 2001 From: Mark Sapiro Date: Wed, 20 Mar 2013 18:32:32 -0700 Subject: Added recognition for another Yahoo bounce format. LP: #1157961 --- Mailman/Bouncers/Yahoo.py | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) (limited to 'Mailman') diff --git a/Mailman/Bouncers/Yahoo.py b/Mailman/Bouncers/Yahoo.py index b3edf4fa..47fedce2 100644 --- a/Mailman/Bouncers/Yahoo.py +++ b/Mailman/Bouncers/Yahoo.py @@ -1,4 +1,4 @@ -# Copyright (C) 1998,1999,2000,2001,2002 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 @@ -12,7 +12,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. """Yahoo! has its own weird format for bounces.""" @@ -20,9 +21,15 @@ import re import email from email.Utils import parseaddr -tcre = re.compile(r'message\s+from\s+yahoo\.\S+', re.IGNORECASE) +tcre = (re.compile(r'message\s+from\s+yahoo\.\S+', re.IGNORECASE), + re.compile(r'Sorry, we were unable to deliver your message to ' + r'the following address(\(es\))?\.', + re.IGNORECASE), + ) acre = re.compile(r'<(?P[^>]*)>:') -ecre = re.compile(r'--- Original message follows') +ecre = (re.compile(r'--- Original message follows'), + re.compile(r'--- Below this line is a copy of the message'), + ) @@ -36,18 +43,26 @@ def process(msg): # simple state machine # 0 == nothing seen # 1 == tag line seen + # 2 == end line seen state = 0 for line in email.Iterators.body_line_iterator(msg): line = line.strip() - if state == 0 and tcre.match(line): - state = 1 + if state == 0: + for cre in tcre: + if cre.match(line): + state = 1 + break elif state == 1: mo = acre.match(line) if mo: addrs.append(mo.group('addr')) continue - mo = ecre.match(line) - if mo: - # we're at the end of the error response - break + for cre in ecre: + mo = cre.match(line) + if mo: + # we're at the end of the error response + state = 2 + break + elif state == 2: + break return addrs -- cgit v1.2.3 From bd2a529358762ccbc0c1066da06a7c9e5f26da63 Mon Sep 17 00:00:00 2001 From: Mark Sapiro Date: Thu, 28 Mar 2013 14:20:32 -0700 Subject: Fixed a bug where BounceRunner could create and leave behind zero length bounce-events files. (LP: 1161610) --- Mailman/Queue/BounceRunner.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Mailman') diff --git a/Mailman/Queue/BounceRunner.py b/Mailman/Queue/BounceRunner.py index d219d6e9..08ebe859 100644 --- a/Mailman/Queue/BounceRunner.py +++ b/Mailman/Queue/BounceRunner.py @@ -244,6 +244,7 @@ class BounceRunner(Runner, BounceMixin): return # If that still didn't return us any useful addresses, then send it on # or discard it. + addrs = filter(None, addrs) if not addrs: syslog('bounce', '%s: bounce message w/no discernable addresses: %s', @@ -254,7 +255,8 @@ class BounceRunner(Runner, BounceMixin): # BAW: It's possible that there are None's in the list of addresses, # although I'm unsure how that could happen. Possibly ScanMessages() # can let None's sneak through. In any event, this will kill them. - addrs = filter(None, addrs) + # addrs = filter(None, addrs) + # MAS above filter moved up so we don't try to queue an empty list. self._queue_bounces(mlist.internal_name(), addrs, msg) _doperiodic = BounceMixin._doperiodic -- cgit v1.2.3 From f8be4916b365a654723436092a23f56e305b6b7f Mon Sep 17 00:00:00 2001 From: Mark Sapiro Date: Thu, 28 Mar 2013 15:12:45 -0700 Subject: Fixed a bug where the Subject: of the user notification of a bin/remove_members unsubscribe was not in the user's language. (LP: 1161445) Also updated BounceRunner copyright for previous change. --- Mailman/Deliverer.py | 3 ++- Mailman/Queue/BounceRunner.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'Mailman') diff --git a/Mailman/Deliverer.py b/Mailman/Deliverer.py index 0f8f26b8..2e65e87f 100644 --- a/Mailman/Deliverer.py +++ b/Mailman/Deliverer.py @@ -1,4 +1,4 @@ -# Copyright (C) 1998-2006 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 @@ -81,6 +81,7 @@ your membership administrative address, %(addr)s.''')) def SendUnsubscribeAck(self, addr, lang): realname = self.real_name + i18n.set_language(lang) msg = Message.UserNotification( self.GetMemberAdminEmail(addr), self.GetBouncesEmail(), _('You have been unsubscribed from the %(realname)s mailing list'), diff --git a/Mailman/Queue/BounceRunner.py b/Mailman/Queue/BounceRunner.py index 08ebe859..fcd6e3fb 100644 --- a/Mailman/Queue/BounceRunner.py +++ b/Mailman/Queue/BounceRunner.py @@ -1,4 +1,4 @@ -# Copyright (C) 2001-2008 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 -- cgit v1.2.3 From 9cdad15508bbc5a77e36edfd707dcc8c2140a340 Mon Sep 17 00:00:00 2001 From: Mark Sapiro Date: Wed, 3 Apr 2013 15:23:53 -0700 Subject: The pending (un)subscriptions waiting approval are now sorted by email address in the admindb interface as intended. (LP: 1164160) --- Mailman/Cgi/admindb.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'Mailman') diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index d1873321..dcca1389 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.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 @@ -314,10 +314,10 @@ def show_pending_subs(mlist, form): for id in pendingsubs: addr = mlist.GetRecord(id)[1] byaddrs.setdefault(addr, []).append(id) - addrs = byaddrs.keys() + addrs = byaddrs.items() addrs.sort() num = 0 - for addr, ids in byaddrs.items(): + for addr, ids in addrs: # Eliminate duplicates for id in ids[1:]: mlist.HandleRequest(id, mm_cfg.DISCARD) @@ -365,10 +365,10 @@ def show_pending_unsubs(mlist, form): for id in pendingunsubs: addr = mlist.GetRecord(id) byaddrs.setdefault(addr, []).append(id) - addrs = byaddrs.keys() + addrs = byaddrs.items() addrs.sort() num = 0 - for addr, ids in byaddrs.items(): + for addr, ids in addrs: # Eliminate duplicates for id in ids[1:]: mlist.HandleRequest(id, mm_cfg.DISCARD) -- cgit v1.2.3 From 5c108a6559c6a39beaed2600871b02d0a54edd25 Mon Sep 17 00:00:00 2001 From: Mark Sapiro Date: Sat, 6 Apr 2013 17:57:32 -0700 Subject: The Switchboard.finish() method now logs the text of the exception when it fails to unlink/preserve a .bak file. (LP: 1165589) --- Mailman/Queue/Switchboard.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'Mailman') diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py index bd1cd357..a2c31263 100644 --- a/Mailman/Queue/Switchboard.py +++ b/Mailman/Queue/Switchboard.py @@ -1,4 +1,4 @@ -# Copyright (C) 2001-2008 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 @@ -184,8 +184,8 @@ class Switchboard: else: os.unlink(bakfile) except EnvironmentError, e: - syslog('error', 'Failed to unlink/preserve backup file: %s', - bakfile) + syslog('error', 'Failed to unlink/preserve backup file: %s\n%s', + bakfile, e) def files(self, extension='.pck'): times = {} -- cgit v1.2.3 From 64047694242be831634a82c35d948cbc44f60f20 Mon Sep 17 00:00:00 2001 From: Mark Sapiro Date: Mon, 20 May 2013 08:19:19 -0700 Subject: - added Persian (Farsi - fa) language. - Updated messages/mailman.pot - msgmerged all mailman.po files. --- Mailman/Defaults.py.in | 1 + 1 file changed, 1 insertion(+) (limited to 'Mailman') diff --git a/Mailman/Defaults.py.in b/Mailman/Defaults.py.in index be1ac735..6666d1e5 100755 --- a/Mailman/Defaults.py.in +++ b/Mailman/Defaults.py.in @@ -1515,6 +1515,7 @@ add_language('en', _('English (USA)'), 'us-ascii', 'ltr') add_language('es', _('Spanish (Spain)'), 'iso-8859-1', 'ltr') add_language('et', _('Estonian'), 'iso-8859-15', 'ltr') add_language('eu', _('Euskara'), 'iso-8859-15', 'ltr') # Basque +add_language('fa', _('Persian'), 'utf-8', 'rtl') add_language('fi', _('Finnish'), 'iso-8859-1', 'ltr') add_language('fr', _('French'), 'iso-8859-1', 'ltr') add_language('gl', _('Galician'), 'utf-8', 'ltr') -- cgit v1.2.3 From 41072c693aa53a0072963119c8cf7919096eaad5 Mon Sep 17 00:00:00 2001 From: Mark Sapiro Date: Thu, 30 May 2013 19:29:33 -0700 Subject: Backported fixes for lp:1074592, lp:1079249 and lp:1079254 from lp:flufl.bounce. Actually, lp:1074592 doesn't affect MM 2, but I included the test case. --- Mailman/Bouncers/SimpleMatch.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'Mailman') diff --git a/Mailman/Bouncers/SimpleMatch.py b/Mailman/Bouncers/SimpleMatch.py index 0607ce86..2aa082a2 100644 --- a/Mailman/Bouncers/SimpleMatch.py +++ b/Mailman/Bouncers/SimpleMatch.py @@ -42,7 +42,7 @@ PATTERNS = [ # sz-sb.de, corridor.com, nfg.nl (_c('the following addresses had'), _c('transcript of session follows'), - _c(r'<(?P[^>]*)>|\(expanded from: [^>)]*)>?\)')), + _c(r'^ *(\(expanded from: )?[^\s@]+@[^\s@>]+?)>?\)?\s*$')), # robanal.demon.co.uk (_c('this message was created automatically by mail delivery software'), _c('original message follows'), @@ -184,6 +184,10 @@ PATTERNS = [ _c( 'Your message to (?P[^\s@]+@[^\s@]+) was automatically rejected' )), + # mail.ru + (_c('A message that you sent was rejected'), + _c('This is a copy of your message'), + _c('\s(?P[^\s@]+@[^\s@]+)')), # Next one goes here... ] -- cgit v1.2.3 From 1c59d01e49d944bcffccd2154a1e8ec9f3175874 Mon Sep 17 00:00:00 2001 From: Mark Sapiro Date: Fri, 7 Jun 2013 13:52:54 -0700 Subject: - It is no longer possible to add 'invalid' addresses to the ban_list and the *_these_nonmembers filters from the check boxes on the admindb interface. (LP: #1187201) --- Mailman/Cgi/admindb.py | 54 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 15 deletions(-) (limited to 'Mailman') diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index dcca1389..8b73ae8d 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -710,6 +710,7 @@ def show_post_requests(mlist, id, info, total, count, form): def process_form(mlist, doc, cgidata): senderactions = {} + badaddrs = [] # Sender-centric actions for k in cgidata.keys(): for prefix in ('senderaction-', 'senderpreserve-', 'senderforward-', @@ -762,20 +763,27 @@ def process_form(mlist, doc, cgidata): # Now see if this sender should be added to one of the nonmember # sender filters. if actions.get('senderfilterp', 0): + # Check for an invalid sender address. try: - which = int(actions.get('senderfilter')) - except ValueError: - # Bogus form - which = 'ignore' - if which == mm_cfg.ACCEPT: - mlist.accept_these_nonmembers.append(sender) - elif which == mm_cfg.HOLD: - mlist.hold_these_nonmembers.append(sender) - elif which == mm_cfg.REJECT: - mlist.reject_these_nonmembers.append(sender) - elif which == mm_cfg.DISCARD: - mlist.discard_these_nonmembers.append(sender) - # Otherwise, it's a bogus form, so ignore it + Utils.ValidateEmail(sender) + except Errors.EmailAddressError: + # Don't check for dups. Report it once for each checked box. + badaddrs.append(sender) + else: + try: + which = int(actions.get('senderfilter')) + except ValueError: + # Bogus form + which = 'ignore' + if which == mm_cfg.ACCEPT: + mlist.accept_these_nonmembers.append(sender) + elif which == mm_cfg.HOLD: + mlist.hold_these_nonmembers.append(sender) + elif which == mm_cfg.REJECT: + mlist.reject_these_nonmembers.append(sender) + elif which == mm_cfg.DISCARD: + mlist.discard_these_nonmembers.append(sender) + # Otherwise, it's a bogus form, so ignore it # And now see if we're to clear the member's moderation flag. if actions.get('senderclearmodp', 0): try: @@ -785,8 +793,15 @@ def process_form(mlist, doc, cgidata): pass # And should this address be banned? if actions.get('senderbanp', 0): - if sender not in mlist.ban_list: - mlist.ban_list.append(sender) + # Check for an invalid sender address. + try: + Utils.ValidateEmail(sender) + except Errors.EmailAddressError: + # Don't check for dups. Report it once for each checked box. + badaddrs.append(sender) + else: + if sender not in mlist.ban_list: + mlist.ban_list.append(sender) # Now, do message specific actions banaddrs = [] erroraddrs = [] @@ -836,6 +851,8 @@ def process_form(mlist, doc, cgidata): if cgidata.getvalue(bankey): sender = mlist.GetRecord(request_id)[1] if sender not in mlist.ban_list: + # We don't need to validate the sender. An invalid address + # can't get here. mlist.ban_list.append(sender) # Handle the request id try: @@ -854,7 +871,14 @@ def process_form(mlist, doc, cgidata): doc.AddItem(Header(2, _('Database Updated...'))) if erroraddrs: for addr in erroraddrs: + addr = Utils.websafe(addr) doc.AddItem(`addr` + _(' is already a member') + '
') if banaddrs: for addr, patt in banaddrs: + addr = Utils.websafe(addr) doc.AddItem(_('%(addr)s is banned (matched: %(patt)s)') + '
') + if badaddrs: + for addr in badaddrs: + addr = Utils.websafe(addr) + doc.AddItem(`addr` + ': ' + _('Bad/Invalid email address') + + '
') -- cgit v1.2.3 From 3c41c584c3bd587e0d38ab48ba63a47ead2b18e3 Mon Sep 17 00:00:00 2001 From: Mark Sapiro Date: Thu, 13 Jun 2013 17:48:43 -0700 Subject: - Fixed a bug causing the admin web interface to fail CSRF checking if the list name contains a '+' character. (LP: #1190802) --- Mailman/CSRFcheck.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'Mailman') diff --git a/Mailman/CSRFcheck.py b/Mailman/CSRFcheck.py index a3b6885a..d531ffc2 100644 --- a/Mailman/CSRFcheck.py +++ b/Mailman/CSRFcheck.py @@ -1,4 +1,4 @@ -# Copyright (C) 2011-2012 by the Free Software Foundation, Inc. +# Copyright (C) 2011-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 @@ -55,8 +55,9 @@ def csrf_check(mlist, token): try: issued, keymac = marshal.loads(binascii.unhexlify(token)) key, received_mac = keymac.split(':', 1) - klist, key = key.split('+', 1) - assert klist == mlist.internal_name() + if not key.startswith(mlist.internal_name() + '+'): + return False + key = key[len(mlist.internal_name()) + 1:] if '+' in key: key, user = key.split('+', 1) else: -- cgit v1.2.3 From 36f6592662f0a84b60b2277a00cf3cc85df52b8d Mon Sep 17 00:00:00 2001 From: Mark Sapiro Date: Fri, 12 Jul 2013 14:29:44 -0700 Subject: - Added logging for template errors in HyperArch.py. (LP: #558254) --- Mailman/Archiver/HyperArch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Mailman') diff --git a/Mailman/Archiver/HyperArch.py b/Mailman/Archiver/HyperArch.py index 33a77f0b..a7f46276 100644 --- a/Mailman/Archiver/HyperArch.py +++ b/Mailman/Archiver/HyperArch.py @@ -226,9 +226,9 @@ def quick_maketext(templatefile, dict=None, lang=None, mlist=None): Utils.GetCharSet(lang), 'replace') text = sdict.interpolate(utemplate) - except (TypeError, ValueError): + except (TypeError, ValueError), e: # The template is really screwed up - pass + syslog('error', 'broken template: %s\n%s', filepath, e) # Make sure the text is in the given character set, or html-ify any bogus # characters. return Utils.uncanonstr(text, lang) -- cgit v1.2.3 From 77626e68e3ad0fd5fdfa7fac5e62a4264003a03a Mon Sep 17 00:00:00 2001 From: Mark Sapiro Date: Fri, 12 Jul 2013 15:11:08 -0700 Subject: - Changed the admin GUI to report only the bad entries in a list of email addresses if any are bad. (LP: #558253) --- Mailman/Gui/GUIBase.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'Mailman') diff --git a/Mailman/Gui/GUIBase.py b/Mailman/Gui/GUIBase.py index a365acaf..9a8b68fb 100644 --- a/Mailman/Gui/GUIBase.py +++ b/Mailman/Gui/GUIBase.py @@ -63,6 +63,7 @@ class GUIBase: if isinstance(val, ListType): return val addrs = [] + bad_addrs = [] for addr in [s.strip() for s in val.split(NL)]: # Discard empty lines if not addr: @@ -77,22 +78,24 @@ class GUIBase: try: re.compile(addr) except re.error: - raise ValueError + bad_addrs.append(addr) elif (wtype == mm_cfg.EmailListEx and addr.startswith('@') and property.endswith('_these_nonmembers')): # XXX Needs to be reviewed for list@domain names. # don't reference your own list if addr[1:] == mlist.internal_name(): - raise ValueError + bad_addrs.append(addr) # check for existence of list? For now allow # reference to list before creating it. else: - raise + bad_addrs.append(addr) if property in ('regular_exclude_lists', 'regular_include_lists'): if addr.lower() == mlist.GetListEmail().lower(): - raise Errors.EmailAddressError + bad_addrs.append(addr) addrs.append(addr) + if bad_addrs: + raise Errors.EmailAddressError, ', '.join(bad_addrs) return addrs # This is a host name, i.e. verbatim if wtype == mm_cfg.Host: @@ -168,9 +171,9 @@ class GUIBase: except ValueError: doc.addError(_('Invalid value for variable: %(property)s')) # This is the parent of MMBadEmailError and MMHostileAddress - except Errors.EmailAddressError: + except Errors.EmailAddressError, error: doc.addError( - _('Bad email address for option %(property)s: %(val)s')) + _('Bad email address for option %(property)s: %(error)s')) else: # Set the attribute, which will normally delegate to the mlist self._setValue(mlist, property, val, doc) -- cgit v1.2.3 From 552a3b3336b4ef8e9b309db33c8c3b2a2543ca91 Mon Sep 17 00:00:00 2001 From: Mark Sapiro Date: Fri, 12 Jul 2013 15:37:42 -0700 Subject: - Added "message_id" to the interpolation dictionary for the Article.html template. (LP: #725498) --- Mailman/Archiver/HyperArch.py | 1 + 1 file changed, 1 insertion(+) (limited to 'Mailman') diff --git a/Mailman/Archiver/HyperArch.py b/Mailman/Archiver/HyperArch.py index a7f46276..4d95c5ab 100644 --- a/Mailman/Archiver/HyperArch.py +++ b/Mailman/Archiver/HyperArch.py @@ -471,6 +471,7 @@ class Article(pipermail.Article): d["email_html"] = self.quote(self.email) d["title"] = self.quote(self.subject) d["subject_html"] = self.quote(self.subject) + d["message_id"] = self.quote(self._message_id) # TK: These two _url variables are used to compose a response # from the archive web page. So, ... d["subject_url"] = url_quote('Re: ' + self.subject) -- cgit v1.2.3 From 4086aa6244b4a50ab7ed654876add30d801b30de Mon Sep 17 00:00:00 2001 From: Mark Sapiro Date: Sun, 14 Jul 2013 02:10:07 -0700 Subject: Bumped version and updated docs for 2.1.16rc1. --- Mailman/Version.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) mode change 100755 => 100644 Mailman/Version.py (limited to 'Mailman') diff --git a/Mailman/Version.py b/Mailman/Version.py old mode 100755 new mode 100644 index 4cd2d551..801a614a --- a/Mailman/Version.py +++ b/Mailman/Version.py @@ -16,7 +16,7 @@ # USA. # Mailman version -VERSION = '2.1.15' +VERSION = '2.1.16rc1' # And as a hex number in the manner of PY_VERSION_HEX ALPHA = 0xa @@ -28,10 +28,10 @@ FINAL = 0xf MAJOR_REV = 2 MINOR_REV = 1 -MICRO_REV = 15 -REL_LEVEL = FINAL +MICRO_REV = 16 +REL_LEVEL = GAMMA # at most 15 beta releases! -REL_SERIAL = 0 +REL_SERIAL = 1 HEX_VERSION = ((MAJOR_REV << 24) | (MINOR_REV << 16) | (MICRO_REV << 8) | (REL_LEVEL << 4) | (REL_SERIAL << 0)) -- cgit v1.2.3 From cb2733e029d419904f0fc488f5989beb3aa3ce71 Mon Sep 17 00:00:00 2001 From: Mark Sapiro Date: Thu, 18 Jul 2013 20:21:19 -0700 Subject: Backported the held message sorting to 2.1 and made it optional. --- Mailman/Cgi/admindb.py | 69 +++++++++++++++++++++++++++++++++++++------------- Mailman/Defaults.py.in | 6 ++++- 2 files changed, 56 insertions(+), 19 deletions(-) (limited to 'Mailman') diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index 8b73ae8d..fd9febe8 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -50,16 +50,35 @@ i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) EXCERPT_HEIGHT = 10 EXCERPT_WIDTH = 76 +SSENDER = 0 +SSENDERTIME = 1 +STIME = 2 +ssort = SSENDER -def helds_by_sender(mlist): +def helds_by_skey(mlist, ssort=SSENDER): heldmsgs = mlist.GetHeldMessageIds() - bysender = {} + byskey = {} for id in heldmsgs: + ptime = mlist.GetRecord(id)[0] sender = mlist.GetRecord(id)[1] - bysender.setdefault(sender, []).append(id) - return bysender + if ssort in (SSENDER, SSENDERTIME): + skey = (0, sender) + else: + skey = (ptime, sender) + byskey.setdefault(skey, []).append((ptime, id)) + # Sort groups by time + for k, v in byskey.items(): + if len(v) > 1: + v.sort() + byskey[k] = v + if ssort == SSENDERTIME: + # Rekey with time + newkey = (v[0][0], k[1]) + del byskey[k] + byskey[newkey] = v + return byskey def hacky_radio_buttons(btnname, labels, values, defaults, spacing=3): @@ -76,6 +95,7 @@ def hacky_radio_buttons(btnname, labels, values, defaults, spacing=3): def main(): + global ssort # Figure out which list is being requested parts = Utils.GetPathPieces() if not parts: @@ -253,7 +273,7 @@ def main(): raw=1, mlist=mlist)) num = show_pending_subs(mlist, form) num += show_pending_unsubs(mlist, form) - num += show_helds_overview(mlist, form) + num += show_helds_overview(mlist, form, ssort) addform = num > 0 # Finish up the document, adding buttons to the form if addform: @@ -402,20 +422,29 @@ def show_pending_unsubs(mlist, form): -def show_helds_overview(mlist, form): - # Sort the held messages by sender - bysender = helds_by_sender(mlist) - if not bysender: +def show_helds_overview(mlist, form, ssort=SSENDER): + # Sort the held messages. + byskey = helds_by_skey(mlist, ssort) + if not byskey: return 0 form.AddItem('
') form.AddItem(Center(Header(2, _('Held Messages')))) + # Add the sort sequence choices if wanted + if mm_cfg.DISPLAY_HELD_SUMMARY_SORT_BUTTONS: + form.AddItem(Center(_('Show this list grouped/sorted by'))) + form.AddItem(Center(hacky_radio_buttons( + 'summary_sort', + (_('sender/sender'), _('sender/time'), _('ungrouped/time')), + (SSENDER, SSENDERTIME, STIME), + (ssort == SSENDER, ssort == SSENDERTIME, ssort == STIME)))) # Add the by-sender overview tables admindburl = mlist.GetScriptURL('admindb', absolute=1) table = Table(border=0) form.AddItem(table) - senders = bysender.keys() - senders.sort() - for sender in senders: + skeys = byskey.keys() + skeys.sort() + for skey in skeys: + sender = skey[1] qsender = quote_plus(sender) esender = Utils.websafe(sender) senderurl = admindburl + '?sender=' + qsender @@ -499,7 +528,7 @@ def show_helds_overview(mlist, form): right.AddCellInfo(right.GetCurrentRowIndex(), 0, colspan=2) right.AddRow([' ', ' ']) counter = 1 - for id in bysender[sender]: + for ptime, id in byskey[skey]: info = mlist.GetRecord(id) ptime, sender, subject, reason, filename, msgdata = info # BAW: This is really the size of the message pickle, which should @@ -540,13 +569,14 @@ def show_helds_overview(mlist, form): def show_sender_requests(mlist, form, sender): - bysender = helds_by_sender(mlist) - if not bysender: + byskey = helds_by_skey(mlist, SSENDER) + if not byskey: return - sender_ids = bysender.get(sender) + sender_ids = byskey.get((0, sender)) if sender_ids is None: # BAW: should we print an error message? return + sender_ids = [x[1] for x in sender_ids] total = len(sender_ids) count = 1 for id in sender_ids: @@ -709,6 +739,7 @@ def show_post_requests(mlist, id, info, total, count, form): def process_form(mlist, doc, cgidata): + global ssort senderactions = {} badaddrs = [] # Sender-centric actions @@ -730,6 +761,8 @@ def process_form(mlist, doc, cgidata): discardalldefersp = cgidata.getvalue('discardalldefersp', 0) except ValueError: discardalldefersp = 0 + # Get the summary sequence + ssort = int(cgidata.getvalue('summary_sort', SSENDER)) for sender in senderactions.keys(): actions = senderactions[sender] # Handle what to do about all this sender's held messages @@ -744,8 +777,8 @@ def process_form(mlist, doc, cgidata): preserve = actions.get('senderpreserve', 0) forward = actions.get('senderforward', 0) forwardaddr = actions.get('senderforwardto', '') - bysender = helds_by_sender(mlist) - for id in bysender.get(sender, []): + byskey = helds_by_skey(mlist, SSENDER) + for ptime, id in byskey.get((0, sender), []): if id not in senderactions[sender]['message_ids']: # It arrived after the page was displayed. Skip it. continue diff --git a/Mailman/Defaults.py.in b/Mailman/Defaults.py.in index 6666d1e5..f1a5d907 100755 --- a/Mailman/Defaults.py.in +++ b/Mailman/Defaults.py.in @@ -1,6 +1,6 @@ # -*- python -*- -# Copyright (C) 1998-2012 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 @@ -247,6 +247,10 @@ BROKEN_BROWSER_REPLACEMENTS = {'\x8b': '‹', # single left angle quote '\xbe': '¾', # > plus high order bit '\xa2': '¢', # " plus high order bit } +# +# Shall the admindb held message summary display the grouping and sorting +# option radio buttons? +DISPLAY_HELD_SUMMARY_SORT_BUTTONS = No -- cgit v1.2.3 From 9b8cf403719c083270b2d51a0aac22e120355522 Mon Sep 17 00:00:00 2001 From: Mark Sapiro Date: Thu, 18 Jul 2013 20:49:20 -0700 Subject: First cut at the author_is_list feature. --- Mailman/Defaults.py.in | 9 ++++++++- Mailman/Gui/General.py | 10 +++++++++- Mailman/Handlers/Cleanse.py | 24 ++++++++++++++++++++++-- Mailman/Handlers/CleanseDKIM.py | 13 ++++++++----- Mailman/Handlers/CookHeaders.py | 7 ++++--- Mailman/MailList.py | 3 ++- Mailman/Version.py | 4 ++-- Mailman/versions.py | 4 +++- 8 files changed, 58 insertions(+), 16 deletions(-) (limited to 'Mailman') diff --git a/Mailman/Defaults.py.in b/Mailman/Defaults.py.in index f1a5d907..f87f6665 100755 --- a/Mailman/Defaults.py.in +++ b/Mailman/Defaults.py.in @@ -552,7 +552,10 @@ NNTP_REWRITE_DUPLICATE_HEADERS = [ # footer or scrubbing attachments or even reply-to munging can break these # signatures. It is generally felt that these signatures have value, even if # broken and even if the outgoing message is resigned. However, some sites -# may wish to remove these headers by setting this to Yes. +# 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 author_is_list setting is Yes. +# Yes, 2, True -> always remove headers. REMOVE_DKIM_HEADERS = No # All `normal' messages which are delivered to the entire list membership go @@ -1069,6 +1072,10 @@ DEFAULT_SEND_WELCOME_MSG = Yes # Send goodbye messages to unsubscribed members? DEFAULT_SEND_GOODBYE_MSG = Yes +# Rewrite the From: header of posts replacing the posters address with +# that of the list. Also see REMOVE_DKIM_HEADERS above. +DEFAULT_AUTHOR_IS_LIST = No + # Wipe sender information, and make it look like the list-admin # address sends all messages DEFAULT_ANONYMOUS_LIST = No diff --git a/Mailman/Gui/General.py b/Mailman/Gui/General.py index e9f8f9b5..53f2e908 100644 --- a/Mailman/Gui/General.py +++ b/Mailman/Gui/General.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 @@ -154,6 +154,14 @@ class General(GUIBase): (listname %%05d) -> (listname 00123) """)), + ('author_is_list', mm_cfg.Radio, (_('No'), _('Yes')), 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.""")), + ('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)""")), diff --git a/Mailman/Handlers/Cleanse.py b/Mailman/Handlers/Cleanse.py index 725cb41b..678f6b56 100644 --- a/Mailman/Handlers/Cleanse.py +++ b/Mailman/Handlers/Cleanse.py @@ -1,4 +1,4 @@ -# Copyright (C) 1998-2010 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 @@ -19,7 +19,7 @@ import re -from email.Utils import formataddr +from email.Utils import formataddr, getaddresses, parseaddr from Mailman.Utils import unique_message_id from Mailman.Logging.Syslog import syslog @@ -38,6 +38,26 @@ def process(mlist, msg, msgdata): del msg['x-approve'] # Also remove this header since it can contain a password del msg['urgent'] + # Do we change the from so the list takes ownership of the email + # This really belongs in CookHeaders. + if mlist.author_is_list: + realname, email = parseaddr(msg['from']) + replies = getaddresses(msg.get('reply-to', '')) + reply_addrs = [x[1].lower() for x in replies] + if reply_addrs: + if email.lower() not in reply_addrs: + rt = msg['reply-to'] + ', ' + msg['from'] + else: + rt = msg['reply-to'] + else: + rt = msg['from'] + del msg['reply-to'] + msg['Reply-To'] = rt + del msg['from'] + msg['From'] = formataddr(('%s via %s' % (realname, mlist.real_name), + mlist.GetListEmail())) + del msg['sender'] + #MAS mlist.include_sender_header = 0 # We remove other headers from anonymous lists if mlist.anonymous_list: syslog('post', 'post to %s from %s anonymized', diff --git a/Mailman/Handlers/CleanseDKIM.py b/Mailman/Handlers/CleanseDKIM.py index c4b06613..3a157890 100644 --- a/Mailman/Handlers/CleanseDKIM.py +++ b/Mailman/Handlers/CleanseDKIM.py @@ -1,4 +1,4 @@ -# Copyright (C) 2006-2007 by the Free Software Foundation, Inc. +# Copyright (C) 2006-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 @@ -29,8 +29,11 @@ from Mailman import mm_cfg def process(mlist, msg, msgdata): - if mm_cfg.REMOVE_DKIM_HEADERS: - del msg['domainkey-signature'] - del msg['dkim-signature'] - del msg['authentication-results'] + if not mm_cfg.REMOVE_DKIM_HEADERS: + return + if mm_cfg.REMOVE_DKIM_HEADERS == 1 and not mlist.author_is_list: + return + 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 a2096172..7455dcc6 100755 --- a/Mailman/Handlers/CookHeaders.py +++ b/Mailman/Handlers/CookHeaders.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 @@ -157,9 +157,10 @@ def process(mlist, msg, msgdata): # Cc header. BAW: should we force it into a Reply-To header in the # 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. + # is already in From and Reply-To in this case and similarly for + # an 'author is list' list. if mlist.personalize == 2 and mlist.reply_goes_to_list <> 1 \ - and not mlist.anonymous_list: + and not mlist.anonymous_list and not mlist.author_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/MailList.py b/Mailman/MailList.py index 2d653acb..afb1ce15 100755 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -1,4 +1,4 @@ -# Copyright (C) 1998-2012 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 @@ -347,6 +347,7 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin, self.bounce_matching_headers = \ mm_cfg.DEFAULT_BOUNCE_MATCHING_HEADERS self.header_filter_rules = [] + self.author_is_list = mm_cfg.DEFAULT_AUTHOR_IS_LIST self.anonymous_list = mm_cfg.DEFAULT_ANONYMOUS_LIST internalname = self.internal_name() self.real_name = internalname[0].upper() + internalname[1:] diff --git a/Mailman/Version.py b/Mailman/Version.py index 801a614a..bda02592 100644 --- a/Mailman/Version.py +++ b/Mailman/Version.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 @@ -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 = 100 +DATA_FILE_VERSION = 101 # qfile/*.db schema version number QFILE_SCHEMA_VERSION = 3 diff --git a/Mailman/versions.py b/Mailman/versions.py index 84943efc..e731d33c 100755 --- a/Mailman/versions.py +++ b/Mailman/versions.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 @@ -418,6 +418,8 @@ def NewVars(l): mm_cfg.DEFAULT_REGULAR_INCLUDE_LISTS) add_only_if_missing('regular_exclude_ignore', mm_cfg.DEFAULT_REGULAR_EXCLUDE_IGNORE) + add_only_if_missing('author_is_list', + mm_cfg.DEFAULT_AUTHOR_IS_LIST) -- cgit v1.2.3 From 037acd8e92f42f86f5086a570a23466c128fc480 Mon Sep 17 00:00:00 2001 From: Mark Sapiro Date: Fri, 19 Jul 2013 12:24:22 -0700 Subject: Second cut at the author_is_list feature. --- Mailman/Defaults.py.in | 4 ++++ Mailman/Gui/General.py | 24 +++++++++++++++--------- Mailman/Handlers/Cleanse.py | 3 ++- Mailman/Handlers/CleanseDKIM.py | 4 +++- Mailman/Handlers/CookHeaders.py | 3 ++- 5 files changed, 26 insertions(+), 12 deletions(-) (limited to 'Mailman') diff --git a/Mailman/Defaults.py.in b/Mailman/Defaults.py.in index f87f6665..ee17d344 100755 --- a/Mailman/Defaults.py.in +++ b/Mailman/Defaults.py.in @@ -108,6 +108,10 @@ 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_AUTHOR_IS_LIST below. +ALLOW_AUTHOR_IS_LIST = No + # Form lifetime is set against Cross Site Request Forgery. FORM_LIFETIME = hours(1) diff --git a/Mailman/Gui/General.py b/Mailman/Gui/General.py index 53f2e908..d28fe311 100644 --- a/Mailman/Gui/General.py +++ b/Mailman/Gui/General.py @@ -153,15 +153,21 @@ class General(GUIBase): directive. eg.; [listname %%d] -> [listname 123] (listname %%05d) -> (listname 00123) """)), + ] - ('author_is_list', mm_cfg.Radio, (_('No'), _('Yes')), 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.""")), - + if mm_cfg.ALLOW_AUTHOR_IS_LIST: + rtn.append( + ('author_is_list', mm_cfg.Radio, (_('No'), _('Yes')), 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.""")) + ) + + rtn.extend([ ('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)""")), @@ -382,7 +388,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/Handlers/Cleanse.py b/Mailman/Handlers/Cleanse.py index 678f6b56..c1b500e5 100644 --- a/Mailman/Handlers/Cleanse.py +++ b/Mailman/Handlers/Cleanse.py @@ -21,6 +21,7 @@ import re from email.Utils import formataddr, getaddresses, parseaddr +from Mailman import mm_cfg from Mailman.Utils import unique_message_id from Mailman.Logging.Syslog import syslog from Mailman.Handlers.CookHeaders import uheader @@ -40,7 +41,7 @@ def process(mlist, msg, msgdata): del msg['urgent'] # Do we change the from so the list takes ownership of the email # This really belongs in CookHeaders. - if mlist.author_is_list: + if mm_cfg.ALLOW_AUTHOR_IS_LIST and mlist.author_is_list: realname, email = parseaddr(msg['from']) replies = getaddresses(msg.get('reply-to', '')) reply_addrs = [x[1].lower() for x in replies] diff --git a/Mailman/Handlers/CleanseDKIM.py b/Mailman/Handlers/CleanseDKIM.py index 3a157890..c492a096 100644 --- a/Mailman/Handlers/CleanseDKIM.py +++ b/Mailman/Handlers/CleanseDKIM.py @@ -31,7 +31,9 @@ from Mailman import mm_cfg def process(mlist, msg, msgdata): if not mm_cfg.REMOVE_DKIM_HEADERS: return - if mm_cfg.REMOVE_DKIM_HEADERS == 1 and not mlist.author_is_list: + if (mm_cfg.ALLOW_AUTHOR_IS_LIST and + mm_cfg.REMOVE_DKIM_HEADERS == 1 and + not mlist.author_is_list): return del msg['domainkey-signature'] del msg['dkim-signature'] diff --git a/Mailman/Handlers/CookHeaders.py b/Mailman/Handlers/CookHeaders.py index 7455dcc6..71534c11 100755 --- a/Mailman/Handlers/CookHeaders.py +++ b/Mailman/Handlers/CookHeaders.py @@ -160,7 +160,8 @@ def process(mlist, msg, msgdata): # is already in From and Reply-To in this case and similarly for # an 'author is list' list. if mlist.personalize == 2 and mlist.reply_goes_to_list <> 1 \ - and not mlist.anonymous_list and not mlist.author_is_list: + and not mlist.anonymous_list and not (mlist.author_is_list and + mm_cfg.ALLOW_AUTHOR_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 = [] -- cgit v1.2.3 From 1beb49523f59142b215d75343fd797b582db77b6 Mon Sep 17 00:00:00 2001 From: Mark Sapiro Date: Fri, 19 Jul 2013 14:33:57 -0700 Subject: Enable setting a default grouping/sorting for the admindb held message summary via a DISPLAY_HELD_SUMMARY_SORT_BUTTONS setting. --- Mailman/Cgi/admindb.py | 11 +++++++---- Mailman/Defaults.py.in | 10 +++++++++- 2 files changed, 16 insertions(+), 5 deletions(-) (limited to 'Mailman') diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py index fd9febe8..d3350ea7 100644 --- a/Mailman/Cgi/admindb.py +++ b/Mailman/Cgi/admindb.py @@ -50,10 +50,13 @@ i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE) EXCERPT_HEIGHT = 10 EXCERPT_WIDTH = 76 -SSENDER = 0 -SSENDERTIME = 1 -STIME = 2 -ssort = SSENDER +SSENDER = mm_cfg.SSENDER +SSENDERTIME = mm_cfg.SSENDERTIME +STIME = mm_cfg.STIME +if mm_cfg.DISPLAY_HELD_SUMMARY_SORT_BUTTONS in (SSENDERTIME, STIME): + ssort = mm_cfg.DISPLAY_HELD_SUMMARY_SORT_BUTTONS +else: + ssort = SSENDER diff --git a/Mailman/Defaults.py.in b/Mailman/Defaults.py.in index ee17d344..10a2f056 100755 --- a/Mailman/Defaults.py.in +++ b/Mailman/Defaults.py.in @@ -253,7 +253,10 @@ BROKEN_BROWSER_REPLACEMENTS = {'\x8b': '‹', # single left angle quote } # # Shall the admindb held message summary display the grouping and sorting -# option radio buttons? +# option radio buttons? Set this in mm_cfg.py to one of the following: +# SSENDER -> Default to grouped and sorted by sender. +# SSENDERTIME -> Default to grouped by sender and sorted by time. +# STIME -> Default to ungrouped and sorted by time. DISPLAY_HELD_SUMMARY_SORT_BUTTONS = No @@ -1411,6 +1414,11 @@ UNSUBSCRIBE = 5 ACCEPT = 6 HOLD = 7 +# admindb summary sort button settings. All must evaluate to True. +SSENDER = 1 +SSENDERTIME = 2 +STIME = 3 + # Standard text field width TEXTFIELDWIDTH = 40 -- cgit v1.2.3 From ba100ba6e5ebd6c8fe71152fdb1589fefdacc28b Mon Sep 17 00:00:00 2001 From: Mark Sapiro Date: Fri, 19 Jul 2013 16:28:59 -0700 Subject: Preparing 2.1.16rc2 --- Mailman/Version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Mailman') diff --git a/Mailman/Version.py b/Mailman/Version.py index bda02592..1c0d2de8 100644 --- a/Mailman/Version.py +++ b/Mailman/Version.py @@ -16,7 +16,7 @@ # USA. # Mailman version -VERSION = '2.1.16rc1' +VERSION = '2.1.16rc2' # And as a hex number in the manner of PY_VERSION_HEX ALPHA = 0xa @@ -31,7 +31,7 @@ MINOR_REV = 1 MICRO_REV = 16 REL_LEVEL = GAMMA # at most 15 beta releases! -REL_SERIAL = 1 +REL_SERIAL = 2 HEX_VERSION = ((MAJOR_REV << 24) | (MINOR_REV << 16) | (MICRO_REV << 8) | (REL_LEVEL << 4) | (REL_SERIAL << 0)) -- cgit v1.2.3 From 5391cce1af71723b92bafc2419cec962f1e3ece3 Mon Sep 17 00:00:00 2001 From: Mark Sapiro Date: Fri, 27 Sep 2013 16:33:35 -0700 Subject: Made author_is_list a 3-way with an option to wrap the message. --- Mailman/Defaults.py.in | 13 +++++--- Mailman/Gui/General.py | 8 +++-- Mailman/Handlers/AvoidDuplicates.py | 9 +++-- Mailman/Handlers/Cleanse.py | 20 ------------ Mailman/Handlers/CleanseDKIM.py | 2 +- Mailman/Handlers/CookHeaders.py | 65 +++++++++++++++++++++++++++---------- Mailman/Handlers/Tagger.py | 6 ++-- Mailman/Handlers/WrapMessage.py | 57 ++++++++++++++++++++++++++++++++ 8 files changed, 131 insertions(+), 49 deletions(-) create mode 100644 Mailman/Handlers/WrapMessage.py (limited to 'Mailman') diff --git a/Mailman/Defaults.py.in b/Mailman/Defaults.py.in index 10a2f056..305be5cb 100755 --- a/Mailman/Defaults.py.in +++ b/Mailman/Defaults.py.in @@ -561,7 +561,7 @@ 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 author_is_list setting is Yes. +# 1 -> remove headers only if the list's author_is_list setting is 1. # Yes, 2, True -> always remove headers. REMOVE_DKIM_HEADERS = No @@ -594,6 +594,7 @@ GLOBAL_PIPELINE = [ # (outgoing) path, finally leaving the message in the outgoing queue. 'AfterDelivery', 'Acknowledge', + 'WrapMessage', 'ToOutgoing', ] @@ -1079,9 +1080,13 @@ DEFAULT_SEND_WELCOME_MSG = Yes # Send goodbye messages to unsubscribed members? DEFAULT_SEND_GOODBYE_MSG = Yes -# Rewrite the From: header of posts replacing the posters address with -# that of the list. Also see REMOVE_DKIM_HEADERS above. -DEFAULT_AUTHOR_IS_LIST = No +# The following is a three way setting. +# 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. +# 2 -> Do not modify the From: of the message, but wrap the message in an outer +# message From the list address. +DEFAULT_AUTHOR_IS_LIST = 0 # Wipe sender information, and make it look like the list-admin # address sends all messages diff --git a/Mailman/Gui/General.py b/Mailman/Gui/General.py index d28fe311..c1c27ff9 100644 --- a/Mailman/Gui/General.py +++ b/Mailman/Gui/General.py @@ -157,14 +157,18 @@ class General(GUIBase): if mm_cfg.ALLOW_AUTHOR_IS_LIST: rtn.append( - ('author_is_list', mm_cfg.Radio, (_('No'), _('Yes')), 0, + ('author_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.""")) + all emails.""") + + _("""
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([ diff --git a/Mailman/Handlers/AvoidDuplicates.py b/Mailman/Handlers/AvoidDuplicates.py index 038034c7..549d8e79 100644 --- a/Mailman/Handlers/AvoidDuplicates.py +++ b/Mailman/Handlers/AvoidDuplicates.py @@ -24,6 +24,7 @@ warning header, or pass it through, depending on the user's preferences. from email.Utils import getaddresses, formataddr from Mailman import mm_cfg +from Mailman.Handlers.CookHeaders import change_header COMMASPACE = ', ' @@ -95,6 +96,10 @@ def process(mlist, msg, msgdata): # Set the new list of recipients msgdata['recips'] = newrecips # RFC 2822 specifies zero or one CC header - del msg['cc'] if ccaddrs: - msg['Cc'] = COMMASPACE.join([formataddr(i) for i in ccaddrs.values()]) + change_header('Cc', + COMMASPACE.join([formataddr(i) for i in ccaddrs.values()]), + mlist, msg, msgdata) + else: + del msg['cc'] + diff --git a/Mailman/Handlers/Cleanse.py b/Mailman/Handlers/Cleanse.py index c1b500e5..9e9dba15 100644 --- a/Mailman/Handlers/Cleanse.py +++ b/Mailman/Handlers/Cleanse.py @@ -39,26 +39,6 @@ def process(mlist, msg, msgdata): del msg['x-approve'] # Also remove this header since it can contain a password del msg['urgent'] - # Do we change the from so the list takes ownership of the email - # This really belongs in CookHeaders. - if mm_cfg.ALLOW_AUTHOR_IS_LIST and mlist.author_is_list: - realname, email = parseaddr(msg['from']) - replies = getaddresses(msg.get('reply-to', '')) - reply_addrs = [x[1].lower() for x in replies] - if reply_addrs: - if email.lower() not in reply_addrs: - rt = msg['reply-to'] + ', ' + msg['from'] - else: - rt = msg['reply-to'] - else: - rt = msg['from'] - del msg['reply-to'] - msg['Reply-To'] = rt - del msg['from'] - msg['From'] = formataddr(('%s via %s' % (realname, mlist.real_name), - mlist.GetListEmail())) - del msg['sender'] - #MAS mlist.include_sender_header = 0 # We remove other headers from anonymous lists if mlist.anonymous_list: syslog('post', 'post to %s from %s anonymized', diff --git a/Mailman/Handlers/CleanseDKIM.py b/Mailman/Handlers/CleanseDKIM.py index c492a096..809bae9b 100644 --- a/Mailman/Handlers/CleanseDKIM.py +++ b/Mailman/Handlers/CleanseDKIM.py @@ -33,7 +33,7 @@ def process(mlist, msg, msgdata): return if (mm_cfg.ALLOW_AUTHOR_IS_LIST and mm_cfg.REMOVE_DKIM_HEADERS == 1 and - not mlist.author_is_list): + mlist.author_is_list != 1): return del msg['domainkey-signature'] del msg['dkim-signature'] diff --git a/Mailman/Handlers/CookHeaders.py b/Mailman/Handlers/CookHeaders.py index 71534c11..96862851 100755 --- a/Mailman/Handlers/CookHeaders.py +++ b/Mailman/Handlers/CookHeaders.py @@ -64,13 +64,20 @@ def uheader(mlist, s, header_name=None, continuation_ws='\t', maxlinelen=None): charset = 'us-ascii' 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_AUTHOR_IS_LIST and mlist.author_is_list == 2: + msgdata.setdefault('add_header', {})[name] = value + elif repl or not msg.has_key(name): + if delete: + del msg[name] + msg[name] = value + def process(mlist, msg, msgdata): # Set the "X-Ack: no" header if noack flag is set. if msgdata.get('noack'): - del msg['x-ack'] - msg['X-Ack'] = 'no' + change_header('X-Ack', 'no', mlist, msg, msgdata) # Because we're going to modify various important headers in the email # message, we want to save some of the information in the msgdata # dictionary for later. Specifically, the sender header will get waxed, @@ -87,7 +94,8 @@ def process(mlist, msg, msgdata): pass # Mark message so we know we've been here, but leave any existing # X-BeenThere's intact. - msg['X-BeenThere'] = mlist.GetListEmail() + change_header('X-BeenThere', mlist.GetListEmail(), + mlist, msg, msgdata, delete=False) # Add Precedence: and other useful headers. None of these are standard # and finding information on some of them are fairly difficult. Some are # just common practice, and we'll add more here as they become necessary. @@ -101,12 +109,32 @@ def process(mlist, msg, msgdata): # known exploits in a particular version of Mailman and we know a site is # using such an old version, they may be vulnerable. It's too easy to # edit the code to add a configuration variable to handle this. - if not msg.has_key('x-mailman-version'): - msg['X-Mailman-Version'] = mm_cfg.VERSION + change_header('X-Mailman-Version', mm_cfg.VERSION, + mlist, msg, msgdata, repl=False) # We set "Precedence: list" because this is the recommendation from the # sendmail docs, the most authoritative source of this header's semantics. - if not msg.has_key('precedence'): - msg['Precedence'] = 'list' + 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_AUTHOR_IS_LIST and mlist.author_is_list: + realname, email = parseaddr(msg['from']) + replies = getaddresses(msg.get('reply-to', '')) + reply_addrs = [x[1].lower() for x in replies] + if reply_addrs: + if email.lower() not in reply_addrs: + rt = msg['reply-to'] + ', ' + msg['from'] + else: + rt = msg['reply-to'] + else: + rt = msg['from'] + change_header('Reply-To', rt, mlist, msg, msgdata) + change_header('From', + formataddr(('%s via %s' % (realname, mlist.real_name), + mlist.GetListEmail())), + mlist, msg, msgdata) + if mlist.author_is_list != 2: + del msg['sender'] + #MAS ?? mlist.include_sender_header = 0 # Reply-To: munging. Do not do this if the message is "fast tracked", # meaning it is internally crafted and delivered to a specific user. BAW: # Yuck, I really hate this feature but I've caved under the sheer pressure @@ -142,12 +170,14 @@ def process(mlist, msg, msgdata): if mlist.reply_goes_to_list == 1: i18ndesc = uheader(mlist, mlist.description, 'Reply-To') add((str(i18ndesc), mlist.GetListEmail())) - del msg['reply-to'] # Don't put Reply-To: back if there's nothing to add! if new: # Preserve order - msg['Reply-To'] = COMMASPACE.join( - [formataddr(pair) for pair in new]) + change_header('Reply-To', + COMMASPACE.join([formataddr(pair) for pair in new]), + mlist, msg, msgdata) + else: + del msg['reply-to'] # The To field normally contains the list posting address. However # when messages are fully personalized, that header will get # overwritten with the address of the recipient. We need to get the @@ -170,6 +200,9 @@ def process(mlist, msg, msgdata): add(pair) i18ndesc = uheader(mlist, mlist.description, 'Cc') add((str(i18ndesc), mlist.GetListEmail())) + # We don't worry about what AvoidDuplicates may have done with a + # Cc: header or using change_header here since we never get here + # if author_is_list is allowed and True. del msg['Cc'] msg['Cc'] = COMMASPACE.join([formataddr(pair) for pair in new]) # Add list-specific headers as defined in RFC 2369 and RFC 2919, but only @@ -193,8 +226,7 @@ def process(mlist, msg, msgdata): # without desc we need to ensure the MUST brackets listid_h = '<%s>' % listid # We always add a List-ID: header. - del msg['list-id'] - msg['List-Id'] = listid_h + change_header('List-Id', listid_h, mlist, msg, msgdata) # For internally crafted messages, we also add a (nonstandard), # "X-List-Administrivia: yes" header. For all others (i.e. those coming # from list posts), we add a bunch of other RFC 2369 headers. @@ -221,13 +253,12 @@ def process(mlist, msg, msgdata): # First we delete any pre-existing headers because the RFC permits only # one copy of each, and we want to be sure it's ours. for h, v in headers.items(): - del msg[h] # Wrap these lines if they are too long. 78 character width probably # shouldn't be hardcoded, but is at least text-MUA friendly. The # adding of 2 is for the colon-space separator. if len(h) + 2 + len(v) > 78: v = CONTINUATION.join(v.split(', ')) - msg[h] = v + change_header(h, v, mlist, msg, msgdata) @@ -304,8 +335,7 @@ def prefix_subject(mlist, msg, msgdata): h = u' '.join([prefix, subject]) h = h.encode('us-ascii') h = uheader(mlist, h, 'Subject', continuation_ws=ws) - del msg['subject'] - msg['Subject'] = h + change_header('Subject', h, mlist, msg, msgdata) ss = u' '.join([recolon, subject]) ss = ss.encode('us-ascii') ss = uheader(mlist, ss, 'Subject', continuation_ws=ws) @@ -323,8 +353,7 @@ def prefix_subject(mlist, msg, msgdata): # TK: Subject is concatenated and unicode string. subject = subject.encode(cset, 'replace') h.append(subject, cset) - del msg['subject'] - msg['Subject'] = h + change_header('Subject', h, mlist, msg, msgdata) ss = uheader(mlist, recolon, 'Subject', continuation_ws=ws) ss.append(subject, cset) msgdata['stripped_subject'] = ss diff --git a/Mailman/Handlers/Tagger.py b/Mailman/Handlers/Tagger.py index 38a8e465..6729b76d 100644 --- a/Mailman/Handlers/Tagger.py +++ b/Mailman/Handlers/Tagger.py @@ -27,6 +27,7 @@ from email.Header import decode_header from Mailman import Utils from Mailman.Logging.Syslog import syslog +from Mailman.Handlers.CookHeaders import change_header CRNL = '\r\n' EMPTYSTRING = '' @@ -69,8 +70,9 @@ def process(mlist, msg, msgdata): break if hits: msgdata['topichits'] = hits.keys() - msg['X-Topics'] = NLTAB.join(hits.keys()) - + change_header('X-Topics', NLTAB.join(hits.keys()), + mlist, msg, msgdata, Delete=False) + def scanbody(msg, numlines=None): diff --git a/Mailman/Handlers/WrapMessage.py b/Mailman/Handlers/WrapMessage.py new file mode 100644 index 00000000..350a8a81 --- /dev/null +++ b/Mailman/Handlers/WrapMessage.py @@ -0,0 +1,57 @@ +# Copyright (C) 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. + +"""Wrap the message in an outer message/rfc822 part and transfer/add +some headers from the original. +""" + +import copy + +from Mailman import mm_cfg +from Mailman.Utils import unique_message_id +from Mailman.Message import Message + +# Headers from the original that we want to keep in the wrapper. +KEEPERS = ('to', + 'in-reply-to', + 'references', + 'x-mailman-approved-at', + ) + + + +def process(mlist, msg, msgdata): + if not mm_cfg.ALLOW_AUTHOR_IS_LIST or mlist.author_is_list != 2: + return + + # There are various headers in msg that we don't want, so we basically + # make a copy of the msg, then delete almost everything and set/copy + # what we want. + omsg = copy.deepcopy(msg) + for key in msg.keys(): + if key.lower() not in KEEPERS: + del msg[key] + msg['MIME-Version'] = '1.0' + msg['Content-Type'] = 'message/rfc822' + msg['Content-Disposition'] = 'inline' + msg['Message-ID'] = unique_message_id(mlist) + # Add the headers from CookHeaders. + for k, v in msgdata['add_header'].items(): + msg[k] = v + # And set the payload. + msg.set_payload(omsg.as_string()) + -- cgit v1.2.3 From 2d8a570e69262584f838526a01df91a3c37de3ad Mon Sep 17 00:00:00 2001 From: Mark Sapiro Date: Sat, 28 Sep 2013 16:08:15 -0700 Subject: Renamed author_is_list to from_is_list. --- Mailman/Defaults.py.in | 8 ++++---- Mailman/Gui/General.py | 4 ++-- Mailman/Handlers/CleanseDKIM.py | 4 ++-- Mailman/Handlers/CookHeaders.py | 12 ++++++------ Mailman/Handlers/WrapMessage.py | 2 +- Mailman/MailList.py | 2 +- Mailman/Version.py | 2 +- Mailman/versions.py | 5 +++-- 8 files changed, 20 insertions(+), 19 deletions(-) (limited to 'Mailman') diff --git a/Mailman/Defaults.py.in b/Mailman/Defaults.py.in index 305be5cb..bb70f497 100755 --- a/Mailman/Defaults.py.in +++ b/Mailman/Defaults.py.in @@ -109,8 +109,8 @@ ALLOW_SITE_ADMIN_COOKIES = No AUTHENTICATION_COOKIE_LIFETIME = 0 # The following must be set to Yes to enable the 'author is list' feature. -# See DEFAULT_AUTHOR_IS_LIST below. -ALLOW_AUTHOR_IS_LIST = No +# 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,7 +561,7 @@ 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 author_is_list setting is 1. +# 1 -> remove headers only if the list's from_is_list setting is 1. # Yes, 2, True -> always remove headers. REMOVE_DKIM_HEADERS = No @@ -1086,7 +1086,7 @@ DEFAULT_SEND_GOODBYE_MSG = Yes # that of the list. Also see REMOVE_DKIM_HEADERS above. # 2 -> Do not modify the From: of the message, but wrap the message in an outer # message From the list address. -DEFAULT_AUTHOR_IS_LIST = 0 +DEFAULT_FROM_IS_LIST = 0 # Wipe sender information, and make it look like the list-admin # address sends all messages diff --git a/Mailman/Gui/General.py b/Mailman/Gui/General.py index c1c27ff9..24bc009a 100644 --- a/Mailman/Gui/General.py +++ b/Mailman/Gui/General.py @@ -155,9 +155,9 @@ class General(GUIBase): """)), ] - if mm_cfg.ALLOW_AUTHOR_IS_LIST: + if mm_cfg.ALLOW_FROM_IS_LIST: rtn.append( - ('author_is_list', mm_cfg.Radio, + ('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 diff --git a/Mailman/Handlers/CleanseDKIM.py b/Mailman/Handlers/CleanseDKIM.py index 809bae9b..0df2d97f 100644 --- a/Mailman/Handlers/CleanseDKIM.py +++ b/Mailman/Handlers/CleanseDKIM.py @@ -31,9 +31,9 @@ from Mailman import mm_cfg def process(mlist, msg, msgdata): if not mm_cfg.REMOVE_DKIM_HEADERS: return - if (mm_cfg.ALLOW_AUTHOR_IS_LIST and + if (mm_cfg.ALLOW_FROM_IS_LIST and mm_cfg.REMOVE_DKIM_HEADERS == 1 and - mlist.author_is_list != 1): + mlist.from_is_list != 1): return del msg['domainkey-signature'] del msg['dkim-signature'] diff --git a/Mailman/Handlers/CookHeaders.py b/Mailman/Handlers/CookHeaders.py index 96862851..150b4922 100755 --- a/Mailman/Handlers/CookHeaders.py +++ b/Mailman/Handlers/CookHeaders.py @@ -65,7 +65,7 @@ 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_AUTHOR_IS_LIST and mlist.author_is_list == 2: + if mm_cfg.ALLOW_FROM_IS_LIST and mlist.from_is_list == 2: msgdata.setdefault('add_header', {})[name] = value elif repl or not msg.has_key(name): if delete: @@ -116,7 +116,7 @@ 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_AUTHOR_IS_LIST and mlist.author_is_list: + if mm_cfg.ALLOW_FROM_IS_LIST and mlist.from_is_list: realname, email = parseaddr(msg['from']) replies = getaddresses(msg.get('reply-to', '')) reply_addrs = [x[1].lower() for x in replies] @@ -132,7 +132,7 @@ def process(mlist, msg, msgdata): formataddr(('%s via %s' % (realname, mlist.real_name), mlist.GetListEmail())), mlist, msg, msgdata) - if mlist.author_is_list != 2: + if mlist.from_is_list != 2: del msg['sender'] #MAS ?? mlist.include_sender_header = 0 # Reply-To: munging. Do not do this if the message is "fast tracked", @@ -190,8 +190,8 @@ def process(mlist, msg, msgdata): # is already in From and Reply-To in this case and similarly for # an 'author is list' list. if mlist.personalize == 2 and mlist.reply_goes_to_list <> 1 \ - and not mlist.anonymous_list and not (mlist.author_is_list and - mm_cfg.ALLOW_AUTHOR_IS_LIST): + and not mlist.anonymous_list and not (mlist.from_is_list and + mm_cfg.ALLOW_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 = [] @@ -202,7 +202,7 @@ def process(mlist, msg, msgdata): add((str(i18ndesc), mlist.GetListEmail())) # We don't worry about what AvoidDuplicates may have done with a # Cc: header or using change_header here since we never get here - # if author_is_list is allowed and True. + # if from_is_list is allowed and True. del msg['Cc'] msg['Cc'] = COMMASPACE.join([formataddr(pair) for pair in new]) # Add list-specific headers as defined in RFC 2369 and RFC 2919, but only diff --git a/Mailman/Handlers/WrapMessage.py b/Mailman/Handlers/WrapMessage.py index 350a8a81..68c89ff2 100644 --- a/Mailman/Handlers/WrapMessage.py +++ b/Mailman/Handlers/WrapMessage.py @@ -35,7 +35,7 @@ KEEPERS = ('to', def process(mlist, msg, msgdata): - if not mm_cfg.ALLOW_AUTHOR_IS_LIST or mlist.author_is_list != 2: + if not mm_cfg.ALLOW_FROM_IS_LIST or mlist.from_is_list != 2: return # There are various headers in msg that we don't want, so we basically diff --git a/Mailman/MailList.py b/Mailman/MailList.py index afb1ce15..cdebb507 100755 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -347,7 +347,7 @@ class MailList(HTMLFormatter, Deliverer, ListAdmin, self.bounce_matching_headers = \ mm_cfg.DEFAULT_BOUNCE_MATCHING_HEADERS self.header_filter_rules = [] - self.author_is_list = mm_cfg.DEFAULT_AUTHOR_IS_LIST + self.from_is_list = mm_cfg.DEFAULT_FROM_IS_LIST self.anonymous_list = mm_cfg.DEFAULT_ANONYMOUS_LIST internalname = self.internal_name() self.real_name = internalname[0].upper() + internalname[1:] diff --git a/Mailman/Version.py b/Mailman/Version.py index 1c0d2de8..8ed7fa31 100644 --- a/Mailman/Version.py +++ b/Mailman/Version.py @@ -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 = 101 +DATA_FILE_VERSION = 102 # qfile/*.db schema version number QFILE_SCHEMA_VERSION = 3 diff --git a/Mailman/versions.py b/Mailman/versions.py index e731d33c..31c4f470 100755 --- a/Mailman/versions.py +++ b/Mailman/versions.py @@ -313,6 +313,9 @@ def UpdateOldVars(l, stored_state): pass else: l.digest_members[k] = 0 + # from_is_list was called author_is_list in 2.1.16rc2 (only). + PreferStored('author_is_list', 'from_is_list', + mm_cfg.DEFAULT_FROM_IS_LIST) @@ -418,8 +421,6 @@ def NewVars(l): mm_cfg.DEFAULT_REGULAR_INCLUDE_LISTS) add_only_if_missing('regular_exclude_ignore', mm_cfg.DEFAULT_REGULAR_EXCLUDE_IGNORE) - add_only_if_missing('author_is_list', - mm_cfg.DEFAULT_AUTHOR_IS_LIST) -- cgit v1.2.3 From 944eb488e40e3b3ba7f2709dbacaaf8d46d85c01 Mon Sep 17 00:00:00 2001 From: Mark Sapiro Date: Sat, 28 Sep 2013 18:50:50 -0700 Subject: Tagging branch with: 2.1.16rc3 --- Mailman/Version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Mailman') diff --git a/Mailman/Version.py b/Mailman/Version.py index 8ed7fa31..17ee2531 100644 --- a/Mailman/Version.py +++ b/Mailman/Version.py @@ -16,7 +16,7 @@ # USA. # Mailman version -VERSION = '2.1.16rc2' +VERSION = '2.1.16rc3' # And as a hex number in the manner of PY_VERSION_HEX ALPHA = 0xa @@ -31,7 +31,7 @@ MINOR_REV = 1 MICRO_REV = 16 REL_LEVEL = GAMMA # at most 15 beta releases! -REL_SERIAL = 2 +REL_SERIAL = 3 HEX_VERSION = ((MAJOR_REV << 24) | (MINOR_REV << 16) | (MICRO_REV << 8) | (REL_LEVEL << 4) | (REL_SERIAL << 0)) -- cgit v1.2.3 From 68073e8b93147371bc020afd3b41d5e173574044 Mon Sep 17 00:00:00 2001 From: Mark Sapiro Date: Mon, 7 Oct 2013 21:57:09 -0700 Subject: - Fixed a crash in SpamDetect.py which caused messages with unparseable RFC 2047 encoded headers to be shunted. (LP: #1235101) --- Mailman/Handlers/SpamDetect.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'Mailman') diff --git a/Mailman/Handlers/SpamDetect.py b/Mailman/Handlers/SpamDetect.py index 8d26da03..9e01f623 100644 --- a/Mailman/Handlers/SpamDetect.py +++ b/Mailman/Handlers/SpamDetect.py @@ -1,4 +1,4 @@ -# Copyright (C) 1998-2012 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 @@ -27,6 +27,7 @@ TBD: This needs to be made more configurable and robust. import re +from email.Errors import HeaderParseError from email.Header import decode_header from Mailman import mm_cfg @@ -68,7 +69,10 @@ def getDecodedHeaders(msg, cset='utf-8'): headers = '' for h, v in msg.items(): uvalue = u'' - v = decode_header(re.sub('\n\s', ' ', v)) + try: + v = decode_header(re.sub('\n\s', ' ', v)) + except HeaderParseError: + v = [(v, 'us-ascii')] for frag, cs in v: if not cs: cs = 'us-ascii' -- cgit v1.2.3 From 5215628191e7994a69333813b582098bde9dc7c5 Mon Sep 17 00:00:00 2001 From: Mark Sapiro Date: Wed, 16 Oct 2013 07:06:16 -0700 Subject: Tagging branch with: 2.1.16 --- Mailman/Version.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'Mailman') diff --git a/Mailman/Version.py b/Mailman/Version.py index 17ee2531..1df71cdd 100644 --- a/Mailman/Version.py +++ b/Mailman/Version.py @@ -16,7 +16,7 @@ # USA. # Mailman version -VERSION = '2.1.16rc3' +VERSION = '2.1.16' # And as a hex number in the manner of PY_VERSION_HEX ALPHA = 0xa @@ -29,9 +29,9 @@ FINAL = 0xf MAJOR_REV = 2 MINOR_REV = 1 MICRO_REV = 16 -REL_LEVEL = GAMMA +REL_LEVEL = FINAL # at most 15 beta releases! -REL_SERIAL = 3 +REL_SERIAL = 0 HEX_VERSION = ((MAJOR_REV << 24) | (MINOR_REV << 16) | (MICRO_REV << 8) | (REL_LEVEL << 4) | (REL_SERIAL << 0)) -- cgit v1.2.3 From 2be3678296dfe921fc9cf4a2cd3a8ed71fdd4232 Mon Sep 17 00:00:00 2001 From: Mark Sapiro Date: Tue, 29 Oct 2013 13:24:47 -0700 Subject: Implemented whitelist for headers to keep for anonymous lists. --- Mailman/Defaults.py.in | 21 +++++++++++++++++++++ Mailman/Handlers/Cleanse.py | 23 +++++++++++++++++++++++ 2 files changed, 44 insertions(+) (limited to 'Mailman') diff --git a/Mailman/Defaults.py.in b/Mailman/Defaults.py.in index bb70f497..a7bf31e5 100755 --- a/Mailman/Defaults.py.in +++ b/Mailman/Defaults.py.in @@ -983,6 +983,27 @@ USER_FRIENDLY_PASSWORDS = Yes MEMBER_PASSWORD_LENGTH = 8 ADMIN_PASSWORD_LENGTH = 10 +# The following headers are always removed from posts to anonymous lists as +# they can reveal the identity of the poster or at least the poster's domain. +# +# From:, Reply-To:, Sender:, Return-Path:, X-Originating-Email:, Received:, +# Message-ID: and X-Envelope-From:. +# +# In addition, Return-Receipt-To:, Disposition-Notification-To:, +# X-Confirm-Reading-To: and X-Pmrqc: headers are removed from all posts as +# they can be used to fish for list membership in addition to possibly +# revealing sender information. +# +# In addition to the above removals, all other headers except those matching +# regular expressions in the following setting are also removed. The default +# setting below keeps all non X- headers, those X- headers added by Mailman +# and any X-Spam- headers. +ANONYMOUS_LIST_KEEP_HEADERS = ['^(?!x-)', '^x-mailman-', + '^x-content-filtered-by:', '^x-topics:', + '^x-ack:', '^x-beenthere:', + '^x-list-administrivia:', '^x-spam-', + ] + ##### diff --git a/Mailman/Handlers/Cleanse.py b/Mailman/Handlers/Cleanse.py index 9e9dba15..c3e7aa43 100644 --- a/Mailman/Handlers/Cleanse.py +++ b/Mailman/Handlers/Cleanse.py @@ -26,6 +26,25 @@ from Mailman.Utils import unique_message_id from Mailman.Logging.Syslog import syslog from Mailman.Handlers.CookHeaders import uheader +cres = [] +for regexp in mm_cfg.ANONYMOUS_LIST_KEEP_HEADERS: + try: + cres.append(re.compile(regexp, re.IGNORECASE)) + except re.error, e: + syslog('error', + 'ANONYMOUS_LIST_KEEP_HEADERS: ignored bad regexp %s: %s', + regexp, e) + +def remove_nonkeepers(msg): + for hdr in msg.keys(): + keep = False + for cre in cres: + if cre.search(hdr): + keep = True + break + if not keep: + del msg[hdr] + def process(mlist, msg, msgdata): # Always remove this header from any outgoing messages. Be sure to do @@ -54,6 +73,10 @@ def process(mlist, msg, msgdata): # And so can the message-id so replace it. del msg['message-id'] msg['Message-ID'] = unique_message_id(mlist) + # And something sets this + del msg['x-envelope-from'] + # And now remove all but the keepers. + remove_nonkeepers(msg) i18ndesc = str(uheader(mlist, mlist.description, 'From')) msg['From'] = formataddr((i18ndesc, mlist.GetListEmail())) msg['Reply-To'] = mlist.GetListEmail() -- cgit v1.2.3 From 9eab7c50e3a5413fdaf8d3bf6be22cf28ad3f09d Mon Sep 17 00:00:00 2001 From: Mark Sapiro Date: Thu, 14 Nov 2013 18:15:47 -0800 Subject: Fixed a misspelling in Tagger.py --- Mailman/Handlers/Tagger.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Mailman') diff --git a/Mailman/Handlers/Tagger.py b/Mailman/Handlers/Tagger.py index 6729b76d..cb90bfc4 100644 --- a/Mailman/Handlers/Tagger.py +++ b/Mailman/Handlers/Tagger.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 @@ -71,7 +71,7 @@ def process(mlist, msg, msgdata): if hits: msgdata['topichits'] = hits.keys() change_header('X-Topics', NLTAB.join(hits.keys()), - mlist, msg, msgdata, Delete=False) + mlist, msg, msgdata, delete=False) -- cgit v1.2.3