Procmailとは?



 Procmailとは、メールサーバーが受信したメールを条件(レシピ:ルールのことです)に応じて振り分けるソフトウェアです。一般にMDA(Mail Delivery Agent)と言われるものです(他にはmail等があります)。これらのアプリケーションは両方とも、Local Delivery Agentと考えられ、その両方が電子メールをMTAのスプールファイルからユーザーのメールボックスへ転送します。しかし、Procmailはただ単に転送するだけでなく、強力なフィルターシステムを有しています。
 Procmailが起動すると、電子メールメッセージを読み取り、ヘッダー情報から本体を分離します。次に、Procmailはデフォルトのシステム全体のProcmail環境変数とレシピ用の/etc/procmailrcsディレクトリ内の/etc/procmailrcファイルとrcファイルを探します。次に、Procmailはユーザーのホームディレクトリ内の.procmailrcファイルを探し、そのユーザーに 固有の規則を見つけます。多くのユーザーは、.procmailrcで参照されるProcmail用の独自の 追加rcファイルも作成します。ただし、これらのファイルはメールフィルタ処理の問題が発生した場合にただちにオン/オフすることができます。
 Red Hat Linuxの場合、デフォルトではシステム全体のprocmailrcファイルが/etcディレクトリに存在せず、ユーザー.procmailrcファイルも存在しません。Procmailの使用を開始するには、特定の環境変数と特定のメッセージタイプ用の規則をもって、procmailrcファイル、.procmailrcファイルを作成する必要があります。
 例えば、「未承諾広告」という件名のメールを受け取りたくない場合、条件(レシピ)を設定することにより受信せずに自動的に削除できます。
 設定ファイルの記述方法は正規表現を多用しますが、慣れてしまえば比較的簡単に設定する事が可能です。Postfix が受信する全てのメールに対するフィルタの設定や、特定のユーザに対するフィルタも柔軟に設定する事が可能です。文字列のマッチングを行うことにより、様々な不要なメールを受信せずに削除する事も可能となります。
 まず始めに、procmailがインストールされているか確認して下さい。以下のように表示されればインストールされています。
  $ rpm -qa | grep procmail
  procmail-3.22-9
  $ which procmail
  /usr/bin/procmail
 インストールされていない場合はRPM resource procmailProcmail Homepageからダウンロードしてインストールして下さい。

 Red Hat Linuxの場合、「procmailrc」、「.forward」、「.procmailrc」が設定されていないため、自分が設定したドメインにメールを送信すると、「user unknown. Command output: procmail: Unknown」となって受信することができません。
 これを解消するには、procmailrc 」、「.forward」、「.procmailrc」を設定するか、main.cfの「mailbox_command」行のコメントアウトしてください。

 例 # mailbox_command = /usr/bin/procmail -a $DOMAIN -d LOGNAME

 mailbox_commandを上記のように設定するとroot宛に届いたメールを特定のユーザに転送することができませんので、特定ユーザに転送した場合はこちらを参照してください。

postfixをrestartまたはreloadして下さい。

●Procmailレシピの形式

Procmailレシピは、次の形式をとります。
  :0<flags>: <lockfile-name>
  * <special-condition-character> <condition-1>
  * <special-condition-character> <condition-2>
  * <special-condition-character> <condition-N>
  <special-action-character><action-to-perform>
 Procmailレシピの最初の2文字はコロン(:)と0です。このレシピを処理するときにProcmailが何をするかを制御するには、 オプションとして0の後に各種●フラグを設定できます。<flags>セクションの後のコロンは、このメッセージのためのロックファイルを作成することを指定します。ロックファイルを作成する場合は、<lockfile-name>スペースでその名前を指定します。
 レシピには、メッセージと照合するいくつかの条件を含めることができます。条件がない場合、すべてのメッセージはレシピに一致します。正規表現にはメッセージとの照合を実施するために、いくつかの条件が設定されます。複数の条件を使用する場合、●アクションを実行するためにこれらの 条件がすべて一致しなければなりません。条件は、レシピの最初の行で設定された●フラグに基づいてチェックされます。*文字の後に設定されたオプションの特別な 文字は、さらに条件を制御できます。
 <action-to-perform>は、条件のうちの1つに一致する場合にメッセージに対して何が発生するかを指定します。レシピごとに●アクションは1つしか指定できません。多くの場合、ここではメールボックスの名前を使用して照合用のメッセージをそのファイルに送り、電子メールを有効にソートします。●アクションを指定する前にも、特別な●アクション文字を使用できます

