Initial commit.

Signed-off-by: Thomas Hochstein <thh@inter.net>
This commit is contained in:
Thomas Hochstein 2010-01-15 09:01:52 +01:00
commit 0bd5c08c20
2 changed files with 350 additions and 0 deletions

224
checkmail.pl Normal file
View file

@ -0,0 +1,224 @@
#!/usr/bin/perl -w
#
# checkmail.pl
##############
# (c) 2002-2005 Thomas Hochstein <thh@inter.net>
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 2 of the License, or (at your option)
# any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
# Versionsnummer ######################
$ver = '0.2 beta (20050803)';
# Modules #############################
use Getopt::Std;
use Net::DNS;
use Net::SMTP;
# Konfiguration #######################
# Hier passende Werte einsetzen! #
#######################################
%config=();
# HELO-/EHLO-Parameter - a valid hostname you own
$config{'helo'} = 'testhost.domain.example';
# MAIL FROM:-Parameter - a valid address you control
$config{'from'} = 'mailtest@testhost.domain.example';
# Zufaelliger Localpart fuer -r - a valid random localpart
$config{'rand'} = 'ZOq62fow1i';
################################################################
# Hauptprogramm #######################
# Konfiguration einlesen
my %options;
getopts('hqlrf:m:', \%options);
if ($options{'h'} or (!$options{'f'} and !$ARGV[0])) {
print "$0 v $ver\nUsage: $0 [-hqlr] [-m <host>] -f <file>|<address>\n";
print "Options: -h display this notice\n";
print " -q quiet (no output, just exit with 0/1/2/3)\n";
print " -l extended logging\n";
print " -r test random address to verify verification\n";
print " -m <host> no DNS lookup, just test this host\n";
print " -f <file> parse file (one address per line)\n";
print " <address> mail address to check\n\n";
exit(100);
};
if ($options{'f'}) {
if (-e $options{'f'}) {
open FILE, "<$options{'f'}" or die("ERROR: Could not open file $options{'f'} for reading: $!");
} else {
die("ERROR: File $options{'f'} does not exist!\n");
};
$log = '';
while(<FILE>) {
chomp;
($status,$log) = checkdns($_,$log);
};
close FILE;
# force exit(0)
$status = 0;
} else {
($status,$log) = checkdns($ARGV[0]);
};
print $log if ($options{'l'});
# status 0: valid / batch processing
# 1: invalid
# 2: cannot verify
# 3: temporary (?) failure
exit($status);
################################################################
# Subroutinen #########################
sub checkdns {
# - fester Host angegeben (-m)?
# - sonst: MX-Record ermitteln
# - bei Verbindungsproblemen naechsten MX versuchen
# - falls kein MX vorhanden, Fallback auf A
# -> jeweils Adresse testen via checksmtp()
my ($address,$logging) = @_;
my ($rr,$mailhost,$status,@mx);
my $dnsresult = 'okay';
# (my $lp = $address) =~ s/^([^@]+)@.*/$1/;
(my $domain = $address) =~ s/[^@]+\@(\S*)$/$1/;
$logging .= "\n----- BEGIN $address -----\n";
# DNS-Lookup unterdrueckt?
if ($options{'m'}) {
print " Connection to $options{'m'} forced by -m.\n";
$logging .= "Connection to $options{'m'} forced by -m.\n";
($status,$logging) = checksmtp($options{'m'},$address,$domain,$logging);
$logging .= "----- END $address -----\n";
return ($status,$logging);
};
# Resolver-Objekt
$resolve = Net::DNS::Resolver -> new();
$resolve->usevc(1);
$resolve->tcp_timeout(15);
# MX-Record feststellen
@mx = mx($resolve,$domain) or $dnsresult = $resolve->errorstring;
print " $domain (MX: $dnsresult)\n" if !($options{'q'});
if (@mx) {
WALKMX: foreach $rr (@mx) {
$mailhost = $rr->exchange;
print " MX: $mailhost / $address\n" if !($options{'q'});
$logging .= "Try MX: $mailhost\n";
($status,$logging) = checksmtp($mailhost,$address,$domain,$logging);
last WALKMX if ($status < 3);
};
} elsif ($dnsresult eq 'NXDOMAIN' or $dnsresult eq 'NOERROR' or $dnsresult eq 'REFUSED') {
# wenn kein MX-Record: A-Record feststellen
$logging .= "MX error: $dnsresult\n";
$dnsresult = 'okay';
$query = $resolve->search($domain) or $dnsresult = $resolve->errorstring;
print " $domain (A: $dnsresult)\n" if !($options{'q'});
if ($query) {
foreach $rr ($query->answer) {
next unless $rr->type eq "A";
$mailhost = $rr->address;
print " A: $mailhost / $address\n" if !($options{'q'});
$logging .= "Try A: $mailhost\n";
($status,$logging) = checksmtp($mailhost,$address,$domain,$logging);
};
} elsif ($dnsresult eq 'NXDOMAIN' or $dnsresult eq 'NOERROR' or $dnsresult eq 'REFUSED') {
# wenn auch kein A-Record: what a pity ...
print " > NO DNS-RECORD (MX/A) FOUND.\n" if !($options{'q'});
$logging .= "A error: $dnsresult\n";
$status = 1;
};
};
$logging .= "----- END $address -----\n";
return ($status,$logging);
};
sub checksmtp {
# - zu $mailhost verbinden, $adresse testen (SMTP-Dialog bis RCPT TO)
# - ggf. (-r) testen, ob sicher ungueltige Adresse abgelehnt oder
# alles angenommen wird
my($mailhost,$address,$domain,$logging)=@_;
my($smtp,$status,$valid);
$logging .= "-------------------------\n";
CONNECT: if ($smtp = Net::SMTP->new($mailhost,Hello => $config{'helo'},Timeout => 30)) {
$logging .= $smtp->banner;
$logging .= "EHLO $config{'helo'}\n";
$logging .= parse_reply($smtp->code,$smtp->message);
$smtp->mail($config{'from'});
$logging .= "MAIL FROM:<$config{'from'}>\n";
$logging .= parse_reply($smtp->code,$smtp->message);
# wird RCPT TO akzeptiert?
$valid = $smtp->to($address);
$logging .= "RCPT TO:<$address>\n";
if ($smtp->code > 0) {
# es kam eine Antwort auf RCPT TO
$logging .= parse_reply($smtp->code,$smtp->message);
if ($valid) {
# RCPT TO akzeptiert
$status = 0;
if ($options{'r'}) {
# werden sicher ungueltige Adressen abgewiesen?
$valid = $smtp->to($config{'rand'}.'@'.$domain);
$logging .= 'RCPT TO:<'.$config{'rand'}.'@'.$domain.">\n";
if ($smtp->code > 0) {
# es kam eine Antwort auf RCPT TO (fuer $rand)
$logging .= parse_reply($smtp->code,$smtp->message);
if ($valid) {
# ungueltiges RCPT TO akzeptiert
print " > Sorry, cannot verify. You'll have to send a testmail ...\n" if !($options{'q'});
$status = 2;
};
} else {
# Timeout nach RCPT TO (fuer $rand)
print " > Temporary failure.\n" if !($options{'q'});
$logging .= "---Timeout---\n";
$smtp->quit;
$status = 3;
};
};
print " > Address is valid.\n" if (!$status and !$options{'q'});
} else {
# RCPT TO nicht akzeptiert
print " > Address is INVALID.\n" if !($options{'q'});
$status = 1;
};
# Verbindung beenden
$smtp->quit;
$logging .= "QUIT\n";
$logging .= parse_reply($smtp->code,$smtp->message);
} else {
# Timeout nach RCPT TO
print " > Temporary failure.\n" if !($options{'q'});
$logging .= "---Timeout---\n";
$smtp->quit;
$status = 3;
};
} else {
# Verbindung fehlgeschlagen
print " > Temporary failure.\n" if !($options{'q'});
$logging .= "---Timeout---\n";
$status = 3;
};
return ($status,$logging);
};
sub parse_reply {
my($code,$message)=@_;
my($reply);
$reply = $code . ' ' . $message;
return $reply;
}

