6. メールトランスファエージェント (MTA)

この章では、みっつの異なる MTA, つまり Sendmail, Postfix, qmail について述べます。これらは LDAP から情報を取り出すように設定できる MTA です。個人的な経験からすると Postfix の方が Sendmail よりも ずっと 簡単に設定できますが、これについては将来、 Sendmail における LDAP サポートがさらに成熟の域に達することで 変わってくるかもしれません。qmail は使ったことがありません。

6.1. Sendmail

6.1.1. Sendmail における LDAP サポート

Sendmail には、バージョン 8.8.x あたりから ldapx というマップタイプを使うかたちで LDAP がサポートされています。 バージョン 8.10 以降では LDAP データベースタイプが ldap としてサポートされています。 しかし LDAP マップのサポートは、RedHat のパッケージのデフォルトのままの設定では 無効になっているので、ご注意ください。 Debian のバージョン 2.2 以降には Sendmail の LDAP サポートがあるそうです。 自分でコンパイルする必要がある場合は、 Sendmail のソースの sendmail/README というファイルを読んでください。このファイルには、LDAP サポート付きで コンパイルする方法についての有益な情報が含まれています。

これら新旧の LDAP マップタイプには、ともに LDAP データベース内のエントリを 検索する能力があります。ただし、ひとつ注意があります。 検索の完了時に結果がひとつしか返ってこないのです。 結果が複数あるとしても、最初のものだけが使われます。 しかも、その結果に返り値が複数あっても最初の値だけが返されるのです。 次の LDIF ファイルの例に注目してみましょう。

dn: cn=mailuser1,ou=mail,dc=company,dc=com
objectclass: top
objectclass: foo
cn: mailuser1
mail: mailuser1@company.com
mail: info@company.com

検索を cn=mailuser1 のような 単純な検索フィルタで実行すると、求める属性が mail だとしても mailuser1@company.com しか返されません。 両方の結果を得るには、これらは単一値を持つ属性に、コンマで区切られたかたちで 格納されていなければならないのです。次のようなかたちです。

mail: mailuser1@company.com,info@company.com

この話題に関連した情報を含む電子メールメッセージを、 LIH ホーム で見ることができます。

6.1.2. システムの配置

LDAP マップが利用できるときには、ほとんど何でも LDAP データベース内から探し出せます。 そこで、以下のような構成の設定を簡素化したいと思います。

中規模もしくは大規模のネットワークがあることにしましょう。 たくさんのドメインのためにメールを受信します。ふたつのメールホストと、 ふたつのフォールバックホストという配置です。 これだと通常は、以下の三種類の情報を格納する場所が、四箇所にまで なってしまいます。

  • 両方のメールホストが local-host-names ファイル (伝統に従えば sendmail.cw) を必要とします。 これは、どのドメインへのメールを受信するかについて記録しておくものです。 フォールバックホストは同じ情報をaccess ファイルに保持していますが、こちらは受信メールをどのドメインへ中継すべきか 一覧するために使います。

  • メールホストには両方とも virtusers ファイルがあります。このファイルで複数のアドレス (あるいはドメイン全体) を単独の仮想ユーザやローカルユーザに割り当てます。

  • 両メールホストに aliases ファイルがあります。このファイルで仮想ユーザを メールアドレスやローカルユーザに割り当てます (複数も可)。

こうした情報がひとつのデータベースにまとめて格納されていれば、 各ホストがそのデータベースから設定を読み出すことによって、 ネットワークの拡張性と管理しやすさが向上します。 全データを nfs にマップさせて、それを単独のホストに持たせるような 形だって考えられるでしょう。そのような場合も 接続するホストに違いはまったく生じません。ユーザにとっても まったく同様に映ります。

6.1.3. Sendmail 設定ファイル

この情報がどうやって LDAP データベースから標準ファイルの代わりに 読み出されるかを理解するには、sendmail.cf ファイルについての背景的な知識が少し必要です。ここで扱う情報は、ふたつの 異なる方法で格納されています。local-host-names ファイルは、クラスに読み込まれます (正確にはクラス w です。このゆえに昔は cw という拡張子が付いていました)。 一方で virtusers ファイルは 単純なマップを通して使われます。aliases ファイルもマップですが、定義方法が異なりますし、内部で使われるので、 ルール内で参照されるわけではありません。

