slapdの改造

データベースの全操作が可能なadminというdnが最初のインストール時に作成されます。 これ以外のユーザはパスワードの認証はできますが、 パスワードの生文を読み取ることはできません。 そのため、生パスワードが必要なsambaやapopプログラムが ldapデータベースにアクセスする場合、 どこかにadminのパスワードを書いておかねばなりません。
以前はこの目的のために、少し権利の低いauthというdnを作成し、 このdnにパスワードの読み取りの権利を与えていましたが、 いずれにせよauthのパスワードを書いておかねばならないため、 セキュリティ上はもちろん、 あちこちにauthのパスワードが分散してしまい面倒です。

そこで、slapd自身を改造して、APOP認証と、 sambaやpptpで用いられるNTLM認証を受け付けるようにします。

% apt-get source slapd
% cd openldap2-2.0.23/libraries/liblutil
% vi passwd.c
...
        { {0, NULL}, NULL, NULL }
};

#include "md4.h"
static int chk_ntlm(
        const struct pw_scheme *scheme,
        const struct berval *passwd,
        const struct berval *cred );
static int chk_apop(
        const struct pw_scheme *scheme,
        const struct berval *passwd,
        const struct berval *cred );
static const struct pw_scheme cred_schemes[] =
{
        { {sizeof("{NTLM}")-1, "{NTLM}"},       chk_ntlm, NULL },
        { {sizeof("{APOP}")-1, "{APOP}"},       chk_apop, NULL },
        { {0, NULL}, NULL, NULL }
};

static const struct pw_scheme *get_scheme(
        const char* scheme )
{
...
                                /* only free the berval structure as the bv_val points
                                 * into passwd->bv_val
                                 */
                                ber_memfree( p );

                                return rc;
                        }
                }
        }

        for( i=0; cred_schemes[i].name.bv_val != NULL; i++ ) {
                if( cred_schemes[i].chk_fn ) {
                        struct berval *p = passwd_scheme( &cred_schemes[i],
                                cred, schemes );

                        if( p != NULL ) {
                                int rc = (cred_schemes[i].chk_fn)( &cred_schemes[i], passwd, p );

                                ber_memfree( p );

                                return rc;
                        }
                }
        }

