[Mimedefang]

vito piserchia johndoe at sif.it
Fri Sep 10 09:58:30 EDT 2004


Hi all .
I' m new in the MD-world but I'm impressioned by the power of MD.
After several days in reading the source and search in the list archive
and  with the irreplaceable help of the man pages ;) i finally wrote my 
first working filter.
There is more work to do , dcc/surbl/LDAP/greylisting and so on, but
perhaps this can help other peaple as the list helped me :)
Thanks to all guys.

Sorry .... too happy ;)

-vp


_________________________________________________________________________
 
  
 Vito Piserchia - Reparto Informatico
 Societa' Italiana si Fisica
 via Saragozza 12 40123 - BOLOGNA
 tel: 0513396742 - fax : 051581340 
-------------------------------------------------------------------------


-------------- next part --------------
# -*- Perl -*-
#***********************************************************************
#
# mimedefang-filter
#
# Suggested minimum-protection filter for Microsoft Windows clients, plus
# SpamAssassin checks if SpamAssassin is installed.
#
# Copyright (C) 2002 Roaring Penguin Software Inc.
#
# This program may be distributed under the terms of the GNU General
# Public License, Version 2, or (at your option) any later version.
#
# $Id: suggested-minimum-filter-for-windows-clients,v 1.79 2004/03/04 01:23:11 dfs Exp $
#***********************************************************************

#***********************************************************************
# Set administrator's e-mail address here.  The administrator receives
# quarantine messages and is listed as the contact for site-wide
# MIMEDefang policy.  A good example would be 'defang-admin at mydomain.com'
#***********************************************************************
$AdminAddress = 'defang at localdomain';
$AdminName = "MIMEDefang Administrator";


#***********************************************************************
# Set the e-mail address from which MIMEDefang quarantine warnings and
# user notifications appear to come.  A good example would be
# 'mimedefang at mydomain.com'.  Make sure to have an alias for this
# address if you want replies to it to work.
#***********************************************************************
$DaemonAddress = 'mimedefang at localdomain';

#***********************************************************************
# If you set $AddWarningsInline to 1, then MIMEDefang tries *very* hard
# to add warnings directly in the message body (text or html) rather
# than adding a separate "WARNING.TXT" MIME part.  If the message
# has no text or html part, then a separate MIME part is still used.
#***********************************************************************
$AddWarningsInline = 0;

#***********************************************************************
# To enable syslogging of virus and spam activity, add the following
# to the filter:
# md_graphdefang_log_enable();
# You may optionally provide a syslogging facility by passing an
# argument such as:  md_graphdefang_log_enable('local4');  If you do this, be
# sure to setup the new syslog facility (probably in /etc/syslog.conf).
# An optional second argument causes a line of output to be produced
# for each recipient (if it is 1), or only a single summary line
# for all recipients (if it is 0.)  The default is 1.
# Comment this line out to disable logging.
#***********************************************************************
md_graphdefang_log_enable('mail', 1);
md_graphdefang_log_enable('spam',1);
md_graphdefang_log_enable('possible_spam',1);
md_graphdefang_log_enable('virus',1);
md_graphdefang_log_enable('suspicious_chars',1);
md_graphdefang_log_enable('bad_filename',1);
md_graphdefang_log_enable('non_multipart',1);

#***********************************************************************
# Uncomment this to block messages with more than 50 parts.  This will
# *NOT* work unless you're using Roaring Penguin's patched version
# of MIME tools, version MIME-tools-5.411a-RP-Patched-02 or later.
#
# WARNING: DO NOT SET THIS VARIABLE unless you're using at least
# MIME-tools-5.411a-RP-Patched-02; otherwise, your filter will fail.
#***********************************************************************
$MaxMIMEParts = 50;

#***********************************************************************
# Set various stupid things your mail client does below.
#***********************************************************************

# Set the next one if your mail client cannot handle nested multipart
# messages.  DO NOT set this lightly; it will cause action_add_part to
# work rather strangely.  Leave it at zero, even for MS Outlook, unless
# you have serious problems.
$Stupidity{"flatten"} = 0;

# Set the next one if your mail client cannot handle multiple "inline"
# parts.
$Stupidity{"NoMultipleInlines"} = 0;

