cygwin を更新すると bogofilter が動かなくなったので最新のソースからインストールし直しました。更に、プライベートではあまりメールを使わないのでこれまで真剣に取り組まなかったのですが、最近 bogofilter の判定精度がいま一つのようなので原因を調べてみました。するといろいろなことがわかりました。
・MIME エンコードされた本文がきちんと処理できていない
・データベースがいまひとつ
・データベースがいまひとつ
1) MIME エンコードされた本文
本文が MIME エンコードされたスパムが多くなっているのですが、これを nkf や kakasi に喰わせる前にデコードしなければなりません。あちこちの Web に nkf の “-m” で MIME デコードと書いてあったのでそれをそのまま信用していたのですが、実は “-m” だけでは何も起きていないことがわかりました。
本文が MIME エンコードされたスパムが多くなっているのですが、これを nkf や kakasi に喰わせる前にデコードしなければなりません。あちこちの Web に nkf の “-m” で MIME デコードと書いてあったのでそれをそのまま信用していたのですが、実は “-m” だけでは何も起きていないことがわかりました。
参考までにnkf のソース(最新は 2.0.7) でオプションを解析する部分を見ると、
case ‘m’: /* MIME support */
/* mime_decode_f = TRUE; */ /* this has too large side effects… */
if (*cp==’B’||*cp==’Q’) {
mime_decode_mode = *cp++;
mimebuf_f = FIXED_MIME;
} else if (*cp==’N’) {
mime_f = TRUE; cp++;
} else if (*cp==’S’) {
mime_f = STRICT_MIME; cp++;
} else if (*cp==’0′) {
mime_decode_f = FALSE;
mime_f = FALSE; cp++;
}
continue;
(nkf.c より)
/* mime_decode_f = TRUE; */ /* this has too large side effects… */
if (*cp==’B’||*cp==’Q’) {
mime_decode_mode = *cp++;
mimebuf_f = FIXED_MIME;
} else if (*cp==’N’) {
mime_f = TRUE; cp++;
} else if (*cp==’S’) {
mime_f = STRICT_MIME; cp++;
} else if (*cp==’0′) {
mime_decode_f = FALSE;
mime_f = FALSE; cp++;
}
continue;
(nkf.c より)
のようになっています。”-mB” とすると base64 のデコードをしますが、メール全体をデコードしてしまいます。
仕方ないので perl の Email::MIME を利用して MIME 形式のメールをデコードするスクリプトを作成しました。と言ってもとても簡単です。
#!/usr/bin/perl
# prebogo
use Email::MIME;
# prebogo
use Email::MIME;
local $/;
my $message = <>;
my $parsed = Email::MIME->new($message);
my @parts = $parsed->parts;
print $parsed->_headers_as_string . “\n”;
foreach my $p (@parts) {
if ($p->content_type =~ /text/i) {
print $p->body;
}
}
これ (prebogo とする) を nkf の前に入れます。例えば newham スクリプトは次のようになります。
#!/bin/sh
prebogo | nkf -e | kakasi -w | bogofilter -n -v
prebogo | nkf -e | kakasi -w | bogofilter -n -v
判定用の bogo スクリプトは次の通りです。
#!/bin/sh
PATH=/bin:/usr/bin:/usr/local/bin:$PATH
PATH=/bin:/usr/bin:/usr/local/bin:$PATH
for i in $*; do
prebogo $i | nkf -e | kakasi -w | bogofilter
if [ $? -eq 0 ]; then
echo $i
fi
done
「判定に用いるスクリプトのパフォーマンスが悪い」、「パートごとに charset が違うときはどうする?」、「content-type に “application/octet-stream” を指定してテキスト入れてくるスパムも除外したいなあ」等の問題はありますが、とりあえず今の段階ではここまでにしておきます。
2) データベースの内容
まずはどの程度の数のメッセージを学習させたか確認しようとしました。
まずはどの程度の数のメッセージを学習させたか確認しようとしました。
bogoutil -w ~/.bogofilter/wordlist.db .MSG_COUNT
を実行すると学習させたメッセージの数が出力されるのですが、思ったほど数が増えていません。実は spam/ham の登録は、 mew の summary モードより以前の記事で作成した newspam/newham スクリプトを呼び出していたのですが、このときに bogofilter コマンドのオプションに “-N”/”-S” を指定して登録するのはまずいようです。
これらのオプションはメッセージを間違えて登録してしまった時にそれを取り消す場合のみ指定するもので、例えば “-S” については bogofilter のマニュアルに以下のような記載があります。
If -S is used for a message that wasn’t registered as spam, the counts will still be decremented.
ということは間違って登録したわけではないのにこれらのオプションを使ってしまうとカウントが減ってしまいます。
そこで newspam/newham スクリプト中の bogofilter 実行オプションを “-s -N” → “-s” 、”-n -S” → “-n” と変更しました。これも某所で見かけたオプションを深く考えず使っていました。newham は先の通りですが、newspam もついでに載せておきます。
#!/bin/sh
prebogo | nkf -e | kakasi -w | bogofilter -s -v
prebogo | nkf -e | kakasi -w | bogofilter -s -v
これらを改善してデータベースを作り直してから使うと大分判定が良い感じになりました。前の記事では書いていませんが、スパムだけでなくハム(正常なメール)も学習させておかなければなりません。特に nkf の “-m” オプションは、間違って使って「bogofilter は使えない」と思ってしまった人が結構いるのではないでしょうか。
教訓: きちんと自らオプションの意味を確認しなければならない。
2007. 6. 16 追記:
内容を修正して改訂版を作っていますのでこちらを参照ください。
(更に追記)
nkf はデフォルトの動作で、
Subject: =?iso-2022-jp?B?GyRCJUYlOSVIJWEhPCVrGyhC?=
のような行はデコードしてくれますね。いずれにせよ、
Content-Transfer-Encoding: base64
となっているパートをデコードしてくれるわけではないです。