[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