--- /dev/null
+# Copyright (C) 2011 by Jan Moskyto Matejka <moskyto@atrey.karlin.mff.cuni.cz>
+#
+# 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.
+
+from Mailman import mm_cfg
+from Mailman import Errors
+from Mailman.Logging.Syslog import syslog
+from Mailman.Handlers import Hold
+from subprocess import Popen, PIPE
+import socket, errno
+
+"""Do custom filtering.
+
+Messages are passed to a custom script for filtering.
+"""
+
+# This is the default location of the filter script
+GLOBAL_FILTER_COMMAND = getattr(mm_cfg, 'GLOBAL_FILTER_COMMAND', '/var/lib/mailman/scripts/filter')
+
+class FilterDiscard(Errors.DiscardMessage):
+ '''The filter wants to discard the message'''
+ def __init__(self, rea, rej):
+ Errors.DiscardMessage.__init__(self)
+ self.reason = rea
+ self.rejection = rej
+
+class FilterHold(Errors.HoldMessage):
+ '''The filter wants to hold the message'''
+ def __init__(self, rea, rej):
+ Errors.HoldMessage.__init__(self)
+ self.reason = rea
+ self.rejection = rej
+
+def process(mlist, msg, msgdata):
+ sender = msg.get_sender(use_envelope=0)
+ if sender.replace('moskyto','') != sender:
+ return
+ def not_a_delimiter(str):
+ if (len(str) == 0):
+ return False
+ if (len(str) < 4):
+ return True
+ if (str.replace("=",'') == '\n'):
+ return False
+ return True
+
+ try:
+ filter = Popen(args=GLOBAL_FILTER_COMMAND, stdin=PIPE, stdout=PIPE)
+ filter.stdin.write(str(msg))
+ filter.stdin.close()
+ except IOError, e:
+ syslog('error', 'We got SIGPIPE from the filter on %s post from %s: %s' % (mlist.real_name, msg.get_sender(), e))
+ Hold.hold_for_approval(mlist, msg, msgdata, FilterHold("Filter dead.", "Your mail is held for moderation because of internal rules."))
+
+ reason = ""
+ r = filter.stdout.readline()
+ while (not_a_delimiter(r)):
+ reason += r
+ r = filter.stdout.readline()
+
+ rejection = ""
+ r = filter.stdout.readline()
+ while (not_a_delimiter(r)):
+ rejection += r
+ r = filter.stdout.readline()
+
+ reason = reason.strip()
+ rejection = rejection.strip()
+
+ state = filter.wait()
+
+ if state == 0:
+ return
+ if state == 1:
+ Hold.hold_for_approval(mlist, msg, msgdata, FilterHold(reason, rejection))
+ elif state == 2:
+ syslog('vette', '%s post from %s discarded: %s' % (mlist.real_name, msg.get_sender(), reason))
+ raise FilterDiscard(reason, rejection)
+
+ else:
+ syslog('error', 'The filter returned on %s post from %s some strange state: %s' % (mlist.real_name, msg.get_sender(), state))
+ Hold.hold_for_approval(mlist, msg, msgdata, FilterHold("The post is hold for UNDEFINED reason.", "The post is hold for UNDEFINED reason."))
--- /dev/null
+Mailman custom filter plugin.
+(c) 2011 Jan Moskyto Matejka <moskyto@atrey.karlin.mff.cuni.cz>
+
+== Description ==
+This plugin runs your custom script for each message passed in. Your custom
+script should
+- return 0 if the message is to be passed through
+- return 1 if the message is to be hold for moderation
+- return 2 if the message is to be discarded
+
+Your script should also provide the reason message for the moderator (or into
+the 'vette' log file) and the rejection message for the sender.
+
+The script gets the message on its stdin terminated by EOF. It shall read whole
+the message before writing anything to output, otherwise deadlock is possible.
+It shall write the reason message on stdout delimited by a line with at least
+four = and nothing else: ===== is a delimiter, ==x==x== isn't a delimiter.
+
+Example:
+The script returns
+Lorem ipsum
+Dolor sit
+amet.
+=====
+Est consectetur
+Adipiscing
+elit.
+=====
+
+The reason message is
+Lorem ipsum
+Dolor sit
+amet.
+
+The rejection message is
+Est consectetur
+Adipiscing
+elit.
+
+== Usage ==
+Put Filter.py into the Mailman's handler directory, mostly it is
+/usr/lib/mailman/Mailman/Handlers/.
+
+Create your own script wherever you want. It must have the executable bit set
+for the user who runs the list (mostly `list'). It must be also readable for
+that user, obviously. It may be also binary if you want.
+
+Edit your mm_cfg.py (mostly in /etc/mailman) -- append this:
+GLOBAL_PIPELINE.insert(1, 'Filter')
+GLOBAL_FILTER_COMMAND = "/path/to/your/custom/filter/script"
+
+You may also put the provided (or any other) simple `filter' script into
+/var/lib/mailman/scripts/ as this is its default location hard-coded into
+Filter.py.
+
+Restart Mailman.
+
+== Known bugs ==
+On moderation, Mailman in version 2.1.14 sends not the rejection message to
+the sender, but the reason message. It is not a bug of this plugin, it is a bug
+of Mailman. It may occur also in other versions. Patch for 2.1.14 included --
+apply on /usr/lib/mailman/Mailman/Handlers/Hold.py (or similar) and don't
+forget to recompile Hold.pyc (pycompile) and restart Mailman.
+
+== License ==
+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, US