Networking and security
SMTP: how email travels, and how to run it securely.
What the protocol is, the conversation between servers, the ports (25, 587, 465), TLS and authentication, SPF, DKIM and DMARC, open-source servers to practise with, and a Docker lab with real output.
O que é o SMTPWhat SMTP is
SMTP (Simple Mail Transfer Protocol) é o protocolo que empurra uma mensagem de um servidor para outro até ela chegar ao servidor de quem recebe. Ele só entrega; quem lê a caixa de entrada depois usa outro protocolo (IMAP ou POP3). Pense no correio: o SMTP é o caminhão que leva a carta de agência em agência; o IMAP é você abrindo a caixa postal.SMTP (Simple Mail Transfer Protocol) is the protocol that pushes a message from one server to another until it reaches the recipient's server. It only delivers; reading the inbox later uses another protocol (IMAP or POP3). Think of the postal service: SMTP is the truck carrying the letter from office to office; IMAP is you opening your mailbox.
É um protocolo de texto, nascido em 1982 e hoje definido na RFC 5321. Foi desenhado quando todo mundo se conhecia, então não tinha autenticação nem criptografia. Quase tudo o que é "segurança de e-mail" é uma camada colocada por cima depois (seção 3).It is a text protocol, born in 1982 and now defined in RFC 5321. It was designed when everyone knew each other, so it had no authentication and no encryption. Almost everything called "email security" is a layer added on top later (section 3).
O passo "quem recebe example.org?" é uma consulta DNS ao registro MX:The "who receives example.org?" step is a DNS query for the MX record:
$ dig +short MX gmail.com
10 alt1.gmail-smtp-in.l.google.com.
5 gmail-smtp-in.l.google.com.
# o número menor tem prioridade; se falhar, o MTA tenta o próximo e depois tenta de novo mais tarde
dig você mesmo. O resto do texto vem das RFCs e docs citadas ao fim de cada seção.Personal note, not verified in the sources: the MX hosts in the example above are from memory and change; run dig yourself. The rest of the text comes from the RFCs and docs cited at the end of each section.1. A conversa, comando a comando1. The conversation, command by command
Cliente e servidor trocam linhas de texto. O servidor responde com um código de três dígitos: 2xx deu certo, 3xx continue, 4xx erro temporário (tente de novo depois), 5xx erro permanente (desista). Esta é uma sessão real contra o Mailpit (seção 5); as linhas numeradas 2xx, 354 e 221 são do servidor, o resto eu digitei:Client and server exchange lines of text. The server answers with a three-digit code: 2xx worked, 3xx keep going, 4xx temporary error (try again later), 5xx permanent error (give up). This is a real session against Mailpit (section 5); the numbered lines (2xx, 354, 221) are from the server, the rest I typed:
S: 220 633819653d9a Mailpit ESMTP Service ready
C: EHLO laptop.local
S: 250-633819653d9a greets laptop.local
S: 250-SIZE 52428800
S: 250-8BITMIME
S: 250 SMTPUTF8
C: MAIL FROM:<ana@example.com>
S: 250 2.1.0 Ok
C: RCPT TO:<bruno@example.org>
S: 250 2.1.5 Ok
C: DATA
S: 354 Start mail input; end with <CR><LF>.<CR><LF>
C: From: Ana <ana@example.com>
C: To: Bruno <bruno@example.org>
C: Subject: Oi
C:
C: Primeira mensagem.
C: .
S: 250 2.0.0 Ok: queued as 4UAGUIw2T5z4DNJwPRVPgh
C: QUIT
S: 221 2.0.0 633819653d9a Mailpit ESMTP Service closing transmission channel
| ComandoCommand | O que fazWhat it does |
|---|---|
EHLO nome | Apresenta o cliente e pede a lista de extensões (STARTTLS, AUTH, SIZE...). O antigo HELO não lista nada.Introduces the client and asks for the list of extensions (STARTTLS, AUTH, SIZE...). The old HELO lists nothing. |
STARTTLS | Pede para trocar a conexão aberta por uma criptografada (seção 3).Asks to upgrade the open connection to an encrypted one (section 3). |
AUTH | O cliente prova quem é (usuário e senha, ou token).The client proves who it is (user and password, or token). |
MAIL FROM | Remetente do envelope; para onde voltam os erros (bounces).The envelope sender; where errors (bounces) go back. |
RCPT TO | Destinatário do envelope; um por comando, quantos precisar.Envelope recipient; one per command, as many as needed. |
DATA | Começa o conteúdo (cabeçalhos, linha em branco, corpo) e termina com uma linha só com .Starts the content (headers, blank line, body) and ends with a line holding only . |
RSET / QUIT | Cancela a transação atual / encerra a sessão.Cancels the current transaction / ends the session. |
MAIL FROM do envelope e o From: que o leitor vê são coisas separadas, e o servidor não exige que combinem. É por isso que dá para forjar o remetente de um e-mail, e é exatamente o furo que SPF, DKIM e DMARC tentam fechar (seção 3).Envelope is not header. The envelope MAIL FROM and the From: the reader sees are separate things, and the server does not require them to match. That is why a sender can be forged, and it is exactly the hole SPF, DKIM and DMARC try to close (section 3).Os códigos que você mais vai ver ao depurar:The codes you will see most when debugging:
| CódigoCode | SignificadoMeaning |
|---|---|
250 | OkOk |
421 / 451 | Temporário: servidor ocupado, ou greylisting. O MTA reenvia sozinho.Temporary: server busy, or greylisting. The MTA retries by itself. |
530 | Autenticação ou TLS exigidos (veja o laboratório)Authentication or TLS required (see the lab) |
535 | Credenciais inválidasInvalid credentials |
550 | Caixa inexistente ou mensagem recusada por política (SPF/DMARC, spam)Mailbox does not exist, or message rejected by policy (SPF/DMARC, spam) |
- RFC 5321 · commands, reply codes, envelope
- Sessão e códigos 530/535 reproduzidos por mim no laboratório da seção 5Session and 530/535 codes reproduced by me in the section 5 lab
2. Portas: 25, 587, 4652. Ports: 25, 587, 465
O SMTP usa portas diferentes para papéis diferentes. Separar os papéis é a primeira decisão de segurança: quem envia mensagem como usuário deve se autenticar; quem só recebe de outros servidores, não.SMTP uses different ports for different roles. Separating the roles is the first security decision: someone sending as a user must authenticate; a server only receiving from other servers does not.
| PortaPort | UsoUse | CriptografiaEncryption | AutenticaçãoAuthentication |
|---|---|---|---|
25 | Entre servidores (MTA para MTA)Server to server (MTA to MTA) | STARTTLS oportunistaOpportunistic STARTTLS | Normalmente nenhumaUsually none |
587 | Submissão: app/usuário envia (RFC 6409)Submission: app/user sends (RFC 6409) | STARTTLS (exigido)STARTTLS (required) | ObrigatóriaRequired |
465 | Submissão com TLS implícito (RFC 8314)Submission with implicit TLS (RFC 8314) | TLS desde o primeiro byteTLS from the first byte | ObrigatóriaRequired |
2525 | Não oficial; provedores o oferecem quando a 587 é bloqueadaUnofficial; providers offer it when 587 is blocked | Como a 587Like 587 | ObrigatóriaRequired |
1025 | Convenção de ferramentas de teste (Mailpit, MailHog)Convention of test tools (Mailpit, MailHog) | OpcionalOptional | OpcionalOptional |
A porta 25 de saída costuma ser bloqueada por provedores de nuvem e residenciais para conter spam; se o seu servidor na nuvem não consegue enviar, isso é o primeiro suspeito. Por isso quem hospeda um app costuma enviar por um serviço de relay (SES, Postmark, Mailgun, Resend) na 587/465 em vez de operar o próprio MTA.Outbound port 25 is often blocked by cloud and residential providers to curb spam; if your cloud server cannot send, that is the first suspect. That is why people hosting an app usually send through a relay service (SES, Postmark, Mailgun, Resend) on 587/465 instead of running their own MTA.
3. Como fazer com segurança3. Doing it securely
A sua pergunta tinha duas partes: "entra SSL?" e "tem um equivalente?". Entra, e o nome certo hoje é TLS (o SSL é o antecessor, já descontinuado). Mas TLS resolve só uma de quatro perguntas. O SMTP precisa de quatro camadas, cada uma com um problema diferente:Your question had two parts: "does SSL apply?" and "is there an equivalent?". It does, and the right name today is TLS (SSL is the deprecated predecessor). But TLS answers only one of four questions. SMTP needs four layers, each solving a different problem:
| PerguntaQuestion | CamadaLayer | FerramentasTools |
|---|---|---|
| Alguém no caminho lê a mensagem?Can someone on the path read the message? | TransporteTransport | TLS: STARTTLS ou TLS implícito, MTA-STS, DANETLS: STARTTLS or implicit TLS, MTA-STS, DANE |
| Quem pode enviar pelo meu servidor?Who may send through my server? | AcessoAccess | AUTH só dentro do TLS, senhas de app, OAuth2, limites e fail2banAUTH only inside TLS, app passwords, OAuth2, limits and fail2ban |
| Este e-mail é mesmo do domínio que diz ser?Is this email really from the domain it claims? | IdentidadeIdentity | SPF, DKIM, DMARCSPF, DKIM, DMARC |
| Meu servidor virou um relay de spam?Did my server become a spam relay? | OperaçãoOperation | Sem open relay, PTR, monitoramento, TLS-RPT/DMARC reportsNo open relay, PTR, monitoring, TLS-RPT/DMARC reports |
3.1 Transporte: STARTTLS ou TLS implícito3.1 Transport: STARTTLS or implicit TLS
Há dois jeitos de criptografar. No TLS implícito (porta 465) o TLS começa antes de qualquer palavra do SMTP. No STARTTLS (587, 25) a conexão começa em texto puro, o servidor anuncia 250-STARTTLS no EHLO, o cliente pede STARTTLS e só então a conexão é trocada por uma criptografada.There are two ways to encrypt. With implicit TLS (port 465) TLS starts before any SMTP word. With STARTTLS (587, 25) the connection starts in plain text, the server announces 250-STARTTLS in EHLO, the client sends STARTTLS and only then is the connection upgraded to an encrypted one.
250-STARTTLS da resposta. Um cliente que trata o TLS como "se possível" segue em texto puro sem avisar. A defesa: o cliente exige TLS e aborta se ele faltar. É o que o Mailpit faz com MP_SMTP_REQUIRE_STARTTLS, e o que o laboratório mostra com o erro 530 Must issue a STARTTLS command first.The downgrade attack. Because STARTTLS is negotiated in plain text, someone in the middle of the network can strip the 250-STARTTLS line from the reply. A client that treats TLS as "if possible" carries on in plain text without warning. The defence: the client requires TLS and aborts if it is missing. That is what Mailpit does with MP_SMTP_REQUIRE_STARTTLS, and what the lab shows with the 530 Must issue a STARTTLS command first error.Entre servidores (porta 25) o problema é maior: não há como exigir TLS de qualquer destino, porque muitos servidores antigos ainda não o oferecem. Por isso o TLS ali é oportunista (criptografa se der, e aceita qualquer certificado). Duas tecnologias fecham essa brecha para o seu domínio:Between servers (port 25) the problem is bigger: you cannot require TLS from every destination, because many old servers still do not offer it. So TLS there is opportunistic (encrypt if possible, accept any certificate). Two technologies close that gap for your domain:
- MTA-STS (RFC 8461): você publica uma política em HTTPS (
https://mta-sts.seudominio.com/.well-known/mta-sts.txt) e um registro DNS_mta-sts. Quem envia para você passa a exigir TLS válido para os seus MX. Não depende de DNSSEC.MTA-STS (RFC 8461): you publish a policy over HTTPS (https://mta-sts.yourdomain.com/.well-known/mta-sts.txt) and a_mta-stsDNS record. Senders to you then require valid TLS for your MX hosts. It does not depend on DNSSEC. - DANE (RFC 7672): você publica o certificado (registros
TLSA) no DNS, assinado com DNSSEC. Mais forte, mas exige DNSSEC.DANE (RFC 7672): you publish the certificate (TLSArecords) in DNS, signed with DNSSEC. Stronger, but requires DNSSEC. - TLS-RPT (RFC 8460): relatórios diários dos remetentes dizendo onde o TLS falhou com você.TLS-RPT (RFC 8460): daily reports from senders saying where TLS failed with you.
# política MTA-STS (arquivo servido em HTTPS)
version: STSv1
mode: enforce # comece com "testing"
mx: mail.seudominio.com
max_age: 604800
# DNS
_mta-sts.seudominio.com. TXT "v=STSv1; id=20261027T000000"
_smtp._tls.seudominio.com. TXT "v=TLSRPTv1; rua=mailto:tls@seudominio.com"
3.2 Acesso: autenticação3.2 Access: authentication
Na 587/465 o cliente prova quem é com o comando AUTH (RFC 4954). Os mecanismos comuns, PLAIN e LOGIN, mandam a senha só codificada em base64, ou seja, legível. Por isso a regra é: o servidor só oferece AUTH depois do TLS. O laboratório confere: o EHLO do Mailpit, logo após o TLS, anuncia AUTH LOGIN PLAIN.On 587/465 the client proves who it is with the AUTH command (RFC 4954). The common mechanisms, PLAIN and LOGIN, send the password only base64-encoded, which means readable. So the rule is: the server only offers AUTH after TLS. The lab checks it: Mailpit's EHLO, right after TLS, announces AUTH LOGIN PLAIN.
- Uma credencial por aplicação (senha de app, usuário SMTP dedicado), para revogar uma sem derrubar as outras.One credential per application (app password, dedicated SMTP user), so you can revoke one without breaking the others.
- OAuth2 (
XOAUTH2,OAUTHBEARER) onde o provedor suporta: o app usa um token de curta duração em vez da senha da conta.OAuth2 (XOAUTH2,OAUTHBEARER) where the provider supports it: the app uses a short-lived token instead of the account password. - Limite de taxa e bloqueio de tentativas falhas (fail2ban,
postscreen), porque a porta 587 é alvo constante de força bruta.Rate limits and blocking of failed attempts (fail2ban,postscreen), because port 587 is a constant brute-force target. - Credenciais fora do código: variável de ambiente ou gerenciador de segredos, como nas outras notas.Credentials out of the code: environment variable or secrets manager, as in the other notes.
3.3 Identidade: SPF, DKIM e DMARC3.3 Identity: SPF, DKIM and DMARC
Lembra do envelope forjável? Três registros no DNS do seu domínio permitem que o destinatário verifique de quem a mensagem realmente veio:Remember the forgeable envelope? Three records in your domain's DNS let the recipient verify who a message really came from:
| RespondeAnswers | ComoHow | |
|---|---|---|
| SPF RFC 7208 | "Este IP pode enviar por este domínio?""May this IP send for this domain?" | Registro TXT com a lista de IPs/serviços autorizados. Confere o domínio do MAIL FROM (envelope).A TXT record listing authorized IPs/services. Checks the MAIL FROM (envelope) domain. |
| DKIM RFC 6376 | "A mensagem foi alterada no caminho? Quem a assinou?""Was the message altered in transit? Who signed it?" | O servidor assina cabeçalhos e corpo com uma chave privada; a chave pública fica no DNS (seletor._domainkey). Vai no cabeçalho DKIM-Signature. É a mesma ideia de assinatura digital da nota de criptografia.The server signs headers and body with a private key; the public key lives in DNS (selector._domainkey). It goes in the DKIM-Signature header. It is the same digital-signature idea from the cryptography note. |
| DMARC | "O que fazer se SPF e DKIM falharem para o From: que o leitor vê?""What to do if SPF and DKIM fail for the From: the reader sees?" | Registro _dmarc com a política (none, quarantine, reject). Exige que o domínio autenticado alinhe com o do From: e envia relatórios.A _dmarc record with the policy (none, quarantine, reject). Requires the authenticated domain to align with the From: domain and sends reports. |
# SPF: só o MX do domínio e o relay do provedor podem enviar; o resto falha ("-all")
seudominio.com. TXT "v=spf1 mx include:_spf.provedor.com -all"
# DKIM: chave pública do seletor "2026a"
2026a._domainkey.seudominio.com. TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqh..."
# DMARC: comece monitorando, depois suba para quarantine e reject
_dmarc.seudominio.com. TXT "v=DMARC1; p=none; rua=mailto:dmarc@seudominio.com"
O caminho seguro é gradual: publique SPF e DKIM, ponha o DMARC em p=none e leia os relatórios agregados por algumas semanas para descobrir todos os serviços que enviam em nome do domínio (marketing, suporte, sistema de cobrança), depois suba para quarantine e por fim reject. Subir direto para reject derruba o e-mail legítimo que você esqueceu. Desde 2024 Google e Yahoo exigem SPF, DKIM e DMARC de quem envia em volume para contas deles, então isto deixou de ser opcional para app com usuários reais.The safe path is gradual: publish SPF and DKIM, set DMARC to p=none and read the aggregate reports for a few weeks to discover every service sending on the domain's behalf (marketing, support, billing), then move to quarantine and finally reject. Jumping straight to reject kills legitimate email you forgot about. Since 2024 Google and Yahoo require SPF, DKIM and DMARC from bulk senders to their accounts, so this stopped being optional for an app with real users.
pct. Os registros v=DMARC1 continuam iguais; confira a RFC 9989 no rfc-editor.org antes de depender de detalhes. A exigência do Google/Yahoo é de memória (fevereiro de 2024, para remetentes de mais de 5 mil mensagens por dia).Personal note, not verified in primary sources: the original DMARC is RFC 7489 (informational). Searching, I found only vendor blogs saying that in May 2026 the IETF published the revision (DMARCbis) as RFC 9989, 9990 and 9991, replacing 7489 and removing tags such as pct. v=DMARC1 records stay the same; check RFC 9989 on rfc-editor.org before relying on details. The Google/Yahoo requirement is from memory (February 2024, for senders of more than 5,000 messages a day).3.4 Operação: não vire um relay aberto3.4 Operation: do not become an open relay
Um open relay é um servidor que aceita e reenvia e-mail de qualquer pessoa para qualquer lugar. Spammers o encontram em horas, e o seu IP vai para listas de bloqueio. A regra é simples: o servidor só aceita mensagens (a) para domínios que ele hospeda, ou (b) de clientes autenticados (ou de redes de confiança explícitas, como um IP interno). Em Postfix, é o papel de smtpd_relay_restrictions:An open relay is a server that accepts and forwards email from anyone to anywhere. Spammers find it within hours, and your IP lands on blocklists. The rule is simple: the server only accepts messages (a) for domains it hosts, or (b) from authenticated clients (or explicit trusted networks, such as an internal IP). In Postfix, that is the job of smtpd_relay_restrictions:
# /etc/postfix/main.cf (trecho)
smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination
smtpd_tls_security_level = may # porta 25: oportunista
smtpd_tls_auth_only = yes # AUTH só dentro do TLS
disable_vrfy_command = yes # não revela quais caixas existem
- DNS reverso (PTR) do IP de saída apontando para o nome do seu servidor, e o nome resolvendo de volta ao IP. Sem isso, muitos destinos recusam.Reverse DNS (PTR) for the outbound IP pointing to your server's name, and the name resolving back to the IP. Without it, many destinations refuse.
- Certificado válido (Let's Encrypt) com renovação automática, para o nome que o MX anuncia.A valid certificate (Let's Encrypt) with automatic renewal, for the name the MX announces.
- Monitore a fila (
mailq) e os logs: uma fila crescendo de repente é sinal de conta comprometida enviando spam.Monitor the queue (mailq) and logs: a suddenly growing queue is a sign of a compromised account sending spam. - Mantenha atualizado: servidores de e-mail ficam expostos à internet e já tiveram falhas graves (por exemplo, a smuggling de SMTP de 2023, em que terminadores de DATA ambíguos permitiam forjar mensagens).Keep it patched: mail servers are exposed to the internet and have had serious flaws (for example, the 2023 SMTP smuggling, where ambiguous DATA terminators let attackers forge messages).
- RFC 8314 · Cleartext Considered Obsolete
- RFC 3207 · SMTP Service Extension for Secure SMTP over TLS (STARTTLS)
- RFC 4954 · SMTP Service Extension for Authentication
- RFC 8461 · MTA-STS
- RFC 7672 · DANE for SMTP
- RFC 8460 · SMTP TLS Reporting
- RFC 7208 · SPF
- RFC 6376 · DKIM
- RFC 7489 · DMARC (DMARCbis: blog, not primary)
- Postfix · access restrictions (relay)
4. Projetos open-source para praticar4. Open-source projects to practise with
Há dois grupos com objetivos diferentes. As caixas de areia capturam e mostram o que o seu código enviou, sem entregar nada a ninguém; são para desenvolver e testar. Os servidores de verdade entregam e-mail de verdade; são para aprender a operar.There are two groups with different goals. Sandboxes capture and show what your code sent, delivering nothing to anyone; they are for developing and testing. Real servers deliver real email; they are for learning to operate.
| ProjetoProject | TipoType | Para quêWhat for |
|---|---|---|
| Mailpit | SandboxSandbox | Servidor SMTP de teste (porta 1025) com interface web (8025) e API REST; aceita STARTTLS, TLS e autenticação opcionais. Comece por aqui (seção 5).Test SMTP server (port 1025) with a web UI (8025) and REST API; optional STARTTLS, TLS and authentication. Start here (section 5). |
| MailHog | SandboxSandbox | O clássico, com as mesmas portas. A página do repositório mostra 2014-2017 e não indica manutenção ativa; hoje o Mailpit é o substituto mais usado.The classic, with the same ports. The repository page shows 2014-2017 and no sign of active maintenance; Mailpit is now the most-used replacement. |
| docker-mailserver | Servidor completoFull server | Postfix (SMTP) + Dovecot (IMAP) + Rspamd, ClamAV, OpenDKIM, OpenDMARC, Fail2ban em um contêiner; configuração em arquivos; Let's Encrypt, certificado manual ou autoassinado. O melhor para ver as seções 2 e 3 funcionando juntas.Postfix (SMTP) + Dovecot (IMAP) + Rspamd, ClamAV, OpenDKIM, OpenDMARC, Fail2ban in one container; file-based config; Let's Encrypt, manual or self-signed certificate. The best to see sections 2 and 3 working together. |
| Stalwart | Servidor completoFull server | Escrito em Rust: SMTP, IMAP, JMAP, POP3 e também CalDAV/CardDAV, em um binário só. Licença AGPL-3.0 (com uma edição Enterprise). Bom para ver uma implementação moderna.Written in Rust: SMTP, IMAP, JMAP, POP3 and also CalDAV/CardDAV, in a single binary. AGPL-3.0 license (with an Enterprise edition). Good for seeing a modern implementation. |
| Postfix | MTAMTA | O MTA mais comum; o main.cf da seção 3.4 é dele. Estudar o Postfix puro ensina o que o docker-mailserver esconde.The most common MTA; the main.cf in section 3.4 is its. Studying plain Postfix teaches what docker-mailserver hides. |
| OutrosOthers | MTA | Exim, OpenSMTPD, Haraka (em Node.js, extensível por plugins), Maddy (um binário em Go), Mailu e Mail-in-a-Box (pacotes prontos). Citados de memória; confira o estado de cada um antes de escolher.Exim, OpenSMTPD, Haraka (Node.js, extensible by plugins), Maddy (a single Go binary), Mailu and Mail-in-a-Box (ready-made bundles). Mentioned from memory; check each one's status before choosing. |
localhost e com domínios de teste; só ponha um servidor na internet depois de ter fechado o relay (seção 3.4).A real server at home rarely delivers. For Gmail to accept your email you need a fixed IP, PTR, port 25 open, SPF, DKIM and DMARC, and even then a new IP starts with a bad reputation. Practise everything on localhost and with test domains; only put a server on the internet after closing the relay (section 3.4).5. Laboratório em Docker: Mailpit com TLS e autenticação5. Docker lab: Mailpit with TLS and authentication
Vamos subir um servidor SMTP que exige STARTTLS e senha, enviar um e-mail por um script Python e ver cada tentativa insegura ser recusada. Tudo em 127.0.0.1; nada sai do seu computador. Testado com Docker e a imagem axllent/mailpit em 27/10/2026.We will start an SMTP server that requires STARTTLS and a password, send an email from a Python script and watch every insecure attempt get refused. Everything on 127.0.0.1; nothing leaves your computer. Tested with Docker and the axllent/mailpit image on 2026-10-27.
Parte A: a conversa em texto puroPart A: the conversation in plain text
docker run -d --rm --name mp -p 127.0.0.1:1025:1025 -p 127.0.0.1:8025:8025 axllent/mailpit
# fale SMTP "na mão" (a sessão da seção 1)
printf 'EHLO laptop.local\r\nMAIL FROM:<ana@example.com>\r\nRCPT TO:<bruno@example.org>\r\nDATA\r\nSubject: Oi\r\n\r\nOla.\r\n.\r\nQUIT\r\n' | nc localhost 1025
# abra http://localhost:8025 e veja a mensagem na interface
docker rm -f mp
Parte B: exigir TLS e senhaPart B: requiring TLS and a password
mkdir smtp-lab && cd smtp-lab && mkdir certs
# certificado autoassinado, só para o laboratório
openssl req -x509 -newkey rsa:2048 -nodes -days 30 -subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost" -keyout certs/key.pem -out certs/cert.pem
# arquivo de senhas (bcrypt): usuário "ana", senha "segredo"
docker run --rm httpd:2.4-alpine htpasswd -nbB ana segredo > auth.txt
compose.yaml:compose.yaml:
services:
mailpit:
image: axllent/mailpit
ports:
- "127.0.0.1:8025:8025" # interface web
- "127.0.0.1:1025:1025" # SMTP
volumes:
- ./certs:/certs:ro
- ./auth.txt:/auth.txt:ro
environment:
MP_SMTP_TLS_CERT: /certs/cert.pem
MP_SMTP_TLS_KEY: /certs/key.pem
MP_SMTP_REQUIRE_STARTTLS: "true"
MP_SMTP_AUTH_FILE: /auth.txt
send.py (cliente seguro: confia só no nosso certificado, faz STARTTLS e depois login):send.py (secure client: trusts only our certificate, does STARTTLS and then login):
import smtplib, ssl, sys
from email.message import EmailMessage
msg = EmailMessage()
msg["From"] = "ana@example.com"
msg["To"] = "bruno@example.org"
msg["Subject"] = "Olá pelo SMTP"
msg.set_content("Corpo da mensagem.\n")
ctx = ssl.create_default_context(cafile="certs/cert.pem") # confia só no nosso certificado
with smtplib.SMTP("localhost", 1025) as s:
s.starttls(context=ctx)
s.login("ana", sys.argv[1])
s.send_message(msg)
print("enviado")
Suba e rode os quatro testes. Esta é a saída real:Start it and run the four tests. This is the real output:
docker compose up -d
# [smtpd] starting on [::]:1025 (STARTTLS required)
# 1. sem TLS: recusado
python3 -c "import smtplib; smtplib.SMTP('localhost',1025).sendmail('a@x.com',['b@y.org'],'Subject: x\r\n\r\nhi')"
# SMTPSenderRefused: (530, b'5.7.0 Must issue a STARTTLS command first')
# 2. TLS, senha errada: recusado
python3 send.py errada
# SMTPAuthenticationError: (535, b'5.7.8 Authentication credentials invalid')
# 3. TLS e senha certa: aceito
python3 send.py segredo
# enviado
# 4. inspecionar o TLS e o que o servidor anuncia depois dele
echo QUIT | openssl s_client -starttls smtp -connect localhost:1025 -CAfile certs/cert.pem
# Verification: OK
# New, TLSv1.3, Cipher is TLS_AES_128_GCM_SHA256
# Verify return code: 0 (ok)
# a mensagem chegou
curl -s localhost:8025/api/v1/messages | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['total'], d['messages'][0]['Subject'])"
# 1 Olá pelo SMTP
O teste 1 é a defesa contra o downgrade da seção 3.1 em ação, o 2 mostra o 535 e o 3 mostra o caminho feliz. Sem o cafile, o Python recusaria o certificado autoassinado: é a verificação de certificado funcionando, e o motivo de nunca desligá-la (CERT_NONE) para "fazer funcionar".Test 1 is the section 3.1 downgrade defence in action, 2 shows the 535 and 3 shows the happy path. Without the cafile, Python would refuse the self-signed certificate: that is certificate verification working, and the reason never to turn it off (CERT_NONE) just to "make it work".
compose.yaml publica as portas só em 127.0.0.1.Lab only. The self-signed certificate and the "segredo" password are for testing. In production use a CA certificate (Let's Encrypt) and real secrets. Also note Mailpit warns in its log that the web UI (8025) has no authentication; that is why compose.yaml publishes ports only on 127.0.0.1.Limpar e exercíciosCleanup and exercises
docker compose down -v
- Faça um
EHLOna mão antes e depois do TLS (openssl s_client -starttls smtp -connect localhost:1025 -crlf). Em qual dos dois apareceAUTH LOGIN PLAIN? Por quê?Do anEHLOby hand before and after TLS (openssl s_client -starttls smtp -connect localhost:1025 -crlf). In which of the two doesAUTH LOGIN PLAINappear? Why? - No
send.py, troque ocafileporssl.create_default_context()e rode. Que erro aparece? Leia a mensagem.Insend.py, replace thecafilewithssl.create_default_context()and run it. What error appears? Read the message. - Mande um e-mail com
From: presidente@example.comno cabeçalho e outroMAIL FROMno envelope. O Mailpit aceita? O que isso prova sobre a seção 1?Send an email withFrom: presidente@example.comin the header and a different envelopeMAIL FROM. Does Mailpit accept it? What does that prove about section 1? - Suba o docker-mailserver seguindo a documentação dele, crie uma conta e repita o
openssl s_clientna 587 e na 465. Compare o que muda entre STARTTLS e TLS implícito (-connect localhost:465sem-starttls).Start docker-mailserver following its documentation, create an account and repeatopenssl s_clienton 587 and on 465. Compare what changes between STARTTLS and implicit TLS (-connect localhost:465without-starttls). - Use
dig TXTno SPF, no DMARC e no seletor DKIM de um domínio que você usa e leia os registros com o que aprendeu na seção 3.3.Usedig TXTon the SPF, DMARC and a DKIM selector of a domain you use and read the records with what you learned in section 3.3.
6. Checklist6. Checklist
- Aplicação envia pela 587 (STARTTLS) ou 465 (TLS implícito), autenticada, com TLS obrigatório e certificado verificado.The application sends via 587 (STARTTLS) or 465 (implicit TLS), authenticated, with TLS required and the certificate verified.
- Uma credencial por aplicação, fora do código, revogável.One credential per application, out of the code, revocable.
- Servidor oferece
AUTHsó depois do TLS; relay fechado (reject_unauth_destination);VRFYdesligado.Server offersAUTHonly after TLS; relay closed (reject_unauth_destination);VRFYoff. - SPF, DKIM e DMARC publicados; DMARC começa em
p=nonee sobe com base nos relatórios.SPF, DKIM and DMARC published; DMARC starts atp=noneand rises based on the reports. - MTA-STS (e TLS-RPT) no domínio que recebe e-mail; DANE se você já tem DNSSEC.MTA-STS (and TLS-RPT) on the domain that receives email; DANE if you already have DNSSEC.
- PTR do IP de saída, certificado válido com renovação, fila e logs monitorados, software atualizado.PTR for the outbound IP, valid certificate with renewal, queue and logs monitored, software patched.
- Em desenvolvimento, use uma caixa de areia (Mailpit); nunca aponte testes para um relay real com endereços de clientes.In development, use a sandbox (Mailpit); never point tests at a real relay with customers' addresses.
- Para um app, prefira um serviço de relay a operar o próprio MTA.For an app, prefer a relay service to running your own MTA.