org.apache.directory.server.operations.bind.SaslBindIT.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.directory.server.operations.bind.SaslBindIT.java

Source

/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 * 
 *    http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 * 
 */
package org.apache.directory.server.operations.bind;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.lang.reflect.Field;
import java.nio.ByteBuffer;

import javax.naming.NamingEnumeration;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.net.SocketClient;
import org.apache.directory.api.ldap.codec.api.LdapDecoder;
import org.apache.directory.api.ldap.codec.api.LdapEncoder;
import org.apache.directory.api.ldap.codec.api.LdapMessageContainer;
import org.apache.directory.api.ldap.codec.api.AbstractMessageDecorator;
import org.apache.directory.api.ldap.model.constants.SaslQoP;
import org.apache.directory.api.ldap.model.constants.SchemaConstants;
import org.apache.directory.api.ldap.model.constants.SupportedSaslMechanisms;
import org.apache.directory.api.ldap.model.entry.DefaultEntry;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.api.ldap.model.message.BindRequest;
import org.apache.directory.api.ldap.model.message.BindRequestImpl;
import org.apache.directory.api.ldap.model.message.BindResponse;
import org.apache.directory.api.ldap.model.message.Message;
import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.api.util.Network;
import org.apache.directory.ldap.client.api.LdapConnection;
import org.apache.directory.ldap.client.api.LdapNetworkConnection;
import org.apache.directory.ldap.client.api.SaslCramMd5Request;
import org.apache.directory.ldap.client.api.SaslDigestMd5Request;
import org.apache.directory.ldap.client.api.SaslGssApiRequest;
import org.apache.directory.server.annotations.CreateKdcServer;
import org.apache.directory.server.annotations.CreateLdapServer;
import org.apache.directory.server.annotations.CreateTransport;
import org.apache.directory.server.annotations.SaslMechanism;
import org.apache.directory.server.core.annotations.ApplyLdifs;
import org.apache.directory.server.core.annotations.ContextEntry;
import org.apache.directory.server.core.annotations.CreateDS;
import org.apache.directory.server.core.annotations.CreateIndex;
import org.apache.directory.server.core.annotations.CreatePartition;
import org.apache.directory.server.core.integ.AbstractLdapTestUnit;
import org.apache.directory.server.core.integ.FrameworkRunner;
import org.apache.directory.server.core.kerberos.KeyDerivationInterceptor;
import org.apache.directory.server.kerberos.kdc.KerberosTestUtils;
import org.apache.directory.server.ldap.handlers.extended.StoredProcedureExtendedOperationHandler;
import org.apache.directory.server.ldap.handlers.sasl.cramMD5.CramMd5MechanismHandler;
import org.apache.directory.server.ldap.handlers.sasl.digestMD5.DigestMd5MechanismHandler;
import org.apache.directory.server.ldap.handlers.sasl.gssapi.GssapiMechanismHandler;
import org.apache.directory.server.ldap.handlers.sasl.ntlm.NtlmMechanismHandler;
import org.apache.directory.server.ldap.handlers.sasl.plain.PlainMechanismHandler;
import org.apache.directory.shared.kerberos.KerberosAttribute;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * An {@link AbstractServerTest} testing SASL authentication.
 * 
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 */
@RunWith(FrameworkRunner.class)
@ApplyLdifs({
        // Entry # 1
        "dn: ou=users,dc=example,dc=com", "objectClass: organizationalUnit", "objectClass: top", "ou: users\n",

        // Entry # 2
        "dn: uid=hnelson,ou=users,dc=example,dc=com", "objectClass: inetOrgPerson",
        "objectClass: organizationalPerson", "objectClass: person", "objectClass: krb5principal",
        "objectClass: krb5kdcentry", "objectClass: top", "uid: hnelson", "userPassword: secret",
        "krb5PrincipalName: hnelson@EXAMPLE.COM", "krb5KeyVersionNumber: 0", "cn: Horatio Nelson", "sn: Nelson",

        // krbtgt
        "dn: uid=krbtgt,ou=users,dc=example,dc=com", "objectClass: inetOrgPerson",
        "objectClass: organizationalPerson", "objectClass: person", "objectClass: krb5principal",
        "objectClass: krb5kdcentry", "objectClass: top", "uid: krbtgt", "userPassword: secret",
        "krb5PrincipalName: krbtgt/EXAMPLE.COM@EXAMPLE.COM", "krb5KeyVersionNumber: 0", "cn: KDC Service",
        "sn: Service",

        // ldap per host
        "dn: uid=ldap,ou=users,dc=example,dc=com", "objectClass: inetOrgPerson",
        "objectClass: organizationalPerson", "objectClass: person", "objectClass: krb5principal",
        "objectClass: krb5kdcentry", "objectClass: top", "uid: ldap", "userPassword: randall",
        "krb5PrincipalName: ldap/localhost@EXAMPLE.COM", "krb5KeyVersionNumber: 0", "cn: LDAP Service",
        "sn: Service" })
