摘要

  • RSET 把未完成的邮件变成一项可以丢弃的事务:清除发件人、收件人与邮件数据,却不得因此关闭连接。
  • 这条命令确认的是状态边界,不是投递结果。它不能撤回已经完成的事务,也不会抹掉 TLS、认证身份或整个会话的历史。

一半有效的信封最危险

设想客户端先发出 MAIL FROM,再连续提交两个 RCPT TO。服务器接受第一位收件人,拒绝第二位。应用规则却要求两人必须同时收到;少一人,这封邮件就不该发。

第二次拒绝并不会自动取消第一次接受。服务器此刻仍可能保存着回程路径与一条有效的收件路径。如果客户端继续发送 DATA,它就会把内容交给一个已经不符合业务意图的信封;如果马上再发新的 MAIL,旧事务仍未关闭;如果断开 TCP,虽然状态肯定消失,却连一条健康连接也一起浪费。

RSET 表达的是更小、更准确的动作:废弃当前邮件事务。服务器返回 250 OK 后,双方才有共同证据,确认下一封邮件不会继承上一封的地址。

1982 年的 SMTP 已经需要“忘记”

RFC 821 从一开始就定义了这条命令。接收方必须丢弃已经保存的发件人、收件人和邮件数据,清空相关缓冲区与状态表,并给出成功答复。同一份规范又明确说,一次会话里可以有零个或多个邮件事务。

两条规则互为条件。若一条连接要运送多封邮件,它必须有办法只放弃其中一封;否则“复用”就会变成状态串线的风险。RSET 不是后来添加的异常补丁,而是让持久会话能够安全复用的基本操作。

RFC 821 还要求,前一个命令即使报错,双方也不应随意关闭传输通道。若连接意外中断,接收方取消的是尚未完成的事务,不能反悔已经完成的事务。通道、当前尝试与既有完成结果,从协议诞生时就不是同一个层次。

本地清空不等于远端清空

客户端随时可以删除自己的队列内容,却看不到服务器保存了什么。安全复用依赖远端确认,而不是本机觉得“已经重置”。

RFC 5321 把确认方式写得很清楚:无参数 RSET 必须得到 250 OK;它可以在会话任何时刻发出;如果没有进行中的事务,它基本没有效果;如果有,服务器必须丢弃发件人、收件人与数据。服务器不能因为收到它而关闭连接,关闭通道属于 QUIT。

这里的 250 只表示重置动作完成。它不是上一封邮件的收件回执,也不保证日志、滥用记录或存储介质上的每个字节都被物理擦除。协议保证的是可见状态:旧信封不能继续支配新事务。

该忘掉什么,也要知道该留下什么

“清空所有缓冲区与状态表”听起来像整个会话失忆,但周边规则给出了更窄的范围。

TCP 连接仍在,服务器不重新发送欢迎语。仅仅因为某封邮件作废,客户端不必重新发现全部扩展能力。TLS 不会被取消,已经建立的会话认证也不会消失。限速、审计与反滥用观察可以留下;绝不能留下的是上一事务的发件人、收件人或半段正文。

RFC 5321 说,会话中重新接受一次 EHLO,会像 RSET 一样清除当前事务状态。不过 EHLO 还要完成问候与能力应答,通常成本更高。两者只在“终止当前事务”这一点形式等价,并不意味着它们承担相同的会话职责。

STARTTLS 则展示更大的重置。RFC 3207 要求 TLS 握手成功后,双方丢弃握手前获得的知识,客户端重新发送 EHLO。安全上下文已经改变,因此扩展能力也必须重新确认。普通信封作废没有理由把清理范围扩大到这里。

事务能中止,历史不能回滚

一项邮件事务由 MAIL 开始,经过一个或多个 RCPT,再完成内容传输。RSET 处理的是仍然开放、尚未完成的那一项。最终内容一旦被成功接受,责任已经形成;之后的 reset 只能为下一封信清场,不能把此前的成功改写成失败。

这也是长连接最容易被误解的地方。SMTP 会话不是包住所有邮件的一笔数据库事务。每封完成的邮件拥有各自的结果。连接继续存在,不代表过去的接受记录还能被整体撤销。

因此,监控若只统计 250 而不记录它回答了哪个命令,就会制造假证据。RCPT 的 250、最终数据的 250 与 RSET 的 250,分别对应地址、内容责任与状态清理。

PIPELINING 让边界更近,却没有让它消失

RFC 2920 允许客户端成组发送若干命令,以减少高延迟链路上的等待。RSET、发件人命令和 RCPT TO 都可以出现在组内;上一封邮件的最后一段内容,甚至可以与下一封邮件的 reset 和新发件人共享一次 TCP 发送。

线上的距离缩短了,结果仍必须逐条对应。客户端要分清哪个答复属于旧内容、哪个确认 reset、哪个接受新发件人。任何一个失败,都不能被旁边的成功覆盖。

所以 PIPELINING 要求更严格的状态账本。客户端清空本地缓冲区,只能证明自己准备好了;只有顺序匹配的服务器答复,才能证明对端跨过同一条事务边界。

分块错误后的确定清场

RFC 3030 用 BDAT 分块代替传统 DATA。若在 BDAT LAST 后又发块、在同一事务混用 DATA 与 BDAT,或某个失败块使状态无法确定,规范要求先执行 RSET,再继续其他邮件命令。

此时 reset 的价值很具体:此前属于当前事务的分块不得渗入后续邮件。它没有宣称连接、TLS 或审计记录从未存在,只确认这些残片不再构成待处理消息。

RFC 4954 从另一个方向划界:进行中的邮件事务里不允许执行 AUTH,服务器应返回 503。会话身份不能插进发件人、收件人与内容之间。若身份上下文需要改变,应先把当前事务完成或明确废弃。

可靠恢复不是“一切重来”

协议遇到错误时,最粗糙的选择是装作双方仍然同步,或直接摧毁整条连接。RSET 提供第三条路:准确命名要放弃的状态,由对端确认,再保留仍然有效的会话。

它留下的历史经验是,状态本身并不可怕,边界不清才可怕。SMTP 可以忘掉一封未完成邮件而不忘掉对话,也可以让对话继续而不改写已经完成的邮件。

来源