#ifdef SLAPD_CLEARTEXT
        if( is_allowed_scheme("{CLEARTEXT}", schemes ) ) {
...
/* PASSWORD CHECK ROUTINES */

static void desenc(
        const unsigned char src[8],
        unsigned char dst[8],
        const unsigned char key[7])
{
        int i;
        char des[64];
        for(i= 7; i<64; i+=8) des[i]= 0;
        for(i= 0; i<56; i++) des[i+i/7]=key[i/8]>>(7-i%8)&1;
        setkey(des);
        for(i= 0; i<64; i++) des[i]= src[i/8]>>(7-i%8)&1;
        encrypt(des, 0);
        for(i= 0; i<8; i++) {
                int j;
                unsigned char c= 0;
                for(j= 0; j<8; j++) c= c<<1|des[i*8+j];
                dst[i]= c;
        }
}

static int chk_ntlm(
        const struct pw_scheme *sc,
        const struct berval * passwd,
        const struct berval * cred )
{
        int i;
        unsigned char *buf = NULL;
        MD4_CTX MD4context;
        unsigned char pdigest[21];
        unsigned char *bcred = NULL;
        int rc;
        char des[64];

        /* base64 un-encode cred */
        bcred = (unsigned char *) ber_memalloc( (size_t) (
                LUTIL_BASE64_DECODE_LEN(cred->bv_len) + 1) );

        if( bcred == NULL )return -1;

        rc = lutil_b64_pton(cred->bv_val, bcred, cred->bv_len);
        if ( rc != 24+8 ) { ber_memfree(bcred); return -1; }

        buf= ber_memalloc( passwd->bv_len<7 ? 14 : passwd->bv_len*2 );
        if(buf==NULL) { ber_memfree(bcred); return -1; }

        /* make md4 digest from passwd*/
        for(i= 0; i<passwd->bv_len; i++ ) {
                buf[i*2]= passwd->bv_val[i];
                buf[i*2+1]= 0;
        }
        MD4Init(&MD4context);
        MD4Update(&Mamp;D4context, buf, passwd->bv_len*2 * 8 );
        MD4Final(pdigest, &MD4context);
        for(i= 16; i<21; i++) pdigest[i]= 0;
        for(i= 0; i<3; i++) {
                desenc(bcred+24, buf, pdigest+i*7);
                if(memcmp(bcred+i*8, buf, 8)) break;
        }
        if(i<3) { /* check for lanman pass */
                for(i= 0; i<passwd->bv_len; i++) {
                        rc= passwd->bv_val[i];
                        if(rc>='a'&&rc<='z') rc-= 0x20;
                        buf[i]= rc;
                }
                for(; i<14; i++) buf[i]= 0;
                desenc("KGS!@#$%", pdigest, buf);
                desenc("KGS!@#$%", pdigest+8, buf+7);
                for(i= 0; i<3; i++) {
                        desenc(bcred+24, buf, pdigest+i*7);
                        if(memcmp(bcred+i*8, buf, 8)) break;
                }
        }
        ber_memfree(bcred); ber_memfree(buf);
        return (i<3);
};


static int chk_apop(
        const struct pw_scheme *sc,
        const struct berval * passwd,
        const struct berval * cred )
{
        lutil_MD5_CTX MD5context;
        unsigned char MD5digest[LUTIL_MD5_BYTES];
        int rc;
        unsigned char *orig_cred = NULL;

        /* base64 un-encode cred */
        orig_cred = (unsigned char *) ber_memalloc( (size_t) (
                LUTIL_BASE64_DECODE_LEN(cred->bv_len) + 1) );

        if( orig_cred == NULL ) return -1;

        rc = lutil_b64_pton(cred->bv_val, orig_cred, cred->bv_len);
        if ( rc < sizeof(MD5digest) ) {
                ber_memfree(orig_cred);
                return -1;
        }

        /* make response from salt */
        lutil_MD5Init(&MD5context);
        lutil_MD5Update(&MD5context,
                orig_cred+sizeof(MD5digest),
                rc-sizeof(MD5digest));
        lutil_MD5Update(&MD5context,
                (const unsigned char *) passwd->bv_val,
                passwd->bv_len );
        lutil_MD5Final(MD5digest, &MD5context);

        /* compare */
        rc = memcmp((char *)orig_cred, (char *)MD5digest, sizeof(MD5digest));
        ber_memfree(orig_cred);
        return rc ? 1 : 0;
}
...

コンパイルにあたってはmd4.cも必要ですので、 pppdのソースツリーなどから持ってきます。 (pppdの改造の項参照) なお、md4.cのソースによっては上記のMD4Updateの第3引数がバイト単位のものと ビット単位のものがあるので必要に応じて編集してください。

% cp /PPP-SOURCE/pppd/md4.c .
% cp /PPP-SOURCE/pppd/md4.h .
% vi Makefile.in
...
SRCS    = base64.c debug.c entropy.c sasl.c signal.c \
        md5.c md4.c passwd.c sha1.c getpass.c lockf.c utils.c sockpair.c \
        @LIBSRCS@ $(@PLAT@_SRCS)

OBJS    = base64.o debug.o entropy.o sasl.o signal.o \
        md5.o md4.o passwd.o sha1.o getpass.o lockf.o utils.o sockpair.o \
        @LIBOBJS@ $(@PLAT@_OBJS)
...
コンパイルしてインストールします。
# cd openldap2-2.0.23
# dpkg-buildpackage
(この時に出るコンパイルに必要なパッケージ群はapt-get installで取ってきます。)
# cd ..
# dpkg -i libldap2_2.0.23-xxx.deb
# dpkg -i slapd_2.0.23-xxx.deb
ライブラリの変更ですが入れ替えるのは/usr/sbin/slapdだけなので、
% debian/rules build
# /etc/init.d/slapd stop
# cd debian/build/servers/slapd/slapd
# strip slapd; cp slapd /usr/sbin/slapd
# /etc/init.d/slapd start
でもかまいません。いずれにせよslapdをdselect等でhold-stateにしておくか、 apt-get upgradeがslapdにかかったら 最新のソースで再コンパイルして入れ替えるのを忘れずに。

改造slapdの使い方

ldap_simple_bind_sとかする場合のパスワードに、 次のようなパスワードを設定して認証することが可能となります。 つまり、libpam-ldap経由で、通常のpamログインにこれらのパスワードを渡せます。
{APOP}response[16]challenge[]
APOP認証です。 16バイトのレスポンスの後に任意バイトのチャレンジをつけたバイト列全体を base64でエンコードし、その文字列の前に{APOP}を付けます。
チャレンジには < 、 > 自身も含めてください。 事実上CRAM-MD5認証と同じです。
{NTLM}reponse[24]challenge[8]
NTLM認証です。 24バイトのレスポンスの後に8バイトのチャレンジをつけたバイト列全体を base64でエンコードし、その文字列の前に{NTLM}を付けます。
NT認証(パスワードのmd4ハッシュ)、LANMAN認証(マジックをパスワードで暗号化)の どちらかが認証されると認証OKになります。
NT認証しか許したくない場合には、 これでログインした後にuserPasswordフィールドを読み出して、 自分で再チェックしてください。