cómo se hace un formulario de contacto_ un mailer con php

47
11/7/2014 Cómo se hace un formulario de contacto: Función mail() de PHP http://www.wextensible.com/como-se-hace/formulario-contacto/ 1/16 27 DIC 2011 El formulario de contacto con función mail() de PHP Inicio » Cómo se hace » I. El formulario de contact...» II. Incorporando seguridad al f... » III. Evitar el uso automático de... » IV. Un Mail User Agent con PHP ... 1. El formulario de contacto 2. Configurando la función mail() de PHP bajo Windows 3. Un formulario para usar con la función mail() de PHP 4. El servidor de correo SMTP 5. El servidor de correo POP3 1. El formulario de contacto El formulario de contacto con envío de mensajes a una cuenta de email es una utilidad de uso de un sitio web. Cuando la gente ya lleva tiempo usando otras forma alternativas de comunicarse, como las redes sociales, dedicar un rato a estudiar cómo funciona el correo electrónico puede parecer una pérdida de tiempo. Realmente no lo sé, pero creo que es bueno tener al menos una idea general acerca de este tema. Hay varias cosas que quisiera saber, por ejemplo, cómo funciona básicamente un servidor de correo electrónico o qué son los protocolos SMTP o POP3 entre otros. Además sería interesante instalar un servidor de correo a modo de localhost para hacer pruebas. Normalmente se usa la función mail() de PHP para enviar el mensaje a una cuenta de correo del administrador del sitio. Pero ¿Y sí el propietario del servidor no me permite usar esa función por tenerla desactivada para evitar el spam? ¿Puedo usar algún módulo alternativo a mail() de PHP?. Y sobre todo qué riesgos hay que controlar para que no usen nuestro formulario de contacto con otros propósitos que el previsto, especialmente usando la técnica del CAPTCHA. En definitiva, varias cosas por saber que espero wextensible

Upload: leon-verde

Post on 27-Dec-2015

105 views

Category:

Documents


1 download

TRANSCRIPT

Page 1: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Función mail() de PHP

http://www.wextensible.com/como-se-hace/formulario-contacto/ 1/16

27

DIC

2011

El formulario de contacto con función mail() de PHP

Inicio» Cómo se hace» I. El formulario de contact...» II. Incorporando seguridad al f...» III. Evitar el uso automático

de...» IV. Un Mail User Agent con PHP ...

1. El formulario de contacto

2. Configurando la función mail() de PHP bajo Windows

3. Un formulario para usar con la función mail() de PHP

4. El servidor de correo SMTP

5. El servidor de correo POP3

1. El formulario de contacto

El formulario de contacto con envío de mensajes a una cuenta de email es una

utilidad de uso de un sitio web. Cuando la gente ya lleva tiempo usando otras forma

alternativas de comunicarse, como las redes sociales, dedicar un rato a estudiar cómo

funciona el correo electrónico puede parecer una pérdida de tiempo. Realmente no lo

sé, pero creo que es bueno tener al menos una idea general acerca de este tema. Hay

varias cosas que quisiera saber, por ejemplo, cómo funciona básicamente un servidor

de correo electrónico o qué son los protocolos SMTP o POP3 entre otros. Además

sería interesante instalar un servidor de correo a modo de localhost para hacer

pruebas.

Normalmente se usa la función mail() de PHP para enviar el mensaje a una cuenta de correo del

administrador del sitio. Pero ¿Y sí el propietario del servidor no me permite usar esa función por tenerla

desactivada para evitar el spam? ¿Puedo usar algún módulo alternativo a mail() de PHP?. Y sobre todo qué

riesgos hay que controlar para que no usen nuestro formulario de contacto con otros propósitos que el

previsto, especialmente usando la técnica del CAPTCHA. En definitiva, varias cosas por saber que espero

wextensible

Page 2: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Función mail() de PHP

http://www.wextensible.com/como-se-hace/formulario-contacto/ 2/16

aprenderlas con estas pruebas. El objetivo final es incorporar todo esto en un nuevo formulario de contacto

para este sitio.

La función mail() de PHP puede conectar con cualquier servidor de correo, pero a veces éstos bloquean

mensajes que provienen de servidores localhost. El asunto tiene que ver con el uso de Apache+PHP para

enviar spam. No me interesa saber como conseguir enviar correos con servidores externos, sino más bien

cómo funciona el correo y los servidores de correo. Es mejor instalarse uno su propio servidor de correo en

modo local y hacer con comodidad todas las pruebas que necesitemos. De otra forma, usando un servidor

externo, no sabremos muy bien si el correo no llegó porque hicimos algo mal en el script o que el servidor lo

bloqueó.

Hay servidores de correo que podemos montar en local para hacer pruebas. Algunos sólo como una demo,

otros tienen licencias no comerciales con lo que podemos usarlos sólo en un entorno localhost. El sitio del

autor David Harris Pegasus Mail - Mercury contiene la aplicación de correo Pegasus y el servidor Mercury,

ambos para Windows. Este servidor tiene una licencia libre para uso individual sin fines comerciales. Por lo

tanto nos servirá para hacer estas pruebas, pero he de decir que no pretendo convertirme en un experto de

servidores de correo, pues la finalidad de instalarlo es sólo y exclusivamente para hacer pruebas con la

función mail() y aprender algo sobre el protocolo SMTP. A continuación hay unas capturas de pantalla que

tomé cuando hice la instalación y la configuración. Son requirimientos mínimos para que la cosa funcione,

pues hay otras configuraciones que no me he detenido a estudiar:

Instalando y configurando Mercury

Page 3: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Función mail() de PHP

http://www.wextensible.com/como-se-hace/formulario-contacto/ 3/16

1/20

Estos son todos los pasos que seguí para instalarlo. En cada pantalla el botón que pulsé esel que aparece con el foco.

NOTA: Mercury y Pegasus Mail son marcas del software del autor David Harris. Para más detalles ver su sitio web Pegasus

Mail - Mercury.

2. Configurando la función mail() de PHP bajo Windows

La función mail() de PHP nos permite enviar correos electrónicos. Sin embargo hay una diferencia

importante si nuestro servidor Apache+PHP está en Windows o Unix. El archivo de configuracion php.ini

que viene por defecto con la instalación de PHP contiene una parte para la configuración de la función

mail():

[mail function]; For Win32 only.; http://php.net/smtpSMTP = localhost; http://php.net/smtp-portsmtp_port = 25

Page 4: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Función mail() de PHP

http://www.wextensible.com/como-se-hace/formulario-contacto/ 4/16

; For Win32 only.; http://php.net/sendmail-from;sendmail_from = [email protected]

; For Unix only. You may supply arguments as well (default: "sendmail -t -i").; http://php.net/sendmail-path;sendmail_path =

; Force the addition of the specified parameters to be passed as extra parameters; to the sendmail binary. These parameters will always replace the value of; the 5th parameter to mail(), even in safe mode.;mail.force_extra_parameters =

; Add X-PHP-Originating-Script: that will include uid of the script followed by the filenamemail.add_x_header = On

; The path to a log file that will log all mail() calls. Log entries include; the full path of the script, line number, To address and headers.;mail.log =

En el sistema operativo Unix existe un ejecutable local que funciona a modo de MTA. Se trata del

denominado Mail Transfer Agent o Message Transfer Agent o a veces también llamado mail relay, términos

que se traducen como Agente de Transferencia de Correo. El MTA es el encargado de transferir correo de un

ordenador a otro, usando la arquitectura cliente-servidor basada en el protocolo SMTP, implementando un

smtp server y un stmp client.

En cambio bajo Windows hemos de montar un MTA para lograr transferir el correo, pues este sistema

operativo no trae ese ejecutable. Yo tengo mi Apache+PHP en localhost bajo Windows. Para tener un MTA

he instalado un servidor de correo que incorpora esa función aparte de otras como servidor de POP3.

Por lo tanto usaré un servidor de correo que he denominado localemail montado a modo localhost, cuyo

servidor SMTP lo he llamado smtp.localemail. He creado 3 cuentas de prueba [email protected],

[email protected] y [email protected]. Podría hacer pruebas usando servidores externos

como Gmail, Hotmail o Yahoo. O incluso el propio servidor de correo de nuestro sitio en producción. Pero

estos servidores suelen estár protegidos para no gestionar correos procedentes de un dominio localhost,

pues son una posible fuente de spam y otros riesgos. Como dije antes, no trato aquí de buscar la forma de

Page 5: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Función mail() de PHP

http://www.wextensible.com/como-se-hace/formulario-contacto/ 5/16

saltarnos esas protecciones para usar servidores externos, pues no es ese mi interés. Creo que es más

productivo saber instalar y configurar un servidor de correo en modo local para hacer estas pruebas.

Una vez instalado el servidor de correo, hemos de configurar el php.ini a algo como esto:

[mail function]; For Win32 only.; http://php.net/smtpSMTP = smtp.localemail; http://php.net/smtp-portsmtp_port = 25

; For Win32 only.; http://php.net/sendmail-fromsendmail_from = php@localhost

Estos valores que vamos a dar en el php.ini son configurados por el administrador de nuestro servidor en

producción, por lo que no tendremos porque cambiarlos cuando usemos mail() en nuestro sitio real. Pero

para estas pruebas locales si hemos de hacerlo. Ponemos el SMTP al de nuestro servidor local de correo

