sendmail と dovecot と SASL と LDAP

SASL CRAM-MD5 のジレンマ

SMTP-AUTHPOP3IMAP で使われる、認証フレームワークの SASL。SASL という名前よりも「CRAM-MD5」といった、具体的な認証方式の方が有名かもしれません。

CRAM-MD5 や、POP3 専用の APOP といった認証方式は、平文のパスワードが保存されているのが前提で、

  • 平文のパスワードを保存しておく。
  • 平文のパスワードがネットワークを流れる。

のどっちかを選ぶ必要があります。で、「平文のパスワードがネットワークを流れる問題なら、SSL/TLS 化しちゃえば OK じゃん」ということで、

  • SSL/TLS 化した上で、平文の認証(PLAIN or LOGIN)

というのが正解、とされます。Dovecot のデフォルトだと、LOGIN や PLAIN を使う場合には、SSL/TLS 化されている事が条件となっていて、「disable_plaintext_auth = no」という Bad Know How が広まっていたりします。

オレオレ証明書を用意すれば SSL/TLS 化は出来るのですが、オレオレ証明書自体が推奨出来るものじゃないし、かと言って、メールサーバへのアクセスの為に証明書のコストを払う事に、あまり理解が得られない状況だったり*1、ましてや、自宅サーバだったりしたら...。

ということで、今でも CRAM-MD5APOP の需要はあると思います。対応しているクライアントも多いし、とりあえず、パスワードが平文でネットワークを流れないし*2

でも、サーバを預かる側としては、平文のパスワードがサーバ上に保存されている、というのは、とにかく落ち着かない。

DIGEST-MD5 なら

CRAM-MD5 の他に、DIGEST-MD5 という認証方式もあって、CRAM-MD5 の様なチャレンジ&レスポンスの認証方式で、ネットワークにパスワードが流れないのですが、実は、この場合だと、平文のパスワードを保存する必要がありません。

RFC 2831の「2.1.2.1 Response-value」をよく見ると、

  response-value  =

     HEX( KD ( HEX(H(A1)),
             { nonce-value, ":" nc-value, ":",
               cnonce-value, ":", qop-value, ":", HEX(H(A2)) }))

If authzid is specified, then A1 is

  A1 = { H( { username-value, ":", realm-value, ":", passwd } ),
       ":", nonce-value, ":", cnonce-value, ":", authzid-value }

If authzid is not specified, then A1 is

  A1 = { H( { username-value, ":", realm-value, ":", passwd } ),
       ":", nonce-value, ":", cnonce-value }

(中略)

If the "qop" directive's value is "auth", then A2 is:

  A2       = { "AUTHENTICATE:", digest-uri-value }

If the "qop" value is "auth-int" or "auth-conf" then A2 is:

  A2       = { "AUTHENTICATE:", digest-uri-value,
               ":00000000000000000000000000000000" }

A1 を計算しているところの「H( { username-value, ":", realm-value, ":", passwd } )」の部分は、先に計算しておいて保存しても大丈夫なのが分かります*3。実際、Dovecot Ver 2 であれば、「doveadm pw」*4でこの値の計算結果を表示します。

$ doveadm pw -s DIGEST-MD5 -u JULY -p password
{DIGEST-MD5}3d206b3a34d0d5f8e54320d57bec654c

この「{DIGEST-MD5}〜」という文字列をそのまま、LDAP の userPassword アトリビュートに設定しておき、DovecotLDAP を参照する様にしておくと、DIGEST-MD5 の認証が出来ます。

ただ、この場合、あくまでもアトリビュートの値として「{DIGEST-MD5}〜」を保持している、というのが LDAP 側の立場で、例えば、ldappasswd でパスワード変更をしても、この値が更新される訳ではありません。

LDAP の userPassword 自体は、同時に複数の値を持つことができ、例えば、OpenLDAP の設定で、slapd.conf に

password-hash {SSHA} {CLEARTEXT}

と書くと、ldappasswd を使ったパスワード変更で、SSHA(ソルト付き SHA ハッシュ値)を保持している userPassword と、平文のパスワードを保持している userPassword の、2つの userPassword が更新されます。

なので、理想的には、

password-hash {DIGEST-MD5} {SSHA}

と書ければ、通常の LDAP の認証や PAM 経由の認証に加えて、SASL の DIGEST-MD5 認証で同じパスワードが使える事になります*5。しかし、残念ながら、上記のような設定は出来ません。

OpenLDAP に overlay というプラグインの仕組があり、この中に smbk5pwd という物があって、これを使うと、Samba とのパスワード同期が実現出来ます。同じように DIGEST-MD5 用の overlay があれば...*6

Dovecot の CRAM-MD5

DIGEST-MD5 に関しては、原理的に平文のパスワードを保存していなくても良い事が分かります。実は、Dovecot の場合、CRAM-MD5 でも同様に、「平文ではないパスワード」を使って CRAM-MD5 が使えるようになっています。

$ doveadm pw -s CRAM-MD5 -p password
{CRAM-MD5}9186d855e11eba527a7a52ca82b313e180d62234f0acc9051b527243d41e2740

