On Wed, Oct 25, 2006 at 02:07:51PM -0700, Marc Perkel wrote:
> I was thinking it would be handy to have some perl scripts to do custom 
> sender/recipient verification. Basically I want to connect, wait for the 
> greeting, send a HELO, interact using the script, and return the final 
> response string.
> 
> Anyone have a sample of how to do this?

Relevant perl excerpt follows. Actually this has some
deficiencies (doesn't handle CNAMEs correctly; the check
for postmaster@ causes it to generate bad results for
people who don't support that alias) but it should provide
a useful starting-point. I haven't fixed the problems I
describe since, as discussed previously, sender
verification isn't a whole lot of good; I suppose if I
wanted to do recipient verification it would be worth
improving, but at the moment that's done by a downstream
server in our configuration.

# verify_address_on_host ADDRESS HOST
# Test whether ADDRESS is deliverable on HOST. Returns an array giving a status
# and an explanation; status is '+' when a positive response to the RCPT
# command is received, '-' when a permanent failure response is received, and
# '?' otherwise; the explanation is a human-readable account of the condition.
sub verify_address_on_host ($$) {
    my ($email, $host) = @_;
    my $S = new Net::SMTP($host, Timeout => 15, Hello => 'your.hostname.here');
    if (!$S) {
        Exim::log_write("verify_address_on_host: unable to connect to $host; 
system error: $!");
        return ('?', "unable to connect to $host: $!");
    }
    $S->mail('')
        || return ('?', "response to MAIL FROM: <> was '" . $S->code() . " " . 
ch($S->message()) . "'");
    if ($S->recipient($email)) {
        return ('+', "host $host accepted RCPT TO with '" . $S->code() . " " . 
ch($S->message()) . "'");
    } else {
        my $code = $S->code();
        if (!defined($code)) {
            return ('?', "unknown response to RCPT TO; system error $!");
        } elsif ($code =~ /^4\d\d$/) {
            return ('?', "temporary failure response to RCPT TO '" . $S->code() 
. " " . ch($S->message()) . "'");
        } elsif ($code =~ /^5\d\d$/) {
            return ('-', "permanent failure response to RCPT TO '" . $S->code() 
. " " . ch($S->message()) . "'");
        } else {
            return ('?', "unknown response to RCPT TO; code = $code");
        }
    }
    $S->quit();
}

# verify_address ADDRESS [NOCACHE]
# Test whether ADDRESS is deliverable, by consulting the appropriate servers
# for email addressed to that domain. If NOCACHE is true the cache of tested
# addresses is not consulted.
sub verify_address ($;$) {
    my ($email, $nocache) = @_;
    if ($email eq '') {
        return ('+', "blank return-path always valid");
    } elsif ($email !~ /^([EMAIL PROTECTED])@(.+)$/) {
        return ('-', "not a valid email address");
    }

    my ($local_part, $domain) = ($1, lc($2));

    if ($domain =~ /^\[[^]]+\]$/) {
        return ('-', "we regard IP literals as invalid");
    }

    $email = $local_part . '@' . $domain;

    # An irritating new trick is for spammers to point the MX records for
    # their domains at those of some other provider, say Yahoo. This would be
    # fine, except that some servers use large numbers of failing RCPT calls
    # as evidence that a host is a source of spam, and should be blacklisted.
    # Therefore, we also check that the postmaster address is deliverable,
    # using a cached result even if NOCACHE is true, and if it is definitely
    # undeliverable, we return a negative result.
    if (lc($local_part) ne 'postmaster') {
        my ($result, $expln) = verify_address('postmaster@' . $domain);
        return ('-', "[EMAIL PROTECTED] is undeliverable, so it is not worth 
checking for [EMAIL PROTECTED]; result for postmaster was: $expln")
            if ($result eq '-')
    }

    if (!$nocache) {
        my $x = $T->fetch($email);
        if ($x) {
            my ($result, $expln, $when) = split(/\0/, $x);
            if ($when > time() - $cachetime) {
                return ($result, "$expln (cached)");
            } else {
                $T->delete($email);
            }
        }
    }

    our $R;
    if (!$R) {
        $R = new Net::DNS::Resolver;
        $R->tcp_timeout(10);
        $R->udp_timeout(10);
    }

    my @mailhosts = ( );

    # First try MX records.
    my @mx = mx($R, $domain);
    if (@mx) {
        # Only try the highest preference MXs. Others are likely just to be
        # relays which won't know what is or isn't a valid address.
        my $p = $mx[0]->preference();
        @mailhosts = map { $_->exchange() } grep { $_->preference() == $p } @mx;
        Exim::log_write("verify_address: $email: relevant MX hosts are: " . 
join(", ", @mailhosts));
    } else {
        @mailhosts = ($domain);
        Exim::log_write("verify_address: $email: no MX records");
    }
    
    my ($result, $expln);
    
    my $bad_domain = 0;
    my $found_servers = 0;
    foreach my $host (@mailhosts) {
        my $reply = $R->send($host, 'A');
        if (!defined($reply)) {
            Exim::log_write("verify_address: $email: DNS error resolving 
'$host' (timeout?)");
            next;
        } if ($reply->header()->rcode() eq 'NXDOMAIN') {
            $bad_domain = 1;
            Exim::log_write("verify_address: $email: no such domain '$domain'");
            next;
        } else {
            $bad_domain = 0;
        }
        foreach my $r ($reply->answer()) {
            next if (ref($r) ne 'Net::DNS::RR::A');
            ++$found_servers;
            my $addr = $r->address();
            ($result, $expln) = verify_address_on_host($email, $addr);
            if ($result ne '?') {
                goto done;
            }
        }
    }
    
    if ($bad_domain) {
        ($result, $expln) = ('-', "no such domain '$domain'");
    } else {
        $expln ||= 'n/a';
        ($result, $expln) = ('?', "unable to get a definitive answer; tried 
$found_servers servers; last result: $expln");
    }

done:
    Exim::log_write("verify_address: $email: $result $expln");
    $T->store($email => "$result\0$expln\0" . time());
    if (wantarray()) {
        return ($result, $expln);
    } else {
        return $result;
    }
}


-- 
``As Lord Denning said,
  there's no smoke without fire:
  Nil Combustibus Profumo.'' (Flanders and Swann)

-- 
## List details at http://www.exim.org/mailman/listinfo/exim-users 
## Exim details at http://www.exim.org/
## Please use the Wiki with this list - http://www.exim.org/eximwiki/

Reply via email to