.qmail ファイルに挟むプログラムを作成するには

SPECIAL


qmail の到着メール処理にプログラムを挟むには

Linux のメールサーバのひとつである qmail は、各メールアドレス毎に用意される .qmail ファイルにてプログラムを指定することが出来るようになっています。

あるときウィルスメールが来すぎるみたいな話しを聞いて、有名どころのウィルスといえばその添付ファイルに添えられた拡張子が特殊なことから、大げさにもウィルススキャンなど導入しなくても良いんじゃないかなと思いました。

 

そこで、そういう機能が既に無いか調べてみたところ、詳しくまではわからないのですけど、どうやら "qmail-scanner" というソフトウェアがそういう機能を保有しているようでした。

本来は、なのか、"qmail-scanner" は、さらに "ClamAV" というアンチウィルスソフトと合わせて使用することによって、ウィルスチェックをサーバ側で行うという感じのようなのですけど、どうにもそこまでやるのは大げさだし、過保護すぎて逆に無意味なような感じもします。そうしたところでクライアントにウィルス対策の意識が不要になることもないわけですし。

 

 

とりあえず、拡張子の古い分けが行えるらしい "qmail-scanner" について少しだけ調べてみました。

けれどこれをインストールするにあたっては、qmail のソースファイルにパッチを充てたり、充てなくても qmail-queue を差し替えたりなどなど、どうにも大掛かりな下準備が必要そうな感じでした。なにもそこまでしなくても、.qmail ファイルにプログラムを記載すれば動くようになっているのだから、それを利用できないものなんでしょうか…。

そう思って、今回はその .qmail に挟み込むプログラムを用意するにはどうしたら良いか、そんな観点から調べてみようと思います。

 

.qmail ファイル

qmail は最終的なメールの配信先を決定するのに、各アドレスに用意された .qmail ファイルを使用します。

このファイルは /var/qmail/alias ディレクトリの他、そのアドレスに割り当てられたアカウントのホームディレクトリに存在しています。また、さらに独自にメールアドレスを拡張するためや、仮想ドメイン使用時などには、.qmail-* というかたちでも存在しています。

 

qmail ファイル内には一行ごとに、次の記号から始まる指示を指定することが出来るようになっています。

. メールの保存場所を、相対パスで指定する場合はこの文字から始めます。末尾の文字が "/" ならば maildir 形式、それ以外で終わる場合は mailbox 形式となります。
/ メールの保存場所を、絶対パスで指定する場合はこの文字から始めます。末尾の文字が "/" ならば maildir 形式、それ以外で終わる場合は mailbox 形式となります。
& メールの転送先を示すアドレスを表記する場合にはこの文字から始めます。続いて転送先のメールアドレスを記述します。
# コメント行を記載する場合にはこの文字から始めます。この文字から始まった行は処理の対象とはなりません。
| 何かプログラムを介入させる場合にはこの文字から始めます。この後ろに指定された文字列が sh へ引数として渡されるそうです。

これらの行を複数を組み合わせることも出来ます。その場合は上から順に処理されるようですけど、その途中にエラーが発生した場合はその時点で処理は打ち切りとなるそうです。

ただし、メールの転送処理については最後に処理されるそうですので、エラーが起こると転送はされないそうなので、その辺りを気をつける必要がありそうです。

 

それと編集の際の注意事項として、運用中のメールサーバの .qmail ファイルを書き換える際には、たとえば次のようにして、その .qmail ファイルが保存されているディレクトリのスティッキービットを次のようにしてセットしておくと良いそうです。

chmod +t $HOME

このようにすることで、その更新最中に新着メールを受信処理に回してしまわないようにすることができるとのことでした。編集を行ったら今度は "chmod -t $HOME" としておかないと、受信をできないままなので気をつけましょう。

 

挟み込んだプログラムは、どのように動くものなのか

今回、重要となってくるのが "|" で始まるプログラムに処理を委ねる部分ですので、その辺りについてもう少し掘り下げてみようと思います。

付属文書を眺めてみると、このようにしてプログラムが指定された場合、処理的には sh -c に続いて、指定された文字列が引数として渡され、実行されるとのことでした。そのときに取り扱っているメッセージは、標準入力から読み取ることが出来るそうです。

ただし標準入力に渡されたメッセージには、Delivery-To: や Return-Path: 行は含まれないので注意とのことでした。

 

標準入力からのメッセージの他にも、環境変数により細かな情報を取得することが出来るようです。

