Practice makes perfect

2004年3月号

../images/misc/hina.jpg

あかりをつけましょぼんぼりに・・・

(ぼんぼりの灯が綺麗でしょ)

我が家にもりっぱな 親王飾り と呼ばれるお雛様がいます。 でも忙しさにかまけて去年も今年も出してはもらえません。 不運な家に来ちゃったと、さぞなげいていることでしょう。

この画像は身長3cmぐらいのちっちゃくて可愛いお雛様です。 こちらの方はやっと3月3日に飾ってもらえたものです。 私がポンポンと何も考えずに並べたので並ぶ順番が変かもしれません。

そうそうじつはこの ぼんぼり は光ったりはしていません。 画像処理でフレアも付けたのですよ。

IMAP

IMAP server の乗り換えを行なったので、今月はそのお話。 POP 等から IMAP に移行する際にもきっと役立つ内容でしょう。

ここには私はこうやっている(やった)を書いています。 説明はあまりしていませんし、実に即物的・実践的な内容になっています。 (.... だから今月は 習うより慣れよ なのですねえ) 読みものというよりは資料として bookmark に記録しとこ、 といった内容でしょう。

ただし、鵜呑みにしないで自分でやってみましょうね。 自分でやってみる が大切ですからね。 もしかすると違う環境や状況では違った結果になるやもしれませんからね。

Which server?

IMAP とは何でしょうか? IMAP サーバにはどんなものがあつでしょうか? ではどれを選べば良いでしょうか?

IMAP に関する情報はおばたさんちの「 The IMAP Conneco-chan 」がとてもよくまとまっています。一度は目を通すと良いでしょう。

私が最初に使ったものは、 UW(University of Washington) IMAP Server でした。 それまで、MH 形式(1 mail = 1 file)を使っていたので、 MH → mhe.el → Mew → Gnus という 順に進化(移行?)してきました。 初めに IMAP に移行する際に「MH 形式をサポートする IMAP serve」という観点から UW imapを選択しました。MH 形式との併用期間があったためのです。

その後、MH 形式では遅いのでメールボックスを mbx 形式にしました。 その時点でもうメールボックスの形式はなんでも良いということだったわけで、 その時がじつは乗換え時だったのだと今では思います。

mbx も約2年ほど使っていて(飽きてきたので :-) 、 次は速度的にも良好という噂の Courier imap にしようと思い立ったのでした。 じつは Binc IMAP (Binc Is Not Courier-imap) に魅かれる[1]ものがあるのですが、 「日本語で検索できない」というのを見かけたので躊躇してしまいました。 でも Binc のうたい文句の 「モジュール式の小さなコードのシンプルなデザイン」は気になってて、 検索エンジンに Namazu を組み込めないかと思ってます。 まだコードは見てないけどね。

IMAP server の選択条件として「日本語で検索できること」は必須でしょうね。 もちろん。

しかし、 来月号では「Courier imap (Maildir) を Namazu で全文検索する」 を書くわけですが、それができるのだったら IMAP サーバ自体に 「日本語で検索できること」を求めなくとも良いのかもしれません。(と後から思った)

インストールの解説記事ほどつまらないものはない[2]と私は思っているので、 次節からは IMAP server はもうインストールできたものとして、 順に説明して行きます。 MTA(Exim) から IMAP へ流しこむ話、 fetchmail で外部 POP サーバからメールを取って来る話、 そして、謎の呪文と言われ悪名高き procmail のレシピの順で説明します。

Exim

MTA には Debian 標準の Exim を使って[3]いました。 .forward ファイルは次のようになります。 この設定で受け取ったメール全てを procmail に渡します。 とーぜん MTA が違えば違ってきます。

# Exim filter
pipe "/usr/bin/procmail"

Exim は次のページを参考にしました。 でも Debian なので eximconf で設定しただけで使えちゃいました。 設定ファイルを手で編集など結局する必要はありませんでした。

fetchmail

直接受け取るメールは .forward の設定で行けますが、 外部の POP サーバからメールを取って来る場合には fetchmail が便利です。

