SMTP協議解讀以及如何使用SMTP協議傳送電子郵件

一隻會鏟史的貓發表於2022-04-20

電子郵件協議中POP3協議用於接收郵件,SMTP協議用於傳送郵件。SMTP的全稱為Simple Mail Transfer Protocol,也就是簡單郵件傳輸協議,字如其名。
 
相較於POP3而言,SMTP確實比較簡單。這裡的簡單並不是指SMTP的命令比POP3少,而是指SMTP的命令是有序的,而POP3的命令是無序的,理解這一點很重要。也就是說SMTP的命令是要組合在一起才能完成一次郵件傳送任務,單獨呼叫每個命令的意義不大。POP3命令則不同,LIST、STAT、UIDL、TOP、RETR、DELE等命令都可以獨立使用,比如用LIST命令檢視郵件清單,然後用RETR命令接收郵件。
 
簡單的另一層含義是:就socket程式設計而言實現傳送資料要比實現接收資料簡單點。
比如接收資料時要判斷資料是否接收完畢。如果一條資料以回車換行結束,就需要判斷是否接收到了"\r\n",從而確保讀取到一條完整的訊息體。而傳送資料則不需要考慮上述問題,你可以按照自己的節奏傳送資料,可以一次將整個訊息體傳送出去,也可以不用考慮伺服器的死活一個位元組一個位元組傳送資料,直至將整條訊息傳送完畢。
換句話說,接收資料要以流的方式進行,而不是簡單的開闢一個緩衝區,進行一次recv操作。 雖然大部分情況下這種方式也沒有問題,比如寫個Demo程式,但如果要讓你的網路程式非常健壯的話,最好以流的方式進行讀取。因為並不是每次對方都會按照你期望的方式傳送資料給你,比如,你開闢了1024位元組緩衝區用於接收網路資料,但對方可能一次只給你傳送一個位元組,或者發出了1025個位元組。
 
SMTP和HTTP協議一樣都屬於請求應答式協議,也就是一問一答,客戶端傳送命令後,伺服器返回響應內容。 SMTP的響應格式和HTTP協議的基本一樣,都是響應碼+響應描述。響應碼用三位數字表示,空格後則是響應資訊的描述,只是HTTP協議會多一個版本資訊。

這種一問一答式協議,在HTTP協議上體現的並不是很明顯,只有HTTP連線設定為Keep-Alive時,你才有機會使用GET或POST命令反覆與伺服器進行互動,否則只有一次問答的機會。

但在SMTP協議下這種一問一答的互動方式就非常明顯了。 主要原因是完成一次郵件的傳送任務涉及到的步驟比較多,我把電子郵件的傳送分為如下五個步驟:

1、建立會話;
2、身份認證;
3、傳送郵件信封(發件人和收件人);
4、傳送郵件內容(郵件正文和附件);
5、關閉會話

 

 
SMTP的命令主要就分佈在這五個步驟中。下面以網易的yeah郵箱(smtp.yeah.net伺服器)為例,具體說明這五個步驟的實現。C代表客戶端,S代表服務端
 

一、建立會話

SMTP命令:HELO
該階段用於建立客戶端與SMTP伺服器的連線,在此基礎上,雙方進行友好的問候。SMTP伺服器的預設埠號是25,如果是支援SSL協議,則預設埠號是465。如果採用的是STARTTLS協議,則預設埠是587,所謂的STARTSSL其實就是SSL協議,只是開始會話前雙方客套一下,問一下對方你還支援SSL啊?

連線建立後,伺服器會傳送一條歡迎語。接著你就需要問候一下伺服器,並帶上你的機器的名稱。如下:

S: 220 yeah.net Anti-spam GT for Coremail System (yeah[20141016])
C: HELO your-computer-name
S: 250 OK

 

二、身份認證

SMTP命令:AUTH LOGIN
該命令用於進行身份驗證,雖然這一步在SMTP協議中不是強制的要求,但目前幾乎所有的SMTP伺服器都需要進行身份認證。增加這一步可以大大減少垃圾郵件的存在,以及避免有人偽造其它發件人進行郵件的傳送操作。
這一步中賬號和密碼需要進行base64編碼,包括伺服器發來的提示資訊也是base64編碼。
首先傳送AUTH LOGIN命令,伺服器會返回“334 XNlcm5hbWU6”,“dXNlcm5hbWU6”解碼後為“username:”
UGFzc3dvcmQ6解碼為"Password:"
也就是提示使用者輸入使用者名稱和密碼。認證成功後返回235注意返回的不是二百五(250)哦。 接著根據伺服器返回的提示,傳送賬號(發件人的郵箱賬號)和密碼。

C: AUTH LOGIN
S: 334 dXNlcm5hbWU6
C: base64編碼後的賬號(發件人的郵箱賬號)
S: 334 UGFzc3dvcmQ6
C: base64編碼後的密碼
S: 235 Authentication successful

至於為何是base64編碼,可能是SMTP協議設計時考慮到使用者名稱和密碼的重要性,所採用的最簡單的“加密”手段。雖然base64只是編碼方式,不是加密方式,但在早期控制檯輸入命令的情況下,別人還是一下無法像記住明文一樣記住這些無規律的base64編碼。不過隨著SSL的應用,這些都已不重要了。
 

三、傳送郵件信封

SMTP命令:MAIL FROM、RCPT TO
該階段是告訴伺服器發件人和收件人的郵箱地址,可以把這個階段想象為你在寫紙質信件的信封。MAIL FROM用於指定發件人郵箱,該郵箱地址其實就是上述身份認證中的賬號,如:
MAIL FROM: <lig4961@yeah.net>
RCPT TO用於指定收件人郵箱,一次只能指定一個收件人地址,如果收件人有多個的話,可以多次傳送RCPT TO命令。

