# 自然人眼中的 Email 通訊協定技術 因為跨部門的合作中,需要程式自動化從公司信箱寄出,因此展開了這個篇幅。最後是用 Python 實做。 會牽扯到的協定分別是**LDAP、SMTP、IMAP、POP3**,每個擔任的角色不同,我實做的部份會多著墨在寄信 SMTP 以及認證方式 NTLM 上。 --- ## 四大通訊協定 先簡單了解背景知識如下: | 協定 | 全名 | 功能角色 | 常用 Port | 傳輸對象 | 協定類型 | |-----|------|--------|----------|---------|---------| | **LDAP** | Lightweight Directory Access Protocol | 驗證 & 目錄查詢 | 389(LDAP)、636(LDAPS) | LDAP Server | Directory Access | | **SMTP** | Simple Mail Transfer Protocol | 郵件傳送(寄信) | 25、587(STARTTLS)、465(SMTPS) | Mail Server(MTA) → MTA | Push Protocol | | **IMAP** | Internet Message Access Protocol | 郵件存取(收信),多裝置同步信件 | 143(IMAP)、993(IMAPS) | Mail Server | Pull Protocol | | **POP3** | Post Office Protocol v3 | 郵件下載(收信),單一裝置下載後刪除server副本 | 110(POP3)、995(POP3S) | Mail Server | Pull Protocol | ### LDAP 參考 [What is LDAP?](https://teleforum.ethiotelecom.et/blogs/1446/What-is-LDAP) 以及 [鳥哥: 使用 LDAP 統一管理帳號](https://linux.vbird.org/linux_server/rocky9/0240ldap.php)  使用者登入 - Mail Client 傳帳號密碼給系統 - 系統對接 LDAP,使用 Bind 操作驗證身份 ### SMTP 參考 [Email Protocols Explained: SMTP vs POP3 vs IMAP](https://chamaileon.io/resources/email-protocols-explained-smtp-vs-pop3-vs-imap/)  - 使用者透過 Mail Client 編寫並發送郵件 - SMTP Server 接收,查詢對方網域的 MX 記錄並發信 - 常用 MTA:Postfix、Sendmail、Exim ### IMAP/POP3  - Mail Client 透過 IMAP 或 POP3 與 Mail Server 溝通 - IMAP:支援多裝置同步信件與資料夾 - POP3:單一下載信件,預設刪除伺服器副本 ## 動手做 SMTP Email Sender 網路上 SMTP with Python 資源非常多,主要參考 [Sending emails with attachments in Python](https://mailtrap.io/blog/python-send-email-gmail/#Send-email-with-attachments) 以及 [Python Email](https://docs.python.org/3/library/email.html),這邊不多贅述,多說明其中的認證方式尤其是 ntlm。 ## 常見的 SMTP 認證方式 接下來進到重頭戲ㄌ,我們要用 smtp 寄信出去取決於該伺服器使用哪一種認證方式,像是公共網域常見的 gamil 是用 PLAIN / LOGIN,而公司內部網域使用較安全的 ntlm,伺服器不需要知道密碼本身,只需要確認用戶是否「能夠證明知道正確的密碼」。 | 認證方式 | 說明 | | --------- | ---- | | **PLAIN** | 最常見的方式,帳號與密碼以 Base64 編碼傳送,**需搭配 TLS 加密**以防洩漏| | **LOGIN** | 與 PLAIN 類似,但分兩步傳送帳號與密碼,**一樣需要 TLS**| | **CRAM-MD5**| 使用 HMAC-MD5 演算法,回傳加密的驗證資料,也是 Challenge-Response Authentication | | **NTLM** | 微軟自己的 Challenge-Response Authentication,常用於公司 Exchange 環境,**安全但複雜** | ### Challenge-Response Authentication 一種不直接傳密碼,而是用密碼去算一個答案來證明你是本人的登入方式,伺服器會丟 challenge,用戶端會用拿到的密碼算一個答案並 response,伺服器自己也算一次如果答案一樣就登入成功。 | NTLM 封包 | 對應角色 | 說明 | | ---------- | ------------------------- | -------- | | Type 1 | Client 發送的 **negotiate** | 用戶主動說:「我要登入,而且支援 NTLM,這是我能接受的安全參數」| | Type 2 | Server 發送的 **challenge** | 伺服器說:「這是你要解的挑戰(含隨機亂數、domain name 等)」 | | Type 3 | Client 發送的 **authenticate** | 用戶說:「這是我的回應(用密碼 + challenge 算出來的 hash 值)」 | ### 實做 ntlm 認證 不過我們客戶端在 Python 中 smtplib package 並沒有直接提供 ntlm 接口,所以需要另外安裝有大神寫好的第三方 [pyspnego](https://github.com/jborean93/pyspnego) package,而 spnego 是微軟提供的一種 GSS-API 認證機制(先認知到這裡,難QQ)。 不囉唆上程式碼,完整程式碼請至 [github](https://github.com/keepgoing-228/smtpx/blob/main/smtp_email.py) ```python! # create NTLM context context = spnego.client( username=f"{domain}\\{username}" if domain else username, password=password, protocol="ntlm", ) # send AUTH NTLM command by smtplib smtp.docmd("AUTH", "NTLM") # first step: send negotiate message negotiate_token = context.step() negotiate_b64 = base64.b64encode(negotiate_token).decode("ascii") code, challenge_b64 = smtp.docmd("", negotiate_b64) if code != 334: raise Exception(f"NTLM negotiate failed: {code}") ``` 先準備好 ntlm 認證的 context,呼叫 context.step() 產生 NTLM Type 1 token,並把它用 base64 編碼後才能送給 SMTP server,server 回傳 code 跟 NTLM Type 2 base64,此時還不是 token。 code 334 代表已經傳 challenge 並等待你回應。 ```python! # second step: process challenge and authenticate challenge_token = base64.b64decode(challenge_b64) auth_token = context.step(challenge_token) auth_b64 = base64.b64encode(auth_token).decode("ascii") code, response = smtp.docmd("", auth_b64) if code != 235: raise Exception(f"NTLM authentication failed: {code}") ``` 把剛剛拿到的 base64 轉/解碼成 token,再次使用 context.step(challenge_token) 產生 NTLM Type 3 認證回應,並以 base64 再次送出。 ## 結論 經過這次經驗有簡單的 email 輪廓,成功由公司信箱寄信出去,不過因為使用 python 在 OA 機上沒有運行的環境,所以我用 [Nuitka](https://nuitka.net/user-documentation/tutorial-setup-and-build.html) 多做一步成 binary executable program(而且還必須在 Windows 環境下,頭痛),未來要學習用 GO 來避免以後有類似的情節QQ
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up