fetchmail+qmail+bogofilter(2006/12/04)


fetchmail+qmail+bogofilter

とりあえずメモ的に現状をまとめておきます。

まず、私が手元で使っている環境の概要から。

メールをとりに行く先のサーバは2つ。片方はpop3でもうひとつはimap4です。 このメールが、以下の手順で私のメールクライアントに届きます。

サーバ-->fetchmail(pop3, imap4)--> qmail(メールのフィルタ)-->ローカルフォルダ

この流れのシステムを作る上で、キーになる要素は以下のとおりです。

それぞれ核心を担うのは fetchmail qmail bogofilter です。

fetchmail はおなじみの、 pop3 や ipam4 で取得したメールをローカルの smtp に投げるという動作です。

この配送プロセスの中に、メールの振り分けを仕込みます。仕込み方は、おなじみ .qmail を使う手口です。 .qmail にはメールを標準入力から読み込むような、何らかのコマンドを実行し、 その結果によって特定のアドレスへの配送を行うような記述ができます。 私の場合の記述は以下のとおりです。

| condredirect yuji-trash ~/bin/mail_filter

~/bin/mail_filter は標準入力からメールを読み込み、 spamかどうかを判定します。 ~/bin/mail_filter が exit0 すると、 yuji-trash@localhost へ配送するという記述です。

この行がきちんと機能するためには、 メールアドレス yuji-trash@localhost が必要です。 これが存在しないと bounce になってしまいます。 そこで、このメアドを作ります。 qmail では .qmail-trash というファイルを自分のホームディレクトリに作れば、 できあがりです。 .qmail-trash にはゴミの行き先のローカルフォルダを 何か指定しておけばいいでしょう。私の場合はこうです。

/home/yuji/.spam/

この記述がきちんと動作するためには、 ~/.spam という Maildir 形式のディレクトリを作成しておく必要があります。 さもないと、 実際に spam が来たときに 結局配送先のメールボックスが無くて エラーになります。 ディレクトリの作成は /var/qmail/bin/maildirmake ~/.spam でできあがりです。

もちろん、本家 .qmail には、spamの行き先だけではなく、 正しいメールの行き先もちゃんと定義しておきましょう。 これで、spam の行き先と、正常なメールの行き先がそれぞれ準備完了です。 あとは、spam かどうかを判定してくれるコマンドを作ればシステム完成です。

判定コマンド ~/bin/mailfilter の中身は以下の通りです。

#!/bin/sh
filter=/usr/bin/bogofilter
~/bin/mail_2_text | nkf -e | mecab -O wakati | $filter -v
  

上記のとおり、非常に短いシェルスクリプトです。 パイプでつないだコマンドの exit status は最後に起動したプログラムのものになりますから、 このシェルスクリプトの exit status は、 最後に起動される bogofileter -v の exit status です。 bogofilter は読み込んだメッセージが spam 判定であれば、 exit0 し、そうでなければ 0 以外の exit status を返します。 つまり、このスクリプトは正常なメールと bogofilter が判定すれば 異常終了し、デフォルトの配送先へ、 そうでなければ正常終了して、 yuji-trash@localhost への配送になるわけです。

これは短いシェルスクリプトですが、処理の内容はけっこう複雑です。 まず、 mail_2_text がメールのmimeを解析してヘッダと、 添付ファイル及び本文からテキスト化可能なものをあつめ、 出力します。 これを文字コードをそろえたうえで、 mecab でわかち書き処理、 つまり日本語の単語や文節に分解します。 そして、最後に bogofilter で出現語彙を解析し、 そのメッセージのスコアを計算して判決が下ります。

mail_2_text のコードはこちらです。 ruby と ruby の rmail ライブラリが必要です。 debian なら ruby と librmail-ruby の二つのパッケージを インストールしてください。 出力されるテキスト情報は、 decode するだけにして、 できるだけそのままの形を保つようになっています。 これは、html などを使ったメッセージの場合は、 いわゆる本文よりもむしろタグ文字列のほうが雄弁に spam 性を物語っている場合がままある、 という観察に基づいた仕様です。 たとえば、 blink(チカチカ) や #FF0000(真っ赤) などが指定されている html メールはかなり疑い濃厚ですが、 この手がかりは html 文を整形してしまうと失われてしまいます。

bogofilter は多数のキーワードと、 それに与えられるスコアのリストを用いてメッセージのスコアを計算し、 それに基づいてメッセージの「スパム性」を判定するので、 キーワードとそのスコアの充実ぶりが判定の精度に決定的に影響します。 bogofilter の初期化は、できるだけたくさん(数万通あればベスト)の、 しかも同じくらいの数の spam と正常なメールのセットが必要です。 これらメールのセットが汚染されていた場合、 つまり、正常なメールに spam が混入していたり、 spam 側に正常なメールが混入している場合は 精度が悪くなります。

前者の汚染は spam を見分けられずにユーザまで届くという false negative につながりますが、これは件数が少なければまだ許容できます。 しかし、後者の汚染は正常なメッセージを誤って spam とみなすという false positive につながりますので、僅かの発生であっても致命的です。 ここはきちんと仕分けしてください。

分類されたメールのセットを用いて キーワードのデータベースを初期化します。 zsh や bash など b-shell 系のシェルであれば、 プロンプトからこんな感じのコマンド(複数行を入力しても構わない)いっちょあがりでしょう。

for f in ~/verified_mails/*
do
~/bin/mail_2_text < $f | nkf -e | mecab -O wakati | bogofilter -n
done
  

同様のコマンドを bogofilter のオプションを -s に、ディレクトリを spam をまとめたディレクトリに変更して、 実行すればOKです。 念のため、うまく登録できたかどうか、

bogoutil -w ~/.bogofilter/wordlist.db

して確認しておきましょう。

全体のシステムがうまく動かないとドえらい事になりますから、 一旦、たまっているメールを全部まとめてどこかに退避しておき、 テストのメールを自分に送信してうまく動くのを確認してから、 実戦投入すると良いと思います。つまり、それで私はえらいめにあったというわけです。はっはっは。

spam を Maildir 形式で貯めておくのは、 あとで bogofilter を再初期化する必要が生じた時に、 簡単に実行できるようにするためです。

最初のうちは、フィルタをくぐりぬけるものや、 誤って spam フォルダに配送されるものもありますので、 監視を怠ることはできません。 しかし、個別のメッセージを都度学習させ直すうちに、 精度も向上し、圧倒的なノイズの海から必要なメッセージだけが 浮かび上がって来るシステムが完成します。


記事リストへ