# The next lines force SpamAssassin modules to be loaded and rules
# to be compiled immediately.  This may improve performance on busy
# mail servers.  Comment the lines out if you don't like them.

$VirusFound=0;

if ($Features{"SpamAssassin"}) {
    spam_assassin_init()->compile_now(1) if defined(spam_assassin_init());

    # If you want to use auto-whitelisting:
#   if (defined($SASpamTester)) {
#       use Mail::SpamAssassin::DBBasedAddrList;
#       my $awl = Mail::SpamAssassin::DBBasedAddrList->new();
#       $SASpamTester->set_persistent_address_list_factory($awl) if defined($awl);
#   }
}

# This procedure returns true for entities with bad filenames.
sub filter_bad_filename ($) {
    my($entity) = @_;
    my($bad_exts, $re);

    # Bad extensions
    $bad_exts = '(ade|adp|app|asd|asf|asx|bas|bat|chm|cmd|com|cpl|crt|dll|exe|fxp|hlp|hta|hto|inf|ini|ins|isp|jse?|lib|lnk|mdb|mde|msc|msi|msp|mst|ocx|pcd|pif|prg|reg|scr|sct|sh|shb|shs|sys|url|vb|vbe|vbs|vcs|vxd|wmd|wms|wmz|wsc|wsf|wsh|\{[^\}]+\})';

    # Do not allow:
    # - CLSIDs  {foobarbaz}
    # - bad extensions (possibly with trailing dots) at end
    $re = '\.' . $bad_exts . '\.*$';

    return 1 if (re_match($entity, $re));

    # Look inside ZIP files
    if (re_match($entity, '\.zip$') and
	$Features{"Archive::Zip"}) {
	my $bh = $entity->bodyhandle();
	if (defined($bh)) {
	    my $path = $bh->path();
	    if (defined($path)) {
		return re_match_in_zip_directory($path, $re);
	    }
	}
    }
    return 0;
}

#***********************************************************************
# %PROCEDURE: filter_begin
# %ARGUMENTS:
#  None
# %RETURNS:
#  Nothing
# %DESCRIPTION:
#  Called just before e-mail parts are processed
#***********************************************************************
sub filter_begin () {
#OLD
    # ALWAYS drop messages with suspicious chars in headers
#    if ($SuspiciousCharsInHeaders) {
#        md_graphdefang_log('suspicious_chars');
	# action_quarantine_entire_message("Message quarantined because of suspicious characters in headers");
	# Do NOT allow message to reach recipient(s)
#	return action_discard();
# 
#   } 
#OLD END
  if ($SuspiciousCharsInHeaders || $SuspiciousCharsInBody) 
   {
    action_quarantine_entire_message();
    if ($SuspiciousCharsInHeaders) 
     {
      action_notify_administrator("Message quarantined because of suspicious characters in headers");
      md_graphdefang_log('suspicious_chars');
     } else 
  {
    action_notify_administrator("Message quarantined because of suspicious characters in body");
    md_graphdefang_log('suspicious_chars');    
   }
   # Do NOT allow message to reach recipient(s)
   return action_discard();
  }


	$VirusScannerMessages = "";
	action_delete_all_headers("X-Virus-Scanned");
}

