Procmail の紹介とレシピの書き方

[ 日本語 | English ]


Procmail とは

基本的にはメールの振り分けを行うソフトウェアです。 Procmail が利用可能な環境であれば簡単なレシピを記述しておくだけで、 サブジェクトや差出人ごとに振り分けや携帯端末等へ転送を行ったり、 広告や勧誘などの spam の削除を行うことができます。
また、ここでは(まだ)紹介していませんが、スコア機能 (man procmailsc) を使えば、 単なる文字列マッチングでは表現できない高度な振り分けルールを記述することもできます。
詳しくはこちらを参照…。

メールの振り分けよりも spam 退治に興味のある方は 以下のサイトも眺めておくときっと役に立ちます。
junkfilter
The SpamBouncer
Panix Help System 公開されたレシピ
SpamAssassin
Vipul's razor
SpamProbe
Bogofilter
bsfilter
spamarchive
Brightmail Inc.
Yet Another antiVirus Recipe
Enhancing E-Mail Security With Procmail


リンク、転載はご自由にどうぞ(知らせていただけると嬉しいですが)。 ただし、ここで紹介する内容は各自の責任において利用してください。 誤って大事なメールを削除してしまっても私にはどうすることもできませんから。
尚、お約束ですが「未承諾広告※のレシピについて」の様なサブジェクトのメッセージは私に届きません。


JAIST の人は

JAIST 内で Procmail を利用する方法


レシピの書き方

Procmail のレシピは、 ホームディレクトリのレシピファイル ($HOME/.procmailrc) に記述します。 レシピファイルには複数のレシピを記述することが可能で、 1つのレシピは行頭の「:0」から始まり、次の「:0」かファイルの終端(EOF)で終わります。 また、行頭に "#" を記述すればコメントを記述することもできます。 基本的なレシピの構成は Procmail にメッセージ (メール) を渡す方法を指定する「フラグ」、 処理を行うメッセージを特定する「条件文」、 メッセージが条件に該当した場合に行う処理を示す「アクション」の3要素から構成されます。
実際に使えるレシピの記述例はこのページの後半で紹介しています。


フラグ

「フラグ」にはメッセージを処理する際の Procmail への メッセージ (メール) の渡し方を記述します。 フラグの記述には以下のものがあります。 なお、表中のメッセージのヘッダ (header) とは、 本体 (body) に含まれるもの以外のものを指し、 送り主や送り先のアドレス、サブジェクト、 メーラーの種類 (Outlook、Becky!、Sylpheed…) 等の情報が含まれています。 メッセージの本体とはメッセージに書かれた本文そのものを意味します。

フラグフラグの説明
Hデフォルト (記述が省略された場合) の動作。 メッセージのヘッダだけを条件文で検査します。
Bメッセージの本体だけを条件文で検査します。
HBメッセージのヘッダと本体の両方を条件文で検査します。
D検査の際に大文字と小文字を区別します (デフォルトでは区別しない)。
A同じブロックレベルに記述された A や a の用いられてないレシピが該当した場合にだけ検査します。同じ条件文の後で更に条件分岐したい場合に用います。
aA と同様だが、手前のレシピが正しく実行された場合にだけ検査します。
EA の逆で手前のレシピが実行されなかった場合にだけ検査します。
ea の逆で手前のレシピが失敗した場合にだけ検査します。
hアクション (実行部) にメッセージヘッダだけを渡します。
bアクションにメッセージ本体だけを渡します。
hbデフォルトの動作。アクションにメッセージのヘッダと本体の両方を渡します。
fパイプをフィルタとみなす。パイプを通して処理したメッセージを以降のレシピに渡すことができる。要するに下処理 (前処理) ができます。
c続くレシピのためにメッセージのコピーを残す。他のアドレスへのメッセージ送信や、ブロック文で単一のメッセージにたいして複数の処理を行いたい場合に用います。
wフィルタやプログラムの終了を待ち、その返り値をチェックする。失敗した場合にはテキストが処理されることはありません。アクションの実行内容を確実に行わせたい場合に指定すると良いみたいです。
Ww と同様だけど、処理の失敗に関するメッセージを出力しない。
iあらゆる書きこみエラーを無視して検査します。a や E などのオプションを指定している場合にファイルシステムの問題で書き込みに失敗する (条件文は満たしていたのにレシピが失敗する) などして、期待した動作が得られなくなるケースに対処します。
rメールが空行 (メールの終端を示す) で終わっているかどうかをチェックしないで検査します。…よくわからない…
:[ロックファイル]フラグに続いて記述し、このレシピの実行の際にロックファイルを用いることを示します。ロックファイルは省略可能。

以下に示すサンプルのレシピはメールのヘッダ、本体を問わず、 ある1行の行頭が大文字の「HOGEHOGE」で始まるメールを受け取ると、 続くレシピでも処理が行えるようにメッセージをコピーしつつ、 ヘッダ部分だけを gzip によって圧縮して、hoge.gz というファイルに追加していく レシピです (意味無し)。 ファイルに触るため、一応、ロックも指定しておきます。

:0 HBDhc :
* ^HOGEHOGE
| gzip -fc >> hoge.gz

条件文

「条件文」はフラグに続く行頭が「*」で始まる行を指し、 メッセージを選択する条件を正規表現によって記述します。 条件文は省略することが可能で、その場合は無条件に適用されることを意味します。 また、1つのレシピに複数の条件文を記述すことが可能で、 複数の条件文が存在する場合には全ての条件を満たした場合にだけ適用されます。 条件文で用いられる記号には以下のようなものがあります。

条件条件の説明
!条件の否定を意味します。
$条件文に現れる (環境) 変数をシェルの様に評価することを意味します。
?指定したプログラムの終了コード (0:成功か1:失敗) を利用します。
<長さが後に記述されたバイト数 (10進) 以下であれば実行されます。
>長さが後に記述されたバイト数 (10進) 以上であれば実行されます。
変数名 ??指定された値と変数を比較します。
\クォートします。 \/ と記述することで該当箇所を変数 MATCH に格納します。

