# -*- 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.65 2003/07/03 23:56:05 dfs Exp $ #*********************************************************************** #Modified by KAM: 04-01-04 # Added the command to quarantine the entire message if a bad_filename is # received #Modified by KAM: 03-30-04 # Upgraded to mirror changes in 2.42 Beta Release Filter from DFS including: # Changes for filter_bad_filename to look inside zip files # Remove message_contains_virus subroutine # Remove entity_contains_virus subroutine # Suspicious Chars in Headers no longer quarantines the message # FoundViruses are now discarded due to the extremely good hit rate for virus # scanners and the lack of needed files from virus-laden emails #Modified by KAM: 01-16-04 # Network tests are on by default # AWL Use is on by default # Bad Exts bugfix # Vexira Support Added (though not recommended to use ;-) ) # SpamAssassin implementation differences to mimic more of SpamAssassin's features # Removing HTML emails is not the default #Modified by KAM: 04-16-04 # Added Columbia iframe/object/script filter #Modified by KAM: 05-25-04 # Changed action_delete_header calls to action_delete_all_headers #Modified by KAM: 05-31-04 # Changed build_status_line to put in the bayesian learn information # Modified by KAM: 06-01-04 # Disabled bayesian learn information. Requires a status variable not available in the # subfilter #Modified by KAM: 06-02-04 # Updated Columbia filter to fix entity handler issues #Modified by KAM: 08-09-04 # Fixed some strict issues #Modified by KAM: 06-14-04 # Added Really_Bad_Filename with change from Paul Murphy #Modified by KAM: 03-22-05 # Add a | to filter_bad_filename from Matthew van Eerde #Modified by KAM: 10-11-05 # Added a filter_initialize, a filter_sender, and the check primary relay routines #Modified by KAM: 11-4-05 # Added new version of check valid mx routine & small update to filter_sender to allow bounce message #Modified by KAM: 10-18-2006 # Changed the SpamAssassin Initialization routine # Added the latest check valid mx routine via the cpan modules (http://search.cpan.org/~kmcgrail/Net-validMX-2.2.0/) # Added detect_and_load_perl_modules() call and also changed the sa_object per MD Author # Added filter_sender and is_authorized_routines with helo checks #Modified by KAM: 10-20-2006 # Added reverse DNS check use strict; #*********************************************************************** # 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@mydomain.com' #*********************************************************************** $AdminAddress = 'postmaster@localhost'; $AdminName = "MIMEDefang Administrator's Full Name"; #*********************************************************************** # Set the e-mail address from which MIMEDefang quarantine warnings and # user notifications appear to come. A good example would be # 'mimedefang@mydomain.com'. Make sure to have an alias for this # address if you want replies to it to work. #*********************************************************************** $DaemonAddress = 'mimedefang@localhost'; #*********************************************************************** # 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); #*********************************************************************** # 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; # Disable SpamAssassin (For Example, to use procmail mode for SpamAssassin); # $Features{"SpamAssassin"} = ""; # Disable Checking inside Zip # $Features{"Archive::Zip"} = ""; # 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. my $sa_object; if ($Features{"SpamAssassin"}) { #$SALocalTestsOnly = 1; # I do NOT want network tests $SALocalTestsOnly = 0; # I DO want network tests #Switched to $sa_object as returned by SA per DFS $sa_object = spam_assassin_init() if defined(spam_assassin_init()); # If you want to use auto-whitelisting: if (defined($sa_object)) { use Mail::SpamAssassin::SQLBasedAddrList; my $awl = Mail::SpamAssassin::SQLBasedAddrList->new(); $sa_object->set_persistent_address_list_factory($awl) if defined($awl); } } sub filter_initialize { #for Reverse DNS use Net::DNS; #for Check Valid MX use Net::validMX qw(check_valid_mx); } # This procedure returns true for entities with bad filenames. sub filter_bad_filename ($) { my($entity) = @_; my($bad_exts, $re); # Really Bad extensions we never want $bad_exts = '(pif|com|scr|bat|\{[^\}]+\})'; $re = '\.' . $bad_exts . '\.*$'; return 2 if (re_match($entity, $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; } sub is_authorized_sender { my ($sender, $RelayAddr) = @_; my ($auth, $popauthdb); $auth = 0; #MOVED TO THE TOP TO SHORT CIRCUIT THE DB OPEN if ($RelayAddr =~ /^127\.0\.0\.1$/) { return 1; } #CHECKS FOR LOCAL IP RANGES if ($RelayAddr =~ /^209\.225\.49\.\d{1,3}$/) { return 1; } if ($RelayAddr =~ /^209.190.235.18$/) { return 1; } #CHECK YOUR AUTH DATABASE OR SENDMAIL VARIABLE FOR AUTH STATUS HERE return $auth; } sub filter_sender { my ($sender, $ip, $hostname, $helo) = @_; my ($rv, $reason); #md_syslog('warning', "Testing $sender, $ip, $hostname, $helo"); if (&is_authorized_sender($sender, $RelayAddr)) { return ('CONTINUE', "ok"); } #BOUNCE? if ($sender ne '<>') { ($rv, $reason) = &check_valid_mx($sender); if ($rv) { if ($reason =~ /Resolution Problem/i) { md_syslog('warning', "Net::validMX Resolution Problem: $sender - $reason."); } } else { md_syslog('warning', "Rejecting $sender - Invalid MX: $reason."); return ('REJECT', "Sorry; $sender has an invalid MX record: $reason."); } } #LOCALHOST? if ($helo =~ /^\[?(localhost|127.0.0.1)\]?$/i) { md_syslog('warning', "Rejecting $sender because invalid helo where $helo ($ip) is invalid localhost."); return('REJECT', "Rejecting $sender because $ip is not localhost."); } #SPOOF OF MY NAME? if ($helo =~ /^(mail.pccc.com|intel1.peregrinehw.com|intel1.pccc.com)$/i) { md_syslog('warning', "Rejecting $sender because invalid helo where $helo ($ip) is invalidly trying to use our machine or MX name."); return('REJECT', "$ip / $helo is not valid."); } #USING MY IP RANGE? if ($helo =~ /^\[?209\.225\.49\.\d{1,3}\]?$/) { md_syslog('warning', "Rejecting $sender because invalid helo where $ip is not authorized to use helo of $helo."); return('REJECT', "Rejecting $sender because invalid helo where $ip is not $helo."); } #USING THE WORD FRIEND? if ($helo =~ /^friend$/) { md_syslog('warning', "Rejecting $sender because invalid helo of $helo."); return('REJECT', "$helo is not valid."); } #USING A NON FQDN or bracketed IP AS REQUIRED BY RFC2821 if (length($helo) < 3 or $helo !~ /\./) { md_syslog('warning', "Rejecting $sender because invalid helo where $helo ($ip) is too short or has no periods."); return('REJECT', "Rejecting $sender because $helo ($ip) is invalid."); } return ('CONTINUE', "ok"); } #*********************************************************************** # %PROCEDURE: filter_begin # %ARGUMENTS: # None # %RETURNS: # Nothing # %DESCRIPTION: # Called just before e-mail parts are processed #*********************************************************************** sub filter_begin () { # 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(); } # Scan for viruses if any virus-scanners are installed my($code, $category, $action) = message_contains_virus(); my ($FoundVirus); # 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) = @_; 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(); } my ($bad_filename_status); $bad_filename_status = filter_bad_filename($entity); if ($bad_filename_status == 2) { md_graphdefang_log('really_bad_filename', $fname, $type); action_bounce("This file type not accepted here"); return action_discard(); } elsif ($bad_filename_status) { md_graphdefang_log('bad_filename', $fname, $type); action_quarantine_entire_message; 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 contact\nthe sender and arrange an alternate means of receiving it.\n"); } # 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); } } #Disable bad HTML code -- Based on work by Columbia University / Joseph Brennan #Modified by KAM 2004-04-16 #Modified by KAM 2004-04-21 to add slurp of entire message and one regexp check + size check #Modified by KAM 2004-06-02 to add a check for defined bodyhandle and path to prevent issues. #Modified by KAM 2004-08-09 to add $io to defined variables thanks to Tony Nelson if ($type eq "text/html") { my($currentline, $output, $badtag, $delimiter_backup, $sizelimit, $bh, $path, $io); $badtag = 0; $output = ""; $sizelimit = 1048576; #1MB #max size of an email you want to check in bytes $delimiter_backup = $/; $bh = $entity->bodyhandle(); if (defined($bh)) { $path = $bh->path(); } if (defined($path)) { if (-s $path <= $sizelimit) { if ($io = $entity->open("r")) { undef $/; # undef the seperator to slurp it in. $output = $io->getline; $io->close; $badtag = $output =~ s/<(iframe|script|object)\b/open("w")) { $io->print($output); $io->close; } md_graphdefang_log('modify',"$badtag Iframe/Object/Script tag(s) deactivated by MIMEDefang"); action_change_header("X-Warning", "$badtag Iframe/Object/Script tag(s) deactivated by MIMEDefang"); action_rebuild(); } $/ = $delimiter_backup; } } } } 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 my ($bad_filename_status); $bad_filename_status = filter_bad_filename($entity); if ($bad_filename_status == 2) { md_graphdefang_log('really_bad_filename', $fname, $type); action_bounce("This file type not accepted here"); return action_discard(); } elsif ($bad_filename_status) { 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(); my ($spamtest_enabled, $recip, $rewrite_subject, $report_safe); my ($res, $packet, @answer, $reverse, $reverse_subdomain, $has_subdomain, $suspect_spammy_country_tlds); #REVERSE DNS CHECK if (&is_authorized_sender($Sender, $RelayAddr) < 1) { $res = Net::DNS::Resolver->new; $suspect_spammy_country_tlds = 1; if (defined ($res)) { $res->tcp_timeout(30); #Number of Seconds before query will fail $res->udp_timeout(30); #Number of Seconds before query will fail #Perform a reverse DNS lookup and set headers for SpamAssassin Scoring based on AOL's reverse DNS policy as of Sept/22/2006 #See http://postmaster.aol.com/info/rdns.html $packet = $res->send($RelayAddr); if (defined ($packet)) { #print "No Error - May or may not have resolved. Check ancount.\n"; if (defined ($packet->answer) && $packet->header->ancount) { #HAS A REVERSE ENTRY @answer = $packet->answer; if ($answer[0]->type eq "PTR") { $reverse = $answer[0]->{'ptrdname'}; #TO AVOID FAILING DYNDNS.ORG, ETC. WE ARE ONLY TESTING THE SUBDOMAIN(s) (i.e. the part to the left of the domain) $has_subdomain = ($reverse =~ s/\././g > 1); if ($has_subdomain) { $reverse_subdomain = $reverse; $reverse_subdomain =~ s/[^\.]*\.[^\.]*$//; } if ($reverse =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ or $reverse !~ /\./ or $reverse =~ /in-addr.arpa/i) { #FAILED REQUIREMENT HAD AN INVALID IP QUAD, CONTAINED IN-ADDR.ARPA OR FAILED TO USE A FQDN action_change_header("X-KAM-Reverse", "Failed - $reverse - Reverse PTR was invalid ip quad, contained in-addr.arpa or failed to use a FQDN"); &append_header_immediately(header=>"X-KAM-Reverse: Failed - $reverse - Reverse PTR was invalid ip quad, contained in-addr.arpa or failed to use a FQDN"); } elsif ($has_subdomain && $reverse_subdomain =~ /pool|dhcp|dip|dyn|dial|home|cable|dsl|\d{1,3}.\d{1,3}.\d{1,3}|\d{9,12}/i) { #REVERSE DNS SUBDOMAIN ENTRY IS SUSPECT action_change_header("X-KAM-Reverse", "Suspect - $reverse - Reverse PTR contains data that indicates it is a dynamic IP"); &append_header_immediately(header=>"X-KAM-Reverse: Suspect - $reverse - Reverse PTR contains data that indicates it is a dynamic IP"); } elsif ($suspect_spammy_country_tlds > 0 && $reverse =~ /\.(nl|ru|br|ae|cz|cy|pl|il)$/) { #SPAMMY COUNTRIES action_change_header("X-KAM-Reverse", "Suspect - $reverse - Reverse PTR contains data that indicates it is outside the US"); &append_header_immediately(header=>"X-KAM-Reverse: Suspect - $reverse - Reverse PTR contains data that indicates it is outside the US"); } else { #VALID REVERSE DNS. SCORE AS HAM action_change_header("X-KAM-Reverse", "Passed - Reverse DNS of $reverse"); &append_header_immediately(header=>"X-KAM-Reverse: Passed - Reverse DNS of $reverse"); } } } else { #FAILED REQUIREMENT DID NOT HAVE A REVERSE ENTRY action_change_header("X-KAM-Reverse", "Missing - Reverse PTR for $RelayAddr was missing!"); &append_header_immediately(header=>"X-KAM-Reverse: Missing - Reverse PTR for $RelayAddr was missing!"); } } else { #Undef = Error. DO NOT BASE ANY CODE ON THIS RETURN } } } #END REVERSE DNS CHECK # 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(); # Modified to add headers hopefully identical (as close as possible) to spamassassin default -- KAM 07-09-03 # # SpamAssassin modifies three headers X-Spam-Status, X-Spam-Level & X-Spam-Checker-Version. These are edited # regardless of positive or negative SPAM status # X-Spam-Status: No, hits=1.4 required=7.0 # tests=AWL,FROM_HAS_MIXED_NUMS,HTML_40_50,MAILTO_TO_SPAM_ADDR,MISSING_OUTLOOK_NAME # version=2.55 action_change_header("X-Spam-Status", &build_status_line($hits, $req, $names, $report)); # X-Spam-Level: * # SpamAssassin can show up to 100 *'s my ($score); if ($hits < 100) { $score = "*" x int($hits); } else { $score = "*" x 100; } my ($extra); unless ($score) { $score = " "; } action_change_header("X-Spam-Level", "$score"); # X-Spam-Checker-Version: SpamAssassin 2.55 (1.174.2.19-2003-05-19-exp) # No warnings to STDERR using $^W. Gets rid of logged notes caused by blank variable names below $^W = 0; action_change_header("X-Spam-Checker-Version", "SpamAssassin $Mail::SpamAssassin::VERSION$Mail::SpamAssassin::EXTRA_VERSION ($Mail::SpamAssassin::SUB_VERSION)"); $^W = 1; if ($hits >= $req) { md_graphdefang_log('spam', $hits, $RelayAddr); #MIMEDEFANG DEFAULT HEADER -- SHOWS UP TO 40 *'s $score = substr($score,0,40); action_change_header("X-Spam-Score", "$hits ($score) $names"); # If you find the SA report useful, add it, I guess... # No longer needed. KAM 10-29-2003. Using Report_safe-esque routine below #action_add_part($entity, "text/plain", "-suggest", "$report\n", "SpamAssassinReport.txt", "inline"); # Old SpamAssassin Subject Change Rule # Change Subject: header action_change_header("Subject", "*****SPAM***** $Subject"); # BEGIN REPORTSAFE - KAM 10-29-2003 # Implement Report_Safe -- Thanks to James W. Curtis for the original starting code! my ($container, $parser, $original, $report2); $container = MIME::Entity->build(Type => 'message/rfc822', Description => 'Original message before MIMEDefang', Data => [ "" ]); $parser = new MIME::Parser; open(IN, '< INPUTMSG'); $original = $parser->parse(\*IN); close(IN); $original->head()->replace('X-Relay-Addr', $RelayAddr); $RelayHostname ||= 'N/A'; $original->head()->replace('X-Relay-Host', $RelayHostname); $original->head()->replace('X-Relay-Time', scalar(localtime)); # Add the original message to the container $container->add_part($original); $report2 = MIME::Entity->build(Type => 'text/plain', Description => 'SpamAssassin Warning', Data => ["$report\n"], Disposition => "inline", Encoding => "-suggest", Filename => "SpamAssassinReport.txt"); $entity->parts([$report2]); $entity->head()->mime_attr('content-type' => 'multipart/mixed'); $entity->head()->mime_attr('content-type.boundary' => '------------=_' . scalar(time) . "-$$-nikc"); $entity->add_part($container); action_rebuild(); # END REPORTSAFE } else { # Delete any existing X-Spam-Score header? action_delete_all_headers("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 don't mind HTML mail, comment out the next line. #remove_redundant_html_parts($entity); # If your Server is an inbound gateway, this will be correct. Otherwise, logic # to decide mail_in will need to be inserted. 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(); } sub build_status_line { # Based on _build_status_line from Mail/SpamAssassin/PerMsgStatus.pm # KAM 07-09-03 # Doesn't handle the spam/ham bayes determination as I don't think I can accurately # get that information # KAM 05-31-04 # Thanks to major input from Chris Gauch as we are not implement the spam/ham bayes info # KAM 6-1-04 # Still problems with the autolearn information. the code is here in case we get it working later my ($hits, $req, $names, $report, $autolearn) = @_; my $line; $line = (($hits >= $req) ? "Yes, " : "No, "); $line .= sprintf("hits=%2.1f required=%2.1f\n", $hits, $req); if($_ = $names) { $Text::Wrap::columns = 74; $Text::Wrap::huge = 'overflow'; $Text::Wrap::break = '(?<=,)'; $line .= Text::Wrap::wrap("\ttests=", "\t ", $_) . "\n"; } else { $line .= "\ttests=none\n"; } #if ($autolearn ne "") { # $line .= "\tautolearn=$autolearn\n"; #} $line .= "\tversion=" . Mail::SpamAssassin::Version(); return $line; } sub change_header_immediately { my (%params) = @_; &append_header_immediately(%params, change=>1); #md_syslog('warning', "Changing $params{'header'} immediately"); } sub append_header_immediately { #IN ORDER TO HAVE A HEADER TEST IN SPAMASSASSIN REACT TO DATA THAT MD CREATES, WE HAVE TO EDIT THE INPUTMSG FILE PRIOR TO #CALLING SA. USING action_change_header, ETC WILL NOT WORK. NO CHANGES TO THIS FILE ARE SAVED SO IF YOU WANT HEADERS #ADDED OR APPENDED, USE STANDARD MD CALLS LIKE: # action_change_header('Date', $date); # &change_header_immediately(header=>"Date: $date"); my (%params) = @_; my ($filehandle, $output, $firstlineonly, $header); $filehandle = new IO::File('+< ./INPUTMSG'); $firstlineonly = 1; $params{'change'} ||= 0; $header = $params{'header'}; $header =~ s/^([^:.]*): .*$/$1/; if (-s "./INPUTMSG" == 0) { md_syslog('warning', "INPUTMSG is Size 0"); } if (-s "./INPUTMSG" < 512*1024 && $filehandle) { while (<$filehandle>) { if ($params{'change'} > 0) { if ($_ =~ /^$header:/) { $output .= "$params{'header'}\n"; } else { $output .= $_; } } elsif ($_ =~ /^$/ && $firstlineonly) { $output .= "$params{'header'}\n$_"; $firstlineonly = 0; } else { $output .= $_; } } if ($output eq '') { md_syslog('warning', '$output is blank'); } seek $filehandle, 0, 0; print $filehandle $output; close ($filehandle); } #md_syslog('warning', "Adding $params{'header'} immediately"); } # DO NOT delete the next line, or Perl will complain. 1;