#***********************************************************************
# %PROCEDURE: filter
# %ARGUMENTS:
#  entity -- a Mime::Entity object (see MIME-tools documentation for details)
#  fname -- the suggested filename, taken from the MIME Content-Disposition:
#           header.  If no filename was suggested, then fname is ""
#  ext -- the file extension (everything from the last period in the name
#         to the end of the name, including the period.)
#  type -- the MIME type, taken from the Content-Type: header.
#
#  NOTE: There are two likely and one unlikely place for a filename to
#  appear in a MIME message:  In Content-Disposition: filename, in
#  Content-Type: name, and in Content-Description.  If you are paranoid,
#  you will use the re_match and re_match_ext functions, which return true
#  if ANY of these possibilities match.  re_match checks the whole name;
#  re_match_ext checks the extension.  See the sample filter below for usage.
# %RETURNS:
#  Nothing
# %DESCRIPTION:
#  This function is called once for each part of a MIME message.
#  There are many action_*() routines which can decide the fate
#  of each part; see the mimedefang-filter man page.
#***********************************************************************
sub filter ($$$$) {
    my($entity, $fname, $ext, $type) = @_;
    return if message_rejected(); # Avoid unnecessary work

    # Block message/partial parts
    if (lc($type) eq "message/partial") {
        md_graphdefang_log('message/partial');
	action_bounce("MIME type message/partial not accepted here");
	return action_discard();
    }

   if (re_match_ext($entity,'^\.(bat|chm|cmd|com|cpl|dll|exe|hlp|hta|ini|js|lib|lnk|msi|msp|ocx|pif|reg|reg|scr|sct|shb|shs|sys|vbe|vbs?|vxd|wsf|wsh)$')) 
    {
        return action_quarantine($entity, " An attachment named $fname was removed from this document as it\nconstituted a security hazard.  If you require this document, please \ncontact the sender and arrange an alternate means of receiving it.\n");
    }

    if (filter_bad_filename($entity)) {
        md_graphdefang_log('bad_filename', $fname, $type);
	return action_drop_with_warning("An attachment named $fname was removed from this document as it\nconstituted a security hazard.  If you require this document, please \ncontact the sender and arrange an alternate means of receiving it.\n");
    }

###################################################NEW##########################################################
#
#For now i prefair scan  each part of the email for virus and replace them with a warning for each virus found
#
################################################################################################################

     my($code, $category, $action) = entity_contains_virus_clamd($entity); 
     $FoundVirus=(($category eq "virus")|| ( $category eq "suspicious"));
     md_syslog('debug', " $FoundVirus,code=$code, category=$category, action=$action");
     if ($FoundVirus)  {
	 $VirusFound++;
         md_syslog('debug', "FoundVirus: $FoundVirus VirusName:$VirusName num. virus found : $VirusFound : code=$code, category=$category, action=$action");
         if ( ($category eq "virus" ) || ( $category eq "suspicious") ) {
           if ( $action eq "quarantine") {
               return action_replace_with_warning("The attachment named $fname was removed from this document by Clamd\n" . "because virus $VirusName was found.");
            } else {
               return action_quarantine($entity,"The attachment named $fname was removed from this document by Clamd and placed in quarantine dir\n If you are sure this attachment doesn\'t constitute a security hazard reply this mail to quarantine\@sif\.it ");
           } 
         }
     }
     if ($action eq "tempfail") {
          action_tempfail("Problem running virus-scanner");
          md_syslog('warning', "Problem running virus scanner: code=$code, category=$category, action=$action");
     }
#     if (($category = 'interrupted') || ($category = 'swerr')) {
#          action_tempfail("Problem running virus-scanner");
#          md_syslog('warning', "Problem running virus scanner: code=$code, category=$category, action=$action");
#     }
    
###########################################################################################
    # eml is bad if it's not multipart
    if (re_match($entity, '\.eml')) {
        md_graphdefang_log('non_multipart');
	return action_drop_with_warning("A non-multipart attachment named $fname was removed from this document as it\nconstituted a security hazard.  If you require this document, please \ncontact the sender and arrange an alternate means of receiving it.\n");
    }
    # Clean up HTML if Anomy::HTMLCleaner is installed.
    if ($Features{"HTMLCleaner"}) {
	if ($type eq "text/html") {
	    return anomy_clean_html($entity);
	}
    }


    return action_accept();
}

#***********************************************************************