@CreateDS(allowAnonAccess = false, name = "SaslBindIT-class", partitions = {
        @CreatePartition(name = "example", suffix = "dc=example,dc=com", contextEntry = @ContextEntry(entryLdif = "dn: dc=example,dc=com\n"
                + "dc: example\n" + "objectClass: top\n" + "objectClass: domain\n\n"), indexes = {
                        @CreateIndex(attribute = "objectClass"), @CreateIndex(attribute = "dc"),
                        @CreateIndex(attribute = "ou") }) }, additionalInterceptors = {
                                KeyDerivationInterceptor.class })
@CreateLdapServer(transports = {
        @CreateTransport(protocol = "LDAP") }, saslHost = "localhost", saslPrincipal = "ldap/localhost@EXAMPLE.COM", saslMechanisms = {
                @SaslMechanism(name = SupportedSaslMechanisms.PLAIN, implClass = PlainMechanismHandler.class),
                @SaslMechanism(name = SupportedSaslMechanisms.CRAM_MD5, implClass = CramMd5MechanismHandler.class),
                @SaslMechanism(name = SupportedSaslMechanisms.DIGEST_MD5, implClass = DigestMd5MechanismHandler.class),
                @SaslMechanism(name = SupportedSaslMechanisms.GSSAPI, implClass = GssapiMechanismHandler.class),
                @SaslMechanism(name = SupportedSaslMechanisms.NTLM, implClass = NtlmMechanismHandler.class),
                @SaslMechanism(name = SupportedSaslMechanisms.GSS_SPNEGO, implClass = NtlmMechanismHandler.class) }, extendedOpHandlers = {
                        StoredProcedureExtendedOperationHandler.class }, ntlmProvider = BogusNtlmProvider.class)
@CreateKdcServer(transports = { @CreateTransport(protocol = "UDP", port = 6088),
        @CreateTransport(protocol = "TCP", port = 6088) })
public class SaslBindIT extends AbstractLdapTestUnit {
    @Before
    public void init() throws Exception {
        KerberosTestUtils.fixServicePrincipalName("ldap/" + Network.LOOPBACK_HOSTNAME + "@EXAMPLE.COM",
                new Dn("uid=ldap,ou=users,dc=example,dc=com"), getLdapServer());
    }