126
checkmail.readme Normal file
View file

@ -0,0 +1,126 @@
NAME
checkmail
SYNOPSIS
checkmail.pl [-hqlr] [-m <host>] -f <file>|<address>
DESCRIPTION
checkmail prüft die Zustellbarkeit von E-Mail-Adressen. Es ist
entweder die zu prüfende Adresse als letztes Argument oder
mit dem Parameter -f eine Datei mit zu prüfenden Adressen zu
übergeben.
Argumente:
-h
Mit dem Argument -h wird eine kurze Hilfe ausgegeben.
-q
Mit dem Argument -q werden alle Ausgaben unterdrückt;
das Script beendet sich nur mit einem Exit-Status zwischen
0 und 3.
-l
Mit dem Argument -l wird ein ausführliches Logging des
SMTP-Dialogs aktiviert.
-r
Mit dem Argument -r wird auch eine gezielt ungültige Adresse
geprüft, um festzustellen, ob der Host nicht einfach jede
Mailadresse ohne Prüfung akzeptiert.
-m
Mit dem Argument -m, gefolgt nach Leerzeichen von einem
Hostnamen, kann statt der Abfrage des für die Domain
zuständigen Hosts per DNS die Zustellfähigkeit bei einem
bestimmten Host geprüft werden.
Beispiel: checkmail.pl -m test.host.example mail@domain.example
-f
Mit dem Argument -f, gefolgt nach Leerzeichen von einem
Dateinamen, kann eine ganze Reihe von zu prüfenden Mailadressen
aus einer Datei eingelesen werden, die jeweils eine Adresse
pro Zeile enthält.
Beispiel: checkmail.pl -f adressen.txt
Basiskonfiguration:
Die Basiskonfiguration erfolgt innerhalb des Scripts. Anzugeben
sind:
$config{'helo'}:
Der im SMTP-Dialog für HELO/EHLO zu verwendende Hostname.
$config{'from'}:
Die Absenderadresse (Envelope-From:) für den Test.
$config{'rand'}:
Der Localpart der für den Parameter -r erforderlichen
zufälligen Adresse.
Funktionsweise:
Vor dem ersten Aufruf ist innerhalb des Scripts die
Basiskonfiguration vorzunehmen.
Danach kann das Script unter Angabe der zu prüfenden E-Mail-Adresse
aufgerufen werden. Es versucht den oder die zuständigen MX (Mail
eXchanger) für die Domain der Mailadresse zu ermitteln (ggf., falls
nicht vorhanden, an den entsprechenden Host zuzustellen), nach dort
zu verbinden und den SMTP-Dialog bis zum "RCPT TO:" durchzuführen,
um dann die Antwort des Mailservers auszuwerten.
Um Mailserver zu erkennen, die zunächst jede Mailadresse akzeptieren
und unerwünschte Mail erst später bouncen oder unterdrücken, kann
danach ein weiterer Test mit einer sicher ungültigen Mailadresse
ausgeführt werden (-r).
Nicht nur das Ergebnis des Tests, sondern auch der Dialog mit dem
Mailserver kann vermittels des Parameters -l ausgegeben werden; dies
ist hilfreich, um auszuschließen, daß die die Testverbindung aus
andere Gründen abgewiesen wird.
Wenn (gar) keine Textausgabe gewünscht ist, kann diese vermittels -q
unterdrückt werden; das Script beendet sich dann mit einem der vier
folgenden Statuscodes:
0: Adresse gültig oder Massenaufruf (-f)
1: Adresse ungültig
2: Adresse kann nicht geprüft werden (-r und negativer Test)
3: temporärer Fehler
Mehrere Mailadressen können mit Hilfe des Parameters -f innerhalb
einer Datei (eine Adresse pro Zeile) übergeben werden; in diesem
Fall wird immer der Status "0" zurückgegeben.
Schließlich kann mit Hilfe des Parameters -m die DNS-Abfrage
unterdrückt und die Verbindung zu einem bestimmten Host erzwungen
werden.
Hinweis: Der Test sollte nicht von einem Dialup-Host oder einem Host
auf einer sonstigen Blacklist aus durchgeführt werden! Ggf. kann ein
erneuter Aufruf von checkmail.pl -l für Klarheit sorgen, ob der Test
wegen der Auswertung einer Blacklist - unabhängig von der verwendeten
Adresse - fehlschlägt.
DEPENDANCIES
Die folgenden CPAN-Module werden neben Perl 5.6.1 oder höher benötigt:
Net::DNS
BUGS
- Fehler und Fehleingaben werden größtenteils nicht abgefangen.
Weitere Bugs nimmt <thh@inter.net> gerne entgegen.
AUTHOR
Thomas Hochstein <thh@inter.net>
VERSION
V 0.2 [beta]
COPYRIGHT
© 2002-2005 Thomas Hochstein.
See source for license und warranty.