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;
}

Reply via email to