# Scan Mailman messages using ClamAV and hold or discard messages # based on the results. # # Written by: Sean Reifschneider # Modified by: Athmane Madjoudj # # Original URL: http://www.tummy.com/Software/mailman-clamav # # Copyright (c) 2003, tummy.com, ltd. # # Licensed under GPL. """Scan messages with ClamAV. Pass messages to ClamAV via a pipe to clamscan and hold or drop the message if it is a virus. """ import string, os, popen2, re from Mailman import mm_cfg from Mailman import Errors from Mailman.Logging.Syslog import syslog from Mailman.Handlers import Hold from Mailman.Handlers.Moderate import matches_p CLAMAV_DISCARD = getattr(mm_cfg, 'CLAMAV_DISCARD', 0) CLAMAV_CLAMDSCANPATH = getattr(mm_cfg, 'CLAMAV_CLAMSCANPATH', '/usr/bin/clamscan') ########################################### class ClamAVDiscard(Errors.DiscardMessage): 'Discard: The message was identified as a virus.' def __init__(self, virusName): self.reason = 'ClamAV identified this message as a virus (%s)' % virusName self.rejection = ('Your message has been discarded because it was ' 'identified as a virus (%s)' % virusName) ######################################## class ClamAVHold(Errors.HoldMessage): 'Discard: The message was identified as a virus.' def __init__(self, virusName): self.reason = 'ClamAV identified this message as a virus (%s)' % virusName self.rejection = ('Your message has been held for moderator approval ' 'because it was identified as a virus (%s)' % virusName) ########################### def check_message(message): '''Check the message with clamdscan. Return None if not a virus, or the virus name.''' fpIn, fpOut = popen2.popen2('%s --stdout -' % CLAMAV_CLAMSCANPATH) fpOut.write(message) fpOut.close() ret = None for line in fpIn.readlines(): #stream: Exploit.IFrame.Gen FOUND m = re.match(r'^stream:\s*(.*)\s*FOUND\s*$', line) if m: ret = string.strip(m.group(1)) fpIn.close() return(ret) ################################# def process(mlist, msg, msgdata): # let message through if it's been approved by the moderator if msgdata.get('approved'): return # scan for virus, pass message on if an exception happens try: virusName = check_message(str(msg)) except IOError, e: syslog('vette', 'Exception trying to run ClamAV scanner: "%s"' % str(e)) return # if not a virus, let message through if virusName == None: return # discard message if configured to do so if CLAMAV_DISCARD: syslog('vette', '%s post from %s discarded: ClamAV identified it as "%s".' % ( mlist.real_name, msg.get_sender(), virusName )) raise(ClamAVDiscard(virusName)) # hold message if that option is selected else: Hold.hold_for_approval(mlist, msg, msgdata, ClamAVHold(virusName))