merged check_badmailfrom_patterns into check_badmailfrom

This commit is contained in:
Matt Simerson 2012-04-26 04:52:21 -04:00 committed by Ask Bjørn Hansen
parent 102e068297
commit 368ce9401b
3 changed files with 154 additions and 90 deletions

View File

@ -14,48 +14,94 @@ recipient address for a message if the envelope sender address is
listed in badmailfrom. A line in badmailfrom may be of the form listed in badmailfrom. A line in badmailfrom may be of the form
@host, meaning every address at host." @host, meaning every address at host."
You may optionally include a message after the sender address (leave a space), You may include an optional message after the sender address (leave a space),
which is used when rejecting the sender. to be used when rejecting the sender.
=head1 PATTERNS
This plugin also supports regular expression matches. This allows
special patterns to be denied (e.g. FQDN-VERP, percent hack, bangs,
double ats).
Patterns are stored in the format pattern(\s+)response, where pattern
is a Perl pattern expression. Don't forget to anchor the pattern
(front ^ and back $) if you want to restrict it from matching
anywhere in the string.
^streamsendbouncer@.*\.mailengine1\.com$ Your right-hand side VERP doesn't fool me
^return.*@.*\.pidplate\.biz$ I don' want it regardless of subdomain
^admin.*\.ppoonn400\.com$
=head1 NOTES =head1 NOTES
According to the SMTP protocol, we can't reject until after the RCPT According to the SMTP protocol, we can't reject until after the RCPT
stage, so store it until later. stage, so store it until later.
=cut
# TODO: add the ability to provide a custom default rejection reason =head1 AUTHORS
initial author of badmailfrom - Jim Winstead
pattern matching plugin - Johan Almqvist <johan-qpsmtpd@almqvist.net>
merging of the two and plugin tests - Matt Simerson <matt@tnpi.net>
=cut
sub hook_mail { sub hook_mail {
my ($self, $transaction, $sender, %param) = @_; my ($self, $transaction, $sender, %param) = @_;
my @badmailfrom = $self->qp->config("badmailfrom") my @badmailfrom = $self->qp->config('badmailfrom');
or return (DECLINED); if ( defined $self->{_badmailfrom_config} ) { # testing
@badmailfrom = @{$self->{_badmailfrom_config}};
};
return (DECLINED) unless ($sender->format ne "<>" return DECLINED if ! scalar @badmailfrom;
and $sender->host && $sender->user); return DECLINED if $sender->format eq '<>';
return DECLINED if ! $sender->host || ! $sender->user;
my $host = lc $sender->host; my $host = lc $sender->host;
my $from = lc($sender->user) . '@' . $host; my $from = lc($sender->user) . '@' . $host;
for my $config (@badmailfrom) { for my $config (@badmailfrom) {
my ($bad, $reason) = $config =~ /^\s*(\S+)(?:\s*(.*))?$/; $config =~ s/^\s+//g; # trim any leading whitespace
$reason = "sorry, your envelope sender is in my badmailfrom list" unless $reason; my ($bad, $reason) = split /\s+/, $config, 2;
next unless $bad; next unless $bad;
$bad = lc $bad; next unless $self->is_match( $from, $bad, $host );
$self->log(LOGWARN, "Bad badmailfrom config: No \@ sign in $bad") and next unless $bad =~ m/\@/; $reason ||= "Your envelope sender is in my badmailfrom list";
$transaction->notes('badmailfrom', $reason) $transaction->notes('badmailfrom', $reason);
if ($bad eq $from) || (substr($bad,0,1) eq '@' && $bad eq "\@$host");
} }
return (DECLINED); return DECLINED;
} }
sub is_match {
my ( $self, $from, $bad, $host ) = @_;
if ( $bad =~ /[\/\^\$\*\+]/ ) { # it's a regexp
$self->log(LOGDEBUG, "badmailfrom pattern ($bad) match for $from");
return 1 if $from =~ /$bad/;
return;
};
$bad = lc $bad;
if ( $bad !~ m/\@/ ) {
$self->log(LOGWARN, "badmailfrom: bad config: no \@ sign in $bad");
return;
};
if ( substr($bad,0,1) eq '@' ) {
return 1 if $bad eq "\@$host";
return;
};
return if $bad ne $from;
return 1;
};
sub hook_rcpt { sub hook_rcpt {
my ($self, $transaction, $rcpt, %param) = @_; my ($self, $transaction, $rcpt, %param) = @_;
my $note = $transaction->notes('badmailfrom'); my $note = $transaction->notes('badmailfrom') or return (DECLINED);
if ($note) {
$self->log(LOGINFO, $note); $self->log(LOGINFO, $note);
return (DENY, $note); return (DENY, $note);
}
return (DECLINED);
} }