smtp.localemail. Dejamos el puerto 25 que viene por defecto. También hemos de poner una dirección para

la cabecera from. En este caso ponemos php@localhost, una dirección que ni siquiera existe pero es

necesario poner algo. Esta dirección en principio no tiene mayor interés ahora y, como dije antes, estos

datos son puestos por el administrador del servidor. Luego veremos un poco más de esto.

3. Un formulario para usar con la función mail() de PHP

Vamos ahora con el formulario de contacto que podría ser un PHP como este primer ejemplo llamado mail-

01.php (ver el código). Este ejemplo sirve única y exclusivamente para hacer pruebas con la funcion

mail(), observando los riesgos de seguridad relacionados con el email pero no tiene en cuenta otras

medidas (como por ejemplos las que expongo en formularios seguros). Por lo tanto ni puede ejecutarse

desde este sitio ni mucho menos usarlo para un propósito real.

<?php/* mail-01.php * Ejemplo de formulario de contacto para enviar email. Esto es un ejemplo

Page 6: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Función mail() de PHP

http://www.wextensible.com/como-se-hace/formulario-contacto/ 6/16

* muy básico sin protecciones de seguridad, sólo para entender cómo es la * función mail() de PHP. * Andrés de la Paz © 2011 * http://www.wextensible.com * */$nombre = "";$email = "";$mensaje = "";$form_iniciado = false;$enviado = false;$mensaje_error = "";if (isset($_GET) && isset($_GET["envio"]) && ($_GET["envio"]=="Enviar")){ foreach($_GET as $campo=>$valor){ switch ($campo) { case "nombre": $nombre = $valor; break; case "email": $email = $valor; break; case "mensaje": $mensaje = $valor; break; } } if (($nombre != "")&&($email != "")&&($mensaje != "")){ $form_iniciado = true; $destino = "[email protected]"; $cabeceras = "From: ".$email."\n"; $asunto = "Mensaje de contacto"; $cuerpo = "MENSAJE DEL FORMULARIO DE CONTACTO\n". "NOMBRE: ".$nombre."\n". "MENSAJE: \n".$mensaje; $enviado = @mail($destino, $asunto, $cuerpo, $cabeceras); $arr_error = error_get_last(); if (!is_null($arr_error)){ $mensaje_error = "No se pudo enviar el mensaje: ".$arr_error["message"]; } } else { $mensaje_error = "Todos los campos son requeridos"; } }?>

Page 7: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Función mail() de PHP

http://www.wextensible.com/como-se-hace/formulario-contacto/ 7/16

<!DOCTYPE html><html lang="es"><head> <meta http-equiv="content-type" content="text/html; charset=UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge; chrome=1" /> <title>Ejemplo función mail() de PHP</title></head><body> <h3>Formulario de contacto</h3> <?php if(!$form_iniciado){ ?> <form action="mail-01.php" method="get"> Nombre <input type="text" name="nombre" size="60" value="<?php echo $nombre; ?>" /><br /> Email <textarea name="email" rows="2" cols="60"> <?php echo $email; ?></textarea><br /> Mensaje <textarea name="mensaje" rows="10" cols="50"> <?php echo $mensaje; ?></textarea> <input type="submit" name="envio" value="Enviar" /> </form> <?php } else { if ($enviado) {?> <p>Hemos recibido su mensaje.</p> <?php } else { ?> <p>Hubo un error en el envío.</p> <?php }?> <p>Datos del mensaje:</p> <ul> <li>Nombre: <?php echo $nombre; ?></li> <li>Email: <?php echo $email; ?></li> <li>Mensaje: <?php echo $mensaje; ?></li> </ul> <?php }?></body> </html>

Si queremos recibir un mensaje, suponemos que lo mínimo será conocer el $nombre de la persona y el

$mensaje que quiere comunicarnos. Esto en principio no tiene mayores problemas. El campo de su dirección

de $email es el más importante, pues nos permitirá responderle. Hemos puesto un <textarea> en este

campo para probar los riesgos de seguridad, pero lo usual es que sea un elemento <input type="text">

Page 8: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Función mail() de PHP

http://www.wextensible.com/como-se-hace/formulario-contacto/ 8/16

(en el ejemplo que vamos a ejecutar veremos el motivo de esto). Estos datos los recibimos en el servidor y

los insertamos en la función mail()

$enviado = @mail($destino, $asunto, $cuerpo, $cabeceras)

El $destino es la dirección de correo del administrador del sitio donde queremos recibirlo. En este caso el

ejemplo pone $destino = "[email protected]" y sería una cuenta de nuestro servidor de correo

donde veríamos los mensajes del formulario de contacto. El último argumento son las $cabeceras del

email. Entonces lanzamos el formulario de contacto y vamos a enviar nuestro primer mensaje:

4. El servidor de correo SMTP

Antes de seguir con el ejemplo anterior, debemos saber que la especificación RFC5321 Simple Mail Transfer

Protocol (Protocolo simple de transferencia de correo) expone la semántica y sintaxis de los comandos a

usar en una comunicación SMTP. Ese documento actualiza la versión anterior RFC2821, que a su vez

actualizó la RFC821. Al final de esa especificación hay algunas muestras de ejemplos como A Typical SMTP

Transaction Scenario que expone una simple comunicación.

Veámos ahora que pasó con el correo de ejemplo que envié. Recuerde que debe ir al destino

[email protected] y debe decir que proviene de [email protected]. Observando el panel de

control del Mercury, la parte que gestiona los mensajes del SMTP-Server:

Page 9: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Función mail() de PHP

http://www.wextensible.com/como-se-hace/formulario-contacto/ 9/16

La conexión se establece a través de la IP 127.0.0.4, que es sobre la que configuré el SMTP-Server del

Mercury. Lo que vemos son las peticiones según el protocolo SMTP que recibe el MTA (el SMTP-Server) de la

función mail() de PHP, que hace las veces de un MUA (Mail User Agent o Agente de Usuario de Correo). La

función mail() le ha enviado las siguientes peticiones en azul y el SMTP-Server le va respondiendo en color

marrón:

HELO HP92155003154250-smtp.localemail Hello smtp.localemailMAIL FROM:<php@localhost>250 Sender OK - send RCPTs.RCPT TO:<[email protected]>250 Recipient OK - send RCPT or DATA.DATA354 OK, send data, end with CRLF.CRLFFrom: [email protected]: [email protected]: Mensaje de contacto

Page 10: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Función mail() de PHP

http://www.wextensible.com/como-se-hace/formulario-contacto/ 10/16

Date: Sat, 3 Dec 2011 20:33:16 +0000

MENSAJE DEL FORMULARIO DE CONTACTONOMBRE: AndrésMENSAJE: Prueba1.250 Data received OK.QUIT221 smtp.localemail Service closing channel.

El MUA se identifica con la palabra clave HELO y su nombre de dominio. En este caso pone el nombre de la

máquina que es suministrado por la función mail(). El SMTP-Server le responde con un código 250 de

estado correcto. Luego el MUA (recuerde que es la función mail() de PHP) le dice la dirección de

procedencia MAIL FROM:<php@localhost>. Esta proviene de la configuración

sendmail_from=php@localhost que pusimos en el php.ini. El SMTP-Server responde correcto y le pide un

destinatario (RCPT). El MUA le envía el destinatario con RCPT TO: <[email protected]>. Esta es la

dirección que pusimos en el primer argumento de la función mail():

$destino = "[email protected]";$cabeceras = "From: ".$email."\n";$asunto = "Mensaje de contacto";$cuerpo = "MENSAJE DEL FORMULARIO DE CONTACTO\n". "NOMBRE: ".$nombre."\n". "MENSAJE: \n".$mensaje;$enviado = @mail($destino, $asunto, $cuerpo, $cabeceras);

A continuación el SMTP-Server le pide otro RCPT o los datos, es decir, el cuerpo del mensaje (DATA). En este

caso el MUA envía la palabra DATA para hacerle saber eso al SMTP-Server, quién le comunica un código 354

diciendo que está preparado, puede enviar los datos y que los finalice con dos saltos de linea CLRF.CRLF

con un punto en medio. Los datos ocupan 9 líneas que podemos desglosar en estos grupos:

From: [email protected], este es el "supuesto" origen del mensaje, pero que realmente hemos

insertado en el script PHP dentro del argumento cabeceras de la función mail() mediante $cabeceras

= "From: ".$email."\n";

Page 11: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Función mail() de PHP

http://www.wextensible.com/como-se-hace/formulario-contacto/ 11/16

To: [email protected]. Este es el destinatario, el mismo del argumento $destino =

"[email protected]" para mail().

Subject: Mensaje de contacto, el argumento $asunto = "Mensaje de contacto" para mail().

Date: Sat, 3 Dec 2011 20:33:16 +0000. La fecha es obligatoria para algunos servidores y la inserta

mail() automáticamente.

El cuerpo del mensaje, es decir, el contenido de texto del mensaje, que hemos compuesto en el script

PHP en la variable $cuerpo. La palabra Andrés es la correspondiente a Andrés pero dado que no

especificamos una cabecera de UTF-8 en el correo, el servidor no puede descifrar esos caracteres. Es

algo que tendré que estudiar como se resuelve.

La función mail() finalizará con CLRF.CRLF

Finalmente el SMTP-Server le responde un 250 de datos recibidos y correctos. El MUA finaliza la conexión

