fml の SPAM 対策


fml という国産ソフトがあります。 所謂 Mailing List (以下 ML と略) を運営するための server/manager で、 国内ではそれなりのシェアを持っているんじゃないかと思います。
しかし、 正式版 fml4.0 の最終リリースが 2001/12/02 と相当古いソフトで、 当時の穏やかな Internet 事情にあって SPAM のことを余り考えていない設計になってしまっています。 開発版の current も 2004/02/15 のリリースが最後ですし、 次世代版と銘打たれた fml8 のα版も 2006/12/03 を最後に開発が止まっており、 fml8 正式版としては日の目を見ることが出来なさそうな状況です。 この状況では、 昨今の厳しい SPAM 状況への対応は残念ながら期待出来そうにもありません。
とは言え、 一度 fml で運営を始めてしまった ML を他の ML server に移行するとなると結構厄介な作業を伴います。 何とか現行の fml を使い続けながら、 尚且つ押し寄せる SPAM に対抗する手段は無いものなんでしょうか?

ここでは、 fml への quick hack によって暫定的に SPAM に対抗していく幾つかの手法を紹介したいと思います。

  1. 設定で何とかする
    1. 参加者以外の投稿を禁止
    2. 拒否アドレス機能の抑制
    3. Postfix の Loop Alert 抑制
    4. SPAM filter の併用
  2. Perl ソースに手を加える
    1. 不正メール受信時の警告メール抑制
    2. Loop Alert 警告メールの抑制
    3. エラー時の bounce の抑制

1. 設定でなんとかする

まずは fml の既存の枠組の中で出来得る対策を講じておきましょう。 fml では各 ML 毎に config.ph という設定ファイルが用意され、 その中で運営ポリシーを定めています。 この設定を見直すことから始めましょう。


1-a. 参加者以外の投稿を禁止

これは既に多くの ML 運営者の方が実践済のことだとは思いますが、 念のために設定法を記しておきます。 誰でも投稿可能な ML を運営したいニーズもあるかとは思いますが、 そのような ML に対して SPAM が押し寄せて来た場合には、 所謂 SPAM filter のようなものを併用するしか手はありませんので、 別項を参照して下さい。

参加者以外の投稿を禁止する場合には、 config.ph の PERMIT_POST_FROM の値を "members_only" にします。 これで参加者以外から ML に送られて来たメールは全て拒否されますが、 これだけだと、 その際に発信者に向けて拒否メールが送り返されてしまうので、 拒否時の動作を「無視」にしておきましょう。 これには、 config.ph の REJECT_POST_HANDLER の値を "ignore" にする必要があります。
一般的に SPAM の From: 欄が真の発信者を謳っている可能性はまず考えられず、 普通は無関係の第三者もしくは存在しないアドレスを騙っています。 そういう偽の発信者に拒否メールを返したところで、 SPAM の片棒を担ぐ羽目になるか、 もしくは ML 管理アドレス宛にエラーメールが返って来るだけのことで、 何も嬉しくはありません。 現実的に考え得る対処は「無視」しかないでしょう。

また、 コマンドメールを有効にしてある ML の場合は、 ML 本体への対処と同様にコマンドメールの宛先に対しても同じく拒否する設定にする必要があります。 対応する config.ph の設定項目は PERMIT_COMMAND_FROMREJECT_COMMAND_HANDLER で、 このそれぞれの値を上と同様に "members_only""ignore" にして下さい。
但し、 コマンドメールを使って ML への自動登録を行ないたい場合は、 参加者以外からの登録メールを受付ける必要がありますので、 REJECT_COMMAND_HANDLER の値は "auto_subscribe" にしないといけません。 更にこの場合は、 SPAM の発信者アドレスを間違って自動登録してしまわないように、 config.ph の AUTO_REGISTRATION_TYPE の値を "confirmation" にして、 発信者アドレスに確認メールを送るような設定にしておいた方がいいと思います。