以下に示すサンプルのレシピは、メッセージの本体だけ(フラグが B)を検査します。
検査の内容は、本文の行頭(^ が行頭を意味する)に $HOGE ($HOGE は、先に代入文が記述されているが条件文を示す * の後に $ が指定されていないので HOGEHOGE ではない「何か」)が無く(!によって否定されている)、 行頭に大文字小文字を問わず(フラグに D が指定されていない) FOOBAR と書かれていて (今度は条件文の $ で $FOO が展開される) 、 test プログラムの結果によって、 ホームディレクトリに「anti_hoge」というファイルの存在が確認されて、 メッセージ本文が5000バイト以下であって、4900バイト以上であるメッセージが、 変数 CHECK に yes が代入されている (例では該当する) メッセージを探すものです。
さらに、最後の条件文に書かれた \/ によって、 行頭が「ADDR」(または「addr」…) で始まる行の 「ADDR」に続く空白文字の後に記述された文字列を変数 MATCH に格納し、 最終的にその文字列を anti_hoge というファイルに書き足していくものです (意味無し)。

# SAMPLE
HOGE=HOGEHOGE
FOO=FOOBAR
CHECK=yes
:0 B :
* ! ^$HOGE
* $ ^$FOO
* ? test -f $HOME/anti_hoge
* < 5000
* > 4900
* $CHECK ?? yes
* ^ADDR \/.*
| echo "$MATCH" >> anti_hoge

アクション

「アクション」にはメッセージが全ての条件を満たした場合に実行される 内容を1行で記述します。 複数のアクションを実行したい場合には、 アクションブロックを用いるか外部プログラムを呼び出して対応します。 アクションの記述には以下のものがあります。

アクションアクションの説明
!指定されたアドレスに転送します。スペースで区切れば列挙可能。
|メッセージを他のプログラムに渡す。シェルから呼び出せるプログラムを実行したい場合に用います。
{..}メッセージに新しい条件文を付加したい場合や、複数のアクションを実行したい場合に「{」「}」で括ることでネストします。
ファイル名メッセージをファイルに格納します。ロックを指定するのを忘れずに。
ディレクトリ名メッセージにユニークなファイル名をつけてディレクトリに格納します。
ディレクトリ名/.メッセージをディレクトリに MH 形式 (連番) で格納します。
/dev/nullメッセージを廃棄します。復元不可。


Procmail のレシピに用いられる環境変数

代表的な「環境変数」には以下のものがあります。

環境変数環境変数の説明
COMSATデフォルトが on になっているメッセージの到着通知を止めることができます (COMSAT=no)。または service@ や @hostname、service@hostname の様に biff の設定のカスタマイズもできます。
DEFAULTどのレシピにも適合しなかったメッセージが格納されます。
DELIVEREDメールを送信したことにします (DELIVERED=yes)。その後も放っておくとメールは失われる…(?)。
DROPPRIVSProcmailの実行特権 (権限?) (root, wheel...) を落とします。共有の rc ファイルとかを用意するときに変なことされないようにします(?)。
EXITCODEProcmail の終了時の返り値を指定します。この変数が空に設定されていた場合 (EXITCODE=) には、TRAP の終了時の返り値が用いられます。
HOST指定された値が、procmail (rc) が呼び出されたホストと違う場合には以降のレシピを処理せずにその場で終了します。コマンドラインで実行する場合など、複数の rc ファイルが指定されていた場合には次の rc ファイルが読み込まれます (最後の rc ファイルの場合は終了)。普通は使わないと思います…誤用するとメッセージが消えます (rc ファイルの冒頭に HOST= と書くとか)。
INCLUDERC他の rc ファイルの内容を現在の rc ファイルに取り込む場合に用います。
LASTFOLDERProcmail が処理を行う度に、最後にメッセージを降り分けた先 (プログラム or フォルダ) が設定されます。
LINEBUF内部で用いられるラインバッファのサイズ。128以上の値を指定します (デフォルトは2048)。
LOCKEXT
LOCKFILEデフォルトのロックファイルを指定。
LOCKSLEEPロックが獲得できなかった場合のリトライまでの待ち時間 (デフォルトは8[sec])。
LOCKTIMEOUTロックが何らかのエラーで残ってしまったと判断して強制的に排除するまでの時間 (デフォルトは1024[sec])。
LOGこの変数に何か割り当てておくとログ出力 (の最初、送り主などの出力の前) に加えられます。
LOGABSTRACTログの出力を抑制します (LOGABSTRACT=no)。デフォルトでは送り主、サブジェクト、日付、サイズを出力します (LOGABSTRACT=all)。
LOGFILEProcmailや呼び出された外部プログラムの処理内容とその結果の出力先。指定しないとメールで届きます。以下は 2ch で紹介されていた例(目から鱗):
LOGFILE=/path/`date +%y-%m`
MAILDIRProcmail による処理が実行されるディレクトリ。メールの格納場所。
MATCHレシピ中の \/ によって切り出された値が格納されます。
MSGPREFIXメッセージの格納先が mbox 形式や MH 形式でなく、ディレクトリが指定されていた場合に、メッセージを保存する各ファイル名の先頭部分に用いられます。
NORESRETRYプロセステーブルが一杯になった場合や、メモリの不足などで処理が実行できなかった場合にやり直す回数 (デフォルトは4回)。やり直しの間隔は SUSPEND によって決められます。
ORGMAILファイルシステムのトラブルなどでメールが取り込めなかった場合に、最後の手段として利用する格納場所。ここも駄目な時は送信者に通知されます。
PATHコマンドを検索するパス。formail へのパスが通っているようにすること。
PROCMAIL_OVERFLOW何らかの値が設定されている場合にはオーバーフローを検出します。
PROCMAIL_VERSIONProcmail のバージョン。
SENDMAILフォワードするときに用いる Sendmail の場所。$SENDMAIL $SENDMAILFLAGS $@ って感じに実行されます。
SHELL利用するシェル。
SHELLFLAGSシェルに渡すフラグ(?)。$SHELL $SHELLFLAG $* って感じに実行されます。
SHELLMETASフィルタやプログラムを指定している行に設定されていると SHELL の代わりに実行されます(?)
SHIFT正の値を与えると sh の shift コマンドと同様の意味を持つ(?)。外から Procmail を呼び出すに渡す属性を抜き出すのに便利らしい (??)。
SUSPENDシステムの問題で処理が実行できなかった場合のリトライするまでの間隔 (デフォルトは16[sec])。
TRAPProcmail の終了時に実行する処理を記述します。tmp ファイルを消すときなどに使える。
TIMEOUT子プロセスがササってしまったと判断するまでの時間 (デフォルトは960[sec])。
UMASK8進数で指定するUMASK。デフォルトは077(?)。
VERBOSEレシピの動作を確かめる場合などに詳細なログを得るには VERBOSE=ON とします (デフォルトはオフ)。

