GmailのSMTPサーバーにコマンドを送ってメールを送信する
emailSMTP (Simple Mail Transfer Protocol)
SMTPはメールクライアントからサーバーへ、あるいはサーバー間でメールを送信する際のプロトコル。 受信する際のプロトコルとしては、認証とメールのダウンロード・削除を行うPOP (Post Office Protocol) や、 複数の環境から見ることを想定した、サーバーのメールボックスを管理しアクセスするIMAP (Internet Message Access Protocol) がある。
SMTP標準には認証の仕組みが含まれておらず、以前はPOPの認証が用いられていたが、現在はSMTP-AUTHという拡張が標準化されて用いられている。 SMTPでは通常25番ポートを使うが、スパムメール対策としてプロバイダがOP25B(Outbound Port 25 Blocking)を行うようになったので、 メールクライアントからサーバーへの通信はSMTP-AUTHの認証がかかる587や465番ポートで行われる。これらのポートはサブミッションポートと呼ばれる。
通信はテキストで行われ、HELO example.com
のようなコマンドを送ると250 smtp.gmail.com at your service
のようなリプライコードとメッセージが返ってくる。
telnetでのメール送信(失敗)
telnetでGmailのSMTPサーバーにコマンドを送ってみる。
DNSのMXレコードで返ってきたアドレスに接続しようとしたが、これらはサーバー間のリレー専用のようでサブミッションポートが空いていなかった。
なので接続する先はドキュメントに書いてあるsmtp.gmail.com
。
$ dig gmail.com mx +short
10 alt1.gmail-smtp-in.l.google.com.
20 alt2.gmail-smtp-in.l.google.com.
30 alt3.gmail-smtp-in.l.google.com.
40 alt4.gmail-smtp-in.l.google.com.
5 gmail-smtp-in.l.google.com.
$ telnet alt1.gmail-smtp-in.l.google.com. 587
...
telnet: Unable to connect to remote host
接続が完了すると220(Service ready)とメッセージが送られてきた。
HELO
コマンドでクライアントのドメイン名を送ると250(Requested mail action okay, completed)が返った。
続けてMAIL
コマンドで送信元のアドレスを送ったところSTARTTLS
でTLS接続に切り替えることを要求されたので実行すると
サーバーからレスポンスが返ってきた後、TLSで再接続できずにコネクションが切れてしまった。
telnetはTLSハンドシェイクをサポートしていないのでここまで。
$ telnet smtp.gmail.com 587
...
220 smtp.gmail.com ESMTP ****** - gsmtp
HELO example.com
250 smtp.gmail.com at your service
MAIL FROM: <*****@gmail.com>
530 5.7.0 Must issue a STARTTLS command first. ****** - gsmtp
STARTTLS
220 2.0.0 Ready to start TLS
MAIL FROM: <*****@gmail.com>
Connection closed by foreign host.
opensslでのメール送信
TLS接続できるopensslで再挑戦。今度は認証を要求されたので
AUTH PLAIN base64("\0メールアドレス\0パスワード")
を送ったところ、2FAが有効なためにアプリパスワードでないといけないようなので発行した。
MAIL
とRCPT
コマンドで送信元と送信先を設定しDATA
コマンドを送ると354が返ってくるのでヘッダーと本文を書いて最後に.を送るとメールが送信される。
ヘッダーと本文の間は一行空け、ヘッダーに日本語のような非ASCII文字を含める場合は=?UTF-8?B?<UTF-8をbase64エンコードしたもの>?=
のようなフォーマットにする必要がある。
$ echo -ne '\00*****@gmail.com\00password' | base64
$ openssl s_client -connect smtp.gmail.com:465 -crlf -ign_eof
...
220 smtp.gmail.com ESMTP ****** - gsmtp
HELO example.com
250 smtp.gmail.com at your service
MAIL FROM: <*****@gmail.com>
530-5.7.0 Authentication Required. Learn more at
530 5.7.0 https://support.google.com/mail/?p=WantAuthError ****** - gsmtp
AUTH PLAIN *****
534-5.7.9 Application-specific password required. Learn more at
534 5.7.9 https://support.google.com/mail/?p=InvalidSecondFactor ****** - gsmtp
AUTH PLAIN *****
235 2.7.0 Accepted
MAIL FROM: <*****@gmail.com>
250 2.1.0 OK ****** - gsmtp
RCPT TO: <*****@gmail.com>
250 2.1.5 OK ****** - gsmtp
DATA
354 Go ahead ****** - gsmtp
Subject: testmail
this mail is test
.
250 2.0.0 OK ****** ****** - gsmtp
quit
221 2.0.0 closing connection ****** - gsmtp
read:errno=0
送信されたメールを見るとFromにはアドレスが表示されているが、Toには何も入っていない。これは今回含めていないFromとToヘッダーの値のようで、
FromヘッダーはGmailによって認証に用いたアカウントのものに上書きされるようだ。Fromヘッダーを含めた場合、元々の値はX-Google-Original-From
として送られていた。
また、返信先であるReturn-Path
もMAIL FROM
の値になっていなかった。
ということでGmailのSMTPサーバーで送信する場合、誰かになりすますことはできなさそうだが、そうでなくてもTXTレコードのv=spf1 include:_spf.google.com ~all
のような文字列で送信サーバーが正しいか確認するSPF (Sender Policy Framework)やヘッダーに署名を含めるDKIM (DomainKeys Identified Mail)といった手段で対策されている。
参考
Simple Mail Transfer Protocol - Wikipedia
POP3(Post Office Protocol version 3):インターネット・プロトコル詳説(7) - @IT
How to send email using simple SMTP commands via Gmail? - Stack Overflow