フレッツADSL導入日誌

home / adsl / qmail メールサーバー

導入編
FreeBSDルーター編
ローカルDNSサーバー
フレッツADSLで固定IP
ドメイン取得とDNS運用
>>> メールサーバー
Webサーバー
27 Oct 2018 パッチまとめサイトを見つけたので覚え書き。
10 Jun 2017 ひっそりとパッチの中身を変更。
14 May 2017 STARTTLSパッチについてさらっと記述。
27 Dec 2015 tcp-envのident問い合わせを止めるオプションを追加。
29 Aug 2011 512バイトを超えるDNS応答に関する情報を追加。
27 Aug 2011 ひっそりとパッチ追加。
22 Jul 2011 SPAM対策追加。
15 Nov 2006 dot-qmailに「コマンド実行」を追加。仮想ドメインの説明を少し見直し。
01 Nov 2006 dot-qmailに「条件付ゴミ箱」を追加。「qmailのクセ」を追加。
28 Oct 2006 MUA用のSMTPについて解説するのを忘れてたので追加。
27 Oct 2006 qmail-users・仮想ユーザーと仮想ドメイン・メーリングリスト・副次的アドレスを追加。
25 Oct 2006 aliasさん、dot-qmailのowner・VERPsについて追記。
24 Oct 2006 新規作成。

 まあ、導入日誌なので、メモ書き程度にしておきます。読んでもよく分からないという人は手を出さない方がよいでしょう。DNSの知識は必須です。分からないことはまずサーチエンジンに聞いてください。:-)

home / adsl / qmail ●メールに関係するプログラム

[MTA]

 Mail Transfer Agentで、メール転送役のこと。SMTP(Simple Mail Transfer Protocol, RFC2821)というプロトコルを使用するのが一般的だが、メールキューへ直接メールを放り込む場合(ローカルからメールが発信された場合)はこの限りではない。MTAはMUAや他のSMTPからメールを受け取り、ローカル宛かリモート宛かを判断し、ローカル宛ならばメールボックスへ配送する。リモート宛ならば他のSMTPにメールを転送する。MTAがやるのはメールボックスへ配送するところまで。

[MUA]

 Mail User Agentで、人間がメールを読み書きするためのツール。いわゆるメーラー。メールボックスのあるマシンでMUAを立ち上げた場合は直接メールボックスを読めばよい。他のホストにメールボックスがある場合はPOP(Post Office Protocol, RFC1939)やIMAP(Internet Message Access Protocol, RFC3501)というプロトコルでメールを取り出す。

[その他]

 他にMDA(Mail Delivery Agent)やMSA(Mail Submission Agent)なども語られることがある。

home / adsl / qmail ●qmail

 D.J.Bernstein作のMTA。MTAというと一番メジャーなのはsendmailだが、設定が面倒なのとたびたびセキュリティホールが見つかるため、嫌いな人はとことん嫌いである(ちなみに私は食わず嫌い)。qmailは小さなプログラムを組みあわせて動作させるようにできており、かなり柔軟な設定ができるのが特徴。ただし、djbdns同様、設定方法はかなりハナモゲラである。最新は1.03で1998年にFixしたものだが、今なお現役で動作させることができる。そのままでも充分使えるが、パッチを当てることが多い。公式サイトはhttp://www.qmail.org/。日本語のバナー画像をつつくと前野年紀氏作成の日本語解説ページへ飛ぶ。なお、qmailを受け継いだプロジェクトにnetqmailというものもある。

home / adsl / qmail ●インストール

 とりあえずこの辺からソースアーカイブを落としてきて、適当なディレクトリに展開する。展開したらINSTALLを必ず読むこと。DJBのツール類はインストール方法が一般的なUnixアプリケーションとはかなり異なるので、これを読まないと絶対インストールできない。日本語訳が前野さんのページにある(「qmail付属文書」の「インストール」)。インストールは基本的にスーパーユーザーで行う。

[決めなければならないこと]

 最初にインストールする場所とID類の名前を決める必要があるが、通常はデフォルトのままで構わない。デフォルトのままにすると /var/qmail 以下にインストールされることになる。メールキューの信頼性を上げたい場合、/var/qmail 以下を別のファイルシステムにし、ソフトアップデートをオフにしておくとよい(速度は犠牲になる)。場所を決めたらそのディレクトリを作る(ファイルシステムを別にした場合はマウントし、/etc/fstab も書き換える)。

[ユーザーとグループを作る]

 次に、ユーザーとグループを作る。uid・gidは1000番以下(たいていシステム予約になっている)や実ユーザー・グループに近いIDは避けたほうがよい。10000以上、例えば10025あたりから作るとよいだろう。ちなみに25はSMTPが使うTCPポート番号である。FreeBSDの場合、adduserで作ってもよいが、vipwで直接パスワードデータベースを編集した方がおそらく早い。とりあえず、/etc/group を編集してqmailとnofilesグループを作り、続いてvipwでalias・qmaild・qmaill・qmailp・qmailq・qmailr・qmailsの7つのユーザーを作る。グループやホームディレクトリは指定されたとおりに設定する。普通、aliasのホームディレクトリ(~alias)は /var/qmail/alias、残りのユーザーのホームディレクトリは /var/qmail である。以下、/var/qmail と書いてあったらインストールしたディレクトリのことであり、~qmaild と同じ意味である。違う場所にインストールした場合は適宜読み替えを。

[パッチ当て]

 近年、DNSSEC導入に伴ってDNS応答のサイズが大きくなっており、512バイトを超えるDNS応答を適切に扱えないとメールがうまく配送できないことがある。 qmailも「適切に扱えない」側に入るらしいので、qmail/netqmailにおける512バイトを超えるDNS応答の不適切な取り扱いについて(JPRS)で紹介されているパッチを当てた方がよいようだ。

 その他のパッチは当てなくてもほぼ問題ないが、いくつか癖のある動作をする点がある(後述)ので、お好みに応じてパッチを当てておくとよい。

 また、STARTTLSに対応したい場合は、Qmail-TLS patchを当てておくとよい。 いろいろと手順があるので、パッチ付属の説明書をよく読むこと。 なお、SMTPのSSL/TLSは基本的にホスト・クライアント認証は行わないので、認証局から証明書を取得する必要はない。 つまり、通信路が暗号化されるというだけで、MITM攻撃は防げない。

 あと、最近見つけたのだが、Patching qmail(sagredo.eu linux notes)にqmailのパッチ情報がいろいろ載っている。 英語だけどね。(27 Oct 2018)

[ビルドとインストール]

 ユーザーとグループを作ったら、展開したソースディレクトリの中で make setup check でビルド・インストールが行われる。必要に応じて /var/qmail/bin にパスを通しておく(以下、パスが通っているものとして説明する)。

home / adsl / qmail ●設定と起動

[メールの受信をしない場合(発信専用)の設定]

 Unixはメールを送るときにローカルホストのMTAを使うのが一般的。FreeBSDもcronとかperiodicとかがメールを発信するので、SMTPサーバーとして使用しない場合も設定しておく必要がある。この場合、ほとんどの制御ファイルを省略できる。空のlocalsを作り、メールアドレスのドメインをmeに設定、メッセージID用のホスト名をidhostに設定するだけでOK。これで全部リモート配送になる。

# cd ~qmaild/control
# touch locals
# echo mail.example.jp > me
# echo host.mail.example.jp > idhost
DNSのMXが正しければsmtproutesとかは要らない。ちゃんとMXを見て正しいサーバーへ流れ着く。qmail-injectでメールを送るのならqmail-smtpdは要らない。SMTPがなければ不正中継も起き得ない。設定ができたら [起動] の手順へ進む。

[基本設定]

 INSTALL.ctlを読む。config-fastを使った方がよい。引数はFQDNでホスト名を指定することになっているが、メールアドレスのドメイン部分を使ったほうがよい。例えば、メールホストが mx.example.jp で、メールアドレスのドメインが mail.example.jp ならば、./config-fast mail.example.jp とした方が後々都合がよい。これで最低限の環境(かつ、不正中継されない環境)は整う。ただし後で激しくいぢる予定。

[擬似ユーザー]

 次に、擬似ユーザーを作る。書いてある通りにやるとaliasさん宛配送になってしまうので、touchするのではなく、実メールアカウントを書いたファイルを作った方がよい。

# cd ~alias
# echo someone@mail.example.jp > .qmail-postmaster
# echo someone@mail.example.jp > .qmail-mailer-daemon
# echo someone@mail.example.jp > .qmail-root
# chmod 644 .qmail*

 これでpostmaster・mailer-daemon・root宛にローカル配送されたメールは、someone@mail.example.jp さんに転送される。webmasterなども必要に応じて作っておく。

[ユーザー環境の設定]

 ユーザーのメールボックスを作る。Unixで一般的なのはmbox形式で /var/spool/mail に配送される方式だが、qmail-pop3d を使う場合は Maildir形式で ~/Maildir、つまり受信するユーザーのホームディレクトリ下に配送されるようにする必要がある(そしてこちらの方がお勧め。いろいろすっきりする)。メールを受けるユーザーになって(スーパーユーザーなら su someone で可能)、maildirmake ~/Maildir とする。

[起動]

 /var/qmail/boot/home を /var/qmail/rc にコピーする。デフォルトで Maildir形式配送にするため、今コピーしたファイルを編集し、./Mailbox となっているところを ./Maildir/ に修正する(先頭のピリオドと最後の / を忘れないように)。あとは、

