2007-05-18
_ [COMP] spam 対策 — qmail-smtpd 改造編
前回の続き。連休中に spamd を OpenBSD 4.1 のものに換装するつもりだったんだけど、知り合いの別荘に行ったりして遊び惚けたので頓挫。簡単にできるってことで、もっぱら qmail-smtpd に手を入れる事に終始してた。つごう、3回ほど変更をいれた結果、今では日に数通しか通過してこなくなった。
前回にも載せた、通過してくる spam の数*1、今回はグラフにしてみた。
元は 500 から 600 ほど通過してきてたことを考えると、この一ヶ月で数百分の一にできたともいえる。
badheloarg
mfcheck の真似をして、badheloarg というのをでっちあげた。spam の HELO, EHLO の引数は実にいいかげん。特に、自分の FQDN ではなく、こっちのホスト名やらアドレスを入れてくるのが大半である。これらのいい加減な HELO をほざく相手を reject する。
mfcheck と同じく、HELO の引数として弾くものを /var/qmail/control/badheloarg というファイルに入れておくだけ。ここに、localhost.localdomain だとか自前のサイトのホスト名や MX に使ってる名前をいれておく。
さらに、ピリオドがひとつもないものも一律弾く。rfc2821 的に正しい FQDN や [アドレス] な形式であれば、必ずピリオドが入ってるはずだからね。IPv6 なアドレスはどうよ? てな部分は残るんだけど、qmail 自体が IPv6 非対応だからよしとする。
4/28 にリリース。グラフ見てわかるとおり、効果は絶大。ほぼこれだけで半減してる。
rcptusers
qmail は、RCPT TO に指定されたアドレスのローカルパートが有効なものであるかを SMTPセッション中にチェックできない。「そんなユーザーはいないよ」エラーは、一旦受け取った後、エラーメールとして送信するしかない。これが、qmail の一番の弱点である。
もちろん、これに関しては山ほどパッチが作られている。有効なアドレスをファイルに書いておく小規模なものから、外部の ldap を見に行く大規模なものまでよりどりみどり。が、毎度のことながら、気に入るものがなかったので自前で実装した。
これまた mfcheck 同様に、有効なローカルパートのみを rcptusers に書いておくというパターンの仕組みである。user-default と書いておけば「user-なんちゃら」の拡張アドレスはすべて受け入れる。
5/4 に、テストを終えた rcptusers 版をリリース。最初 rcptusers には noroi-default を書いていたのだが、翌日拡張アドレスをすべて書き連ねたものに置き換えた。かなり効果がある。あたりまえだ。今までは存在しないアドレスもすべて喰っていたのだ。
さらに、セキュリティホールmemo、free-memo、connect24h の各メーリングリスト用アドレスを変更した。これらの ML のウェブアーカイブは、個々の記事のメールアドレス部分が隠蔽されてなかった時期がかつてあり、その頃収集されてしまったアドレス宛の spam が今でも山のように来るからだ。
そんなこんなで、5/4 以降なだらかに減っているのは、ちょこまか直しを入れていたから。
greet pause
いまさらだけど実施。これが5/11。
qmail-smptd のログを眺め続けた結果、tcpserver が接続を受けてから qmail-smtpd を起動するまで時間がかかることがあるのに気づく。そんな場合に、送信をあきらめてしまう接続が結構あるのだ。知識として greet pause で spam を 8割抑えられることはわかってるつもりだったんだけど、ここにきてようやく実感したわけだ。
tcpserver が qmail-smtpd を起動するまで時間がかかる「ことがある」ってのは、ident を引いてるから。これのタイムアウトが 26秒。RST が返ったり ident が引けちゃったりもするので、遅延がある場合とない場合ができてしまう。そこで、tcpserver に ident 引かせるのを止め、qmail-smtpd で一律 26秒待たせることにした。いやはや弾ける弾ける。まぁ、greet pause してなくても、他の対策に引っかかるものばかりなんだろうけどね。
その後
ちなみにここまでで、marunouchi.tokyo.ocn.ne.jp はほぼ撲滅できた。あ、いや、5/9 に一通通り抜けてきてるな。まぁ、やり方の違う業者なんだろうね。甘い ISP には spam 業者が群がってるだろうし。
badheloarg を実施している内部的な関数 helocheck() には手を入れ続けている。引数がアドレスでなければ名前を引き、nxdomain なら reject するところまでは既に取り込んでいる。が、ここまで減ってるとあまり効果は見えてこないんだな。
その他にも、接続元が逆引きできないなら、引数は FQDN じゃなくアドレスであるべきだし、アドレスは bracket で括られてないとおかしいし、A RR は接続元と一致するものが含まれてないとおかしいし…てな部分はチェックだけしてログに残して様子を見ている。これらの理由で reject する根拠があまりないのと、connect24h ML や harden-mac ML が使っている FreeML のように、spammer じゃないのにちゃんとした FQDN を送ってこれない間抜けなところがあるので、reject できないのだ。まぁ、本気でおかしなヤツらは、たいてい後段で別のチェックに引っかかって reject されてるから、いいんだけど。
*1 実は、これには自分以外のアドレスへの spam の数は含まれてない。bounce してくるのを見るに、たぶん今でも日に数通あると思うんだけど、spam かどうか判定する方法がないので、含めようがないのだ。