2012-04-29 10:35:59 +02:00
|
|
|
#!perl -w
|
2007-04-10 00:19:40 +02:00
|
|
|
|
2012-05-23 22:13:00 +02:00
|
|
|
use strict;
|
|
|
|
use warnings;
|
|
|
|
|
|
|
|
use Qpsmtpd::Constants;
|
2007-04-10 00:19:40 +02:00
|
|
|
use Qpsmtpd::DSN;
|
2007-05-26 02:48:09 +02:00
|
|
|
use Qpsmtpd::TcpServer;
|
2007-04-10 00:19:40 +02:00
|
|
|
|
2012-05-23 22:13:00 +02:00
|
|
|
#use ParaDNS; # moved into register
|
|
|
|
use Socket;
|
|
|
|
|
2007-04-10 00:19:40 +02:00
|
|
|
my %invalid = ();
|
2007-05-26 02:48:09 +02:00
|
|
|
my $has_ipv6 = Qpsmtpd::TcpServer::has_ipv6();
|
2007-04-10 00:19:40 +02:00
|
|
|
|
|
|
|
sub register {
|
2013-04-21 06:50:39 +02:00
|
|
|
my ($self, $qp) = @_;
|
2012-06-25 09:24:08 +02:00
|
|
|
|
2013-04-21 06:50:39 +02:00
|
|
|
foreach my $i ($self->qp->config("invalid_resolvable_fromhost")) {
|
2007-04-10 00:19:40 +02:00
|
|
|
$i =~ s/^\s*//;
|
|
|
|
$i =~ s/\s*$//;
|
2013-04-21 06:50:39 +02:00
|
|
|
if ($i =~ m#^((\d{1,3}\.){3}\d{1,3})/(\d\d?)#) {
|
2007-04-10 00:19:40 +02:00
|
|
|
$invalid{$1} = $3;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-05-23 22:13:00 +02:00
|
|
|
eval 'use ParaDNS';
|
2013-04-21 06:50:39 +02:00
|
|
|
if ($@) {
|
2012-05-23 22:13:00 +02:00
|
|
|
warn "could not load ParaDNS, plugin disabled";
|
|
|
|
return DECLINED;
|
2013-04-21 06:50:39 +02:00
|
|
|
}
|
|
|
|
$self->register_hook(mail => 'hook_mail_start');
|
|
|
|
$self->register_hook(mail => 'hook_mail_done');
|
2007-04-10 00:19:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
sub hook_mail_start {
|
2013-04-21 06:50:39 +02:00
|
|
|
my ($self, $transaction, $sender) = @_;
|
2012-06-25 09:24:08 +02:00
|
|
|
|
2007-04-10 00:19:40 +02:00
|
|
|
return DECLINED
|
2012-06-04 08:53:43 +02:00
|
|
|
if ($self->connection->notes('whitelisthost'));
|
2012-05-23 22:13:00 +02:00
|
|
|
|
2013-04-21 06:50:39 +02:00
|
|
|
if ($sender ne '<>') {
|
|
|
|
|
|
|
|
unless ($sender->host) {
|
2007-04-10 00:19:40 +02:00
|
|
|
|
|
|
|
# default of addr_bad_from_system is DENY, we use DENYSOFT here to
|
|
|
|
# get the same behaviour as without Qpsmtpd::DSN...
|
2013-04-21 06:50:39 +02:00
|
|
|
return
|
|
|
|
Qpsmtpd::DSN->addr_bad_from_system(DENYSOFT,
|
|
|
|
"FQDN required in the envelope sender");
|
2007-04-10 00:19:40 +02:00
|
|
|
}
|
2008-03-13 20:59:15 +01:00
|
|
|
|
|
|
|
return DECLINED if $sender->host =~ m/^\[(\d{1,3}\.){3}\d{1,3}\]$/;
|
|
|
|
|
2013-04-21 06:50:39 +02:00
|
|
|
unless ($self->check_dns($sender->host)) {
|
2008-03-13 20:59:15 +01:00
|
|
|
return Qpsmtpd::DSN->temp_resolver_failed(
|
2013-04-21 06:50:39 +02:00
|
|
|
"Could not resolve " . $sender->host);
|
2008-03-13 20:59:15 +01:00
|
|
|
}
|
|
|
|
|
2007-04-10 00:19:40 +02:00
|
|
|
return YIELD;
|
|
|
|
}
|
2012-05-23 22:13:00 +02:00
|
|
|
|
2007-04-10 00:19:40 +02:00
|
|
|
return DECLINED;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub hook_mail_done {
|
2013-04-21 06:50:39 +02:00
|
|
|
my ($self, $transaction, $sender) = @_;
|
2012-06-25 09:24:08 +02:00
|
|
|
|
2007-04-10 00:19:40 +02:00
|
|
|
return DECLINED
|
2013-04-21 06:50:39 +02:00
|
|
|
if ($self->connection->notes('whitelisthost'));
|
|
|
|
|
|
|
|
if ($sender ne "<>" && !$transaction->notes('resolvable_fromhost')) {
|
2007-04-10 00:19:40 +02:00
|
|
|
|
|
|
|
# default of temp_resolver_failed is DENYSOFT
|
|
|
|
return Qpsmtpd::DSN->temp_resolver_failed(
|
2013-04-21 06:50:39 +02:00
|
|
|
"Could not resolve " . $sender->host);
|
2007-04-10 00:19:40 +02:00
|
|
|
}
|
|
|
|
return DECLINED;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub check_dns {
|
2013-04-21 06:50:39 +02:00
|
|
|
my ($self, $host) = @_;
|
2007-04-10 00:19:40 +02:00
|
|
|
my @host_answers;
|
|
|
|
|
|
|
|
my $qp = $self->qp;
|
2008-03-13 20:59:15 +01:00
|
|
|
$qp->input_sock->pause_read;
|
2012-06-25 09:24:08 +02:00
|
|
|
|
2013-04-21 06:50:39 +02:00
|
|
|
my $a_records = [];
|
2008-05-01 08:18:46 +02:00
|
|
|
my $num_queries = 1; # queries in progress
|
2013-04-21 06:50:39 +02:00
|
|
|
my $mx_found = 0;
|
2008-03-13 20:59:15 +01:00
|
|
|
|
2007-04-10 00:19:40 +02:00
|
|
|
ParaDNS->new(
|
2013-04-21 06:50:39 +02:00
|
|
|
callback => sub {
|
2007-04-10 00:19:40 +02:00
|
|
|
my $mx = shift;
|
2013-04-21 06:50:39 +02:00
|
|
|
return if $mx =~ /^[A-Z]+$/; # error
|
2008-03-13 20:59:15 +01:00
|
|
|
|
2007-04-10 00:19:40 +02:00
|
|
|
my $addr = $mx->[0];
|
2008-05-01 08:18:46 +02:00
|
|
|
$mx_found = 1;
|
2008-03-13 20:59:15 +01:00
|
|
|
|
2007-04-10 00:19:40 +02:00
|
|
|
$num_queries++;
|
|
|
|
ParaDNS->new(
|
2013-04-21 06:50:39 +02:00
|
|
|
callback => sub {
|
|
|
|
push @$a_records, $_[0] if $_[0] !~ /^[A-Z]+$/;
|
|
|
|
},
|
|
|
|
finished => sub {
|
|
|
|
$num_queries--;
|
|
|
|
$self->finish_up($qp, $a_records, $num_queries);
|
|
|
|
},
|
|
|
|
host => $addr,
|
|
|
|
type => 'A',
|
|
|
|
);
|
2008-03-13 20:59:15 +01:00
|
|
|
|
2007-04-10 00:19:40 +02:00
|
|
|
if ($has_ipv6) {
|
|
|
|
$num_queries++;
|
|
|
|
ParaDNS->new(
|
2013-04-21 06:50:39 +02:00
|
|
|
callback => sub {
|
|
|
|
push @$a_records, $_[0] if $_[0] !~ /^[A-Z]+$/;
|
|
|
|
},
|
|
|
|
finished => sub {
|
|
|
|
$num_queries--;
|
|
|
|
$self->finish_up($qp, $a_records, $num_queries);
|
|
|
|
},
|
|
|
|
host => $addr,
|
|
|
|
type => 'AAAA',
|
|
|
|
);
|
2007-04-10 00:19:40 +02:00
|
|
|
}
|
|
|
|
},
|
2013-04-21 06:50:39 +02:00
|
|
|
finished => sub {
|
2008-05-01 08:18:46 +02:00
|
|
|
|
|
|
|
unless ($mx_found) {
|
|
|
|
|
|
|
|
$num_queries++;
|
|
|
|
ParaDNS->new(
|
2013-04-21 06:50:39 +02:00
|
|
|
callback => sub {
|
|
|
|
push @$a_records, $_[0] if $_[0] !~ /^[A-Z]+$/;
|
|
|
|
},
|
|
|
|
finished => sub {
|
|
|
|
$num_queries--;
|
|
|
|
$self->finish_up($qp, $a_records, $num_queries);
|
|
|
|
},
|
|
|
|
host => $host,
|
|
|
|
type => 'A',
|
|
|
|
);
|
2008-05-01 08:18:46 +02:00
|
|
|
|
|
|
|
if ($has_ipv6) {
|
|
|
|
$num_queries++;
|
|
|
|
ParaDNS->new(
|
2013-04-21 06:50:39 +02:00
|
|
|
callback => sub {
|
|
|
|
push @$a_records, $_[0] if $_[0] !~ /^[A-Z]+$/;
|
|
|
|
},
|
|
|
|
finished => sub {
|
|
|
|
$num_queries--;
|
|
|
|
$self->finish_up($qp, $a_records, $num_queries);
|
|
|
|
},
|
|
|
|
host => $host,
|
|
|
|
type => 'AAAA',
|
|
|
|
);
|
2008-05-01 08:18:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
$num_queries--;
|
|
|
|
$self->finish_up($qp, $a_records, $num_queries);
|
|
|
|
},
|
2013-04-21 06:50:39 +02:00
|
|
|
host => $host,
|
|
|
|
type => 'MX',
|
|
|
|
)
|
|
|
|
or $qp->input_sock->continue_read, return;
|
2008-03-13 20:59:15 +01:00
|
|
|
|
|
|
|
return 1;
|
2007-04-10 00:19:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
sub finish_up {
|
2008-05-01 08:18:46 +02:00
|
|
|
my ($self, $qp, $a_records, $num_queries) = @_;
|
2008-03-13 20:59:15 +01:00
|
|
|
|
|
|
|
return if defined $qp->transaction->notes('resolvable_fromhost');
|
|
|
|
|
2007-04-10 00:19:40 +02:00
|
|
|
foreach my $addr (@$a_records) {
|
|
|
|
if (is_valid($addr)) {
|
|
|
|
$qp->transaction->notes('resolvable_fromhost', 1);
|
2008-03-13 20:59:15 +01:00
|
|
|
$qp->input_sock->continue_read;
|
|
|
|
$qp->run_continuation;
|
|
|
|
return;
|
2007-04-10 00:19:40 +02:00
|
|
|
}
|
|
|
|
}
|
2012-06-25 09:24:08 +02:00
|
|
|
|
2008-03-13 20:59:15 +01:00
|
|
|
unless ($num_queries) {
|
2013-04-21 06:50:39 +02:00
|
|
|
|
2008-03-13 20:59:15 +01:00
|
|
|
# all queries returned no valid response
|
|
|
|
$qp->transaction->notes('resolvable_fromhost', 0);
|
|
|
|
$qp->input_sock->continue_read;
|
|
|
|
$qp->run_continuation;
|
|
|
|
}
|
2007-04-10 00:19:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
sub is_valid {
|
|
|
|
my $ip = shift;
|
2013-04-21 06:50:39 +02:00
|
|
|
my ($net, $mask);
|
|
|
|
foreach $net (keys %invalid) {
|
2007-04-10 00:19:40 +02:00
|
|
|
$mask = $invalid{$net};
|
2013-04-21 06:50:39 +02:00
|
|
|
$mask = pack "B32", "1" x ($mask) . "0" x (32 - $mask);
|
2007-04-10 00:19:40 +02:00
|
|
|
return 0
|
2013-04-21 06:50:39 +02:00
|
|
|
if join(".", unpack("C4", inet_aton($ip) & $mask)) eq $net;
|
2007-04-10 00:19:40 +02:00
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|