# -*- Perl -*- #*********************************************************************** # use DBI; $dbh= DBI->connect("DBI:mysql:spamassassin:IP","user","pass") or die "Can't connect to the database: $DBI::errstr\n"; #*********************************************************************** # 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@hsc.wvu.edu'; $AdminName = "Postmaster"; #*********************************************************************** # 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 = 'postmaster@hsc.wvu.edu'; #*********************************************************************** # If you set $AddWarningsInline to 1, then MIMEDefang tries *very* hard # to add warnings directly in the message body (text or html) rather # 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_log_enable(); # You may optionally provide a syslogging facility by passing an # argument such as: md_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; #*********************************************************************** # %PROCEDURE: filter_begin # %ARGUMENTS: # None # %RETURNS: # Nothing # %DESCRIPTION: # Called just before e-mail parts are processed #*********************************************************************** sub filter_begin () { if(stream_by_spam_list()) { 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(); } $foundSpam = "no"; if((-s "./INPUTMSG") <= (100*1024)) { ($hits,$req,$names,$report) = spam_assassin_check(); ### users specific hit count my $recip = $Recipients[0]; $recip = lc($recip); $recip =~ s/[<>]//g; $sth2 = $dbh->prepare("SELECT value FROM userpref WHERE username = '$recip' && preference= 'required_hits'"); $sth2->execute or die "Can't execute SQL statement: $DBI::errstr\n";; $sth2->bind_columns(\$req); $sth2->fetchrow_arrayref; md_graphdefang_log('Database value', $req); md_graphdefang_log('Recipient is Database value', $recip); if($hits >= $req) { $foundSpam = "yes"; } } } #*********************************************************************** # %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 Spamassassin found spam, convert text/html to text/plain # if ($foundSpam eq "yes") { # if ($type eq "text/html") { # $entity->head->mime_attr("content-type" => "text/plain"); # } # } 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) = @_; # 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(); } # If SpamAssassin found SPAM, append report. We do it as a separate # attachment of type text/plain sub filter_end ($) { my($entity) = @_; # No sense doing any extra work return if message_rejected(); # Spam checks if SpamAssassin is installed if (($Features{"SpamAssassin"}) && should_check_for_spam($Recipients[0])) { 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. if ($hits >= $req) { md_graphdefang_log('spam', $hits, $RelayAddr); 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... action_change_header("X-Spam-Score", "$hits ($score) $names"); ### Attaches Spam Assassin Report # action_add_part($entity, "text/plain", "-suggest", # "$report\n", # "SpamAssassinReport.txt", "inline"); action_change_header("Subject", "{Possible SPAM} $Subject"); } else { action_add_header("X-Spam-Status", "No, hits=$hits req=$req $names"); } } } # If you don't mind HTML mail, comment out the next line. remove_redundant_html_parts($entity); } ##################################################################################### # Procedures for spam check ##################################################################################### sub should_check_for_spam ($) { my($recip) = @_; $recip = lc($recip); $recip =~ tr/<>//d; $Sender = lc($Sender); $Sender =~ tr/<>//d; $sth = $dbh->prepare("SELECT checkspam FROM userpref WHERE username = '$recip' && preference= 'rewrite_subject'"); $sth->execute or die "Can't execute SQL statement: $DBI::errstr\n";; $sth->bind_columns(\$checkspam); $sth->fetchrow_arrayref; if ($checkspam eq 0) { md_graphdefang_log('Recipient is equal to', $recip); md_graphdefang_log('Checkspam is equal to', $checkspam); return 0; } $sth1 = $dbh->prepare("SELECT value FROM userpref WHERE username = '$recip' && preference= 'whitelist_from'"); $sth1->execute or die "Can't execute SQL statement: $DBI::errstr\n";; my (@whitelist); my $i = 0; my $count; my $j = $sth1->rows; my @rows; while (@row = $sth1->fetchrow()) { $whitelist[$i] = @row[0]; if ($whitelist[$i] eq $Sender) { return 0; } $i++; } $count = $i; return 1; $sth->finish; $dbh->disconnect; } # Stream message if required. If some recipients are on spam # list and others are not, re-mail two copies: One to the spam list # and the other to those not on the spam list. sub stream_by_spam_list () { my(@on_list, @off_list, $recip); foreach $recip (@Recipients) { if (should_check_for_spam($recip)) { push(@on_list, $recip); } else { push(@off_list, $recip); } } if ($#on_list >= 0 && $#off_list >= 0) { # Some on, some off -- remail resend_message(@on_list); resend_message(@off_list); $TerminateAndDiscard = 1; return 1; } return 0; } # DO NOT delete the next line, or Perl will complain. 1;