ただのにっき。

ただつらつらと日記が書かれていくようです。

Tag: PHP

PHPMailerからTLSでSMTPサーバに接続してメールを送ろうとするとコケた。

PHPmailerから、TLS対応のSMTPサーバにTLSで接続して、SMTP認証してメールを送信しようとしたら、PHPmailerがSMTPサーバへの接続でコケてエラーを吐いていた。

んで、SMTPサーバ側のpostfixのログを確認すると見慣れないエラーが出ていた。

warning: TLS library problem: 855:error:14094418:SSL routines:ssl3_read_bytes:tlsv1 alert unknown ca:s3_pkt.c:1493:SSL alert number 48:

どうやら、TLSのコネクションを張る際のエラーのようだった。ま、何かTLSにまつわるエラー(証明書とか接続先の確認とか)だろうって、とりあえずTLSで接続する際に、

$mail = new PHPMailer();
$mail->SMTPOptions = array(
    'ssl' => array(
        'verify_peer' => false,
        'verify_peer_name' => false,
    )
);

を使って、接続先の確認を省略するようにすると、無事にメールを送信できるようになったのだが、やっぱり、なんだか気持ち悪い。

ってことで、いろいろと調べてみたら、原因はPostfixに設定してあったSSL証明書に中間証明書を付けてなかったことだった。つまり、中間CAの証明書がないせいで、クライアントが持っているrootCAの証明書と、postfixにインストールしてある証明書が証明書チェーンでつながらなかったために発生していたエラーのようだった。…そういえば、先日、SSL証明書を入れ替えたときに、買ってきた証明書をそのままインストールしたような気がする…。あー、しまった。

…というわけで、postfixが読み込んでいる証明書をnginxと同じやりかたで、サーバ証明書と中間CAの証明書を1つのファイルにしてPostfixを再起動してみると、phpmailerから普通にTLSで接続してメールを投げることができた。

今回は自分でイジれるSMTPサーバだったから、証明書を入れ替え直して問題を解決したが、もし、中間CAの証明書を含んだ証明書に簡単に入れ替え直してもらえないようなMTPサーバを使わなきゃいけない場合は、前述のとおり、$mail->SMTPOptionsでpeerの確認を省略すれば送れるようにはなるけれど、一連のTLSの仕組みの重要な部分が確認できてない形で暗号化することになることは覚えておいた方がいいだろう。

ふたたびAPCの試用を開始してみるなど。

PHPの中間コードキャッシュ用のモジュールと言えば、いくつかあるが、ま、とりあえずAPCでも使ってみるかということで、ビルドしてapc.soを作って入れてみている。

そして、/etc/php.d/の下にapc.iniを作って、apc.soを読み込ませているつもりだったが、恐るべきことに「extension」と書いたつもりが「extention」とかなっていて、apc.soが読み込まれず。orzこういうスペルミスではまると、なかなか見つからないのが辛いところで、もう少しでAPCをビルドし直すところだった。

とりいそぎ、動き始めたAPCではあるが、いまのところ、割り当てるメモリ不足が不足してなさそうではあるけれど、この辺を監視してチューニングしたいと思っているけど…これまたなかなか面倒くさい(汗)

spawn-fcgiでphp-cgiを起動できずにハマる。

…実にがっかりなことでハマったので記録しておくことにする。

さくらのVPSにnginxとfastcgiのPHPをインストールしてある。要するにHTTPはnginxが受け付けて、PHPのリクエストに関しては、nginxからFastCGIで常駐しているPHP-cgiを呼び出して処理する…そんな構成になっている。

このサーバでは、WordPressが動いているのだが、nginxが軽いせいか、普通にApache httpd+mod_php5の組み合わせよりも体感速度は速い。