    /**
     * Tests to make sure the server properly returns the supportedSASLMechanisms.
     */
    @Test
    public void testSupportedSASLMechanisms() throws Exception {
        // We have to tell the server that it should accept anonymous
        // auth, because we are reading the rootDSE
        getLdapServer().getDirectoryService().setAllowAnonymousAccess(true);

        // Point on rootDSE
        DirContext context = new InitialDirContext();

        Attributes attrs = context.getAttributes(Network.ldapLoopbackUrl(getLdapServer().getPort()),
                new String[] { "supportedSASLMechanisms" });

        //             Thread.sleep( 10 * 60 * 1000 );
        NamingEnumeration<? extends Attribute> answer = attrs.getAll();
        Attribute result = answer.next();
        assertEquals(6, result.size());
        assertTrue(result.contains(SupportedSaslMechanisms.GSSAPI));
        assertTrue(result.contains(SupportedSaslMechanisms.DIGEST_MD5));
        assertTrue(result.contains(SupportedSaslMechanisms.CRAM_MD5));
        assertTrue(result.contains(SupportedSaslMechanisms.NTLM));
        assertTrue(result.contains(SupportedSaslMechanisms.PLAIN));
        assertTrue(result.contains(SupportedSaslMechanisms.GSS_SPNEGO));
    }

    /**
     * Tests to make sure PLAIN-binds works
     */
    @Test
    public void testSaslBindPLAIN() throws Exception {
        LdapNetworkConnection connection = new LdapNetworkConnection(Network.LOOPBACK_HOSTNAME,
                getLdapServer().getPort());
        connection.setTimeOut(0L);

        BindResponse resp = connection.bindSaslPlain("hnelson", "secret");
        assertEquals(ResultCodeEnum.SUCCESS, resp.getLdapResult().getResultCode());

        Dn userDn = new Dn("uid=hnelson,ou=users,dc=example,dc=com");
        Entry entry = connection.lookup(userDn);
        assertEquals("hnelson", entry.get("uid").getString());

        connection.close();

        // Try to bind with a wrong user
        resp = connection.bindSaslPlain("hnelsom", "secret");
        assertEquals(ResultCodeEnum.INVALID_CREDENTIALS, resp.getLdapResult().getResultCode());

        // Try to bind with a wrong password
        resp = connection.bindSaslPlain("hnelson", "secres");
        assertEquals(ResultCodeEnum.INVALID_CREDENTIALS, resp.getLdapResult().getResultCode());
    }

    /**
     * Test a SASL bind with an empty mechanism
     */
    @Test
    @Ignore("Activate and fix when DIRAPI-36 (Provide a SaslBindRequest extending BindRequest that can be used in LdapConnection.bind(...) method) is solved")
    public void testSaslBindNoMech() throws Exception {
        Dn userDn = new Dn("uid=hnelson,ou=users,dc=example,dc=com");
        LdapConnection connection = new LdapNetworkConnection(Network.LOOPBACK_HOSTNAME, getLdapServer().getPort());
        BindRequest bindReq = new BindRequestImpl();
        bindReq.setCredentials("secret");
        bindReq.setDn(userDn);
        bindReq.setSaslMechanism(""); // invalid mechanism
        bindReq.setSimple(false);

        try {
            connection.bind(bindReq);
            fail();
        } catch (LdapException le) {
            //expected
        }

        connection.close();
    }

    /**
     * Tests to make sure CRAM-MD5 binds below the RootDSE work.
     */
    @Test
    public void testSaslCramMd5Bind() throws Exception {
        Dn userDn = new Dn("uid=hnelson,ou=users,dc=example,dc=com");
        LdapNetworkConnection connection = new LdapNetworkConnection(Network.LOOPBACK_HOSTNAME,
                getLdapServer().getPort());
        connection.setTimeOut(0L);

        SaslCramMd5Request request = new SaslCramMd5Request();
        request.setUsername(userDn.getRdn().getValue());
        request.setCredentials("secret");

        BindResponse resp = connection.bind(request);
        assertEquals(ResultCodeEnum.SUCCESS, resp.getLdapResult().getResultCode());

        Entry entry = connection.lookup(userDn);
        assertEquals("hnelson", entry.get("uid").getString());

        connection.close();
    }

