原作者:D. J. Bernstein,
譯者:
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: <>" <0a>
"From: " <0a>
"To: (D. J. Bernstein)" <0a>
<0a>
"This is a test." <0a> ","
"24:" "" ","
"30:" "26:djb@silverton.berkeley.edu," ","
"356:" <0d>
"From: " <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 ,"
"36:Backslashes!@silverton.berkeley.EDU," ","
服務端接收該消息的發送請求,并作應答:
"21:Kok 838640135 qp 1390,"
"21:Kok 838640135 qp 1391,"
"21:Kok 838640135 qp 1391,"
客戶端關閉連接。