快速邮件传输协议QMTP
出处:linuxaid.com.cn 作者:D. J. Bernstein, 时间:2004-10-26 15:49:00
原作者:D. J. Bernstein, djb@pobox.com
译者:iamafan@linuxaid.com.cn
1. 简介
快速邮件传输协议QMTP是SMTP协议的替代品。对有相同行结束符协议的主机之间,QMTP去掉了不必要的行结束符扫描。它具有以下特点:自动流水线作业、扁平结构、8位传输、对邮件长度优先声明以及批处理的效率。总之,QMTP被设计成易于实现的协议。
在qmail中,QMTP由 qmail-qmtpd 和 maildir2qmtp 提供支持。在这篇文档中,我们对8位字节的信息表述做如下约定:
1、十六进制表示:本篇约定,一对尖括号“<>”的数值表示十六进制。每两位十六进制表示一个8位字节。比如:
<68 65 6c 6c 6f 20 77 6f 72 6c 64 21>表示一个长度为12的字节序列。
2、字符表示:用一对双引号表示的字节串,比如:
"hello world!"
其实,上面的两个序列表示的是同一个意思。不过要注意,这种约定只在本篇有效,并不体现在协议中。
2. 协议描述
一个QMTP客户通过一个可靠的流式协议连接QMTP服务器,这个流式协议能够保证传送8位的字节流,详细的情况会在第七部分介绍。
协议概述:客户端发送一个或多个包,随后服务器为每个发送包发送应答消息到客户端。
客户端的工作从发送包开始。一个包包含了一些必要得信息:邮件正文、信封的发送者地址、一个或多个接收者地址。具体格式请看第四部分。
当服务端收到客户端发来的所有包之后,就按照客户端发送包的顺序发送一组应答信息。每个应答信息对应包里的一个接受地址。在任何情况下,服务端必须严格按照发送的顺序来应答,即使有两个一样的接受地址也必须如此。详细请看第五部分,关于应答的格式。
在客户端发送完一个包之前,服务端无权对这个包进行应答操作;但是,客户端可以在这段期间关闭与服务端的这次连接,此时,服务端必须把这个未完成的包丢弃。不过,对于已经应答过的包,服务器不必丢弃。
客户端与服务端的通讯是异步的。每发送一个新的包,客户端无需等待服务端对上一个包的应答。在发送一个应答时,服务端不能丢弃正在接收的数据。因此,客户端必须采用某种措施以避免死锁:如果客户端在收到所有的应答之前就发送了一个新包,它必须记得,在接下来的时间内关注这些应答的到来,以借此判断是否重发某些包。当收到大量的发送包时,服务端可以延缓一下,待接收完包后再应答。而没有收到包时,服务端不必等待客户端的发送,直接处理应答操作。
服务端能在任何时候关闭连接,即使是高质量的服务器也会这么做。此时,应答包里并不会指出这个临时错误。
一个QMTP的会话时间不超过1小时,超时将导致服务端或者客户端关闭这个连接。
3. 邮件格式
本篇中, 一个‘八位邮件信息’表示一系列行,每一行都是由0个或者n个字节(8位)组成的字符串。如果一份邮件中没有<0a>这个字符,则称之为‘安全的’。
注意:这里,我们有意把某些操作系统下的文本文件解释成”消息“。比如,在DOS下面,消息就是一个存储在硬盘上的文本文件,它的大体格式如下:
第一行, <0d 0a>,第二行, <0d 0a> ... <0d 0a>, 最后一行
在UNIX下,消息是一个存储在硬盘上的文件:
第一行, <0a>, 第二行, <0a> ... <0a>, 最后一行
注意到上面的两种编码都是不安全的消息。
实际上,通常消息的最后一行都置空。许多现有的有效提法都把最后一行称作”特殊行“而忽略,不管它是否为空。
4. 包格式
一个包包含三个串:A package is the concatenation of three strings:
1、基于8位字节编码的邮件正文
2、邮件发送者地址的编码
3、邮件接收者地址的一组编码。
每份邮件的地址都是一个基于8位字节的字符串。关于地址的详细解释依赖于QMTP的运行环境,而这是本篇讨论范围之外的东西。每个地址被编码成一个纯字符串,一组接收地址被按顺序编码成一组串。
一份邮件按照两种方式编码成字节串:
1、 <0d>, 第一行, <0d 0a>, 第二行, <0d 0a>, 第三行, ..., <0d 0a>, 最后一行
2、 <0a>, 第一行, <0a>, 第二行, <0a>,第三行, ..., <0a>, 最后一行
这样的字节串被依次编码成纯字符串,具体在第六部分讨论。
服务端必须能够处理这几种格式,不能仅仅因为编码格式而拒绝接受邮件的发送任务。
之所以使用上述的编码方式,是为了QMTP协议在DOS或者UNIX下,能够地方便处理这种编码的文本文件,对于它们,只需要简单地拷贝就行了,甚至能直接打印出<0a>和<0d>来。
5. 应答格式
每一条应答消息都按照8位字节编码成净字符串。每个净字符串的第一个字符有以下几种:
“K”:表示允许投递到该邮件接收者。这个标识相当于SMTP应答消息中的250号消息。它符合RFC 1123 规范的可靠性需求。
“Z”:临时错误。客户端在收到这个应答后,要尝试重发邮件。
“D”:永久性错误。
剩余部分作为事件的描述,告诉客户端发生了什么。当使用UTF-2字符集时,有如下要求:
1、描述只能是是可读的,既不包含无法让人理解的字符。
2、不会重复信件的收信地址
3、除了<20>字符之外,不包含其他的控制字符。
然而,这些要求并不是必须的。客户端要有准备接收来自服务端的任何字符。
描述部分的第一位是<20>字符开始,这个位置主要留给将来后续版本的协议使用,而<20>并不是标识描述部分开始的字符。另外,除了在HCMSSC编码中使用了"#",其他任何编码都不会在应答中出现这个字符。
除非服务器能够毫无错误的存储待发邮件,否则,它就有必要接收安全邮件的发送任务。更准确的说:由于安全邮件经过唯一的可逆的算法编码,当客户端发出的一份经过编码的邮件与一封安全邮件 M 匹配,就表明服务端接受这个发送任务,需要将 M 投递到收件人。(对于M至多只有一种可能,因为该算法是可逆的)。服务端不允许删除任何空消息,否则任何空邮件都会被服务器拒绝发送。服务器可以修改不安全的邮件。
6. 净字符串
任何净字符串都以如下的形式表示
[长度]":"[字符串]","
在这里,[字符串]即该串的内容。[长度]表示[字符串]的字符个数,由阿拉伯数值组成,对应的ASCII码是
<30>对应0,<31>对应1,.....<39>对应9
另外,如果长度部分以<30>开头,表示该串为空字符串。
例如:字符串
"hello world!"
在协议中编码成:<31 32 3a 68 65 6c 6c 6f 20 77 6f 72 6c 64 21 2c> 即 "12:hello world!,".
空字符串编码成:
"0:,"
[长度]":"[字符串]"," 这种表达形式叫做净字符串。[字符串]被称作该净字符串的解释。
7. 封装
QMTP运行在TCP协议上,一个QMTP的服务器将监听系统的209端口的TCP连接。
8. 范例
一个客户端打开一个发送连接,发出如下的信息:
"246:" <0a>
"Received: (qmail-queue invoked by uid 0);"
" 29 Jul 1996 09:36:40 -0000" <0a>
"Date: 29 Jul 1996 11:35:35 -0000" <0a>
"Message-ID: <19960729113535.375.qmail@heaven.af.mil>" <0a>
"From: God@heaven.af.mil" <0a>
"To: djb@silverton.berkeley.edu (D. J. Bernstein)" <0a>
<0a>
"This is a test." <0a> ","
"24:" "God-DSN-37@heaven.af.mil" ","
"30:" "26:djb@silverton.berkeley.edu," ","
"356:" <0d>
"From: MAILER-DAEMON@heaven.af.mil" <0d 0a>
"To:" <0d 0a>
" Hate." <22> "The Quoting" <22>
"@SILVERTON.berkeley.edu," <0d 0a>
" " <22> "Backslashes!" <22>
"@silverton.BERKELEY.edu" <0d 0a>
<0d 0a>
"The recipient addresses here could"
" have been encoded in SMTP as" <0d 0a>
"" <0d 0a>
" RCPT TO:" <0d 0a>
" RCPT TO:" <0d 0a>
<0d 0a>
"This ends with a partial last line, right here" ","
"0:" ","
"83:" "39:Hate.The Quoting@silverton.berkeley.edu,"
"36:Backslashes!@silverton.berkeley.EDU," ","
服务端接收该消息的发送请求,并作应答:
"21:Kok 838640135 qp 1390,"
"21:Kok 838640135 qp 1391,"
"21:Kok 838640135 qp 1391," ,