コマンドラインでデーモンモードで起動することができます。 ポーリング時間間隔を秒数で指定します。

fetchmail --daemon 600

でも面倒なのでデーモンモードの設定は .fetchmailrc に書いちゃうのが 簡単です。

# fetchmailをデーモンとして動かす。ポーリング間隔を指定。
set daemon 600
# エラーメールを相手に返さない
set nobouncemail

# 共通設定
defaults
    # プロトコル
    protocol pop3
    # メールをサーバに残す。
    #keep
    # MIME デコードはしない
    no mimedecode
    # procmail に渡す
    mda "/usr/bin/procmail"

# pop server 1
poll 1.exsample.org
    user foo
    password abcdefgh

# pop server 2
poll 2.exsample.org
    user bar
    password ijklmn

procmail

procmail のレシピはとっても難解でわかりにくいです。 説明を読むよりも実際に使っているものを見た方がずっと参考になると思います。

私は次のものを使っています。 見ての通りメーリングリストからのメールを振り分けするのに メーリングリスト名を直接書くことなどしていません。 この設定で私の参加している数十のメーリングリストの 自動振り分けができています。 次の順番でレシピを書いて行けばうまく行くでしょう。順番が大切です。

まず全体の環境の設定。

PATH=/bin:/usr/bin:/usr/local/bin
MAILDIR=$HOME/Maildir
DEFAULT=$MAILDIR/
LOGFILE=$MAILDIR/procmaillog
LOCKFILE=$HOME/.lockmail

spam フィルターの設定。bsfilter の例です。 spam 格納は spam-YYYYMM というように月ごとに分けています。 特に分ける必要を感じないのなら spam でいいと思います。 これは単に数を数えて消す[4]のに便利だからです。

### spam filter
## bsfilterを使っています。
:0 fw
| /home/kose/bin/bsfilter --pipe --insert-flag --insert-probability

:0
* ^X-Spam-Probability: *(1|0\.[89])
.spam-`date +%Y%m`/

:0
* ^X-Spam-Probability: *0\.[567]
.spam-maybe/

次にメールの経路情報を消します。 spam の場合はメールの経路情報を見ますが、その他のメールは 見ることはないのでここで消しちゃいます。 もちろん消す消さないはお好みに応じて。spam filter の付ける 「 X-Spam-Probability: 」のようなヘッダも ここで消しちゃって良いかもしれませんし。

## Remove some Headers

:0 fw
| formail -I"Received:"

メーリングリストからのメールを振り分けします。 メーリングリストソフトが付ける特別なヘッダを使って振り分けします。 「$MATCH」は直前にマッチした文字列です。 これを使うことでメーリングリスト名を直接書かなくとも振り分けできるという 仕掛けです。

### Mailing Lists

:0
* ^x-ml-name: \/.*
.$MATCH/

:0
* ^mailing-list: list \/[^@]+
.$MATCH/

:0
* ^mailing-list: contact \/.*-help@
.`echo $MATCH | sed -e 's/-help@//'`/

:0
* ^(x-beenthere|mail-followup-to|x-apparently-to): \/[^@]+
.$MATCH/

:0
* ^(errors-to|sender): owner-\/[^@]+
.$MATCH/

:0
* ^list-post: <mailto:\/[^@]+
.$MATCH/

cron からのメールは cron フォルダーへ。

なお、「あるファイルが修正されていたら uuencode で送る」 というのを cron で実行しているのでそれを自動展開しています。 ま、こんな使い方をしている人はいないかもしれないのだけれど、 procmail から別なことを実行させるサンプルとして見てください。

uudecode したものは c (コピー)じゃなくても良いのだけど cron グループはまとめて Gnus で expire しています。 グループパラメータでこういうふうに。

	((expiry-wait . 30)
	 (total-expire . t)
	 (expiry-target . delete))
### cron
:0
* ^from:.*cron
{
 :0 Bc
 * ^begin
 * ^end
 | cd /home/kose/cron && uudecode

 :0
 .cron/
}