なお、 これらの設定は config.ph ではなく cf に記述してから makefml update-config ML としても構いません。 また、 makefml config ML として、 メニュー形式で設定項目を選ぶ手法も用意されています。 それぞれの設定法での設定例を以下にまとめておくので参考にして下さい。

config.ph:
$PERMIT_POST_FROM       = "members_only";
$REJECT_POST_HANDLER    = "ignore";
$PERMIT_COMMAND_FROM    = "members_only";
$REJECT_COMMAND_HANDLER = "ignore";	# Or "auto_subscribe"
$AUTO_REGISTRATION_TYPE = "confirmation";
cf:
PERMIT_POST_FROM       members_only
REJECT_POST_HANDLER    ignore
PERMIT_COMMAND_FROM    members_only
REJECT_COMMAND_HANDLER ignore	# Or auto_subscribe
AUTO_REGISTRATION_TYPE confirmation
makefml config ML:
1 POLICY OF ACCESS
→ 1 PERMIT_POST_FROM
   → 1 members_only
→ 2 WHEN_POST_FROM_NOT_MEMBER
   → 3 ignore
→ 3 PERMIT_COMMAND_FROM
   → 1 members_only
→ 4 WHEN_COMMAND_FROM_NOT_MEMBER
   → 3 ignore	(Or 2 auto_subscribe)
2 REGISTRATION METHOD TYPE
→ 1 AUTO_REGISTRATION_TYPE
   → 1 confirmation

1-b. 拒否アドレス機能の抑制

fml の標準機能として、 発信者のアドレスが root とか postmaster とかいった非一般ユーザだった場合に拒否するという仕組みがあります。 しかし、 この機能も発信者アドレスが信用出来ない SPAM に対しては意味を成さず、 むしろ不必要なエラーメールを生むという弊害の方が大きくなってしまいます。 なのでこの機能も無効にしてしまいましょう。
勿論、 この機能を無効にすることで非一般ユーザから発信されたメールを受理してしまうことにはなりますが、 1-a. で参加者以外からの投稿を拒否しているので、 本当にそういう非一般ユーザのアドレスで ML に参加登録していない限りは結局は拒否される筈です。

拒否アドレス機能を無効にするには、 拒否すべきアドレスを一つも登録しなければいいので、 config.ph の REJECT_ADDR の値を "" にします。 また、 拒否アドレス一覧を記したファイルとして、 各 ML 用スプールディレクトリ (config.pl や cf のあるディレクトリ) にある spamlist というファイルが規定値に登録されていますので、 もしそういう名前のファイルがあれば削除するか空にして下さい。

なお、 この設定項目も 1-a. と同様に cf や makefml config ML でも設定可能です。 それぞれの設定法での設定例を以下にまとめておくので参考にして下さい。

config.ph:
$REJECT_ADDR            = "";
cf:
REJECT_ADDR
makefml config ML:
7 SECURITY & FILTERING 
→ 11 REJECT_ADDR
   → 2 accept all accounts

1-c. Postfix の Loop Alert 抑制

これは fml に限った話ではなくて、 MTA である Postfix のとある仕様に起因する問題です。 最近の SPAM には、 何故か「Delivered-To:」フィールドに「To:」フィールドと同じアドレスを記述してあるものが散見されるのですが、 Postfix はそのようなメールを「mail forwarding loop」として発信者アドレス宛に bounce してしまいます。 SPAM に対するメール送信は 1-a. でも触れたとおり百害あって一利なしですので、 この機能も抑制してしまいましょう。

まず、 無視すべき「Delivered-To:」アドレスを登録したファイルを用意します。 ここではこのファイル名を仮に「/etc/postfix/header_checks」としておきます。 ここに「/^Delivered-To: *ML-ctl@sample.com/ IGNORE」という一行を書いておきます。 「ML-ctl@sample.com」の部分に SPAM に含まれる「Delivered-To:」アドレスを記述して下さい。 ここには正規表現の記述が可能なので、 複数のアドレスを記述したい場合には「.*-ctl@sample.com」のような表現でも構いません。
但し、 ここでマッチしたメールは受信したふりをしてどこにも配信されませんから、 ML 本体のアドレスや特定個人のアドレス等、 間違って無視してしまった場合の被害が大きいアドレスは登録しない方がいいでしょう。 ML 用のコマンドメールの宛先を記述するくらいが無難なところだと思います。

