Sorry about my previous post, I now realize that we need to keep some other features of this filter (it keeps a copy of everyones email as well as the exemption thinggy).
So below is my original post, and the listing of the filter code. ============================ To: [email protected] Recently I have started with a new company, and they have deployed MIMEDefang on a few different sites, with a custom filter, that has exemption lists. Unfortunately the custom exemption list only works sometimes. IANAPP (I Am Not A Perl Programmer), and would really like some help on either how to fix this current filter. Here are the logs from me sending a "Movie.wmv" to an exempt user. May 1 12:05:45 mail sendmail[20621]: k412XBf0020621: from=<[EMAIL PROTECTED]>, size=3898524, class=0, nrcpts=1, msgid=<[EMAIL PROTECTED]>, proto=ESMTP, daemon=MTA, relay=cpe-61-9-140-241.vic.bigpond.net.au [61.9.140.241] May 1 12:05:45 mail mimedefang.pl[9050]: MDLOG,k412XBf0020621,exempt user,,0,<[EMAIL PROTECTED]>,<[EMAIL PROTECTED]>,testing May 1 12:05:45 mail mimedefang.pl[9050]: MDLOG,k412XBf0020621,exempt user,,0,<[EMAIL PROTECTED]>,<[EMAIL PROTECTED]>,testing May 1 12:05:45 mail mimedefang.pl[9050]: MDLOG,k412XBf0020621,bad_filename,Movie.wmv,video/x-ms-wmv,<[EMAIL PROTECTED]>,<[EMAIL PROTECTED]>,testing May 1 12:05:45 mail mimedefang.pl[9050]: MDLOG,k412XBf0020621,mail_in,,,<[EMAIL PROTECTED]>,<[EMAIL PROTECTED]>,testing May 1 12:05:45 mail mimedefang.pl[9050]: filter: k412XBf0020621: append_text_boilerplate=1 drop_with_warning=1 May 1 12:05:45 mail sendmail[20642]: k412XBf0020621: to=<[EMAIL PROTECTED]>, delay=00:02:33, xdelay=00:00:00, mailer=local, pri=61316, dsn=2.0.0, stat=Sent It kinda looks like it's going through twice. Luke =========================== mimedefang-filter =========================== # -*- 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.82 2005/02/14 16:56:24 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 '[EMAIL PROTECTED]' #*********************************************************************** $AdminAddress = '[EMAIL PROTECTED]'; $AdminName = "Admin"; #*********************************************************************** # Set the e-mail address from which MIMEDefang quarantine warnings and # user notifications appear to come. A good example would be # '[EMAIL PROTECTED]'. Make sure to have an alias for this # address if you want replies to it to work. #*********************************************************************** $DaemonAddress = '[EMAIL PROTECTED]'; #*********************************************************************** # 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 = 1; #*********************************************************************** # 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); #*********************************************************************** # To set whether there is an exempt user in the list # #*********************************************************************** $exempt_user = 0; $debug_loglevel = 0; #*********************************************************************** # 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 multiple "inline" # parts. $Stupidity{"NoMultipleInlines"} = 0; # Detect and load Perl modules detect_and_load_perl_modules(); # 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. 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 = '(mov|wmv|ade|adp|app|asd|asf|asx|bas|bat|chm|cmd|com|cpl|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|vb|vbe|vbs|vcs|vxd|wmd|wms|wmz|wsc|wsf|wsh|\{[^\}]+\})'; # Turn off most extension # $bad_exts = '(exe)'; # 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; } sub filter_bad_fileattach ($$) { my($entity, $bad_attach_exts) = @_; my($attach_re); # Do not allow: # - CLSIDs {foobarbaz} # - bad extensions (possibly with trailing dots) at end $attach_re = '\.' . $bad_attach_exts . '\.*$'; return 1 if (re_match($entity, $attach_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, $attach_re); } } } return 0; } ################################# # Per User Code # ################################# # Maybe log to syslog sub debug_log($$) { my ($level,$msg) = @_; if ($level<=$debug_loglevel) { md_syslog('info',$msg); } } # Strip strings sub address_strip_nc($) { my($a) = @_; $a = "" if (!defined($a)); $a =~ s/^[<\[]//; $a =~ s/[>\]]$//; return $a; } sub address_strip($) { my($a) = @_; return lc(address_strip_nc($a)); } # Check exempt user sub check_exempt_user_list($$) { my ($addr,$list) = @_; return 0 if (!($list && @{$list})); $addr = address_strip($addr); debug_log(3,"Checking for $addr"); foreach $line (@{$list}) { my $x = '*'; $line =~ s/^(\n?\s*)(.*)(\s*\n?)$/$2/; $line = lc($line); if ($line =~ /^(\S+)\s+(\S+.*)$/) { $x = $1; $line = $2; } $x = '*' if ($x eq '?'); $x =~ s/gray/grey/g; next unless ($x && $line); debug_log(3,"addrmatch") if ($addr =~ /^$line$/); if ( $addr =~ /^$line$/) { debug_log(3,"Matched $addr against $line"); return 1 } } return 0; } # Check if recipient(s) address is in exempt list sub check_recipient_exempt ($) { my ($addr) = @_; my $cfdata = read_list_file('/etc/mail/mimedefang-users'); return check_exempt_user_list($addr,$cfdata) } # Read file into array sub read_list_file($) { my ($fn) = @_; debug_log(4,"Checking list: $fn"); if (open(CF,"<$fn")) { debug_log(5,"Reading list: $fn"); my @cfdata = <CF>; close CF; debug_log(3,"Read list: $fn, $#cfdata"); return [EMAIL PROTECTED]; } return [EMAIL PROTECTED]; } #*********************************************************************** # %PROCEDURE: filter_sender # %ARGUMENTS: # sender, ip, host, helo # %RETURNS: # action # %DESCRIPTION: # Called just after MAIL FROM # Requires -s #*********************************************************************** sub filter_sender { my ($sender, $ip, $hostname, $helo) = @_; # Check if the host is in external whitelist if (check_recipient_exempt($sender)) { debug_log(2, "filter_sender: Exempt user, $sender"); return ('ACCEPT_AND_NO_MORE_FILTERING', "Ok, $sender is exempt, go ahead."); } return ('CONTINUE', "Ok, go ahead."); } #*********************************************************************** # %PROCEDURE: filter_recipient # %ARGUMENTS: # recipient, sender, ip, host, first, helo, rcpt_mailer, rcpt_host, rcpt_addr # %RETURNS: # action # %DESCRIPTION: # Called just after RCPT TO # Requires -t #*********************************************************************** sub filter_recipient { my ($recipient, $ip, $hostname, $helo) = @_; # Check if the host is in external whitelist if (check_recipient_exempt($recipient)) { $exempt_user = 1; debug_log(2, "filter_recipient: Exempt user, $recipient, exempt_user is $exempt_user"); return ('CONTINUE', "Ok, go ahead. exempt_user set."); # return ('ACCEPT_AND_NO_MORE_FILTERING', "Ok, $sender is exempt, go ahead."); } md_graphdefang_log('filter_recipient', $recipient ); return ('CONTINUE', "Ok, go ahead."); } #*********************************************************************** # %PROCEDURE: filter_begin # %ARGUMENTS: # None # %RETURNS: # Nothing # %DESCRIPTION: # Called just before e-mail parts are processed #*********************************************************************** sub filter_begin () { # Stream by Domain if(stream_by_domain()) { return; } # Check if exempt user in list of recipients, split up if true if( $exempt_user eq 1 ) { if(stream_by_recipient()) { $exempt_user = 0; return; } } # 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(); } # Copy original message into work directory as an "mbox" file for # virus-scanning md_copy_orig_msg_to_work_dir_as_mbox_file(); # Scan for viruses if any virus-scanners are installed my($code, $category, $action) = message_contains_virus(); # Lower level of paranoia - only looks for actual viruses $FoundVirus = ($category eq "virus"); # Higher level of paranoia - takes care of "suspicious" objects # $FoundVirus = ($action eq "quarantine"); if ($FoundVirus) { md_graphdefang_log('virus', $VirusName, $RelayAddr); md_syslog('warning', "Discarding because of virus $VirusName"); return action_discard(); } if ($action eq "tempfail") { action_tempfail("Problem running virus-scanner"); md_syslog('warning', "Problem running virus scanner: code=$code, category=$category, action=$action"); } } #*********************************************************************** # %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) = @_; my($warnmsg) = "An 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"; 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($Domain eq "domainname.com.au") { md_graphdefang_log('exempt user', $recipient, $exempt_user); if( $exempt_user eq 1 ) { $bad_attach_exts = '(exe)'; if (filter_bad_fileattach($entity, $bad_attach_exts)) { md_graphdefang_log('bad_filename', $fname, $type); return action_drop_with_warning($warnmsg); } # 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 contact\nthe 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(); } elsif (filter_bad_filename($entity)) { md_graphdefang_log('bad_filename', $fname, $type); return action_drop_with_warning($warnmsg); } # } elseif($Domain eq "test.com.au") { # #Filter actions for test.com.au # } else { if (filter_bad_filename($entity)) { md_graphdefang_log('bad_filename', $fname, $type); return action_drop_with_warning($warnmsg); } # 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 contact\nthe 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 if ($Features{"SpamAssassin"}) { if (-s "./INPUTMSG" < 100*1024) { # Only scan messages smaller than 100kB. Larger messages # are extremely unlikely to be spam, and SpamAssassin is # dreadfully slow on very large messages. my($hits, $req, $names, $report) = spam_assassin_check(); my($score); if ($hits < 40) { $score = "*" x int($hits); } else { $score = "*" x 40; } # We add a header which looks like this: # X-Spam-Score: 6.8 (******) NAME_OF_TEST,NAME_OF_TEST # The number of asterisks in parens is the integer part # of the spam score clamped to a maximum of 40. # MUA filters can easily be written to trigger on a # minimum number of asterisks... if ($hits >= $req) { action_change_header("X-Spam-Score", "$hits ($score) $names"); md_graphdefang_log('spam', $hits, $RelayAddr); # If you find the SA report useful, add it, I guess... action_add_part($entity, "text/plain", "-suggest", "$report\n", "SpamAssassinReport.txt", "inline"); } else { # Delete any existing X-Spam-Score header? action_delete_header("X-Spam-Score"); } } } # 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 copy of mail to backup mbox add_recipient('[EMAIL PROTECTED]'); # Set exempt_user back to 0 $exempt_user = 0; } # DO NOT delete the next line, or Perl will complain. 1; _______________________________________________ NOTE: If there is a disclaimer or other legal boilerplate in the above message, it is NULL AND VOID. You may ignore it. Visit http://www.mimedefang.org and http://www.roaringpenguin.com MIMEDefang mailing list [email protected] http://lists.roaringpenguin.com/mailman/listinfo/mimedefang