    /**
     * Tests to make sure CRAM-MD5 binds below the RootDSE fail if the password is bad.
     */
    @Test
    public void testSaslCramMd5BindBadPassword() throws Exception {
        Dn userDn = new Dn("uid=hnelson,ou=users,dc=example,dc=com");
        LdapNetworkConnection connection = new LdapNetworkConnection(Network.LOOPBACK_HOSTNAME,
                getLdapServer().getPort());

        SaslCramMd5Request request = new SaslCramMd5Request();
        request.setUsername(userDn.getRdn().getValue());
        request.setCredentials("badsecret");

        BindResponse resp = connection.bind(request);
        assertEquals(ResultCodeEnum.INVALID_CREDENTIALS, resp.getLdapResult().getResultCode());
        connection.close();
    }

    /**
     * Tests to make sure DIGEST-MD5 binds below the RootDSE work.
     */
    @Test
    public void testSaslDigestMd5Bind() throws Exception {
        Dn userDn = new Dn("uid=hnelson,ou=users,dc=example,dc=com");
        LdapNetworkConnection connection = new LdapNetworkConnection(Network.LOOPBACK_HOSTNAME,
                getLdapServer().getPort());

        SaslDigestMd5Request request = new SaslDigestMd5Request();
        request.setUsername(userDn.getRdn().getValue());
        request.setCredentials("secret");
        request.setRealmName(ldapServer.getSaslRealms().get(0));
        BindResponse resp = connection.bind(request);
        assertEquals(ResultCodeEnum.SUCCESS, resp.getLdapResult().getResultCode());

        Entry entry = connection.lookup(userDn);
        assertEquals("hnelson", entry.get("uid").getString());

        connection.close();
    }

    /**
     * Tests to make sure DIGEST-MD5 binds below the RootDSE work with
     * SASL Quality of Protection set to 'auth'.
     */
    @Test
    public void testSaslDigestMd5BindSaslQoPAuth() throws Exception {
        Dn userDn = new Dn("uid=hnelson,ou=users,dc=example,dc=com");
        LdapNetworkConnection connection = new LdapNetworkConnection(Network.LOOPBACK_HOSTNAME,
                getLdapServer().getPort());

        SaslDigestMd5Request request = new SaslDigestMd5Request();
        request.setUsername(userDn.getRdn().getValue());
        request.setCredentials("secret");
        request.setRealmName(ldapServer.getSaslRealms().get(0));
        request.setQualityOfProtection(SaslQoP.AUTH);
        BindResponse resp = connection.bind(request);
        assertEquals(ResultCodeEnum.SUCCESS, resp.getLdapResult().getResultCode());

        Entry entry = connection.lookup(userDn);
        assertEquals("hnelson", entry.get("uid").getString());

        connection.close();
    }

    /**
     * Tests to make sure DIGEST-MD5 binds below the RootDSE work with
     * SASL Quality of Protection set to 'auth-int'.
     */
    @Test
    @Ignore
    public void testSaslDigestMd5BindSaslQoPAuthInt() throws Exception {
        Dn userDn = new Dn("uid=hnelson,ou=users,dc=example,dc=com");
        LdapNetworkConnection connection = new LdapNetworkConnection(Network.LOOPBACK_HOSTNAME,
                getLdapServer().getPort());

        SaslDigestMd5Request request = new SaslDigestMd5Request();
        request.setUsername(userDn.getRdn().getValue());
        request.setCredentials("secret");
        request.setRealmName(ldapServer.getSaslRealms().get(0));
        request.setQualityOfProtection(SaslQoP.AUTH_INT);
        BindResponse resp = connection.bind(request);
        assertEquals(ResultCodeEnum.SUCCESS, resp.getLdapResult().getResultCode());

        Entry entry = connection.lookup(userDn);
        assertEquals("hnelson", entry.get("uid").getString());

        connection.close();
    }

