root# /var/qmail/bin/qmail-qstat root# /var/qmail/bin/qmail-qread
qmHandle (perlスクリプト) を使うともう少し詳細なアウトプットも得られる。
※ qmHandle1.2.0 に付属するREADMEは 1.1.1 の時から更新されていないようだ。-v オプションはもう無く、 -m に変更となっている。
メッセージは、いくつかに分解された状態でキューに蓄積されている。キューディレクトリが /var/qmail/queue で、qmail-qread で確認したキューナンバーが 555 だったとすると、 これらのファイルが存在するはず:
queue/mess/[0-22]/555 | メッセージ |
queue/todo/[0-22]/555 | エンベロープの from と to 情報 |
queue/intd/[0-22]/555 | エンベロープの from と to 情報 (qmail-queueが処理中の時) |
queue/info/[0-22]/555 | エンベロープの sender アドレス |
queue/local/[0-22]/555 | ローカル宛に処理待ちのエンベロープ受取人アドレス |
queue/remote/[0-22]/555 | リモート宛に処理待ちのエンベロープ受取人アドレス |
queue/bounce/[0-22]/555 | 回復の見込みのない (permanent な) デリバリーエラー |
[0-22] は 0 から 22 のどれかの意。local, remote はメッセージの状態によっていずれか一方。 qmail-send, qmail-smtpd, qmail-pop3d をすべて止めてから、これらのファイルを削除すればよい。簡単なスクリプト (例:qqclean) を組むか、qmHandle で処理できる。
キューでなく、既にユーザの Maildir に届いたメールの削除の話。世の中のシステム管理者の 5割は忙しすぎ、残りの 4割の管理者はモノグサで、ほとんどのサーバで例えば root 宛のログメールなどが溜まりっぱなしになっているという惨状がある。そこで、期限を決めて、古いメールは自動削除されるように仕掛けをしておこう。これも 1メール 1ファイル形式の Maildir だからこそ成せる技だ。処理自体には find を使う。自分の Maildir にある 10日以上古いメールを削除するとすれば下記のようになる;
find ~/Maildir/ -type f -mtime +10 -exec rm -f '{}' ';'
これを cron で週に一度 (やりたければ毎日でもいいが) 実行してやればいい。ただしユーザ何人分も仕掛けるのはいちいちメンドクサく、ミスの元にもなるので、ちょっと工夫して汎用的に使える rmoldmail というスクリプトを書いた。 /usr/local/bin にでも置いて使って頂きたい。使い方は、ヘルプにも書いたが;
rmoldmail [-V] HOW_DAYS_OLD [OWNER]
OWNER 引数は省略でき、省略した場合にはスクリプト実行ユーザの Maildir が対象となる。例えば、
自分のメールのうちで 7日以上古いメールを毎週日曜日の午前 3時 54分に削除する cronジョブは;
54 3 * * sun /usr/local/bin/rmoldmail 7
hoge というユーザの持つメールのうち 10日以上古いものを毎日午前 4時 31分に削除する、 root 所有のジョブ定義は;
31 4 * * * /usr/local/bin/rmoldmail 10 hoge
Maildir が例外的なディレクトリにある場合には、OWNER 部に `/' で始まる絶対指定のディレクトリ名を指定し、例えば;
31 4 * * * /usr/local/bin/rmoldmail 10 /home/test/hoge
とすると、この場合 rmoldmail は /home/test/hoge/Maildir を探す。`/Maildir' はスクリプトが足すので、引数では付けてはいけない。また、 qmail-vida や 「システムアカウントを増やさずにメールユーザを作る」 の手法を使ってバーチャルメールユーザを管理している場合には、 -V オプションを含めると、スクリプト冒頭の変数 $VMDIR を基底ディレクトリとして指定ユーザの Maildir を探す。詳しくはスクリプト内のコメントを参照。
しかし、システムで多数のバーチャル・メールユーザを管理している場合、一人一人に対する cronジョブを登録するのは面倒だ。そこをカバーするために作ったのが、下の rmmail_virtual スクリプトだ。
これは、前出の rmoldmail と併用して、qmail のユーザ管理ファイルからバーチャルメールユーザを読み取り、各ユーザの古いメールを一括して削除できるシェルスクリプト。上記の rmoldmail スクリプトを /usr/local/bin/ へ配備した上で、こちらのスクリプトを cron のジョブに登録するといい (例えば、 /etc/cron.daily ディレクトリへコピー)。環境によって選択できるように、ふた通りのバージョンを作成した。
いずれも、前述の rmoldmail スクリプトを
/usr/local/bin/rmoldmail -V 21 USER
というコマンドをユーザの数だけ繰り返して呼ぶ仕組みになっている。デフォルトは一応 21日 (3週間) にしているが、rmmail_virtual 冒頭の変数で適宜変更していただきたい。引数は何も要らないが、最低限、
※ユーザ管理ファイルからバーチャルユーザを捜すルーティンには awk 版と egrep+cut 版が書いてあり、後者はコメントアウトしてある。 システム環境などによって awk ルーティンでうまくいかない場合は、後者へ切り替えてみるといいだろう。
※エクステンションアドレス (assignファイルでの +foo-:...) には対応していない。
user$ echo to: hoshu | /var/qmail/bin/qmail-inject
user$ echo to: me@my_isp_mail | /var/qmail/bin/qmail-inject
user$ echo to: nonexistent | /var/qmail/bin/qmail-inject
root# /var/qmail/bin/qmail-inject -f nonexistent to: another_nonexistent subject: test This is a test. <Ctrl-d>
user$ telnet 127.0.0.1 25 helo localhost. mail from: me rcpt to: hoshu data Subject: test This is a test. . quit
root# /usr/sbin/sedmail -t -i -froot@localhost From: root@localhost To: hoshu Subject: test This is a test. <Ctrl-d>
telnet 127.0.0.1 25 ehlo localhost. auth plain ***************** quit
**** の部分の文字列は、userID\0userID\0passwd を Base64 エンコードした文字列。例えば、シェル上から Perl を使って簡単に作れる。userID を hanako, パスワードが passwd だとすると:
perl -MMIME::Base64 -e ' $str = "hanako\0hanako\0passwd"; print encode_base64($str,""),"\n";'
とすれば (※) エンコード文字列が得られるので、それを貼り付ければよい。ただしいちいちコマンドするのは面倒くさいので、こういったスクリプトファイル (smtpauthstr.pl) にしておくと楽だ。なお、Perl に MIME::Base64 モジュールをインストールしてない場合は、何かと便利なのでこの機会にインストールしてしまおう。Perlモジュールのインストール解説は Perlのページで網羅している。
※ SMTP-AUTH で求めるユーザID がメールアドレス (QMAILでは希だが) である場合は、"hanako\@mail.hoge.cxm\0..." のようにアットマークをエスケープしなくてはならない。
なるべく RFC に沿ったフォーマットで日本語のテストメールを簡単に送れる Perl スクリプトを作成した。内部的にはローカル上の sendmail コマンドを使って送信を行っている。
jmailsend.pl
使い方:
jmailsend.pl [-e (jis|sjis|euc|utf8)] [-c N] [-t RECIPIENT] [-h]
スクリプト自体の冒頭に、もっと詳しく使い方が書いてある。文面は固定だが、-e オプションで指定することによって JIS, Shift_JIS, EUC-JP, UTF-8 から文字コードを選べる。 -c オプションで数を指定すれば連続して送るので (宛先はひとつ)、メールサーバの負荷テストにも使える。それらのデフォルト値は冒頭附近の変数で変更することもできる。今のところ SMTP-AUTH には対応していない。
telnet 127.0.0.1 110 +OK <1234.1096123456@hoge.cxm> user hanako +OK pass passwd +OK stat +OK 1 809 list +OK 1 809 . retr 1 (内容が表示される) dele 1 +OK quit
telnet 127.0.0.1 110 +OK <1234.1096123456@mail.hoge.cxm> apop hanako **************** +OK stat (以下、通常の POP3 と同じ)
**** の部分の仕組みは、接続時にサーバが送ってきたチャレンジ文字列 + パスワードを、MD5 でハッシュした 16 オクテット (16進数[小文字] x 32 文字) の値。ちなみに、チャレンジ文字列は、UNIX 上の MTA では通例 <PID.TimeStamp@server> が使われることが多い。上記の例で、生のパスワードが passwd だとすれば、
<1234.1096123456@mail.hoge.cxm>passwd
を ("< >" も含めて) MD5 ハッシュした値を **** に入れれば正解となる。RFC1939 (Post Office Protocol - Version 3)
Fedora Core 1 でやった時には、 openssl を使って発生した値ではうまくいかなかったので Perl のモジュール Digest::MD5 を使った。そのスクリプトを、つまらないものだが参考のため置いておく (mkdigest)。ターミナル上で:
mkdigest "<1234.1096123456@mail.hoge.cxm>passwd"
といった具合に使っていただきたい。素のままだと "<" がリダイレクト記号と解釈されてしまうので、必ずダブルクォートで囲むこと。 Perl モジュール Digest::MD5 はかなり一般的なようだが、万が一インストールされていない場合はインストールすべし。Perlモジュールのインストール解説は Perlのページ。
dot-qmail の項で述べたように、管轄の .qmailファイルに "|program " と書いておけば、そのユーザの Maildir にメールが配信されるや否や (正確にはMaidirに入る直前)、qmail は program にその内容を渡してくれる。program はシェルスクリプトでも perl でも php でも何でもよく、うまく利用すれば、メール内容を処理してデータベースに入力したり、受け取らない (つまり捨てる) ようにしたり、さまざまな処理が実現できる。ユーザ当たりの Maildir 容量に制限を設ける mailquotacheck もこれを応用している。
program は、しかるべき終了コードを返すように書かなくてはならない。program による処理が終わると、制御はまた qmail-local に戻る。その時、qmail-local の挙動を制御するのが「終了コード」だ。
終了コード | 意味 |
---|---|
0 | 処理は正常に完了した。qmail-local はそのまま処理を続けろ |
99 | 処理は正常に完了した。qmail-local はもう何もするな |
100 | permanent なエラー (ハードエラー) |
111 | エラー (ソフトエラー)。qmail-local は後で再び配送を試みよ |
※ 詳しくはman qmail-command せよ。
qmail-local は、stdin を通じてprogram にメール内容を渡し、内容の終了は EOF で知らせてくる。以下に、ごく単純なスクリプト例を示す。
#!/usr/bin/php <?php $input = fopen('php://stdin', 'r'); $msg = array(); while(! feof($input) ){ array_push( $msg, fgets($input) ); } foreach($msg as $line){ echo $line, "<and>\n"; } exit(99); ?>
EOF が来るまで stdin からメッセージを1行ずつ読み取り、エコーするだけのプログラムだ。単純に書き出すだけなら、各行を配列に落とし込む処理は無駄でしかないが、文字列を変換や検索/抽出したり、他のルーティンへ変数渡しする処理などに応用しやすいよう、このようなアルゴリズムを採った。また、実用したわけではないが、スクリプト中で sendmail ラッパーに渡す処理を行えば、自動返信も簡単に実現できそうだ。
このスクリプトを例えば /usr/local/sbin/ に mailtest として置いて実行ビットを立て、テストユーザの .qmail に:
|/usr/local/sbin/mailtest >mailtest.txt ./Maildir/
と書いておけば、受け取ったメールの内容がユーザのメールホームに mailtest.txt として書き出される。 mailtest の終了コードは 99 にしてあるので、qmail-local はここで仕事を放棄するため "./Maildir/" の指示は処理されない。終了コードを 0 にすれば、テキストファイルに加えて通常の Maildir にも配送されるわけだ。