Enviando Emails Autenticados com o Protocolo SMTP
por Carlos Sica

Várias pessoas perguntam sobre como enviar um email quando o servidor exige autenticação de login e senha. A maioria dos servidores de email utilizam o SMTP. Com este protocolo, o usuário pode enviar emails com ou sem autenticação. Este artigo explica os dois modos.



Índice
1 - Conceitos Iniciais
2 - Verbos SMTP
3 - Código Fonte do Script
4 - Informações Complementares e Conclusão


O que é SMTP?
Em principio, é um servidor para enviar email, mas como o nome SMTP, que vem do inglês "Simple Mail Transfer Protocol" diz, é um Protocolo Simples de Transferência de Mensagens, eletrônicas, naturalmente.

O que é protocolo?
Protocolo é um conjunto de regras utilizadas para se estabelecer uma comunicação entre dois sistemas, neste caso, entre o cliente com o servidor.

Os protocolos trabalham através de requerimentos (requisições ou perguntas) e respostas, ou seja, o servidor emite uma resposta para cada requerimento. Em geral, os requerimentos são compostos por uma linha texto, contendo um comando que pode exigir parâmetros ou não. Já a resposta pode conter uma ou mais linhas texto, mas todas elas contém um código que pode ser de acerto ou de erro.

Como é o protocolo SMTP?
Em primeiro lugar é necessário estabelecer um canal de comunicação com o servidor. Muitas linguagens de programação oferecem alguma função que permite o estabelecimento de comunicação entre o computador cliente e o servidor. Em PHP esta função e a fsockopen() que espera como principal parâmetro, o nome do servidor para criar um socket entre o cliente e o servidor.

O que é socket?
Pense numa tomada de energia que liga o aparelho de som na rede elétrica, é essa a idéia. O computador cliente vai se conectar ao computador servidor através de uma ligação lógica para comunicação bidirecional. Esta ligação é chamada socket.

Como são os requerimentos e as respostas nesse protocolo?
Um requerimento é uma string de bytes que contém um comando, chamado na literatura técnica de verbo, que pode receber parâmetros ou não. Ao final de cada comando (e parâmetros) deve vir uma quebra de linha, pois, o próximo comando deve iniciar em uma nova linha. Em PHP a quebra de linha é comandada pelas constantes \r\n.

Forma geral de um comando para o servidor SMTP.


VERBO parâmetro\r\n


As respostas são compostas por um número de três dígitos no formato de uma string ASCII, seguido por uma mensagem explicativa. Este dois componentes podem estar separados por espaço, traço ou outro separador.


999 resposta exemplo
999-resposta exemplo



Existem vários verbos ou comandos SMTP, mas vamos explicar somente os comandos necessários para estabelecer uma comunicação de envio de email e as respostas que cada um fornece. Lembre-se que este verbos serão recebidos pelo servidor através do socket, que existe apenas após o estabelecimento de uma conexão.

MAIL
Este verbo requer que o servidor estabeleça um endereço de retorno no envelope, que será utilizado, por exemplo, para enviar mensagens de erro.


MAIL From: sica@exemplo.com.br\r\n


mensagem de aceitação:

250 ok


mensagens de erro:

500 line too long
555 garbage not permitted
501 malformatted address
451 timeout


RCPT
Este verbo solicita ao servidor que escreva um ou mais endereços de destino no envelope.


RCPT To: carlos@exemplo.com.br\r\n


mensagem de aceitação:

250 ok


mensagens de erro:

450 mailbox busy, try again later
452 recipient out of disk space
452 too many recipients
503 need MAIL before RCPT
550 no such recipient here
551 recipient has moved
553 we don't relay mail to remote addresses


DATA
Este verbo estabelece o inicio da composição do email a ser enviado (não requer parâmetros).


DATA\r\n


As próximas linhas enviadas ao servidor devem ser escritas num formato específico, contendo os vários campos necessários para construir um email (veja abaixo). Um ponto estabelece o final do texto.


MIME-Version: 1.0\r\n
Content-Type: text/html; charset=iso-8859-1\r\n
From: Carlos Sica <sica@exemplo.com.br>\r\n
To: Carlos Sica <sica@exemplo.com.br>\r\n
Subject: teste de email\r\n
\r\n
Aqui vem o corpo do email que é um texto composto por linhas de 80 caracteres.\r\n
.\r\n


mensagem de aceitação:

354 go ahead


