SMTP協議解讀以及如何使用SMTP協議發送電子郵件
電子郵件協議中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: <[email protected]>
RCPT TO用於指定收件人郵箱,一次只能指定一個收件人地址,如果收件人有多個的話,可以多次發送RCPT TO命令。
C: MAIL FROM: <[email protected]>
S: 250 Mail OK
C: RCPT TO: <[email protected]>
S: 250 Mail OK
C: RCPT TO: <[email protected]>
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: <[email protected]>
TO: 'softlee1' <[email protected]>, 'softlee2' <[email protected]>
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格式電子郵件的工具》