●フラグ

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


●条件文

 「●条件文」は●フラグに続く行頭が「*」で始まる行を指し、メッセージを選択する条件を正規表現によって記述します。●条件文は省略することが可能で、その場合は無条件に適用されることを意味します。また、1つのレシピに複数の●条件文を記述すことが可能で、複数の●条件文が存在する場合には全ての条件を満たした場合にだけ適用されます。●条件文で用いられる記号には以下のようなものがあります。
条件 条件の説明
! 条件の否定を意味します。
$ ●条件文に現れる(環境)変数をシェルの様に評価することを意味します。
? 指定したプログラムの終了コード(0:成功か1:失敗)を利用します。
< 長さが後に記述されたバイト数(10進)以下であれば実行されます。
> 長さが後に記述されたバイト数(10進)以上であれば実行されます。
変数名 ?? 指定された値と変数を比較します。
\ クォートします。 \/ と記述することで該当箇所を変数MATCHに格納します。


●アクション

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


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

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


●レシピのサンプル

 SAMPLE1のレシピはメールのヘッダ、本体を問わず、ある1行の行頭が大文字の「HOGEHOGE」で始まるメールを受け取ると、続くレシピでも処理が行えるようにメッセージをコピーしつつ、ヘッダ部分だけをgzipによって圧縮して、hoge.gzというファイルに追加していくレシピです。ファイルに触るため、一応、ロックも指定しておきます。
  # SAMPLE1
  :0 HBDhc :
  * ^HOGEHOGE
  | gzip -fc >> hoge.gz
 SAMPLE2のレシピは、メッセージの本体だけ(●フラグがB)を検査します。
 検査の内容は、本文の行頭(^が行頭を意味する)に$HOGE($HOGEは、先に代入文が記述されているが●条件文を示す*の後に$が指定されていないのでHOGEHOGEではない「何か」)が無く(!によって否定されている)、行頭に大文字小文字を問わず(●フラグにDが指定されていない)FOOBARと書かれていて(今度は●条件文の$で$FOOが展開される) 、testプログラムの結果によって、ホームディレクトリに「anti_hoge」というファイルの存在が確認されて、メッセージ本文が5000バイト以下であって、4900バイト以上であるメッセージが、変数CHECKにyesが代入されている(例では該当する)メッセージを探すものです。
 さらに、最後の●条件文に書かれた\/によって、行頭が「ADDR」(または「addr」…)で始まる行の「ADDR」に続く空白文字の後に記述された文字列を変数MATCHに格納し、最終的にその文字列をanti_hogeというファイルに書き足していくものです。
  # SAMPLE2
  HOGE=HOGEHOGE
  FOO=FOOBAR
  CHECK=yes
  :0 B :
  * ! ^$HOGE
  * $ ^$FOO
  * ? test -f $HOME/anti_hoge
  * < 5000
  * > 4900
  * $CHECK ?? yes
  * ^ADDR *\/.*
  | echo "$MATCH" >> anti_hoge