# %PROCEDURE: filter_multipart
# %ARGUMENTS:
#  entity -- a Mime::Entity object (see MIME-tools documentation for details)
#  fname -- the suggested filename, taken from the MIME Content-Disposition:
#           header.  If no filename was suggested, then fname is ""
#  ext -- the file extension (everything from the last period in the name
#         to the end of the name, including the period.)
#  type -- the MIME type, taken from the Content-Type: header.
# %RETURNS:
#  Nothing
# %DESCRIPTION:
#  This is called for multipart "container" parts such as message/rfc822.
#  You cannot replace the body (because multipart parts have no body),
#  but you should check for bad filenames.
#***********************************************************************
sub filter_multipart ($$$$) {
    my($entity, $fname, $ext, $type) = @_;

    return if message_rejected(); # Avoid unnecessary work

#    if (filter_bad_filename($entity)) {
#        md_graphdefang_log('bad_filename', $fname, $type);
#	action_notify_administrator("A MULTIPART attachment of type $type, named $fname was dropped.\n");
#	return action_drop_with_warning("An attachment of type $type, named $fname was removed from this document as it\nconstituted a security hazard.  If you require this document, please contact\nthe sender and arrange an alternate means of receiving it.\n");
#    }

    # eml is bad if it's not message/rfc822
    if (re_match($entity, '\.eml') and ($type ne "message/rfc822")) {
        md_graphdefang_log('non_rfc822',$fname);
	return action_drop_with_warning("A non-message/rfc822 attachment named $fname was removed from this document as it\nconstituted a security hazard.  If you require this document, please contact\nthe sender and arrange an alternate means of receiving it.\n");
    }

    # Block message/partial parts
    if (lc($type) eq "message/partial") {
        md_graphdefang_log('message/partial');
	action_bounce("MIME type message/partial not accepted here");
	return;
    }

    return action_accept();
}


#***********************************************************************
# %PROCEDURE: defang_warning
# %ARGUMENTS:
#  oldfname -- the old file name of an attachment
#  fname -- the new "defanged" name
# %RETURNS:
#  A warning message
# %DESCRIPTION:
#  This function customizes the warning message when an attachment
#  is defanged.
#***********************************************************************
sub defang_warning ($$) {
    my($oldfname, $fname) = @_;
    return
	"An attachment named '$oldfname' was converted to '$fname'.\n" .
	"To recover the file, right-click on the attachment and Save As\n" .
	"'$oldfname'\n";
}