enviando la palabra QUIT y el servidor le responde con un código 221 cerrando el canal de conexión. Lo más

importante hasta aquí es que hay dos grupos de origenes y destinatarios:

1. Los del sobre (o envolope en inglés), correspondientes a los establecidos en los primeros pasos de la

conexión con

El origen MAIL FROM:<php@localhost>

El destinatario RCPT TO:<[email protected]>

2. Y los del propio mensaje establecidos en el DATA:

El origen From: [email protected]

El destinatario To: [email protected]

Los primeros son los que sirven para trasladar un email desde un servidor de correo a otro. Los segundos

son los que se usan para trasladar el mensaje desde el servidor final de correo a un buzón de ese servidor

que será el destinatario final. Admito que es bastante díficil de entender, pero se hace más fácil si vemos el

siguiente paso.

5. El servidor de correo POP3

El sobre de correo que se recibe en un servidor puede ir destinado a otro servidor de otro dominio. E incluso

puede saltar entre varios servidores. En todo caso tiene que alcanzar al destinatario del mensaje indicado

Page 12: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Función mail() de PHP

http://www.wextensible.com/como-se-hace/formulario-contacto/ 12/16

en To: [email protected]. En este ejemplo todo ocurre dentro del mismo dominio y servidor. El

SMTP-Server tiene que mirar el dominio de destino del sobre RCPT TO:<[email protected]> para

enviarlo a ese servidor, que en este caso es el mismo. Entonces busca los destinatarios finales en To:

[email protected] y encuentra que la dirección [email protected] está en su lista de mailbox

directory, directorio de buzones de correo:

El protocolo POP3 se encarga de la entrega final de los mensajes a MUA's como el Outlook express de

Windows, por ejemplo. Ahora tenemos un servidor POP3 y un cliente POP3. El servidor POP3 recibe los

mensajes de un servidor SMTP y los transfiere a un cliente POP3 que se encarga de la entrega final.

Page 13: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Función mail() de PHP

http://www.wextensible.com/como-se-hace/formulario-contacto/ 13/16

En el Mercury configuré el servidor pop3.localemail apuntando a la IP 127.0.0.5. El mensaje es para el

usuario admin y ocupa 482 bytes. Ahora es el cliente POP3 quién se encarga de la entrega:

En este caso el cliente POP3 estableció contacto con todos los MUA's conectados. En mi caso hice que el

Outlook Express de Windows se conectara al servidor de correo. Esta es la configuración de la cuenta

Page 14: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Función mail() de PHP

http://www.wextensible.com/como-se-hace/formulario-contacto/ 14/16

[email protected]:

Se observa como el correo entrante debe buscarlo en el servidor pop3.localemail. Y este es el correo

recibido en la bandeja de entrada del MUA Outlook Express:

Page 15: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Función mail() de PHP

http://www.wextensible.com/como-se-hace/formulario-contacto/ 15/16

Vemos que aparece el origen De señalado como [email protected], pero que realmente no vino

desde esa dirección sino de la función mail(). En las propiedades del mensaje podemos ver el mensaje

completo, cuyo código fuente copiamos y pegamos aquí para observarlo mejor:

Received: from spooler by smtp.localemail (Mercury/32 v4.72); 3 Dec 2011 20:34:50 -0000X-Envelope-To: [email protected]: from POP3D by smtp.localemail with MercuryD (v4.72); 3 Dec 2011 20:34:41 -0000Received: from spooler by smtp.localemail (Mercury/32 v4.72); 3 Dec 2011 20:34:17 -0000X-Envelope-To: [email protected]: from POP3D by smtp.localemail with MercuryD (v4.72); 3 Dec 2011 20:34:06 -0000Received: from spooler by smtp.localemail (Mercury/32 v4.72); 3 Dec 2011 20:33:32 -0000X-Envelope-To: [email protected]: from POP3D by smtp.localemail with MercuryD (v4.72); 3 Dec 2011 20:33:32 -0000Received: from spooler by smtp.localemail (Mercury/32 v4.72); 3 Dec 2011 20:33:21 -0000X-Envelope-To: <[email protected]>Return-path: <php@localhost>Received: from HP92155003154 (127.0.0.4) by smtp.localemail (Mercury/32 v4.72) ID MG000001; 3 Dec 2011 20:33:16 -0000Date: Sat, 03 Dec 2011 20:33:16 +0000Subject: Mensaje de contactoTo: [email protected]: [email protected]

MENSAJE DEL FORMULARIO DE CONTACTONOMBRE: AndrésMENSAJE: Prueba1

Los distintos agentes van agregando cabeceras al mensaje antes de la fecha, es decir, desde la línea de

Page 16: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Función mail() de PHP

http://www.wextensible.com/como-se-hace/formulario-contacto/ 16/16

Date y hacia arriba. Así la primera cabecera es la que agregró el SMTP-Server al recibirlo de la función

mail(), que en este caso pone como referencia la máquina from HP921... (mi ordenador). Luego el SMTP-

Server lo envia al POP3 para que lo distribuya al destinatario final [email protected]. No es ahora mi

intención entender completamente las cabeceras de un email, pues es bastante complicado. Sólo quiero

tener una visión general del tema para cuando haga uso de la función mail() saber más sobre cuestiones

de seguridad.

Page 17: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Incorporando seguridad

http://www.wextensible.com/como-se-hace/formulario-contacto/email-seguro.html 1/12

27

DIC

2011

Incorporando seguridad al formulario de contacto

Inicio» Cómo se hace» I. El formulario de contacto c...» II. Incorporando seguridad ...» III. Evitar el uso automático

de...» IV. Un Mail User Agent con PHP ...

1. Uso indebido de la función mail() de PHP

2. Evitar la inserción de cabeceras en mail() de PHP

3. Medidas de seguridad para usar mail() en el formulario de contacto

4. Codificación UTF-8 en el formulario

5. La longitud de un texto en UTF-8

1. Uso indebido de la función mail() de PHP

Con el formulario de contacto de ejemplo del tema anterior me propongo

realizar algunas pruebas de usos indebidos de la función mail() de PHP.

Usaré el servidor de correo Mercury montado a modo de localemail y dos

aplicaciones MUA (Mail User Agent) Outlook Express y Pegasus Mail para

que se conecten mediante POP3 a ese servidor. Los usuarios de prueba

serán [email protected] que consultaré desde Outlook Express y

[email protected] desde Pegasus Mail.

La prueba consistirá en enviar un mensaje desde el formulario de contacto

insertando indebidamente una cabecera CC en el campo del email, como se

observa en la imagen. Recuerde que configuramos el script de la función mail() de PHP para que nos

remitiera un correo a nuestra dirección [email protected]. También comenté que el campo para el

email es un <textarea>, por lo que tras la primera dirección [email protected] podemos poner un

salto de línea y Cc: [email protected]. De esta forma estamos enviando una copia del correo a ese

wextensible

Page 18: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Incorporando seguridad

http://www.wextensible.com/como-se-hace/formulario-contacto/email-seguro.html 2/12

otro usuario . Esto es lo que vemos en los dos MUA's, en el Outlook Express (donde muestra la hora 11:31

del envío desde el POP3):

Y en el Pegasus Mail (la hora 11:30 que muestra es la de la cabecera Date, momento en el que se recibe el

mensaje en el SMTP-Server. No es la del envío de POP3 que fue a las 11:31):

La duplicación de mensajes se entiende si vemos el estado del cliente POP3 en Mercury. A las 11:31:08 se

conecta el usuario [email protected] (que está en el Outlook) y el servidor le entrega el mensaje y a

su vez remite una copia a [email protected]. A las 11:31:09 se conecta el usuario que está en

Pegasus user2@localemail, el servidor le entrega el mensaje que va su nombre y envía a su vez una copia

a [email protected].

Page 19: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Incorporando seguridad

http://www.wextensible.com/como-se-hace/formulario-contacto/email-seguro.html 3/12

Puede ver una copia de las cabeceras de estos mensajes. En todo caso aquí lo importante no es esta

duplicación, sino el hecho de que podrían permitirse enviar múltiples correos a modo de spam. Vea como en

la primera de las cabeceras encontramos dos X-Envelope-To, es decir, los dos destinatarios

[email protected] y [email protected]:

Received: from spooler by smtp.localemail (Mercury/32 v4.72); 6 Dec 2011 11:31:11 -0000X-Envelope-To: [email protected]: from POP3D by smtp.localemail with MercuryD (v4.72); 6 Dec 2011 11:31:10 -0000Received: from spooler by smtp.localemail (Mercury/32 v4.72); 6 Dec 2011 11:31:00 -0000X-Envelope-To: <[email protected]>Return-path: <php@localhost>Received: from HP92155003154 (127.0.0.4) by smtp.localemail (Mercury/32 v4.72) ID MG00000A; 6 Dec 2011 11:30:54 -0000

Page 20: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Incorporando seguridad

http://www.wextensible.com/como-se-hace/formulario-contacto/email-seguro.html 4/12

Date: Tue, 06 Dec 2011 11:30:54 +0000Subject: Mensaje de contactoTo: [email protected]: [email protected]: [email protected]

El problema es que el campo donde va la dirección email no debe permitir saltos de línea, pues alguién

podría insertar una lista de cabeceras Cc para hacer salir múltiples copias del mensaje. En esto y otros

