# Adds a configurable mime content stripper to Mailman's processing pipeline diff -crN --exclude=mm_cfg.py Mailman.orig/Defaults.py Mailman/Defaults.py *** Mailman.orig/Defaults.py Wed Oct 24 23:31:13 2001 --- Mailman/Defaults.py Tue Oct 30 22:55:21 2001 *************** *** 374,379 **** --- 374,380 ---- 'CalcRecips', 'Cleanse', 'CookHeaders', + 'MimeDel', # And now we send the message to the digest mbox file, and to the arch and # news queues. Runners will provide further processing of the message, # specific to those delivery paths. *************** *** 620,627 **** ADMIN_CATEGORIES = [ # First column 'general', 'passwords', 'language', 'members', 'nondigest', 'digest', # Second column ! 'privacy', 'bounce', 'archive', 'gateway', 'autoreply', 'topics', ] --- 621,629 ---- ADMIN_CATEGORIES = [ # First column 'general', 'passwords', 'language', 'members', 'nondigest', 'digest', + 'privacy', # Second column ! 'contentfilter', 'bounce', 'archive', 'gateway', 'autoreply', 'topics', ] *************** *** 759,764 **** --- 761,775 ---- # setting this variable to 1, list owners will be given the option to suppress # these headers. ALLOW_RFC2369_OVERRIDES = 0 + + # List of regexps of mime types to be removed by MimeDel; None or empty + # list means don't alter messages. + DEFAULT_REJECT_MIME_TYPES = None + + # Whether to syslog (in 'mimedel' log) every message processed by MimeDel. + # Useful for debugging or giving comfort to those who are unconvinced of + # MimeDel's accuracy. + LOG_MIMEDEL = 0 # Check for administrivia in messages sent to the main list? DEFAULT_ADMINISTRIVIA = 1 diff -crN --exclude=mm_cfg.py Mailman.orig/Gui/ContentFilter.py Mailman/Gui/ContentFilter.py *** Mailman.orig/Gui/ContentFilter.py Wed Dec 31 16:00:00 1969 --- Mailman/Gui/ContentFilter.py Wed Oct 31 10:29:02 2001 *************** *** 0 **** --- 1,49 ---- + # Copyright (C) 2001 by Les Niles + # + # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + """MailList mixin class managing the content filtering options. + """ + + from Mailman import mm_cfg + from Mailman import Utils + from Mailman.i18n import _ + + + + class ContentFilter: + def GetConfigCategory(self): + return 'contentfilter', _('Content-filtering options') + + def GetConfigInfo(self, mlist, category, subcat=None): + if category <> 'contentfilter': + return None + WIDTH = mm_cfg.TEXTFIELDWIDTH + + return [ + _("Policies concerning concerning the content of list traffic."), + + ('reject_mime_types', mm_cfg.Text, (10, WIDTH), 0, + _('Remove sections of messages that have a mime type matching one of these regexps'), + _('''Use this option to remove each message section whose mime + type matches one of a set of regexps (Python/Perl style regular + expressions). Blank lines and lines beginning with "#" will + be ignored. +
For example, to remove all image types, use "image/.*" + (note the "." following the "/"). +
Additionally, each "multipart/alternative" section will + be replaced by just the first alternative that is non-empty + after the specified types have been removed. ''')), + ] diff -crN --exclude=mm_cfg.py Mailman.orig/Gui/__init__.py Mailman/Gui/__init__.py *** Mailman.orig/Gui/__init__.py Wed Oct 24 23:31:15 2001 --- Mailman/Gui/__init__.py Mon Oct 29 14:52:36 2001 *************** *** 26,28 **** --- 26,29 ---- from Topics import Topics from Usenet import Usenet from Language import Language + from ContentFilter import ContentFilter diff -crN --exclude=mm_cfg.py Mailman.orig/Handlers/MimeDel.py Mailman/Handlers/MimeDel.py *** Mailman.orig/Handlers/MimeDel.py Wed Dec 31 16:00:00 1969 --- Mailman/Handlers/MimeDel.py Wed Oct 31 10:27:53 2001 *************** *** 0 **** --- 1,184 ---- + # Copyright (C) 2001 by Les Niles + # + # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + """Mime-stripping filter for Mailman. + + This module scans a message for MIME content, removing those sections whose + mime types match one of a list of regexps. multipart/alternative sections + are replaced by the first non-empty component, and multipart/mixed sections + wrapping only single sections after other processing are replaced by their + contents. + + """ + + import string + import re + + from Mailman import mm_cfg + from Mailman import Message + from email import Parser + from Mailman.Logging.Syslog import syslog + + mime_headers = ['content-type', 'content-transfer-encoding', + 'content-disposition', 'content-description', 'content-id'] + + def replace_mime_headers(dest, src): + """ Use the headers in src to replace all the corresponding headers + in dest. Also delete any other mime headers in dest. + """ + # Remove pre-existing mime_headers from dest + for h in mime_headers: + del dest[h] + # Replace dest headers with corresponding values from src + for h, v in src.items(): + del dest[h] + dest[h] = v + + def proc_part(msg, rejects): + """ Modify the contents of the msg in place by removing any + sections whose mime type matches any of the list of regexps in + rejects[]. Any multipart/mixed section that ends up with just one + component is replaced by that component, and any that ends up with no + components is removed. Multipart/alternative sections are replaced by + the first non-empty component, or deleted if no components remain. + Return 0 if no changes were made to the msg, -1 if the msg ended up + empty and the caller can delete it entirely, and +1 if changes were + made resulting in a non-empty msg. + """ + intype = msg.get_type() + if msg.is_multipart(): # Ensures that get_payload() is a list + if intype[:21] == 'multipart/alternative': + # Two possibilities: + # None of the alternative types is retained => caller should + # delete this section entirely + # One or more of the alternative types is retained => replace + # this section with the first retained type. + for part in msg.get_payload(): + mod = proc_part(part, rejects) + if mod >= 0 and len(part.get_payload()) > 0: + replace_mime_headers(msg, part) + msg.set_payload(part.get_payload()) + return 1 + return -1 + + elif intype[:15] == 'multipart/mixed' \ + or intype[:17] == 'multipart/related': + # Three possibilities: + # None of the parts are retained => caller should delete + # this section entirely + # Exactly one part is retained => that part should replace + # this section + # Two or more parts are retained => Keep this section, + # with those parts. + r = [] + rmod = 0 + for part in msg.get_payload(): + mod = proc_part(part, rejects) + if mod >= 0 and len(part.get_payload()) > 0: + r.append(part) + if mod: + rmod = 1 + else: + rmod = 1 + if not r: + return -1 + if len(r) == 1: + replace_mime_headers(msg, r[0]) + msg.set_payload(r[0].get_payload()) + elif rmod: + msg.set_payload(r) + return rmod + + # Fall through here => Not multipart/mixed or multipart/alternative + for x in rejects: + if x.match(intype): + return -1 + return 0 + + + + def process(mlist, msg, msgdata): + try: + if not mlist.reject_mime_types: + return # => This list doesn't want MimeDel + except: + return # Old-version list object + + # Short-circuit processing for plain-text and digests: + ct = msg.get_type() + if msgdata.get('isdigest') or not ct or ct[:10] == 'text/plain': + return + + rejects = [] + for line in mlist.reject_mime_types.split('\n'): + line = line.strip() + if not line or line[0] == '#': + continue + # Require, at a minimum, a '/' with something before and after: + n = line.find('/') + if n < 1 or n >= len(line)-1: + syslog('config', 'bad reject_mime_types line in %s:\n%s\n', + mlist.real_name, line) + else: + try: + rejects.append(re.compile(line, re.IGNORECASE)) + except re.error, err: + syslog('config', '''\ + bad regexp in reject_mime_types line in %s:\n%s (cause: %s)''', + mlist.real_name, line, err) + if not rejects: + return + + p = Parser.Parser() + parsed = p.parsestr(msg.as_string(unixfrom=1)) + + mod = proc_part(parsed, rejects) + + if not mod: + return + + repmsg = """ + -------------------------------------------------------------------- + MimeDel received the following message + -------------------------------------------------------------------- + \n """ + string.replace(msg.as_string(), '\n', '\n ') + + replace_mime_headers(msg, parsed) # Deletes mime headers if result empty + if mod < 0: + msg.set_payload('[The body of this message was removed by MimeDel]\n') + else: + if msg.preamble: + pre = msg.preamble+'\n' + else: + pre = '' + pre = pre + '[This message was processed by MimeDel]\n\n' + if parsed.is_multipart(): + msg.set_payload(parsed.get_payload()) + msg.preamble = pre + else: + msg.set_payload(pre + parsed.get_payload()) + + repmsg = repmsg + """ + -------------------------------------------------------------------- + And replaced it with the following + -------------------------------------------------------------------- + \n """ + string.replace(msg.as_string(), '\n', '\n ') + '\n\n' + + if mm_cfg.LOG_MIMEDEL: + try: + syslog('mimedel', repmsg) + except: + pass diff -crN --exclude=mm_cfg.py Mailman.orig/MailList.py Mailman/MailList.py *** Mailman.orig/MailList.py Wed Oct 24 23:31:13 2001 --- Mailman/MailList.py Mon Oct 29 11:47:14 2001 *************** *** 319,324 **** --- 319,325 ---- self.discard_these_nonmembers = [] self.forward_auto_discards = mm_cfg.DEFAULT_FORWARD_AUTO_DISCARDS self.generic_nonmember_action = mm_cfg.DEFAULT_GENERIC_NONMEMBER_ACTION + self.reject_mime_types = mm_cfg.DEFAULT_REJECT_MIME_TYPES # BAW: This should really be set in SecurityManager.InitVars() self.password = crypted_password