邮件协议详解:SMTP、POP3 与 IMAP
电子邮件是互联网最早、最广泛使用的应用之一。尽管即时通讯工具层出不穷,电子邮件仍然是企业通信、身份验证、通知推送的重要基础设施。理解邮件协议的工作原理,对于运维邮件服务器、开发邮件应用、排查邮件问题都至关重要。
电子邮件系统概述
邮件系统的组成
一封邮件从发送到接收,需要多个组件协同工作:
发件人 收件人
| |
v v
┌──────────┐ SMTP ┌──────────────────┐ SMTP ┌──────────┐
│ MUA │ ─────────> │ MTA (发送服务器) │ ─────────> │ MTA │
│ (客户端) │ │ │ │(接收服务器)│
└──────────┘ └──────────────────┘ └──────────┘
│
v
┌──────────┐
│ MDA │
│ (投递代理)│
└──────────┘
│
v
┌──────────┐
│ 邮箱存储 │
└──────────┘
│
POP3/IMAP │
┌──────┘
v
┌──────────┐
│ MUA │
│ (客户端) │
└──────────┘
MUA(Mail User Agent,邮件用户代理):用户用于收发邮件的客户端软件,如 Outlook、Foxmail、Apple Mail 等。
MTA(Mail Transfer Agent,邮件传输代理):负责邮件的传输和路由,如 Postfix、Exim、Sendmail、Microsoft Exchange。
MDA(Mail Delivery Agent,邮件投递代理):将收到的邮件投递到用户的邮箱存储中,如 Dovecot、Procmail。
邮件传输流程
- 发件人撰写邮件:使用 MUA(邮件客户端)撰写邮件
- 提交到发送服务器:MUA 通过 SMTP 协议将邮件发送到发件人的邮件服务器
- 路由查找:发送服务器查询收件人域名对应的 MX 记录,找到接收服务器
- 传输邮件:发送服务器通过 SMTP 协议将邮件传输到接收服务器
- 本地投递:接收服务器通过 MDA 将邮件存储到收件人的邮箱
- 接收邮件:收件人通过 POP3 或 IMAP 协议从服务器获取邮件
SMTP 协议
SMTP(Simple Mail Transfer Protocol,简单邮件传输协议)是用于发送邮件的核心协议,由 RFC 5321 定义。它负责将邮件从发件人传送到收件人的邮件服务器。
SMTP 基本特点
- 基于 TCP:使用端口 25(默认)、587(提交端口)或 465(SMTPS)
- 文本协议:命令和响应都是可读的 ASCII 文本
- 推协议:邮件由发送方主动推送给接收方
- 存储转发:邮件可以经过多个 MTA 中继转发
SMTP 会话流程
一个完整的 SMTP 会话包含三个阶段:
SMTP 命令详解
| 命令 | 语法 | 说明 |
|---|---|---|
| EHLO | EHLO <domain> | 扩展的 Hello,启动会话并声明客户端身份 |
| HELO | HELO <domain> | 基本的 Hello(旧版,不推荐) |
| MAIL FROM | MAIL FROM:<address> | 指定发件人地址 |
| RCPT TO | RCPT TO:<address> | 指定收件人地址(可多次使用) |
| DATA | DATA | 开始传输邮件内容,以单独一行的 . 结束 |
| RSET | RSET | 重置当前会话,取消当前邮件 |
| VRFY | VRFY <address> | 验证邮箱地址是否存在(通常被禁用) |
| NOOP | NOOP | 空操作,用于保持连接 |
| QUIT | QUIT | 关闭连接 |
EHLO vs HELO:EHLO 是扩展版本,服务器会返回支持的功能列表(如 STARTTLS、AUTH、PIPELINING 等)。现代邮件系统都应该使用 EHLO。
SMTP 响应码
SMTP 响应码由三位数字组成,第一位表示类别:
| 类别 | 含义 | 示例 |
|---|---|---|
| 2xx | 成功 | 250 请求的操作完成 |
| 3xx | 需要更多信息 | 354 开始邮件输入 |
| 4xx | 暂时失败 | 450 邮箱暂时不可用 |
| 5xx | 永久失败 | 550 邮箱不存在 |
常见响应码:
| 响应码 | 含义 |
|---|---|
| 220 | 服务就绪 |
| 221 | 服务关闭传输通道 |
| 250 | 请求的操作完成 |
| 354 | 开始邮件输入,以 . 结束 |
| 421 | 服务不可用,关闭传输通道 |
| 450 | 邮箱不可用(忙) |
| 451 | 本地错误,操作中止 |
| 452 | 存储空间不足 |
| 500 | 命令无法识别 |
| 501 | 参数语法错误 |
| 502 | 命令未实现 |
| 503 | 命令顺序错误 |
| 550 | 邮箱不可用 |
| 551 | 用户不在本地 |
| 552 | 存储空间超出 |
| 553 | 邮箱名无效 |
| 554 | 事务失败 |
SMTP 认证
SMTP 最初设计时没有认证机制,任何人都可以发送邮件。这导致了垃圾邮件泛滥。现在,邮件提交端口(587)通常要求认证:
AUTH PLAIN:Base64 编码的用户名和密码
C: AUTH PLAIN
S: 334
C: AHVzZXJuYW1lAHBhc3N3b3Jk
S: 235 2.7.0 Authentication successful
AUTH LOGIN:分别发送用户名和密码
C: AUTH LOGIN
S: 334 VXNlcm5hbWU6
C: dXNlcm5hbWU=
S: 334 UGFzc3dvcmQ6
C: cGFzc3dvcmQ=
S: 235 2.7.0 Authentication successful
SMTP 与 TLS
STARTTLS:将明文连接升级为加密连接
C: EHLO client.example.com
S: 250-STARTTLS
C: STARTTLS
S: 220 2.0.0 Ready to start TLS
[TLS 握手]
C: EHLO client.example.com
S: 250-AUTH PLAIN LOGIN
SMTPS(SMTP over SSL):从连接开始就使用 TLS,端口 465
使用 Telnet 测试 SMTP
# 连接邮件服务器
telnet mail.example.com 25
# 会话示例
220 mail.example.com ESMTP Postfix
EHLO client.example.com
250-mail.example.com
250-PIPELINING
250-SIZE 10240000
250-STARTTLS
250 AUTH PLAIN LOGIN
MAIL FROM:<[email protected]>
250 2.1.0 Ok
RCPT TO:<[email protected]>
250 2.1.5 Ok
DATA
354 End data with <CR><LF>.<CR><LF>
From: [email protected]
To: [email protected]
Subject: Test
This is a test email.
.
250 2.0.0 Ok: queued as ABC123
QUIT
221 2.0.0 Bye
Python 发送邮件示例
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.utils import formataddr
def send_email(smtp_server, smtp_port, username, password,
from_addr, to_addr, subject, content, html_content=None):
"""
发送邮件
Args:
smtp_server: SMTP 服务器地址
smtp_port: SMTP 端口(587 或 465)
username: 认证用户名
password: 认证密码
from_addr: 发件人地址
to_addr: 收件人地址(可以是列表)
subject: 邮件主题
content: 纯文本内容
html_content: HTML 内容(可选)
"""
# 创建邮件对象
msg = MIMEMultipart('alternative')
msg['From'] = formataddr(('发件人名称', from_addr))
msg['To'] = ', '.join(to_addr) if isinstance(to_addr, list) else to_addr
msg['Subject'] = subject
# 添加纯文本内容
msg.attach(MIMEText(content, 'plain', 'utf-8'))
# 添加 HTML 内容(如果提供)
if html_content:
msg.attach(MIMEText(html_content, 'html', 'utf-8'))
# 连接服务器并发送
try:
# 使用 STARTTLS(端口 587)
if smtp_port == 587:
with smtplib.SMTP(smtp_server, smtp_port) as server:
server.starttls()
server.login(username, password)
server.sendmail(from_addr, to_addr, msg.as_string())
# 使用 SMTPS(端口 465)
elif smtp_port == 465:
with smtplib.SMTP_SSL(smtp_server, smtp_port) as server:
server.login(username, password)
server.sendmail(from_addr, to_addr, msg.as_string())
print("邮件发送成功")
return True
except smtplib.SMTPException as e:
print(f"邮件发送失败: {e}")
return False
# 使用示例
send_email(
smtp_server='smtp.example.com',
smtp_port=587,
username='[email protected]',
password='password',
from_addr='[email protected]',
to_addr='[email protected]',
subject='测试邮件',
content='这是一封测试邮件。',
html_content='<html><body><h1>这是一封测试邮件</h1></body></html>'
)
POP3 协议
POP3(Post Office Protocol version 3,邮局协议第 3 版)是用于从邮件服务器获取邮件的协议,由 RFC 1939 定义。它的设计理念是"下载并删除",适合单设备使用场景。
POP3 特点
- 端口:110(明文),995(POP3S)
- 简单:命令简单,功能有限
- 离线模式:邮件下载后可以离线阅读
- 单向:只能获取邮件,不能修改服务器状态(除了删除)
- 适合单设备:不适合多设备同步
POP3 会话流程
POP3 命令
| 命令 | 语法 | 说明 |
|---|---|---|
| USER | USER <username> | 发送用户名 |
| PASS | PASS <password> | 发送密码 |
| STAT | STAT | 获取邮箱状态(邮件数和总大小) |
| LIST | LIST [msg] | 列出邮件(或指定邮件的大小) |
| RETR | RETR <msg> | 获取指定邮件的内容 |
| DELE | DELE <msg> | 标记删除指定邮件 |
| NOOP | NOOP | 空操作 |
| RSET | RSET | 重置所有删除标记 |
| QUIT | QUIT | 提交更改并断开连接 |
| TOP | TOP <msg> <n> | 获取邮件头部和前 n 行 |
| UIDL | UIDL [msg] | 获取邮件的唯一标识符 |
POP3 响应
+OK:操作成功-ERR:操作失败
POP3 的限制
- 不支持文件夹:无法在服务器上管理文件夹
- 不支持标记:无法标记邮件为已读、星标等
- 同步困难:删除后其他设备无法获取该邮件
- 只下载完整邮件:无法只下载邮件头部
Python POP3 示例
import poplib
from email.parser import Parser
def fetch_emails(pop_server, port, username, password, delete=False):
"""
从 POP3 服务器获取邮件
Args:
pop_server: POP3 服务器地址
port: 端口(110 或 995)
username: 用户名
password: 密码
delete: 是否在获取后删除服务器上的邮件
"""
emails = []
try:
# 连接服务器
if port == 995:
server = poplib.POP3_SSL(pop_server, port)
else:
server = poplib.POP3(pop_server, port)
# 认证
server.user(username)
server.pass_(password)
# 获取邮箱状态
status = server.stat()
print(f"邮箱状态: {status[0]} 封邮件, {status[1]} 字节")
# 获取邮件列表
msg_list = server.list()
# 遍历邮件
for i in range(len(msg_list[1])):
msg_num = i + 1
# 获取邮件内容
msg_content = b'\r\n'.join(server.retr(msg_num)[1])
email_message = Parser().parsestr(msg_content.decode('utf-8', errors='ignore'))
emails.append(email_message)
# 标记删除
if delete:
server.dele(msg_num)
# 关闭连接
server.quit()
return emails
except Exception as e:
print(f"获取邮件失败: {e}")
return None
# 使用示例
emails = fetch_emails('pop.example.com', 995, '[email protected]', 'password')
for email in emails:
print(f"发件人: {email['From']}")
print(f"主题: {email['Subject']}")
print("---")
IMAP 协议
IMAP(Internet Message Access Protocol,互联网邮件访问协议)是功能更强大的邮件获取协议,由 RFC 3501 定义。它的设计理念是"服务器端管理",适合多设备同步使用。
IMAP 特点
- 端口:143(明文),993(IMAPS)
- 服务器端存储:邮件保存在服务器上
- 双向同步:客户端状态与服务器同步
- 文件夹支持:支持创建、管理文件夹
- 搜索功能:支持服务器端搜索
- 部分获取:可以只获取邮件的一部分
- 多设备同步:适合在多个设备上使用
IMAP 会话流程
IMAP 命令
IMAP 命令以标签(tag)开头,用于匹配请求和响应。
| 命令 | 语法 | 说明 |
|---|---|---|
| CAPABILITY | tag CAPABILITY | 查询服务器支持的功能 |
| LOGIN | tag LOGIN user pass | 登录认证 |
| AUTHENTICATE | tag AUTHENTICATE mechanism | SASL 认证 |
| SELECT | tag SELECT mailbox | 选择邮箱 |
| EXAMINE | tag EXAMINE mailbox | 以只读模式打开邮箱 |
| CREATE | tag CREATE mailbox | 创建邮箱 |
| DELETE | tag DELETE mailbox | 删除邮箱 |
| RENAME | tag RENAME old new | 重命名邮箱 |
| SUBSCRIBE | tag SUBSCRIBE mailbox | 订阅邮箱 |
| UNSUBSCRIBE | tag UNSUBSCRIBE mailbox | 取消订阅 |
| LIST | tag LIST ref pattern | 列出邮箱 |
| STATUS | tag STATUS mailbox (items) | 获取邮箱状态 |
| FETCH | tag FETCH seq items | 获取邮件 |
| STORE | tag STORE seq item value | 存储邮件标志 |
| SEARCH | tag SEARCH criteria | 搜索邮件 |
| COPY | tag COPY seq mailbox | 复制邮件 |
| MOVE | tag MOVE seq mailbox | 移动邮件 |
| EXPUNGE | tag EXPUNGE | 永久删除标记为删除的邮件 |
| CLOSE | tag CLOSE | 关闭邮箱(隐式 EXPUNGE) |
| LOGOUT | tag LOGOUT | 注销连接 |
邮件标志
| 标志 | 说明 |
|---|---|
\Seen | 已读 |
\Answered | 已回复 |
\Flagged | 已标记/星标 |
\Deleted | 已删除(等待 EXPUNGE) |
\Draft | 草稿 |
\Recent | 最近到达(本次会话新到) |
IMAP 数据项
FETCH 命令可以获取邮件的不同部分:
| 数据项 | 说明 |
|---|---|
ALL | 宏:等同于 FLAGS INTERNALDATE RFC822.SIZE ENVELOPE |
FAST | 宏:等同于 FLAGS INTERNALDATE RFC822.SIZE |
FULL | 宏:等同于 FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY |
BODY[] | 完整邮件内容 |
BODY[HEADER] | 只获取邮件头 |
BODY[TEXT] | 只获取邮件正文 |
BODY[1] | 获取第一个 MIME 部分 |
RFC822 | 完整邮件(等同于 BODY[]) |
RFC822.HEADER | 只获取邮件头 |
RFC822.TEXT | 只获取邮件正文 |
ENVELOPE | 邮件信封(发件人、收件人等) |
FLAGS | 邮件标志 |
IMAP 搜索条件
# 搜索未读邮件
SEARCH UNSEEN
# 搜索来自特定发件人的邮件
SEARCH FROM "[email protected]"
# 搜索主题包含关键词的邮件
SEARCH SUBJECT "important"
# 搜索特定日期之后的邮件
SEARCH SINCE 01-Jan-2024
# 组合条件
SEARCH UNSEEN FROM "[email protected]" SINCE 01-Jan-2024
Python IMAP 示例
import imaplib
import email
from email.header import decode_header
class IMAPClient:
"""IMAP 客户端封装"""
def __init__(self, server, port=993, use_ssl=True):
self.server = server
self.port = port
self.use_ssl = use_ssl
self.conn = None
def connect(self, username, password):
"""连接并登录"""
try:
if self.use_ssl:
self.conn = imaplib.IMAP4_SSL(self.server, self.port)
else:
self.conn = imaplib.IMAP4(self.server, self.port)
self.conn.login(username, password)
print("IMAP 连接成功")
return True
except Exception as e:
print(f"IMAP 连接失败: {e}")
return False
def list_folders(self):
"""列出所有邮箱文件夹"""
status, folders = self.conn.list()
result = []
for folder in folders:
# 解析文件夹名称
parts = folder.decode().split('"')
if len(parts) >= 3:
result.append(parts[-2])
return result
def select_folder(self, folder='INBOX'):
"""选择邮箱文件夹"""
status, data = self.conn.select(folder)
if status == 'OK':
print(f"已选择: {folder}")
return int(data[0]) # 返回邮件数量
return 0
def search(self, criteria='ALL'):
"""搜索邮件"""
status, message_ids = self.conn.search(None, criteria)
if status == 'OK':
return message_ids[0].split()
return []
def fetch_email(self, msg_id):
"""获取单封邮件"""
status, data = self.conn.fetch(msg_id, '(RFC822)')
if status == 'OK':
raw_email = data[0][1]
return email.message_from_bytes(raw_email)
return None
def fetch_headers(self, msg_id):
"""只获取邮件头"""
status, data = self.conn.fetch(msg_id, '(BODY[HEADER])')
if status == 'OK':
return email.message_from_bytes(data[0][1])
return None
def set_flags(self, msg_id, flags):
"""设置邮件标志"""
status, data = self.conn.store(msg_id, '+FLAGS', flags)
return status == 'OK'
def mark_as_read(self, msg_id):
"""标记为已读"""
return self.set_flags(msg_id, '\\Seen')
def mark_as_unread(self, msg_id):
"""标记为未读"""
status, data = self.conn.store(msg_id, '-FLAGS', '\\Seen')
return status == 'OK'
def delete_email(self, msg_id):
"""标记删除邮件"""
status, data = self.conn.store(msg_id, '+FLAGS', '\\Deleted')
return status == 'OK'
def expunge(self):
"""永久删除已标记的邮件"""
self.conn.expunge()
def move_email(self, msg_id, dest_folder):
"""移动邮件到另一个文件夹"""
status, data = self.conn.copy(msg_id, dest_folder)
if status == 'OK':
self.delete_email(msg_id)
return True
return False
def close(self):
"""关闭连接"""
if self.conn:
try:
self.conn.close()
self.conn.logout()
except:
pass
def decode_str(header_value):
"""解码邮件头中的编码字符串"""
if header_value is None:
return ""
decoded_parts = decode_header(header_value)
result = []
for part, charset in decoded_parts:
if isinstance(part, bytes):
result.append(part.decode(charset or 'utf-8', errors='ignore'))
else:
result.append(part)
return ''.join(result)
# 使用示例
if __name__ == '__main__':
client = IMAPClient('imap.example.com', 993)
if client.connect('[email protected]', 'password'):
# 列出文件夹
folders = client.list_folders()
print(f"文件夹: {folders}")
# 选择收件箱
count = client.select_folder('INBOX')
print(f"收件箱邮件数: {count}")
# 搜索未读邮件
unread_ids = client.search('UNSEEN')
print(f"未读邮件: {len(unread_ids)} 封")
# 获取最新邮件
if count > 0:
all_ids = client.search('ALL')
latest_id = all_ids[-1]
email_msg = client.fetch_email(latest_id)
print(f"发件人: {decode_str(email_msg['From'])}")
print(f"主题: {decode_str(email_msg['Subject'])}")
print(f"日期: {email_msg['Date']}")
client.close()
POP3 vs IMAP 对比
| 特性 | POP3 | IMAP |
|---|---|---|
| 邮件存储 | 本地 | 服务器 |
| 多设备同步 | 不支持 | 支持 |
| 文件夹管理 | 不支持 | 支持 |
| 已读/未读状态 | 不同步 | 同步 |
| 部分下载 | 不支持 | 支持 |
| 服务器搜索 | 不支持 | 支持 |
| 离线阅读 | 支持(下载后) | 需要预先下载 |
| 服务器空间 | 节省 | 占用 |
| 复杂度 | 简单 | 复杂 |
| 适用场景 | 单设备、存档 | 多设备、日常使用 |
建议:现代邮件系统通常推荐使用 IMAP,方便多设备同步。如果需要本地存档大量邮件,可以结合 POP3 使用。
邮件安全认证
邮件系统面临的主要安全威胁包括:垃圾邮件、钓鱼邮件、邮件伪造。SPF、DKIM、DMARC 三种技术共同构成了邮件安全认证的基础。
SPF(Sender Policy Framework)
SPF 让域名所有者声明哪些服务器被授权发送来自该域名的邮件。
工作原理:
SPF 记录语法:
v=spf1 ip4:192.0.2.0/24 ip4:198.51.100.100 include:_spf.google.com -all
| 机制 | 说明 |
|---|---|
ip4:cidr | 授权的 IPv4 地址范围 |
ip6:cidr | 授权的 IPv6 地址范围 |
a:domain | 授权域名 A 记录指向的 IP |
mx:domain | 授权域名 MX 记录指向的服务器 |
include:domain | 包含另一个域名的 SPF 记录 |
exists:domain | 如果域名存在则通过 |
all | 所有其他情况的处理 |
限定符:
| 限定符 | 说明 |
|---|---|
+ | 通过(默认) |
- | 失败(拒绝) |
~ | 软失败(标记但接受) |
? | 中性(无策略) |
配置示例:
# 只允许特定 IP 发送
v=spf1 ip4:192.0.2.100 -all
# 允许多个 IP 范围
v=spf1 ip4:192.0.2.0/24 ip4:198.51.100.0/24 -all
# 包含第三方邮件服务
v=spf1 include:_spf.google.com include:sendgrid.net -all
# 测试模式(不实际拒绝)
v=spf1 ip4:192.0.2.0/24 ~all
DKIM(DomainKeys Identified Mail)
DKIM 为邮件添加数字签名,验证邮件确实来自声明的域名且未被篡改。
工作原理:
DKIM-Signature 头部:
DKIM-Signature: v=1; a=rsa-sha256; d=example.com; s=default;
h=From:To:Subject:Date;
bh=2jUSO9PuvldFwDnhKvRVdQ==;
b=encrypted-signature-data;
| 字段 | 说明 |
|---|---|
v | DKIM 版本 |
a | 签名算法(rsa-sha256 或 rsa-sha1) |
d | 签名域名 |
s | 选择器(用于查找公钥) |
h | 被签名的头部字段列表 |
bh | 邮件正文的哈希值 |
b | 数字签名 |
DNS 记录格式:
default._domainkey.example.com. IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC..."
生成 DKIM 密钥:
# 使用 opendkim 工具生成
opendkim-genkey -t -s default -d example.com
# 会生成两个文件:
# default.private - 私钥(保存在邮件服务器)
# default.txt - DNS TXT 记录内容(发布到 DNS)
DMARC(Domain-based Message Authentication, Reporting, and Conformance)
DMARC 告诉邮件接收方如何处理 SPF 和 DKIM 验证失败的邮件,并提供报告机制。
工作原理:
1. 接收邮件
2. 检查 SPF
3. 检查 DKIM
4. 检查 DMARC 策略
5. 根据策略决定:通过、隔离、拒绝
6. 发送报告给域名所有者
DMARC 记录语法:
_dmarc.example.com. IN TXT "v=DMARC1; p=reject; rua=mailto:[email protected]; pct=100; adkim=s; aspf=s"
| 标签 | 说明 | 示例值 |
|---|---|---|
v | DMARC 版本 | DMARC1 |
p | 策略 | none, quarantine, reject |
pct | 应用策略的百分比 | 0-100 |
rua | 聚合报告发送地址 | mailto:[email protected] |
ruf | 取证报告发送地址 | mailto:[email protected] |
adkim | DKIM 对齐模式 | s(严格), r(宽松) |
aspf | SPF 对齐模式 | s(严格), r(宽松) |
策略级别:
| 策略 | 说明 | 用途 |
|---|---|---|
p=none | 只监控 | 收集报告,了解现状 |
p=quarantine | 隔离 | 将失败邮件放入垃圾邮件文件夹 |
p=reject | 拒绝 | 直接拒绝失败的邮件 |
部署步骤:
- 配置 SPF:确保合法的发送服务器都在 SPF 记录中
- 配置 DKIM:为邮件服务器配置 DKIM 签名
- 监控模式:设置
p=none,收集报告,了解验证情况 - 调整:根据报告调整 SPF 和 DKIM 配置
- 隔离模式:设置
p=quarantine,观察效果 - 拒绝模式:设置
p=reject,完全启用 DMARC
DMARC 报告示例:
<feedback>
<report_metadata>
<org_name>google.com</org_name>
<email>[email protected]</email>
</report_metadata>
<policy_published>
<domain>example.com</domain>
<adkim>r</adkim>
<aspf>r</aspf>
<p>reject</p>
<pct>100</pct>
</policy_published>
<record>
<row>
<source_ip>192.0.2.100</source_ip>
<count>10</count>
<policy_evaluated>
<disposition>none</disposition>
<dkim>pass</dkim>
<spf>pass</spf>
</policy_evaluated>
</row>
</record>
</feedback>
三者协同工作
邮件到达
│
▼
┌─────────────────┐
│ SPF 检查 │
│ 发送服务器是否 │
│ 被授权? │
└────────┬────────┘
│
┌──────────┴──────────┐
│ │
▼ ▼
[通过] [失败]
│ │
│ ▼
│ 记录结果
│ │
▼ │
┌─────────────────┐ │
│ DKIM 检查 │ │
│ 签名是否有效? │ │
└────────┬────────┘ │
│ │
┌────────┴────────┐ │
│ │ │
▼ ▼ │
[通过] [失败] │
│ │ │
│ ▼ │
│ 记录结果 │
│ │ │
└────────┬────────┘ │
│ │
▼ │
┌─────────────────┐ │
│ DMARC 检查 │◄──────────┘
│ 根据策略决定 │
│ 如何处理 │
└────────┬────────┘
│
┌────────┴────────┐
│ │
▼ ▼
[通过] [隔离/拒绝]
│ │
▼ ▼
正常投递 垃圾邮件/退回
邮件服务器配置实践
Postfix 基本配置
Postfix 是最流行的开源 MTA 之一。
主配置文件 /etc/postfix/main.cf:
# 基本设置
myhostname = mail.example.com
mydomain = example.com
myorigin = $mydomain
inet_interfaces = all
inet_protocols = ipv4
# 邮件存储
mail_location = maildir:/var/mail/vhosts/%d/%n
# 认证设置
smtpd_sasl_auth_enable = yes
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
# TLS 设置
smtpd_tls_cert_file = /etc/ssl/certs/mail.example.com.crt
smtpd_tls_key_file = /etc/ssl/private/mail.example.com.key
smtpd_tls_security_level = may
smtp_tls_security_level = may
# 限制设置
smtpd_recipient_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_unauth_destination,
reject_non_fqdn_recipient,
reject_unknown_recipient_domain
# SPF 检查(需要安装 postfix-policyd-spf-python)
policyd-spf_time_limit = 3600s
smtpd_recipient_restrictions =
...,
check_policy_service unix:private/policyd-spf
# DKIM 签名(需要安装 opendkim)
milter_default_action = accept
milter_protocol = 6
smtpd_milters = inet:localhost:8891
non_smtpd_milters = inet:localhost:8891
Dovecot 配置
Dovecot 是流行的 IMAP/POP3 服务器。
主配置文件 /etc/dovecot/dovecot.conf:
# 启用的协议
protocols = imap pop3
# 监听地址
listen = *, ::
# 认证设置
auth_mechanisms = plain login
# 邮件存储
mail_location = maildir:/var/mail/vhosts/%d/%n
mail_privileged_group = mail
# SSL 设置
ssl = required
ssl_cert = </etc/ssl/certs/mail.example.com.crt
ssl_key = </etc/ssl/private/mail.example.com.key
ssl_protocols = !SSLv2 !SSLv3
ssl_cipher_list = HIGH:!aNULL:!MD5
# IMAP 设置
service imap-login {
inet_listener imap {
port = 143
}
inet_listener imaps {
port = 993
ssl = yes
}
}
# POP3 设置
service pop3-login {
inet_listener pop3 {
port = 110
}
inet_listener pop3s {
port = 995
ssl = yes
}
}
# 认证服务
service auth {
unix_listener /var/spool/postfix/private/auth {
mode = 0666
user = postfix
group = postfix
}
}
邮件故障排查
常见问题诊断
1. 邮件发送失败
# 检查 SMTP 服务状态
systemctl status postfix
# 查看邮件队列
postqueue -p
# 强制发送队列中的邮件
postqueue -f
# 查看日志
tail -f /var/log/mail.log
# 测试 SMTP 连接
telnet mail.example.com 25
2. 邮件被标记为垃圾邮件
检查项:
- SPF 记录是否正确配置
- DKIM 签名是否正常
- DMARC 策略是否设置
- 服务器 IP 是否在黑名单中
# 检查 SPF
dig txt example.com
# 检查 DKIM
dig txt default._domainkey.example.com
# 检查 DMARC
dig txt _dmarc.example.com
# 检查 IP 是否在黑名单
# 使用在线工具如: https://mxtoolbox.com/blacklists.aspx
3. 无法接收邮件
# 检查端口是否开放
netstat -tlnp | grep -E '25|143|110|993|995'
# 检查防火墙
iptables -L -n
# 检查 DNS MX 记录
dig mx example.com
# 检查邮件日志
grep -i "reject\|error\|fail" /var/log/mail.log
日志分析
Postfix 日志示例:
# 正常发送
postfix/smtp[12345]: ABC123: to=<[email protected]>, relay=mail.example.org[192.0.2.1]:25, delay=1.2, delays=0.1/0.1/0.5/0.5, dsn=2.0.0, status=sent (250 OK)
# 被拒绝
postfix/smtp[12345]: ABC123: to=<[email protected]>, relay=none, delay=30, delays=0.1/0.1/30/0, dsn=4.4.1, status=deferred (connect to mail.example.org[192.0.2.1]:25: Connection timed out)
调试技巧
# 启用详细日志
postconf -e "debug_peer_list = example.org"
postconf -e "debug_peer_level = 3"
postfix reload
# 模拟发送邮件
sendmail -v [email protected] < test_email.txt
# 检查配置
postfix check
总结
电子邮件协议是互联网基础设施的重要组成部分:
SMTP:负责邮件的发送和传输,是邮件系统的"发件箱"。
- 使用端口 25/587/465
- 三阶段会话:连接建立、邮件传输、连接关闭
- 支持 STARTTLS 加密和 SASL 认证
POP3:简单的邮件获取协议,适合单设备使用。
- 使用端口 110/995
- 下载并删除模式
- 功能简单,适合存档
IMAP:功能强大的邮件管理协议,适合多设备同步。
- 使用端口 143/993
- 服务器端存储和同步
- 支持文件夹、标志、搜索
邮件安全:SPF、DKIM、DMARC 共同防止邮件伪造和钓鱼。
- SPF:验证发送服务器授权
- DKIM:验证邮件签名
- DMARC:定义验证失败后的处理策略
正确配置邮件服务器和安全机制,是保证邮件可靠送达和防范垃圾邮件的基础。
[!TIP] 想了解其他应用层协议?请看 DNS 域名系统、HTTP 协议详解 和 SSH、FTP 与 SFTP。
练习
- 描述 SMTP 邮件发送的完整会话过程
- 比较 POP3 和 IMAP 的主要区别及各自适用场景
- 解释 SPF、DKIM、DMARC 三者的关系和工作原理
- 使用 telnet 手动发送一封测试邮件
- 配置一个简单的邮件服务器并测试邮件收发