ここまででほとんどのメールの振り分けが済んでいます。 自分宛てのメール、ダイレクトメール(自分で許可した広告メール) だけが残っています。

自分の会社の人からのものは tamra へ。 メーリングリストからの自動応答メールは ML へ。 SUbject: に Re: があったり、references があったら direct へ 振り分けします。

残ったものは DM へ振り分けします。 時々いただく質問メール、ファンレターが direct ではなく DM へ振り分け られちゃいますが、しかたがないので MUA (Gnus) で移動しています。 返事は Re: が付くので次からは direct へ振り分けられます。 最初の一通目だけが DM へ行っちゃいます。 (これを自動で判別する方法ってあるかなあ)

### direct
:0
* ^(to|cc|bcc):.*kose@
{
:0
 * ^from: .*tamra.co.jp
 .tamra/

# fml, Majordomo
 :0
 * ^from: (owner-|Majordomo@)
 .ML/

# fml
 :0
 * ^from: .*-admin@
 .ML/

 :0
 * ^subject: re:
 .direct/

 :0
 * ^references: .*@
 .direct/
}

:0
.DM/

基本は以上です。 少し調整すれば誰が使ってもそれなりにうまく行くと思います。たぶんね。

change mail box

メールボックスの変換の話。 この章の存在を知っていればもうメールサーバの乗り換えに躊躇することは なくなるでしょう。

Exchange

UW-imap/imap-utils には icat, ifrom, imapcopy, imapxfer が あるのでこれらを利用する方法があります。 ふたつの IMAP サーバを間で転送しちゃうわけです。

また、既に IMAP が稼働しているメールボックスを Maildir に変換するのなら isync ってのを使う方法もあります。 これはサーバのメールボックスと同期を取るソフトです。 オフラインでメールを読み書きするような環境用のツールのようです。

でもね、これ、Maildir に保存されるファイル名(ls の順)と MUA で読む番号の逆順になっちゃうんですよねえ。 なんとなくこれでは気持悪いので使いませんでした。 ま、2回転送しちゃえばいいかもしれないけどそれも面倒だしってこと。

で、結局今回は原始的に力技のスクリプトでやってしまったというわけです。

mbx -> MH -> Maildir

UW-imap の mbx 形式を icat(UW-imap/imap-utils) で mbox で掃き出させ、 awk で MH 形式の数字ファイルに変換し、 それを procmail に渡して Maildir にしています。 (なんて回りくどいんだ[5]) この時 procmail は既に振り分けが済んでいるので レシピは単純に ここ とだけ記述したものを使います。

procmail ではなく maildrop というのでも Maildir にできるらしです。(使ったことない)

awk や sed を使う場合はそのスクリプトを here document で書いて 1本のシェルスクリプトにしちゃうってのは良く使う手です。 ファイルを喰わせる部分がシェルスクリプトでフィルター処理するんです。 使いすてスクリプトを書く時でも、メンテナンスするにも、 後から見る時でもひとつにまとまっていると見易いからですね。 編集もいっこの方が簡単ですからね。

もちろん Perl や Ruby でもっとスマートに書けるなら....、 というかどうせ使い捨てなのだから慣れ親しんだもので手早く済ませる のが良いでしょう。

#! /bin/sh

# UW-imap/mbx -> MH -> Maildir script. using icat, awk and procmail.

HMHOME=/home/kose/tmp/Mail
MAILDIR=/home/kose/Maildir

mkdir $HMHOME
mkdir $MAILDIR

## make split script
cat<<EOF > $HMHOME/.mbx2mh
#! /usr/bin/awk -f

BEGIN{i=100000;}
/^From /{
  close(f); f=++i;
}
{print>f}
EOF

chmod +x $HMHOME/.mbx2mh

## make .procmailrc
echo "MAILDIR=$MAILDIR" > $HMHOME/.procmailrc.in

cat<<EOF >> $HMHOME/.procmailrc.in
PATH=/bin:/usr/bin:/usr/local/bin
DEFAULT=$MAILDIR/
LOGFILE=$MAILDIR/procmaillog
LOCKFILE=$HOME/.lockmail
:0
EOF


