Postfix Advent Calendar 2014 の 19日目の記事です。 今回も安定の 5日遅れです。毎度毎度すみません。
今回は、/etc/passwd
に加え、LDAP サーバーやデータベースシステムを UNIX
ユーザー情報源として利用している場合に発生する問題と、その回避方法について紹介します。
Postfix ローカルユーザー = UNIX ユーザー
Postfix の標準の設定 (かつデフォルト) では、mydestination
パラメーターで示されるドメインのユーザー(ローカルユーザー)は、
Postfix 稼動ホスト OS のユーザーになります。
Postfix は UNIX 系の OS 向けに実装されているため、
Postfix ローカルユーザー = UNIX ユーザーと言えます。
一方、UNIX のユーザーは、様々なサービスで維持・管理することができます。
もっとも代表的なものは /etc/passwd
ファイルに保持される、旧来からの
passwd
(5) 情報です。
これ以外にも、ネームサービススイッチと呼ばれる仕組み (nsswitch.conf
(5))
によって、LDAP サーバーやデータベースシステムなどを利用することができます。
ネームサービス障害時の Postfix の挙動
/etc/passwd
の障害
/etc/passwd
を読み込みを阻害して障害をシミュレートしてみましょう。
# chmod go-r /etc/passwd
適当なクライアントから /etc/passwd
に存在するユーザー passwduser
宛にメールを送ってみます。以下の例では telnet
コマンドで試行しています。
$ telnet mail.example.jp 25
Trying 10.0.0.1...
Connected to mail.example.jp.
Escape character is '^]'.
220 mail.example.jp ESMTP Postfix
mail from:<>
250 2.1.0 Ok
rcpt to:<passwduser@example.jp>
451 4.3.0 <passwduser@example.jp>: Temporary lookup failure
quit
221 2.0.0 Bye
Connection closed by foreign host.
宛先アドレス passwduser@example.jp
が拒否されましたが、
応答コードが 4XX
なので一時エラー扱いです。
これなら、障害復旧後に再送してもらえることを期待できます。
このときのログは次のようになります。
Dec 24 00:26:45 mail postfix/proxymap[26447]: warning: cannot access UNIX password database: Connection refused
Dec 24 00:26:45 mail postfix/smtpd[26446]: NOQUEUE: reject: RCPT from mua.example.jp[10.0.0.4]: 451 4.3.0 <passwduser@example.jp>: Temporary lookup failure; from=<> to=<passwduser@example.jp> proto=SMTP
ネームサービスサーバーの障害
ネームサービスに LDAP サーバーを利用している状態であると仮定します。
LDAP サービスを停止した状態で、LDAP サーバー上に存在するユーザー ldapuser
宛にメールを送ってみます。
$ telnet mail.example.jp 25
Trying 10.0.0.1...
Connected to mail.example.jp.
Escape character is '^]'.
220 mail.example.jp ESMTP Postfix
mail from:<>
250 2.1.0 Ok
rcpt to:<ldapuser@example.jp>
550 5.1.1 <ldapuser@example.jp>: Recipient address rejected: User unknown in local recipient table
quit
221 2.0.0 Bye
Connection closed by foreign host.
これも宛先アドレス ldapuser@example.jp
が拒否されましたが、
今度の場合は応答コードが 5XX
なので恒久エラー扱いです。
送信元が MTA であれば、再試行はせずに、即座にバウンスしてしまいます!
このようにネームサービス (LDAP サーバー) の一時的な障害にもかかわらず、
恒久エラーとなってしまう問題が起きてしまいます。
このときのログは次のようになります。
Dec 24 00:48:19 mail postfix/smtpd[27862]: NOQUEUE: reject: RCPT from mua.example.jp[10.0.0.4]: 550 5.1.1 <ldapuser@example.jple.jp>: Recipient address rejected: User unknown in local recipient table; from=<> to=<ldapuser@example.jple.jp> proto=SMTP
ネームサービスに
SSS (nss_sss
) や PADL nss-pam-ldapd (nss_ldap
)
を利用している場合は、その直接のバックエンドである sssd
や nslcd
の障害時にも同様の結果になります。
Postfix のコードを読んでみる
Postfix がローカルユーザーの存在を確認する際に参照するテーブルは何でしょうか。
それは local_recipient_maps
パラメーターの値が示しています。
デフォルトなら次のような設定になっています。
# postconf local_recipient_maps
local_recipient_maps = proxy:unix:passwd.byname $alias_maps
proxy
(proxymap
(8)) を介して unix
テーブルの passwd.byname
でローカルユーザーの確認をしていることがわかります。
そこで Postfix のソースコードから src/util/dict_unix.c
を参照すると、
以下の部分が該当することがわかります。
static const char *dict_unix_getpwnam(DICT *dict, const char *key)
{
…省略…
if ((pwd = getpwnam(key)) == 0) {
if (sanity_checked == 0) {
sanity_checked = 1;
errno = 0;
if (getpwuid(0) == 0) {
msg_warn("cannot access UNIX password database: %m");
dict->error = DICT_ERR_RETRY;
}
}
return (0);
} else {
…省略…
}
…省略…
UNIX ユーザー情報をユーザー名で索く関数 getpwnam
(3) が失敗 (0 を返す)
したとき、さらに UID 番号 0 で getpwuid
(3) 関数も試行し、
それも失敗した場合だけ dict->error = DICT_ERR_RETRY;
するようになっています。
これにより、/etc/passwd
のアクセス障害時は両方とも失敗して /etc/passwd
の障害と認識されるため一時エラーとなり、
そのほかのネームサービスの障害のときは後者は失敗せず、
ユーザーが存在しないと認識されてしまい恒久エラーとなります。
すべてのネームサービス障害を一時エラーにする方法
Postfix にネームサービスサーバーを直接参照させる
今回の例のようにネームサービスに LDAP サーバーを利用しているのであれば、
ldap_table
(5) でローカルユーザーの確認をさせればよいです。
ldap_table
(5) なら LDAP サーバーが利用できない場合は一時エラーになります。
local_recipient_maps =
ldap:$config_directory/local_recipient.ldap.cf
$alias_maps
参考までに local_recipient.ldap.cf
の例も載せておきましょう。
server_host = ldaps://ldap.example.jp/
version = 3
search_base = ou=users,dc=example,dc=jp
scope = sub
query_filter = (&(objectClass=posixAccount)(uid=%s))
result_attribute = uid
Postfix を改修する
確認はしていませんが、
Postfix のソースコードの src/util/dict_unix.c
を次のように変更することにより、
すべてのネームサービス障害を一時エラーにできるのではないかと思われます。
static const char *dict_unix_getpwnam(DICT *dict, const char *key)
{
…省略…
if ((pwd = getpwnam(key)) == 0) {
if (sanity_checked == 0) {
sanity_checked = 1;
errno = 0;
getpwnam(":");
if (errno != 0 && errno != ENOENT) {
msg_warn("cannot access UNIX password database: %m");
dict->error = DICT_ERR_RETRY;
}
}
return (0);
} else {
…省略…
}
…省略…
ただし、ネームサービス障害時の getpwnam
(3) による errno
値に標準はなく、
C ライブラリーやネームサービスモジュールの実装に依存している模様です。
この変更では対応できないケースがあるかもしれません。
ところで、12月25日はクリスマスな上に、
OSS 界隈で地味に活躍されているふみやすさんの誕生日ですね!
っ http://www.amazon.co.jp/registry/wishlist/27M7TV8CEEF6G?sort=priority
逆に、あなたの書いた OSS や Blog や Advent Calendar が気に入ったら何か送りたく なってしまうかもしれないので、プロフィールや Web サイトに あなたの Amazon 欲しいものリストの URL を貼っておいてくださいね!
私が勤める OSSTech っていう某弊社で社員募集しているようです。 人材紹介会社を介さなければ、入社後に 20万円のボーナス! 「ふみやすっていう人に紹介された」と言ってもらえると私にもボーナス!! → https://www.osstech.co.jp/recruit/
よろしければ、これまで参加した/参加予定のほかの Advent Calendar もどうぞ。
- Ansible Advent Calendar 2023
- シェル芸 Advent Calendar 2023
- 闇の魔術に対する防衛術 Advent Calendar 2023
- Ansible Advent Calendar 2023
- Ansible Advent Calendar 2020
- DNS温泉 Advent Calendar 2019
- OSSTech Advent Calendar 2019
- Ansible Advent Calendar 2018
- OSSTech Advent Calendar 2018
- Debian/Ubuntu Advent Calendar 2017
- Linux Advent Calendar 2017
- Shell Script Advent Calendar 2017
- Shell Script Advent Calendar 2016
- OpenLDAP と仲間たち Advent Calendar 2015
- Postfix Advent Calendar 2014
- 拡張 POSIX シェルスクリプト Advent Calendar 2013