# sh -c "/var/qmail/rc &"
で起動する(最後の & を忘れないように)。うまく動いたら起動スクリプト(FreeBSDの場合 /etc/rc.local あたり)に書き込んでおく。

[メールラッパの設定]

 FreeBSDの場合、/usr/sbin/sendmail はsendmailの実体ではなく、メールラッパ mailwrapper へのリンクになっている。メールラッパは /etc/mail/mailer.conf の設定に従い、起動ファイル名やオプションによって実際に立ち上げるプログラムを決定する。デフォルトでは起動するプログラムとして /usr/libexec/sendmail が指定されていて、これがsendmailの実体である。qmailを使う場合の mailer.conf は、/usr/ports/mail/qmail/files ディレクトリの下に mailer.conf.sample.in というのがあるので、参考にして書き換える。ちなみにこんなの。

# Configuration for mailwrapper is kept in /etc/mail/mailer.conf.
# Replace that file with this one to enable qmail under a sendmail
# disguise. Very useful.

sendmail        %%PREFIX%%/bin/sendmail
send-mail       %%PREFIX%%/bin/sendmail
mailq           %%PREFIX%%/bin/qmail-qread
newaliases      %%PREFIX%%/bin/newaliases
hoststat        %%PREFIX%%/bin/qmail-tcpto
purgestat       %%PREFIX%%/bin/qmail-tcpok

# なんだ、portsにqmailあるじゃん、というツッコミはなしで。