# If SpamAssassin found SPAM, append report.  We do it as a separate
# attachment of type text/plain
sub filter_end ($) {

    my($entity) = @_;

    # If you want quarantine reports, uncomment next line
   #  send_quarantine_notifications();

    # IMPORTANT NOTE:  YOU MUST CALL send_quarantine_notifications() AFTER
    # ANY PARTS HAVE BEEN QUARANTINED.  SO IF YOU MODIFY THIS FILTER TO
    # QUARANTINE SPAM, REWORK THE LOGIC TO CALL send_quarantine_notifications()
    # AT THE END!!!
 
    # No sense doing any extra work
    return if message_rejected();

####################################################################################


   # Spam checks if SpamAssassin is installed
    $FoundSpam = "no";
    if ($Features{"SpamAssassin"}) 
    {
##################Only dim(INPUTMSG)< 100KB are passed through SA###################

        if (-s "./INPUTMSG" < 100*1024) 
        {    
            my($hits, $req, $names, $report) = spam_assassin_check(); 
            my $stars = ( $hits < 6 ) ? ( "*" x int($hits) ) : ("*" x 6 ); 
####################################################################################
# Spam are divided into :
# $hits > $req*4 ------------------> ACTION : add_header 'spam_high' and discard OR quarantine OR bounce
# $req  < $hits < $req*4 ----------> ACTION : add_header 'spam'    and discard OR quarantine OR bounce
# $req/2 < $hits <= $req ----------> ACTION : add_header 'possible_spam' for successive user-defined action  
# $hits < $req/2 ------------------>  NO SPAM !
# NOTE: for now i prefair change only the headers for testing
####################################################################################
            if  ( $hits >= $req*4 ) {
               # Add spam-score,detect,status  header
               action_add_header("X-Spam-Detect", "SPAM_HIGH");
               action_add_header("X-Spam-Score", "$hits ($stars) $names");
               action_add_header("X-Spam-Status", "Yes, hits=$hits required=$req");
               md_graphdefang_log('spam_high', "hits=$hits", $RelayAddr);
####################################################################################
#	       action_quarantine_entire_message();
#              send_quarantine_notifications();
####################################################################################
#	    action_discard();
####################################################################################
#            action_bounce("No spam wanted here:spam score $hits ($stars) $names!!");
#################################################################################### 
               $FoundSpam = "yes";
            } elsif ( ( $hits >=  $req ) && ( $hits <= $req*4 ) ) {
	       # Add spam-score,detect,status  header
               action_add_header("X-Spam-Detect", "SPAM");
               action_add_header("X-Spam-Score", "$hits ($stars)");
               action_add_header("X-Spam-Status", "Yes, hits=$hits required=$req");
               md_graphdefang_log('spam', "hits=$hits", $RelayAddr);
               $FoundSpam = "yes";

####################################################################################
#           action_quarantine_entire_message();
#            send_quarantine_notifications();
####################################################################################
#           action_discard();
####################################################################################
#
#            action_bounce("No spam wanted here:spam score $hits ($stars) $names!!"); 
            
            } elsif (( $hits < $req ) && ( $hits > $req/2 )) {
           
########################## ($req/2 $hits < $req) ######################################

              #Delete previous X-Spam-Score and X-Spam-Report
               action_delete_all_headers("X-Spam-Score");
               action_delete_all_headers("X-Spam-Report");
               action_delete_all_headers("X-Spam-Status");
               # Add spam-status header
               action_add_header("X-Spam-Status", "POSSIBLE");
               $FoundSpam = "possible";
               md_graphdefang_log('possible_spam', "hits=$hits", $RelayAddr);
            } 
##################Add spamassassin report inline for successive statistic use##########################         
         if ($FoundSpam eq "yes" )  
         { 
             action_add_part($entity, "text/plain", "-suggest","$report\n","SpamAssassinReport.txt", "inline");
         }
         
        }
###########################FILE TOO BIG################################################################
        md_graphdefang_log('file_too_big', " only virus scan");
    }
#################################END OF SA CHECK######################################################
      




#######################Changing the subject###################################

    if ( ($VirusFound) || ( $FoundSpam eq "yes" )) {
       if ($FoundSpam eq "yes") {
           action_change_header("Subject", "[Spam: HIGH] $Subject");
          } else {
            action_change_header("Subject", "[$VirusFound VIRUS] $Subject");
	}
 	$VirusFound=0;       
    }

    # I HATE HTML MAIL!  If there's a multipart/alternative with both
    # text/plain and text/html parts, nuke the text/html.  Thanks for
    # wasting our disk space and bandwidth...

    # If you want to strip out HTML parts if there is a corresponding
    # plain-text part, uncomment the next line.
      remove_redundant_html_parts($entity);

      md_graphdefang_log('mail_in');

    # Deal with malformed MIME.
    # Some viruses produce malformed MIME messages that are misinterpreted
    # by mail clients.  They also might slip under the radar of MIMEDefang.
    # If you are worried about this, you should canonicalize all
    # e-mail by uncommenting the action_rebuild() line.  This will
    # force _all_ messages to be reconstructed as valid MIME.  It will
    # increase the load on your server, and might break messages produced
    # by marginal software.  Your call.

    # action_rebuild();
     send_quarantine_notifications();

#############################NEW#################################3



}



#use Socket;
#sub filter_relay ($$$) {

 #       my ($hostip, $hostname, $helo) = @_;

# #       md_syslog('debug', "RELAY: <$hostip> <$hostname> <$helo>");
#        my $addr = '';
#        my $network_string = '';
#        my $mask_string = '';

        # List networks that should be exempt from all filtering by
        # putting their network/mask pairs into the exempt_subnets
        # associative array.  (Follow the example for the loopback.)

#        my %exempt_subnets = (
#        '127.0.0.0',            '255.0.0.0',    # loopback
#        '131.154.110.0',        '255.255.255.0' # my internal net
#        );

        # Relays
#        @trusted = ("relay.org", "another-relay.com");
#        for $host (@trusted) {
#                return ('ACCEPT_AND_NO_MORE_FILTERING', 'ok')
#                                                if ($host =~ /$helo/i);
#        }

        # If the address of the connecting client falls within one of
        # the subnets defined by %exempt_subnets, then bypass all
        # further filtering.

#        $addr = inet_aton $hostip;
#        while (($network_string, $mask_string) = each %exempt_subnets) {
#                my $network = inet_aton $network_string;
#                my $mask = inet_aton $mask_string;
#                if (($addr & $mask) eq $network) {
#                        return ('ACCEPT_AND_NO_MORE_FILTERING', 'ok');
#                }
#        }

        # The client isn't in an exempt subnet; filtering should
        # continue.
#        return ('CONTINUE', 'ok');
#}




1;


More information about the MIMEDefang mailing list