    /**
     * Tests to make sure DIGEST-MD5 binds below the RootDSE work with
     * SASL Quality of Protection set to 'auth-conf'.
     */
    @Test
    @Ignore
    public void testSaslDigestMd5BindSaslQoPAuthConf() throws Exception {
        Dn userDn = new Dn("uid=hnelson,ou=users,dc=example,dc=com");
        LdapNetworkConnection connection = new LdapNetworkConnection(Network.LOOPBACK_HOSTNAME,
                getLdapServer().getPort());

        SaslDigestMd5Request request = new SaslDigestMd5Request();
        request.setUsername(userDn.getRdn().getValue());
        request.setCredentials("secret");
        request.setRealmName(ldapServer.getSaslRealms().get(0));
        request.setQualityOfProtection(SaslQoP.AUTH_CONF);
        BindResponse resp = connection.bind(request);
        assertEquals(ResultCodeEnum.SUCCESS, resp.getLdapResult().getResultCode());

        Entry entry = connection.lookup(userDn);
        assertEquals("hnelson", entry.get("uid").getString());

        connection.close();
    }

    /**
     * Tests to make sure DIGEST-MD5 binds below the RootDSE fail if the realm is bad.
     */
    @Test
    public void testSaslDigestMd5BindBadRealm() throws Exception {
        Dn userDn = new Dn("uid=hnelson,ou=users,dc=example,dc=com");
        LdapNetworkConnection connection = new LdapNetworkConnection(Network.LOOPBACK_HOSTNAME,
                getLdapServer().getPort());

        SaslDigestMd5Request request = new SaslDigestMd5Request();
        request.setUsername(userDn.getRdn().getValue());
        request.setCredentials("secret");
        request.setRealmName("badrealm.com");
        BindResponse resp = connection.bind(request);
        assertEquals(ResultCodeEnum.INVALID_CREDENTIALS, resp.getLdapResult().getResultCode());

        connection.close();
    }

    /**
     * Tests to make sure DIGEST-MD5 binds below the RootDSE fail if the password is bad.
     */
    @Test
    public void testSaslDigestMd5BindBadPassword() throws Exception {
        Dn userDn = new Dn("uid=hnelson,ou=users,dc=example,dc=com");
        LdapNetworkConnection connection = new LdapNetworkConnection(Network.LOOPBACK_HOSTNAME,
                getLdapServer().getPort());

        SaslDigestMd5Request request = new SaslDigestMd5Request();
        request.setUsername(userDn.getRdn().getValue());
        request.setCredentials("badsecret");
        request.setRealmName(ldapServer.getSaslRealms().get(0));
        BindResponse resp = connection.bind(request);
        assertEquals(ResultCodeEnum.INVALID_CREDENTIALS, resp.getLdapResult().getResultCode());

        connection.close();
    }

    /**
     * Tests to make sure GSS-API binds below the RootDSE work.
     */
    @Test
    //@Ignore("Fails on ac OSX")
    public void testSaslGssApiBind() throws Exception {
        Dn userDn = new Dn("uid=hnelson,ou=users,dc=example,dc=com");
        LdapNetworkConnection connection = new LdapNetworkConnection(Network.LOOPBACK_HOSTNAME,
                getLdapServer().getPort());
        connection.setTimeOut(0L);

        kdcServer.getConfig().setPaEncTimestampRequired(false);

        SaslGssApiRequest request = new SaslGssApiRequest();
        request.setUsername(userDn.getRdn().getValue());
        request.setCredentials("secret");
        request.setRealmName(ldapServer.getSaslRealms().get(0).toUpperCase());
        request.setKdcHost(Network.LOOPBACK_HOSTNAME);
        request.setKdcPort(6088);
        BindResponse resp = connection.bind(request);
        assertEquals(ResultCodeEnum.SUCCESS, resp.getLdapResult().getResultCode());

        Entry entry = connection.lookup(userDn);
        assertEquals("hnelson", entry.get("uid").getString());

        connection.close();
    }

