From 836ce277559605a6884d4ccb91b102b70c6a977a Mon Sep 17 00:00:00 2001 From: Martin Mares Date: Fri, 21 Oct 2011 20:14:35 +0200 Subject: [PATCH] Original version by Moskyto --- Filter.py | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ README | 78 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 Filter.py create mode 100644 README diff --git a/Filter.py b/Filter.py new file mode 100644 index 0000000..a4458ee --- /dev/null +++ b/Filter.py @@ -0,0 +1,90 @@ +# Copyright (C) 2011 by Jan Moskyto Matejka +# +# 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.")) diff --git a/README b/README new file mode 100644 index 0000000..cf18569 --- /dev/null +++ b/README @@ -0,0 +1,78 @@ +Mailman custom filter plugin. +(c) 2011 Jan Moskyto Matejka + +== 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 -- 2.39.5