VERBOSE=ON なログを眺める際に有効な Vim 用の設定を Creelan さんが紹介しています
見終ったら :g/^procmail/d とか打ってコンパクトにしてしまいましょう。


レシピの記述例

注意

ここで紹介しているいくつかのレシピはある程度の spam を 自動的に捨てることが出来ますが、決して万能ではありません。 また、使い方を誤ると大事なメールまで消してしまうことになります。 実際にレシピを利用する際には、 ご自分の環境で十分なテストをしておくことをお薦めします。

条件文の例と NG ワードレシピの数ヶ所で、 誤って echo の後の環境変数を '"' で括るのを忘れていました。 例えば、サブジェクトに '*' が単独で入っている場合 (例: "* hoge **")、 サブジェクトを代入した環境変数 ($MATCH等) を '"' で括っておかないと echo の仕様で '*' がディレクトリの内容 (ファイルやディレクトリ名) に置き換えられてしまいます。 この際、ディレクトリやファイルの名前に削除対象となるキーワードが入っていると大変なことになるので気をつけてください。

レシピの中で日本語を扱っているものについて、 レシピファイル自体の文字コードが違っていると正しい処理ができません。 ここで紹介しているレシピでは、レシピファイルを EUC で記述し、 「nkf -e」(実際にはデコードと半角への置換も併せて「nkf -meZ1」or「nkf -meZ2」) によって比較文字列を EUC に変換して利用することを前提にしています。

私のレシピでは"ISO-2022-JP"という文字列が サブジェクトに含まれているかどうかでエンコードの有無を判断しているため、 サブジェクトが生 JIS のまま送られて来たりすると チェックを抜けてしまうので気をつけてください。 一応、レシピ中の "* ^Subject:.*iso-2022-jp" の行を削除すれば対処できます。

sed による処理について、 バージョンによってはブラケット表現 ([:space:], [:punct:] 等) をサポートしていないことがあるので気をつけてください。 正規表現について詳しく知りたければ書籍を買うか、 他の紹介ページ (例えば、正規表現最新リンク集正規表現メモなど) を参考にしてください。ただし、Procmail の正規表現自体は微妙に違います。

ここで紹介するほとんどのレシピはメッセージをMHの連番形式で保存するため、 ロックを用いていません。 もし mbox 形式を用いてメッセージを保存するのであれば、 "前のメッセージが保存される前に次のメッセージが書き込まれる" などの事故を防ぐためにロックを利用すべきです。 NFS 等で云々という方はシステム管理者に相談してください。


基本ヘッダ

ヘッダにはレシピから呼び出すプログラムへの PATH や、 いくつかの基本設定を記述しておきます。 設定ファイル $HOME/.procmailrc には ヘッダに続いて各レシピが記述されることになります。 各変数の意味は先に述べているレシピに用いられる環境変数を参照してください。
もちろん、ここで指定しているメールアドレスや path 等は人、及び環境によって異なります。

SHELL=/usr/bin/sh
PATH=/bin:/usr/bin:/usr/local/bin:$HOME/bin
ADDRESS=myname@mydomain.jp
SENDMAIL=/usr/lib/sendmail
LOGFILE=$HOME/tmp/procmail.log
DEFAULT=/var/mail/myname
#LOGFILE=$HOME/tmp/procmail`date +%Y%m`.log
MAILDIR=$HOME/Mail
MLDIR=$MAILDIR/ML

転送レシピ

最初はお手軽な到着したメッセージを他のメールボックスにも転送する例です。 最初に登場するフラグの c を外すとコピーを残さずに転送することもできます。 ただし、その場合は無条件で実行されるレシピを後 (ブロック内) に追加しておかないと、 先の条件にマッチしなかったメッセージが警告なしに破棄されてしまいます。
ところで、ここでは最初の例として紹介していますが、 このレシピを先頭に書いてしまうと、 spam メールなどの必要のないメッセージまで転送してしまうことになります。 だから、 実際には「転送しないメッセージの振り分け」レシピの後に記述することになります。

# サイズが5000より小さく、「<html>」と本文に書かれていなければ転送
:0 Bc
* < 5000
* ! \<html\>
! myname@other.domain.jp

# 上記に該当せず、サブジェクトが「PHS」で、かつ、
# 自分宛のメッセージであれば転送
:0 Ec
* ^Subject: PHS
* $ ^To:.*$ADDRESS
! myname@other.domain.jp

:0
$DEFAULT

Spam 撃退系レシピ

spam 宣言が「!広告!」から「未承諾広告※」に変更されました。 とりあえず、 「未承諾広告※」とか「未 承 諾 広 告 ※」という文字列を見つけたら ゴミ箱 (例では trash というディレクトリ) に捨てるレシピを考えてみます。

以下に示すレシピでは、サブジェクトにエンコードされた文字列が含まれていれば、 環境変数 $MATCH にサブジェクトを格納したうえで、 "nkf -meZ2" によって MIME のデコード、EUC への変換、 および全角から半角への変換を行っています。 sed は [] 内で指定した空白やタブ、 改行文字などの制御文字を置換によって消しています。 そして最後に簡略された文字列に対して egrep により正規表現を用いたマッチングを行います。 もちろんこの場合のレシピファイルの文字コードは EUC です。
sed の利用とレシピの改良に関して 広松さん<matsuan@ca2.so-net.ne.jp> からアドバイスをいただきました。ご報告ありがとうございます。

# Spam filter
# 「未承諾広告※」と書かれているメールをゴミ箱へ捨てる
:0
* ^Subject:.*iso-2022-jp
* ^Subject:\/.*
* ? echo "$MATCH" | nkf -meZ2 | sed 's/[[:space:]]//g' | egrep '未承諾広告※'
$MAILDIR/trash/.

「未承諾広告」って書かれてたら全消しする場合は以下のようにします。 「!広告!」に比べて表現がストレートなので全消ししやすいかも。

:0
* ^Subject:.*iso-2022-jp
* ^Subject:\/.*
* ? echo "$MATCH" | nkf -me | egrep '未承諾広告'
/dev/null

「間違えたのは私だけじゃないよね?」という思いを込めて。 '※'と'*'を間違えている業者もいるはず…倍角の'*'は nkf の 'Z' オプションによって半角に変換されるので '*' を指定しています。