mensagens de erro:

451 unable to set up internal message buffer
503 need RCPT before DATA
552 sorry, this message is too large
554 too many hops, this message is looping


QUIT
Solicita o encerramento da conexão que esta aberta (não requer parâmetros).


QUIT\r\n


mensagem de aceitação:

221 mail.exemplo.com.br


A validação do login e senha
Os verbos estudados acima são os necessários para compor e enviar um email através do servidor SMTP. Porém, quando o servidor está configurado para enviar apenas emails de usuários cadastrados, é necessário validar o login e a senha desses usuário. Para tanto deve-se utilizar o verbo AUTH. Após esta requisição ser aceita, deve-se enviar o login e senha encriptadas.

AUTH
Solicita autenticação


AUTH LOGIN\r\n


mensagem de aceitação:

334 VXNlcm5hbWU6



Em primeiro vamos definir as informações que serão utilizadas na conexão com o servidor SMTP, que são: o nome dele, a porta de comunicação. A porta 25 é padrão para todos os servidores SMTP. Além dessas informações, a função fsockopen() solicita como parâmetro opcional, uma constante como tempo de espera para uma segunda tentativa, caso haja impossibilidade de estabelecer a conexão de imediato.


<?php
$servidor
"smtp.seu_servidor.com.br";
$porta   25;
$timeout 10;
?>


Como planejamos enviar emails com validação, agora vem as informações para validação do usuário no servidor descrito acima.


<?php
$login   
"seu_login@seu_servidor.com.br";  // em alguns servidores basta o seu_login
$senha   "sua_senha";                      // senha no formato texto
?>


É útil também projetar o email antecipadamente. Neste exemplo vamos definir variáveis diretamente, mas é possível receber cada conteúdo dos campos de um form.


<?php
$de      
"origem@seu_servidor.com.br";
$deNome  "Origem";
$para    "destino@seu_servidor.com.br";
$paraNome"Destino";
$corpo   "Mensagem com autenticação OK<br><i>bysica©</i>";
$assunto "assunto";
?>


Curiosamente a data não é gerada automaticamente, então é necessário utilizar a função date() para criar uma string no formato RFC2822 que tem o seguinte estilo: "Wed, 25 May 2005 13:24:07 -0300", onde o último número corresponde a zona, referente aos fusos horários, onde foi gerada a string.


<?php
$data    
'Date: '.date('r',time());        // este formato é exigência do servidor
?>


Começando o script em si abre vem a conexão com o servidor SMTP. A função fsockopen como já discutido é a função escolhida. O símbolo @ no início indica que é para ocultar as mensagens de erro automáticas, porque vamos manipular os erros através das variáveis $errno e $errstr.


<?php
$conexao 
= @fsockopen($servidor$porta$errno$errstr$timeout);
?>


Se ocorrer algum erro, a variável booleana $errno informa 1 (verdadeiro), senão informa 0 (false). Portanto, se ocorrer erro, o script é interrompido pela função exit.


<?php
if ($errno){
    echo 
"Erro ao conectar ao $servidor na $porta: ".$errstr;
    exit;
}
?>


Se não há erro continua o script aqui.
É importante explicar um pouco mais sobre a função fsockopen(). Sempre que ela abre uma conexão, um socket é criado. Bem, abrir uma conexão é muito semelhante a criar um arquivo, assim podemos escrever nela e ler dela, o que significa enviar comandos (requisição) para o socket e receber os resultados através deste pseudo arquivo. Assim os comandos utilizados são os mesmos de ler e escrever em arquivos: fputs() e fgets().

Caso você esteja testando o que o servidor devolve como resposta, é possível imprimir esses resultados lendo o socket como no exemplo abaixo. Esta linha pode ser colocado no script após cada envio de comando ou dado.


<?php
echo "conexão: ".fgets($conexao1024)."<br>";
?>


Neste ponto, é preciso conhecer bem o servidor que está utilizando. Cada um pode responder de forma diferente a uma conexão. Alguns exigem uma mensagem de handshaking precedendo os outros comandos.
Se for necessário, utilize o verbo HELO ou o EHLO, descritos na última página deste artigo. Além disso, a resposta de cada servidor para essas indagações pode ser distinta.

A seguir um trecho elaborado a partir da sugestão feita pelo usuário Flávio Vargas. Este trecho generaliza a espera de um número distinto de respostas ao HELO ou EHLO enviadas pelo servidor.

Atenção existem servidores que não estabelecem handshaking, portanto a resposta a esta consulta será vazia.


<?php
fputs
($conexao,"EHLO $servidor\r\n"512); 
do{
   
$buffer fgets($conexao512);
   echo 
"loop: ".$buffer."<br>"
}while((
substr($buffer, -8) != '8BITMIME') && ($buffer != ""));
?>


Continuando, vamos agora solicitar ao servidor SMTP a autenticação, lógico, se quiser enviar emails sem autenticar é só tirar esta parte.


<?php
fputs
($conexao,"AUTH LOGIN\r\n"512)."<br>";
?>


Em seguida vamos enviar o login e a senha no formato que o servidor espera receber: codificado. A função base64_encode() codifica uma string para o formato esperado.


<?php
fputs
($conexao,base64_encode($login)."\r\n"512)."<br>";
fputs($conexao,base64_encode($senha)."\r\n"512)."<br>";
?>


Quase pronto, agora precisamos estabelecer um email de retorno, por exemplo para, receber msgs de erro, tal como, destinatário não encontrado e o endereço do destinatário.


<?php
fputs
($conexao"MAIL FROM:<$de>\r\n"512);
fputs($conexao"RCPT TO:<$para>\r\n"512);
?>


Finalmente vamos compor o email a ser enviado, aqui vale a pena você estudar um pouquinho sobre o cabeçalho dos emails. Vou dar um exemplo típico que pode ser modificado segundo suas necessidades.


<?php
fputs
($conexao"DATA\r\n"512);
?>


Lembre-se que o comando DATA avisa ao servidor que dali para frente tudo que vier são dados do email.


<?php
fputs
($conexao"MIME-Version: 1.0\r\n");
fputs($conexao"Content-Type: text/html; charset=iso-8859-1\r\n");
fputs($conexao"Date: $data\r\n");
fputs($conexao"From: $deNome <$de>\r\n");
fputs($conexao"To: $paraNome <$para>\r\n");
fputs($conexao"Subject: $assunto\r\n");
fputs($conexao"\r\n");
fputs($conexao"$corpo\r\n");
fputs($conexao".\r\n");
?>


Agora sim o email foi enviado. Repare em dois detalhes importantes para que esta tarefa fosse realmente realizada com sucesso: 1) antes do corpo do email é necessário enviar uma linha em branco e 2) após ele, encerrar com uma linha contendo apenas um ponto. Claro o \r\n é necessário para finalizar todas as linhas.

Se desejar pode inserir a string Cc: que significa ‘Com Cópia’, em inglês, ‘Carbon Copy’ e ainda a string Bcc: que gera uma cópia oculta, do inglês, ‘Blind Carbon Copy’.

Agora basta apenas encerrar a conexão com o comando QUIT.


<?php
fputs
($conexao"QUIT\r\n"512);
echo 
"quit: ".fgets($conexao512)."<br>";
fclose($conexao); 
?>


Vamos ver como fica o script completo


<?php
//*************************************************************************************//
//   Script para enviar email autenticado via SMTP (Simple Mail Transfer Protocol)     //
//   Autor: Prof. Carlos Sica <www.din.uem.br/sica> (Maringá - PR - Brasil)            //
//   Data:  21/05/2005                                                                 //
//                                                                    Adoro programar! //
//*************************************************************************************//

// primeiro vamos definir as informações para o servidor SMTP
$servidor"smtp.seu_servidor.com.br";      // nome do servidor
$porta   25;                              // número da porta, o email sempre fica na 25
$timeout 10;                              // tempo para expirar a tentativa de conexão
// agora vem as informações para validação do usuário no servidor descrito acima
$login   "seu_login@seu_servidor.com.br";  // em alguns servidores basta o seu_login
$senha   "sua_senha";                      // senha no formato texto

// informações para o email
$de      "origem@seu_servidor.com.br";
$deNome  "Origem";
$para    "destino@seu_servidor.com.br";
$paraNome"Destino";
$corpo   "Mensagem com autenticação OK<br>bysica";
$assunto "assunto";
$data    'Date: '.date('r',time());        // este formato é exigência do servidor

// abre conexão com o servidor SMTP utilizando a função fsockopen
// o @ no início é para ocultar as mensagens de erro automáticas
// porque vamos manipular os erros através das variáveis $errno e $errstr
$conexao = @fsockopen($servidor$porta$errno$errstr$timeout);
// se houve erro a variável $errno vem ligada
if ($errno){
    echo 
"Erro ao conectar ao $servidor na $porta: ".$errstr;
    exit;
}
// se não há erro continua o script
echo "conectou com: ".$conexao."<br>";
echo 
"conexão: ".fgets($conexao1024)."<br>";

// handshaking com o servidor
fputs($conexao,"EHLO $servidor\r\n"512); 
do{
   
$buffer fgets($conexao512);
   echo 
"loop: ".$buffer."<br>"
}while((
substr($buffer, -8) != '8BITMIME') && ($buffer != ""));

// solicita autenticação (se quiser enviar email sem autenticar é só tirar esta parte)
fputs($conexao,"AUTH LOGIN\r\n"512)."<br>";             // envia a requisição
echo "auth login: ".fgets($conexao512)."<br>";          // recebe a resposta e imprime
fputs($conexao,base64_encode($login)."\r\n"512)."<br>"// envia a requisição
echo "login: ".fgets($conexao512)."<br>";               // recebe a resposta e imprime
fputs($conexao,base64_encode($senha)."\r\n"512)."<br>"// envia a requisição
echo "senha: ".fgets($conexao512)."<br>";               // recebe a resposta e imprime

// estabelece um email de retorno para receber msgs de erro
fputs($conexao"MAIL FROM:<$de>\r\n"512);
echo 
"mail from: ".fgets($conexao512)."<br>";

// estabelece o endereço de envio
fputs($conexao"RCPT TO:<$para>\r\n"512);
echo 
"recpt to: ".fgets($conexao512)."<br>";

// comanda o inicio do email a ser enviado
fputs($conexao"DATA\r\n"512);
echo 
"data: ".fgets($conexao512)."<br>";
// infomações do email a ser enviado
fputs($conexao"MIME-Version: 1.0\r\n");
fputs($conexao"Content-Type: text/html; charset=iso-8859-1\r\n");
fputs($conexao"Date: $data\r\n");
fputs($conexao"From: $deNome <$de>\r\n");
fputs($conexao"To: $paraNome <$para>\r\n");
fputs($conexao"Subject: $assunto\r\n");
fputs($conexao"\r\n");
fputs($conexao"$corpo\r\n.\r\n");
echo 
"email: ".fgets($conexao512)."<br>";

// encerra a conexão com o servidor
fputs($conexao"QUIT\r\n"512);
echo 
"quit: ".fgets($conexao512)."<br>";
fclose($conexao); 
exit;
?>



O resultado final do teste feito com este script é o seguinte:

conectou com: Resource id #2
conexão: 220 mail.xxx.yyy.br ESMTP
auth login: 334 VXNlcm5hbWU6
login: 334 UGFzc3dvcmQ6
senha: 235 ok, go ahead (#2.0.0)
mail from: 250 ok
recpt to: 250 ok
data: 354 go ahead
email: 250 ok 1128544837 qp 13633
quit: 221 mail.xxx.yyy.br


Informações adicionais
Caso você deseje testar o servidor antes de enviar uma email, pode utilizar os seguintes verbos?

HELO
'Fala oi' para o servidor SMTP.


HELO smtp.exemplo.com.br\r\n


mensagem de aceitação:

220 mail.exemplo.com.br ESMTP


RSET
Limpa o envelope presente no servidor (não requer parâmetros).


RSET\r\n


mensagem de aceitação:

250 flushed


Alguns servidores reconhecem também um HELO extendido.

EHLO
Semelhante ao HELO, porém, responde um texto contendo informações das aptidões do servidor. Este texto é compreensível pelo computador.


EHLO\r\n


mensagens de aceitação

250-mail.exemplo.com.br
250-AUTH LOGIN CRAM-MD5 PLAIN
250-AUTH=LOGIN CRAM-MD5 PLAIN
250-STARTTLS
250-PIPELINING
250 8BITMIME


Conclusão
Conhecendo alguns detalhes, todos podem construir seu próprio carteiro, não foi muito difícil, mas é importante alguns conceitos de rede para entender perfeitamente o socket, portanto, aconselho uma boa leitura no assunto. Claro que o tópico 'arquivos' já foi tema no phpbrasil, mas em breve pretendo colocar um artigo bem didático sobre o assunto, inclusive debatendo sobre sua aplicação nas redes, que acham?

Espero ter ajudado.