[Mimedefang] Problem with backscatter
Jakub Wasielewski
jakub at wasielewski.info
Tue Mar 23 14:51:53 EDT 2010
2010/3/22 David F. Skoll <dfs at roaringpenguin.com>:
> Jakub Wasielewski wrote:
>
>> Thanks for an reply. I tried using stream_by_recipient() function in
>> filter_begin as it is
>> recommended in manual:
>
> I didn't say you *should* use it. I was asking if you were
> already using it.
I was trying to solve this problem with it. But no, I do not use stream_by_*().
> The only way I can see a DSN being generated is if (1) you're misusing
> md_check_against_smtp_server or (2) you're somehow remailing messages.
> You will need to post your entire filter for us to figure it out.
Ok, here it is. Some stuff has been changed - like domain names or IP addrs.
# -*- 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$
#***********************************************************************
use Mail::SPF::Query;
$AdminAddress = 'postmaster at example.pl';
$AdminName = "System pocztowy example.pl";
$DaemonAddress = 'postmaster at example.pl';
$NotifySenderSubject =
'=?ISO-8859-2?Q?Wiadomo=B6=E6_od_systemu_pocztowego_example=2Epl?=';
$NotifyAdministratorSubject =
'=?ISO-8859-2?Q?Wiadomo=B6=E6_od_systemu_pocztowego_example=2Epl?=';
$Features{'SpamAssassin'} = 0;
$Stupidity{"flatten"} = 0;
$Stupidity{"NoMultipleInlines"} = 0;
$NotifyNoPreamble = 1;
$AddWarningsInline = 0;
$MaxMIMEParts = 25;
$ClamdSock = '/var/run/clamav/clamd.sock';
#***********************************************************************
# 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);
# Detect and load Perl modules
detect_and_load_perl_modules();
sub check_spf {
my $spfq = Mail::SPF::Query->new(@_);
my($result, $smtp_comment, $header_comment, $spf_record) = $spfq->result();
if ($result eq 'pass' or $result eq 'fail') {
md_syslog('info', "$QueueID: SPF implemented=yes, result=$result,
smtp_comment=$smtp_comment, header_comment=$header_comment");
} else {
my($result, $smtp_comment, $header_comment) = $spfq->best_guess();
$smtp_comment ||= '';
$header_comment ||= '';
md_syslog('info', "$QueueID: SPF implemented=no, result=$result,
smtp_comment=$smtp_comment, header_comment=$header_comment");
}
return ($result, $smtp_comment, $header_comment);
}
sub filter_sender {
my($sender, $ip, $hostname, $helo) = @_;
return('CONTINUE', "ok") if ($ip eq "127.0.0.1");
md_syslog('info', " == SENDER == :: $sender");
# sprawdzenie czy host nie podszywa się pod moje serwery
if ($helo =~ /(^|.)example\.pl$/i) {
if ($ip ne "1.2.3.8" and $ip ne "1.2.3.9" and $ip ne "1.2.3.10" and
$ip ne "1.2.3.11" and $ip ne "1.2.3.12" and $ip ne "1.2.3.13" and
$ip ne "1.2.3.14" and $ip ne "1.2.3.15" and $ip ne "1.2.3.18" and
$ip ne "1.2.3.130" and $ip ne "1.2.3.178") {
md_syslog('info', "Host $ip sent fake HELO : $helo");
return('REJECT', "Go away. $ip is not a example.pl machine.");
}
}
# oczywista fałszywka ip w HELO != ip hosta
if (($helo =~ /^(d{1,3})(.)(d{1,3})(.)(d{1,3})(.)(d{1,3})$/) && ($ip
ne $helo)) {
return('REJECT', "Header forgery attempt, $ip claims to be $helo");
}
read_commands_file();
# if ($sender eq "<>") {
# return ('REJECT', 'Message rejected. Administrative messages not accepted.');
# }
# SPF jeśli niezautoryzowany
if ($SendmailMacros{auth_authen} eq "") {
my ($result, $smtp_comment, $header_comment) = check_spf(ip => $ip,
sender => $sender, helo => $helo);
if ($result eq 'fail') {
return ('REJECT', 'Message rejected. ' . $smtp_comment);
}
} else {
if ($sender eq "<>") {
return ('REJECT', 'Message rejected. Authorized users should not
send administrative messages.');
}
}
return('CONTINUE', "ok");
}
sub filter_recipient {
my ($recipient, $sender, $ip, $hostname, $first, $helo, $rcpt_mailer,
$rcpt_host, $rcpt_addr) = @_;
return('CONTINUE', "ok") if ($ip eq "127.0.0.1");
read_commands_file();
if ($ip eq '127.0.0.1') {
return ('CONTINUE', "ok");
} elsif ($recipient =~ /\@example\.pl/i) {
return ('CONTINUE', "ok");
} elsif ($recipient =~ /\@example2\.pl/i or $recipient =~
/\@example3\.pl/i or $recipient =~ /\@example4\.pl/i or $recipient =~
/\@example5\.pl/i or $recipient =~ /\@example6\.pl/i) {
my ($result, $msg) = md_check_against_smtp_server($sender,
$recipient, "smtp1.example.pl", "smtp2.example.pl");
md_syslog('info', "Recipient check: $result - $msg");
return ($result, $msg);
}
return ('CONTINUE', "ok");
}
# 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:
# $entity -- the parsed MIME::Entity
# %RETURNS:
# Nothing
# %DESCRIPTION:
# Called just before e-mail parts are processed
#***********************************************************************
sub filter_begin {
my($entity) = @_;
# 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();
}
# if ($RelayAddr ne "127.0.0.1") {
# md_syslog('info', " == RELAY == :: $RelayAddr");
# if (stream_by_recipient()) {
# md_syslog('info', " == STREAM_BY_DOMAIN() ==");
# return;
# }
# }
# 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) = @_;
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 (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 contact\nthe sender and arrange an
alternate means of receiving it.\n");
}
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");
}
# 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();
# 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();
}
# DO NOT delete the next line, or Perl will complain.
1;
Cheers,
--
Jakub Wasielewski
More information about the MIMEDefang
mailing list