    /**
     * Tests to make sure GSS-API binds below the RootDSE fail if the realm is bad.
     */
    @Test
    public void testSaslGssApiBindBadRealm() throws Exception {
        Dn userDn = new Dn("uid=hnelson,ou=users,dc=example,dc=com");
        LdapNetworkConnection connection = new LdapNetworkConnection(Network.LOOPBACK_HOSTNAME,
                ldapServer.getPort());

        SaslGssApiRequest request = new SaslGssApiRequest();
        request.setUsername(userDn.getRdn().getValue());
        request.setCredentials("secret");
        request.setRealmName("badrealm.com");
        request.setKdcHost(Network.LOOPBACK_HOSTNAME);
        request.setKdcPort(6088);
        try {
            connection.bind(request);
        } catch (Exception e) {
            assertTrue(e instanceof LdapException);
        } finally {
            connection.close();
        }
    }

    /**
     * Tests to make sure GSS-API binds below the RootDSE fail if the password is bad.
     */
    @Test
    public void testSaslGssApiBindBadPassword() throws Exception {
        Dn userDn = new Dn("uid=hnelson,ou=users,dc=example,dc=com");
        LdapNetworkConnection connection = new LdapNetworkConnection(Network.LOOPBACK_HOSTNAME,
                ldapServer.getPort());

        SaslGssApiRequest request = new SaslGssApiRequest();
        request.setUsername(userDn.getRdn().getValue());
        request.setCredentials("badsecret");
        request.setRealmName(ldapServer.getSaslRealms().get(0).toUpperCase());
        request.setKdcHost(Network.LOOPBACK_HOSTNAME);
        request.setKdcPort(6088);
        try {
            connection.bind(request);
        } catch (Exception e) {
            assertTrue(e instanceof LdapException);
        } finally {
            connection.close();
        }
    }

    /**
     * Tests that the plumbing for NTLM bind works.
     */
    @Test
    public void testNtlmBind() throws Exception {
        BogusNtlmProvider provider = getNtlmProviderUsingReflection();

        NtlmSaslBindClient client = new NtlmSaslBindClient(SupportedSaslMechanisms.NTLM);
        BindResponse type2response = client.bindType1("type1_test".getBytes());
        assertEquals(1, type2response.getMessageId());
        assertEquals(ResultCodeEnum.SASL_BIND_IN_PROGRESS, type2response.getLdapResult().getResultCode());
        assertTrue(ArrayUtils.isEquals("type1_test".getBytes(), provider.getType1Response()));
        assertTrue(ArrayUtils.isEquals("challenge".getBytes(), type2response.getServerSaslCreds()));

        BindResponse finalResponse = client.bindType3("type3_test".getBytes());
        assertEquals(2, finalResponse.getMessageId());
        assertEquals(ResultCodeEnum.SUCCESS, finalResponse.getLdapResult().getResultCode());
        assertTrue(ArrayUtils.isEquals("type3_test".getBytes(), provider.getType3Response()));
    }

    /**
     * Tests that the plumbing for NTLM bind works.
     */
    @Test
    public void testGssSpnegoBind() throws Exception {
        BogusNtlmProvider provider = new BogusNtlmProvider();

        // the provider configured in @CreateLdapServer only sets for the NTLM mechanism
        // but we use the same NtlmMechanismHandler class for GSS_SPNEGO too but this is a separate
        // instance, so we need to set the provider in the NtlmMechanismHandler instance of GSS_SPNEGO mechanism
        NtlmMechanismHandler ntlmHandler = (NtlmMechanismHandler) getLdapServer().getSaslMechanismHandlers()
                .get(SupportedSaslMechanisms.GSS_SPNEGO);
        ntlmHandler.setNtlmProvider(provider);

        NtlmSaslBindClient client = new NtlmSaslBindClient(SupportedSaslMechanisms.GSS_SPNEGO);
        BindResponse type2response = client.bindType1("type1_test".getBytes());
        assertEquals(1, type2response.getMessageId());
        assertEquals(ResultCodeEnum.SASL_BIND_IN_PROGRESS, type2response.getLdapResult().getResultCode());
        assertTrue(ArrayUtils.isEquals("type1_test".getBytes(), provider.getType1Response()));
        assertTrue(ArrayUtils.isEquals("challenge".getBytes(), type2response.getServerSaslCreds()));

        BindResponse finalResponse = client.bindType3("type3_test".getBytes());
        assertEquals(2, finalResponse.getMessageId());
        assertEquals(ResultCodeEnum.SUCCESS, finalResponse.getLdapResult().getResultCode());
        assertTrue(ArrayUtils.isEquals("type3_test".getBytes(), provider.getType3Response()));
    }