## uw-imap/mbx -> MH -> Maildir
cd /home/kose/imap

for D in [a-z]*;
do
  echo ".... $D"
  mkdir $HMHOME/$D
  cat $HMHOME/.procmailrc.in > $HMHOME/.procmailrc
  echo ".$D/" >> $HMHOME/.procmailrc
  #
  cd $HMHOME/$D
  icat $D | $HMHOME/.mbx2mh
  for F in *;
  do
     procmail -m $HMHOME/.procmailrc < $F
  done
#  rm -rf $HMHOME/$D
  cd .. ; rm -rf $D;
done

unread -> read

変換を行なって早速 Gnus でメールを読んでみました。 そしたら全てのメールが未読状態でした。

Gnus ならグループバッファでカーソル行のグループ内の記事全てを 既読にしたいのなら“c”を押せば良いわけですが、 それにはすっごーい時間がかかることがわかりました。

なのでここでもスクルプトで既読にしちゃいます。

Maildir(5) -- (maildir - メイル受信用ディレクトリ) を見ると次の順だが、うーむ、この :info は実装によるなのか?

	new/uniqueの内容を表示する。
	new/uniqueを削除する。
	new/uniqueをcur/unique:infoにrenameする。

ここでは他がそーなってたから :2,S を付けました。 経験側と言うか、右へ習へをしたというか、ってことですね。

#!/bin/sh

# new/* -> cur/*:2,S (new mail to road mail)

MAILDIR=/home/kose/Maildir

cd $MAILDIR

for D in .[m-z]*
do
  echo = $D =
  cd $MAILDIR
  cd $D/new
  for F in 1*
  do
     if test -f $F; then
	 echo mv $F ../cur/"$F:2,"S
	 mv $F ../cur/"$F:2,"S
     fi
  done
done

MH -> Maildir (read)

組み合わせ的応用的スクリプト。 MH 形式を Maildir にして、既読にしています。

途中「 for F in ? ?? ??? ???? ?????; 」としているのはですねえ、 この方がファイルのタイムスタンプ通りになるからってこと、なんですね。

#! /bin/sh

# MH -> Maildir script. using procmail.

MHHOME=/home/kose/Mail
MAILDIR=/home/kose/Maildir

## make .procmailrc
echo "MAILDIR=$MAILDIR" > $MHHOME/.procmailrc.in

cat<<EOF >> $MHHOME/.procmailrc.in
PATH=/bin:/usr/bin:/usr/local/bin
DEFAULT=$MAILDIR/
LOGFILE=$MAILDIR/procmaillog
LOCKFILE=$MAILDIR/.lockmail
:0
EOF


## MH -> Maildir

cd $MHHOME
for D in [a-z]*;
do
  echo ".... $D"
  cat $MHHOME/.procmailrc.in > $MHHOME/.procmailrc
  echo ".$D/" >> $MHHOME/.procmailrc
  #
  cd $MHHOME/$D
  for F in ? ?? ??? ???? ?????;
  do
     if test -f $F; then
       echo -n "$F",
       procmail -m $MHHOME/.procmailrc < $F
     fi
  done
  echo ""
  # new/* -> cur/ and road.
  echo "...... proc $MAILDIR/.$D read -> road."
  cd $MAILDIR/.$D/new
  for F in 1*
  do
     if test -f $F; then
#        echo mv $F ../cur/"$F:2,"S
         mv $F ../cur/"$F:2,"S
     fi
  done
done

spam filter

spamを撃退しよう という方向もありますが、ここでは受信してしまった spam を分別する 「spam フィルター」のお話。

SpamAssassinBogofilterifile、 Gnus に含まれる spam-sat.el と渡り歩いて、今度は bsfilter なのです。 いろいろ使ってみるのがいいのです。 あ、この順に性能が良いというわけではないです。念のため。

これらを使ってみると 「ベイジアンスパムフィルタは使い始めはとっても調子良い」 ということを経験して来ましたが、 bsfilter もその経験則通りにやっぱり良い感じなのです。