次に、 このファイルを参照するようなルールを Postfix の設定ファイル main.cf に記述します。 このファイルの置き場所は環境によって異なると思いますが、 ここでは仮に「/etc/postfix/main.cf」だとしておきます。 ここに「header_checks = regexp:/etc/postfix/header_checks」という一行を追加します。
最後に Postfix を再起動して下さい。 この部分の設定は Postfix の常駐 daemon ではなくて配信用の sub process が参照しているので、 実際は再起動しなくても有効になるようですが、 念の為に再起動しておいた方がいいでしょう。

/etc/postfix/main.cf:
header_checks = regexp:/etc/postfix/header_checks
/etc/postfix/header_checks:
/^Delivered-To: *ML-ctl@sample.com/ IGNORE

1-d. SPAM filter の併用

spamassassinbsfilter のような SPAM filter と組合わせることで、 機械的でなく intelligent に SPAM を弾くことも出来ます。 fml と SPAM filter を併用するには大きく分けて 2 つの手法があります。

  1. DISTRIBUTE_FILTER_HOOK (又は COMMAND_FILTER_HOOK) に filter 用のフックを用意する。
  2. filter が追加した X-Spam-Flag: を DEFINE_FIELD_PAT_TO_REJECT で判別する。
大抵の SPAM filter にはヘッダ追加機能が備わっているので、 ここでは後者の方法を紹介します。 まず、 filter 機能を有効にするために、 config.ph の USE_DISTRIBUTE_FILTER の値を 1 にします。 その上で、 X-Spam-Flag: フィールドの値が「Yes」の場合に拒否するように、 &DEFINE_FIELD_PAT_TO_REJECT() の記述を追加します。
この設定項目も 1-a. と同様に cf でも設定可能ですが、 makefml config ML では設定出来ないので気をつけて下さい。 それぞれの設定法での設定例は下記の通り。
config.ph:
$USE_DISTRIBUTE_FILTER = 1;
&DEFINE_FIELD_PAT_TO_REJECT('X-Spam-Flag', 'Yes');
cf:
USE_DISTRIBUTE_FILTER	1
&DEFINE_FIELD_PAT_TO_REJECT('X-Spam-Flag', 'Yes');

更に、 include ファイルで fml.pl を呼んでいる個所を書換え、 その前に SPAM filter を通すようにします。 ML-ctl 宛に対して SPAM filter を併用したい場合は、 include の代わりに include-ctl を書換えて下さい。

include:
spamassassin の場合
"|/usr/bin/spamassassin | /usr/local/fml/fml.pl /var/spool/ml/ML"
bsfilter の場合
"|/usr/bin/bsfilter --pipe --insert-flag | /usr/local/fml/fml.pl /var/spool/ml/ML"

2. Perl ソースに手を加える

ここからはかなり敷居が高くなります。 既存の fml の枠組では対応しきれないことを、 fml のソースファイルに手を加えることで実現してしまおうという大胆な対処法です。
fml のソースは Perl で記述されているのですが、 fml は結構大きなプログラムで各ファイル同士の依存関係も複雑なので、 決して読み易いソースではありません。 ましてや私自身 Perl には全く精通していませんから、 これ以降のお話は自己責任で読んで下さい。 私のところではこのような対処で無事に動いていますが、 何か大きな問題を抱えていたとしても私は責任を負い兼ねますので、 予めご了承下さい。

なお、 下記 3 種の対処法をまとめた patch (fml_spam.patch) を用意しておきますので、 必要な場合は参考にして下さい。 但し、 これはオリジナルの fml-4.0.3 に対する patch になっていますので、 細かな revision の違いによっては patch(1) コマンドによる修正が効かない場合もあります。 その場合は手修正で対応して下さい。


