
Perlからメールを送信する必要があったので、Net::SMTPあたりのモジュールを入れて
さくっとスクリプトを書きたかったのだが、今ではどのISPでも当たり前となったOP25Bによって
自宅サーバーからはメールが送れないので、仕方なくGmailのSMTPサーバーを使って
メールを送ることにした。
GmailのSMTPサーバー(ポート587)はSTARTTLSの使用が強制になっている。
「Perl Gmail」でぐぐってみると、Net::SMTP::TLSというCPANにあるモジュールを使えば
よい(STARTTLSでSMTP通信をしてくれる)ことがわかったので、これを使うことにした。
とりあえず何通かThunderbirdとかで適当に作ったメッセージを送ってみると、
FromヘッダのメールアドレスがログインしているGmailのメールアドレスに
書き換えられてしまうが、それ以外は特に問題なく送信できた。
普通のメールは問題なく送れることがわかったので、
次はHTML形式かつ472KBほどの添付ファイルつきメールを送ってみたのだが...
送ったメールを受信側で開くと、同じ添付ファイルとHTMLの文書が
41個存在するとんでもないメールになっていた。
メールのソースを見ると・・・
?!
Base64エンコードされた添付ファイルのパートの途中から、
なぜかメールの先頭にあるはずのヘッダが続いている・・・!
しかもこれがあと40回同じように繰り返し出現する。(そのためメーラーで41個添付ファイルがあるように見えた)
これは・・・Gmailのバグだ!
私は、このとき愚かにもそれを確信した。
なぜそう思ったかというと、Gmailは送信時にFromヘッダを書き換えたりしているから
きっとそこで変なことをしてバグっているんだろう、という安直な考えから。。
当然、送信側(つまりNet::SMTP::TLS)に問題がある可能性もあるわけで
その切り分けすらまだしていないじゃないか。
Gmailのバグとかいうのはまだ早い。
切り分けは簡単で、接続するSMTPサーバーをローカルのPostfixとかに変えて
同じメールを送ってみればよくて、試した結果、同じようにメールが壊れた。。
これで、Net::SMTP::TLS側の問題ということがわかった。
わかった。
とりあえず、既知のバグかどうか調べるため、
https://rt.cpan.org/Public/Dist/Display.html?Name=Net-SMTP-TLS
を見てみた。
まず、すべてステータスが「新規」か「オープン」になっているのを見て
不穏な予感がするが、それっぽいものを探すと、それっぽいものを一つ見つけた。
https://rt.cpan.org/Public/Bug/Display.html?id=63538
書いてある内容は今回の件と同じっぽい。
ありがたいことに、修正する箇所とコードまで書いてある。
試しに、TLS.pmをこのように書き換えてメールを送ってみたところ
受信側でメールは壊れなくなった。
ということで、原因はやはりNet::SMTP::TLSのバグだった・・・。
TLS.pmのdatasend()内、350行目から原因箇所を抜粋。
363行目と369行目に注目。
syswrite()が1回で$lineの内容を全て出力してくれれば問題ないが、
$lineのサイズが大きい場合、1回で全て出力されず、
出力できた分の長さが戻り値として返ってくる場合がある。
そのとき、369行目で$line全体の長さから出力できた長さを引いて、
次のsyswrite()が呼び出されることになるが、
$offsetの値に出力した長さを加算していないため、
毎回$lineの先頭から同じデータが出力される。
こうして、今回の壊れたメールが出来たというわけだ。
試しに、369行目の直前に、printで$wの値を出力してみると
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 9325
のように、41回出力されているのがわかる。
2013年8月現在、 Net-SMTP-TLS-0.12 ではこの問題は未修正で、
使用時には先ほどのパッチを当てる必要がある。
また、他にも未解決のバグがいくつかあり、
長い間メンテナンスされていないようなので、
このモジュールを使う際は気を付けたほうがよい。
というかできるだけ使わないほうがよいかもしれない。
多数の記事で紹介されていて、広く使われていそうなCPANモジュールでも、基本的なバグが残っていることがある
CPANモジュールを使う際は、まず登録されているバグの数とメンテナンスされているかを調べたほうがよい
WindowsのバグとかGoogleのバグとか安易に言わないほうがいよい。だいたい間違っている
ライブラリのバグだ!は割とあるので、使う際にはちゃんとテストをしよう
テストもコードレビューもしてないような糞モジュールをうっかりCPANで公開して放置すると、
異国の糞ブログで晒されたりするので気を付けよう
Google 神 Gmail SMTP メール Perl STARTTLS
CPAN カス Net::SMTP::TLS 糞モジュール Ruby厨 去れ
さくっとスクリプトを書きたかったのだが、今ではどのISPでも当たり前となったOP25Bによって
自宅サーバーからはメールが送れないので、仕方なくGmailのSMTPサーバーを使って
メールを送ることにした。
GmailのSMTPサーバー(ポート587)はSTARTTLSの使用が強制になっている。
$ telnet smtp.gmail.com 587
Trying 74.125.25.108...
Connected to gmail-smtp-msa.l.google.com.
Escape character is '^]'.
220 mx.google.com ESMTP xs1sm2390861pac.7 - gsmtp
HELO kobanza.me
250 mx.google.com at your service
MAIL FROM: ■■■@gmail.com
530 5.7.0 Must issue a STARTTLS command first. xs1sm2390861pac.7 - gsmtp
「Perl Gmail」でぐぐってみると、Net::SMTP::TLSというCPANにあるモジュールを使えば
よい(STARTTLSでSMTP通信をしてくれる)ことがわかったので、これを使うことにした。
とりあえず何通かThunderbirdとかで適当に作ったメッセージを送ってみると、
FromヘッダのメールアドレスがログインしているGmailのメールアドレスに
書き換えられてしまうが、それ以外は特に問題なく送信できた。
普通のメールは問題なく送れることがわかったので、
次はHTML形式かつ472KBほどの添付ファイルつきメールを送ってみたのだが...
送ったメールを受信側で開くと、同じ添付ファイルとHTMLの文書が
41個存在するとんでもないメールになっていた。
メールのソースを見ると・・・
QRbmd/uXuvuayOzrL1y6dh2leYdPnkZWe/PO3VOoIZ9aj6KBvtXDnf0rkeZp7OhGzAo/PAK2
i9YM2M3fHNjw2QiME7NxcimCzy6jwHgBRMCmwHguKSWnMDkbKkjKImYnZuYlZOSB2fE4n6YT
s2PTsh2pYDaAzU3204Et0prHxhmwk8TA+NfGxu2c1u7ABq3nAPuPBmMDXLv5/wbhdbr0DcYi
T4xj5lKcPxsGQPhgMSpCYAPMVppRIUipCo0NsRAwG+WEydHxxGyG7VTIRet0c/KMLBi0IUWC
ARwdFRjJuZSaZWOyY5znkgOHUBqAjcNpRnEYF3JlkuKz8kQl0NiRlIhxpJtEYCflFELJuUXu
4sxm2J6BtGu9xJlPeiqzsTENY1mmJyBdnl70FGUUVwjCgBgj4zJSdkWVqJzKaq7cqpo8qLoW
yodq6gqg2vReturn-Path: <■■■@□□□□□□>
X-Original-To: ■■■@kobanza.me
Delivered-To: ■■■@kobanza.me
Received: from smtpa25.m3.home.ne.jp (smtpa25.m3.home.ne.jp [220.152.44.32])
by kobanza.me (unknown) with ESMTP id 89B76B9D1B
for <■■■@kobanza.me>; Thu, 15 Aug 2013 22:08:13 +0900 (JST)
(メールアドレスを一部伏せています)
?!
Base64エンコードされた添付ファイルのパートの途中から、
なぜかメールの先頭にあるはずのヘッダが続いている・・・!
しかもこれがあと40回同じように繰り返し出現する。(そのためメーラーで41個添付ファイルがあるように見えた)
これは・・・Gmailのバグだ!
私は、このとき愚かにもそれを確信した。
なぜそう思ったかというと、Gmailは送信時にFromヘッダを書き換えたりしているから
きっとそこで変なことをしてバグっているんだろう、という安直な考えから。。
当然、送信側(つまりNet::SMTP::TLS)に問題がある可能性もあるわけで
その切り分けすらまだしていないじゃないか。
Gmailのバグとかいうのはまだ早い。
切り分けは簡単で、接続するSMTPサーバーをローカルのPostfixとかに変えて
同じメールを送ってみればよくて、試した結果、同じようにメールが壊れた。。
これで、Net::SMTP::TLS側の問題ということがわかった。
わかった。
とりあえず、既知のバグかどうか調べるため、
https://rt.cpan.org/Public/Dist/Display.html?Name=Net-SMTP-TLS
を見てみた。
まず、すべてステータスが「新規」か「オープン」になっているのを見て
不穏な予感がするが、それっぽいものを探すと、それっぽいものを一つ見つけた。
https://rt.cpan.org/Public/Bug/Display.html?id=63538
When I try to send an e-mail with multiple attachments through
Net::SMTP::TLS, the attachments get corrupted.
書いてある内容は今回の件と同じっぽい。
ありがたいことに、修正する箇所とコードまで書いてある。
The error can be resolved by adding a single line to TLS.pm (by analogy
with NET::SMTP) -- the error is in the "datasend" subroutine.
Just after line 369 ($len -= $w;) add a new line:
$offset += $w;
試しに、TLS.pmをこのように書き換えてメールを送ってみたところ
受信側でメールは壊れなくなった。
ということで、原因はやはりNet::SMTP::TLSのバグだった・・・。
以下、バグの詳解
TLS.pmのdatasend()内、350行目から原因箇所を抜粋。
350 my $len = length($line);
351 my $offset = 0;
352 my $win = "";
353 vec($win,fileno($cmd->{sock}),1) = 1;
354 my $timeout = $cmd->{sock}->timeout || undef;
355
356 local $SIG{PIPE} = 'IGNORE' unless $^O eq 'MacOS';
357
358 while($len)
359 {
360 my $wout;
361 if (select(undef,$wout=$win, undef, $timeout) > 0 or -f $cmd->{sock}) # -f for testing on win32
362 {
363 my $w = syswrite($cmd->{sock}, $line, $len, $offset);
364 unless (defined($w))
365 {
366 carp("Error: $!");
367 return undef;
368 }
369 $len -= $w;
370 }
371 else
372 {
373 carp("Error: Timeout");
374 return undef;
375 }
376 }
377
378 }
363行目と369行目に注目。
syswrite()が1回で$lineの内容を全て出力してくれれば問題ないが、
$lineのサイズが大きい場合、1回で全て出力されず、
出力できた分の長さが戻り値として返ってくる場合がある。
そのとき、369行目で$line全体の長さから出力できた長さを引いて、
次のsyswrite()が呼び出されることになるが、
$offsetの値に出力した長さを加算していないため、
毎回$lineの先頭から同じデータが出力される。
こうして、今回の壊れたメールが出来たというわけだ。
試しに、369行目の直前に、printで$wの値を出力してみると
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 16384
w: 9325
のように、41回出力されているのがわかる。
2013年8月現在、 Net-SMTP-TLS-0.12 ではこの問題は未修正で、
使用時には先ほどのパッチを当てる必要がある。
また、他にも未解決のバグがいくつかあり、
長い間メンテナンスされていないようなので、
このモジュールを使う際は気を付けたほうがよい。
というかできるだけ使わないほうがよいかもしれない。
教訓
異国の糞ブログで晒されたりするので気を付けよう
検索用
Google 神 Gmail SMTP メール Perl STARTTLS
CPAN カス Net::SMTP::TLS 糞モジュール Ruby厨 去れ
スポンサーサイト

| ホーム |