C: MAIL FROM: <lig4961@yeah.net>
S: 250 Mail OK
C: RCPT TO: <syfzxm@163.com>
S: 250 Mail OK
C: RCPT TO: <lig4961@yeah.net>
S: 250 Mail OK

注意,郵件地址要用放入<>中,此外,每條命令傳送完畢後,一定要判斷伺服器返回碼是否是250。 如果返回的不是二百五,說明你傳送的地址可能是二百五,也就是不正確的地址,比如郵件地址中沒有@,或者在同一個郵箱系統中,SMTP伺服器發現收件人的地址並不存在,也就是沒有註冊過。

看到這裡可能有人會有疑問:我們通過郵件客戶端或網頁寫郵件時,不是有三種身份的收件人麼?即:主送人(to)、抄送人(cc)、密送人(bcc)。是否存在RCPT CC和RCPT BCC命令,用於傳送抄送人和密送人的郵箱地址呢?很遺憾,沒有這兩個命令。也就是說抄送人(cc)和密送人(bcc),也是通過RCPT TO命令進行傳送。
 
既然傳送時不區分,那麼我們在收到的郵件中怎麼還能看到主送人和抄送人呢?或者說如何做到讓密送人在收到的郵件中看不見的。答案在下面郵件內容中。
 

四、傳送郵件內容

SMTP命令:DATA

這一步是傳送資料最多也是最複雜的一步,但操作命令卻只有一個,就是DATA,也就是資料(郵件內容),郵件內容主要包括三個部分(可能會有內嵌資原始檔,也可以理解為狹義上的附件):

1、郵件頭;
2、郵件正文;
3、郵件附件;

DATA命令傳送後,伺服器會返回354響應碼,並告訴客戶端,資料結束要以"\r\n.\r\n"來標識。接下來客戶端就可以傳送整個郵件內容了。

DATA
354 End data with <CR><LF>.<CR><LF>
SUBJECT: =?UTF-8?B?5p2l6IeqU29mdGxlZe+8jOi/meaYr+S4gOWwgea1i+ivlemCruS7tg==?=
FROM: <lig4961@yeah.net>
TO: 'softlee1' <syfzxm@163.com>, 'softlee2' <lig4961@yeah.net>
MIME-Version: 1.0
Content-Type: multipart/mixed;
        boundary="=NextPart_SOFTLEE_Mail_E0B1A829CB1D4f55A037AE04B6A72078"

--=NextPart_SOFTLEE_Mail_E0B1A829CB1D4f55A037AE04B6A72078
Content-Type: text/html; charset=utf-8
Content-Transfer-Encoding: base64

PCFET0NUWVBFIGh0bWwgUFVCTElDICItLy9XM0MvL0RURCBYSFRNTCAxLjAgVHJhbnNpdGlvbmFs
Ly9FTiIgImh0dHA6Ly93d3cudzMub3JnL1RSL3hodG1sMS9EVEQveGh0bWwxLXRyYW5zaXRpb25h
bC5kdGQiPg0KPGh0bWwgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiPg0KDQo8
aGVhZD4NCiAgICA8bWV0YSBodHRwLWVxdWl2PSJDb250ZW50LVR5cGUiIGNvbnRlbnQ9InRleHQv

郵件內容的格式目前基本都採用MIME格式,MIME格式比較簡單,可參見文章:《如何解析EML(郵件)格式的檔案以及一款小巧的EML郵件閱讀工具》。
 
這裡我們不具體介紹如何編碼郵件正文和附件。主要介紹郵件頭中的資訊,主題(Subject)、發件人(From)、收件人(To)、抄送(Cc)。我們看檢視郵件時,讀到的主題、收件人和抄送人就來自於上述欄位。這裡收件人和抄送人僅作為郵件頭的一部分進行展現,伺服器並不會關心這些地址是否真實存在,或者說伺服器並不關心這些地址是否跟使用RCPT TO命令傳送的地址保持一致。回到第三階段中最後的幾個問題,我們可以通過郵件頭來展現該郵件的抄送人是誰,並且將密送人隱藏掉。當然你也可以篡改上述資訊,比如隱瞞某些收件人,或者將密送人也一併展現。
 
郵件正文和附件的編碼可參照MIME格式的文章。最後所有資料傳送完畢後,一定要傳送"\r\n.\r\n",從而告訴SMTP伺服器所有資料傳送完畢。
 

五、會話結束

SMTP命令:QUIT
這一步非常簡單,就是傳送一條QUIT命令,QUIT命令傳送完畢後,還是要判斷伺服器的返回碼是否為250。如果返回的不是,則表明傳送失敗,SMTP伺服器可能不會將郵件轉發到收件人所在的郵箱中,這一點很重要。

至此,整個SMTP協議介紹完畢。SMTP協議比較簡單,但具體的實現細節可能還有很多,需要在實踐中去體驗,有的伺服器返回訊息體是多行的,比如outlook郵箱的伺服器,下面是outlook郵箱使用STARTTLS協議截圖:
 

 

附一: SMTP郵件傳送工具
該工具特點:
1、基於命令列方式且只有一個獨立檔案;
2、支援SSL、STARTSSL協議;
3、具有豐富的命令列引數;

附二: 電子郵件相關文章和工具
POP3協議(電子郵件郵局協議)中UIDL和TOP命令在實際使用中的作用
POP3:基於命令列的電子郵件(EMail)線上檢視和批量下載工具
EmlParse:一款超輕量級的批量解析EML格式電子郵件的工具

相關文章