On Thu, 2 Sep 2004, Ask Bj�rn Hansen wrote:
> > it out. Having an additional config file only makes things more
> > complicated.
> > I suggest removing the check for the configfile from the source.
>
> Yeah, good idea. I've done it in my development copy.
I posted a patch to require_resolvable_fromhost a month or so ago, which
had that useless (IMHO) check removed. It also performed it's DNS lookups
in the background, allowed IPv4 and IPv6 address literals, caught
private/local address literals and DNS lookups. For some reason it didn't
make it into CVS.
It's attached.
Cheers.
--
Mark Powell - UNIX System Administrator - The University of Salford
Information Services Division, Clifford Whitworth Building,
Salford University, Manchester, M5 4WT, UK.
Tel: +44 161 295 4837 Fax: +44 161 295 5888 www.pgp.com for PGP key
use Net::DNS qw(mx);
use Time::HiRes qw(gettimeofday tv_interval);
use constant RRF_SUCCESS => 0;
use constant RRF_RESOLVE_FAIL => 1;
use constant RRF_PRIVATE_FAIL => 2;
sub register {
my ($self, $qp) = @_;
$self->register_hook('mail', 'mail_handler');
$self->register_hook('rcpt', 'rcpt_handler');
$self->register_hook('disconnect', 'disconnect_handler');
}
sub mail_handler {
my ($self, $transaction, $sender) = @_;
my $host = $sender->host;
my $tldre = $self->qp->config('tld');
$tldre = '[a-z]{2,}' unless $tldre;
return DECLINED if ($sender->format eq '<>' || !$host);
# IPv6 address literals aren't validated, as I don't know how, anyone?
if ($host =~ /^\[[a-z\d\-]*[a-z\d]:([^\\\[\]]|\\.)+\]$/i) {
$self->log(LOGDEBUG, "its an ipv6");
$transaction->notes('_ipv6', 1);
return DECLINED;
}
# IPv4 address literals can be checked for private/unreachable addresses
if ($host =~ /^\[(((?(?<!\[)\.)(25[0-5]|2[0-4]\d|[01]?\d?\d)){4})\]$/) {
$self->log(LOGDEBUG, "its an ipv4");
$transaction->notes('_ipv4_address', $1);
$transaction->notes('_ipv4', 1);
return DECLINED;
}
# check for an RFC2821 compliant domain
if ($host !~ /^((?!-)[a-z\d\-]+(?<!-)\.)+$tldre$/i) {
$self->log(LOGDEBUG, "not rfc2821 compliant");
$transaction->notes('_not_fqdn', 1);
return DECLINED;
}
my $res = new Net::DNS::Resolver;
my $sel = IO::Select->new();
$self->log(LOGDEBUG, "Checking $host for A or MX record in the background");
$sel->add($res->bgsend($host, 'MX'));
$sel->add($res->bgsend($host));
$transaction->notes('_start_time', [gettimeofday]);
$transaction->notes('rrf_sockets', $sel);
return DECLINED;
}
sub rcpt_handler {
my ($self, $transaction, $rcpt) = @_;
my $sender = $self->transaction->sender;
my $host = $sender->host;
return DECLINED if ($sender->format eq '<>' || $transaction->notes('_ipv6'));
return(DENY, "Host name required in sender") unless $host;
if ($transaction->notes('_ipv4')) {
my $ip = $transaction->notes('_ipv4_address');
return DECLINED if ($ip !~
/^(0\.0\.0\.0$|127\.|10\.|224\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.)/);
$self->log(LOGDEBUG, "Mail from $host declined due to private/unreachable
address");
return (DENY, "Mail from $host not accepted here.");
}
if ($transaction->notes('_not_fqdn')) {
$self->log(LOGDEBUG, "Mail from $host is not a valid FQDN: see RFC821/1123/2821.");
return (DENY, "Mail from $host is not a valid FQDN: see RFC821/1123/2821.");
}
my ( $return_code, $msg) = $self->process_sockets();
$self->log(LOGDEBUG, $msg) if $msg;
return ($return_code, $msg) if $msg;
return DECLINED;
}
sub process_sockets {
my ($self) = @_;
my $trans = $self->transaction;
my $host = $trans->sender->host;
return ($trans->notes('rrf_rcode'), $trans->notes('rrf_rmsg')) if
$trans->notes('rrf_rcode');
my ( $result, $msg ) = ();
my $res = new Net::DNS::Resolver;
my $sel = $trans->notes('rrf_sockets') or ( $result, $msg ) = (DENYSOFT, "Could not
resolve $host, given no sockets");
my $start_time = $trans->notes('_start_time');
my $timeout = 30;
PS:
while ($sel->count) {
$self->log(LOGDEBUG, 'waiting for answers on ', $sel->count, ' sockets ...');
# bear in mind how long we've already waited
my @ready = $sel->can_read($timeout - tv_interval($start_time, [gettimeofday]));
my $wait = tv_interval($start_time, [gettimeofday]);
$self->log(LOGDEBUG, "waited ${wait}s for answers on " , scalar @ready, ' sockets
...') ;
for my $socket (@ready) {
my $query = $res->bgread($socket);
$self->log(LOGDEBUG, "$host errorstring after bgread: ", $res->errorstring);
( $result, $msg ) = ( $res->errorstring eq 'NXDOMAIN' ? DENY : DENYSOFT, "could
not resolve $host (" . $res->errorstring . ')') if ($res->errorstring ne 'NOERROR');
$sel->remove($socket);
undef $socket;
if ($query) {
$self->log(LOGDEBUG, 'checking socket answers');
foreach my $rr ($query->answer) {
#$rr->print;
if ($rr->type eq 'MX') {
$self->log(LOGINFO, 'found MX record ' . $rr->rdatastr . " for $host");
( $result, $msg ) = (DECLINED, "found MX record for $host");
last PS;
} elsif ($rr->type eq 'A') {
if ($rr->address =~
/^(0\.0\.0\.0$|127\.|10\.|224\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.)/) {
$self->log(LOGINFO, "A record for $host resolved to private/unreachable
address " . $rr->address);
( $result, $msg ) = (DENY, "A record for $host resolved to
private/unreachable address.");
} else {
$self->log(LOGINFO, 'found A record ' . $rr->address . " for $host");
( $result, $msg ) = (DECLINED, "found A record for $host");
last PS;
}
}
}
} else {
$self->log(LOGDEBUG, "$host query failed: ", $res->errorstring);
}
}
# Net::DNS 0.47 doesn't support retries or timeouts for background queries,
# so we'll handle the timeouts ourselves.
if ($wait >= $timeout) {
( $result, $msg ) = (DENYSOFT, "timeout resolving $host") unless $result;
$self->log(LOGDEBUG, $msg);
last;
}
}
# clear out any sockets
$self->log(LOGDEBUG, "destroying sockets");
$trans->notes('rrf_sockets', undef);
( $result, $msg ) = (DENYSOFT, "Could not find MX or A record for $host") unless
$result;
$self->log(LOGDEBUG, "saving return values $result $msg");
return ($trans->notes('rrf_rcode', $result), $trans->notes('rrf_rmsg', $msg));
}
sub disconnect_handler {
my ($self, $transaction) = @_;
$transaction->notes('rrf_sockets', undef);
return DECLINED;
}