●全ユーザ共通設定

 Procmailは、デフォルトで/etc/procmailrcというファイルを使用します。procmailrcファイルを設定することにより、記載されたレシピが全ユーザーに適用されます。
 viエディタ等で/etc/procmailrcを編集します。procmailrc というファイル名がなければ作成します。
 メールの件名に「未承諾広告」という文字列があれば自動的に削除させるprocmailrcの内容は以下の通りです。
  PATH=/bin:/usr/bin:/usr/sbin
  LOGFILE=$HOME/procmail.log
  LOCKFILE=$HOME/.lockfile
  MAILDIR=$HOME
  # 未承諾広告を自動削除するレシピ
  :0 ←:0の後に必ず空白(半角スペース)を一つ含めること!(必須)
  * ^Subject:.*iso-2022-jp
  * ^Subject:.*\/.*
  * ? echo "$MATCH" | nkf -meZ2 | sed 's/[[:space:]]//g' | egrep '未承諾広告'
  /dev/null
【環境設定】
PATH Procmailの動作中に参照するコマンドのパスを指定します。
LOGFILE Procmailのログファイルの場所を指定します。
LOCKFILE .lockfileファイルの場所を指定します。(2重処理を防止する為の一時ファイル)
MAILDIR メール振り分け時に使用するディレクトリを指定します。

 上記のように設定すれば、メールの件名が「未承諾広告」の場合は、自動的に削除されます。「/dev/null」(破棄)する指定であり、最後の行を例えば「 trash/. 」と置き換えると、MAILDIRで指定したディレクトリー下に1から連番のファイル名で排除メールが保存されます。

●ユーザ毎の設定

 Procmailはユーザ個別にレシピを書く事が可能です。書式もprocmailrcと全く同じです。
ユーザ毎にレシピを書くには以下の2つのファイルが必要です。
    「.forward」  「.procmailrc
この2つのファイルは、対象のユーザのホームディレクトリに設置します。どちらも「. (ドット)」で始まるファイル名です。
まずは、「.forward」を対象のユーザのホームディレクトリに作成します。.forward の内容は以下の通りです。
"|IFS=' ' && exec /usr/bin/procmail -f- || exit 75 #username"
(2012.05.01更新)
 上記の記載方法では下記のようなエラーが返って来てしまいました。
   ----- The following addresses had permanent fatal errors ----- 
"|IFS=' ' && exec /usr/bin/procmail -f- || exit 75 #******" 
    (reason: Service unavailable) 
    (expanded from: <******@bigbang.dyndns.org>) 
 
   ----- Transcript of session follows ----- 
smrsh: "IFS='" not available for sendmail programs (stat failed) 
554 5.0.0 Service unavailable
******はユーザ名です。
 改修したものが以下となります。
"|exec /usr/bin/procmail -f- || exit 75 #username"
 「/usr/bin/procmail」は、自分のOSのProcmailまでのパスを記述します。分からない場合は、CUI上で$which procmailと入力して下さい。末尾の「username」部分は、対象の一般ユーザ名を記入します。
 次に、「.procmailrc」を作成します。
 これも、対象のユーザのホームディレクトリ内に設置します。この.procmailrcと先ほどの.forwardと対になっていますので、両方とも必ず設置します。全ユーザ向け設定の「/etc/procmailrc」で未承諾広告メールの削除設定をしていますのでそれ以外のレシピを紹介します。
 .procmailrc の内容は以下の様になります。

 .procmailrcの頭の環境変数の記載方法によって、MailDir形式の場合、正常にメールが配送されず、/var/spool/mail/$usernameに保存されてしまうことがあるようです。
SHELL=/bin/bash
PATH=$HHOME/bin:/usr/bin:/usr/local/sbin
MAILDIR=$HOME/Maildir/
DEFAULT=$MAILDIR
LOGFILE=$HOME/.procmail.log
(下記は正常に振り分けされなかった時の記載)
PATH=/bin:/usr/bin:/usr/sbin
LOGFILE=$HOME/.procmail.log
MAILDIR=$HOME/Maildir
# 特定のメールアドレスから来たメールを自動削除するレシピ
:0 ←:0の後に必ず空白(半角スペース)を一つ含めること!(必須)
* From: ^test@localhost.localdomain
/dev/null
# 特定のメールアドレスから来たメールを自動転送するレシピ
:0 
* From: ^test@localhost.localdomain
!hogehoge@hoge.ne.jp
注意
 転送用レシピはprocmailrcに記載し、main.cfのmailbox_commandを
  mailbox_command = /usr/bin/procmail