:0
* ^Subject:.*iso-2022-jp
* ^Subject:\/.*
* ? echo "$MATCH" | nkf -meZ2 | sed 's/[[:space:]]//g' | egrep '未承諾広告[※*]'
$MAILDIR/trash/.

以下のレシピはいいじまさん<delmonta@ht.sakura.ne.jp>に紹介していただいた、 韓国語の「広告メール」が MIME エンコードされずに(生 EUC-KR)送られてきた場合のレシピです。 他国語のエンコードを弾くレシピも紹介しています。

# サブジェクトに韓国語で「広告」書かれたメールを捨てる。
:0
* ^Subject:\/.*
* ? echo "$MATCH" | perl -ne 'exit 1 if /\xB1\xA4\xB0\xAD/'
/dev/null

# レシピが EUC で保存されている場合は上のレシピと同等です。
:0
* ^Subject:\/.*
* ? echo "$MATCH" | egrep '韻壱'
/dev/null

以下の内容は以前の「!広告!」の時(平成14年7月1日以前)のものです。
次は!広告!」とサブジェクトに書かれた spamを弾くためのものです。 読者が希望購読しているメールのサブジェクトには「!広告!」とつけなくても 良いとのことなので、さくさく弾いてしまいましょう。 不安な人のために下のレシピでは直接 /dev/null 送りにせず、 任意の $MAILDIR にある trash というディレクトリに MH 形式の連番ゴミメールとして、 保存する例を紹介しています。

# Spam filter
:0
* ^Subject:.*iso-2022-jp
* ^Subject:\/.*
* ? echo "$MATCH" | nkf -meZ1 | sed 's/[[:space:]]//g' | egrep '!広告!'
$MAILDIR/trash/.

上記のレシピでは「!」が半角全角両方の場合や、 「広告」の前後(間)に半角全角のスペースが入る場合を考慮しています。 「!激安広告メール!」みたいな例には対応していませんが、 正規表現の箇所を次のようにすれば簡単に防げます。
…というか違反なので出すべきところに出してあげましょう B)

:0
* ^Subject:.*iso-2022-jp
* ^Subject:\/.*
* ? echo "$MATCH" | nkf -meZ1 | sed 's/[[:space:]]//g' |egrep '!.*広告.*!'
$MAILDIR/trash/.

「広告」って書かれてたら全捨てにするにはこんな感じ。 ただし、このレシピだと「○広告発〜」とか「□広告白〜」とかも消えてしまいます。

:0
* ^Subject:.*iso-2022-jp
* ^Subject:\/.*
* ? echo "$MATCH" | nkf -me | egrep '広告'
/dev/null

ブラックリストレシピ

要らないメールばかり送ってくる spam 業者や受信拒否をしたい相手っていますよね。 ここで紹介するのは、 メールを受け取りたくない相手のメールアドレスをファイルに追加するだけで 自動的に捨てることができるレシピです。
注意: サーバ側の設定で完全に受信を拒否できるのでなければ自衛するしかないのです。 一般的に受信を拒否したい相手に対して、 「もう、送ってこないでください。」とか返事を送っても、 相手はメールの送信をやめるどころか「逆上して or 喜んで」、 引き続きメールを送ってくるものだと考えています。

まず、ブラックリストを登録するファイルを作成しておきます (例:~/.blacklist)。 ブラックリストのフォーマットは次の様に1行に1アドレスずつ記述するものとします。

warumono@waru-mono.com
spamer@spaaaam.com
motokano@uso.tekito.co.jp

実際のブラックリストレシピは以下のようになります。
まず、環境変数 BLACKLIST を用いてブラックリストファイルを指定します。 次に、送信者のメールアドレスを "Reply-To:"、"Sender:"、 "From" の順に探して見つかったメールアドレスを環境変数 FROM に代入します。 ダメだった場合には formail によって "From:" ヘッダを抽出して代入します ("From:" はエンコードされていたり、詐称されている場合が多いので優先順位を下げています)。 "^From" の後は日付なのでホワイトスペースまでマッチングさせます。 ちなみに [] の中は '^' とスペースとタブです。
注意:
レシピ中で使用している fgrep は GNU な grep でないとリストの処理が正しく行われない場合があります。 うまく動作しない場合には GNU な grep に最初にパスが通っているかどうか確認してみてください。

# ブラックリストファイルの指定
BLACKLIST=$HOME/.blacklist

# ブラックリストレシピ
# 送信者のメールアドレスを抽出。
# 後のレシピのフラグにEを指定することで上のレシピが実行されなかったら、
# つまり Reply-To、Sender、From のどれかが見つかったら、
# 記述されていたメアドを FROM に代入。
:0
*$ ! ^Reply-To: *\/[^ 	].*
*$ ! ^Sender: *\/[^ 	].*
*$ ! ^From *\/[^ 	]+
{
	FROM = `formail -x From:`
}
:0 E
{
	FROM = $MATCH
}

# ブラックリストファイルがあることを確認した上で、
# 送信者のメールアドレスがブラックリストに含まれているかどうかチェック。
# 見つかったらごみ箱に送っています。
:0
* ? test -s $BLACKLIST
* ? echo "$FROM" | fgrep -iqf $BLACKLIST
$MAILDIR/trash/.

もっとサクサクはじきたい場合には次のようなレシピを書くことができます。 上のレシピは "Reply-To:"、"Sender:"、"From"、"From:" の内、 最初に見つかったヘッダにブラックリストのアドレスが無いかチェックしますが、 下のレシピは4つともチェックします。 同じように "To:" や "Cc:" を追加すれば 「似たようアカウントの他人」宛と一緒に送られてくるメール (onajinamae@hotmail.com等)や、 怪しいエイリアスでまとめて送られるメール (Valued.Clients@anata.no.domain.jp等) も弾くこともできます。

# From:, Reply-To:, Sender:, From のどれかに
# ブラックリストのメールアドレスが含まれていたらゴミ箱へ。
:0
* ? test -s $BLACKLIST
* ? (formail -x From: -x Reply-To: -x Sender: -x From | fgrep -iqf $BLACKLIST)
$MAILDIR/trash/.

最後に述べるのもどうかと思うけど、ブラックリストの中で正規表現を使えないのがこのレシピの欠点です。 .*spamer.*@(yahoo|hotmail).com とか書けるとシンプルだし、 連番アカウント (hoge00, hoge01,..) などを防いだりできるからね。