View File

@ -1,62 +0,0 @@
#!perl -Tw
=head1 SYNOPSIS
This plugin checks the badmailfrom_patterns config. This allows
special patterns to be denied (e.g. FQDN-VERP, percent hack, bangs,
double ats).
=head1 CONFIG
Configuration is placed in the following file:
F<config/badmailfrom_patterns>
Patterns are stored in the format pattern\sresponse, where pattern
is a Perl pattern expression. Don't forget to anchor the pattern
(front ^ and back $) if you want to restrict it from matching
anywhere in the string.
^streamsendbouncer@.*\.mailengine1\.com$ Your right-hand side VERP doesn't fool me
^return.*@.*\.pidplate\.biz$ I don' want it regardless of subdomain
^admin.*\.ppoonn400\.com$
=head1 AUTHOR
Johan Almqvist <johan-qpsmtpd@almqvist.net> based on L<check_badmailfrom>
This software is free software and may be distributed under the same
terms as qpsmtpd itself.
=cut
sub hook_mail {
my ($self, $transaction, $sender, %param) = @_;
my @badmailfrom = $self->qp->config("badmailfrom_patterns")
or return (DECLINED);
return (DECLINED) if ($sender->format eq "<>");
my $host = lc $sender->host;
my $from = lc($sender->user) . '@' . $host;
for (@badmailfrom) {
my ($pattern, $response) = split /\s+/, $_, 2;
next unless $from =~ /$pattern/;
$response = "Your envelope sender is in my badmailfrom_patterns list"
unless $response;
$transaction->notes('badmailfrom_patterns', $response);
}
return (DECLINED);
}
sub hook_rcpt {
my ($self, $transaction, $rcpt, %param) = @_;
my $note = $transaction->notes('badmailfrom_patterns');
if ($note) {
$self->log(LOGINFO, $note);
return (DENY, $note);
}
return (DECLINED);
}

View File

@ -0,0 +1,80 @@
use strict;
use Data::Dumper;
use Qpsmtpd::Address;
sub register_tests {
my $self = shift;
$self->register_test("test_badmailfrom_match", 1);
$self->register_test("test_badmailfrom_hook_mail", 1);
$self->register_test("test_badmailfrom_hook_rcpt", 1);
}
sub test_badmailfrom_hook_mail {
my $self = shift;
my $transaction = $self->qp->transaction;
my $test_email = 'matt@test.com';
my $address = Qpsmtpd::Address->new( "<$test_email>" );
$transaction->sender($address);
$self->{_badmailfrom_config} = ['matt@test.net','matt@test.com'];
$transaction->notes('badmailfrom', '');
my ($r) = $self->hook_mail( $transaction, $address );
ok( $r == 909, "badmailfrom hook_mail");
ok( $transaction->notes('badmailfrom') eq 'Your envelope sender is in my badmailfrom list',
"badmailfrom hook_mail: default reason");
$self->{_badmailfrom_config} = ['matt@test.net','matt@test.com Yer a spammin bastert'];
$transaction->notes('badmailfrom', '');
my ($r) = $self->hook_mail( $transaction, $address );
ok( $r == 909, "badmailfrom hook_mail");
ok( $transaction->notes('badmailfrom') eq 'Yer a spammin bastert',
"badmailfrom hook_mail: custom reason");
};
sub test_badmailfrom_hook_rcpt {
my $self = shift;
my $transaction = $self->qp->transaction;
$transaction->notes('badmailfrom', 'Yer a spammin bastart. Be gon wit yuh.' );
my ($code,$note) = $self->hook_rcpt( $transaction );
ok( $code == 901, 'badmailfrom hook hit');
ok( $note, $note );
}
sub test_badmailfrom_match {
my $self = shift;
# is_match receives ( $from, $bad, $host )
my $r = $self->is_match( 'matt@test.net', 'matt@test.net', 'test.net' );
ok($r, "check_badmailfrom match");
ok( ! $self->is_match( 'matt@test.net', 'matt@test.com', 'tnpi.net' ),
"check_badmailfrom non-match");
ok( $self->is_match( 'matt@test.net', '@test.net', 'test.net' ),
"check_badmailfrom match host");
ok( ! $self->is_match( 'matt@test.net', '@test.not', 'test.net' ),
"check_badmailfrom non-match host");
ok( ! $self->is_match( 'matt@test.net', '@test.net', 'test.not' ),
"check_badmailfrom non-match host");
ok( $self->is_match( 'matt@test.net', 'test.net$', 'tnpi.net' ),
"check_badmailfrom pattern match");
ok( ! $self->is_match( 'matt@test.net', 'test.not$', 'tnpi.net' ),
"check_badmailfrom pattern non-match");
};