I stort sett en fungerande lösning
Det finns ingen till 100 % fungerande lösning. Följande utkast funkar till 90 %.
<?php
/**
* Kontroll av mejladress
*
* @author Lars Gunther, Hans Eriksson
* @copyright (c) Lars Gunther, Hans Eriksson
* @license Creative Commons Attribution-Noncommercial-Share Alike 3.0
* http://creativecommons.org/licenses/by-nc-sa/3.0/
* @package Keryx_Globala_funktioner
* @subpackage KeryxMejl
*/
/**
* Klass med metoder för att kolla om en email-adress är riktig
*
* Varje metod anropar den föregående om det behövs, så att de bildar en trestegsraket.
* Man kan välja vilken ambitionsnivå man skall hålla sig på.
*
* @version 0.98 Beta.
* @todo Fixa problem med baklänges DNS
*/
class keryxMejl_validAddress {
/**
* @var string @mejl Adressen som undersöks
*/
private static $mejl = '';
/**
* @var string @mejl Adressens alias-del
*/
private static $alias = '';
/**
* @var string @mejl Adressens domän-del
*/
private static $domain = '';
/*
* Felmeddelanden
*/
const FEL_KORT = 'Adressen är för kort.';
const FEL_SNABEL = 'Adressen har inte exakt ett snabel-a.';
const FEL_ALIAS = 'Otillåtna tecken i mejlets alias del.';
const FEL_DOMÄN = 'Otillåtna tecken i domänangivelsen eller felaktig toppdomän.';
const FEL_INGENDOMÄN = 'Ingen domän registrerad med namnet %s.'; // Används med sprintf()
const FEL_SMTPKONTAKT = 'Kunde ej kontakta %s [%s (%u)].'; // Används: sprintf(FEL_SMTPKONTAKT,$mailserver,$errstr,$errno);
const FEL_SMTPEJSVAR = 'Mejlserver för domänen %s kunde inte kontaktas.'; // Används: sprintf(FEL_SMTPEJSVAR,self::$domain);
const FEL_SMTPFALSE = 'Användaren %s bekräftas inte av mejlservern för domänen %s.'; // Används: sprintf(FEL_SMTPFALSE,self::$mejl,self::$domain);
/**
* Funktion som kollar att adressen är korrekt enligt RFC
*
* @param string $mejl Den adress som skall testas
* @param mixed $kod Felkod från funktionen (skickas "by reference") NULL = inget fel
* @param array $fel Eventuella felmeddelanden (skickas "by reference")
* @return bool Om adressen uppfyller villkoren eller inte
* @uses keryxMailAndUriChars
*/
public static function rfcCheck($mejl='',&$kod=null,&$fel=array())
{
self::$mejl = & $mejl;
// $fel och $kod skall alltid tömmas här så att felen inte ackumuleras om man testar flera adresser
$fel = array();
$kod = null;
// Variabler att använda i regexp-tester
$MAILATOMCHAR = keryxMailAndUriChars::getString('MAILATOMCHAR');
$ADRCHAR = keryxMailAndUriChars::getString('ADRCHAR');
$TOPS = keryxMailAndUriChars::getString('TOPS');
// Förutsätt att adressen är ok
$ok = true; // Denna variabel håller reda på om villkoren uppfyllts
// Är adressen tillräckligt lång?
if ( strlen($mejl) < 5 ) {
$ok = false;
$fel[] = self::FEL_KORT;
$kod = 'rfc';
}
// Exakt ett @?
if ( preg_match_all('/@/',$mejl, $out) !== 1 ) {
$ok = false;
$fel[] = self::FEL_SNABEL;
$kod = 'rfc';
} else {
@list($alias,$domain) = explode('@',$mejl);
// Skapa self::$alias och self::$domain
// för senare bruk
self::$alias = & $alias;
self::$domain = & $domain;
// Första delen av adressen
if ( !preg_match( "/^{$MAILATOMCHAR}+(\.|{$MAILATOMCHAR}+)*\$/" , $alias ) ) {
$ok = false;
$fel[] = self::FEL_ALIAS;
$kod = 'rfc';
}
// Andra delen av adressen
if ( !preg_match( "/^{$ADRCHAR}+\.({$ADRCHAR}+\.)*({$TOPS})\$/", $domain ) ) {
$ok = false;
$fel[] = self::FEL_DOMÄN;
$kod = 'rfc';
}
}
return $ok;
}
/**
* Kontrollera DNS
*
* Denna funktion fungerar inte på Windows-servrar, då den använder checkdnsrr
*
* Denna funktion anropar rfcCheck om den inte redan körts
* @param string $mejl Den adress som skall testas
* @param mixed $kod Felkod från funktionen (skickas "by reference") NULL = inget fel
* @param array $fel Eventuella felmeddelanden (skickas "by reference")
* @return bool Om adressen går till en faktiskt registrerad domän
* @todo Fixa så att den kan köras på Windows också
*/
public static function dnsCheck($mejl,&$kod=null,&$fel=array())
{
// Kolla alltid RFC först, som dessutom tömmer $fel
if ( false === self::rfcCheck($mejl,$kod,$fel) ) {
return false;
}
if ( checkdnsrr(self::$domain,'MX') ) {
return true;
}
if ( checkdnsrr(self::$domain,'A') ) {
return true;
}
// Kommer man hit är det fel
$fel[] = sprintf(self::FEL_INGENDOMÄN,self::$domain);
$kod = 'dns';
return false;
}
/**
* Kontrollera att mejladressen verkligen finns
*
* Denna funktion anropar dnsCheck om den inte redan körts.
* Vi vet alltså att det finns minst ett A-record för domänen när testerna körs.
* De enda adresser som inte är äkta, men som slinker igenom, återfinns på "catch-all" system.
* I händelse av "Reverse DNS lookup failed" från den svarande SMTP-servern, så bekräftas inte
* korrekta adresser, vilket den anropande kodens logik måste kunna hantera.
*
* @param string $mejl Den adress som skall testas
* @param mixed $kod Svarskod från servern (skickas "by reference")
* @param array $fel Eventuella felmeddelanden (skickas "by reference")
* @param string $serv_str Svar från servern (skickas "by reference")
* @return bool Om adressen känns igen på en svarande mailserver eller inte
* @todo Denna funktion fungerar inte på Windows-servrar, då den använder mx_records
*/
public static function smtpCheck($mejl,&$kod=null,&$fel=array(),&$serv_str=null,
$server_name='keryx.se', $server_admin='gunther@keryx.se')
{
// Nollställ $serv_str så att den inte ackumuleras vid flera körningar
$serv_str = null;
// Om inte någon DNS är registrerad, så bryt nu
if ( !self::dnsCheck($mejl,$kod,$fel) ) {
return false;
}
// starta med obekräftat = false
$mejlok = false;
// self::$domain skapades i dnsCheck
getmxrr(self::$domain,$mx_records,$mx_weight);
// om MX-record finns använd det (ex "swipnet.se" har inget MX-record)
// annars om MX-record inte finns använd A-record
// Vi vet sedan tidigare self::dnsCheck() att det finns minst ett A-record
if ( count($mx_records) < 1 ) {
$mx_records[0] = self::$domain;
} else {
// sortera MX-records efter prioritet
array_multisort($mx_weight,$mx_records);
reset($mx_records);
}
// kontrollera om SMTP-server svarar
$mx_checked = false;
while ( !$mx_checked AND ( list($key,$mailserver) = each($mx_records) ) ) {
//försök att öppna anslutning till SMTP-server
$fp = @fsockopen("$mailserver", 25, $errno, $errstr, 5);
if (!$fp) {
$serv_str = sprintf(self::FEL_SMTPKONTAKT,$mailserver,$errstr,$errno);
$kod = 'smtpdown';
} else {
$mx_checked = true;
//simulara en SMTP-session för att verifiera användare
//se RFC 821 för mer detaljer i SMTP-protokollet (http://www.faqs.org/rfcs/rfc821.html)
$res = fread($fp,1024);
// Somliga mejlservrar svarar med mer än en rad när de kontaktas, därav fread
fputs($fp, "HELO $server_name\r\n");
$res .= fgets($fp,1024);
fputs($fp, 'MAIL FROM:<'.$server_admin.">\r\n");
// Catch reverse DNS-lookup failure
$prel_res = fgets($fp,1024);
if ( (int)$prel_res != 250 ) {
$kod = 'reverse_dns_lookup_failed';
$serv_str = substr($prel_res,4);
$mx_checked = false;
} else {
$res .= $prel_res;
fputs($fp, 'RCPT TO:<'.self::$mejl.">\r\n");
$svar = fread($fp,1024); // kan sträcka sig över flera rader
// $kod blir här svarskoden från smtp-servern
$kod = (int)$svar;
$serv_str = substr($svar,4);
$res .= $svar;
}
//avbryt SMTP-session (skicka inget mail)
fputs($fp, "RSET\r\n");
$res .= fgets($fp,1024);
fputs($fp, "QUIT\r\n");
$res .= fgets($fp,1024);
//stäng anslutning till SMTP-server
fclose($fp);
}
}
// Om ingen server kunde kontaktas returnera false
if ( !$mx_checked ) {
$mejlok = false;
$fel[] = sprintf(self::FEL_SMTPEJSVAR,self::$domain);
}
//om svarskod från mailserver är ok (250) eller mail forward (251) returnera true
// Svarskod mellan 450 och 460 är greylistade adresser
// Se http://projects.puremagic.com/greylisting/
else {
$mejlok = ( ($kod == 250) OR ($kod == 251) OR ( ($kod >= 450) AND ($kod < 460) ) );
if ( !$mejlok ) {
$fel[] = sprintf(self::FEL_SMTPFALSE,self::$mejl,self::$domain);
}
}
return $mejlok;
}
}