…連番や似たパターンを持つアカウントからの spam に対応するには、 専用のレシピを用意してしまいましょう。 行頭の "9876543210^0" は条件文の評価に「スコア」を用いる場合の記述の一例です。 ここでは「どちらかの条件に該当したらゴミ箱へ捨てる OR 条件みたいなもの」 と考えておいてください (本家のMLではスコアの最大値より大きい値を与えて他の条件をスキップさせるための常套手段になってます)。 …そのうち時間ができたら詳しく紹介します。納得できない方は "man procmailsc" を…

# 特定文字列を含むアカウントからのメールをゴミ箱へ捨てる
:0
* 9876543210^0 ^From:.*spam.*@(ahoaho|bakabaka).com
* 9876543210^0 ^From:.*akutou.*@(ahoaho|manuke).com
$MAILDIR/trash/.

広告系に全く興味が無ければ、次のような単純なレシピでもメールボックスをだいぶ軽くしてくれる筈です。 これは「info@」で始まる「.net」「.com」ドメインのメールをゴミ箱に捨てます。 ただし、まとも?な業者の方でもこのレシピにマッチするメールを送ってくることがあるので気をつけてください。

# 「info@」対策
:0
* ^From: info@.*\.(net|com)
$MAILDIR/trash/.

NGワードレシピ (サブジェクト用)

ブラックリストレシピの方法を応用して、 サブジェクトに嫌いなキーワードが含まれているメールを捨てしまう、 NG ワードレシピを用意することができます。

まず、ブラックリストレシピの場合と同様の方法で NG ワードを登録する ファイルを作成しておきます (例:~/.ngwords)。 NG ワードは慎重に選ばないと必要なメールまで捨ててしまうことになります。 2つか3つの語を並べるなどして、あまり一般的な用語を指定しないようにしましょう。 実際に受け取ってしまった spam のサブジェクトや、 spam を晒しているサイトをチェックして代表的な用語を抜き出すといいでしょう…。

adult video
viagra
Protect Your Computer Against Viruses for $9.95
Verification Department
…

実際の NG ワードレシピは以下のようになります。

# NG ワードを保存したファイルの指定
NGWORD=$HOME/.ngwords

# NG ワードレシピ
# サブジェクトに NG ワードがあったら削除する。
:0
* ^Subject:\/.*
* ? test -s $NGWORD
* ? echo "$MATCH" | fgrep -iqf $NGWORD
/dev/null

日本語の NG ワード (未承諾広告※、末承諾広告*、18禁、アダルト、結婚相手、恋人探し…等) も指定する場合には、以下のように書くことができます。
この場合、 NG ワードを保存するファイルの文字コードを EUC にしておくことを忘れずに。
「18禁」と「18禁」等のように数字やアルファベット、スペースの半角全角を 区別したく無い場合には、"nkf -me" を "nkf -meZ1" に置き換えて NG ワードを半角で指定します (ただし、カタカナは半角にしない)。
また、下の例では "sed 's/[[:space:][:punct:]]//g'" を使うことで、 空白スペースの類 ([:space:]) と、 「!@#$%*,.」などの英字記号 ([:punct:]) を削除しています。 これによって「18*禁」とか「$E$A$R$N$ \M\O\N\E\Y\」を弾くことができます。 この場合には自分で用意した NG ワードにこれらの記号 (特にスペース) が入っているとマッチングに失敗するので気をつけてください。

:0
* ^Subject:\/.*
{
	DECODED=$MATCH
	:0
	* ^Subject:.*iso-2022-jp
	DECODED=|echo "$MATCH"|nkf -me

	CHECK = `echo "$DECODED" | sed 's/[[:space:][:punct:]]//g'`

	:0
	* ? test -s $NGWORD
	* ? echo "$CHECK" | fgrep -iqf $NGWORD
	/dev/null
}

注意点とか欠点はブラックリストレシピと一緒です。
自分が使っている NG ワードのサンプルを公開します。 このサンプルは "nkf -meZ1" 等によって全角文字(スペース、アルファベット、数字) を半角に変換し、sed によってスペースや記号を消している場合に有効です。 半角全角を区別している場合や、スペースや記号を残している場合には NG ワードを正確に記述する必要があります。NG ワードを大文字小文字も区別して記述したい場合には "fgrep -qf" とするとうまくいくと思います。

他国語エンコードとして ISO-8859-1 (Latin-1) を弾いてしまえない方も居ると思います。 そのような場合には、先に紹介したレシピの冒頭のデコード部分を次のように書いてしまうこともできます。 nkf のバージョンによっては、"mN" (non-strict) と指定してやる必要があります (処理が重い気がします…)。 コピーライトは省略しますが、"Network Kanji Filter Version 2.0 (3/0301/Shinji Kono)" と "Network Kanji Filter Version 2.0 (4/0401/Shinji Kono)" のバージョン (nkf203, 204?) では、次の書き方で期待通りに動作しました。
この場合にマッチさせる文字列については説明が難しいのですが、 弾きたい ISO-8859-1 で書かれた文字列が含まれるサブジェクトを、 強制的に(正当ではない?) EUC 等の文字コードの「文字列」に変換したうえで NG ワードに追加する必要があります。

# とりあえずデコードしてみる
:0
DECODED=|echo "$MATCH"| nkf -meZ1

デコードされたサブジェクトを再利用する必要がない場合で、単に短く書くのであれば次のようにも書けます。

# サブジェクトを強制的にデコード(Z2 は全角スペースを半角スペース2個に変換)
# スペースや記号を削除した後で NG ワードファイルに書かれた文字列をチェック
:0
* ^Subject:\/.*
* ? test -s $NGWORD
* ? echo "$MATCH" | nkf -meZ2 | sed 's/[[:space:][:punct:]]//g' | fgrep -iqf $NGWORD
$MAILDIR/trash/.

NGワードレシピ (本文用)

本文中に書かれた NG ワードを対象としたレシピも簡単に書くことができます。 以下の例は良く見かけるキーワードを元に spam をゴミ箱へ送ります。 データサイズの大きなメールを受け取った場合に大変なことになるので、 忘れずに上限を設定しましょう。 また、ここで紹介しているレシピは、先に紹介したサブジェクト用 NGワードレシピとの併用を考えているので軽めの設定になっています。
注意:
ここまでやるのであれば、 SpamProbeSpamAssassin 等の利用を検討した方が良いかもしれません。