SENDER 送信元アドレスです。 sender@dummy.ez-net.jp
NEWSENDER 転送時の送信元アドレスらしいです。 sender@dummy.ez-net.jp
RECIPIENT 送信先アドレスです。 test@dummy.ez-net.jp
USER メールアカウントのユーザ名の部分です。ハイフンによる拡張を行っている場合は、それを含まない純粋なユーザアカウント名が設定されます。ユーザ名自体にハイフンが含まれる場合は、それはここに含まれるようです。 test
HOME そのメールを担当するユーザのホームディレクトリのようです。 /home/test
HOST RECIPIENT に指定されたもののうちの、ドメイン部分です。 dummy.ez-net.jp
HOST2 HOST から最後のドット以降を省いた部分です。 dummy.ez-net
HOST3 HOST の最後から 2 番目のドット以降を省いた部分です。 dummy
HOST4 HOST の最後から 3 番目のドット以降を省いた部分です。ドットが全部で 2 つしかない場合などは、最初のドットが現れるまでの文字が設定されるようです。 dummy
LOCAL RECIPIENT のローカル部分だそうです。 @ マークが現れるまでの最初の部分ということなのでしょう。拡張を含むアドレスの場合も、それを含むすべてがここに設定されます。 test
EXT .qmail を "-" で拡張した場合の拡張部分 です。アカウント自体にハイフンが含まれている場合には、それはここには含まれません。また .qmail-default による配送であっても、拡張部分の文字列はここに設定されます。  
EXT2 EXT に設定された文字列にさらにハイフンが含まれる場合に、その最初に現れたハイフンの次からの部分が設定されます。拡張部分に 3 つ 4 つとハイフンが含まれる場合は、それもここに含まれます。  
EXT3 EXT に設定された文字列の 2 番目に現れたハイフン以降に現れる文字列が設定されます。該当がない場合には空文字が設定されます。  
EXT4 EXT に設定された文字列の 3 番目に現れたハイフン以降に現れる文字列が設定されます。該当がない場合には空文字が設定されます。  
DEFAULT .qmail-default での処理の場合、拡張部分に該当した文字列が設定されます。.qmail-default による処理の場合にのみこの環境変数名が存在するようです。  
DTLINE Delivered-To 行が設定されるみたいです。行末は改行を含むそうです。 この行が設定されていないメールの場合は、この環境変数も登録されないようでした。  
RPLINE Return-Path 行が設定されるみたいです。行末は改行を含むそうです。 Return-Path: <sender@dummy.ez-net.jp>
UFLINE UUCP スタイルの From 行? mbox 形式のファイルに出力するときに使うらしい。 From sender@dummy.ez-net.jp Mon Oct 31 12:21:03 2005

なお、これらの値には特殊文字を含んでいる可能性もあるため、処理の際には気をつけて取り扱う必要があるとのことです。

 

また、プログラムの実行結果として返す値にも意味があるとのことでした。

0 プログラムの処理が正常に終了し、以降の行も継続して処理を行います。
99 プログラムの処理が正常に終了し、以降の行は処理を行わずに終了させます。
100 プログラムの処理は失敗したことを意味し、配送処理は失敗とします。(ハードエラー)
111 プログラムの処理は失敗したことを意味し、配送処理は少し後で再試行とします。(ソフトエラー)

 

これらを組み合わせてプログラムを作成すれば、けっこう思い通りに処理を行うことができそうですね。

 

実際に作成して試してみる

配送の制御を実感してみる

単純に成功させてみる

とりあえず .qmail ファイルとして次のようなものを用意して、いろいろと実験してみようと思います。

|/home/test/smtp.bin/test.sh

./Maildir/

このような感じで、とりあえず /home/test/smtp.bin/test.sh を実行した後で、Maildir へメールを格納するという感じです。

そしてまずは単純に、期待通りの動作を行ってくれるかどうか、次のようなスクリプトを使って実験してみます。

#!/bin/sh

exit 0

この状態でメールを送信してみると、なんら問題なくメールを受信することができました。

 

単純に成功させて、それ以降の処理をないものとしてみる

これを行うに先立って、まずは .qmail ファイルを次のように調整してみます。

&further-delivery@dummy.ez-net.jp

|/home/test/smtp.bin/test.sh

./Maildir/

この状態でメールを送信してみると、"further-delivery@dummy.ez-net.jp" 宛ておよび自分自身の Maildir へメールが配信されます。ここで続いて、スクリプトを次のように修正して、スクリプト以降の行を処理の対象としないようにしてみます。

#!/bin/sh

exit 99

すると、最初の行に記載されているアドレスへメールが転送されてきましたけど、コマンドを実行した行より下にある Maildir への配信は、リザルトコードの 99 が意味するように処理されずに終わりました。

 

次のように最初の行と最後の行を入れ替えてみても、今度は Maildir にだけ配信されるというように、予期したとおりの処理結果となりました。

./Maildir/

|/home/test/smtp.bin/test.sh

&further-delivery@dummy.ez-net.jp

 