この値は、MD5ハッシュ値を計算している最中の中間値らしく、下記のページで解説されています。

Dovecot が保存する CRAM-MD5 認証用パスワード - snbhsmt_log

この計算方法を OpenLDAP の plugin に移植すれば*7

  • LDAP でユーザ情報を一元管理。
  • SASL の CRAM-MD5 が使える。
  • 平文パスワードを保存する必要が無い。

と思ったのですが、落とし穴が...。

SASL の実装

SASL を実装しているライブラリとして、良く使われているのが Cyrus SASL Library です。SASL をサポートする多くのソフトでは、この Cyrus SASL が使われていて、OpenLDAP の SASL 認証も、このライブラリを使っています。

一方、Dovecot は自前で SASL の仕組を実装しています。Cyrus SASL は使いません。POP3IMAP での SASL 認証はこれで良いのですが、困るのは SMTP-AUTH です。

実は、postfix なら Dovecot が実装した SASL を利用できます。逆に、Dovecot の SASL を使える Dovecot 以外のソフトは postfix(と exim)ぐらいです。postfix と並んでよく使われる sendmail は Cyrus SASL を使います。

先の、DIGEST-MD5 や CRAM-MD5 で、「平文のパスワードを保存しないで実現する」というのは、あくまでも Dovecot SASL の実装であって、Cyrus SASL ではそんな事は出来ません。

つまり、OpenLDAP の userPassword を頑張っても、「sendmail + Dovecot」の組み合わせでは使えない事になります。

まとめ

LDAP でアカウントを集中管理して、メール関係だけでなく、OS や Samba も含めて認証情報を統一したい、という要望は、かなり昔からあるんだけど、現実には、ちょっとずつ都合が悪いところがあって、納得した環境にならない、という感じがします。

上記の話だって、「SSL/TLS 化して、CRAM-MD5 や DIGEST-MD5 はサポートしない」とすれば問題はないし、さらに言えば、「CRAM-MD5 や DIGEST-MD5 のような半端な物を使わず、Kerberos を使えば、GSSAPI 経由でシングルサインオン」という話もあります。

ただ、SSL/TLS 化であれば、証明書の管理とコストの問題が付いて回るし、GSSAPI にすると、サポートしていないクライアントソフトが多いし、妥協点として CRAM-MD5 を使おうとすると、上記のような問題にぶつかる、というのは歯がゆいです。

一応、OpenLDAP の部分を頑張れば、postfix(or exim) + Dovecot で良い感じになるのですが、postfix に関しては milter との関係がイマイチだったり、かと言って exim に手を出すのは躊躇するし、と、なかなか、満足のいく組み合わせは無いですね。

*1:既に、www.example.jp という Web サーバ用の証明書を持っていて、同じ FQDN でメールサーバも運用するなら、追加の費用も発生しません。最初からワイルドカード証明書、という手もありますが、メールクライアントがきちんと対応しているかは微妙です。

*2:でも、メールの中身が平文で流れるんですけどね。

*3:但し、同じパスワードでもユーザ名が変わったり、レルム(ドメイン名)が変わると、同じパスワードでも値が変わるので、これらに変更が無ければ、というのが条件。レルムはデフォルトだと空。なので、ユーザ名が JULY、パスワードが password なら、"JULY::password" という文字列に対する MD5 の値の値になります。

*4:Ver 1.x であれば、dovecotpw で同様の事が出来ます

*5:但し、Dovecot が一人のユーザに対して複数のパスワードを持っている場合、扱いが微妙です。Authentication/PasswordSchemes - Dovecot Wiki を読むと、非プレーンテキスト認証、すなわち、CRAM-MD5 や DIGEST-MD5 を、同時に使いたければ、平文のパスワードを保存しておく必要がある、という事が書いてあり、1ユーザに対して複数の違うスキーマのパスワードを使えるようにするのは、「In future」となっています。ところが実際には、Dovecot としては CRAM-MD5 しか使わないけど、pam 経由でも使うから SSHA の userPassword も、という事をやると、LDAP を検索した時に、「2つある userPassword をどの順序で返すか」で Dovecot の認証結果が変わってきます。先に SSHA のハッシュ値が返ってくると、その時点で Dovecot としては諦めてしまっているようです。password-hash で指定する順序を変えて、ldappasswd で更新すると、検索結果も password-hash の順序で返ってくるように見えます。だとすれば、password-hash の順序は、Dovecot に拾わせたい物を先に書く必要があります。ちなみに、pam 経由に認証は、この順序の影響は受けませんでした。ただ、password-hash の順序が検索結果と連動する、とは明確になっていないので、あくまでも「経験的に」です。

*6:overlay 以外にも、単に「plugin」と呼んでいる物があって、SHA256 や SHA512 を使ったハッシュ値を扱えるように出来るのですが、同じインタフェースを使うと、ユーザ名が取れないので、この DIGEST-MD5 用のハッシュ値が計算できません。overlay なら出来そうなんだけど、インタフェースが複雑で、しかも、ドキュメントがほとんど無いので、自分で作るのは無理そう...

*7:この移植は、比較的簡単に出来ました。実際のコードは別の機会にでも。