agujeros de seguridad se basa el spam. No basta con cambiar el <textarea> por un <input

type="text">, el cual suprime los saltos de línea. Por un lado podrían ponernos una lista de direcciones de

correo separadas por comas. Por otro lado este ejemplo envía los datos por GET, por lo que podrían

también hacer una petición directa con cabeceras Cc, Bcc o con una lista de direcciones. Por ejemplo:

http://.../mail-01.php?nombre=A&email=user1%40smtp.localemail%0D%0ACc%3A+user2%40smtp.localemail&mensaje=XXXXXXX&envio=Enviar

Esta cadena la he separado en dos líneas pero realmente es una única línea. Los puntos suspensivos sería

la ruta donde se encontraría el script mail-01.php que vimos en el tema anterior. Veáse resaltado el salto

de línea codificado para enviar por URL %0D%0A antes de la cabecera de copia Cc. Algo que podemos hacer

es cambiar el método de envío a POST, pero aún así se pueden insertar cabeceras. Con algo como Telnet

podemos enviar una petición como esta:

POST /.../mail-01.php HTTP/1.1Host: localhostContent-Type: application/x-www-form-urlencodedContent-Length: 102

nombre=A&email=user1%40smtp.localemail%0D%0A...&envio=Enviar

Esta petición incluye el tipo de contenido application/x-www-form-urlencoded que es para poner los

campos y valores codificados tal como lo hacemos en las peticiones GET. La longitud de 102 caracteres es la

que se corresponde con los caracteres de la cadena completa, la cual hemos cortado aquí para simplificar.

Antes de esa cadena hay que poner un salto de línea.

2. Evitar la inserción de cabeceras en mail() de PHP

Page 21: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Incorporando seguridad

http://www.wextensible.com/como-se-hace/formulario-contacto/email-seguro.html 5/12

Por lo tanto sea con GET o POST hemos de impedir que el usuario pueda modificar las cabeceras que

incluiremos en la función mail(). Recordemos como era nuestro script en ese punto:

$destino = "[email protected]";$cabeceras = "From: ".$email."\n";$asunto = "Mensaje de contacto";$cuerpo = "MENSAJE DEL FORMULARIO DE CONTACTO\n". "NOMBRE: ".$nombre."\n". "MENSAJE: \n".$mensaje;$enviado = @mail($destino, $asunto, $cuerpo, $cabeceras);

Para evitar exponer el argumento de $cabeceras (headers) con el email que nos ponga el usuario del

formulario de contacto, podemos incluir esa dirección dentro del cuerpo de texto del mensaje:

$destino = "[email protected]";$cabeceras = "From: [email protected]\n";$asunto = "Mensaje de contacto";$cuerpo = "MENSAJE DEL FORMULARIO DE CONTACTO\n". "NOMBRE: ".$nombre."\n". "EMAIL: ".$email."\n". "MENSAJE: \n".$mensaje;$enviado = @mail($destino, $asunto, $cuerpo, $cabeceras);

De esta forma cualquier cosa que introduzcan en nuestro formulario de contacto irá a parar al cuerpo del

mensaje, no tocándo en ningún caso las cabeceras ni por supuesto la línea del $asunto (subject) que lo

dejamos con una cadena fija, pues ahí también podrían insertarse cabeceras con saltos de línea. Hacemos

ese cambio en el script anterior y lo llamamos ahora mail-02.php (ver código). Por ejemplo, enviando un

mensaje con el formulario de prueba con 4 direcciones en el campo de email y habiendo hecho esos cambios

en el script tenemos el siguiente mensaje recibido en el correo del administrador del sitio (la cuenta

[email protected]):

Page 22: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Incorporando seguridad

http://www.wextensible.com/como-se-hace/formulario-contacto/email-seguro.html 6/12

Todas las direcciones introducidas en el campo $email recibido del formulario van a parar dentro del texto

del cuerpo del mensaje. El remitente y el destinatario son ambos [email protected]. Por supuesto

que en una versión final del formulario de contacto podemos impedir que se ponga más de una dirección en

el campo email, pero eso lo veremos a continuación con una versión mejorada del formulario.

3. Medidas de seguridad para usar mail() en el formulario de contacto

Ahora tenemos una nueva versión del script que controla el formulario. Se trata de la página email-03.php

(ver código) que tiene este PHP al inicio