今まではスパムフィルタの統計学習を対話的に MUA 上から行っていましたが (だって mbx だったんだもん)、 こんどは週に1回程度にまとめて (cron) で実行します。 これで、タイムスタンプ比較で新しいものだけを処理対象にしています。 既読で、統計学習未処理のメールに対して行うつもりのスクリプトです。

#!/bin/sh

MAILDIR=/home/kose/Maildir
UpFlag=$MAILDIR/update
BSF=/home/kose/bin/bsfilter

cd $MAILDIR
for F in `find .spam*/cur -name "*S" -newer $UpFlag` 
do
  if test -f $F; then
      echo "$BSF --add-spam --update $F"
      $BSF --add-spam --update $F
  fi
done

for F in `find .[a-rt-zA-Z0-9]*/cur -name "*S" -newer $UpFlag` 
do
  if test -f $F; then
      echo "$BSF --add-clean --update $F"
      $BSF --add-clean --update $F
  fi
done

date > $UpFlag

MTA でやる方法は ここ。 この方法は ifile の場合だけど、bsfiler 用に少し修正すれば同じでしょう。 こういうの も書いたんだけど全く定量的じゃない。

やっぱり spam フィルターの性能比較をやりたいなあ。

どっかのメーリングリストの spam も含めた1年分のアーカイブが欲しいな。 それで spam, ham を分類して統計処理させて、 他のメーリングリストでどういった結果になるかグラフを書いて比較する。 サンプルがあればやりたな。 (課題だよ課題。でもほんっとに暇にならないとやらないかもね)

exercise

さあ、ここまでやったらなもう完璧でしょう。 でも最後にもうひとつの TIPS を。

1日に200通ものメールを受け取り、振り分け間違いがあった場合に 1週間もほったらかしにしたら手作業で振り分けするのなど気が 遠くなってしまいます。 (Gnus のような素晴しい MUA を使えば簡単なんですけどね)

なので、2週間ぐらいは慣らし運転をしましょう。 procmail のレシピも実際受け取るメールで実験できますし、 振り分け間違いメールを見たら .procmail を修正して再度 procmail に 渡してみれば良いのです。

慣らし運転が終ったら、慣らし運転期間中のメールを全て消して 再度その期間のメールを流し込めば良いわけです。 過去に遡ってメールを受信したようにすれば良いわけです。

procmail レシピ先頭に次の記述をすれば、 全メールを (gziped) mbox 形式で保存できます。 c (コピー)なので全メールが保存された上でこれ以降のレシピが適用されます。

:0 c
| gzip -d > Allmails.gz

こうしてメールの振り分け、spam filter の学習を試し、 Allmails.gz(mbox) -> MH -> procmail(Maildir) ってなことで再受信したようにすれば良いんです。

これだと実環境でいったん試してみて動きを確認できるし、 IMAP server の性能の見極めもできるのです。

半月ほど試せば 2,000 通ぐらいのメールが入ってくるので十分な試験が できるという寸法です。 やり直しがきくということは安心感があって良いですよね。

では happy mail life! を

今月の update

update

今月は次のものがリリースされました。

マイブーム

なんだろ? 「Namazu だ Namazu!!」 ってことで来月号に、つづく。


Footnotes:
[1]  やっぱりこういう GNU 的な名前には魅かれるものがある!?!?
[2]  もちろん「書いていてつまんな〜い」ということ。
[3]  だって他のに入れ替えするの面倒じゃん。
[4]  毎月自動で spam/ham のグラフを書いてくれる procmail レシピが欲しいよね。 う〜んとぉ、$LOGFILE 解析ツールという方向の方がいいか。 メールを spam フォルダ(から/へ) 移動した記録も必要だよなあ。 どんなのがいいかなあ。
[5]  ここでは mbx -> MH を awk でやっていますが、 「icat folder | formail -ds procmail -m ./.procmailrc」 で直接 mbx -> Maildir ができると教えてもらいました。 その方が簡単ですね。


変更履歴
Copyright (C) 2004 KOSEKI Yoshinori. < kose@meadowy.org >