nginxとFastCGIで起動してあるphp-cgiは連携はしているものの、起動するプロセスの数を制御する関係にはないので、FastCGIで常駐しているphp-cgiの数以上は同時にリクエストを捌けないという欠点もある。しかし、この欠点が問題になるようなシーンでは、動的にいくつもプロセスを起動すれば解決するような気もしない(結局、山ほどプロセス起動してみても、足りないリソースが増えるわけじゃないし)から、あんまり気にしてない。裏側のphp-cgiが足りなくなったら、nginxがBad Gatewayあたりを返しそうな気がするから、それを見かけたら、リソース見合いでFastCGIで起動するphp-cgiの数を増やすかなぁという感じ。

そんな構成のサーバに対して、久々にメンテナンスでもするかってことで、PHP5のバージョンアップを行うことにした。

いつも通りのPHPのビルドオプションでビルド(…実はコレがまずかったのだが、すぐに気づくこともなく…)に成功。make installでインストールを完了して、CLIのPHPを実行してみたら、普通に動作するのだが、php-cgiを常駐させるために使っているspawn-fcgiがうまく動かない。

メンテナンス前に普通に使えていた起動スクリプトからphp-cgiを常駐させようとしても、なぜか、子プロセスがexitしてしまい、pidファイルがむなしく残る…と。いろんなログを見てみても、何も出力されておらず、ただ、起動直後に子プロセスがexitしていることだけがわかった。

まぁ、PHPを新バージョンにするまでは普通に動いていたわけだから、PHPが問題ってのはわかったので、1バージョン前のPHPをビルドし直してみたり、なぜかspawn-fcgiのバージョンを更新してみたり…ひたすら迷走。

ふと、PHPのビルドオプションを確認し直しているときに、軽いめまいが…。いつものPHPのビルドオプション=Apache httpd + mod_php5用なんだった(涙)端的に言えば、FastCGIで常駐させるってのにPHP5のビルドオプションに「–enable-fastcgi」が追加されてなかった。そりゃ、起動スクリプトで起動しようとしても終了するわけだ(汗)

…というわけで、PHP5を「–enable-fastcgi」付でビルド(実は、ダメ押しで「–with-fastcgi」なんて謎のオプションつけてconfigureかけちゃったのは秘密)したら、何事もなくFastCGIでphp-cgiを常駐させられた…と。

ま、いくらネタがないからってケアレスミス(構成管理ミス…か)をネタにしちゃいけないなぁ…なんて思いながら、しっかり書いちゃった(汗)

phpmailerを使うと、MIMEエンコードしたsubjectが改行されない(涙)

PHPからメールを簡単に送りたいなぁと思って、phpmailer(使ったバージョンは、リリースされたてのVer.5.0.0)を使ってみた。

ちらっとソースとドキュメントを読んで、サンプルアプリで使ってみていたら、Subjectの処理で少し気になることがあった。端的に言えば、長いSubjectのメールを送ろうとしたときにMIMEエンコードされた文字列が改行されずに1行になってしまい、その文字列を埋め込んだメールヘッダーがRFCに引っかかってしまう恐れがあるという事象に直面した。

で。

Subjectに日本語を入れたりする場合は、SubjectをMIMEエンコードする必要があるので、PHPのmb_encode_mimeheaderって関数でMIMEエンコードした文字列を作って、それをphpmailerに渡して、どーんとメールを送ってみた。

…すると。

MIMEエンコードされた文字列は生成されたものの、MIMEエンコードされた文字列が1行に連結されてしまっていた(汗)(…強制改行されているとは思いますが、実際は1行です。)

Subject: =?ISO-2022-JP?B?GyRCJDMkbCRPJUYlOSVIJWEhPCVrJE4lNSVWJTglJyUvJUgkMyRsGyhC?= =?ISO-2022-JP?B?GyRCJE8lRiU5JUglYSE8JWskTiU1JVYlOCUnJS8lSCQzJGwkTyVGGyhC?= =?ISO-2022-JP?B?GyRCJTklSCVhITwlayROJTUlViU4JSclLyVIJDMkbCRPJUYlOSVIGyhC?= =?ISO-2022-JP?B?GyRCJWEhPCVrJE4lNSVWJTglJyUvJUgkMyRsJE8lRiU5JUglYSE8GyhC?= =?ISO-2022-JP?B?GyRCJWskTiU1JVYlOCUnJS8lSBsoQg==?=

