miércoles, 20 de junio de 2012

Validar E-mails con PHP & Sockets



Muchas veces en nuestros desarrollos nos toca validar campos y uno de los más importantes es el campo de “Email”.

La típica forma “expresiones”

La típica forma de realizar esto es buscando caracteres inválidos dentro de la cadena…
Hace poco tiempo, salió en la red el sitio emailvalido.com, dentro del cual colocabas tu e-mail y te decía si era valido o no.
Me llamó mucho la atención, ¿Cómo hace esto?, estuve investigando y me acordé que los servidores smtp tienen dos comandos “vrfy” y “expn” pero por seguridad estos se encuentran deshabilitados, ¿entonces cómo verificar un mail?
mediante RCPT TO, cuando se introduce un email valido que existe en el sistema este responde con un código numérico (250).
Por eso me he montado una clase, que espero que les pueda ser útil a la hora de checar mails
/**
* Clase para validar mails (http://www.coders.me - Coders community)
*
* Esta clase corre unicamente sobre Linux y  PHP5
* (siempre  y cuando esté activado el modulo de sockets en PHP)
* para más información relativa a este código visite: http://www.coders.me
* http://www.rfc-es.org/rfc/rfc1869-es.txt
*
* @author  Amir Canto Palomo <amircanto@hotmail.com>
* @copyright  validEmail Class  2008-02-23
* @version 1.3
* @todo Hacer que la clase pueda contactar con servidores SMTP que soporten SSL
* @license MIT
The MIT License
Copyright (c) 2008 www.coders.me
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/
define("CRLF","\r\n");      // [ENTER]
define("PORT","25");        // SMTP PORT.
Class ValidEmail
{
private $mail;
private $user;
private $domain;
public function validate()
{
if($sock $this->connectSMTP())
{
if($this->getResponse($sock) == "220")
{
$this->writeData($sock,"EHLO ".$this->domain.CRLF);
// echo "Mandando helo $this->domain\n";
if($this->getResponse($sock) == "250")
{
$this->writeData($sock,"HELO ".$this->domain.CRLF);
if($this->getResponse($sock) == "250")
{
$this->writeData($sock,"MAIL FROM: $this->user@".$this->domain.CRLF);
if($this->getResponse($sock) == "250")
{
$this->writeData($sock,"RCPT TO: ".$this->user."@".$this->domain.CRLF);
if($this->getResponse($sock) == "250")
{
// echo "email valido\n";
$this->writeData($sock,"QUIT".CRLF);
$this->socketClose($sock);
return 1; // valid email
}
}
}
}
}
}
return 0;
}
private function socketClose($socket)
{
socket_close($socket);
}
private function writeData($socket,$data)
{
if($socket)
{
if(socket_write($socket,$data,strlen($data)))
{
return 1;
}
}
return 0;
}
private function getResponse($socket)
{
if($socket)
{
// echo "Esperando respuesta\n";
$response = socket_read($socket,2048);
// echo "respuesta: $response\n";
if(strlen($response) > 0 )
{
//echo $response;
$rescode $response[0].$response[1].$response[2];
return $rescode;
}
}
}
private function connectSMTP()
{
if( function_exists("socket_create") && function_exists('socket_connect') ) //Ok.. existen las funciones..
{
ifempty($this->domain) || ($this->domain == "") ) $this->extractData();
if($sock = socket_create(AF_INET,SOCK_STREAM,SOL_TCP))
{
$mxrecords $this->getMxRecords();
/*echo count($mxrecords)."\n";
echo "records:".(int)$mxrecords."\n";
print_r($mxrecords);*/
if(isset($mxrecords) && (int)$mxrecords > 0 )
{
if($this->validString() )
{
foreach($mxrecords as $records)
{
if(isset($records) && $records != "")
{
// echo "Conectando con: $records \n";
$address gethostbyname($records);
if($address != $records)
{
if(socket_connect($sock,$address,PORT))
{
// echo "conectado";
return  $sock//Conected <img src="http://www.coders.me/wp-includes/images/smilies/icon_smile.gif" alt=":)" class="wp-smiley">  devolvemos el handle
}
}
}
}
else return 0;
else return 0;
}
}
return 0; // no logramos conectar / something has failed.. we cannot connect.
}
public function validString()
{
$email $this->mail;
iferegi"^([a-z0-9._]+)@([a-z0-9.-_]+).([a-z]{2,4})$"$email) )
{
return 1;
}
return 0;
}
public function getMxRecords()
{
$dominio $this->domain;
if( isset($dominio) && ($dominio != "") )
{
if(getmxrr($dominio,$records))
{
if(count($records) > 0)  // hay mx records...
{
return $records;
}
else    // NO hay MX records.. entonces usamos el dominio para conectar.
{
return $dominio// retornamos el dominio tal cual..
}
else
{
return 0; // algo fallo... no pudimos conectar
}
}
return 0; // de nueva cuenta algo falló...
}
private function extractData()
{
$data explode("@",strtolower($this->mail));
$this->user = $data[0];
$this->domain = $data[1];
}
function __construct($email)
{
$this->mail = $email;
$this->extractData();
}
}

