• 2019年5月11日土曜日
アリスト戦記
アリスト戦記 https://blog.aristo-solutions.net/2019/05/python.html

Pythonでメールを送る時に、本文の宛先と実際に届く宛先が違う

現場でPythonで作ったメール送信プログラムがあるんだが、そこでバグが見つかった。
その内容としては、本文の載っているメールの宛先と、実際に届いているメールの宛先が違うというものだ。

原因を追ってみたので解説しよう。

基本:メールの宛先は偽装可能

まず根本的な基礎知識だけど、メールのあて先は偽装可能である。

以下はThunderbirdの画面キャプチャだけど、普段メールソフトを使ってメールを送信する時、当然ながら宛先に入れたアドレスにメールが飛ぶものと考えているでしょ?


宛先に入れたアドレスにメールが飛ぶのは、あくまでThunderbird、もしくはメールソフトの仕様であって、メール送信そのものを管轄するSMTPの仕様ではない

SMTPの仕様として、宛先の偽装。
つまり、メール本文に表示されている宛先と、実際に送っている宛先を異なるものにすることは可能である。




“メール本体に書かれる宛先や差出人”と“エンベロープに書かれる宛先や差出人”が同一である必要はありません。

メール送信は封筒形式

実際に送る宛先と、本文に書かれている宛先が違うのは仕様」とかって、そんなバカなと思うかもしれない。

しかし、上記の参考サイトも見ると分かるけど、メールの送信は封筒形式と考えると分かり易い。

封筒
封筒


封筒の中身
封筒の中身

封筒に書いている宛先と、その封筒の中に入れるべき手紙の相手を間違えちゃった!!

みたいな。

これと同じで、メール送信は「エンペローブ(封筒)」に書いた宛先に送付するものであって、本文に何が書いてあるかは関係無い。

Pythonのソース

メール送信処理

では、実際にPythonのメール送信ソースを見てみよう。


Pythonにおけるメール送信はPython標準ライブラリ「smtplib」を使用する。

smtplibの中には、メール送信実行メソッドが2つある。

sendmail
def sendmail(self, from_addr, to_addrs, msg, mail_options=(),
                 rcpt_options=()):


send_message
def send_message(self, msg, from_addr=None, to_addrs=None,
                 mail_options=(), rcpt_options=()):


「sendmail」と「send_message」。
何で2つもあるのか?

メソッド「sendmail」

まず、本当にメールを送っているのはメソッド「sendmail」である。
その中に以下のソースがある。

for each in to_addrs:
    (code, resp) = self.rcpt(each, rcpt_options)
    if (code != 250) and (code != 251):
        senderrs[each] = (code, resp)
    if code == 421:
        self.close()
        raise SMTPRecipientsRefused(senderrs)


to_addrsでループしているでしょ?

だからto_addrsに100コ書いていれば100回通信が発生してメールを送るけど、本文に何が書いてあるかは関係無い。

メソッド「send_message」

じゃあ、「send_message」って何だ?
見てみるとこんなソースがある。

if to_addrs is None:
    addr_fields = [f for f in (msg[header_prefix + 'To'],
                               msg[header_prefix + 'Bcc'],
                               msg[header_prefix + 'Cc'])
                   if f is not None]


何かメッセージ本文から宛先を取り出してるな。
変数「msg」の中にはこんな感じで文字が入っているから、ここから「To」を探して取っているんだな。

Content-Type: text/plain; charset="iso-2022-jp"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: =?utf-8?b?MjAxOS0wNS0xMSDjga7kvb/nlKjmg4XloLE=?=
From: aaa@example.com
To: bbb@example.com,bbb@example.com


そして、取得した宛先を使って「sendmail」に渡す。

return self.sendmail(from_addr, to_addrs, flatmsg, mail_options,
                     rcpt_options)

だから、メソッド「send_message」の方は、本文中の宛先を実際の送信先に使用するんだ。

コメントにもそう書いてあるし。

Converts message to a bytestring and passes it to sendmail.
The arguments are as for sendmail, except that msg is an
email.message.Message object. 

結論

smtplibのメソッド「sendmail」はSMTP純正の仕様。
メールの送信先と、本文の送信先は別々に記述して送信するもの。

対して、メソッド「send_message」は、メールの送信先と本文の送信先を別々に管理するなんて面倒という人の為に、Python様がわざわざメール本文から宛先を取得して送信先に使い回すというロジックを組んで下さった便利機能だ。


だから、メールを送信したい場合は潔く「send_message」を使えということだ。


そして、確かに現場のソースは「sendmail」を使っちゃってるな。(;´^ω^`)
次に修正の機会があったら直しておくか。(;´^ω^`)

0 件のコメント:

コメントを投稿