前言
本文主要是記錄一下在使用 IMAP 協議訪問網易郵箱時遇到的一個很常見的問題以及之后我如何解決並一步一步發現 QQ 郵箱 BUG 的過程。
發現問題
由於工作需要,開發的系統中需要訪問用戶設置的郵箱內的郵件列表。我們選用的方式是采用 IMAP 協議進行讀取。簡化后的代碼如下:
import imaplib client = imaplib.IMAP4_SSL(host="imap.163.com", port=993) client.login("username", "password") client.select()
一般郵箱都沒有問題,但訪問網易郵箱時,select 命令返回的結果是:
# ('NO', [b'SELECT Unsafe Login. Please contact kefu@188.com for help'])
解決問題
原因分析
首先,百度一下很容易就知道答案,此處不贅述。可參考文章最后的引用鏈接
簡述一下就是:
網易郵箱服務器會檢查 IMAP 客戶端有沒有發送 ID 命令來表名自己的身份,如果沒有,則拒絕訪問。
關於 ID 命令,ID 命令的設計初衷是為了讓服務器可以統計不同客戶端的使用情況,但並非強制的。
協議中明確表示,服務器不得以 ID 命令內的信息而對客戶端拒絕提供服務。
詳細內容可參見 RFC2971 的定義:
https://datatracker.ietf.org/doc/html/rfc2971。
解決方案
原因知道之后,解決方案其實很簡單,登陸之后再發送一個ID命令就好了。代碼示例如下:
import imaplib imaplib.Commands["ID"] = "NONAUTH" client = imaplib.IMAP4_SSL(host="imap.163.com", port=993) client.login("username", "password") client._simple_command("ID",'("name" "test" "version" "0.0.1")') client.select()
引申一下
解決方案很簡單,但總感覺不夠完美,少了點啥。
在實際應用中,是需要考慮用戶會訪問不同的郵箱的,那么就需要考慮以下兩個問題:
- 是否有可能某些郵箱服務器不支持 ID 命令?
- 如果郵箱支持 ID 命令,但並不強制使用,會對其他正常功能有影響嗎?
第一個問題,這個我覺得應該是有的,畢竟 ID 並非標准協議中的一部分,有可能某些郵箱服務器開發的比較早並且無人維護了吧.
所以針對這種情況是需要測試 IMAP 對於不認識的命令是如何處理的。
第二個問題,應該是不會有影響的,不過簡單測試一下以求個安心。
於是,我先試了一下 QQ 郵箱。
先說第二個問題的測試結果,QQ 郵箱是沒有啥影響的,這個也不重要,跳過就好。
但第一個問題有點出乎意料,測試方式很簡單,分別向網易郵箱和 QQ 郵箱發送不支持的命令,看一下是否會影響接下來的使用即可。代碼如下:
import imaplib imaplib.Debug = 100 imaplib.Commands["XXX"] = "NONAUTH" host = "imap.163.com" # host = "imap.qq.com" client = imaplib.IMAP4_SSL(host=host, port=993) client._simple_command("XXX", '("name" "aaa")') client.select()
其中網易郵箱在接收到未知命令時,返回結果是:
# b'PMGB1 BAD command not support'
日志如圖

這個符合預期,直接捕獲一下異常即可。
但 QQ 郵箱在接收到未知命令時,返回的結果是:
# b'* BAD Command!'
日志如圖

且代碼在這里直接卡住了,等待幾分鍾后:
# ConnectionResetError: [WinError 10054] 遠程主機強迫關閉了一個現有的連接。
QQ 郵箱服務器主動斷開了跟我的鏈接。
WTF !!!
網易郵箱的拋出異常,我可以直接捕獲跳過即可,對程序主流程無影響。但 QQ 郵箱卡在這里是啥意思???
為了搞清楚這個事情,又開始了對代碼卡住的分析。
代碼分析過程不贅述,各種斷點調試一通,最終發現,代碼卡在了等待郵箱服務器返回數據的部分。
但從調試日志中可以看到,QQ 郵箱服務器已經返回了數據:b'* BAD Command!' 為啥還要等呢?難道數據有問題?
然后又是一通搜索,在 IMAP RFC3501 定義得到了答案,其中 2.2.1 節定義了郵箱服務端的返回數據格式規范:
返回數據的第一部分應該是 tag、*、+ 其中之一。
- tag 是客戶端在發送命令時攜帶的一個唯一標識,主要用於客戶端區分收到的響應是那個命令的響應
- * 代表此命令的相應內容尚未完結,應繼續等待后續響應
- + 代表服務端繼續等待客戶端發送尚未發送完的命令
因此,這里 QQ 郵箱返回的 b'* BAD Command!' 是不對的,應該是 b'tag BAD Command!'。
至此,感覺已經沒有什么可做的事情了。不過,我還是給 QQ 郵箱發了封郵件,簡單描述了一下問題,也不知道他們改不改,等結果吧。。。
然后我的應用代碼改成了:
import imaplib client = imaplib.IMAP4_SSL(host="imap.163.com", port=993) client.login("username", "password") typ, dat = client.select() if typ != "OK": try: client._simple_command("ID", '("name" "test" "version" "0.0.1")') except Exception as e: print(e) typ, dat = client.select() if typ != "OK": raise Exception("郵箱登錄失敗: {} {}".format(typ, dat))
后記
待回復
不相關知識點整理
- POP3 可以認為是只讀的協議,客戶端內的操作不會影響到服務端,只用來下載郵件
- IMAP 是雙向的協議,客戶端的操作會反饋到服務端上,服務端也同步進行操作,不僅可以下載郵件,還可以刪除,移動郵件,但不能發送郵件
- SMTP 是用來發送郵件的
參考文檔
什么是POP3、SMTP及IMAP?:
https://help.mail.163.com/faqDetail.do?code=d7a5dc8471cd0c0e8b4b8f4f8e49998b374173cfe9171305fa1ce630d7f67ac21b87735d7227c217
imap連接提示Unsafe Login,被阻止的收信行為:
https://help.mail.163.com/faqDetail.do?code=d7a5dc8471cd0c0e8b4b8f4f8e49998b374173cfe9171305fa1ce630d7f67ac211b1978002df8b23