単純にハードエラーを通知してみる

.qmail ファイルは上記のままに、今度はスクリプトにハードエラーを通知させてみます。

#!/bin/sh

exit 100

このようにしてメールを送信してみると、qmail からエラーを示す "failure notice" メッセージが送信元へ返送されてきました。それと併せてポイントとなるのが、ハードエラーを通知する前に記載した "./Maildir/" 行は有効なため、ローカルにはしっかりと配信されているというところでした。

なお、qmail から返却されてきたエラーメールは次のような内容となっていました。

Hi. This is the qmail-send program at smtp.dummy.ez-net.jp.

I'm afraid I wasn't able to deliver your message to the following addresses.

This is a permanent error; I've given up. Sorry it didn't work out.

 

<test@dummy.ez-net.jp>:

続いてもうひとつのチェックポイントです。

qmail の付属文書によると、転送処理は最後に行われるとのことなので、もしエラーの前に実行していたとしても最後までの間にエラーになると、一切の転送が行われないとのことでした。

それを試してみるために、ふたたび .qmail ファイルの最初の行と最後の行とを取り替えて、送信実験を行ってみました。

&further-delivery@dummy.ez-net.jp

|/home/test/smtp.bin/test.sh

./Maildir/

このようにしてみると、不達メールが送信元へ返却されるだけでした。

付属文書の示すとおり、転送メールの行を通過はしているものの全体的には失敗となるので、エラーよりも次の行のローカル配信も然ることながら、最後の締めの転送処理も中士となりました。

 

標準出力を使ってみる

qmail から返送されてきたエラーメッセージを眺めていてふと思ったんですけど、エラーメッセージを自分で指定することはできないのかなと思って、単純に標準出力へメッセージを出力したらどうなるのかやってみました。

#!/bin/sh

 

echo "test message"

exit 100

スクリプトをこのように書き換えてメールを送信してみると、なんとしっかり、標準出力に含めたメッセージがメールに含まれるようになりました。これは便利かもしれないですね。

そのときの通知メッセージは次のような感じです。

Hi. This is the qmail-send program at smtp.dummy.ez-net.jp.

I'm afraid I wasn't able to deliver your message to the following addresses.

This is a permanent error; I've given up. Sorry it didn't work out.

 

<test@dummy.ez-net.jp>:

test message

ちなみにこれは不達メールとして送信元へ返却されたエラー通知メールだけが対象で、正常に送信できた場合はどこにも含まれないようです。

 

そのほかの気になる部分を調べてみる

環境変数を確認する

とりあえず、どんな環境変数が登録されているのかを目で確認してみることにしました。

確認方法はいたって簡単で、わざと exit 100 でエラーを出すとともに、環境変数を確認するためのコマンド env を実行するだけな感じです。

#!/bin/sh

 

env

exit 100

こうやってエラー通知を受け取ってみると、次のような環境変数が登録されていました。

HOST2 xxx.ez-net
HOST3 xxx
SENDER sender@dummy.ez-net.jp
RECIPIENT test@dummy.ez-net.jp
HOST dummy.ez-net.jp
/HOST4 xxx
USER test
EXT  
LOCAL test
PATH /var/qmail/bin:/usr/local/bin:/usr/bin:/bin
_ /usr/bin/env
RPLINE Return-Path: <sender@dummy.ez-net.jp>
/NEWSENDER sender@dummy.ez-net.jp
PWD /home/test
HOME /home/test
SHLVL 2
EXT3  
EXT2  
EXT4  
UFLINE From sender@dummy.ez-net.jp Mon Oct 31 09:36:39 2005

ここで現れた環境変数はこれですべてでしたので、その他の細かなシェル変数については含まれないようですね。それ以上の情報が必要だった場合には、命令指定の際に引数で渡す必要がありそうです。

 

各環境変数の詳細については、いろいろ試してみた結果を最初の方の表へ含めてみましたので参考にしてみてください。

なお、.qmail によるコマンドの実行に関する付属文書に記されていなかった環境変数名がいくつかありますけど、それぞれは次のような役割となっています。

PATH 実行ファイルを検索するパスです。見る限りは svscan で実行したときに run スクリプトで指定したものが示されていましたので、そのような都合で登録されてきているものなのかも知れないです。
_ この変数は何なのでしょうね。とりあえず、環境変数を登録するプログラム env の存在位置が記録されている感じです。
PWD 現在のディレクトリ位置です。
SHLVL シェルの深さを示す値だそうです。最初のシェルには 1 が設定され、そこからさらにシェルを呼び出すと、それが 2 になり、3 になり、そんな感じのもののようです。

 

ところで、環境変数を env で表示してみると、HOST4 と NEWSENDER の変数名の頭に "/" という文字がついてしまっていたりしたのですけど、いったい何なのでしょうね。