情報が LDAP データベースから取り出されるときには必ずマップ内で 完結します。local-host-names ファイルに格納されるべき情報にとっては、この点がやや問題となります。 このファイルの情報はクラスに使われるからです。著者はこれまでのところ、 マップ等からの情報でクラスを満たせたためしがありません。簡単にできそうな ものですが、どうも不可能なようです (間違いがあれば、どうかお知らせください)。 そのため新しくマップを定義しなくてはなりませんでした。Sendmail の設定で、 w クラス内の値を読むときに (ほぼ) 毎回、そのマップから値が検索される ようなルールを追加したのです。

マップについては、設定変更が簡単です。通常、マップは名前と、 データベースタイプと、各データベース特有のオプション (例えば通常使われる newdb データベースタイプでのファイルの位置など) とで定義されています。それでマップについては、定義を変更するだけで 十分です。ほら、もうできました。さて、LDAP マップにはさらに幾つかの オプションがあり、そのうちのいくつかは事前にグローバル定義 しておけます。それらのオプションは次のリストで説明されています (このリストは、大部分 Booker Bense の文書によります)。

sendmail.cf における LDAP 特有のマップオプション

-h

スペース区切りで LDAP サーバのホスト名を定義します。 この順番で問い合わせを行なっていき、結果が出たらそこで終了します。 グローバルに設定できます。

-b

LDAP 検索ベースを定義します。つまり、 その LDAP ディレクトリ内だけを検索するのです。グローバルに設定できます。

-k

LDAP 検索フィルタを定義します。これは「sprintf」形式の 文字列で、マップが入力値をどのように受け取って LDAP 検索を構築するのか 定義します。検索する値を %s で置換した、一般的な LDAP 検索フィルタの 形式をとります。LDAP 検索フィルタについてさらに学びたい方は、 RFC 2254 をご覧ください。ところで、この検索フィルタと 上記の検索ベースとでは、最大でもひとつのエントリしか返さないような 検索を定義すべきです。 LDAP マップは、受け取った最初のエントリだけを利用するのです。

-v

どの LDAP 属性の値がマップ検索で返されることになるのか を定義します。詳細は後述します。

LDAP オプションはすべてダブルクォートし、 Sendmail オプションの直後に置かなければなりません。ご注意ください。 例を挙げます。

Kldapexamplemap ldap -h"localhost ldap.myorg.com" -b"ou=mail,dc=myorg,dc=com" -k"(&(objectclass=mailstuff)(uid=%s))" -v"mailaddress"

6.1.4. スキーマ

著者は、この特別な設定のために、mail というサブツリーを LDAP ディレクトリ内で定義しています。 この下にメール関連のあらゆる情報を格納するためです。 ユーザ関連のメール情報は ou=Users のサブツリー に入れておくことも可能だったでしょうが、それはわざと避けました。 各ユーザと別に単一のサブツリーを使う方が、Sendmail のための情報が すべて一箇所に格納されるので、たくさんのユーザがいるときの検索が 速くなるのです。なぜなら、検索する必要があるのは ou=Users サブツリー全体ではなく、ou=mail だけだからです。