$nombre = "";$max_longitud_nombre = 50;$email = "";$max_longitud_email = 50;$mensaje = "";$max_longitud_mensaje = 500;$form_iniciado = false;$enviado = false;$mensaje_error = "";if (isset($_POST) && isset($_POST["envio"]) && ($_POST["envio"]=="Enviar")){ foreach($_POST as $campo=>$valor){

Page 23: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Incorporando seguridad

http://www.wextensible.com/como-se-hace/formulario-contacto/email-seguro.html 7/12

$valor = htmlspecialchars($valor, ENT_QUOTES); $longitud = strlen(utf8_decode($valor)); switch ($campo) { case "nombre": $nombre = $valor; if ($nombre == ""){ $mensaje_error .= "El nombre es requerido.<br />"; } else if ($longitud > $max_longitud_nombre) { $mensaje_error .= "Nombre sobrepasa ". $max_longitud_nombre." letras.<br />"; } break; case "email": $email = $valor; $patron = "/̂\w+(?:[\-\.]?\w+)*@\w+(?:[\-\.]?\w+)". "*(?:\.[a-zA-Z]{2,4})+$/"; if ($email == ""){ $mensaje_error .= "El email es requerido.<br />"; } else if ($longitud > $max_longitud_email) { $mensaje_error .= "Email sobrepasa ". $max_longitud_email." letras.<br />"; } else if (!preg_match($patron, $email)) { $mensaje_error .= "Email no válido.<br />"; } break; case "mensaje": $mensaje = $valor; if ($mensaje == ""){ $mensaje_error .= "El mensaje es requerido.<br />"; } else if ($longitud > $max_longitud_mensaje) { $mensaje_error .= "Mensaje sobrepasa ". $max_longitud_mensaje." letras.<br />"; } $mensaje = wordwrap($mensaje, 70, PHP_EOL, true); break; } } if ($mensaje_error == ""){ $form_iniciado = true; $destino = "[email protected]";

Page 24: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Incorporando seguridad

http://www.wextensible.com/como-se-hace/formulario-contacto/email-seguro.html 8/12

$cabeceras = "From: [email protected]".PHP_EOL. "X-Mailer: PHP-mail".PHP_EOL. "MIME-Version: 1.0".PHP_EOL. "Content-type: text/plain; charset=UTF-8".PHP_EOL. "Content-transfer-encoding: 8BIT".PHP_EOL; $asunto = "Mensaje de contacto"; $cuerpo = "MENSAJE DEL FORMULARIO DE CONTACTO".PHP_EOL. "NOMBRE: ".$nombre.PHP_EOL. "EMAIL: ".$email.PHP_EOL. "MENSAJE: ".PHP_EOL.$mensaje; $enviado = @mail($destino, $asunto, $cuerpo, $cabeceras); if (!$enviado){ $mensaje_error = "No se pudo enviar el mensaje. "; //En producción no debería mostrarse el error $arr_error = error_get_last(); $mensaje_error .= $arr_error["message"]; } }}

En este ejemplo escapamos en los valores recibidos todas las referencias a caracteres reservados de HTML

con la función htmlspecialchars(). Es una medida extra de seguridad más bien orientada a que esos

valores se van a devolver en la misma página en caso de error o cuando se finalice el envío correcto del

formulario. Puede ver más sobre esto en este mismo sitio en filtrar entradas con htmlspecialchars().

También controlamos las longitudes máximas de los campos contando los caracteres con

$longitud=strlen(utf8_decode($valor)). Sobre esto comentaré algo más en un apartado posterior.

También forzamos a que se nos envíe una dirección de email correcta. Aunque luego la vamos a insertar en

el cuerpo y por tanto no importaría que no lo fuera, es obvio que esperamos que ahí haya una dirección de

email y no otra cosa. El patrón usado es:

/̂\w+(?:[\-\.]?\w+)*@\w+(?:[\-\.]?\w+)*(?:\.[a-zA-Z]{2,4})+$/

Puede ver más sobre esto en el tema de validar formularios, en la sección de expresiones regulares. En este

punto he de decir que estoy presentando este ejemplo sin hacer uso del script para validar formularios

expuesto en esos temas. La razón es que quiero ir viendo los pormenores antes de pasar a usar ese

sistema de validación con el formulario de contacto ya en producción en este sitio.

Page 25: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Incorporando seguridad

http://www.wextensible.com/como-se-hace/formulario-contacto/email-seguro.html 9/12

Siguiendo con este script pasamos a validar el campo $mensaje. Por un lado controlamos el tamaño

máximo. Luego recortamos a líneas de 70 caracteres. En principio la especificación RFC5322 (la que

sustituye a la 2822) dice que una línea no puede tener más de 998 caracteres (1000 con el salto CRLF),

aunque en todo caso no debería tener más de 78 (80 con CRLF). Al mismo tiempo en la página del manual

de PHP de la función mail() pone un ejemplo haciendo un recorte de línea a 70 caracteres. El caso es que

es posible que los MUA que reciben y muestran el correo podrían gestionar líneas incluso más largas de los

1000 caracteres. Pero sinceramente no sé si podría afectar en algo, por lo que para empezar es mejor hacer

ese recorte.

La función wordwrap($mensaje, 70, PHP_EOL, true) recorta en líneas de 70 caracteres insertando un

salto de línea que viene definido por la constante PHP_EOL propia de PHP. Es tal que el salto de línea queda

definido según donde actúe PHP (p.e., CRLF en Windows y LF en Unix). El último argumento ordena que

corte la palabra aún el caso de que tenga más de 70 caracteres.

Si no hay ningún mensaje de error pasamos a enviar el mensaje. Las cabeceras se separan por un salto de

línea, usándose la constante PHP_EOL. Aparte de la cabecera From: [email protected] donde

ponemos la misma dirección que en $destino, hemos puesto también X-Mailer que nos servirá para

identificar (con ese o cualquier otro término) este correo como proveniente de nuestro formulario. Luego

vienen tres cabeceras para configurar la codificación de caracteres. Se trata de MIME-Version, Content-

type y Content-transfer-encoding de 8 bits para UTF-8. La cabecera MIME es necesaria para activar las

características de codificación distintas a ASCII. El tipo de contenido en este caso es text/plain pues no

deseamos que nos envíen código HTML que sería con text/html. El charset=UTF-8 coincide con el de la

página y por tanto el del formulario.

Por último señalar que con @mail() desactivamos que se muestre cualquer posible error. Aunque mientras

lo probamos en localhost lo recuperamos con error_get_last() para luego mostrarlo.

4. Codificación UTF-8 en el formulario

En cuanto al HTML que le sigue al script anterior es igual que el del ejemplo del tema anterior, pero sólo

cambia lo que aparece resaltado

Page 26: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Incorporando seguridad

http://www.wextensible.com/como-se-hace/formulario-contacto/email-seguro.html 10/12

...<body> <h3>Formulario de contacto</h3> <?php if(!$form_iniciado){ ?> <form action="mail-03.php" method="post" accept-charset="utf-8"> ... ...</body> </html>

Aquí cambiamos el GET por el POST. Aunque éste método no evita que alguién use algo como Telnet para

hacer peticiones, tiene la ventaja de que los campos no aparecen en la URL. Los buscadores a veces

indexan estas URL y no siempre conviene exponer datos privados de esta forma en índices públicos.

El atributo accept-charset fuerza al navegador a que use sólo la codificación dada UTF-8 para los datos

del formulario. Si este atributo no está presente el navegador usa la codificación de la página. Podría

pensarse que no es necesario hacer más nada si ya la página está en UTF-8. Pero también es posible que el

usuario cambie manualmente la codificación en el menu de herramientas del navegador. Por ejemplo, en

Chrome cambiando a una codificación no occidental como Árabe (Windows-1256), remitimos los siguientes

valores:

NOMBRE: andrésEMAIL: [email protected]:CañónBarça

Este mensaje se recibe en Outlook Express con las siguientes cabeceras relacionadas con la codificación,

realmente las que pusimos en el script PHP:

...MIME-Version: 1.0Content-type: text/plain; charset=UTF-8Content-transfer-encoding: 8BIT...

Page 27: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Incorporando seguridad

http://www.wextensible.com/como-se-hace/formulario-contacto/email-seguro.html 11/12

Pero lo que vemos en el correo del Outlook son caracteres que han desaparecido o están sustituidos por su

referencia Unicode en los campos de nombre y mensaje:

La razón es que el contenido del formulario se envía con la codificación seleccionada por el usuario Árabe

(Windows-1256), de tal forma que la codificación UTF-8 resulta mal formada. Para evitar esto ponemos ese

atributo accept-charset en el formulario. Así aunque el usuario cambie la codificación, esto no afecta a los

valores del mismo que siguen estando codificados en UTF-8.

5. La longitud de un texto en UTF-8

Al preparar el script anterior me he dado cuenta de que la medición de longitud de caracteres la estaba

haciendo erróneamente con la función de PHP strlen($cadena). Sin embargo para una cadena como

"cañón" que contiene la "ñ" y la "ó" que no pertenecen a ASCII códigos 1-127, UTF-8 las codifica con 2

bytes, con lo que la función strlen("cañón") nos da una longitud de 7 caracteres.

Hace ya tiempo que hice unas pruebas con algoritmos de transformación UTF-8 para

entender un poco todo eso. Veámos esto otra vez. La imagen de la izquierda

presenta cuatro caracteres UTF-8. Corresponden a los códigos UNICODE con

números decimales 65, 937, 35486 y 66436. Su representación de texto es AΩ語 . Quizás los dos últimos

caracteres no se presenten en su navegador y se verán como un recuadro o un signo de interrogación. Para

verlos es necesario ajustar su navegador para que los represente, pero en todo caso esos caracteres están

ahí. ¿Cuál es la longitud de esta cadena de texto?. La respuesta es obvia, tiene 4 caracteres. Pero si

Page 28: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Incorporando seguridad

http://www.wextensible.com/como-se-hace/formulario-contacto/email-seguro.html 12/12

usamos la función de PHP strlen($cadena) resulta que nos dará 10 caracteres. Realmente esa función

está contando bytes, pues son exactamente 10 los que contiene: 41,CE,A9,E8,AA,9E,F0,90,8E,84

(expresados en hexadecimal).

Para contar los caracteres de una cadena UTF-8 podemos decodificarla primero a ISO-8859-1, que son

caracteres codificados en 1 byte (8 bits), en el rango 0-255 (es el equivalente al ASCII extendido con 8

bits). Esa conversión la podemos hacer con la función de PHP utf8_decode() que tomará caracter a caracter

UTF-8 para intentar hacerlos corresponder con los 256 de ISO-8859-1. Luego le pasaríamos el strlen()

para contarlos. Si hacemos utf8_decode("AΩ語 ") obtenemos A???. Los signos de interrogación son los

caracteres UTF-8 que PHP no pudo traducir pues no están en la tabla 0-255. Pero a efectos de contar

caracteres no nos importa en que se traduzcan, pues haciendo strlen(utf8_decode("AΩ語 ")) obtenemos

la longitud de 4 caracteres que estamos buscando.

Page 29: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Uso de CAPTCHA

http://www.wextensible.com/como-se-hace/formulario-contacto/captcha.html 1/12

27

DIC

2011

Evitar el uso automático del formulario de contacto con Captcha

Inicio» Cómo se hace» I. El formulario de contacto c...» II. Incorporando seguridad al f...» III. Evitar el uso

automáti...» IV. Un Mail User Agent con PHP ...

1. Cómo funciona un CAPTCHA

2. Instalando GD2 de PHP

3. Un CAPTCHA con GD2 de PHP

4. Mejorando la usabilidad del CAPTCHA

5. Mejorando la robustez del CAPTCHA

6. Estructura de la página de prueba del CAPTCHA

1. Cómo funciona un CAPTCHA

CAPTCHA es el acrónimo de Completely Automated Public Turing test to tell Computers and Humans Apart,

traducido como prueba de Turing completamente pública y automática para diferenciar los ordenadores y

humanos. En las pruebas de formulario de contacto del tema anterior intentaba evitar que el usuario

insertara cabeceras no deseadas en el email. Con eso quería que no se utilizara el formulario para enviar

múltiples correos, a modo de SPAM. Pero aún así es posible usar un programa que haga un montón de

peticiones a nuestro sitio enviando formularios con datos y por tanto nos llenen el buzón de basura. Con la

técnica del CAPTCHA intentaré impedir que esto suceda.

wextensible

Page 30: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Uso de CAPTCHA

http://www.wextensible.com/como-se-hace/formulario-contacto/captcha.html 2/12

El sitio captcha.net ofrece servicio gratuito para incorporar este sistema en un formulario de nuestro sitio.

La imagen anterior es como se vería un control de este tipo. El usuario tendrá que introducir las dos

palabras que aparecen como una imagen y que verificará el servidor en www.captcha.net. En caso de que

le resulte ilegible puede actualizarlas dándole otro conjunto de palabras para intentar descifrarlas.

La base de esta técnica es muy simple. Se trata de que a un ordenador, es decir, a un programa de

ordenador le resulta bastante trabajaso extraer las palabras en forma de texto a partir de una imagen de

ese texto. Se trata en parte de usar la técnica de reconocimiento de caracteres, OCR Optical character

recognition. Aunque explotando aquellos aspectos que suponen más esfuerzo en OCR. Un uso de OCR es

para extraer el texto de un documento de papel escaneado, observándose que el reconocimiento de

caracteres puede verse alterado por cosas como:

Píxeles del fondo que no forman parte de los caracteres pueden alterarlos.

El documento contiene muchas fuentes de texto y al sistema le resulta díficil encontrar patrones para

algunas fuentes poco comunes.

Los caracteres pueden aparecer deformados o le faltan partes.

La distancia entre caracteres puede verse alterada produciendo errores.

Los caracteres que aparecen juntos o incluso solapados son díficiles de separar.

Actualmente el reconocimiento de caracteres por parte de un programa informático es un problema no

totalmente resuelto. Sin embargo a los humanos nos resulta más fácil alcanzar la solución a ese problema.

Pero a medida que la informática avanza se va reduciendo esa distancia. Prueba de ello es que los primeros

sistemas de CAPTCHA fueron rotos en su día y a medida que esos sistemas van incorporando aspectos más

complejos también se van buscando nuevas técnicas para romperlos. Esto es importante de resaltar porque

un CAPTCHA sólo tiene una utilidad: "Intentar saber que el usuario es un humano", pero las máquinas cada

vez se parecen más a los humanos.

Como dice la definición, un CAPTCHA debe ser automático y sobre todo debe ser público. Así el

hecho de romper un CAPTCHA debe basarse en técnicas de inteligencia artificial más que en el

conocimiento del algoritmo que genera el CAPTCHA. Pero no podemos perder de vista que un

CAPTCHA no es invulnerable. Esto quiere decir que de un conjunto de CAPTCHAs podría ser resuelto

alguna proporción de casos por una máquina, al menos en un tiempo comparable al que necesitaría un

Page 31: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Uso de CAPTCHA

http://www.wextensible.com/como-se-hace/formulario-contacto/captcha.html 3/12

humano. Será más robusto si esa proporción es baja. Pero al mismo tiempo también debe tener la

característica de usabilidad. Por ejemplo, la imagen de la izquierda contiene los caracteres aQ3mK y podría

ser un buen candidato para cumplir la cualidad de robustez, pero es dificultoso para que un humano lo

resuelva. Si cada vez más las máquinas son capaces de resolver los CAPTCHAs, habrá un momento en que

ya no podrán ser más robustos pues dejarán de ser usables. Entonces habrá que usar otra cosa como

CAPTCHAs basado en imágenes en lugar de texto.

2. Instalando GD2 de PHP

En el siguiente apartado presento como hice unas pruebas para hacer un sistema de CAPTCHA. Usaré la

extensión de PHP GD image. Como dice la introducción del manual de PHP, con esa extensión se pueden

crear y manipular archivos de imágenes en una variedad de formatos como GIF, PNG, JPEG entre otros.

Algunas funciones de esa extensión como imagechar() que nos permite dibujar un caracter en la imagen

nos servirán para crear el CAPTCHA de texto. Otras funciones como imagerotate() nos permitirán girar la

imagen. También podemos aplicar filtros con imagefilter() pudiendo hacer cosas como contrastar,

difuminar o superficializar la imagen. Con estos u otros filtros conseguiremos aplicar las deformaciones

necesarias a la imagen del texto de nuestro CAPTCHA.

Podemos hacer pruebas con esa extensión en localhost, pero si al final vamos a poner un CAPTCHA en

nuestro sitio en producción hemos de saber si tiene activada la extension GD (lo podemos saber viendo el

info.php). En las instalaciones de PHP en Windows (como es mi caso en localhost para aprender),

necesitamos que esta extensión haya sido instalada inicialmente. Si no es así podemos modificar la

instalación tal como comento en este grupo de capturas de pantalla:

Instalando GD2 de PHP

Page 32: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Uso de CAPTCHA

http://www.wextensible.com/como-se-hace/formulario-contacto/captcha.html 4/12

1/7

En agregar o quitar programas de Windows podemos ir a la aplicación de PHP para hacercambios

3. Un CAPTCHA con GD2 de PHP

Una forma de aprender cómo funciona un CAPTCHA es hacer un ejemplo. Tras probarlo intentaré ponerlo en

Page 33: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Uso de CAPTCHA

http://www.wextensible.com/como-se-hace/formulario-contacto/captcha.html 5/12

el formulario de contacto de este sitio. El ejemplo lo haré primero para ser ejecutado en localhost. Se trata

de una página PHP con un formulario que reenvía los datos al script en el mismo documento. En el último

apartado de este tema comentaré algunas cosas sobre el script. Por ahora me detengo en los detalles de

concepto. En la imagen de la izquierda aparece una captura de pantalla del formulario para hacer pruebas

generando CAPTCHAs. El botón "Ver texto" y la cadena adjunta nos ofrece el texto a verificar mientras

estamos haciendo pruebas, pero en una versión definitiva el texto a resolver no será enviado bajo ninguna

circustancia al navegador del usuario. Sólo la respuesta correcta nos llegará al servidor. Y aún así habría que

considerar la posibilidad de que pueda ser interceptada en el camino, pero esa es otra díficil cuestión que ni

me atrevo a abordar por ahora. Estos son unos ejemplos de imágenes de CAPTCHA que se obtienen:

La robustez se basa en generar imágenes que contengan el menor número posible de invariantes. Estas

son características que no varían entre una imagen y otra. Serían esas las que podrían tomarse como

patrones para resolver el problema. El tema es interesante pero muy complejo y sobrepasa mis

conocimientos. Pero hay un par de principios mínimos que tendré en cuenta al generar un texto CAPTCHA:

Generar cadenas con caracteres elegidos aleatoriamente.

Caracteres posicionados en el eje horizontal tratando de que se peguen entre ellos, buscando el

equilibrio adecuado para evitar un exceso de solapamiento que dificulte la usabilidad.

Posicionamiento aleatorio de cada caracter en el eje vertical. Con esto se busca que los caracteres

estén unidos entre sí no siempre por los mimos sitios.

La imagen final ha de tener el menor tamaño original posible, ajustándolo al tamaño de la fuente y

aplicando un escalado final suficiente para no perder usabilidad.

La imagen se rota un cierto ángulo positivo o negativo, elegido al azar entre un rango predeterminado

para deformar los caracteres.

La imagen se distorsiona aplicando filtros de difuminado, contraste o superficialización.

En estas dos imágenes se muestra una cadena sin aplicar ninguna deformación y como resulta con ellas:

Page 34: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Uso de CAPTCHA

http://www.wextensible.com/como-se-hace/formulario-contacto/captcha.html 6/12

Se puede usar un conjunto de caracteres a predeterminar. En este ejemplo uso los rangos 0..9, a..z, A..Z.

La cadena de texto se genera seleccionando aleatoriamente 6 caracteres, tamaño que se puede

predeterminar también, adaptándose el script automáticamente a ese tamaño. Elegir al azar los caracteres

tiene la desventaja de que pueden formarse combinaciones más débiles apareciendo caracteres aislados que

no se conectan con los adyacentes. Pero la posibilidad de construir un diccionario de palabras robustas no

me parece apropiado. Por un lado supone un mayor coste de recursos, pues habrá que cargar esa lista de

palabras en alguna estructura como un array. Además las palabras resueltas podrían ser reutilizadas de

alguna forma. Por lo tanto la solución pasa por generarlos aleatoriamente y corregir ciertas deficiencias en

el script, como acercando más algunos caracteres como i,l,1 que tienen un ancho efectivo de caracter

menor.

Se usa la fuente GD que viene por defecto con la extensión. Sería una mejora usar otras fuentes True Type

e incluso poner varias de forma aleatoria. Pero hay que saber donde están instaladas en el servidor en

producción. Y el script creo que debe basarse sólo en lo que genere dinámicamente, sin tener que tocar para

nada recursos de disco que en principio podemos desconocer. Con eso lo hacemos más rápido y más fiable

al no depender de recursos externos. La imagen final ni siquiera se guarda en archivo, como veremos más

abajo.

Se puede actuar sobre la resolución del trazo de los caracteres, usando un tamaño de fuente más pequeño y

luego escalando la imagen al presentarla. Cuanto más pequeña sea la imagen generada más robuto será el

CAPTCHA, pues al aplicar luego el escalado la imagen pierde resolución. El ajuste estará en el punto donde

no pierda usabilidad. La fuente GD sólo permite tamaños 1 a 5. En los ejemplos uso el tamaño máximo 5 y

un escalado (o zoom) de 2. Pero es cuestión de hacer pruebas. Por ejemplo, con fuente 2 y zoom 5 la

imagen obtenida tiene poca resolución y en muchos casos resulta ilegible:

Page 35: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Uso de CAPTCHA

http://www.wextensible.com/como-se-hace/formulario-contacto/captcha.html 7/12

4. Mejorando la usabilidad del CAPTCHA

Hay que tener en cuenta que el que uno vea bien los caracteres no significa que sea así para otras

personas. Cuando hacemos pruebas nos vamos acostumbrando a resolver los CAPTCHAs, por lo que será

conveniente hacer un muestreo y comprobar que personas ajenas al desarrollo web pueden resolverlos en

una proporción aceptable. El script acompaña una opción para guardar un número determinado de archivos

de imágenes. Extraje 25 CAPTCHAs y los inserte en esta página para comprobar la usabilidad. Le he pedido

a algunas personas para que me den las soluciones. Aunque no es una demostración muy rigurosa dado el

bajo número de encuestados, he podido extraer algunas conclusiones.

Usar un conjunto de letras mayúsculas y minúsculas ofrece mayor robustez pero dificulta la usabilidad. Por

ejemplo, a veces no es fácil diferenciar las letras "P" mayúscula y "p" minúscula. No conviene usar

minúsculas o mayúsculas solamente, pues hay muchas letras que son diferentes en ambos conjuntos lo que

supone una ventaja. Es mejor usar las dos formas y mejorar la usabilidad asimilando letras como las del

ejemplo anterior.

Por lo tanto la mejora de la usabilidad se refieren a caracteres que tienen un gran parecido. Son 0, o, O,

es decir, el cero y las "oes", el dígito "1" con la letra "l" minúscula, el dígito "5" con la letra "S"

mayúscula y algunas cuyas formas minúscula y mayúscula pueden ser confundidas. En estos casos prefiero

mantener esos caracteres en la lista de los posibles y admitir el intercambio entre ellos. Por ejemplo, si hay

un cero y alguién ve una O mayúscula, o al revés, se dará por bueno.

Hay otra mejora de usabildad relacionada con el posicionamiento. Vea estas imágenes:

Page 36: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Uso de CAPTCHA

http://www.wextensible.com/como-se-hace/formulario-contacto/captcha.html 8/12

Se generaron sin usar la opción de no cerrar las letras "C, c, G, q". En este caso cuando la letra C es

seguida de alguna otra como "H" o "E" la anterior no queda bien definida, pareciéndose a la letra "O" o un

cero. Con la opción de no cerrar esas letras obligamos a que la siguiente se desplace verticalmente.

Forzando el script para usar la misma cadena de la última imagen "aVsCEL", vemos que en todos los casos

desplaza la siguiente letra "E" verticalmente para que la "C" no pierda legibilidad:

5. Mejorando la robustez del CAPTCHA

El factor de acercamiento horizontal entre caracteres es clave para dar mayor robustez al CAPTCHA. Lo ideal

sería que se solapen, pero se perdería legibilidad. Para posicionar un caracter tomamos la última posición

horizontal del anterior, le sumamos un ancho de la fuente y luego reducimos algo para que queden lo más

pegados posible:

$x += $ancho_fuente * (1 - $pegarx * $pegarmas);

El factor $pegarx tiene un valor de 1/10. Así el siguiente caracter se separa un ancho de la fuente y se

reduce luego una décima parte. La última imagen del apartado anterior se generó con ese factor. Si lo

modificamos a 1/5 y forzamos la misma cadena "aVsCEL" (aunque variarán los otros parámetros como

posición vertical y deformación) obtenemos una de las imágenes así:

Page 37: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Uso de CAPTCHA

http://www.wextensible.com/como-se-hace/formulario-contacto/captcha.html 9/12

Los caracteres se pegan más entre sí, pero la "s" minúscula pierde legibilidad. El otro factor de

acercamiento es $pegarmas. Es el que ya mencioné para pegar aún más algunos caracteres más estrechos,

como "i,l,1". Tiene un valor de 2.5 que al multiplicarlo por el otro factor de 0.1 queda en 0.25, con lo que

acercamos más esos caracteres estrechos.

Hay un filtro para deformación llamado superficialización que consiste en aligerar el trazo de los caracteres.

Esto deforma aún más cada caracter. Con la misma cadena de antes y a con un factor de pegado de 1/10

tenemos este ejemplo:

Hay más cosas que podríamos hacer para mejorar la robustez. Como usar otras deformaciones como el

tachado u otros filtros. También podríamos utilizar ángulos aleatorios para situar cada caracter con fuentes

True Type. Esto lo podríamos hacer con la función de PHP imagettftext().

6. Estructura de la página de prueba del CAPTCHA

La página de prueba contiene un script PHP con un formulario que se remite al mismo script para validar el

CAPTCHA. No voy a exponer todo el código aquí, pues si lo desea puede ver el código completo. Presentaré

la parte del script PHP que va antes del HTML, aunque omitiendo algunas cosas para abreviar:

session_start(); //Declaramos variables para configurar el CAPTCHA...//Declaramos variables para recibir el formulario

Page 38: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Uso de CAPTCHA

http://www.wextensible.com/como-se-hace/formulario-contacto/captcha.html 10/12

$texto = "";$texto_verif = "";$nombre = "";$email = "";$mensaje = "";$verificado = false;$primera_sesion = true;if (isset($_SESSION["texto"]) && ($_SESSION["texto"]!="") && isset($_GET) && isset($_GET["envio"]) && ($_GET["envio"]=="Enviar")){ foreach($_GET as $campo=>$valor){ switch ($campo) { case "nombre": $nombre = $valor; break; case "email": $email = $valor; break; case "texto-verif": $texto_verif = preg_replace("/[ \s]+/", "", $valor); break; } } $texto1 = $texto_verif; $texto2 = $_SESSION["texto"]; ... if ($texto1 == $texto2) { //Si se verifica destruimos sesión $verificado = true; $_SESSION = array(); if (ini_get("session.use_cookies")) { $params = session_get_cookie_params(); setcookie(session_name(), '', time() - 42000, $params["path"], $params["domain"], $params["secure"], $params["httponly"] ); } session_destroy(); //Aquí va el proceso del formulario, enviar por email //por ejemplo o remitir a otra página } else { $texto_verif = ""; $texto = ""; }

Page 39: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Uso de CAPTCHA

http://www.wextensible.com/como-se-hace/formulario-contacto/captcha.html 11/12

}if (!$verificado){ //Aquí construimos el CAPTCHA ... //La cadena de texto generada se guarda en la variable de sesión if (!isset($_SESSION["texto"])) { $primera_sesion = true; } else { $primera_sesion = false; } $_SESSION["texto"] = $texto; ... //Creamos imagen GD y posicionamos los caracteres. Luego aplicamos filtros para deformar $imagen = imagecreate($ancho_imagen, $alto_imagen); ... //Luego abrimos un búfer para extraer la imagen codificada en base64 ob_start(); imagepng($imagen); imagedestroy($imagen); $buffer = ob_get_clean(); $imagen_data = base64_encode($buffer); }

Abrimos una sesión con session_start(). Establecemos los valores iniciales de las variables y si ya existe

una sesión, buscamos en el GET si hay algo recibido del formulario. El texto a verificar viene en el campo

texto-verif y le quitamos todos los espacios. Comprobamos que es igual que el texto que tenemos

almacenado en $_SESSION["texto"]. Si se verifica destruimos completamente esa sesión. En ese punto

podemos remitir el proceso a otro destino, por ejemplo ejecutar el envío por email o redireccionar a otra

página. En este ejemplo sacamos el resultado en la misma página controlando que $verificado = true.

Esa página de prueba no tiene ninguna medida de seguridad para formularios, pues su único propósito es probar la extensiónGD en localhost para generar CAPTCHA. Para una versión definitiva se debería tener en cuenta cosas como lo expuesto enformularios seguros.

Page 40: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Uso de CAPTCHA

http://www.wextensible.com/como-se-hace/formulario-contacto/captcha.html 12/12

Si !$verificado (será también la situación inicial) pasamos a construir el CAPTCHA. Generamos

aleatoriamente la cadena de texto y la guardamos en $_SESSION["texto"]. Luego creamos una imagen GD

con la función imagecreate() para posicionar en ella los caracteres y aplicar los filtros de deformación con

imagerotate() e imagefilter(). Finalmente tenemos la imagen en la variable $imagen, pero aún no

podemos usarla en el HTML que iría a continuación.

La función imagepng($imagen, $archivo) convierte esa imagen en un archivo con formato png. Pero si no

queremos usar recursos de disco, es mejor abrir un búfer y lanzar la imagen en él. Con ob_start() lo

abrimos y entonces con imagepng($imagen) lo que hacemos es enviarlo a ese búfer. Liberamos la memoria

destruyendo la variable y luego volcamos todo lo que hay en el búfer con $buffer=ob_get_clean().

Finalmente lo codificamos en base 64 con $imagen_data=base64_encode($buffer). Así tenemos la imagen

en forma de texto plano que luego insertaremos en el elemento <img> quedando su atributo como

src="data:image/png;base64,<?php echo $imagen_data;?>. La imagen no supera los 2KB, por lo que

codificarlas en base64 y enviarlas con la página no supone un coste excesivo. Y además no hay que tocar

recursos de disco en operaciones intermedias.

Page 41: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Un mailer con PHP

http://www.wextensible.com/como-se-hace/formulario-contacto/mail-user-agent.html 1/7

27

DIC

2011

Un Mail User Agent con PHP cuando mail() no está disponible

Inicio» Cómo se hace» I. El formulario de contacto c...» II. Incorporando seguridad al f...» III. Evitar el uso automático

de...» IV. Un Mail User Agent con ...

1. Hacer un mailer con PHP

2. Una clase en PHP para crear un Agente de Usuario SMTP

3. Conversación SMTP

1. Hacer un mailer con PHP

Debido a que la función mail() de PHP puede ser objeto de SPAM en los servidores

compartidos, a veces el administrador del servidor la desactiva. Recordemos que esa

función hace las veces de un MUA. Es el componente que se comunica con un servidor

SMTP entregándole el correo para que ese servidor lo gestione, bien transfiriéndolo a

otro servidor o, si es el servidor final, depositándolo en los buzones finales mediante el

protocolo POP3. Esta explicación está muy resumida, pues hay más cosas relacionadas

con el transporte de correo. Pero lo que nos interesa es que hacemos cuando mail() no

está disponible.

En ese caso tenemos que usar algún script que haga las veces de un MUA, es decir, que haga lo mismo que

mail(). El código más conocido es el de PHPMAILER originalmente en el sitio sourceforge.net. Aunque la

versión actual en este momento es la 5.2.0 y se encuentra en el nuevo sitio phpmailer.worxware.com.

Hace tiempo hice unas pruebas con la versión 5.1 y observé que se compone de dos módulos. Uno es

class.smtp.php que viene a ser el agente de transferencia, digamos el que se encarga de comunicarse con

el servidor de forma equivalente a como lo hace la función mail(). El otro módulo es class.phpmailer.php y

es la parte que se encarga de la edición del mensaje, preparando las cabeceras y conectando con el agente

wextensible

Page 42: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Un mailer con PHP

http://www.wextensible.com/como-se-hace/formulario-contacto/mail-user-agent.html 2/7

de transferencia que puede ser seleccionado entre los siguientes:

El propio módulo class.smtp.php

La función mail() de PHP

La aplicación Sendmail o QMail instaladas en servidores UNIX. Estas aplicaciones además de recibir el

correo de un MUA son capaces de transferirlo al servidor final de correo. Digamos que funcionan como

un MTA, que viene a ser una parte de las funciones que realiza un servidor completo de correo SMTP.

De hecho la función mail() de PHP usa Sendmail para transferir el correo bajo UNIX, mientras que en

Windows hemos de darle la dirección de un servidor SMTP para que haga la función MTA.

Si tenemos nuestro Apache+PHP en Windows no encontraremos Sendmail, por lo que hemos de usar mail()

o un módulo que haga esa función. En este tema no voy a explicar cómo se usa PHPMAILER pues en las

referencias anteriores puede encontrar mejores explicaciones. Lo que sí voy a hacer es construirme un

módulo muy simple que haga las funciones de esos dos módulos: preparar el mensaje y enviarlo a un

servidor SMTP.

2. Una clase en PHP para crear un Agente de Usuario SMTP

Este ejercicio trata de construir una clase denominada objetoSmtp con la que crear instancias para

funcionar a modo de MUA. Servirá para enviar email desde un formulario de contacto. Son ejemplos

exclusivamente para ejecutar en un localhost, pues si busca algo con propósito de producción es mejor usar

la función mail() o bien algo como PHPMAILER que indiqué en el apartado anterior. Este ejercicio, por su

sencillez, nos ayudará a entender como funciona un poco esto del protocolo SMTP. Hay dos archivos en

formato de texto, uno de ellos contiene el código de smtp.php que es el de la clase objetoStmp. El otro es

el código de index.php que contiene un documento PHP-HTML con un formulario de contacto que lo envía a

una dirección de email.

De forma abreviada, indicando las declaraciones de variables y métodos, el código de la clase objetoSmtp

es la siguiente:

class objetoSmtp { const SALTO = "\r\n";

Page 43: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Un mailer con PHP

http://www.wextensible.com/como-se-hace/formulario-contacto/mail-user-agent.html 3/7

public $dominio = ""; public $puerto = 25; public $timeout = 30; private $conectado = false; private $manejador = 0; private $conversacion = ""; //Constructor public function __construct(){} //Conectar con un servidor de correo public function conectar($dominio, $puerto, $timeout){}

//Enviar un correo public function enviar($de, $para, $asunto, $cuerpo){} //Ejecutar comandos SMTP private function comandar($comando, $parametro){}

//Leer líneas del socket private function extraer_linea(){} //Generar fecha para campo Date public static function Fecha(){} //Presentar la conversación con el servidor public function extraer_conversacion(){}}

La clase anterior contiene los métodos mínimos para hacer la función de enviar un email a un servidor de

correo. Aparte del constructor de la clase tenemos el método para conectar con el servidor, el que envía el

correo y otros métodos privados para el funcionamiento de la clase. Hay un método para presentar el detalle

de la "conversación" mantenida entre el servidor y el MUA, es decir, este objeto. Este clase se usaría desde

una página PHP que incluye un script PHP al inicio y luego un HTML con el formulario de contacto. El script

PHP es el siguiente:

<?phprequire "smtp.php";

Page 44: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Un mailer con PHP

http://www.wextensible.com/como-se-hace/formulario-contacto/mail-user-agent.html 4/7

$servidor_smtp = "";$de = "";$para = "";$asunto = "";$mensaje = "";$conversacion = "";$enviado = false;if (isset($_GET) && isset($_GET["envio"]) && ($_GET["envio"]=="Enviar")){ foreach($_GET as $campo=>$valor){ switch ($campo) { case "servidor-smtp": $servidor_smtp = $valor; break; case "de": $de = $valor; break; case "para": $para = $valor; break; case "asunto": $asunto = $valor; break; case "mensaje": $mensaje = $valor; break; } } $correo = new objetoSmtp; $enviado = false; if ($correo->conectar($servidor_smtp, 25, 30)) { $enviado = $correo->enviar($de, $para, $asunto, $mensaje); } $conversacion = $correo->extraer_conversacion(); }?>

El script revisa si hay GET y recoge los campos del formulario. Entre los típicos de un envío de email está el

del servidor SMTP. Para estas pruebas he usado Mercury instalado en modo local, tal como comenté en el

primer capítulo de estos temas. Vemos que creamos una nueva instancia del objeto con new objetoSmtp.

Luego hacemos la conexión con el método conectar() y si es válida enviamos el correo. Extraemos la

conversación para luego presentarla en el HTML que vemos parcialmente a continuación:

<!DOCTYPE html><html lang="es"><head> ...</head><body>

Page 45: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Un mailer con PHP

http://www.wextensible.com/como-se-hace/formulario-contacto/mail-user-agent.html 5/7

<h3>Pruebas SMTP</h3> <form action="index.php" method="get"> ... </form> <?php if ($enviado) { echo "<p style=\"color: blue\">Mensaje enviado<p>"; } else { echo "<p style=\"color: red\">Mensaje no enviado</p>"; }?> <pre style="border: maroon solid 1px;"><?php echo $conversacion; ?></pre></body></html>

Omito el formulario pues no tiene mayor interés (puede verlo en el código). El resto es simple, nos dirá si

fue o no enviado y muestra la conversación. Una captura de una prueba la vemos aquí:

El servidor, como dije antes, es el Mercury que monté en local y que llamé smtp.localemail. Los usuarios

admin y user1 tienen buzones en ese servidor. El mensaje se envía y nos devuelve otra vez ese formulario

con un texto incluyendo la conversación producida entre el servidor y el MUA (esto lo veremos con más

detalle después). El servidor se conecta con POP3 a un agente de correo, en este caso Outlook Express,

recibiéndose ese correo y cuyas cabeceras podemos ver aquí:

Page 46: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Un mailer con PHP

http://www.wextensible.com/como-se-hace/formulario-contacto/mail-user-agent.html 6/7

3. Conversación SMTP

El protocolo SMTP y lo necesario para hacer un Agente de Usuario de email se basa en las siguientes

especificaciones :

RFC5321 Simple Mail Transfer Protocol Protocolo simple de transferencia de correo. Actualiza la versión

anterior RFC2821, que a su vez actualizó la RFC821

RFC5322 Internet Message Format Formato de mensajes de Internet. Actualiza las versiones RFC2822

y ésta a su vez a RFC822.

Decimos que es una "conversación" entre el servidor SMTP y el agente de usuario, que es en este caso

nuestro script objetoSmtp. Lo que sucedió en la conexión del ejemplo anterior se ve aquí en su totalidad.

En marrón están los comandos que envío el objetoStmp y en verde las respuestas del servidor:

Nuevo objetoSmtp construido el Thu, 15 Dec 2011 13:28:03 +0000Conectando a smtp.localemail......220 smtp.localemail ESMTP server ready.EHLO smtp.localemail250-smtp.localemail Hello smtp.localemail; ESMTPs are:250-TIME250-SIZE 0

Page 47: Cómo Se Hace Un Formulario de Contacto_ Un Mailer Con PHP

11/7/2014 Cómo se hace un formulario de contacto: Un mailer con PHP

http://www.wextensible.com/como-se-hace/formulario-contacto/mail-user-agent.html 7/7

250-8BITMIME250 HELPMAIL FROM:<[email protected]>250 Sender OK - send RCPTs.RCPT TO:<[email protected]>250 Recipient OK - send RCPT or DATA.DATA354 OK, send data, end with CRLF.CRLFFrom: [email protected]: [email protected]: Probando SMTPSubject: pruebaDate: Thu, 15 Dec 2011 13:28:03 +0000

Mensaje.250 Data received OK.QUIT221 smtp.localemail Service closing channel.

Con el método conectar($dominio, $puerto, $timeout) intentamos conectar con el servidor de correo

en smtp.localemail. En el código de objetoSmtp esto se hace con la función fsockopen() que abre un

socket para comunicarnos con un servidor. Éste nos responde con un código 220 smtp.localemail ESMTP

server ready. Viene a decir que nos reconoce la conexión y está listo. El servidor responde con códigos

numéricos especificados en el protocolo, mientras que nosotros (el MUA) le envíamos peticiones mediante

comandos. Son palabras claves especificadas también en el protocolo. Así EHLO es un comando de saludo

inicial, algo así como que el MUA requiere al servidor para iniciar una petición. El servidor le responde con el

código 250 que dice que entiende la petición. El MUA va enviando las cabeceras del sobre como MAIL FROM

o RCPT TO y el servidor las recoge devolviendo un estado correcto. Tras el último RCPT TO (en este caso

sólo uno) se envían los datos del mensaje, pero antes se lo hacemos saber al servidor enviando el comando

DATA. El código 354 le dice al MUA que el servidor está listo para recibir el mensaje y que debe finalizarlo

con la secuencia CRLF.CRLF. El mensaje se compone de las cabeceras From, To, X-Mailer, Subject,

Date tras lo cual viene un salto de línea y el texto del mensaje. Cuando el servidor recibe CRLF.CRLF

devuelve un código OK 250 y luego el MUA cierra la conexión con el comando QUIT.