[sendmailを止める]

 sendmailをkillして回る。起動スクリプトから削除する。FreeBSDの場合、/etc/rc.conf に sendmail_enable="NONE" と書く(特定のバージョンの /stand/sysinstall ではきちんと止まらないので注意)。さらにsendmailのバイナリを chmod 0 しておくとよい。FreeBSDの場合、sendmailの実体は /usr/sbin/sendmail ではなく、/usr/libexec/sendmail/* なので注意。

home / adsl / qmail ●配送テスト

 メールの送信は /var/qmail/bin/qmail-inject を使う。リモートからローカルへの配送は、DNSやSMTPを設定する必要があるのでまだできない。

[ローカルからローカルへ]

$ qmail-inject someone
This is a test message.
^D

 スーパーユーザーになって /var/log/maillog を見てみる。~someone/Maildir/new の中を見てみる。

[ローカルからリモートへ]

 (外部の)DNSがちゃんと引けていれば、この状態でリモートへ配送可能なはずである(ファイアウォールなどの環境は各自で確認のこと)。

$ qmail-inject someone@somedomain.example.com.jp
This is a test message for outside.
^D

 スーパーユーザーになって /var/log/maillog を見てみる。someone@somedomain.example.com.jp のメールを適当なMUAで読んでみる。

 あとはバウンス・ダブルバウンスなど、好きなだけテストする。TEST.deliver(インストール - 配送テストの方法)にテストの参考になる情報が書かれている。

home / adsl / qmail ●メールを受ける

 これでローカルからメールを送ることはできるようになったが、リモート(外)からメールを受け取る部分がまだである。外からメールを受け取るにはSMTPデーモンを立ち上げ、DNSにMXレコードを設定する必要がある。

[SMTPデーモン]

 qmailにはqmail-smtpdというSMTPデーモンが付属しており、tcpserverから立ち上げることが推奨されているが、inetdから立ち上げることも可能。inetd.conf に以下の行を加えて、inetdにHUPシグナルを送ればよい。qmail-smtpdはtcp-env経由で立ち上げる必要があることに注意。

smtp stream tcp nowait qmaild /var/qmail/bin/tcp-env tcp-env -R /var/qmail/bin/qmail-smtpd

 -Rはidentによる問い合わせをしない、というオプションである。 FreeBSDルーター/サーバー編 の「ファイアウォールの設定」でも書いたとおり、 identサービス は面倒を引き起こすことが多い。 identの受け側ではdropではなくresetする方が望ましいのだが、dropしている場合はidentのタイムアウト待ちになる。 tcp-envの場合はデフォルトで30秒なので、

となって、SMTPデーモンが動き出すまで30秒待たされることになる。

 ファイアウォールがある場合は、もちろんSMTPポート(TCPポート25)を開ける必要がある。なお、SMTPはMUAからメールを受ける(ユーザーがメールを発信する)際にも利用するが、ここで設定したSMTPは不正中継防止のために外部へメールが送れないようになっている(rcpthosts)。MUA用のSMTPは別に設定する必要がある

[DNSのMXレコード]

 SMTPでメールを転送する場合、DNSのMXレコードを引いて送り先のMTAホストを決めている。例えば、あるSMTPが someone@mail.example.jp 宛のメールを送る時には、mail.example.jp のMXレコードを引く。結果としてmail.example.jp宛のメールを受け取るSMTPサーバー名が得られるので、ポート25にメールを送りつける。

 メールを受け取りたければ自分のDNSにMXレコードを設定しなければならない。MXレコードは名前の部分にメールを受け取るドメイン名、資源データにプリファレンス値とMTAホスト名を書く。プリファレンス値は、同じドメインに対するMXレコードが複数見つかった場合に、どのレコードを優先して使うか、を示している。複数のメールサーバーを運用していないのであれば、とりあえずあまり気にしなくてよい。

 例えば、mail.example.jpドメイン宛のメールを、mx.example.jp というMTAホストが受ける場合、ドメイン取得とDNS運用の例を使うと、

$TTL    432000
@       3600    SOA     ns.example.jp. dnsadmin.example.jp. (
                              2004050701      ; serial
                              10800           ; refresh
                              3600            ; retry
                              604800          ; expire
                              600 )           ; negative
                NS      ns
mail            MX      10 mx
ns              A       172.16.12.34
www             A       172.16.12.34
mx              A       172.16.12.35

というゾーンファイルになる(ゾーンファイルを読み込み直すのを忘れないように)。ホスト名に対するAレコードも必要な点に注意。また、CNAMEレコードの名前を書くことは推奨されていない(避けるべきである)。場合によっては逆引きゾーンファイルにも追加が必要だろう。example.jp ドメイン直下にもメールアドレスがある(admin@example.jpのようなアドレス)場合、

$TTL    432000
@       3600    SOA     ...
...
                NS      ns
                MX      10 mx
mail            MX      10 mx
mx              A       172.16.12.35
...

のような書き方になるだろう。もし、example.jpドメイン下に外からは見えない複数のサブドメインがあり、サブドメイン宛のメールをmx.example.jpが中継しなければならない場合、ワイルドカードを使って、

$TTL    432000
@       3600    SOA     ...
...
*               MX      10 mx
mx              A       172.16.12.35
...

のように書くと楽だが、ワイルドカードは注意して使う必要がある(後述)。

 とりあえず、foo.example.jpドメイン宛のメールをmx.example.jpが受け取ったとする。もし、宛先がメールサーバーmx.example.jp内になければ、mx.example.jpはfoo.example.jpのMXレコードを引いて転送しようとする。この時に引くDNSは内部専用のDNSで、foo.example.jpに関して何か情報を持っていなければならない。外から見えるDNSを引いてしまうと、さっきと同じ結果になるから、自分にメールを転送することになってハマる。メールを中継する場合は内部のDNS設定も確認する必要がある。

[ワイルドカードに関する注意]

 DNSでの名前の比較時にはリソースレコードのタイプは使用されず、また、ワイルドカードはそれ以外の名前にヒットしなかった場合にのみ使用される。例えば、

$TTL    432000
@       3600    SOA     ...
                NS      ns
*               MX      10 mail
mail            A       172.16.12.35

というゾーンファイルを書いたとする。*.example.jpドメイン宛のメールは、mail.example.jp というSMTPサーバーが受け取り、そのIPアドレスは172.16.12.35である、という意図だろう。foo.example.jp ドメインや、bar.example.jp ドメインならばその通りに動く。しかし、mail.example.jp ドメインはAレコードとして書かれているmailにヒットするからワイルドカード扱いにならない。たとえAレコードではなくMXレコードを検索したとしても、である(DNS的にはno such nameではなく、no dataになる)。つまり、この設定では user@mail.example.jp というメールアドレスは使えない。そのようなアドレスを使いたい場合は、明示的にmailという名前のMXレコードを記述する必要がある。

 なお、ワイルドカードはMXレコード以外に使うべきではない、とされている。

 DNSまで設定が終われば、外からメールを受信できるようになっているはずなので、外部から内部向けにメールを送って、正しく受け取れるかどうかを確認する。 届かない場合は、まず外部ネットワークからnslookupでDNSの設定を確認、続いてtelnetかなにかでSMTPを直接叩いて確認するとよい。 プロトコルは検索すれば出てくる。 何を言ってるか分からない人は素直にあきらめれ。

home / adsl / qmail ●MUA向けのサーバー

[POPサーバー]

 MUAでメールを読むためにはPOPサーバーを立ち上げる必要がある。qmailではqmail-popupを使う。qmail-popupを使うには、Mailbox形式ではなく、Maildir形式で配送する必要がある(「起動」を参照)。パスワードチェック用にcheckpasswordをインストールし、inetd.confに

pop3 stream tcp nowait root /var/qmail/bin/qmail-popup qmail-popup FQDN /bin/checkpassword /var/qmail/bin/qmail-pop3d Maildir

と書くだけでよい(inetdにHUPを送るのを忘れずに)。FQDNにはPOPサーバーの完全修飾ドメイン名(mail.example.jpのように、最後まできちっと書いたホスト名)を書く。ちなみに、qmail-popupはユーザー名とパスワードを要求し、それをargv[2]に書かれたプログラムに渡すだけである。argv[2]にはcheckpasswordが書かれているが、これは/etc/passwdを参照してパスワードをチェックし、問題がなければユーザーを変更してargv[1]を実行する。checkpasswordのargv[1]にはqmail-pop3dが書かれているので、結局qmail-pop3dが実行される。qmail-pop3dが実際にメールを読む動作を行う。

[SMTPサーバー]

 MUAからメールを送るにはSMTPサーバーを使う。SMTPサーバーは先ほど設定したが、このサーバーは不正中継防止のため内部へのメールしか受け取らないようになっているため、この用途には使えない。MUAからの接続の場合はRELAYCLIENT環境変数を定義(空でよい)してqmail-smtpdを立ち上げる。そうすると、qmail-smtpdはすべてのメールを受け取るようになる。

 まず問題になるのは、既にqmail-smtpdが立ち上がっているから使えるIPアドレス・ポートがない、という点である。tcpserverなら接続元のアドレスがクライアントマシンに割り当てられたものなら、RELAYCLIENT環境変数を定義してqmail-smtpdを立ち上げるという芸当が比較的簡単にできる(FAQに書いてある)が、inetdだとちょっと面倒。inetdではポートを変えないといけないため、まず、例えばポート2525でRELAYCLIANTなqmail-smtpdが立ち上がるようにする。/etc/services に「smtp-client 2525/tcp」のような行を書き加えておいて、

smtp-client stream tcp nowait qmaild /var/qmail/bin/tcp-env tcp-env -R /usr/bin/env RELAYCLIENT= /var/qmail/bin/qmail-smtpd

とする。inetdにHUPを送れば、ポート2525のqmail-smtpdはメール発信に使えるようになる。これだけだとMUAの設定が面倒なので、FreeBSDならipfwでポート25に来た接続をポート2525に転送してしまう。クライアントのアドレス範囲が 192.0.2.128/25 だったとすれば、

# ipfw add ルール番号 fwd 127.0.0.1,2525 tcp from 192.0.2.128/25 to me 25

というルールを加えておけば、192.0.2.128/25から来た接続はlocalhostのポート2525へ転送され、メール発信用のSMTPが相手をするようになる。当然だが、ポート2525は外に開放してはいけない(いわゆるオープンリレーになってしまう)。tcp-envの後にシェルスクリプトを指定して、TCPREMOTEIPを見てRELAYCLIENTを定義するかどうか決め、その後qmail-smtpdをexecしてもいいだろう(たぶん)。他の環境は各自研究を。

これでメールサーバーとして最低限必要な設定はおしまい!

home / adsl / qmail ●qmail TIPS

 qmailは単純な構造とちょっとした仕様のトリックで、まあとにかくいろんなことができる反面、トリッキーすぎて意外に全体の見通しが悪かったりもします(ハナモゲラが多いしね)。以下、マニュアルだけでは分かりにくいと思ったことを書いておきます。メモ程度にね。ここに書いてない情報は多分マニュアルに書いてありますので、そっちを読んでください。

qmailがメールを配送する仕組み

 以下、宛先メールアドレスがuser@domainだったとします。メールの受け取り・差し戻しの判断機構は、以下に書いてある他にも存在します。

[メールの発生]

・ローカルに発生したメールは、qmail-injectがqmail-queueを使ってキューに放り込む。

・リモートから来たメールは、qmail-smtpdが受ける。

・qmail-smtpdは、domainがmorercpthostsかrcpthostsにヒットすれば、qmail-queueを使ってメールをキューに放り込む。ヒットしなければメールは受け取らない(不正中継対策)。

[qmail-send]

・キューに放り込まれたメールはqmail-sendが読み出し、宛先を調べる。

・domainがlocalsにあれば、メールはuserさん宛ローカル配送と見なされる。

・domainがvirtualdomainsにあれば、virtualdomainsの設定に従ってプレフィックスが付けられ、prefix-userさん宛ローカル配送と見なされる。

・それ以外のメールはリモート配送と見なされる。

[ローカル配送]

・ローカル配送のメールはqmail-lspawnに渡される。

・qmail-lspawnは、まずqmail-users機構を使って、配送する実ユーザーを決定する。

・qmail-users機構にヒットしなければ、qmail-getpwを呼び出し、/etc/passwdを参照して配送する実ユーザーを決定する。

・qmail-getpwは実ユーザーが見つからなければ、aliasさん宛配送になるように仕向ける。

・いずれの場合もdot-qmail制御ファイル名の形式を決定する。通常 .qmaildashextという形で、dashは空もしくはハイフン、extは拡張部を示す。

・以上の情報をqmail-localに渡して実際の配送を行う。

[qmail-local]

・メッセージにDelivered-Toヘッダを付け、メール転送ループをチェックする。

・メッセージにReturn-Pathヘッダを付ける(表書きの送り主)。

・dot-qmail制御ファイルを調べる。dashが空で.qmailextファイルがない場合、制御ファイルが空であったのと同様に扱われる。

・dot-qmail制御ファイルが空(0バイト)の場合は、qmail-start起動時に指定したdefaultdeliveryの指示に従う。

・dot-qmail制御ファイルがない場合、メールは差し戻される。

・それ以外の場合はdot-qmail制御ファイルの内容に従ってメールを配送する。

[リモート配送]

・リモート配送の場合はqmail-rspawn経由でqmail-remoteが起動される。

・qmail-remoteはdomainがsmtproutesに含まれれば、smtproutesで指定されたSMTPサーバーにメールを送りつける。

・そうでなければDNSでdomainのMXレコードを引き、そこに送りつける。

home / adsl / qmail ●dot-qmail

 通常、ユーザーのホームディレクトリにある.qmailという名前のファイルのことだが、qmail-users機構や拡張アドレス機構で、他の名前になったり、複数の同じ働きをするファイルが存在したりする。

[dot-qmailファイル名の決め方]

 メールアドレスはvirtualdomainsによってプレフィックスが付けられる可能性がある。プレフィックスを付けた結果によってqmail-users機構が検索され、続いてqmail-getpwで検索される。qmail-usersではアドレスから直接ユーザーを決めるのではなく、あくまでデータベース中で指定されたユーザーに配送され、dot-qmailファイル名もデータベースの指示によって決定される(後述: qmail-users参照のこと)。

 qmail-getpwでは、最初のハイフンより前の部分がユーザー名として扱われ(マシンの実アカウント名にハイフンが入ってる場合は知らん)、dot-qmailはメールアドレスが user の形なら単に .qmail、user-ext の形なら .qmail-ext の形になる(ハイフンは複数あってもよい)。実ユーザーが見つからなかった場合はaliasさん宛配送になり、dot-qmailは .qmail-user あるいは .qmail-user-ext になる。つまり、unknownさんがいない場合、unknwon宛のメールは ~alias/.qmail-unknown の配送指示に、unknown-ext宛のメールは ~alias/.qmail-unknwon-ext の配送指示に従う。

 通常、dot-qmailファイルはユーザーのホームディレクトリ(~user)にある。aliasさん宛配送になった場合も同じで、aliasさんのホームディレクトリはインストール時に /var/qmail/alias にしているはずである(分からなかったら /etc/passwd を見ればよい)。qmail-users機構を使用する場合は場所を変更することができる。

[ゴミ箱アドレス]

 dot-qmailが空(0バイト)ならメールが配送されるが、空でなければ中の配送指示に従う。dot-qmailにはコメントを書けるが、もし、dot-qmailの中身がコメントだけならば、配送は行われずにメッセージは消失する。もう使ってなくてSPAMばっかり来るメールアドレスに使うとよい。バウンスも発生しない。

[コマンド実行]

 dot-qmailの行頭が「|」だと、それ以降の文字列を /bin/sh -c で実行する。シェルは /bin/sh とハードコーディングされているので注意。終了コード0は配送成功、99は配送成功だが以降の行を無視、100はバウンス発生、それ以外は再配送となる。細かいルールはdot-qmailマニュアルの下のほうに書いてある。

 コマンド実行中、標準入力からはメールメッセージがヘッダ・本文の順に普通のテキストファイルとして読める。標準出力に書き出した文字列はログに残る。また、バウンスする場合はバウンスメール中の説明部分

Return-Path: <>
Date: 27 Oct 2006 02:04:15 -0000
From: MAILER-DAEMON@example.jp
To: someone@mail.example.jp
Subject: failure notice

Hi. This is the qmail-send program at mail.example.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.

<nobody@mail.example.jp>:
Sorry, no mailbox here by that name. (#5.1.1) ← この部分

--- Below this line is a copy of the message.

うんぬんかんぬん
に使用される。変なエラーメッセージが出ないように注意。これ以外ではqmail-injectなどを呼ばない限り、コマンド実行結果がメールとなって出て行くことはない(と思う)。

[条件付ゴミ箱]

 dot-qmailのコマンド実行時に99を返すと配送は成功したと扱われるが、dot-qmailの残りの部分は無視される。成功なので再配送もバウンスも発生しないが、99を返したコマンドがqmail-injectやqmail-queueを呼んでない限り、配送もされない。これを利用すると、needed というコマンドが成功したら配送、失敗したらゴミという場合、

|/path/to/needed || exit 99
./Maildir/
と書けばよい。1行目では99を返そうがそうでなかろうが配送は起きないので、2行目に配送のための行が必要な点に注意。2行目をbouncesayingにすると配送ではなくバウンスにすることもできる。

[拡張アドレスとdefault]

 例えば、配送アドレスがuser-foo-barだとしたら、qmail-getpwではuser宛の配送になり、~user/.qmail-foo-bar というファイルの指示に従うことになる。このファイルが存在してなおかつ空ならば、userのメールボックスにメールが配送される。

 ~user/.qmail-foo-bar がなかった場合は、~user/.qmail-foo-default・~user/.qmail-default を探し、いずれもなければメールは差し戻される。ただし、拡張部がない場合(単にuser@domain宛だった場合)は、~user/.qmailがないのは空のdot-qmailファイルがあったのと同じ扱い(厳密な条件はqmail-usersを参照)になる(=メールはuserのメールボックスに配送される)。

 つまり、userがホームディレクトリで .qmail-foo というファイルをtouchしておけば、user@domain というアドレスの他に、user-foo@domain というアドレスでメールを受け取れる。.qmail-default をtouchしておけば、user-foo@domain だろうが user-bar@domain だろうが user-foo-bar@domain だろうが、とにかく user-ほげ@domain というアドレス宛のメールを全部受け取れる。~alias/.qmail-default をtouchしておくと、配送からあぶれたメールは全部aliasさんのメールボックスへ行く。ゴミ箱にしておくと勝手に消えてなくなる(バウンスもしなくなる)。

 qmail-usersを使う場合は、拡張アドレスにヒットするワイルドカード型の割り付けを記述しておく必要がある。dot-qmailの場所・名前は、qmail-getpwと同じにすることもできるし、まったく違うものにすることもできる。

 なお、qmail-localがdot-qmailファイルを探す時には、拡張部の「.」(ドット)は「:」に、大文字は小文字に変換してから探すので注意。区切り文字は常に「-」(ハイフン)で、qmail-usersのdashの設定とは関係ない。検索に関係するのは拡張部だけ。dashが「-this-is-dash-」のような文字列だと、.qmail-this-is-dash-default までしか検索されない。aliasさん宛配送になる場合、dashは「-」となり、宛先アドレスの@より前全部が拡張部となる。このため、unknown-user 宛メールがaliasさん宛配送になった場合は、~alias/.qmail-unknown-user → ~alias/.qmail-unknown-default → ~alias/.qmail-default の順に検索される。

[owner]

 よくメーリングアドレスの返信先に使われるアレ。dot-qmailで転送を指示すると、普通は表書き差出人(=返信先)は書き換わらない。しかし、使用したdot-qmailファイル名の後ろに-ownerという名前の付いたファイルがあれば、返信先アドレスが「受信したアドレス-owner@domain」という形になる(元が空アドレス <> や、#@[] だった場合は除く)。例えば、foo@somewhere さんから user-ext@domain さんにメールが着き、~user/.qmail-ext で bar@anotherplace に転送したとすると、通常は表書き差出人は foo@somewhere さんのままである。しかし、~user/.qmail-ext-owner というファイルがあると(空でもよい)、メールヘッダ中の From: はそのままで、表書き差出人が user-ext-owner@domain に書き換わる。この後で何かエラーが起きるとエラーメールは user-ext-owner@domain 宛に返ってくる。このアドレスに対する dot-qmail は ~user/.qmail-ext-owner で、このファイルが存在するからこのアドレスが自動生成されたので、必ずメールを受け取ることができるのがミソ・・・なのだが、仮想ドメインを使うと実はうまくいかない(仮想ドメインと転送ownerを参照のこと)。

[VERPs]

 Variable Envelope Return Pathsのこと。直訳すると可変表書き返信パス。メールの送り先によって表書きの差出人アドレスを書き換えること。メール転送時に-ownerで終わるdot-qmailファイルがあると表書き差出人が 〜-owner に書き換わるが、-owner-default で終わるdot-qmailファイルあると、qmail-localは表書き差出人を「受信したアドレス-owner-@domain-@[]」というハナモゲラに書き換える。これが送信先アドレスと共にqmail-sendに渡ると、さらに「受信したアドレス-owner-送信先アドレスの@を=に変えたもの@domain」に書き換えられる。

 先ほどの例なら、~user/.qmail-ext-owner-default があれば、表書き差出人は user-ext-owner-@domain-@[] に書き換わり、さらにqmail-sendが user-ext-owner-bar=anotherplace@domain に書き換える。~user/.qmail-ext-owner-default があるため、このアドレスもやはり受け取り可能。

 通常、エラーメールの表書き差出人は空アドレスになっていて、表書きではどこからエラーメールが戻ってきたか分からない。VERPsだとエラーメールの受取人に元のメールの受取人(=エラー発生源の可能性が高い)の情報が入るため、表書きの受取人を見ればどこからメールが戻ってきたか分かる。この場合なら、頭の user-ext-owner- と @ 以降を取り去って、= を @ に変えた「bar@anotherplace」がエラーを起こした送信先だと分かる。この操作はsedで簡単にできる。また、メールアドレスが書き換わっているため、さらに転送してしまう(=ループになる)危険が少ない。

home / adsl / qmail ●qmail-users

 /var/qmail/users/assign というテキストファイルを書き、/var/qmail/bin/qmail-newu を実行することで実際にqmailが参照するデータベースが作成される。このファイルはqmail-lspawnから参照され、配送先のユーザー・ディレクトリと、dot-qmail制御ファイルの名前を決定するために使われる。qmail-newuを実行し忘れると変更が反映されないので注意。

 assignの書式は以下の通り。

=local:user:uid:gid:homedir:dash:ext:
+loc:user:uid:gid:homedir:dash:pre:
.

前者は単純割り付けで一つのアドレスのみにヒット。後者はワイルドカード型割り付けで複数のアドレスにヒットする。最後にピリオドだけの行が必要なので注意。

 qmail-sendがローカル配送と決めたメールは、必要に応じてプレフィックスが付けられてqmail-lspawnに渡される。宛先はローカル部(@より前の部分、いわゆるユーザー名)とドメイン部に分けられ、ローカル部のみが配送先ユーザーの決定に使用される。つまり、qmail-lspawn が foo@d1.example.jp 宛のメールを受け取り、user1 に配送すると決定したならば、foo@d2.example.jp 宛のメールだろうが、foo@hanamogera.example.com.jp 宛のメールだろうが、とにかく qmail-lspawn に渡れば user1 に配送される、ということである。locals に上記のドメインが全部書いてあれば実際にそうなる。

 単純割り付けもワイルドカード型割り付けも、ルールにヒットしたメールはユーザIDがuid・グループIDがgidのユーザーuserに配送され、qmail-localはhomedirに移って処理を行うが、制御ファイルの決定の仕方が若干違う。

[単純割り付け]

 行の先頭が「=」の場合は単純割り付けとなり、ローカル部がlocalに一致した場合に配送が行われ、制御ファイル名は .qmaildashextになる。qm-sales 宛のメールをuidが1234、gidが5678のfooに配送し、/home/foo/.qmail-sales の指示に従わせたければ、

=qm-sales:foo:1234:5678:/home/foo:-:sales:

/home/foo/sales/.qmail の指示に従わせたければ、

=qm-sales:foo:1234:5678:/home/foo/sales:::

となる。なお、この場合、/home/foo/sales/.qmail が空(でdefaultdeliverlyが ./Maildir/ )だったり、

./Maildir/
と書いてあったりすると、/home/foo/Maildir ではなく、/home/foo/sales/Maildir に配られることになるので注意。そういう用途もあるかもしれないが、POPで読むときに困ることになるだろう。転送先を書いておくか、
/home/foo/Maildir/

と書いておけばよい。なお、dashやextには「/」を含めることもでき、

=qm-sales:foo:1234:5678:/home/foo:-sales/:dot/qmail:
と書くと、/home/foo/.qmail-sales/dot/qmail の配送指示に従うことになる。

[ワイルドカード型割り付け]

 行の先頭が「+」の場合はワイルドカード型割り付けとなり、ローカル部の先頭がlocに一致した場合にヒットする。ローカル部のlocを除いた残りがextとして扱われ、制御ファイルは .qmaildashpreext となる。ローカル部がlocのみだった場合にも一致し、その場合extは空になる。qmail-localは preext の部分を新たにextと見なして処理を行う。dashは dash のまま扱われる。

+qm-sales:foo:1234:5678:/home/foo:-:sales:
と書くと、qm-sales宛のメールは /home/foo/.qmail-sales、qm-sales-person宛のメールは /home/foo/.qmail-sales-person、qm-salesperson宛のメールは /home/foo/.qmail-salesperson の配送指示に従う。ハイフンの入り方に注意。dashやextに「/」を入れることができるのは単純割り付けと同じ。

[qmail-pw2u]

 qmail-usersだけでqmail-getpwと同じ働きをさせたい場合、1人のユーザーについて次の2行を書く。

=user:user:uid:gid:/home/user:::
+user-:user:uid:gid:/home/user:-::

これで user宛のメールは単純割り付けにヒットし、/home/user/.qmail の指示に従い、user-ext宛のメールはワイルドカード割り付けにヒットし、/home/user/.qmail-ext の指示に従う。userextのようなアドレスにはヒットしない。qmail-pw2uはこれを自動でやってくれる。結果は標準出力に出力されるので、assignにリダイレクトしてqmail-newuすればよい。

[dashとpre・ext]

 dashpreextはつながっているから、どちらにどちらを書いてもいいように思う。例えば、

+user-:user:uid:gid:/home/user:-ab-::
+user-:user:uid:gid:/home/user:-ab:-:
+user-:user:uid:gid:/home/user:-a:b-:
+user-:user:uid:gid:/home/user:-:ab-:
+user-:user:uid:gid:/home/user::-ab-:

は、どれもdot-qmailのファイル名は同じになる。

 しかし、拡張部を「-」で切ってdefaultを付け加える動作を行う場合、最後のファイル名が .qmaildashdefault となるため、上記5行はファイル探索の最後の方で若干異なった検索が行われる。例えば、ローカル部が user-ext だった場合、

+user-:user:uid:gid:/home/user:-ab-:: → .qmail-ab-ext → .qmail-ab-default
+user-:user:uid:gid:/home/user:-ab:-: → .qmail-ab-ext → .qmail-ab-default → .qmail-abdefault
+user-:user:uid:gid:/home/user:-a:b-: → .qmail-ab-ext → .qmail-ab-default → .qmail-adefault
+user-:user:uid:gid:/home/user:-:ab-: → .qmail-ab-ext → .qmail-ab-default → .qmail-default
+user-:user:uid:gid:/home/user::-ab-: → .qmail-ab-ext → .qmail-ab-default → .qmail-default → .qmaildefault

となる(たぶん)。このため、拡張部がある場合はdashの最後をハイフンにしておかないと非常に不自然な動作になる。

 また、dash空の場合、dot-qmailファイルがないのは空のdot-qmailファイルがあるのと同じ扱いになる。つまり、最初の4行はdot-qmailファイルがなければメールは差し戻されるが、最後の1行だけはdot-qmailファイルがなくても全拡張アドレス宛のメールを受信することになるので注意。普通は単純割り付けの方はdashを空にし、ワイルドカード割り付けの方はdashに何か書いておく。

home / adsl / qmail ●仮想ユーザーと仮想ドメイン

 /var/qmail/control/virtualdomains に設定を書き込み、qmail-sendを再起動するか、HUPシグナルを送ると使えるようになる。名称を分けているが処理は一緒。virtualdomainsには「仮想アドレス:追加するプレフィックス」の形の行を好きなだけ並べておく。受信アドレスが「仮想アドレス」にヒットしたら、宛先を「追加するプレフィックス-元の宛先」の形に書き換え、ローカル配送する。ただし「追加するプレフィックス」が空だった場合は書き換えは行わず、配送もリモートのまま。「仮想アドレス」が「.」(ピリオド)で始まるドメイン名の場合、そのドメインのサブドメインすべてにヒットする(そのドメインにはヒットしない、と思う)。ローカル部(ユーザー名の部分)にはワイルドカードは使えない。

 virtualdomainsはlocalsの後に処理されるため、localsに含まれているドメインは必ずローカル扱いとなり、上記書き換えが行われることはない。外からメールを受けている場合、virtualdomains だけでなく、SMTPのrcpthosts(かmorercpthosts)にもドメインを書き加える必要がある。もちろんDNSのMXレコードも正しく設定しなければならない。

 virtualdomainsに

virtual@virtual.example.jp:user
virtual.example.jp:virtual
.virtual.example.jp:virtual-sub
virtual@mail.example.jp:user
と書くと、virtual.example.jp がlocalsに含まれていなければ、
virtual@virtual.example.jp 宛のメールはuser-virtual@virtual.example.jp 宛に、
foo@virtual.example.jp 宛のメールはvirtual-foo@virtual.example.jp 宛に、
bar@subdomain.virtual.example.jp 宛のメールはvirtual-sub-bar@subdoamin.virtual.example.jp 宛に、

それぞれローカル配送となる。

 ローカル部にはワイルドカードは使えないので、virtual@mail.example.jp は4行目にヒットしてローカル配送になるが、is.it.virtual@mail.example.jp はリモートのまま。rcpthostsに mail.example.jp が書いてあると、SMTPで受け取って、qmail-sendでリモート配送と判定され、またSMTPで自分に送りつけてループになりそうだが、実際にはqmail-remoteが適宜判断してくれてループにはならない(普通はね。意地悪すればループにできそうだけど)。

[その後の処理]

 アドレスを書き換えてqmail-lspawnに処理が渡された後は、他のメールと処理はまったく同じ。書き換えた結果が virtual-foo@virtual.example.jp だとすれば、以下のような方法が考えられる。

1. aliasさんに配送する

 何もせず放っておくとaliasさんに配送される。dot-qmailファイルは ~alias/.qmail-virtual-foo。ここに実際に受信するアドレスを書いておけばよい。

2. virtualさんに配送する

 virtualというユーザーを作っておけばそこに配送される。dot-qmailファイルは ~virtual/.qmail-foo。

3. qmail-usersを使う

 以下の行を /var/qmail/control/assign に書いて、qmail-newuする。

=virtual-foo:user:uid:gid:/home/user:-virtual-foo::

userさん宛に配送され、dot-qmailファイルは ~user/.qmail-virtual-foo。拡張アドレスも受信したい場合は、以下の行も加える。

+virtual-foo-:user:uid:gid:/home/user:-virtual-foo-::

この例だと、拡張アドレスに対するdot-qmailは .qmail-virtual-foo-default まで検索される。.qmail-default まで検索させたい場合は、

=virtual-foo:user:uid:gid:/home/user:-:virtual-foo:
+virtual-foo-:user:uid:gid:/home/user:-:virtual-foo-:

とする。

[仮想ドメインと転送owner]

 仮想ユーザー・仮想ドメインを使うとプレフィックスが付く。メールを差し戻す時はqmail-sendがこのプレフィックスを取り除いてくれるのだが、qmail-localはそれをやってくれない。Delivered-To: にもプレフィックスが付いたままのアドレスが出る。

 また、転送時に.qmail-*-owner ファイルがあると表書き差出人が *-owner になるが、この処理をやっているのはqmail-localなので、転送時の-owner表書きアドレスにもプレフィックスがついてしまう。例えば、list@virtual.example.jp 宛のメールがvirtualdomainsにヒットして virtual-list@virtual.example.jp 宛ローカルになり、aliasさんに配送されて、~alias/.qmail-virtual-list に何か転送アドレスが書かれているとする。このとき、~alias/.qmail-virtual-list-owner があると表書き差出人アドレスが書き換えられるが、qmail-local はこのメールが仮想ドメイン扱いだと知らない(調べようともしない)ので、本来 list-owner@virtual.example.jp となるべき表書き差出人アドレスが virtual-list-owner@virtual.example.jp になってしまう。このアドレス宛に何かメールが帰ってくると仮想ドメイン定義にヒットし、virtual-virtual-list-owner宛にローカル配送となるが、~alias/.qmail-virtual-virtual-list-owner というファイルはないので、そのままでは受け取れない

 これはqmail-localで対応すべき問題だと思うのだが・・・(qmail-localは受取人のドメイン名を知っているから、virtualdomainsを調べれば仮想ドメインにヒットしたことを判定できるし、プレフィックスも分かるから、余計なプレフィックスを取り除くこともできる) → ということでパッチを作ってみた。

home / adsl / qmail ●メーリングリスト

 一般ユーザーの拡張アドレスで簡単なメーリングリストを作りたければ、~user/.qmail-list に配送するユーザーをずらずら書いて、~user/.qmail-list-owner をtouchしておけばよい。VERPsを使いたければ ~user/.qmail-list-owner-default もtouchする。

 これだとナンバリングも何もされないので、~user/.qmail-list でナンバリングや投稿者制限を行ってから、例えばuser-list-deliver宛に転送し、~user/.qmail-list-deliver に

|bouncesaying "No such address" [ "$SENDER" != "user-list-owner@example.jp" ]
配信アドレス1
配信アドレス2
配信アドレス3
...
と書いておけばよいだろう。この方法は user-list-owner@example.jp を詐称すれば配信アドレスに直接メールを送れてしまう欠点がある。

[仮想ドメインを用いたメーリングリスト]

 まず、virtualdomainsで仮想ドメインを作る。rcpthostsやDNSの設定も忘れずに。

list.example.jp:list

これで foo@list.example.jp 宛のメールは list-foo@list.example.jp 宛にローカル配送となるので、先ほどの3つの方法から好きな方法を選んで配送する。ML管理者は一般ユーザーのことが多いのと、一つのドメインに複数の(異なるユーザーが管理する)MLがあるのが普通だろうから、以下のようなassignを書いて、qmail-usersでMLを管理するユーザーに振るのがよいと思われる。

=list-foo:user:1234:5678:/home/user:-list-foo::
+list-foo-:user:1234:5678:/home/user:-list-foo-::

これで、foo@list.example.jp 宛のメールはuserさん宛に配送され、dot-qmailは /home/user/.qmail-list-foo となる。このファイルはML管理者であるuserさんが自由に編集できる(ファイルパーミッションに注意)。.qmail-list-foo-owner をtouchしておくと差出人を書き換えてくれるが、list-foo-owner になってしまうので、.qmail-list-foo で

|qmail-inject -f foo-owner@list.example.jp foo-deliver@list.example.jp
として表書き差出人を書き換えて foo-deliver@list.example.jp へ転送する。すると、上記assignの2行目にヒットして /home/user/.qmail-list-foo-deliver の指示に従うようになるので、ここに先ほどのような転送リストを書く。

 ナンバリングする時は、qmail-injectと同じ行で、qmail-injectの前に処理し、パイプでqmail-injectへ渡せばよい。うまくやればsedでもできそうだが、あまり考えたくない。:-)

[deliverを隠す]

 この例でも foo-owner@list.example.jp を詐称されると、直接 foo-deliver@list.example.jp へメールを放り込むことができてしまう。これを防ぐには、virtualdomainsに

list.example.jp:list
list.deliver:deliver
と書いて(もちろんdeliverドメインはrcpthostsやDNSには教えない)、assignで
=list-foo:user:1234:5678:/home/user:-list-foo::
+list-foo-:user:1234:5678:/home/user:-list-foo-::
=deliver-foo:user:1234:5678:/home/user:-deliver-foo::
とし、~user/.qmail-list-foo で、
|qmail-inject -f foo-owner@list.example.jp foo@list.deliver
とすればよい。foo@list.example.jp に送られたメールは差出人が書き換えられて foo@list.deliver へ送られ、virtualdomainsで deliver-foo@list.deliver 宛となり、qmail-usersでuserさん宛ローカル配送となって、/home/user/.qmail-deliver-foo の指示に従うようになる。

 list.deliverというドメインは中からも外からもDNSを引けないから、SMTP経由で foo@list.deliver 宛のメールが入ってくることはありえない。qmail-injectで foo-owner@list.example.jp を名乗ってメールを送ってくるような確信犯については知らん。ちなみに、list.deliverの部分に使うドメイン名にはピリオドが入ってないとqmail-injectが余計なことをしてくれるので注意。

 まだ副次的なアドレスの問題が残っているが、それは次以降で。

home / adsl / qmail ●副次的アドレス

 qmailでは拡張アドレスや仮想ドメインを簡単に作れる反面、副作用として意外なアドレスが同時にできてしまうことがよくある。そのようなアドレスをここでは「副次的アドレス」と呼ぶことにする。

[dot-qmailによる副次的アドレス]

 今まではアドレスからdot-qmailの名前を考えていたが、では逆にdot-qmailからアドレスを作り直したらどうなるか? 例えば、先ほど一生懸命隠した foo@list.deliver は /home/user/.qmail-deliver-foo の指示に従うが、ここから逆にアドレスをguessすると、user-deliver-foo@example.jp へ送るとこのdot-qmailが使われることに気づく。つまり、user-deliver-foo@example.jp というアドレスに投げれば、foo@list.deliver へ投げたのと同じ効果が得られてしまう。

 これを防ぐには、dashの先頭にハイフン以外のものを混ぜればよい。例えば、

=list-foo:user:1234:5678:/home/user:.list-foo::
+list-foo-:user:1234:5678:/home/user:.list-foo-::
=deliver-foo:user:1234:5678:/home/user:.deliver-foo::

とすれば、

foo@list.example.jp のdot-qmailは/home/user/.qmail.list-foo
foo-owner@list.example.jp のdot-qmailは/home/user/.qmail.list-foo-owner
foo@list.deliver のdot-qmailは/home/user/.qmail.deliver-foo

になる。qmail-getpwスタイルではdashは必ずハイフンになるので、これらのdot-qmailに対応する example.jp ドメインの名前は絶対に生成できない。ディレクトリを分けて、

=list-foo:user:1234:5678:/home/user:.list/foo::
+list-foo-:user:1234:5678:/home/user:.list/foo-::
=deliver-foo:user:1234:5678:/home/user:.list/foo.deliver::

とすることもでき、こうすると

foo@list.example.jp のdot-qmailは/home/user/.qmail.list/foo
foo-owner@list.example.jp のdot-qmailは/home/user/.qmail.list/foo-owner
foo@list.deliver のdot-qmailは/home/user/.qmail.list/foo.deliver

になる。好みの問題だが、ディレクトリが分かれるのと、ファイル名先頭がドットではなくなるので、この方が扱いやすいだろう。deliverの前がドットになっているのは、ハイフンにしてしまうと foo-deliver@list.example.jp と制御ファイルが同じになってしまうから。

[localsとvirtualdomainsによる副次的アドレス]

 副次的アドレスにはもう一つ、locals にヒットしたアドレスの先頭が、virtualdomainsの付けるプレフィックスと同じになってしまうために起きるものがある。例えば、example.jp がlocalsにあると、deliver-foo@example.jp 宛のメールはそのままローカル配送となり、qmail-usersでvirtualdomainsのdeliverプレフィックスのために作ったルールにヒットして、foo@list.deliver宛と同じように配送されてしまう。これは、example.jp も仮想ドメインとして扱うことで防ぐことができる。逆に言うと、仮想ドメインを使うならローカルドメインは使うな!ということになる。

 実現するにはlocalsを空にする。削除すると control/me が使われてしまうので注意。0バイトのlocalsを作る。続いて、virtualdomainsに

example.jp:mail
list.example.jp:list
list.deliver:deliver

と書くと、user@example.jp 宛のメールはvirtualdomainsにヒットして mail-user@example.jp 宛ローカル配送になる。これはqmail-getpwではうまく扱えないから、qmail-usersを使うことになる。assignはこうなるだろう。

=mail-user:user:1234:5678:/home/user:::
+mail-user-:user:1234:5678:/home/user:-::
=list-foo:user:1234:5678:/home/user:.list/foo::
+list-foo-:user:1234:5678:/home/user:.list/foo-::
=deliver-foo:user:1234:5678:/home/user:.list/foo.deliver::

ユーザー1人につき2行ずつ追加する必要がある。要するにqmail-pw2uの出力の前にmail-を追加した形なので、qmail-pw2uで吐き出させて手動でmail-を書き加えてもよいし、sedでも簡単にできるだろう。これで、user@example.jp 宛のメールはuserさん宛にローカル配送となり、アドレスは mail-user@example.jp となる。dot-qmail は /home/user/.qmail で以前と同じである。同様に、user-foo@example.jp 宛のメールもuserさん宛にローカル配送となり、アドレスは mail-user-foo@example.jp、dot-qmail は /home/user/.qmail-foo となる。

 この方法はユーザーがMaildirを作ろうがdot-qmailを作ろうが、assignを書き換えなければメールは配送されないため、シェルアカウントのみでメールアカウントを付与しないようなユーザーも作れる。

 ただし、全部が仮想ドメインになるので、[仮想ドメインと転送owner]で書いた問題が発生することに注意。したがって、ユーザーがお気軽にメーリングリストを作る場合でも、表書き差出人アドレスを明示的に書き直す必要がある。これはユーザーにはっきり伝えておかなければならない。

[qmailを2つ立ち上げる]

 どうしてもこれがイヤなら、サーバーを2台用意するか、1台のサーバー中で2つのqmailを走らせ、一方を実ドメイン専用、もう一方を仮想ドメイン専用にすれば解決する(たぶん)。サーバーを分けると実ドメインと仮想ドメインでdot-qmailファイルが置いてあるサーバーが別になるが、NFSを使えばある程度は解決する。内部DNSのMXの設定はもちろん変更する必要がある。

 qmailを2つにする場合は、qmailのホームディレクトリが2つということだから、qmail用のユーザーも全部2つずついる。ということは、設定を変えて2回ビルドする必要がある。ただ、この場合は実ユーザーのホームディレクトリは1つなので、dot-qmail置き場は一箇所にまとめることができる。SMTPが問題になるが、一方のSMTPをポート25以外(例えばポート10025)で動かしておき、ポート25で動いている方のrcpthostsは実ドメインも仮想ドメインも全部受け取るように書いて、smtproutesに

:localhost:10025

と書いておけばよい(たぶん)。ポート25のSMTPで受け取ったメールは、qmail-sendでlocalsとvirtualdomainsにヒットしたものがローカル配送処理され、残りはqmail-remoteに行き、DNSの設定に関係なく全部もう一方のSMTPに行く。DNSのMXはポート25で動いているqmailが全部受け取るように書く。つまり今まで通りでよい。

 究極的にはすべてのドメインでqmailを分けてしまえば「仮想ドメインと転送owner」問題は完全になくなる。あるいは自力でqmail-localにパッチを当(ry

home / adsl / qmail ●qmailのクセ

[Message-ID]

 Message-IDを付けるのは送信するMUAの責任という方針で、qmail-inject以外は基本的に付けてくれない。付ける方法はいくつかあるがそれぞれ特徴がある。

1. fixme@fixup方式

 FAQで解説されている。qmail-smtpdを立ち上げる時、RELAYCLIENT環境変数になにか文字列を設定しておくと、宛先の制限がなくなるだけでなく、その文字列が宛先に付け加えられてキューに放り込まれる。RELAYCLIENTを@fixmeにしておくと、foo@example.jp 宛メールは foo@example.jp@fixme 宛になり、qmailは基本的に最後の@以降をドメインとして扱うので、virtualdomainsで「fixme:fixup」と指定しておくと、fixup-foo@example.jp@fixme 宛ローカル配送される。これをaliasで受け、~alias/.qmail-fixup-defaultで宛先を元に戻してqmail-injectで送り直す

 Message-IDを付けるのはqmail-injectである。元のメールの宛先が複数あると、それぞれがqmail-injectで送りなおされるので、Message-IDがバラバラになってしまう欠点がある。これはスレッドがつながらないという事態を生む。お勧めできない。

2. ofmipdを使う

 mess822パッケージをインストールして、MUA用のSMTPにはofmipd(Old-Fashioned Mail Inject Protocol Daemon)を使う。Message-IDはofmipdが付けてくれる。が、その他のヘッダも強烈に書き換えてくれるので、Fromとかにエスケープされそうなキャラクタが入っていると結構痛い目に遭う。

3. qmail-smtpdにパッチを当てる。

 Yahoo! で「qmail smtpd message id」とかでサーチすると出てくる。seclan のほえほえルームさんの自作ソフトウエアの下の方、「Message-ID & Date フィールド付加パッチ - PDS for qmail-1.03 」あたりがメジャー? なのかな? うちも基本的にこれを使ってますが、環境変数ADDMSGIDがある時だけMessage-IDを付けるように変更し、自分のホストから出すメッセージだけに付けるようにしています。上記のパッチからの変更点はこんな感じ。

-           if(!flaghasm) {
+           if(!flaghasm && env_get("ADDMSGID")) {

[qmail-smtpdとbare LF]

 SMTPの仕様上、行末はCR+LFである。qmail-smtpdはCRを伴わないLFを見つけると、そこで接続を切ってしまう。私が使っているWinbiff 1.26の場合、普段は問題ないのだが、どういうわけか添付ファイルのbase64エンコード時にbare LFを生成する。qmail-smtpdにパッチを当てる方法もあるが、inetdからqmail-smtpdを立ち上げる時にucspi-tcp同梱のfixcrioを噛ますことで解決している。

[Receivedの時刻表示]

 UTCになっている。ローカルタイムゾーンにしたい人は、Yahoo!に「qmail smtpd received タイムゾーン」とかで聞いてみましょう。うちはそのまんま。海外のメールだと結局全部UTCに変換することになるし。DSTとかもう最悪だよね。

[qmail-popupとパスワードミス]

 Winbiff 1.26でパスワードを間違えると刺さるのでパッチ当てしてある。qmail-popupはcheckpasswordが認証エラーを報告するとそのまま死んでしまうが、Winbiff 1.26はQUITを送ってサーバーが応答するのを待ってしまう。qmail-popupにこんな感じのパッチを当てる。

@@
-  if (wait_exitcode(wstat)) { die_badauth(); }
+  if (wait_exitcode(wstat)) { die_badauth(); return; }
@@
   doanddie(username.s,username.len,arg);
+  seenuser = 0;
 }

[ヘッダの特殊文字エスケープ]

 qmail-injectはヘッダ中に特殊文字があるとエスケープしてくれる。これ自体はいいのだが、「"」に囲まれた ( ) までエスケープする。RFC上はクオートされていればエスケープする必要はないように見えるのだが、「From: "You (as known as Moz) SUZUKI" <you@example.jp>」が「From: "You \(as known as Moz\) SUZUKI" <you@example.jp>」のようになってしまう。みっともないので、クオートされた ( ) [ ] についてはエスケープしないよう、以下のようなパッチを当ててある。パッチは自己責任で適用のこと。

diff -u qmail-1.03/token822.c qmail-1.03-patch/token822.c
--- qmail-1.03/token822.c	Mon Jun 15 19:53:16 1998
+++ qmail-1.03-patch/token822.c	Wed Jan 22 15:38:48 2003
@@ -115,8 +115,10 @@
        for (j = 0;j < t->slen;++j)
 	 switch(ch = t->s[j])
 	  {
-	   case '"': case '[': case ']': case '(': case ')':
-	   case '\\': case '\r': case '\n': ++len;
+	   case '"': case '\\': case '\r': case '\n':
+		len += 2; break;
+	   case '[': case ']': case '(': case ')':
+		if (t->type != TOKEN822_QUOTE) ++len;
 	   default: ++len;
 	  }
        break;
@@ -163,9 +165,12 @@
        for (j = 0;j < t->slen;++j)
 	 switch(ch = t->s[j])
 	  {
-	   case '"': case '[': case ']': case '(': case ')':
-	   case '\\': case '\r': case '\n': *s++ = '\\';
-	   default: *s++ = ch;
+	   case '"': case '\\': case '\r': case '\n':
+		*s++ = '\\'; *s++ = ch; break;
+	   case '[': case ']': case '(': case ')':
+		if (t->type != TOKEN822_QUOTE) *s++ = '\\';
+	   default:
+		*s++ = ch;
 	  }
        if (t->type == TOKEN822_QUOTE) *s++ = '"';
        if (t->type == TOKEN822_LITERAL) *s++ = ']';

 ちなみに、( ) はRFC上はコメントということになっているのだが、ofmipdは「(」と「)」を削除してくれるので注意。ofmipdを使う場合は、クライアント側で ( ) をエスケープしなければならない。そういった理由でofmipdを使うのはあまりお勧めできない。

[ヘッダが多い]

 qmail-smtpdの他にqmail-queueもReceivedヘッダを付けるので、やたらReceivedが多くなる。こんな感じ。

Return-Path: <user@example.jp>
Delivered-To: user@example.jp
Received: (qmail 4150 invoked by alias); 31 Oct 2006 11:00:00 -0000
Message-ID: <20061031110000.4148.qmail@mx.example.jp>
Received: (qmail 4135 invoked from network); 31 Oct 2006 11:00:00 -0000
Received: from localhost (HELO example.jp) (127.0.0.1) by localhost with SMTP; 31 Oct 2006 11:00:00 -0000

メーリングリストだと、入ってきたところでinvoked from network、deliverに転送したところでinvoked by uid、deliverに配送されたところでDelivered-To、MLメンバーに送るところでさらにinvoked by uidが付くので、どうでもいいReceivedが3行と、Delivered-Toが1つ付くことになる。MLの行き帰り、途中の経路が全部qmailだと、SMTPを1段踏むごとにReceivedが2つ付いて、ヘッダがやたら長いメールが届く。特に、invoked from networkはその直前にqmail-smtpdがReceivedを付けてるのでどう見ても冗長。

 取るには以下のような感じでqmail-queue.cにパッチを当てる。

- if (substdio_bput(&ssout,received,receivedlen) == -1) die_write();
+ if ((uid != auto_uidd) && (uid != auto_uida))
+  {
+   if (substdio_bput(&ssout,received,receivedlen) == -1) die_write();
+  }

このパッチを当てると、invoked from networkとinvoked by aliasのヘッダが出なくなり、少しはすっきりする。上の例ならこうなる。

Return-Path: <user@example.jp>
Delivered-To: user@example.jp
Message-ID: <20061031110000.4148.qmail@mx.example.jp>
Received: from localhost (HELO example.jp) (127.0.0.1) by localhost with SMTP; 31 Oct 2006 11:00:00 -0000

[NULLバイトのあるメール]

 普通、SMTPではテキストを送るわけだが、RFC2821的にはなるべくデータは保存しなさい、ということになっている。特に、文字コード0から127はそのまま通せということになっているので、NULLバイトも受けなければならない。qmailもちゃんとNULLバイト入りのメッセージを配送してくれるが、Winbiff 1.26は受信時(POP3から読み出す時)に刺さる

 とはいっても、普通のテキストファイルや、添付ファイルをbase64やuuencodeで送ってくる分にはNULLバイトが入ることはない(ので、かなり長いこと問題なかった)。ところが、最近送られてくるSPAMの中にNULLバイトが入っているものがあり、こいつのせいで結構頻繁に刺さるようになってしまった。よく観察してみると、本文が丸ごとbase64でエンコードされていて、最後の辻褄あわせ(「=」を吐き出す部分)でNULL文字を吐いている模様。おそらくSPAM送信ソフトのbase64エンコード部分にバグがあるのだろう。

 どっちにしろ普通のメールではあり得ないのと、迷惑なので、NULL文字が入っているメールはdot-qmailで捨てるようにした。まず、標準入力からデータを読み込み、NULL文字を検査するプログラム「check-null」というのを作る。NULL文字が入っていたら1を、入っていなければ0(正常終了)を返すように作る。あとは ~/.qmail に

|/path/to/check-null || exit 99
./Maildir/
と書いておけばよい。NULLが混じっているメールは黙って捨ててくれる。バウンスも発生しない。
home / adsl / qmail SPAM対策
 qmailはrcpthostsにヒットしたドメインはとりあえず受け取ってしまうため、受け取った後でユーザーがないと分かると、バウンスを生成する。 バウンスの宛先は表書き差出人アドレスである。 バウンスが送れないとダブルバウンスになる。 もし、表書き差出人アドレスが実在する他人のアドレスだと、その他人に向かってバウンスが送られてしまう(表書き差出人の詐称は簡単)。 表書き差出人アドレスが実在しない場合は、管理者のアカウントにダブルバウンスが送られる。 数が多いとかなりうっとうしい。

 とにかくドメインが存在すると受け取ってしまうので、一番有効なのは「ドメインを細かく分ける」ことである。 ぁゃιぃサイトにメールアドレスを登録するときや、不特定多数が参加するメーリングリストにメールを投げるときは新しくドメインを作る。 そのためには管理しているドメインの直下(user@example.jpなど)は利用せず、最初からサブドメインを作っておく必要がある。 SPAMが来るようになったらドメインごと潰してしまえばよい。

 ドメインが存在すると受け取ってしまう、というのは、裏を返せば「ドメインが存在しなければメールを受け取らない」ということである。 ドメインがなければ宛先アドレスを指定した時点でqmail-smtpdが「そのドメインは受け取れないよーん」と返してメールを受け取らないので、qmail-sendに渡されることもなく、キューに入ることもない。 バウンスが発生するとすれば、こちら側(受け側)ではなく送り側のSMTPで発生する。 こちら側でバウンスが発生しないので、ダブルバウンスがこちら側で発生することも、受け取ることもない。

$ telnet localhost 25
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 mx.example.jp ESMTP
HELO mail.example.com
250 mx.example.jp
MAIL FROM: user@mail.example.com
250 ok
RCPT TO: user@hoge.example.jp
553 sorry, that domain isn't in my list of allowed rcpthosts (#5.7.1)

 ドメインを潰すのは簡単で、rcpthostsからドメインを抜くだけである。 このファイルを読んでいるのはqmail-smtpdで、qmail-smtpdはinetdのようなインターネットスーパーサーバーから起動するように設計されており、接続の度に起動されてそのときにrcpthostsを読むので、HUPをどこかに送る必要もない。

 あかもずドメインもあかもず直下とmailドメインを潰して、ドメインをキカイが読める形で書かないようにしたら、SPAMはまったく来なくなった。 ちなみにmailドメインはDNSでMXが引けないようにしてある。

home / adsl / qmail ●SPAM対策(2)

 メーリングリストに登録し、そこに投稿するとメールアドレスがオープンになるためSPAMがやってくるようになる。 この場合、専用のアドレスを作って、こんな.qmailを書けばよい。

|if [ "$SENDER" = "owner-mailing-list@list.example.com" ]; then forward user@example.jp; exit 99; fi

 owner-... は実際のメーリングリストの表書き差出人アドレスに書き換えること。 qmailでメールを受け取っているのならReturn-Path:で分かる。 forwardの後ろはメールを実際に受け取るアカウント。 exit 99は以降の配送指令は読まずに打ち切り、という意味。 複数のメーリングリストに登録している場合は、表書き差出人アドレスを書き換えて複数行に渡って書いておけばよい。

|if [ "$SENDER" = "owner-mailing-list@list.example.com" ]; then forward user@example.jp; exit 99; fi
|if [ "$SENDER" = "owner-joyful@list.example.com" ]; then forward user@example.jp; exit 99; fi
|if [ "$SENDER" = "owner-cool@list.example.co.jp" ]; then forward user@example.jp; exit 99; fi

 この.qmailを書くと、表書き差出人アドレスがこれ以外のメールはすべて黙って捨てられるので、メーリングリストに投稿する際にはその旨断った方がよい。

home / adsl / qmail ●SPF

 Sender Policy Frameworkの略。 これはどちらかというとSPAMを受けないというより、SPAMのとばっちりを受けないという意味が強い。 簡単に言うと「このドメインのメールは、このホストからしか発信しません」という宣言をするもの。 メールを受ける側で表書き差出人のドメインからこの情報を探し、送ってきたホストが含まれていなければ、差出人詐称と判断できる。 自分のサイトがSPFレコードを提供していれば、もし自分のサイトが差出人の詐称に使われても、受ける側がSPFを見てくれれば要らんバウンスを受けずに済む。

 SPFレコードの公開はDNSを使う。 メールの送り先をDNSのMXレコードに指定したが、同じ要領でTXTレコードを作り、

mail    IN  MX mx
        IN  TXT "v=spf1 +ip4:192.168.0.1 -all"
mx      IN  A 192.168.0.1

のようにすればよい。 TXTレコードは名前が省略されているが、DNSのゾーンファイルの規則から、すぐ上のMXレコードと同じ名前が使われる。 したがって、この例ではmailに対してMXレコードとしてホストmxを、TXTとして"v=spf1 ..."を、ホストmxのアドレスとして192.168.0.1を指定していることになる。

 そして、このTXTレコードは「SPFバージョン1で、このドメインからのメールはIPV4アドレス192.168.0.1から送信され、それ以外のアドレスからは出て行きません」という意味である。 他にもいろいろな記述方法があるが、小規模なネットワークではたいていこれだけで十分だろう。

 SPFは受ける側がSPFをチェックしてないと意味がない。 DNSのログ見ると意外と少ないんだよな。 自分のサイトで表書き差出人を詐称しているメールを拒否したい場合も、qmail単体ではダメで、パッチを当てるなりなんなりしなければならない。 また、拒否できるのは表書き差出人を詐称しているメールだけで、詐称していないメールはSPAMでも受け取ってしまう。 ただ、「詐称していない」ということは重要で、詐称していないことが保証されていれば、ホワイトリストやブラックリストを適用することが可能になる(ホワイトリストは詐称されたら使い物にならない)。

 メールを転送する場合、表書き差出人は書き換えないことが多いが、メール送信元とメール転送先がどちらもSPFに対応しているとメールを受け取ってもらえないので注意。 この場合はqmail-injectの-fオプションで表書き差出人を書き換える(以下で説明する)。

home / adsl / qmail ●SPFとメールの転送

 メールを転送する場合、普通に.qmailを書くと表書き差出人はそのままで、新しい宛先にメールが発信される。 メールを転送するのはもちろんメールを受信したqmailが動いているホストで、自分のところにあるサーバーである。 表書き差出人は最初にメールを出した人のアドレスだから、普通は自分のドメインのものではないだろう。

 もし、表書き差出人のドメインがSPFレコードをサポートしていて、転送先のメールサーバーがSPFレコードをチェックしていると、メールを転送しているのは自分のサーバーで、これは当然表書き差出人のドメインのSPFレコードには含まれていないので、受信してもらえない。 最悪黙って捨てられる可能性もある。

 正しく転送されるようにするには、表書き差出人を自分のドメインのアドレスに書き換える必要がある。 つまり、メーリングリストやVERPsと同様のことを行う必要がある。 この場合、エラーメールが表書き差出人のアドレスに返ってくるので、最初に受信したアドレスと、書き換えた表書き差出人のアドレスが同じになっていると、エラーが起きたときにまた転送しようとする。 この転送は再びエラーとなる可能性が高く、メールループを作ってしまう。

 メールループを防ぐには、メーリングリストの-ownerのように受信したアドレスとは別のアドレスを表書き差出人のアドレスとする(.qmail-*-ownerをtouchしておけば勝手にやってくれる・・・が、仮想ドメイン環境ではうまくいかない)か、エラーメールの表書き差出人アドレスは普通は空になっているので、これを利用して.qmailの中でエラーメールかどうか判定し、エラーメールの場合は転送しないようにすればよい。

 VERPsを使わずにオリジナルの表書き差出人をどこかに・・・例えばX-Old-Senderというヘッダフィールドを作り出して・・・記録しておきたい場合は、.qmailに

| { /bin/echo "X-Old-Sender:" "$SENDER"; /bin/cat; } | /var/qmail/bin/qmail-inject -f 新しい差出人 転送先

と書けばよい。

 もし何らかのエラーが起きて、元の差出人のサーバーから自分のサーバーへ届かない場合は、元の差出人のサーバーの方でエラーメールが生成され、元の差出人に返される。 自分のサーバーが受け取って、転送先に送るところでエラーになった場合は、表書き差出人が書き換えられているから、書き換えられた表書き差出人に返される。 転送先でエラーになった場合も表書き差出人が書き換えられているので、書き換えられた表書き差出人に返ってくる。

 転送がエラーになった場合、エラーメールを再び転送しようとしてもおそらくエラーになるだろう。 そしてこれはメールループを作る可能性が高く、しかも転送先のメールボックスだけを見ているとこのエラーメールは永遠に見られない。 実際には、自分のサイトでエラーになる場合は転送先に転送できないということである。 転送先でエラーになる場合は転送先が受け取ってくれないということである。 どちらも転送先のメールボックスを見る意味がない。 スプールがいっぱいとか、一時的にホストが落ちているような場合は別として、何か設定ミスがあるはず。

 元の差出人にもエラーメールが返らないので、元の差出人も気づかない。 エラーメールを確実に元の差出人に返したい場合はもう一工夫必要になる。 VERPsを利用すると楽だとは思うけど。 あるいは例えば、.qmailから、

#!/bin/sh
# 試してないからちゃんと動くかどうかは知らない。
DOT_QMAIL=`/usr/bin/mktemp .qmail-XXXXX`
/bin/echo "|/bin/echo \"fail to send mail to $LOCAL@$HOST\" | qmail-inject -f \"\" \"$SENDER\"" > $DOT_QMAIL
qmail-inject -f $USER-${DOT_QMAIL#.qmail-} $1

なんていうスクリプトを起動すればいいんと違うだろうか。 mktempした.qmailがどんどん溜まっていくから、cronかなんかで定期的に掃除する必要があるけど。


Copyright (C) 2006-2017 akamoz.jp

$Id: qmail.htm,v 1.25 2018/10/27 06:08:55 you Exp $