このサブツリー内に二種類のレコードを作ります。

  1. virtuser ファイルや aliases ファイルに由来する、 仮想ユーザひとりひとりの割り当てを保持するエントリです。 両ファイルからの割り当てを同じエントリに格納することにしました。 これによって、用いている設定や効果が明確になるからです。 これには、inetmailrecipient という objectclass と、 mailid, mailacceptinggeneralid, maildrop というみっつの属性を定義しました。

    inetmailrecipient

    この階級は、このエントリが、 一つまたは複数の実メールアドレスやメールドメインから、 一人または複数人の実ユーザへのマッピングであることを示します。

    mailid

    この仮想ユーザが受信するメールアドレスを記述します。 foo@myorg.com のように普通のアドレスの形式でも結構ですし、 @my2nd.org のようにドメインごとでも大丈夫です。 この属性は複数存在できますが、格納する値はそれぞれひとつずつでなければなりません。 これらの ID それぞれに対して、メールが mailacceptinggeneralid に送られます。

    ここには、今まで virtusers ファイルの左側にあったデータを入れることになるわけです。

    mailacceptinggeneralid

    仮想ユーザを定義します。実は、これが virtusers ファイルと aliases ファイルとの繋ぎ目です。 この属性が各エントリにひとつずつ存在していなくてはなりませんが、 それより多くてもいけません。しかも値をひとつしか入れられません。 値にはローカルユーザ名か仮想ユーザを入れることができます。 後者の場合は maildrop 属性が存在しなくてはなりません。 前者には必要ありません。

    ここには、これまで virtusers ファイルにあった情報と aliases ファイルの左側にあった情報を入れることになるわけです。

    maildrop

    受信したメールの配信先となるアドレスやユーザを定義します。 この属性はひとつしか存在できませんが、コンマ区切りのリストを入れられます。 mailacceptinggeneralid の値が仮想ユーザなら、 この属性は必須です。実在するユーザなら省略できます。

    つまり、ここには aliases ファイルの右側の部分を入れるということです。

    一般的に、mailidmailacceptinggeneralid とが一緒になって virtusers ファイルの機能を 提供すると言えます。そして mailacceptinggeneralidmaildrop とが aliases ファイルの機能を提供するのです。

  2. 通常 sendmail.cw ファイルや access ファイルにあるドメイン名を 保持するエントリです (複数つくれます)。このエントリのために inetmaildomain という objectclass と maildomain, sendmailislokalkey, sendmailaccesskey という属性を定義しました。

    inetmaildomain

    システムに属するメールドメインの一覧であり、かつ、 ローカルに配送すべきか他ホストに転送すべきかの一覧であるエントリ を表す階級です。

    maildomain

    メールドメインを定義します。 ひとつのエントリに複数存在できます。 値は「@」マークなしのドメインになります。

    この属性は、local-host-names ファイル内の ドメインのエントリごとに、ひとつずつ存在しているべきです。

    sendmailislocalkey

    ドメインがローカルかどうかを見分けるために Sendmail のルールの中で使う、シンプルな合言葉 (キー) を定義します。 Sendmail ルールの中で使う文字列と正確に一致していなくてはならない ということを除けば、本当に何でも構いません。著者はとりあえず <LDAPLOCAL> を使っています。必須属性で、各エントリに複数は存在できません。

    sendmailaccesskey

    Sendmail のルールで使われるキーのうち、 何を使うか定義します。このキーで、そのドメインで行うべき動作を決定します。 RELAY, OK, REJECT, DISCARD と エラー表示を使えます。(詳しくは Sendmail のソース内の cf/README ファイルを参照してください。)

    Note: ご注意ください。 今回は特別な設定として、access ファイルでは ドメインまるごとのエントリしか使わないことにしました。 これはつまり、maildomain 属性を access ファイルの情報にも local-host-names の情報にも使い回せるのは 今回のような場合だけだということです。 アクセスリストをもっと細かく制御したいならば、 一緒の maildomain 属性にしてしまわずに、 それぞれ別のエントリを使うべきです。

6.1.5. さらなる情報のために

有用と思われる情報源をいくつか紹介します。

  • Booker Bense が こういう文書 を書いてくれました。Sendmail 8.9.3 での LDAP の使用法に関するものです。 Sendmail と LDAP の使い方を学習する際の出発地点には向いていない、と 本人は言っていますが、著者にとっては、たいへん助けになりました。

  • LDAP と Sendmail に関する 新着記事sendmail.net 上で 公開されています。作者は Michael Donnelly で、そもそも ldapmap.org という、 これまた興味深い一般的な LDAP 関連情報満載のサイトから始まりました。

6.2. Postfix

6.2.1. サポート

Postfix では、本体に最初から LDAP サポートが入っています。 オプションの多くを設定する マップ の たくさんの種類のなかに、LDAP もあるのです。 各 LDAP マップにつき、オプションが二、三あります。 (Section 6.2.2 を参照してください。)

Postfix に LDAP データベース内のデータを検索させるための手順は、 極めてわかりやすくなっています。最も一般的な使い方 (と著者が思うもの) は、 仮想ユーザを LDAP データベースから参照させることです。 これを前述の nss_ldap とともに使えば、すべての電子メール利用者の情報を LDAP データベースに入れておけます。しかし、設定できる項目は他にもあります。 例えば Postfix がメールを転送できるドメインや、 逆に Postfix が転送要求を受け付けるドメイン、 またバックアップサーバとして動作すべきドメインです。

6.2.2. 設定

設定オプションに関する説明はすべて、バージョン 20001217 における Postfix docs の LDAP_README から引用しています。

server_host

LDAP サーバを動かしているホストの名前です。設定例を挙げます。

ldapsource_server_host = ldap.your.com

前述した全てのライブラリで、 複数のサーバをスペースで区切って指定することもできます。 最初のものが失敗すると、ライブラリはそれらを順に試行します。 「ldap.your.com:1444」というように書いて、 各サーバにそれぞれ異なるポートを渡すこともできます。

server_port (389)

LDAP サーバが要求を受け付けるポートです。例えばこうなります。

ldapsource_server_port = 778

search_base (既定値なし ― 設定が必要です)

検索する最上位ディレクトリです。例えばこうです。

ldapsource_search_base = dc=your, dc=com

