132 lines
3.3 KiB
Perl
132 lines
3.3 KiB
Perl
#!perl -w
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
deny connections to recipients in the I<badrcptto> file
|
|
|
|
like badmailfrom, but for recipient address rather than sender
|
|
|
|
=head1 CONFIG
|
|
|
|
Recipients are matched against entries in I<config/badrcptto>. Entries can be
|
|
a complete email address, a host entry that starts with an @ symbol, or a
|
|
regular expression. For regexp pattern matches, see PATTERNS.
|
|
|
|
=head1 PATTERNS
|
|
|
|
This allows special patterns to be denied (e.g. percent hack, bangs,
|
|
double ats).
|
|
|
|
Patterns are stored in the format pattern\sresponse, where pattern
|
|
is a Perl pattern expression. Don't forget to anchor the pattern if
|
|
you want to restrict it from matching anywhere in the string.
|
|
|
|
qpsmtpd already ensures that the address contains an @, with something
|
|
to the left and right of the @.
|
|
|
|
=head1 AUTHOR
|
|
|
|
2002 - original badrcptto plugin - apparently Jim Winstead
|
|
https://github.com/smtpd/qpsmtpd/commits/master/plugins/check_badrcptto
|
|
|
|
2005 - pattern feature, (c) Gordon Rowell <gordonr@gormand.com.au>
|
|
|
|
2012 - merged the two, refactored, added tests - Matt Simerson
|
|
|
|
=head1 LICENSE
|
|
|
|
This software is free software and may be distributed under the same
|
|
terms as qpsmtpd itself.
|
|
|
|
=cut
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use Qpsmtpd::Constants;
|
|
use Qpsmtpd::DSN;
|
|
|
|
sub hook_rcpt {
|
|
my ($self, $transaction, $recipient, %param) = @_;
|
|
|
|
return (DECLINED) if $self->is_immune();
|
|
|
|
my ($host, $to) = $self->get_host_and_to($recipient)
|
|
or return (DECLINED);
|
|
|
|
my @badrcptto = $self->qp->config("badrcptto") or do {
|
|
$self->log(LOGINFO, "skip, empty config");
|
|
return (DECLINED);
|
|
};
|
|
|
|
for my $line (@badrcptto) {
|
|
$line =~ s/^\s+//g; # trim leading whitespace
|
|
my ($bad, $reason) = split /\s+/, $line, 2;
|
|
next if !$bad;
|
|
if ($self->is_match($to, lc($bad), $host)) {
|
|
$self->adjust_karma(-2);
|
|
if ($reason) {
|
|
return (DENY, "mail to $bad not accepted here");
|
|
}
|
|
else {
|
|
return Qpsmtpd::DSN->no_such_user(
|
|
"mail to $bad not accepted here");
|
|
}
|
|
}
|
|
}
|
|
$self->log(LOGINFO, 'pass');
|
|
return (DECLINED);
|
|
}
|
|
|
|
sub is_match {
|
|
my ($self, $to, $bad, $host) = @_;
|
|
|
|
if ($bad =~ /[\/\^\$\*\+\!\%\?\\]/) { # it's a regexp
|
|
$self->log(LOGDEBUG, "badmailfrom pattern ($bad) match for $to");
|
|
if ($to =~ /$bad/i) {
|
|
$self->log(LOGINFO, 'fail: pattern match');
|
|
return 1;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ($bad !~ m/\@/) {
|
|
$self->log(LOGERROR, "badrcptto: bad config: no \@ sign in $bad");
|
|
return;
|
|
}
|
|
|
|
$bad = lc $bad;
|
|
$to = lc $to;
|
|
|
|
if (substr($bad, 0, 1) eq '@') {
|
|
if ($bad eq "\@$host") {
|
|
$self->log(LOGINFO, 'fail: host match');
|
|
return 1;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ($bad eq $to) {
|
|
$self->log(LOGINFO, 'fail: rcpt match');
|
|
return 1;
|
|
}
|
|
return;
|
|
}
|
|
|
|
sub get_host_and_to {
|
|
my ($self, $recipient) = @_;
|
|
|
|
if (!$recipient) {
|
|
$self->log(LOGERROR, 'skip: no recipient!');
|
|
return;
|
|
}
|
|
|
|
if (!$recipient->host || !$recipient->user) {
|
|
$self->log(LOGINFO, 'skip: missing host or user');
|
|
return;
|
|
}
|
|
|
|
my $host = lc $recipient->host;
|
|
return ($host, lc($recipient->user) . '@' . $host);
|
|
}
|