摘要

  • RFC 1425 用 EHLO 为 SMTP 扩展建立共同的发现入口。成功答复只描述当前会话的能力,客户端被明确禁止把结果缓存到下一次会话。
  • 规范给旧服务器留出了整洁退路:拒绝 EHLO、维持连接状态、允许 HELO。RFC 1651 后来却记录了会直接断线,或在拒绝新问候后又拒绝旧问候的实现。
  • 已登记的扩展、一次会话里的声明、可执行的回退、具体命令被接受、邮件交易完成与最终投递是不同事实,任何一层都不能替下一层作证。

1993 年,电子邮件面对的不是“要不要变化”,而是怎样变化才不要求全世界在同一天升级。

SMTP 的生命力来自简洁。客户端建立连接,收到问候,用 HELO 报上来处,再递交信封与正文。新的传输需求不断出现;若每项能力都自造一套私语,公共协议很快就会碎成方言。

RFC 1425 没有预先规定未来所有功能。它只铺了一层很薄的共同地面:扩展客户端以 EHLO 开始会话,服务器用多行答复列出自己支持的已登记关键词与参数,双方若选择其中一项,再遵循那项扩展自己的 RFC。

这是一套命名、一种语法和一次当场声明,不是全网升级令。

一次答复只属于一次会话

RFC 1425 对时间边界写得极清楚。客户端若需要扩展设施信息,就必须在每个 SMTP 会话开始时发送 EHLO;一次成功答复里的信息不得缓存。

旧观察不能被升格为新事实。同一个主机名背后可能换了进程、配置、路径或维护状态。即使运营者认为“一切都没变”,协议仍要求服务器在本次传输通道内重新作出声明。

成功的 EHLO 使用 250。它同时标记一个明确状态:客户端与服务器都位于初始状态,没有邮件交易正在进行,状态表和缓冲区已经清空。随后,多行答复才能列出关键词。关键词不区分大小写;不以 X 开头的公共关键词必须对应登记扩展,而当年的本地双边扩展使用 X 前缀。

列表不是工作完成报告。它只回答“这台服务器在这个会话里宣布可以做什么”。客户端还需选择某项能力,按其独立规范构造命令或参数,并得到针对那项操作的答复。

RFC 1426 提供了清楚例子:先在 EHLO 答复中看到 8BITMIME,再于 MAIL FROM 中提交 BODY=8BITMIME;扩展命令被接受之后,才轮到 DATA。具体的八位内容规则属于 RFC 1426,本篇不复述;但它证明“看见能力”和“执行能力”从来不是同一条记录。

纸面回退依赖没有被写在箭头上的状态

新客户端必须能与只懂 RFC 821 的旧服务器相处。RFC 1425 设想的场景很平滑:旧端不认识 EHLO,返回错误,却继续保持通道;客户端重置或改发 HELO,便可按旧 SMTP 继续。

这是一种克制的兼容设计。新方探测新词汇,旧方不必因此实现新功能;拒绝也不等于违规。双方可以落回共同拥有的较小规则集。

然而,那条箭头暗含了多个执行条件:服务器必须不关连接;遇到未知命令后,其内部状态仍须有效;接下来的 RSET 或 HELO 必须在规范预期的状态中被解释。

这些不是语法图的属性,而是运行事实。

下一版 RFC 把现场写回了规范

1994 年 7 月,RFC 1651 取代 RFC 1425。扩展框架大体延续,却新增了一节“不正确实现的服务器”。修订真正有价值的地方,就藏在这节的存在本身。

当时已知有服务器在收到 EHLO 时关闭 SMTP 传输通道。关闭可能发生在答复之前,也可能发生在答复之后。RFC 821 原本要求通常只在 QUIT 后断开,所以这不是规范新认可的自由;但再重复一次“合规服务器应该怎样”并不能让邮件送达。

客户端必须观察连接本身。若通道关闭,就要判断任务能否不用任何扩展完成;若可以,则另开一条连接,在新会话里使用 HELO。这不是原会话的自然延续,而是具有新问候、新状态和新结果的第二次尝试。

另一类服务器不关连接,却在拒绝 EHLO 后也不接受 HELO。有时可先发 RSET 再发 HELO。修复又带来一个异常:不少实现会以 503 Bad sequence of commands 拒绝这次 RSET;RFC 1651 允许客户端在这个狭窄恢复场景中忽略它。

1995 年 11 月取代 RFC 1651 的 RFC 1869 保留了这段运行警告。现场失配不再是外部轶事,而成为标准自己的历史记忆。

这并不证明规范无用。恰恰相反,修订做了健康规范应做的事:吸收部署证据,不把初版设计自动当成已实现现实。运行代码反过来约束了文字。

“回退成功”其实是一串关系

把全部过程记成“ESMTP 不支持,SMTP 回退成功”很方便,也很危险。

连接可能建立;220 可能到达;客户端可能发出 EHLO;服务器可能答复,也可能在答复前后关线。若通道还在,RSET 可能成功、失败或被忽略;随后 HELO 才可能被接受。再往后还有发件人、收件人、DATA、中继责任、下一跳和最终投递。

每条记录都有自己的时间和身份。第二条连接不能悄悄覆盖第一条。重试中的 HELO 成功,不会让先前断开的通道重新变得连续。出现某个关键词,不证明其扩展命令必然成功。服务器在 DATA 后返回肯定答复,也不证明收件人读到了内容。

RFC 1425 的禁止缓存规则因此不只是一条实现细节。能力从未被设计成资产清单里附着在主机上的永久属性。它是一个会话里的声明;通道一旦结束,能力就失去赋予它意义的上下文。

薄扩展层没有消灭成本,只改变了承担者

RFC 1425 警告:选项少的协议趋向普及,选项多的协议趋向晦涩。每项扩展都应按实现、部署和互操作成本仔细衡量。它以一个小型发现面管理这种成本,把具体行为留给各自 RFC。

旧服务器无需参加统一升级,代价则更多落到追求广泛可达性的客户端。它必须解析多行答复,区别错误路径,丢弃过期能力,并应付那些仅因听见发现命令便进入错误状态的对端。

登记机构只能给关键词共同含义,不能使代码自动部署。服务器本地决定运行哪些扩展,客户端本地决定采用哪一条已声明路径;若回退状态机真实存在,新旧双方就在较小共同语言上继续。

历史重量全落在“真实存在”四个字上。文档能定义合法转换,只有观察到的会话才能证明两端确实完成了它。