Pequeña explicación de la clase

Lo que hace esta clase es al crear el objeto y pasarle el mail para despues llamar a las funciones correspondientes es:
  • Validar la cadena y verificar si tiene caracteres invalidos
  • Buscar el servidor smtp usando los registros mx obtenidos mediante el dominio del mail.
  • Conectar al servidor smtp si no puede intenta con el siguiente registor MX.
  • Una vez conectado comienza a interactuar con el servidor y le manda el mail usando RCPT TO: y espera la respuesta con el código 250 (que significa que el servidor validó correctamente el e-mail)

Uso de la clase

$validar new ValidEmail("correo@micorreo.com");
if($validar->validString())   // el email tiene caracteres validos
{
echo "el email tiene caracteres validos";
if($validar->validate()) // Ahora comprobamos que exista la cuenta en el servidor.
{
echo "el dominio es valido";
}
}
Las dos funciones aquí importantes son “validString()” y “validate()”

validString()

Si queremos validar solamente los caracteres del mail podemos usar unicamente esta funcion, si la cadena esta limpia devuelve true y si tiene caracteres invalidos devuelve false.

validate()

La función validate() será la forma de validar mediante el servidor smtp, devuelve true si el mail es invalido, false en caso contrario.

¿Por qué no usar la función validString() dentro de validate()?

Por que no todos vamos a querer validar mails de las dos formas de una sola vez, habrá quien solo quiera verificar los caracteres.

Errores

Hasta ahora solo he testeado la clase 2 o 3 veces. Por lo que si alguien encuentra algún error sería bueno que nos lo comente.

Errores conocidos

Por el momento la clase solo puede validar mails en los cuales los servidores smtp no estén configurados para usar SSL.

Descargar

Si quieres descargar el código fuente y los archivos de ejemplo puedes hacerlo aqui.


Fuente:
http://www.coders.me/php/validar-e-mails-con-php









Otra buena manera de comprobar un email




Algunas veces se necesita validar una cuenta de correo introducida por un usuario. Las formas más conocidas pasan por chequear la validez de la construcción de la dirección de correo mediante regular expresions y enviarle un mensaje de validación a esa cuenta y que el usuario le dé a algun link.
Pero yo buscaba algún sistema que me dijera si una dirección de correo existe realmente sin enviarle un mail de validación, y he encontrado un método que consulta a un servidor DNS por la existencia del dominio y luego le pregunta al servidor del dominio por la existencia del mailbox.

Después de mucho buscar encuentro en Tienhuis Networking un sistema que realiza tres pasos para comprobar una dirección de correo:
  1. Comprueba la correcta construcción mediante una expresión regular.
  2. Comprueba la existencia y disponibilidad de un registro MX en el DNS con el nombre del dominio de la dirección de mail.
  3. Comprueba si el mailserver encontrado accepta esa cuenta de mail.