のように記載して下さい。
  mailbox_command = /usr/bin/procmail -a $DOMAIN -d LOGNAME
のように記載するとroot宛に来るメールの転送がうまくいきません。

●応用編

/etc/procmailrc(全ユーザー適応レシピ)
  PATH=/bin:/usr/bin:/usr/sbin
  LOGFILE=$HOME/procmail.log
  LOCKFILE=$HOME/.lockfile
  NGWORD=$HOME/.ngwords
  #.ngwordsによる、自動削除
  :0
  * ^Subject: *\/.*
  {
	DECODED=$MATCH
	:0
	* ^Subject:.*iso-2022-jp
	DECODED=|echo "$MATCH"|nkf -me  文字コードがEUC-JP場合、この行を使用します。
	DECODED=|echo "$MATCH"|nkf -w  文字コードがUTC-8場合、この行を使用します。
	CHECK = `echo "$DECODED" | sed 's/[[:space:][:punct:]]//g'`
	:0
	* ? test -s $NGWORD
	* ? echo "$CHECK" | fgrep -iqf $NGWORD  qオプションが使用できない場合は、qオプションを削除します。
	/dev/null
  }
自動削除したい文字列をいちいちレシピに追加するのが面倒なので、ホームディレクトリ下に「.ngwords」というファイル作成し、その中にNGワードを設定しています。

/home/popo/.ngwords
  未承諾広告
  未承認広告
  ADULT
  アダルト
  18禁
  裏ビデオ
  検索エンジン登録代行
  至急御連絡致します。
/home/popo/.forward
"|IFS=' ' && exec /usr/bin/procmail -f- || exit 75 #popo"  
/home/popo/.procmailrc(ユーザー個別のレシピ)
PATH=/bin:/usr/bin:/usr/sbin
LOGFILE=$HOME/procmail.log
LOCKFILE=$HOME/.lockfile
# 中国(gb2312)、韓国語(euc-kr, ks_c_5601-1987)を自動削除
:0 
* ^Content-type:.*(gb2312|euc-kr|ks_c_5601-1987)
/dev/null
# ウイルス対策
:0B 
* ^Content-Type:.*name=.*\.exe.*
/dev/null
:0B 
* .*filename=.*\.exe.*
/dev/null
:0B 
* ^Content-Type:.*name=.*\.pif.*
/dev/null
:0B 
* .*filename=.*\.pif.*
/dev/null
:0B 
* ^Content-Type:.*name=.*\.scr.*
/dev/null
:0B 
* .*filename=.*\.scr.*
/dev/null
:0B 
* ^Content-Type:.*name=.*\.com.*
/dev/null
:0B 
* .*filename=.*\.com.*
/dev/null
:0B 
* ^Content-Type:.*name=.*\.lnk.*
/dev/null
:0B 
* .*filename=.*\.lnk.*
/dev/null
:0B 
* ^Content-Type:.*name=.*\.vbs.*
/dev/null
:0B 
* .*filename=.*\.vbs.*
/dev/null
:0B 
* ^Content-Type:.*name=.*\.bat.*
/dev/null
:0B 
* .*filename=.*\.bat.*
/dev/null
:0B 
* ^Content-Type:.*name=.*\.hta.*
/dev/null
:0B 
* .*filename=.*\.hta.*
/dev/null
●レシピの記述例

注意
 ここで紹介しているいくつかのレシピはある程度の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/.
 上記のレシピでは、「!」が半角全角両方の場合や、「広告」の前後(間)に半角全角のスペースが入る場合を考慮しています。「!激安広告メール!」みたいな例には対応していませんが、正規表現の箇所を次のようにすれば簡単に防げます。…というか違反なので出すべきところに出してあげましょう。
  :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/.
 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 -m