timeout (10 秒)

検索結果をあきらめるまでの秒数です。例えばこう指定します。

ldapsource_timeout = 5

query_filter (mailacceptinggeneralid=%s)

ディレクトリ検索に使う、RFC2254 式フィルタです。 Postfix が解決しようとするアドレスのところに、代わりに %s を入れます。 例は次のとおり。

ldapsource_query_filter = (&(mail=%s)(paid_up=true))

domain (既定値なし ― 設定が必要です)

ドメイン名、ファイルへのパス、および 辞書の一覧です。 指定されていると、この中にあるドメインで名前が終わるホストしか検索しません。 これによって LDAP サーバへの問い合わせ負荷を劇的に軽減できます。

ldapsource_domain = postfix.org,
hash:/etc/postfix/searchdomains

result_attribute (maildrop)

検索によって返されるディレクトリエントリから メールアドレス解決のために読み込む属性です (複数も可)。

ldapsource_result_attribute = mailbox,maildrop

special_result_attribute (既定値なし)

エントリのうち、DN や URL を含んでいる属性です (複数も可)。 指定されていると、その値を使って再帰的に順次検索していきます。

ldapsource_special_result_attribute = member

scope (sub)

LDAP 検索スコープ ― sub, base, one のいずれか ― です。 それぞれ LDAP_SCOPE_SUBTREE, LDAP_SCOPE_BASE, LDAP_SCOPE_ONELEVEL に 変換されます。

bind (yes)

LDAP サーバにバインドするかどうかの指定です。 LDAP の最近の実装ではバインドを必要とせず、時間を節約できます。 設定例は次のとおり。

ldapsource_bind = no

バインドする必要があるなら、ローカルマシンのポートを LDAP サーバへの SSL トンネルにして、そこに接続させた方がよいかもしれません。 LDAP サーバが SSL をサポートしていなければ、サーバ側のシステムにも トンネルを設置します (ラッパでもプロクシでも、呼び方はお好みで)。 これで、パスワードがネットワークを丸見えのまま通過しなくなります。

bind_dn ("")

バインドする必要があるとき、この識別名でバインドします。 例えばこうです。

ldapsource_bind_dn = uid=postfix, dc=your, dc=com

bind_pw ("")

上の識別名のパスワードです。これを使う必要のあるときは きっと、main.cf を Postfix ユーザからしか見えないように したいと思うはずです。設定例はこうなります。

ldapsource_bind_pw = postfixpw

cache (no)

LDAP 接続にクライアントサイドキャッシュを使うかどうかです。 ldap_enable_cache(3) を参照してください。 これはデフォルトではオフになっています。

cache_expiry (30 秒)

クライアントサイドキャッシュが有効なとき、 ここで指定した秒数の後で、結果のキャッシュを破棄します。

cache_size (32768 バイト)

クライアント側のキャッシュが有効なら、そのバイト数です。

dereference (0)

どういうときに LDAP エイリアスを 辿るかの指定です。 (Postfix のエイリアスとは関係ありませんので注意してください。) OpenLDAP と UM LDAP の実装で有効な値は以下の通りです。

0一切しない
1検索時
2検索のためにベースオブジェクトの位置を探すとき
3常にする

6.2.3. 設定例

バーチャルドメイン (foo.virtualdomain.com とします) を使いたいとき、 そしてそのドメインのメールアドレスを LDAP に格納したいときには、 main.cf に以下のように書く必要があります。

virtual_maps = ldap:ldapvirtual
ldapvirtual_search_base = ou=mail,o=YourOrg,c=nl
ldapvirtual_query_filter = (mailacceptinggeneralid=%s)
ldapvirtual_domain = foo.virtualdomain.com
ldapvirtual_result_attribute = maildrop
ldapvirtual_bind = no
ldapvirtual_scope = one

この設定では、Postfix が「foo.virtualdomain.com」ドメインの ユーザへのメールを受信すると、 mailacceptinggeneralid という属性が 「user@foo.virtualdomain.com」に合致するエントリを探します。 そのようなエントリがあれば、maildrop 属性の値がすべて返ってきます。そこにメールが配送されるのです。 もし「user@foo.virtualdomain.com」がなければ、 ドメイン全体をひっくるめたユーザに合致するように、「@foo.virtualdomain.com」 という別の問い合わせをします。これもないときは、メッセージが回送 (バウンス) されます。

6.3. qmail

qmail 自体にはまったく LDAP サポートがありません。 しかしながら Andre Oppermann の LDAP サポートのパッチがあります。 これのパッケージは、文書も含めて 彼のサイト にあります。