2-a. 不正メール受信時の警告メール抑制

1-a. で参加者以外のメールを拒否するような設定にしましたが、 これだけだと拒否した旨の警告メールが ML 管理アドレス宛に届いてしまいます。 SPAM の数が少なければそれでも構わないのですが、 メールボムのように何百何千という SPAM が押し寄せる状況では、 それに対する警告メールすら迷惑な代物でしかありません。 なので、 折角無視した SPAM に対して警告メールを発信しないようにしましょう。

fml.pl の 1770 行辺りに IgnoreHandler という関数定義があります。 この関数が「ignore」時に呼ばれますが、 この中で「WarnE」という関数を呼んで警告メールを送信していますので、 この行をコメントアウトして下さい。

fml.pl:
sub IgnoreHandler
{
    &Log("Ignored: \"From:\" field is not member");
#    &WarnE("Ignored NOT MEMBER article from $From_address $ML_FN",
#	   "Ignored NOT MEMBER article from $From_address");
}

2-b. Loop Alert 警告メールの抑制

fml は何故か「Message-Id:」フィールドを見ていて、 過去の履歴を参照した上で同一の「Message-Id:」フィールドを持つメールを受信すると ML 管理アドレス宛に警告メールを送信します。 この履歴には無視したり拒否したりしたメールも含まれているため、 同じ「Message-Id:」フィールドを持つ SPAM が大量に届いたりすると、 またこの警告メールがメールボム化してしまいます。
本当は無視も拒否もせずに受理したメールについては重複チェックを残しておいた方が安心出来るとは思うのですが、 そういったインテリジェントな対応は難しそうだったので、 この際「Message-Id:」重複を知らせる警告メールは全て禁止してしまいましょう。

fml.pl の 1782 行辺りに DupMessageIdP という関数定義があります。 この中で重複時に「WarnE」という関数を呼んで警告メールを送信していますので、 この行をコメントアウトして下さい。

fml.pl:
sub DupMessageIdP
{
...(snip)...
    if ($status) {
	&Debug("\tDupMessageIdP::(DUPLICATED == LOOPED)") if $debug;
	local($s) = "Duplicated Message-ID";
	&Log("Loop Alert: $s");
#	&WarnE("Loop Alert: $s $ML_FN", "$s in <$MAIL_LIST>.\n\n");
	1;
    }
...(snip)...
}

2-c. エラー時の bounce の抑制

これは 1-a. で自動登録の設定を行なった場合に必要となる対処法です。 コマンドメールの宛先に SPAM が届いた場合、 auto_subscribe 用のハンドラが SPAM 本文をコマンドメールとして構文解釈しようとして失敗してしまい、 発信者アドレス宛にエラーとして bounce してしまいます。 これも SPAM に対するメール送信になってしまうので、 この機能も抑制してしまいましょう。

libconfirm.pl の 630 行辺りに BufferSyntax という関数定義があります。 この中で構文エラー発生時にエラーメッセージを生成している個所があるので、 この行をコメントアウトして下さい。 fml では何らかのエラーメッセージが生成された際に bounce が発生するので、 その生成を抑制することで bounce を回避することが出来るようです。
同様に、 コマンドに日本語が含まれていた場合もエラーメッセージを生成しているので、 こちらもコメントアウトします。

libconfirm.pl:
sub BufferSyntax
{
...(snip)...
	if ($buffer =~ /($re_jin|$re_euc_c)/) {
	    &Log("confirm: request includes Japanese character [$&]");
#	    &Mesg(*e, "Error! Your request seems to include Japanese.",
#		  'confirm.has_japanese_char');
	}

#	&Mesg(*e, &GenConfirmReplyText(*e, *cf, 'BufferSyntax::Error'));
	if (-f $CONFIRMATION_FILE) {
	    $e{'message:append:files'} = $CONFIRMATION_FILE;
	}
	$name = $NULL;
    }
...(snip)...
}

苦情・問い合わせはこちらまで。 shirai@unixusers.net

トップページ