    /**
     * Test for DIRAPI-30 (Sporadic NullPointerException during SASL bind).
     * Tests multiple connect/bind/unbind/disconnect.
     */
    @Ignore("Activate when DIRAPI-30 is solved")
    @Test
    public void testSequentialBinds() throws Exception {
        LdapNetworkConnection connection;
        BindResponse resp;
        Entry entry;
        Dn userDn = new Dn("uid=hnelson,ou=users,dc=example,dc=com");

        for (int i = 0; i < 1000; i++) {
            System.out.println("try " + i);

            // Digest-MD5
            connection = new LdapNetworkConnection(Network.LOOPBACK_HOSTNAME, ldapServer.getPort());
            SaslDigestMd5Request digetDigestMd5Request = new SaslDigestMd5Request();
            digetDigestMd5Request.setUsername(userDn.getRdn().getValue());
            digetDigestMd5Request.setCredentials("secret");
            digetDigestMd5Request.setRealmName(ldapServer.getSaslRealms().get(0));
            resp = connection.bind(digetDigestMd5Request);
            assertEquals(ResultCodeEnum.SUCCESS, resp.getLdapResult().getResultCode());
            entry = connection.lookup(userDn);
            assertEquals("hnelson", entry.get("uid").getString());
            connection.close();

            // Cram-MD5
            connection = new LdapNetworkConnection(Network.LOOPBACK_HOSTNAME, ldapServer.getPort());
            SaslCramMd5Request cramMd5Request = new SaslCramMd5Request();
            cramMd5Request.setUsername(userDn.getRdn().getValue());
            cramMd5Request.setCredentials("secret");
            resp = connection.bind(cramMd5Request);
            assertEquals(ResultCodeEnum.SUCCESS, resp.getLdapResult().getResultCode());
            entry = connection.lookup(userDn);
            assertEquals("hnelson", entry.get("uid").getString());
            connection.close();

            // GSSAPI
            connection = new LdapNetworkConnection(Network.LOOPBACK_HOSTNAME, ldapServer.getPort());
            SaslGssApiRequest gssApiRequest = new SaslGssApiRequest();
            gssApiRequest.setUsername(userDn.getRdn().getValue());
            gssApiRequest.setCredentials("secret");
            gssApiRequest.setRealmName(ldapServer.getSaslRealms().get(0));
            gssApiRequest.setKdcHost(Network.LOOPBACK_HOSTNAME);
            gssApiRequest.setKdcPort(6088);
            resp = connection.bind(gssApiRequest);
            assertEquals(ResultCodeEnum.SUCCESS, resp.getLdapResult().getResultCode());
            entry = connection.lookup(userDn);
            assertEquals("hnelson", entry.get("uid").getString());
            connection.close();
        }
    }

    /**
     * A NTLM client
     */
    class NtlmSaslBindClient extends SocketClient {
        private final Logger LOG = LoggerFactory.getLogger(NtlmSaslBindClient.class);

        private final String mechanism;

        NtlmSaslBindClient(String mechanism) throws Exception {
            this.mechanism = mechanism;
            setDefaultPort(getLdapServer().getPort());
            connect(Network.LOOPBACK_HOSTNAME, getLdapServer().getPort());
            setTcpNoDelay(false);

            LOG.debug("isConnected() = {}", isConnected());
            LOG.debug("LocalPort     = {}", getLocalPort());
            LOG.debug("LocalAddress  = {}", getLocalAddress());
            LOG.debug("RemotePort    = {}", getRemotePort());
            LOG.debug("RemoteAddress = {}", getRemoteAddress());
        }