# 20KB を越えないメールの本文に対して直接 NG ワードを指定してフィルタリング
:0 B
* < 20000
* 9876543210^0 (Get|Generic|Herbal|Super) Viagra
* 9876543210^0 (Get|Introducing) VP-RX
* 9876543210^0 (Viarga|Vairga|Vigara|Vagira)
* 9876543210^0 http://.*(instantaccess4u\.com)|(infobizcom\.com)
$MAILDIR/trash/.

ところで、悪質な HTML メールの中には単語の間や単語自体にタグを埋め込むことで 文字列マッチングによるフィルタリングを回避しようとするものがあります。

Gen<!--dummy-->eric <a href="http://dummy.com/">Viagra</a>

このような場合には html2text や w3m 等を使って plain text に変換してしまう方法がありますが、<a>タグで埋め込まれた URI が見えなくなってしまいます (フィルタリングの条件に使えない)。 そのためにはどちらか (多分、URI の方) を捨てるか、2度に分ける方法が必要になります。
また、Lynx は plain text と共に URI を抽出したリストを出力してくれますが、 上記の様な例の場合、参照元に "[1]" の様な番号を付けてしまうのでいただけません (無視しても構わないと思うけど)。

先の文字列を w3m -dump -T text/html や html2text に通した場合の出力例:
Generic Viagra

先の文字列を lynx -dump -stdin に通した場合の出力例:
   Generic [1]Viagra

References

   1. http://dummy.com/

とりあえずここでは Lynx を使った HTML メール対応版を紹介しておきます。 このレシピは Procmail-ML の次のメール以降のスレッドを元に作成しました。
Date: Thu, 28 Aug 2003 12:17:01 +0100
From: Obantec Support <support@obantec.net>
Subject: SpamAssassin low score on a porn spam.
この方法では Lynx によって HTML メールを plain text に変換し、 指定した NG ワードに対して文字列マッチングを行います。 その際、変換時の折り返しによってキーワードが分割されるケースを考慮し、 チェックするメールサイズと同じ長さを指定しています(つまり1行として扱う)。 また、ここで紹介しているレシピはサブジェクト用 NGワードレシピとの併用を想定して軽めの設定になっています。また、<html> タグより後の内容しか処理していません。
注意:
Lynx が "-stdin" オプションをサポートしている必要があります。 また、レシピ中で環境変数 HOST を誤用するとメッセージが消えてしまいます (保存せずに終了してしまうため)。 日本語を処理したい場合には、日本語対応の Lynx を用いるか、 w3m (例: "| w3m -dump -T text/html -cols 20000") 等に変更する必要があります。

# 一旦、コピーを作ってオリジナルと整形版に分けて処理を行う
# sed で <html> から最終行までを切り出して lynx に渡して整形
# 整形版に NG ワードが見付かればエラーを起こしてオリジナルをゴミ箱等へ
# 整形版は HOST を利用して処理を行わずに終了させる (もっと良い方法があるかも)
# LOGFILE=/dev/null によって余計なログを捨てる
:0 cw
* < 20000
{
	:0 bf
	| sed -n '/<[hH][tT][mM][lL]>/,$p' | lynx -stdin -dump -width=20000

	:0 B
	* 9876543210^0 (Get|Generic|Herbal|Super) Viagra
	* 9876543210^0 (Get|Introducing) VP-RX
	* 9876543210^0 (Viarga|Vairga|Vigara|Vagira)
	{ EXITCODE=1 }

	LOGFILE=/dev/null
	HOST
}
:0 e
$MAILDIR/trash/.

…こんなの書いていると何でもありな気がしてきますね。
とりあえず冷静に考えると URL は変換無しの NG ワードではじけるので、w3m を選択するのが正しい気がしてきました。後、改行に惑わされないために sed の処理を付け加える必要があると思います。

# w3m かつ余分なスペースを削除する版です。とりあえず記号は残しておきます。
# 正規表現を使ってもっと賢く書くこともできます (V...ra とか…重いのかしら)。
:0 cw
* < 20000
{
	:0 bf
	| sed -n '/<[hH][tT][mM][lL]>/,$p' | w3m -dump -T text/html -cols 20000 | sed 's/[[:space:]]//g'

	:0 B
	* 9876543210^0 (Get|Generic|Herbal|Super)Viagra
	* 9876543210^0 (Get|Introducing)VP-RX
	* 9876543210^0 (Viarga|Vairga|Vigara|Vagira)
	{ EXITCODE=1 }

	LOGFILE=/dev/null
	HOST
}
:0 e
$MAILDIR/trash/.

大内さんとやりとりしている間に気づいたのですが、 分岐させないバージョンも書けるみたいです(フラグ B になかなか気づけなかった)。
ただ、 この方法は正規表現でも NG ワードファイルでも書けますが、負荷が高そうな気がします。 また、この場合の NG ワードは慎重に選ばないと、 spam 以外のメッセージがサクサク該当してしまう恐れがあります。 上で示した私のサンプルなど以ての外です。

:0 
* < 20000
{
# 正規表現でマッチさせる場合
	:0 B
	* ? w3m -dump -T text/html -cols 20000 | sed 's/[[:space:]]//g' | egrep -iq '(super|generic)(viagra|vp-rx)'
	$MAILDIR/trash/.

# NG ワードファイルを使う場合(記号を削除していないバージョン)
#	:0 B
#	* ? test -s $NGWORD2
#	* ? w3m -dump -T text/html -cols 20000 | sed 's/[[:space:]]//g' | fgrep -iqf $NGWORD
#	$MAILDIR/trash/.
}

Message-Id のチェック

NG ワードやブラックリストに比べて効果はかなり地味ですが、 こんなレシピではじける spam もあります。
このレシピは「妥当でない Message-Id」の付加されたメッセージを捨てるものです。 ちなみに [] の中はスペースとタブです。

# This recipe is an idea of fleet@teachout.org refined by Dallman Ross
:0 
* ^Message-Id:[ 	]*<.*(\$.*-|-.*\$).*@
$MAILDIR/trash/.