# wオプションに変更し、UTF-8に対応
        DECODED=|echo "$MATCH"|nkf -w
        CHECK = `echo "$DECODED" | sed 's/[[:space:][:punct:]]//g'`
        :0
        * ? test -s $NGWORD
#        * ? echo "$CHECK" | fgrep -iqf $NGWORD
# qオプションが使用出来ないので変更
        * ? echo "$DECODED" | fgrep -if $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ワードレシピとの併用を考えているので軽めの設定になっています。

 メール本文内に怪しいリンク(例:https://amzaon.co.jp.ptlpx.cn/AxYp:危ないので大文字にしている)が記載されているメールを Spam フォルダに移動するレシピ
メール本文NGワード作成
$ vi $HOME/.spamwords_h
cn/AxYp

$ vi $HOME/.procmailrc
# 本文内で該当文字列を検索し、ヒットした場合 Spam フォルダに移動
SPAMWORD_H=$HOME/.spamwords_h
:0 B
        * ? fgrep -iqf $SPAMWORD_H
        $MAILDIR/.Spam/
注意
 ここまでやるのであれば、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)'(実際は1行)
	$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/.
 ウイルスメール撃退系レシピ

 多くのウイルスメールはサブジェクト、添付ファイルの名前、サイズ、バイナリに含まれる文字列等から判別できますが、それらを逐一紹介することは無理なのでここではウイルスメールの撃退方法の簡単なサンプルだけを紹介します。真剣にウイルスメールに対応したい方は、本家のMLやYet Another antiVirus Recipe等を参考にしてみるとよいでしょう。
 一般に多くのウイルスメールは、Outlookの様な余計なスクリプトを実行してしまうメーラを攻撃対象としています。従ってUnix上でメールを読み書きしている場合には影響のないものが多いのですが、何百通ものメールを送信してくるSPAM系のウイルスにはとてもうんざりさせられます。ここでのウイルスメールとは主にそういったSPAM系ウイルスを指していて、読まずにサクサク捨ててしまうことを目的としています。
 もちろん、メッセージをWindows側へ転送したり、Windows上でProcmailを利用している場合にも有効な方法の1つです(Windows ならワクチンソフトのインストールを奨めますが…)。
注意:UNIX系のシステムにも有効な危険で悪質なウイルスも存在します。
 ウイルスメールを撃退するためには、まずウイルスの事を知らなければいけません。サブジェクトを見ただけで捨てられる、有名なLoveLetterウイルス(サブジェクトがI LOVE YOU)とかもありますが、タイトルだけ見て捨ててしまった場合、本物のラブレターだったりするとちょっと困ってしまいます(誤)。そこで、より正確に判断するために、添付ファイルまで調べることにします。
 とりあえずウイルスメールを(メーラではなくて)エディタなどで生テキストとして開くと、マルチパートメールであることを示す「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/.
 他国語のエンコードを弾くレシピ

 中国語や韓国語が読めない人にとって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/.
 ログローテーションの設定

 下記のように記載します。
# vi /etc/logrotate.d/procmail
/home/*/.procmail.log {
    weekly
    missingok
    nocreate
    notifempty
    rotate 12
    dateext
}
 手動で下記コマンドを実行し、動作確認します。
# logrotate /etc/logrotate.conf
 正常に動作しました。
 しかし、cronで自動起動させると下記のようなメールがroot宛に送信され正常に動作しない。
/bin/sh: /etc/logrotate.d/procmail: Permission denied
 /etc/cron.d/procmailを削除したところ、夜中に正常に動作しました。
# ls -l /home/hoge
合計 108
drwx------ 14 hoge hoge  4096  5月  2 18:28 2012 Maildir
-rw-------  1 hoge hoge   629  5月  2 15:24 2012 procmail.log
-rw-------  1 hoge hoge 97104  5月  2 03:06 2012 procmail.log-20120502