Author: msergeant
Date: Mon Apr 9 15:19:40 2007
New Revision: 732
Added:
trunk/plugins/async/require_resolvable_fromhost
Log:
Ported to async
Added: trunk/plugins/async/require_resolvable_fromhost
==============================================================================
--- (empty file)
+++ trunk/plugins/async/require_resolvable_fromhost Mon Apr 9 15:19:40 2007
@@ -0,0 +1,135 @@
+#!/usr/bin/perl -w
+
+use Qpsmtpd::DSN;
+use ParaDNS;
+use Socket;
+
+my %invalid = ();
+my $has_ipv6 = Qpsmtpd::Constants::has_ipv6;
+
+sub register {
+ my ( $self, $qp ) = @_;
+
+ foreach my $i ( $self->qp->config("invalid_resolvable_fromhost") ) {
+ $i =~ s/^\s*//;
+ $i =~ s/\s*$//;
+ if ( $i =~ m#^((\d{1,3}\.){3}\d{1,3})/(\d\d?)# ) {
+ $invalid{$1} = $3;
+ }
+ }
+
+ $self->register_hook( mail => 'hook_mail_start' );
+ $self->register_hook( mail => 'hook_mail_done' );
+}
+
+sub hook_mail_start {
+ my ( $self, $transaction, $sender ) = @_;
+
+ return DECLINED
+ if ( $self->qp->connection->notes('whitelistclient') );
+
+ if ( $sender ne "<>" ) {
+
+ unless ( $sender->host ) {
+ # default of addr_bad_from_system is DENY, we use DENYSOFT here to
+ # get the same behaviour as without Qpsmtpd::DSN...
+ return Qpsmtpd::DSN->addr_bad_from_system( DENYSOFT,
+ "FQDN required in the envelope sender" );
+ }
+
+ $self->check_dns( $sender->host );
+ return YIELD;
+ }
+
+ return DECLINED;
+}
+
+sub hook_mail_done {
+ my ( $self, $transaction, $sender ) = @_;
+
+ return DECLINED
+ if ( $self->qp->connection->notes('whitelistclient') );
+
+ if (!$transaction->notes('resolvable_fromhost')) {
+ # default of temp_resolver_failed is DENYSOFT
+ return Qpsmtpd::DSN->temp_resolver_failed(
+ "Could not resolve " . $sender->host );
+ }
+ return DECLINED;
+}
+
+sub check_dns {
+ my ( $self, $host ) = @_;
+ my @host_answers;
+
+ return if $host =~ m/^\[(\d{1,3}\.){3}\d{1,3}\]$/;
+
+ my $qp = $self->qp;
+
+ my $a_records = [];
+ my $num_queries = $has_ipv6 ? 2 : 1;
+ ParaDNS->new(
+ callback => sub {
+ my $mx = shift;
+ return if $mx =~ /^[A-Z]+$/; # error
+ my $addr = $mx->[0];
+ $num_queries++;
+ ParaDNS->new(
+ callback => sub { push @$a_records, $_[0] if $_[0] !~
/^[A-Z]+$/; },
+ finished => sub { $num_queries--; $self->finish_up($qp,
$a_records) unless $num_queries; },
+ host => $addr,
+ type => 'A',
+ );
+ if ($has_ipv6) {
+ $num_queries++;
+ ParaDNS->new(
+ callback => sub { push @$a_records, $_[0] if $_[0] !~
/^[A-Z]+$/; },
+ finished => sub { $num_queries--; $self->finish_up($qp,
$a_records) unless $num_queries; },
+ host => $addr,
+ type => 'AAAA',
+ );
+ }
+ },
+ host => $host,
+ type => 'MX',
+ );
+ ParaDNS->new(
+ callback => sub { push @$a_records, $_[0] if $_[0] !~ /^[A-Z]+$/; },
+ finished => sub { $num_queries--; $self->finish_up($qp, $a_records)
unless $num_queries },
+ host => $host,
+ type => 'A',
+ );
+ ParaDNS->new(
+ callback => sub { push @$a_records, $_[0] if $_[0] !~ /^[A-Z]+$/; },
+ finished => sub { $num_queries--; $self->finish_up($qp, $a_records)
unless $num_queries },
+ host => $host,
+ type => 'AAAA',
+ ) if $has_ipv6;
+}
+
+sub finish_up {
+ my ($self, $qp, $a_records) = @_;
+
+ foreach my $addr (@$a_records) {
+ if (is_valid($addr)) {
+ $qp->transaction->notes('resolvable_fromhost', 1);
+ last;
+ }
+ }
+
+ $qp->run_continuation;
+}
+
+sub is_valid {
+ my $ip = shift;
+ my ( $net, $mask );
+ foreach $net ( keys %invalid ) {
+ $mask = $invalid{$net};
+ $mask = pack "B32", "1" x ($mask) . "0" x ( 32 - $mask );
+ return 0
+ if join( ".", unpack( "C4", inet_aton($ip) & $mask ) ) eq $net;
+ }
+ return 1;
+}
+
+# vim: ts=4 sw=4 expandtab syn=perl