[Mimedefang] Re: example of DNS sanity-checking
Kees Cook
kees at osdl.org
Wed Aug 27 14:27:01 EDT 2003
On Wed, Aug 27, 2003 at 11:22:03AM -0700, Kees Cook wrote:
> The attached mimedefang-filter was originally the base example, but I've
Or rather, attached HERE... :)
--
Kees Cook
Open Souce Development Lab
kees at osdl.org
-------------- 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.68 2003/08/08 18:28:49 dfs Exp $
#***********************************************************************
# OSDL extra tests
use Net::DNS;
$osdl = {
# Details on how to manage our DNS resolver
resolver => Net::DNS::Resolver->new(
retry=>1,
# tcp_timeout => 10,
# udp_timeout => 10,
),
# Which IPs are allowed to handle our domain
helo_forging => {
'(^|\.)osdl\.(org|net)$' => '^65\.172\.181\.[45]$',
},
# List of IP address regexs to pass immediately
ip_white_list => [
],
# List of IP address regexs to reject immediately
ip_black_list => [
# For example:
#'^172\.20\.1\.45$',
#'^192\.168\.',
],
# List of DNS name regexs to pass immediately
name_white_list => [
],
# List of DNS name regexs to reject immediately
name_black_list => [
#'^webtest\.',
# dynamic address
'\d+-\d+-\d+-\d+',
# Misc sites
'4u2refer\.net$',
'cool-dealz\.com$',
'solutionspromos\.com$',
'optinbargains4u\.com$',
'summertimespecials\.com$',
'expappcc\d+\.dvd\.com$',
'mycoolbargains\.com$',
'\d+\.postnote.com$',
'4pitasake\.com$',
'users\.co\.josephine\.or\.us$',
'a\.ew01\.com$',
'adshas6\.com$',
'e\.rb\d+\.beeshoney\.net$',
'uclickusave\.com$',
'4jo2ely\.com$',
'dsl-.*dial\.inet\.fi$',
'btfusion\.com$',
'bstmail\.com$',
'fireballmail\.com$',
'chesapeakemail\.com$',
'mybeyondoffers\.net$',
'femmail\.com$',
'tomts\d+\.bellnexxia\.net$',
'lessthanyouthink\.com$',
'sender4\.com$',
'tgr\d+\.thegasrag\.com$',
'ustroistvo\.com$',
'infostrea\.us$',
'nnsuserc\d+\.nns\.ne\.jp$',
'tpcper\.com$',
'mail\.odvs\.de$',
'\d+\.dsl\.easynet\.co\.uk$',
'intaspharma\.com$',
'postalbureau\.com$',
'quickinspirations\.com$',
'econpc\.com$',
'amazingoffersdirect\.com$',
'hm02\.com$',
'kidspark\.com\.tw$',
'list\d+\.thundercut\.com$',
'client\d+\.attbi\.com$',
'homelink\.vk\.com\.br$',
'americanhotties\.net$',
'emailselections\.com$',
'dc03\.com$',
'mx\d+\.mail\.ru$',
],
# If there is no reverse DNS, reject
no_dns_reject => 1,
# If ip->name->ip doesn't resolve, reject
spoofed_dns_reject => 1,
# Turn on matching debugging?
debug => 1,
# Turn on normal resolver debugging?
debug_lookup => 1,
};
#***********************************************************************
# 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 = 'rt at osdl.org';
$AdminName = "OSDL System Administration";
#***********************************************************************
# 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 = 'postmaster at osdl.org';
#***********************************************************************
# 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;
# 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 = '(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 or
# followed by non-alphanum
$re = '\.' . $bad_exts . '\.*([^-A-Za-z0-9_.,]|$)';
return re_match($entity, $re);
}
# Scan for a virus using the first supported virus scanner we find.
sub message_contains_virus () {
return message_contains_virus_avp() if ($Features{'Virus:AVP'});
return message_contains_virus_fprot() if ($Features{'Virus:FPROT'});
return message_contains_virus_fsav() if ($Features{'Virus:FSAV'});
return message_contains_virus_hbedv() if ($Features{'Virus:HBEDV'});
return message_contains_virus_nai() if ($Features{'Virus:NAI'});
return message_contains_virus_bdc() if ($Features{'Virus:BDC'});
return message_contains_virus_nvcc() if ($Features{'Virus:NVCC'});
return message_contains_virus_rav() if ($Features{'Virus:RAV'});
return message_contains_virus_sophie() if ($Features{'Virus:SOPHIE'});
return message_contains_virus_trophie() if ($Features{'Virus:TROPHIE'});
return message_contains_virus_sophos() if ($Features{'Virus:SOPHOS'});
return message_contains_virus_trend() if ($Features{'Virus:TREND'});
return message_contains_virus_filescan() if ($Features{'Virus:FileScan'});
return message_contains_virus_clamd() if ($Features{'Virus:CLAMD'});
return message_contains_virus_clamav() if ($Features{'Virus:CLAMAV'});
return message_contains_virus_carrier_scan() if ($Features{'Virus:SymantecCSS'});
return (wantarray ? (0, 'ok', 'ok') : 0);
}
# Scan for a virus using the first supported virus scanner we find.
sub entity_contains_virus ($) {
my($e) = @_;
return entity_contains_virus_avp($e) if ($Features{'Virus:AVP'});
return entity_contains_virus_fprot($e) if ($Features{'Virus:FPROT'});
return entity_contains_virus_fsav($e) if ($Features{'Virus:FSAV'});
return entity_contains_virus_hbedv($e) if ($Features{'Virus:HBEDV'});
return entity_contains_virus_nai($e) if ($Features{'Virus:NAI'});
return entity_contains_virus_bdc($e) if ($Features{'Virus:BDC'});
return entity_contains_virus_nvcc($e) if ($Features{'Virus:NVCC'});
return entity_contains_virus_rav($e) if ($Features{'Virus:RAV'});
return entity_contains_virus_sophie($e) if ($Features{'Virus:SOPHIE'});
return entity_contains_virus_trophie($e) if ($Features{'Virus:TROPHIE'});
return entity_contains_virus_sophos($e) if ($Features{'Virus:SOPHOS'});
return entity_contains_virus_trend($e) if ($Features{'Virus:TREND'});
return entity_contains_virus_filescan($e) if ($Features{'Virus:FileScan'});
return entity_contains_virus_clamd($e) if ($Features{'Virus:CLAMD'});
return entity_contains_virus_clamav($e) if ($Features{'Virus:CLAMAV'});
return entity_contains_virus_carrier_scan($e) if ($Features{'Virus:SymantecCSS'});
return (wantarray ? (0, 'ok', 'ok') : 0);
}
# This returns 4 possible states, along with the DNS name as an array
$DNS_NAME_OK=0;
# 0 Authoritative DNS name found
$DNS_NAME_MISSING=1;
# 1 Authoritatively no DNS name
$DNS_NAME_FAILED=2;
# 2 DNS name lookup failure ("temporary")
$DNS_NAME_SPOOFED=3;
# 3 Authoritative DNS name found, but IP does not match 'A'
# record lookup for that name
sub osdl_warn
{
my ($msg)=@_;
md_syslog('info', "DNS: $msg");
}
sub osdl_spoofing
{
my ($ip,$name)=@_;
my ($result)=(0);
# Force to lower-case
$name=lc($name);
osdl_warn "Looking up '$name'\n" if ($osdl->{debug_lookup});
if (defined($osdl->{resolver})) {
my $query = $osdl->{resolver}->query($name,'A');
if (defined($query)) {
my @ips = grep { $_->type eq 'A' } $query->answer;
foreach my $rec (@ips) {
my $address=$rec->address;
osdl_warn "Got '$address'\n" if ($osdl->{debug_lookup});
return 0 if ($ip eq $address);
}
$result=1; # No matching IP found in list of addresses
}
else {
my $reason = $osdl->{resolver}->errorstring;
if ($reason eq "NXDOMAIN") {
osdl_warn "DNS Missing ($reason)\n"
if ($osdl->{debug_lookup});
# NO DNS FOR THIS NAME: if there is no forward, consider it spoofed
$result=1;
}
else {
osdl_warn "DNS error: $reason\n"
if ($osdl->{debug_lookup});
# Other dns failure: continue
}
}
}
return $result;
}
sub osdl_dns_name
{
my ($ip)=@_;
my ($result,$name)=($DNS_NAME_FAILED,"");
osdl_warn "Looking up '$ip'\n" if ($osdl->{debug_lookup});
if (defined($osdl->{resolver})) {
my $query = $osdl->{resolver}->query($ip,'PTR');
# Did we get an answer?
if (defined($query)) {
my @name = grep { $_->type eq 'PTR' } $query->answer;
$name=$name[0] ? $name[0]->ptrdname : undef;
# Is there a name for this host?
if (defined($name)) {
# Force to lower-case
$name=lc($name);
osdl_warn "Got '$name'\n" if ($osdl->{debug_lookup});
# Do Spoofing check here
if (osdl_spoofing($ip,$name)) {
$result=$DNS_NAME_SPOOFED;
}
else {
$result=$DNS_NAME_OK;
}
}
else {
osdl_warn "DNS Missing (no name)\n"
if ($osdl->{debug_lookup});
# NO DNS FOR THIS IP: REJECT
$result=$DNS_NAME_MISSING;
}
}
else {
my $reason = $osdl->{resolver}->errorstring;
if ($reason eq "NXDOMAIN") {
osdl_warn "DNS Missing ($reason)\n"
if ($osdl->{debug_lookup});
# NO DNS FOR THIS IP: REJECT
$result=$DNS_NAME_MISSING;
}
else {
osdl_warn "DNS error: $reason\n"
if ($osdl->{debug_lookup});
# Other dns failure: continue
}
}
}
$name=lc($name);
return ($result,$name);
}
sub osdl_found_spammer
{
my ($ip)=@_;
# Regex against the IPs
foreach my $match (@{$osdl->{ip_white_list}}) {
if ($ip =~ /$match/) {
osdl_warn "IP Whitelist: $ip matches $match\n"
if ($osdl->{debug});
return (CONTINUE,"Matched IP whitelist: $ip");
}
}
foreach my $match (@{$osdl->{ip_black_list}}) {
if ($ip =~ /$match/) {
osdl_warn "IP Blacklist: $ip matches $match\n"
if ($osdl->{debug});
return (REJECT,"Matched IP blacklist: $ip");
}
}
# Attempt DNS *name* checks
my ($result,$name) = osdl_dns_name($ip);
#osdl_warn "OK: $DNS_NAME_OK\n" if ($osdl->{debug});
#osdl_warn "MISSING: $DNS_NAME_MISSING\n" if ($osdl->{debug});
#osdl_warn "SPOOFED: $DNS_NAME_SPOOFED\n" if ($osdl->{debug});
#osdl_warn "FAILED: $DNS_NAME_FAILED\n" if ($osdl->{debug});
#osdl_warn "result: $result name: $name\n" if ($osdl->{debug});
if ($result == $DNS_NAME_OK ||
($result == $DNS_NAME_SPOOFED && !$osdl->{spoofed_dns_reject})) {
# Handle spammer names
# Regex against the names
foreach my $match (@{$osdl->{name_white_list}}) {
if ($name =~ /$match/) {
osdl_warn "Name ($ip,$name) Matched Whitelist '$match'\n"
if ($osdl->{debug});
return (CONTINUE,"Matched DNS whitelist: $name");
}
}
foreach my $match (@{$osdl->{name_black_list}}) {
if ($name =~ /$match/) {
osdl_warn "Name ($ip,$name) Matched Blacklist '$match'\n"
if ($osdl->{debug});
return (REJECT,"Matched DNS blacklist: $name");
}
}
}
elsif ($result == $DNS_NAME_MISSING) {
if ($osdl->{no_dns_reject}) {
osdl_warn "IP ($ip) Missing DNS Matched\n"
if ($osdl->{debug});
return (REJECT,"No DNS Name for IP $ip");
}
}
elsif ($result == $DNS_NAME_SPOOFED) {
if ($osdl->{spoofed_dns_reject}) {
osdl_warn "IP ($ip,$name) Spoofing Matched\n"
if ($osdl->{debug});
return (REJECT,"Spoofing DNS '$name' ($ip)");
}
}
else {
# no action...
}
# Nothing matched
osdl_warn "IP ($ip,$name) passed all checks\n"
if ($osdl->{debug});
return (CONTINUE,"DNS seems okay");
}
sub filter_relay {
my($ip, $name, $helo) = @_;
return(CONTINUE, "Local interface")
if ($ip eq "127.0.0.1"); # no further checking if localhost
# reject when it's an obvious forgery...
if (($helo =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/) &&
($ip ne $helo)) {
return (REJECT, "HELO forgery attempted: $ip claims to be $helo")
}
# verify our own domains
$helo = lc($helo);
foreach my $domain (keys %{ $osdl->{'helo_forging'} }) {
my $match = $osdl->{'helo_forging'}->{$domain};
if ($helo =~ /$domain/ && $ip !~ /$match/) {
return(REJECT, "HELO forgery attempted: $ip claims to handle $helo");
}
}
# Check for Spammer IP and DNS
return osdl_found_spammer($ip);
#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();
# 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 ($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();
}
# Virus scan
if ($FoundVirus) {
my($code, $category, $action);
$VirusScannerMessages = "";
($code, $category, $action) = entity_contains_virus($entity);
# If you are more paranoid, change to: if ($action eq "quarantine") {
if ($category eq "virus") {
md_graphdefang_log('virus',$VirusName, $RelayAddr);
# Bounce the mail!
action_bounce("Virus $VirusName found in mail - rejected");
# But quarantine the part for examination later. Comment
# the next line out if you don't want to bother.
action_quarantine($entity, "A known virus was discovered and deleted. Virus-scanner messages follow:\n$VirusScannerMessages\n\n");
return;
}
if ($action eq "tempfail") {
action_tempfail("Problem running virus-scanner");
md_syslog('warning', "Problem running virus scanner: code=$code, category=$category, action=$action");
}
}
if (filter_bad_filename($entity)) {
md_graphdefang_log('bad_filename', $fname, $type);
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 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_quarantine($entity, "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) = @_;
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, $version) = 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) {
if ($hits >= 10) {
action_bounce('Spam Rejected -- please send email to rt at osdl.org if you need help');
return;
}
# Let's emulate SpamAssassin's reporting style, the way
# we want it.
action_change_header("X-Spam-Status",
"Yes, hits=$hits required=$req tests=$names version=$version");
action_change_header("X-Spam-Level",$score);
action_change_header("X-Spam-Flag","YES");
action_change_header("X-Spam-Checker-Version","SpamAssassin $version");
md_graphdefang_log('spam', $hits, $RelayAddr);
# If you find the SA report useful, add it, I guess...
# $report=~s/\n/\r\n/g;
#action_change_header("X-Spam-Report",$report);
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");
action_change_header("X-Spam-Status",
"No, hits=$hits required=$req tests=$names");
}
}
else {
action_change_header("X-Spam-Status", "Skipped, large email");
}
}
else {
action_change_header("X-Spam-Status", "Skipped, SpamAssassin not available");
}
# 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);
}
# DO NOT delete the next line, or Perl will complain.
1;
More information about the MIMEDefang
mailing list