で、メールに関するお約束のRFC2822にこんな規定がある。ヘッダーに限定されているわけではないが、メールの1行に並べていい文字数には制限があるようだ。

http://www.puni.net/~mimori/rfc/rfc2822.txtより引用:

行の長さの制限

この標準では1行中の文字数に2つの制限がある。それぞれの行の文字はCRLFを除いて、決して998文字以下でなければならず(MUST)、78文字以下であるべきである(SHOULD)。

そんなわけで、MIMEエンコードされたSubjectがずらっと1行に並んでしまうと、うっかり998文字を超えてしまうおそれがあるわけで、なんとか78文字くらいで改行するのがよろしいということだろう。

ちなみに、上で使ったのと同じ文字列をBecky!2.50.07の件名の欄に突っ込んで、メールを送ってみたら、やっぱり適度に改行されていた。

Subject: =?ISO-2022-JP?B?GyRCJDMkbCRPJUYlOSVIJWEhPCVrJE4lNRsoQg==?=
=?ISO-2022-JP?B?GyRCJVYlOCUnJS8lSCQzJGwkTyVGJTklSBsoQg==?=
=?ISO-2022-JP?B?GyRCJWEhPCVrJE4lNSVWJTglJyUvJUgkMxsoQg==?=
=?ISO-2022-JP?B?GyRCJGwkTyVGJTklSCVhITwlayROJTUlVhsoQg==?=
=?ISO-2022-JP?B?GyRCJTglJyUvJUgkMyRsJE8lRiU5JUglYRsoQg==?=
=?ISO-2022-JP?B?GyRCITwlayROJTUlViU4JSclLyVIJDMkbBsoQg==?=
=?ISO-2022-JP?B?GyRCJE8lRiU5JUglYSE8JWskTiU1JVYlOBsoQg==?=
=?ISO-2022-JP?B?GyRCJSclLyVIGyhC?=

ってことは、phpmailerで処理されたsubjectのスペース区切りで繋がっているところが改行になってれば何の問題もなさそうってことか。

PHPのmb_encode_mimeheaderで生成した文字列を出力してみたら、ちゃんと改行が入っていた(汗)ので、犯人はphpmailerさんですか…と(汗)で、phpmailerに渡されたSubjectがどうなっているのか追いかけてみたら、送信するときにCreateHeader()って関数が呼びされていて、その中でSubjectが以下のように加工されていた。

if($this->Mailer != 'mail') {
$result .= $this->HeaderLine('Subject', $this->EncodeHeader($this->SecureHeader($this->Subject)));
}

で、このSecureHeader()ってメソッドは何をやっているのかというと。。。

/**
* Strips newlines to prevent header injection.
* @access public
* @param string $str String
* @return string
*/
public function SecureHeader($str) {
$str = str_replace("\r", '', $str);
$str = str_replace("\n", '', $str);
return trim($str);
}

あ、改行コードを吹っ飛ばしてるじゃん…(汗)このメソッドを回避してみたら、Becky風にほどよく改行された。

でも、これ、Header Injectionを回避するためのサニタイズをやっているようなメソッドっぽい…、このメソッドを呼び出す限りは、RFC2822の998文字越えをやらかしてしまいそうだし、このメソッドを回避してSubjectを作ると、Header Injectionに直面するリスクを抱えるってことだろうか。

でも、基本的にはシステムが生成したメールを送信するだけなので、悪意思ったSubjectってのはやってこないような気もするが…。ま、ちょっとHeader Injectionについて調べてみよう。

追記:

phpmailerにセットする前に改行コードを外す処理を追加しておけば、phpmailerの中でSecureHeader()を呼び出す必要はないんじゃないかって気がしてきた。とはいえ、phpmailerを直してしまうと、バージョンアップの時にややこしいことになりそうだし…。うーむ。