        BindResponse bindType1(byte[] type1response) throws Exception {
            if (!isConnected()) {
                throw new IllegalStateException("Client is not connected.");
            }

            // Setup the bind request
            BindRequestImpl request = new BindRequestImpl();
            request.setMessageId(1);
            request.setDn(new Dn("uid=admin,ou=system"));
            request.setSimple(false);
            request.setCredentials(type1response);
            request.setSaslMechanism(mechanism);
            request.setVersion3(true);

            // Setup the ASN1 Encoder and Decoder
            LdapDecoder decoder = new LdapDecoder();

            // Send encoded request to server
            LdapEncoder encoder = new LdapEncoder(getLdapServer().getDirectoryService().getLdapCodecService());
            ByteBuffer bb = encoder.encodeMessage(request);

            bb.flip();

            _output_.write(bb.array());
            _output_.flush();

            while (_input_.available() <= 0) {
                Thread.sleep(100);
            }

            // Retrieve the response back from server to my last request.
            LdapMessageContainer<AbstractMessageDecorator<? extends Message>> container = new LdapMessageContainer(
                    ldapServer.getDirectoryService().getLdapCodecService());
            return (BindResponse) decoder.decode(_input_, container);
        }

        BindResponse bindType3(byte[] type3response) throws Exception {
            if (!isConnected()) {
                throw new IllegalStateException("Client is not connected.");
            }

            // Setup the bind request
            BindRequestImpl request = new BindRequestImpl();
            request.setMessageId(2);
            request.setDn(new Dn("uid=admin,ou=system"));
            request.setSimple(false);
            request.setCredentials(type3response);
            request.setSaslMechanism(mechanism);
            request.setVersion3(true);

            // Setup the ASN1 Enoder and Decoder
            LdapDecoder decoder = new LdapDecoder();

            // Send encoded request to server
            LdapEncoder encoder = new LdapEncoder(getLdapServer().getDirectoryService().getLdapCodecService());
            ByteBuffer bb = encoder.encodeMessage(request);
            bb.flip();

            _output_.write(bb.array());
            _output_.flush();

            while (_input_.available() <= 0) {
                Thread.sleep(100);
            }

            // Retrieve the response back from server to my last request.
            LdapMessageContainer<AbstractMessageDecorator<? extends Message>> container = new LdapMessageContainer(
                    ldapServer.getDirectoryService().getLdapCodecService());
            return (BindResponse) decoder.decode(_input_, container);
        }
    }

    private BogusNtlmProvider getNtlmProviderUsingReflection() {
        BogusNtlmProvider provider = null;
        try {
            NtlmMechanismHandler ntlmHandler = (NtlmMechanismHandler) getLdapServer().getSaslMechanismHandlers()
                    .get(SupportedSaslMechanisms.NTLM);

            // there is no getter for 'provider' field hence this hack
            Field field = ntlmHandler.getClass().getDeclaredField("provider");
            field.setAccessible(true);
            provider = (BogusNtlmProvider) field.get(ntlmHandler);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return provider;
    }

    ////////////////////////
    protected Entry getPrincipalAttributes(String dn, String sn, String cn, String uid, String userPassword,
            String principal) throws LdapException {
        Entry entry = new DefaultEntry(dn);
        entry.add(SchemaConstants.OBJECT_CLASS_AT, "person", "inetOrgPerson", "krb5principal", "krb5kdcentry");
        entry.add(SchemaConstants.CN_AT, cn);
        entry.add(SchemaConstants.SN_AT, sn);
        entry.add(SchemaConstants.UID_AT, uid);
        entry.add(SchemaConstants.USER_PASSWORD_AT, userPassword);
        entry.add(KerberosAttribute.KRB5_PRINCIPAL_NAME_AT, principal);
        entry.add(KerberosAttribute.KRB5_KEY_VERSION_NUMBER_AT, "0");

        return entry;
    }

}