他にも「メーラー (MUA) が Message-Id を付けてないメール」を疑ってみる価値があります。先の例では怪しいメーラー (恐らくは spam 配信用) に付けられた「変な Message-Id」をチェックしていますが、「付けない MUA」も怪しいと考えられます (Foxmail とかの例外もあり)。
ただし、この条件をチェックする際に気をつけねばならないことがあります。 例えば、Sendmail (最も使われている MTA) は Message-Id が付けられていないメールに対して Message-Id を付与してしまいます (設定によりますが)。一方、qmail は付与しません (不可?)。つまり、受信者側の MTA によって付けられた Message-Id を持つメールは「実は Message-Id が無かった」かもしれないのです (当然、例外あり)。
ちなみに Message-Id について、RFC2822 的には SHOULD be present なので当然ついてるべきらしいのだけど、MTA が付けたり MUA が付けたりとややこしい上に、qmail はシカトするし、いにしえのメーラー (バークレーとか) も Message-Id を付けない (バージョンや設定による?) ので大変なわけです (当時の RFC822 では optional だったから仕方ないのですが)。
例外についてはホワイトリストなどで拾う必要がありますが大体、次のように書けると思います (ローカルの MTA が付与する Message-Id のフォーマットに依存します)。私は2ヶ月間使ってみて spam しか該当しませんでしたが、例外に備えて /dev/null 送りにはしないようにしましょう。

# 単純に Message-Id が無い場合
:0
* ! ^Message-Id:
$MAILDIR/trash/.

# ローカルの MTA が Message-Id を付与する場合の例
:0 
* ^Message-Id:.*@local_no_mta.jp
$MAILDIR/trash/.

ウイルスメール撃退系レシピ

多くのウイルスメールはサブジェクト、添付ファイルの名前、サイズ、バイナリに含まれる文字列等から判別できますが、それらを逐一紹介することは無理なのでここではウイルスメールの撃退方法の簡単なサンプルだけを紹介します。 真剣にウイルスメールに対応したい方は、 本家の MLYet Another antiVirus Recipe 等を参考にしてみるとよいでしょう。

一般に多くのウイルスメールは、 Outlook の様な余計なスクリプトを実行してしまうメーラを攻撃対象としています。 従って Unix 上でメールを読み書きしている場合には影響のないものが多いのですが、 何百通ものメールを送信してくる spam 系のウイルスにはとてもうんざりさせられます。 ここでのウイルスメールとは主にそういった spam 系ウイルスを指していて、 読まずにサクサク捨ててしまうことを目的としています。
もちろん、メッセージを Windows 側へ転送したり、 Windows 上で Procmail を利用している場合にも有効な方法の1つです (Windows ならワクチンソフトのインストールを奨めますが…)。
注意:UNIX 系のシステムにも有効な危険で悪質なウイルスも存在します。

ウイルスメールを撃退するためには、まずウイルスの事を知らなければいけません。 サブジェクトを見ただけで捨てられる、 有名な LoveLetter ウイルス (サブジェクトが ILOVEYOU) とかもありますが、 タイトルだけ見て捨ててしまった場合、 本物のラブレターだったりするとちょっと困ってしまいます (誤)。 そこで、より正確に判断するために、添付ファイルまで調べることにします。
とりあえずウイルスメールを (メーラではなくて) エディタなどで 生テキストとして開くと、マルチパートメールであることを示す 「Content-Type: multipart/hoge;」のような記述と、 個々の内容を示す「Content-Type: hoge/hoge;」のような記述があることがわかります。 更に、ファイルを添付している場合には「name=hoge.exe」のように、 ファイル名が示されていることが分かります。 また、uuencode によってエンコードされている場合には 「begin (展開後のパーミション) (ファイル名)」のような記述を 見つけることが出来ます。
Klez の用に毎回ランダムなファイル名を用いるタイプには対応できませんが、 多くのウイルスメールは添付されたファイル (Loveletter の場合は Love-letter-for-you.txt.vbs) を実行することで感染するので (Outlookによって強制実行される場合もあります)、 予めこの手のファイルが添付されているのを見つけたら捨ててしまいましょう。 「この手のファイル」や代表的なウイルスメールのサブジェクトは、 トレンドマイクロのウイルスデータベースや、 シマンテックのウイルス辞典などで調べることが出来ます。

これは大流行した Mydoom に対するレシピです。 Procmail-ml に Luis Daniel Lucio Quiroz が流したメール (Message-id: <4016BE19.4090601@asa.gob.mx>) のレシピを参考にしています。十分な動作チェックは行っていません(行末の「\」は継続行)。 以下のレシピでは代表的なサブジェクトのついたメールについて代表的な添付ファイルの存在を確認し、さらにメッセージ中に代表的な文字列があれば /dev/null へ送ります。 また、上記に該当しなくても、ファイルの添付まで確認されたら亜種やランダムな文字列に備えて基本的にゴミ箱に捨てます。最後のコメントアウトした箇所は「ウイルスに関する警告」メールを弾くためのものですが、条件が甘いので誤認識する可能性があります。

# Mydoom 対策
:0
* ^Subject:.*(test|hi|hello|Mail Delivery System|Mail Transaction Failed|\
Server Report|Status|Error|Unable to deliver the message)
{
	:0 B
	* name="(document|readme|doc|text|file|data|test|message|body)\..*\
	(pif|scr|exe|cmd|bat|zip)
	{
		:0 B
		* 9876543210^0 test$
		* 9876543210^0 Mail transaction failed\.
		* 9876543210^0 Partial message is available\.
		* 9876543210^0 Partial message has been received\.
		* 9876543210^0 Error #804 occured during SMTP session\.
		* 9876543210^0 The message contains (Unicode characters|\
		MIME-encoded graphics) and has been sent as a binary attachment\.
		has been sent as a binary attachment\.
		* 9876543210^0 The message cannot be represented in 7-bit \
		ASCII encoding and has been sent as a binary attachment\.
		/dev/null

		:0
		$MAILDIR/trash/.
	}
#	:0 B
#	* Mydoom|Mimail|Novarg
#	$MAILDIR/trash/.
}

これは身近で流行ってしまった WORM_MYPARTY.A (トレンドマイクロ表現) 用のフィルタ例。 でもこれって、すぐ亜種が出てきそうで恐いんだけど… 自動実行されるタイプじゃないからヘッダだけで判別できないんだよね。

# WORM_MYPARTY.A 対策 メッセージ本体をチェックして
# 添付されている www.myparty.yahoo.com を見つけたら削除
:0 B
* ^begin 666 www\.myparty\.yahoo\.com
/dev/null

これも身近で流行ってしまった WORM_FRETHEM.K (トレンドマイクロ表現) のフィルタ例。 ただし、この場合はサブジェクトが十分に怪しいので、 サブジェクトで弾いてしまって良いかもしれません。 添付ファイルを調べて削除するのがベストかな。 下に示す方法だと汎用性がないし、亜種には対抗できない。