La ventaja de este sistema de validación de correo es que no sólo se comprueba que la dirección contenga carácteres válidos, una arroba y una extensión de dominio válida, sino que consulta el propio servidor por su existencia.
Para realizar la segunda función se tira de la funcion de PHP getmxrr() (no disponible en servidores Windows!) que devuelve una posible lista de servidores de correo disponibles. Con éso podríamos decir que el dominio existe y que el servidor está en línea.
Pero para estar seguros que la cuenta existe y está operativa debemos probar la cuenta de correo contra los servidores encontrados en el punto anterior. Se abre un socket en el puerto 25 contra el servidor de correo y se le envía un conjunto de instrucciones directas (HELO, MAIL FROM, RCPT TO y QUIT) que servirán para provocar el error si la cuenta no existe en el servidor.
Este último punto es un poco engañoso, pues mientras el servidor de Hotmail(prueba por excelencia ;) ) funciona perfectamente, lo he probado en mi propio servidor de correo de mi hosting y responde OK para todas las cuentas que verifiquemos en su dominio, aunque no existan. Supongo que debe ser por tener activado el CatchAll, pero claro, ésto provoca falsos positivos. Si alguien tiene una idea será bienvenida ;)
Para poder probar y demostrar el sistema he colgado una página de test aquí:Check Mail. El código fuente de la función lo posteo a continuación, y lo pongo disponible para bajar aquí.





  1. function validateEmail($email$domainCheck = false$verify = false$return_errors=false) {
  2.     global $debug;
  3.     if($debug) {echo "<pre>";}
  4.     $errors = array();
  5.     # Check syntax with regex
  6.     if (preg_match('/^([a-zA-Z0-9\._\+-]+)\@((\[?)[a-zA-Z0-9\-\.]+\.([a-zA-Z]{2,7}|[0-9]{1,3})(\]?))$/'$email$matches)) {
  7.         $user = $matches[1];
  8.         $domain = $matches[2];
  9.         # Check availability of DNS MX records
  10.         if ($domainCheck && function_exists('checkdnsrr')) {
  11.             # Construct array of available mailservers
  12.             if(getmxrr($domain$mxhosts$mxweight)) {
  13.                 for($i=0;$i<count($mxhosts);$i++){
  14.                     $mxs[$mxhosts[$i]] = $mxweight[$i];
  15.                 }
  16.                 asort($mxs);
  17.                 $mailers = array_keys($mxs);
  18.             } elseif(checkdnsrr($domain'A')) {
  19.                 $mailers[0] = gethostbyname($domain);
  20.             } else {
  21.                 $mailers=array();
  22.             }
  23.             $total = count($mailers);
  24.             # Query each mailserver
  25.             if($total0 && $verify) {
  26.                 # Check if mailers accept mail
  27.                 for($n=0$n <$total$n++) {
  28.                     # Check if socket can be opened
  29.                     if($debug) { echo "Checking server $mailers[$n]...\n";}
  30.                     $connect_timeout = 2;
  31.                     $errno = 0;
  32.                     $errstr = 0;
  33.                     $probe_address = 'postmaster@tienhuis.nl';
  34.                     # Try to open up socket
  35.                     if($sock = @fsockopen($mailers[$n]25$errno , $errstr$connect_timeout)) {
  36.                         $response = fgets($sock);
  37.                         if($debug) {echo "Opening up socket to $mailers[$n]... Succes!\n";}
  38.                         stream_set_timeout($sock5);
  39.                         $meta = stream_get_meta_data($sock);
  40.                         if($debug) { echo "$mailers[$n] replied: $response\n";}
  41.                         $cmds = array(
  42.                             "HELO outkast.tienhuis.nl",  # Be sure to set this correctly!
  43.                             "MAIL FROM: <$probe_address>",
  44.                             "RCPT TO: <$email>",
  45.                             "QUIT",
  46.                         );
  47.                         # Hard error on connect -> break out
  48.                         if(!$meta['timed_out'] && !preg_match('/^2\d\d[ -]/'$response)) {
  49.                             $error = "Error: $mailers[$n] said: $response\n";
  50.                             break;
  51.                         }
  52.                         foreach($cmds as $cmd) {
  53.                             $before = microtime(true);
  54.                             fputs($sock"$cmd\r\n");
  55.                             $response = fgets($sock4096);
  56.                             $t = 1000*(microtime(true)-$before);
  57.                             if($debug) {echo htmlentities("$cmd\n$response") . "(" . sprintf('%.2f'$t) . " ms)\n";}
  58.                             if(!$meta['timed_out'] && preg_match('/^5\d\d[ -]/'$response)) {
  59.                                 $error = "Unverified address: $mailers[$n] said: $response";
  60.                                 break 2;
  61.                             }
  62.                         }
  63.                         fclose($sock);
  64.                         if($debug) { echo "Succesful communication with $mailers[$n], no hard errors, assuming OK";}
  65.                         break;
  66.                     } elseif($n == $total-1) {
  67.                         $errors = "None of the mailservers listed for $domain could be contacted";
  68.                     }
  69.                 }
  70.             } else {
  71.                 $error = "No usable DNS records found for domain '$domain'";
  72.             }
  73.         }
  74.     } else {
  75.         $error = 'Address syntax not correct';
  76.     }
  77.     if($debug) { echo "</pre>";}
  78.     #echo "</pre>";
  79.     if($return_errors) {
  80.         # Give back details about the error(s).
  81.         # Return FALSE if there are no errors.
  82.         # Keep this in mind when using it like:
  83.         # if(checkEmail($addr)) {
  84.         # Because of this strange behaviour this
  85.         # is not default ;-)
  86.         if(isset($error)) return htmlentities($error)else return false;
  87.     } else {
  88.         # 'Old' behaviour, simple to understand
  89.         if(isset($error)) return falseelse return true;
  90.     }
  91. }

Fuente: