Cliente SMTP em C

Aplique o que aprendeu sobre sockets para enviar e-mail a partir do seu próprio programa.

O que é SMTP?

SMTP (Simple Mail Transfer Protocol) é o protocolo usado para enviar e-mail. Roda na porta 25 (ou 587 para submissão).
Seu programa
(cliente SMTP)
SMTP

porta 25
Servidor de e-mail
(servidor SMTP)
Caixa postal
do destinatário
Comando SMTPSignificadoExemplo
HELOSaudação (anuncia nosso hostname)HELO myhost
MAIL FROM:Endereço do remetenteMAIL FROM:<sender@example.com>
RCPT TO:Endereço do destinatárioRCPT TO:<receiver@example.com>
DATAInicia o corpo da mensagemDATA
.Encerra o corpo da mensagem. (uma linha só com um ponto)
QUITEncerra a sessãoQUIT

Fluxo da Sessão SMTP

Cliente e servidor literalmente "conversam" em texto. Cada resposta do servidor começa com um código de status de três dígitos.
S: 220 mail.example.com SMTP Ready ← saudação do servidor
C: HELO mycomputer ← saudação do cliente
S: 250 Hello mycomputer
C: MAIL FROM:<alice@example.com> ← remetente
S: 250 OK
C: RCPT TO:<bob@example.com> ← destinatário
S: 250 OK
C: DATA ← início do corpo
S: 354 Start mail input
C: Subject: Test Mail
C:
C: Hello, this is a test.
C: . ← linha só com ponto encerra o corpo
S: 250 OK
C: QUIT ← desconecta
S: 221 Bye
2xx = sucesso
3xx = aguardando mais entrada
4xx = erro temporário
5xx = erro permanente

Implementando em C

Baseando-nos na aula anterior de sockets, vamos construir um cliente SMTP.

Funções Auxiliares

Primeiro, um helper que facilita enviar um comando e ler a resposta.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/socket.h>

#define BUF_SIZE 4096

// envia um comando e imprime a resposta do servidor
void smtp_command(int sock, const char *cmd) {
    char buf[BUF_SIZE];
    if (cmd != NULL) {
        send(sock, cmd, strlen(cmd), 0);
        printf("C: %s", cmd);
    }
    int n = recv(sock, buf, BUF_SIZE - 1, 0);
    if (n > 0) {
        buf[n] = '\0';
        printf("S: %s", buf);
    }
}

// conecta a um servidor SMTP
int connect_smtp(const char *host, int port) {
    struct hostent *h = gethostbyname(host);
    if (!h) { fprintf(stderr, "Falha na resolução do host\n"); return -1; }

    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) { perror("socket"); return -1; }

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    memcpy(&addr.sin_addr.s_addr, h->h_addr, h->h_length);

    if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        perror("connect"); close(sock); return -1;
    }
    return sock;
}

Lógica Principal

int main(void) {
    const char *smtp_host = "mail.example.com";
    const char *from = "alice@example.com";
    const char *to   = "bob@example.com";

    // 1. conecta
    int sock = connect_smtp(smtp_host, 25);
    if (sock < 0) return 1;

    // 2. lê saudação
    smtp_command(sock, NULL);

    // 3. sessão SMTP
    smtp_command(sock, "HELO mycomputer\r\n");

    char cmd[BUF_SIZE];

    snprintf(cmd, BUF_SIZE, "MAIL FROM:<%s>\r\n", from);
    smtp_command(sock, cmd);

    snprintf(cmd, BUF_SIZE, "RCPT TO:<%s>\r\n", to);
    smtp_command(sock, cmd);

    smtp_command(sock, "DATA\r\n");

    // 4. headers e corpo
    snprintf(cmd, BUF_SIZE,
        "Subject: Teste de programa em C\r\n"
        "From: %s\r\n"
        "To: %s\r\n"
        "\r\n"                            // linha em branco encerra headers
        "Olá!\r\n"
        "Este e-mail foi enviado de um programa em C.\r\n"
        ".\r\n",                          // linha só com ponto encerra o corpo
        from, to);
    smtp_command(sock, cmd);

    // 5. encerra sessão
    smtp_command(sock, "QUIT\r\n");

    close(sock);
    printf("\nE-mail enviado.\n");
    return 0;
}
Nota: a maioria dos provedores hoje bloqueia conexões diretas à porta 25 (uma política conhecida como OP25B). Você pode precisar de um servidor de laboratório/teste, ou da porta 587 com autenticação.

Extensões de Funcionalidades

Extensão 1: Ler o corpo do teclado

Use fgets para deixar o usuário digitar o corpo interativamente.
// lê o corpo do teclado
printf("Digite o corpo do e-mail (linha em branco para terminar):\n");
char body[BUF_SIZE] = "";
char line[256];
while (1) {
    fgets(line, 256, stdin);
    if (line[0] == '\n') break;  // linha em branco termina
    strcat(body, line);
}

Extensão 2: Agenda de contatos

Escolha o destinatário pelo número.
// agenda de contatos
const char *addressbook[] = {
    "user1@example.com",
    "user2@example.com",
    "user3@example.com"
};
int num_addr = 3;

printf("Selecione o destinatário:\n");
for (int i = 0; i < num_addr; i++)
    printf("  %d: %s\n", i + 1, addressbook[i]);

int choice;
scanf("%d", &choice);
const char *to = addressbook[choice - 1];

Extensão 3: Carregar o corpo de um arquivo

// lê o corpo de um arquivo de texto
FILE *fp = fopen("message.txt", "r");
if (fp == NULL) { perror("fopen"); return 1; }

char body[BUF_SIZE] = "";
char line[256];
while (fgets(line, 256, fp) != NULL)
    strcat(body, line);
fclose(fp);

Desafios

Desafio 1: Tratamento de erros para send/recv
Melhore o smtp_command para checar os códigos de status do servidor (250, 354, etc.). Se a resposta for inesperada, imprima um erro e aborte.
Desafio 2: Enviar para múltiplos destinatários
Emitir RCPT TO várias vezes entrega uma mensagem para vários endereços. Permita seleção múltipla da agenda de contatos.
Desafio 3: Subject por entrada de teclado
Deixe o usuário definir o header Subject. Leia com fgets e insira no payload do DATA.
Desafio 4: Registrar a sessão em arquivo
Adicione um recurso que grava toda a troca SMTP num arquivo de texto — útil como evidência em relatórios de laboratório.
Avançado: E-mail Criptografado
Escreva um programa que criptografa o corpo antes de enviar.
- Simples: Cifra de César (deslocar cada caractere por N).
- Intermediário: Cifra XOR (XOR com uma string-chave).
- Avançado: implemente o básico de chave pública (exponenciação modular a^n mod p).
Escreva também um programa de descriptografia correspondente no lado receptor.
O que esta série cobriu
Fundamentos de sockets TCP/IP → o protocolo SMTP → implementação de um remetente de e-mail → extensões de funcionalidades.
← Voltar para Programação com Sockets

Quiz de Revisão

Teste seu entendimento desta aula!

Q1. Qual o primeiro comando que um cliente SMTP envia após conectar?

HELO ou EHLO
MAIL FROM
RCPT TO

HELO/EHLO anuncia seu hostname. EHLO é a forma estendida e também retorna uma lista de recursos suportados.

Q2. Qual a porta padrão para SMTP simples (sem TLS)?

25
80
443

A porta 25 é SMTP padrão. Submissão usa 587, e SMTPS tipicamente usa 465.

Q3. Qual sequência marca o fim do corpo do e-mail em SMTP?

Uma linha contendo só um ponto (\r\n.\r\n)
A string literal "END"
Um byte nulo \0

Depois do DATA, uma linha contendo apenas um ponto sinaliza o fim do corpo da mensagem.

Compartilhe este artigo
Compartilhar no X