# WORM_FRETHEM.K 対策
# タイトルと添付ファイルをチェックして該当したら削除
:0
* ^Subject: Re: Your password!
{
	:0B
	*^Content-Type: audio/x-midi;
	*name=decrypt-password\.exe
	/dev/null
}

次は ML に流れていた、 やばそうなファイル (実行可能ファイル等) が 添付されていたらとりあえず捨ててしまうレシピです。 Philip さんのレシピを改行や '"' の影響を考慮して Sergiy さんが修正したものです。 EXT で受け取りたくない拡張子を指定できます。 マルチパートはメッセージ本体に含まれるのでフラグに B を指定しておきます。
レシピはこのまま利用出来ますが、 From: や To: 等の条件文を付加して信頼できるメッセージを逃がしておかないと、 せっかく送ってくれたファイルを消してしまうことになります。 また、.exe や .com には自己解凍形式の圧縮ファイルも含まれるので注意しましょう。

# author: Philip Guenther modified by Sergiy Zhuk
EXT = '\.(scr|vbs|shs|bat|com|exe|pif)'
WS = '[ 	]*($[ 	]+)*'
DOTSTAR = '.*($[ 	].*)*'
DQ = '"'
:0 B
* $ ^Content-(Type|Disposition)*:${DOTSTAR}name${WS}=${WS}${DQ}.*${EXT}${DQ}
/dev/null

メーリングリスト振り分けレシピ

希望購読しているメーリングリストからのメールを適当なディレクトリに保存する例。

# ml@my.hobby.org 宛のメーリングリストのメールをさばく
# メールはディレクトリ $MLDIR/myhobby に MH 形式の連番で保存されます
:0
* ^(To|Cc):.*ml@my\.hobby\.org
$MLDIR/myhobby/.

日本語サブジェクト振り分けレシピ

日本語を扱うレシピが多い場合には予めデコードしておいたものを再利用することで、 時間のかかる nkf や sed などの外部プログラム呼び出しを減らすことができます。 以下のレシピでは環境変数 DECODED_SUBJECT にサブジェクトを代入しています。 これによって、後のレシピでは DECODED_SUBJECT と、 何らかの文字列とのマッチングだけでサブジェクトによる振り分けが行えます。
このレシピは広松さん<matsuan@ca2.so-net.ne.jp>と長南さん<chonan@kreis.hn.org>からアドバイスをいただきました。ありがとうございます。

# サブジェクトがエンコードされていたらデコードして制御文字等を削除
# エンコードされていない場合にも備える
:0
* ^Subject:\/.*
{
	:0
	* ^Subject:.*iso-2022-jp
	DECODED_SUBJECT=|echo "$MATCH"|nkf -meZ1|sed 's/[[:space:]]//g'
	:0 E
	DECODED_SUBJECT=|echo "$MATCH"|nkf -eZ1|sed 's/[[:space:]]//g'
}

# サブジェクトに hoge と書かれていたら
# $MAILDIR の下のディレクトリ hoge にMH形式の連番で保存されます
# このレシピでは「hoge」だけでなく全角の「hoge」「HOGE」なども該当します
:0
* $DECODED_SUBJECT ?? .*hoge.*
$MAILDIR/hoge/.

Spam Mailer を弾くレシピ

最近の spam は従来のチェーンメールのような手作業での送信ではなく、 メールアドレスのデータベースと連携した(?)専用メーラーによる一括送信のものが増えています。 以下のレシピは、いいじまさん<delmonta@ht.sakura.ne.jp>に紹介していただいた spam を送ってくることで有名な Mailer を弾くレシピにいくつか追加したものです。
注: 購読しているメーリングリストや一般の送信者がこれらのメーラーを使っている可能性もあります (私的には限りなくグレーな Pegasus Mail とか egroups とか)。

# 行末の \ は継続行の印
:0
* ^X-Mailer:.*(jpfree Group Mail Express|EVAMAIL|Foxmail|JiXing mailer|\
007 Direct Email Easy|Easy DM free|mPOP Web-Mail|IM2001)
$MAILDIR/trash/.

また、逆に妥当なメーラーからのメールだけを受け取るという方法もありますが、 この場合「妥当なメーラー」の決定がとても困難です。 例えば、私の手元の spam でないメールを眺めたところ70種類以上のメーラーが使われていました(バージョン番号が異なるものは「1つ」としてカウント)。 「X-Mailer」が宣言されていない spam でないメールもありますし、未知のメーラーも山ほどあります。 さらに、これからも次々と新しいメーラーが増えることを考えたら明らかに破綻するのでお勧めできませんが、 レシピ自体は結構簡単に書けます(レシピと妥当な?メーラーの紹介はしません)。


他国語のエンコードを弾くレシピ

中国語や韓国語が読めない人にとって gb2312(中国) や euc-kr(韓国) でエンコードされたメールは迷惑でしかありません。 また、実際に中国や韓国からの spam に悩まされている人も居ます。 以下は、いいじまさん<delmonta@ht.sakura.ne.jp>に紹介していただいたエンコードで判別するレシピです。
注: 中国語や韓国語の混在した重要なメールも存在します。捨てる時には気をつけてください。

# 中国語(gb2312)・韓国語(euc-kr, ks_c_5601-1987)でエンコードされたメールを弾く
:0
* ^Content-type:.*(gb2312|euc-kr|ks_c_5601-1987)
$MAILDIR/trash/.

# (spam が送られてくる)送信者のドメインに対する処理
:0
* ^From:.*=\?(gb2312|euc-kr|ks_c_5601-1987|co.kr|hanmir.com|korea.com)
$MAILDIR/trash/.

おわりに

よろしければ、このページの内容に関するご意見、ご感想、 ここで紹介してよい Procmail のレシピのサンプルなどを 送ってください。 特に Virus が添付されていると断定できるメールのヘッダ情報や、 実際に利用しているレシピを紹介してくれると嬉しいです。

ちなみに私がこのページを書いているのは、 Spam の撃退 の翻訳に携わったので、若干の誤植等を解消できたらいいなとか考えているからです。 そんなこんなで本の内容をちょっとだけ修正して引用しているレシピもありますし、 自分で使いたいものを新たに作成したり、 「日本語」に対応するために多少改良?していたりもします。


戻る Valid HTML 4.01! Valid CSS!