とりあえず、スラッシュを無視した変数名でちゃんと値を読み取ることができるようでした。

 

実行時の権限は

呼び出されたスクリプトが、どのアカウントで実行されているかを調べてみます。

やり方は簡単で、スクリプト上で whoami コマンドを実行することで標準出力へ実効ユーザ名を出力させます。そして終了コード 100 によって不達メールを受け取れば、目で確認することが出来るはずです。

#!/bin/sh

 

echo -n "EUID: "

whoami

 

exit 100

このようにして不達メールを確認してみると、そのメールアカウントを管理しているユーザ名がメッセージ内に含まれていました。実効ユーザはそのメールを取り扱うアカウントになるみたいですね。

 

ハイフンを含むアカウントの場合の環境変数

結果の方は先に上の表に書きましたけど、qmail の拡張部分にではなく、アカウントそのものにハイフンが含まれている場合には、そのハイフンを含む後ろの文字列も、しっかりとアカウント名として取り扱われるようでした。

この場合、環境変数の EXT の値にはアカウントに含まれるハイフン以下は含まれませんので、そういう面での状況想定でごちゃ混ぜになってしまうことはなさそうです。

 

環境変数の値を書き換えると…

ところで設定されている環境変数をスクリプトで変更するとどうなるのでしょう。

何か調整できたりするのかとも思って、少し実験してみました。SENDER や NEWSENDER, RECIPIENT, RPLINE, UFLINE など、もしかすると変更したら何か変わりそうに思えたものを中心にスクリプト内で値を設定しなおしてみましたけど、それで何かが変わることはありませんでした。

 

標準入力に渡される値

標準入力には Delivery-To: や Return-Path: が含まれないとのことでしたので、実際にはどのような感じで始まるのかを調べてみました。

#!/bin/sh

 

read MESSAGE

echo $MESSAGE

 

exit 100

このようにして標準入力から一行読み込んで、環境変数 MESSAGE へ値を保存してそれを表示してみると、最初の行は次のようになっていました。

Received: (qmail 1749 invoked by uid 502); 1 Nov 2005 04:25:59 -0000

これは、戻ってきたメールに引用されていたオリジナルメッセージの中の最初の Return-Path: 行を除いた次の行でしたので、 ほんの少しだけ情報が落ちる程度のようですね。

落ちた値についても環境変数の RPLINE で確認できるので、実質的にもなんら問題なさそうでした。

 

標準で用意されている .qmail で使用できるプログラム

qmail には標準で、.qmail ファイル内でプログラムを使用しやすくするためのプログラムが用意されているようです。それについてメモついでに少し記載しておこうと思います。

 

/var/qmail/bin/bouncesaying 'MESSAGE' PROGRAM

/var/qmail/bin/bouncesaying に、メッセージ ('MESSAGE') と条件判定を行うプログラム (PROGRAM) を引数として渡すと、その条件を満たした場合には 'MESSAGE' を添えてハードエラーとなるようです。

条件は 0 を返したときを真とみなし、そのときに 'MESSAGE' を標準出力に出しつつ、終了コード 0 でハードエラーとして終了します。条件が 111 を返した場合は、終了コード 111 でソフトエラーとして終了するそうです。

 

/var/qmail/bin/condredirect ADDRESS PROGRAM

/var/qmail/bin/condredirect に、メールアドレス (ADDRESS) と条件判定を行うプログラム (PROGRAM) を引数として渡すと、その条件に見合った場合には ADDRESS 宛にメールを転送し、それ以降の行は無視して処理を終了するようです。

条件が 0 を返した場合、ADDRESS に指定されたメールアドレスへメールを転送するとともに、終了コード 99 としてその時点で正常終了とします。条件がそれ以外の値を返したり、そもそも条件判定プログラムが存在しなかったりしたときには、終了コード 0 で継続処理されるようです。

 

/var/qmail/bin/except PROGRAM

/var/qmail/bin/except にプログラムを渡すと、その終了コードの意味合いを反転させてくれるような感じで動作するそうです。

プログラムが正常継続を示す 0 を渡した場合にはハードエラーを示す 100 を返しますが、プログラムがソフトエラーを示す 111 を渡した場合には、except も 111 を返すとのことでした。また、それ以外の値が渡された場合には、except は正常継続を示す 0 を返してくれるそうです。

ソフトエラーの状態を維持したまま、正常だったらハードエラーに、それ以外ならば正常継続に、状態を変更するもののようですね。これを bouncesaying や condredirect と併せて条件判定で使用することで、判定条件を反転させることができるようになります。

/var/qmail/bin/bouncesaying 'test message' except check_program

使い方としてはこのような感じです。