From 0bd5c08c20b3699f1c0a395a9405241600d219ac Mon Sep 17 00:00:00 2001 From: Thomas Hochstein Date: Fri, 15 Jan 2010 09:01:52 +0100 Subject: [PATCH] Initial commit. Signed-off-by: Thomas Hochstein --- checkmail.pl | 224 +++++++++++++++++++++++++++++++++++++++++++++++ checkmail.readme | 126 ++++++++++++++++++++++++++ 2 files changed, 350 insertions(+) create mode 100644 checkmail.pl create mode 100644 checkmail.readme diff --git a/checkmail.pl b/checkmail.pl new file mode 100644 index 0000000..a356f09 --- /dev/null +++ b/checkmail.pl @@ -0,0 +1,224 @@ +#!/usr/bin/perl -w +# +# checkmail.pl +############## + +# (c) 2002-2005 Thomas Hochstein +# +# 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 ] -f |
\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 no DNS lookup, just test this host\n"; + print " -f parse file (one address per line)\n"; + print "
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() { + 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; +} + diff --git a/checkmail.readme b/checkmail.readme new file mode 100644 index 0000000..46c1fa0 --- /dev/null +++ b/checkmail.readme @@ -0,0 +1,126 @@ +NAME + checkmail + +SYNOPSIS + checkmail.pl [-hqlr] [-m ] -f |
+ +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 gerne entgegen. + +AUTHOR + Thomas Hochstein + +VERSION + V 0.2 [beta] + +COPYRIGHT + © 2002-2005 Thomas Hochstein. + See source for license und warranty.