当前位置:Linux教程 - Linux - 快速邮件传输协议QMTP

快速邮件传输协议QMTP

原作者:D. J. Bernstein, [email protected]
译者:[email protected]


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: <[email protected]>"" <0a>
""From: [email protected]"" <0a>
""To: [email protected] (D. J. Bernstein)"" <0a>
<0a>
""This is a test."" <0a> "",""
""24:"" ""[email protected]"" "",""
""30:"" ""26:[email protected],"" "",""

""356:"" <0d>
""From: [email protected]"" <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 [email protected],""
""36:[email protected],"" "",""

服务端接收该消息的发送请求,并作应答:

""21:Kok 838640135 qp 1390,""
""21:Kok 838640135 qp 1391,""
""21:Kok 838640135 qp 1391,""

客户端关闭连接。