Package org.apache.directory.studio.connection.core.io.jndi

Source Code of org.apache.directory.studio.connection.core.io.jndi.JNDIConnectionWrapper

/*
*  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.studio.connection.core.io.jndi;


import java.io.File;
import java.io.IOException;
import java.security.PrivilegedAction;
import java.util.Collection;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;

import javax.naming.CommunicationException;
import javax.naming.CompositeName;
import javax.naming.Context;
import javax.naming.InsufficientResourcesException;
import javax.naming.InvalidNameException;
import javax.naming.Name;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.PartialResultException;
import javax.naming.ReferralException;
import javax.naming.ServiceUnavailableException;
import javax.naming.directory.Attributes;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.Control;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.StartTlsRequest;
import javax.naming.ldap.StartTlsResponse;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.directory.shared.ldap.codec.util.LdapURLEncodingException;
import org.apache.directory.shared.ldap.name.LdapDN;
import org.apache.directory.shared.ldap.util.LdapURL;
import org.apache.directory.studio.connection.core.Connection;
import org.apache.directory.studio.connection.core.ConnectionCoreConstants;
import org.apache.directory.studio.connection.core.ConnectionCorePlugin;
import org.apache.directory.studio.connection.core.ConnectionParameter;
import org.apache.directory.studio.connection.core.IAuthHandler;
import org.apache.directory.studio.connection.core.IConnectionListener;
import org.apache.directory.studio.connection.core.ICredentials;
import org.apache.directory.studio.connection.core.IJndiLogger;
import org.apache.directory.studio.connection.core.IReferralHandler;
import org.apache.directory.studio.connection.core.Messages;
import org.apache.directory.studio.connection.core.Utils;
import org.apache.directory.studio.connection.core.Connection.AliasDereferencingMethod;
import org.apache.directory.studio.connection.core.Connection.ReferralHandlingMethod;
import org.apache.directory.studio.connection.core.ConnectionParameter.AuthenticationMethod;
import org.apache.directory.studio.connection.core.event.ConnectionEventRegistry;
import org.apache.directory.studio.connection.core.io.ConnectionWrapper;
import org.apache.directory.studio.connection.core.io.jndi.ReferralsInfo.Referral;
import org.apache.directory.studio.connection.core.jobs.StudioProgressMonitor;
import org.eclipse.core.runtime.Preferences;
import org.eclipse.osgi.util.NLS;


/**
* A connection wrapper that uses JNDI.
*
* - asychron + cancelable
* - SSL certificate
* - manages broken/closed connections
* - delete old RDN
* - exception handling
* - referral handling
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
* @version $Rev$, $Date$
*/
public class JNDIConnectionWrapper implements ConnectionWrapper
{

    private static final String JAVA_NAMING_LDAP_DELETE_RDN = "java.naming.ldap.deleteRDN"; //$NON-NLS-1$

    private static final String AUTHMETHOD_NONE = "none"; //$NON-NLS-1$

    private static final String AUTHMETHOD_SIMPLE = "simple"; //$NON-NLS-1$

    private static final String AUTHMETHOD_DIGEST_MD5 = "DIGEST-MD5"; //$NON-NLS-1$

    private static final String AUTHMETHOD_CRAM_MD5 = "CRAM-MD5"; //$NON-NLS-1$

    private static final String AUTHMETHOD_GSSAPI = "GSSAPI"; //$NON-NLS-1$

    private static final String NO_CONNECTION = "No connection"; //$NON-NLS-1$

    private static final String JAVA_NAMING_SECURITY_SASL_REALM = "java.naming.security.sasl.realm"; //$NON-NLS-1$

    private static final String JAVA_NAMING_LDAP_FACTORY_SOCKET = "java.naming.ldap.factory.socket"; //$NON-NLS-1$

    private static final String COM_SUN_JNDI_DNS_TIMEOUT_RETRIES = "com.sun.jndi.dns.timeout.retries"; //$NON-NLS-1$

    private static final String COM_SUN_JNDI_DNS_TIMEOUT_INITIAL = "com.sun.jndi.dns.timeout.initial"; //$NON-NLS-1$

    private static final String COM_SUN_JNDI_LDAP_CONNECT_TIMEOUT = "com.sun.jndi.ldap.connect.timeout"; //$NON-NLS-1$

    private static final String JAVA_NAMING_LDAP_VERSION = "java.naming.ldap.version"; //$NON-NLS-1$

    private static final String JAVA_NAMING_LDAP_DEREF_ALIASES = "java.naming.ldap.derefAliases"; //$NON-NLS-1$

    private static final String JAVA_NAMING_LDAP_ATTRIBUTES_BINARY = "java.naming.ldap.attributes.binary"; //$NON-NLS-1$

    private static int SEARCH_RESQUEST_NUM = 0;

    private Connection connection;

    private boolean useLdaps;

    private boolean useStartTLS;

    private String authMethod;

    private String bindPrincipal;

    private String bindCredentials;

    private String saslRealm;

    private Hashtable<String, String> environment;

    private InitialLdapContext context;

    private boolean isConnected;

    private Thread jobThread;

    private Collection<String> binaryAttributes;

    /** JNDI constant for "throw" referrals handling */
    public static final String REFERRAL_THROW = "throw"; //$NON-NLS-1$

    /** JNDI constant for "follow" referrals handling */
    public static final String REFERRAL_FOLLOW = "follow"; //$NON-NLS-1$

    /** JNDI constant for "ignore" referrals handling */
    public static final String REFERRAL_IGNORE = "ignore"; //$NON-NLS-1$

    /** JNDI constant for "searching" alias dereferencing */
    public static final String ALIAS_SEARCHING = "searching"; //$NON-NLS-1$

    /** JNDI constant for "finding" alias dereferencing */
    public static final String ALIAS_FINDING = "finding"; //$NON-NLS-1$

    /** JNDI constant for "always" alias dereferencing */
    public static final String ALIAS_ALWAYS = "always"; //$NON-NLS-1$

    /** JNDI constant for "never" alias dereferencing */
    public static final String ALIAS_NEVER = "never"; //$NON-NLS-1$


    /**
     * Creates a new instance of JNDIConnectionContext.
     *
     * @param connection the connection
     */
    public JNDIConnectionWrapper( Connection connection )
    {
        this.connection = connection;
    }


    /**
     * {@inheritDoc}
     */
    public void connect( StudioProgressMonitor monitor )
    {
        context = null;
        isConnected = false;
        jobThread = null;

        try
        {
            doConnect( monitor );
        }
        catch ( NamingException ne )
        {
            disconnect();
            monitor.reportError( ne );
        }
    }


    /**
     * {@inheritDoc}
     */
    public void disconnect()
    {
        if ( jobThread != null )
        {
            Thread t = jobThread;
            jobThread = null;
            t.interrupt();
        }
        if ( context != null )
        {
            try
            {
                context.close();
            }
            catch ( NamingException e )
            {
                // ignore
            }
            context = null;
        }
        isConnected = false;
        System.gc();
    }


    /**
     * {@inheritDoc}
     */
    public void bind( StudioProgressMonitor monitor )
    {
        try
        {
            doBind( monitor );
        }
        catch ( NamingException ne )
        {
            disconnect();
            monitor.reportError( ne );
        }
    }


    /**
     * {@inheritDoc}
     */
    public void unbind()
    {
        disconnect();
    }


    /**
     * {@inheritDoc}
     */
    public boolean isConnected()
    {
        return context != null;
    }


    /**
     * Sets the binary attributes.
     *
     * @param binaryAttributes the binary attributes
     */
    public void setBinaryAttributes( Collection<String> binaryAttributes )
    {
        this.binaryAttributes = binaryAttributes;
        String binaryAttributesString = StringUtils.EMPTY;
        for ( String string : binaryAttributes )
        {
            binaryAttributesString += string + ' ';
        }

        if ( environment != null )
        {
            environment.put( JAVA_NAMING_LDAP_ATTRIBUTES_BINARY, binaryAttributesString );
        }

        if ( context != null )
        {
            try
            {
                context.addToEnvironment( JAVA_NAMING_LDAP_ATTRIBUTES_BINARY, binaryAttributesString );
            }
            catch ( NamingException e )
            {
                // TODO: logging
                e.printStackTrace();
            }
        }
    }


    /**
     * Search.
     *
     * @param searchBase the search base
     * @param filter the filter
     * @param searchControls the controls
     * @param aliasesDereferencingMethod the aliases dereferencing method
     * @param referralsHandlingMethod the referrals handling method
     * @param controls the LDAP controls
     * @param monitor the progress monitor
     * @param referralsInfo the referrals info
     *
     * @return the naming enumeration or null if an exception occurs.
     */
    public StudioNamingEnumeration search( final String searchBase, final String filter,
        final SearchControls searchControls, final AliasDereferencingMethod aliasesDereferencingMethod,
        final ReferralHandlingMethod referralsHandlingMethod, final Control[] controls,
        final StudioProgressMonitor monitor, final ReferralsInfo referralsInfo )
    {
        final long requestNum = SEARCH_RESQUEST_NUM++;

        // start
        InnerRunnable runnable = new InnerRunnable()
        {
            public void run()
            {
                LdapContext searchCtx = context;
                try
                {
                    // create the search context
                    searchCtx = context.newInstance( controls );

                    // translate alias dereferencing method
                    searchCtx.addToEnvironment( JAVA_NAMING_LDAP_DEREF_ALIASES,
                        translateDerefAliasMethod( aliasesDereferencingMethod ) );

                    // use "throw" as we handle referrals manually
                    searchCtx.addToEnvironment( Context.REFERRAL, REFERRAL_THROW );

                    // perform the search
                    NamingEnumeration<SearchResult> result = searchCtx.search( JNDIConnectionWrapper
                        .getSaveJndiName( searchBase ), filter, searchControls );
                    namingEnumeration = new StudioNamingEnumeration( connection, searchCtx, result, null, searchBase,
                        filter, searchControls, aliasesDereferencingMethod, referralsHandlingMethod, controls,
                        requestNum, monitor, referralsInfo );
                }
                catch ( PartialResultException e )
                {
                    namingEnumeration = new StudioNamingEnumeration( connection, searchCtx, null, e, searchBase,
                        filter, searchControls, aliasesDereferencingMethod, referralsHandlingMethod, controls,
                        requestNum, monitor, referralsInfo );
                }
                catch ( ReferralException e )
                {
                    namingEnumeration = new StudioNamingEnumeration( connection, searchCtx, null, e, searchBase,
                        filter, searchControls, aliasesDereferencingMethod, referralsHandlingMethod, controls,
                        requestNum, monitor, referralsInfo );
                }
                catch ( NamingException e )
                {
                    namingException = e;
                }

                for ( IJndiLogger logger : getJndiLoggers() )
                {
                    if ( namingEnumeration != null )
                    {
                        logger.logSearchRequest( connection, searchBase, filter, searchControls,
                            aliasesDereferencingMethod, controls, requestNum, namingException );
                    }
                    else
                    {
                        logger.logSearchRequest( connection, searchBase, filter, searchControls,
                            aliasesDereferencingMethod, controls, requestNum, namingException );
                        logger.logSearchResultDone( connection, 0, requestNum, namingException );
                    }
                }
            }
        };

        try
        {
            checkConnectionAndRunAndMonitor( runnable, monitor );
        }
        catch ( NamingException ne )
        {
            monitor.reportError( ne );
            return null;
        }

        if ( runnable.isCanceled() )
        {
            monitor.setCanceled( true );
        }
        if ( runnable.getException() != null )
        {
            monitor.reportError( runnable.getException() );
            return null;
        }
        else
        {
            return runnable.getResult();
        }
    }


    /**
     * Modifies attributes of an entry.
     *
     * @param dn the DN
     * @param modificationItems the modification items
     * @param controls the controls
     * @param monitor the progress monitor
     * @param referralsInfo the referrals info
     */
    public void modifyEntry( final String dn, final ModificationItem[] modificationItems, final Control[] controls,
        final StudioProgressMonitor monitor, final ReferralsInfo referralsInfo )
    {
        if ( connection.isReadOnly() )
        {
            monitor.reportError( NLS.bind( Messages.error__connection_is_readonly, connection.getName() ) );
            return;
        }

        InnerRunnable runnable = new InnerRunnable()
        {
            public void run()
            {
                boolean logModifycation = true;
                try
                {
                    // create modify context
                    LdapContext modCtx = context.newInstance( controls );

                    // use "throw" as we handle referrals manually
                    modCtx.addToEnvironment( Context.REFERRAL, REFERRAL_THROW );

                    // perform modification
                    modCtx.modifyAttributes( getSaveJndiName( dn ), modificationItems );
                }
                catch ( ReferralException re )
                {
                    logModifycation = false;
                    try
                    {
                        ReferralsInfo newReferralsInfo = handleReferralException( re, referralsInfo );
                        Referral referral = newReferralsInfo.getNextReferral();
                        if ( referral != null )
                        {
                            Connection referralConnection = getReferralConnection( referral, monitor, this );
                            if ( referralConnection != null )
                            {
                                String referralDn = referral.getLdapURLs().get( 0 ).getDn().getUpName();
                                referralConnection.getJNDIConnectionWrapper().modifyEntry( referralDn,
                                    modificationItems, controls, monitor, newReferralsInfo );
                            }
                            else
                            {
                                canceled = true;
                            }
                        }

                        return;
                    }
                    catch ( NamingException ne )
                    {
                        namingException = ne;
                    }
                }
                catch ( NamingException ne )
                {
                    namingException = ne;
                }

                if ( logModifycation )
                {
                    for ( IJndiLogger logger : getJndiLoggers() )
                    {
                        logger.logChangetypeModify( connection, dn, modificationItems, controls, namingException );
                    }
                }
            }
        };

        try
        {
            checkConnectionAndRunAndMonitor( runnable, monitor );
        }
        catch ( NamingException ne )
        {
            monitor.reportError( ne );
        }

        if ( runnable.isCanceled() )
        {
            monitor.setCanceled( true );
        }
        if ( runnable.getException() != null )
        {
            monitor.reportError( runnable.getException() );
        }
    }


    /**
     * Renames an entry.
     *
     * @param oldDn the old DN
     * @param newDn the new DN
     * @param deleteOldRdn true to delete the old RDN
     * @param controls the controls
     * @param monitor the progress monitor
     * @param referralsInfo the referrals info
     */
    public void renameEntry( final String oldDn, final String newDn, final boolean deleteOldRdn,
        final Control[] controls, final StudioProgressMonitor monitor, final ReferralsInfo referralsInfo )
    {
        if ( connection.isReadOnly() )
        {
            monitor.reportError( NLS.bind( Messages.error__connection_is_readonly, connection.getName() ) );
            return;
        }

        InnerRunnable runnable = new InnerRunnable()
        {
            public void run()
            {
                boolean logModifycation = true;
                try
                {
                    // create modify context
                    LdapContext modCtx = context.newInstance( controls );

                    // use "throw" as we handle referrals manually
                    modCtx.addToEnvironment( Context.REFERRAL, REFERRAL_THROW );

                    // delete old RDN
                    if ( deleteOldRdn )
                    {
                        modCtx.addToEnvironment( JAVA_NAMING_LDAP_DELETE_RDN, "true" ); //$NON-NLS-1$
                    }
                    else
                    {
                        modCtx.addToEnvironment( JAVA_NAMING_LDAP_DELETE_RDN, "false" ); //$NON-NLS-1$
                    }

                    // rename entry
                    modCtx.rename( getSaveJndiName( oldDn ), getSaveJndiName( newDn ) );
                }
                catch ( ReferralException re )
                {
                    logModifycation = false;
                    try
                    {
                        ReferralsInfo newReferralsInfo = handleReferralException( re, referralsInfo );
                        Referral referral = newReferralsInfo.getNextReferral();
                        if ( referral != null )
                        {
                            Connection referralConnection = getReferralConnection( referral, monitor, this );
                            if ( referralConnection != null )
                            {
                                referralConnection.getJNDIConnectionWrapper().renameEntry( oldDn, newDn, deleteOldRdn,
                                    controls, monitor, newReferralsInfo );
                            }
                            else
                            {
                                canceled = true;
                            }
                        }
                    }
                    catch ( NamingException ne )
                    {
                        namingException = ne;
                    }
                }
                catch ( NamingException ne )
                {
                    namingException = ne;
                }

                if ( logModifycation )
                {
                    for ( IJndiLogger logger : getJndiLoggers() )
                    {
                        logger.logChangetypeModDn( connection, oldDn, newDn, deleteOldRdn, controls, namingException );
                    }
                }
            }
        };

        try
        {
            checkConnectionAndRunAndMonitor( runnable, monitor );
        }
        catch ( NamingException ne )
        {
            monitor.reportError( ne );
        }

        if ( runnable.isCanceled() )
        {
            monitor.setCanceled( true );
        }
        if ( runnable.getException() != null )
        {
            monitor.reportError( runnable.getException() );
        }
    }


    /**
     * Creates an entry.
     *
     * @param dn the entry's DN
     * @param attributes the entry's attributes
     * @param controls the controls
     * @param monitor the progress monitor
     * @param referralsInfo the referrals info
     */
    public void createEntry( final String dn, final Attributes attributes, final Control[] controls,
        final StudioProgressMonitor monitor, final ReferralsInfo referralsInfo )
    {
        if ( connection.isReadOnly() )
        {
            monitor.reportError( NLS.bind( Messages.error__connection_is_readonly, connection.getName() ) );
            return;
        }

        InnerRunnable runnable = new InnerRunnable()
        {
            public void run()
            {
                boolean logModifycation = true;
                try
                {
                    // create modify context
                    LdapContext modCtx = context.newInstance( controls );

                    // use "throw" as we handle referrals manually
                    modCtx.addToEnvironment( Context.REFERRAL, REFERRAL_THROW );

                    // create entry
                    modCtx.createSubcontext( getSaveJndiName( dn ), attributes );
                }
                catch ( ReferralException re )
                {
                    logModifycation = false;
                    try
                    {
                        ReferralsInfo newReferralsInfo = handleReferralException( re, referralsInfo );
                        Referral referral = newReferralsInfo.getNextReferral();
                        if ( referral != null )
                        {
                            Connection referralConnection = getReferralConnection( referral, monitor, this );
                            if ( referralConnection != null )
                            {
                                String referralDn = referral.getLdapURLs().get( 0 ).getDn().getUpName();
                                referralConnection.getJNDIConnectionWrapper().createEntry( referralDn, attributes,
                                    controls, monitor, newReferralsInfo );
                            }
                            else
                            {
                                canceled = true;
                            }
                        }
                    }
                    catch ( NamingException ne )
                    {
                        namingException = ne;
                    }
                }
                catch ( NamingException ne )
                {
                    namingException = ne;
                }

                if ( logModifycation )
                {
                    for ( IJndiLogger logger : getJndiLoggers() )
                    {
                        logger.logChangetypeAdd( connection, dn, attributes, controls, namingException );
                    }
                }
            }
        };

        try
        {
            checkConnectionAndRunAndMonitor( runnable, monitor );
        }
        catch ( NamingException ne )
        {
            monitor.reportError( ne );
        }

        if ( runnable.isCanceled() )
        {
            monitor.setCanceled( true );
        }
        if ( runnable.getException() != null )
        {
            monitor.reportError( runnable.getException() );
        }
    }


    /**
     * Deletes an entry.
     *
     * @param dn the DN of the entry to delete
     * @param controls the controls
     * @param monitor the progress monitor
     * @param referralsInfo the referrals info
     */
    public void deleteEntry( final String dn, final Control[] controls, final StudioProgressMonitor monitor,
        final ReferralsInfo referralsInfo )
    {
        if ( connection.isReadOnly() )
        {
            monitor.reportError( NLS.bind( Messages.error__connection_is_readonly, connection.getName() ) );
            return;
        }

        InnerRunnable runnable = new InnerRunnable()
        {
            public void run()
            {
                boolean logModifycation = true;
                try
                {
                    // create modify context
                    LdapContext modCtx = context.newInstance( controls );

                    // use "throw" as we handle referrals manually
                    modCtx.addToEnvironment( Context.REFERRAL, REFERRAL_THROW );

                    // delete entry
                    modCtx.destroySubcontext( getSaveJndiName( dn ) );
                }
                catch ( ReferralException re )
                {
                    logModifycation = false;
                    try
                    {
                        ReferralsInfo newReferralsInfo = handleReferralException( re, referralsInfo );
                        Referral referral = newReferralsInfo.getNextReferral();
                        if ( referral != null )
                        {
                            Connection referralConnection = getReferralConnection( referral, monitor, this );
                            if ( referralConnection != null )
                            {
                                String referralDn = referral.getLdapURLs().get( 0 ).getDn().getUpName();
                                referralConnection.getJNDIConnectionWrapper().deleteEntry( referralDn, controls,
                                    monitor, newReferralsInfo );
                            }
                            else
                            {
                                canceled = true;
                            }
                        }
                    }
                    catch ( NamingException ne )
                    {
                        namingException = ne;
                    }
                }
                catch ( NamingException ne )
                {
                    namingException = ne;
                }

                if ( logModifycation )
                {
                    for ( IJndiLogger logger : getJndiLoggers() )
                    {
                        logger.logChangetypeDelete( connection, dn, controls, namingException );
                    }
                }
            }
        };

        try
        {
            checkConnectionAndRunAndMonitor( runnable, monitor );
        }
        catch ( NamingException ne )
        {
            monitor.reportError( ne );
        }

        if ( runnable.isCanceled() )
        {
            monitor.setCanceled( true );
        }
        if ( runnable.getException() != null )
        {
            monitor.reportError( runnable.getException() );
        }
    }


    private void doConnect( final StudioProgressMonitor monitor ) throws NamingException
    {
        context = null;
        isConnected = true;

        // setup connection parameters
        String host = connection.getConnectionParameter().getHost();
        int port = connection.getConnectionParameter().getPort();

        useLdaps = connection.getConnectionParameter().getEncryptionMethod() == ConnectionParameter.EncryptionMethod.LDAPS;
        useStartTLS = connection.getConnectionParameter().getEncryptionMethod() == ConnectionParameter.EncryptionMethod.START_TLS;

        environment = new Hashtable<String, String>();
        Preferences preferences = ConnectionCorePlugin.getDefault().getPluginPreferences();
        final boolean validateCertificates = preferences
            .getBoolean( ConnectionCoreConstants.PREFERENCE_VALIDATE_CERTIFICATES );
        String ldapCtxFactory = preferences.getString( ConnectionCoreConstants.PREFERENCE_LDAP_CONTEXT_FACTORY );
        environment.put( Context.INITIAL_CONTEXT_FACTORY, ldapCtxFactory );
        environment.put( JAVA_NAMING_LDAP_VERSION, "3" ); //$NON-NLS-1$

        // timeouts
        // Don't use a timeout when using ldaps: JNDI throws a SocketException
        // when setting a timeout on SSL connections.
        if ( !useLdaps )
        {
            environment.put( COM_SUN_JNDI_LDAP_CONNECT_TIMEOUT, "10000" ); //$NON-NLS-1$
        }
        environment.put( COM_SUN_JNDI_DNS_TIMEOUT_INITIAL, "2000" ); //$NON-NLS-1$
        environment.put( COM_SUN_JNDI_DNS_TIMEOUT_RETRIES, "3" ); //$NON-NLS-1$

        // ldaps://
        if ( useLdaps )
        {
            environment.put( Context.PROVIDER_URL, LdapURL.LDAPS_SCHEME + host + ':' + port );
            environment.put( Context.SECURITY_PROTOCOL, "ssl" ); //$NON-NLS-1$
            // host name verification is done in StudioTrustManager
            environment.put( JAVA_NAMING_LDAP_FACTORY_SOCKET, validateCertificates ? StudioSSLSocketFactory.class
                .getName() : DummySSLSocketFactory.class.getName() );
        }
        else
        {
            environment.put( Context.PROVIDER_URL, LdapURL.LDAP_SCHEME + host + ':' + port );
        }

        if ( binaryAttributes != null )
        {
            setBinaryAttributes( binaryAttributes );
        }

        InnerRunnable runnable = new InnerRunnable()
        {
            public void run()
            {
                try
                {
                    context = new InitialLdapContext( environment, null );

                    if ( useStartTLS )
                    {
                        try
                        {
                            StartTlsResponse tls = ( StartTlsResponse ) context
                                .extendedOperation( new StartTlsRequest() );
                            // deactivate host name verification at this level,
                            // host name verification is done in StudioTrustManager
                            tls.setHostnameVerifier( new HostnameVerifier()
                            {
                                public boolean verify( String hostname, SSLSession session )
                                {
                                    return true;
                                }
                            } );
                            if ( validateCertificates )
                            {
                                tls.negotiate( StudioSSLSocketFactory.getDefault() );
                            }
                            else
                            {
                                tls.negotiate( DummySSLSocketFactory.getDefault() );
                            }
                        }
                        catch ( Exception e )
                        {
                            namingException = new NamingException( e.getMessage() != null ? e.getMessage()
                                : "Error while establishing TLS session" ); //$NON-NLS-1$
                            namingException.setRootCause( e );
                            context.close();
                        }
                    }
                }
                catch ( NamingException ne )
                {
                    namingException = ne;
                }
            }
        };

        runAndMonitor( runnable, monitor );

        if ( runnable.getException() != null )
        {
            throw runnable.getException();
        }
        else if ( context != null )
        {
            // all OK
        }
        else
        {
            throw new NamingException( "???" ); //$NON-NLS-1$
        }
    }


    private void doBind( final StudioProgressMonitor monitor ) throws NamingException
    {
        if ( context != null && isConnected )
        {
            // setup authentication methdod
            authMethod = AUTHMETHOD_NONE;
            if ( connection.getConnectionParameter().getAuthMethod() == ConnectionParameter.AuthenticationMethod.SIMPLE )
            {
                authMethod = AUTHMETHOD_SIMPLE;
            }
            else if ( connection.getConnectionParameter().getAuthMethod() == ConnectionParameter.AuthenticationMethod.SASL_DIGEST_MD5 )
            {
                authMethod = AUTHMETHOD_DIGEST_MD5;
                saslRealm = connection.getConnectionParameter().getSaslRealm();
            }
            else if ( connection.getConnectionParameter().getAuthMethod() == ConnectionParameter.AuthenticationMethod.SASL_CRAM_MD5 )
            {
                authMethod = AUTHMETHOD_CRAM_MD5;
            }
            else if ( connection.getConnectionParameter().getAuthMethod() == ConnectionParameter.AuthenticationMethod.SASL_GSSAPI )
            {
                authMethod = AUTHMETHOD_GSSAPI;
            }

            // setup credentials
            IAuthHandler authHandler = ConnectionCorePlugin.getDefault().getAuthHandler();
            if ( authHandler == null )
            {
                NamingException namingException = new NamingException( Messages.model__no_auth_handler );
                monitor.reportError( Messages.model__no_auth_handler, namingException );
                throw namingException;
            }
            ICredentials credentials = authHandler.getCredentials( connection.getConnectionParameter() );
            if ( credentials == null )
            {
                CancelException cancelException = new CancelException();
                monitor.setCanceled( true );
                monitor.reportError( Messages.model__no_credentials, cancelException );
                throw cancelException;
            }
            if ( credentials.getBindPrincipal() == null || credentials.getBindPassword() == null )
            {
                NamingException namingException = new NamingException( Messages.model__no_credentials );
                monitor.reportError( Messages.model__no_credentials, namingException );
                throw namingException;
            }
            bindPrincipal = credentials.getBindPrincipal();
            bindCredentials = credentials.getBindPassword();

            InnerRunnable runnable = new InnerRunnable()
            {
                public void run()
                {
                    try
                    {
                        context.removeFromEnvironment( Context.SECURITY_AUTHENTICATION );
                        context.removeFromEnvironment( Context.SECURITY_PRINCIPAL );
                        context.removeFromEnvironment( Context.SECURITY_CREDENTIALS );
                        context.removeFromEnvironment( JAVA_NAMING_SECURITY_SASL_REALM );

                        context.addToEnvironment( Context.SECURITY_AUTHENTICATION, authMethod );

                        // SASL options
                        if ( connection.getConnectionParameter().getAuthMethod() == AuthenticationMethod.SASL_CRAM_MD5
                            || connection.getConnectionParameter().getAuthMethod() == AuthenticationMethod.SASL_DIGEST_MD5
                            || connection.getConnectionParameter().getAuthMethod() == AuthenticationMethod.SASL_GSSAPI )
                        {
                            // Request quality of protection
                            switch ( connection.getConnectionParameter().getSaslQop() )
                            {
                                case AUTH:
                                    context.addToEnvironment( "javax.security.sasl.qop", "auth" );
                                    break;
                                case AUTH_INT:
                                    context.addToEnvironment( "javax.security.sasl.qop", "auth-int" );
                                    break;
                                case AUTH_INT_PRIV:
                                    context.addToEnvironment( "javax.security.sasl.qop", "auth-conf" );
                                    break;
                            }

                            // Request mutual authentication
                            if ( connection.getConnectionParameter().isSaslMutualAuthentication() )
                            {
                                context.addToEnvironment( "javax.security.sasl.server.authentication", "true" );
                            }
                            else
                            {
                                context.removeFromEnvironment( "javax.security.sasl.server.authentication" );
                            }

                            // Request cryptographic protection strength
                            switch ( connection.getConnectionParameter().getSaslSecurityStrength() )
                            {
                                case HIGH:
                                    context.addToEnvironment( "javax.security.sasl.strength", "high" );
                                    break;
                                case MEDIUM:
                                    context.addToEnvironment( "javax.security.sasl.strength", "medium" );
                                    break;
                                case LOW:
                                    context.addToEnvironment( "javax.security.sasl.strength", "low" );
                                    break;
                            }
                        }

                        // Bind
                        if ( connection.getConnectionParameter().getAuthMethod() == ConnectionParameter.AuthenticationMethod.SASL_GSSAPI )
                        {
                            // GSSAPI
                            doGssapiBind( this );
                        }
                        else
                        {
                            // no GSSAPI
                            context.addToEnvironment( Context.SECURITY_PRINCIPAL, bindPrincipal );
                            context.addToEnvironment( Context.SECURITY_CREDENTIALS, bindCredentials );

                            if ( connection.getConnectionParameter().getAuthMethod() == ConnectionParameter.AuthenticationMethod.SASL_DIGEST_MD5
                                && StringUtils.isNotEmpty( saslRealm ) )
                            {
                                context.addToEnvironment( JAVA_NAMING_SECURITY_SASL_REALM, saslRealm );
                            }

                            context.reconnect( context.getConnectControls() );
                        }
                    }
                    catch ( NamingException ne )
                    {
                        namingException = ne;
                    }
                }
            };

            runAndMonitor( runnable, monitor );

            if ( runnable.getException() != null )
            {
                throw runnable.getException();
            }
            else if ( context != null )
            {
                // all OK
            }
            else
            {
                throw new NamingException( "???" ); //$NON-NLS-1$
            }
        }
        else
        {
            throw new NamingException( NO_CONNECTION );
        }
    }


    private void doGssapiBind( final InnerRunnable innerRunnable ) throws NamingException
    {
        File configFile = null;
        try
        {
            Preferences preferences = ConnectionCorePlugin.getDefault().getPluginPreferences();
            boolean useKrb5SystemProperties = preferences
                .getBoolean( ConnectionCoreConstants.PREFERENCE_USE_KRB5_SYSTEM_PROPERTIES );
            String krb5LoginModule = preferences.getString( ConnectionCoreConstants.PREFERENCE_KRB5_LOGIN_MODULE );

            if ( !useKrb5SystemProperties )
            {

                // Kerberos Configuration
                switch ( connection.getConnectionParameter().getKrb5Configuration() )
                {
                    case DEFAULT:
                        // nothing
                        System.clearProperty( "java.security.krb5.conf" );
                        break;
                    case FILE:
                        // use specified krb5.conf
                        System.setProperty( "java.security.krb5.conf", connection.getConnectionParameter()
                            .getKrb5ConfigurationFile() );
                        break;
                    case MANUAL:
                        // write manual config parameters to connection specific krb5.conf file
                        String fileName = Utils.getFilenameString( connection.getId() ) + ".krb5.conf";
                        configFile = ConnectionCorePlugin.getDefault().getStateLocation().append( fileName ).toFile();
                        String realm = connection.getConnectionParameter().getKrb5Realm();
                        String host = connection.getConnectionParameter().getKrb5KdcHost();
                        int port = connection.getConnectionParameter().getKrb5KdcPort();
                        StringBuilder sb = new StringBuilder();
                        sb.append( "[libdefaults]" ).append( ConnectionCoreConstants.LINE_SEPARATOR );
                        sb.append( "default_realm = " ).append( realm ).append( ConnectionCoreConstants.LINE_SEPARATOR );
                        sb.append( "[realms]" ).append( ConnectionCoreConstants.LINE_SEPARATOR );
                        sb.append( realm ).append( " = {" ).append( ConnectionCoreConstants.LINE_SEPARATOR );
                        sb.append( "kdc = " ).append( host ).append( ":" ).append( port ).append(
                            ConnectionCoreConstants.LINE_SEPARATOR );
                        sb.append( "}" ).append( ConnectionCoreConstants.LINE_SEPARATOR );
                        try
                        {
                            FileUtils.writeStringToFile( configFile, sb.toString() );
                        }
                        catch ( IOException ioe )
                        {
                            NamingException ne = new NamingException();
                            ne.setRootCause( ioe );
                            throw ne;
                        }
                        System.setProperty( "java.security.krb5.conf", configFile.getAbsolutePath() );
                }

                // Use our custom configuration so we don't need to mess with external configuration
                Configuration.setConfiguration( new InnerConfiguration( krb5LoginModule ) );
            }

            // Gets the TGT, either from native ticket cache or obtain new from KDC
            LoginContext lc = null;
            try
            {
                lc = new LoginContext( this.getClass().getName(), new InnerCallbackHandler() );
                lc.login();
            }
            catch ( LoginException le )
            {
                NamingException ne = new NamingException();
                ne.setRootCause( le );
                throw ne;
            }

            // Login to LDAP server, obtains a service ticket from KDC
            Subject.doAs( lc.getSubject(), new PrivilegedAction<Object>()
            {
                public Object run()
                {
                    try
                    {
                        context.reconnect( context.getConnectControls() );
                    }
                    catch ( NamingException ne )
                    {
                        innerRunnable.namingException = ne;
                    }
                    return null;
                }
            } );
        }
        finally
        {
            // delete temporary config file
            if ( configFile != null && configFile.exists() )
            {
                configFile.delete();
            }
        }
    }


    private void checkConnectionAndRunAndMonitor( final InnerRunnable runnable, final StudioProgressMonitor monitor )
        throws NamingException
    {
        // check connection
        if ( !isConnected || context == null )
        {
            doConnect( monitor );
            doBind( monitor );
        }
        if ( context == null )
        {
            throw new NamingException( NO_CONNECTION );
        }

        // loop for reconnection
        for ( int i = 0; i <= 1; i++ )
        {
            runAndMonitor( runnable, monitor );

            // check reconnection
            if ( i == 0
                && runnable.getException() != null
                && ( ( runnable.getException() instanceof CommunicationException )
                    || ( runnable.getException() instanceof ServiceUnavailableException ) || ( runnable.getException() instanceof InsufficientResourcesException ) ) )
            {

                doConnect( monitor );
                doBind( monitor );
                runnable.reset();
            }
            else
            {
                break;
            }
        }
    }


    private void runAndMonitor( final InnerRunnable runnable, final StudioProgressMonitor monitor )
        throws CancelException
    {
        if ( !monitor.isCanceled() )
        {
            // monitor
            StudioProgressMonitor.CancelListener listener = new StudioProgressMonitor.CancelListener()
            {
                public void cancelRequested( StudioProgressMonitor.CancelEvent event )
                {
                    if ( monitor.isCanceled() )
                    {
                        if ( jobThread != null && jobThread.isAlive() )
                        {
                            jobThread.interrupt();
                        }
                        if ( context != null )
                        {
                            try
                            {
                                context.close();
                            }
                            catch ( NamingException ne )
                            {
                            }
                            isConnected = false;
                            context = null;
                            System.gc();
                        }
                        isConnected = false;
                    }
                }
            };
            monitor.addCancelListener( listener );
            jobThread = Thread.currentThread();

            // run
            try
            {
                // try {
                // Thread.sleep(5000);
                // } catch (InterruptedException e) {
                // System.out.println(System.currentTimeMillis() + ": sleep
                // interrupted!");
                // }
                // System.out.println(System.currentTimeMillis() + ": " +
                // runnable);

                runnable.run();
            }
            finally
            {
                monitor.removeCancelListener( listener );
                jobThread = null;
            }

            if ( monitor.isCanceled() )
            {
                throw new CancelException();
            }
        }
    }

    private final class InnerConfiguration extends Configuration
    {
        private String krb5LoginModule;
        private AppConfigurationEntry[] configList = null;


        public InnerConfiguration( String krb5LoginModule )
        {
            this.krb5LoginModule = krb5LoginModule;
        }


        public AppConfigurationEntry[] getAppConfigurationEntry( String applicationName )
        {
            if ( configList == null )
            {
                HashMap<String, Object> options = new HashMap<String, Object>();

                // TODO: this only works for Sun JVM
                options.put( "refreshKrb5Config", "true" );
                switch ( connection.getConnectionParameter().getKrb5CredentialConfiguration() )
                {
                    case USE_NATIVE:
                        options.put( "useTicketCache", "true" );
                        options.put( "doNotPrompt", "true" );
                        break;
                    case OBTAIN_TGT:
                        options.put( "doNotPrompt", "false" );
                        break;
                }

                configList = new AppConfigurationEntry[1];
                configList[0] = new AppConfigurationEntry( krb5LoginModule, LoginModuleControlFlag.REQUIRED, options );
            }
            return configList;
        }


        public void refresh()
        {
        }
    }

    private final class InnerCallbackHandler implements CallbackHandler
    {
        public void handle( Callback[] callbacks ) throws UnsupportedCallbackException, IOException
        {
            for ( int ii = 0; ii < callbacks.length; ii++ )
            {
                Callback callBack = callbacks[ii];

                if ( callBack instanceof NameCallback )
                {
                    // Handles username callback.
                    NameCallback nameCallback = ( NameCallback ) callBack;
                    nameCallback.setName( bindPrincipal );
                }
                else if ( callBack instanceof PasswordCallback )
                {
                    // Handles password callback.
                    PasswordCallback passwordCallback = ( PasswordCallback ) callBack;
                    passwordCallback.setPassword( bindCredentials.toCharArray() );
                }
                else
                {
                    throw new UnsupportedCallbackException( callBack, "Callback not supported" );
                }
            }
        }
    }

    abstract class InnerRunnable implements Runnable
    {
        protected StudioNamingEnumeration namingEnumeration = null;
        protected NamingException namingException = null;
        protected boolean canceled = false;


        /**
         * Gets the exception.
         *
         * @return the exception
         */
        public NamingException getException()
        {
            return namingException;
        }


        /**
         * Gets the result.
         *
         * @return the result
         */
        public StudioNamingEnumeration getResult()
        {
            return namingEnumeration;
        }


        /**
         * Checks if is canceled.
         *
         * @return true, if is canceled
         */
        public boolean isCanceled()
        {
            return canceled;
        }


        /**
         * Reset.
         */
        public void reset()
        {
            namingEnumeration = null;
            namingException = null;
            canceled = false;
        }
    }


    private List<IJndiLogger> getJndiLoggers()
    {
        return ConnectionCorePlugin.getDefault().getJndiLoggers();
    }


    /**
     * Translates the alias dereferencing method to its JNDI specific string.
     *
     * @param aliasDereferencingMethod the alias dereferencing method
     *
     * @return the JNDI specific alias dereferencing method string
     */
    private String translateDerefAliasMethod( AliasDereferencingMethod aliasDereferencingMethod )
    {
        String m = ALIAS_ALWAYS;

        switch ( aliasDereferencingMethod )
        {
            case NEVER:
                m = ALIAS_NEVER;
                break;
            case ALWAYS:
                m = ALIAS_ALWAYS;
                break;
            case FINDING:
                m = ALIAS_FINDING;
                break;
            case SEARCH:
                m = ALIAS_SEARCHING;
                break;
        }

        return m;
    }


    /**
     * Gets a Name object that is save for JNDI operations.
     * <p>
     * In JNDI we have could use the following classes for names:
     * <ul>
     * <li>DN as String</li>
     * <li>javax.naming.CompositeName</li>
     * <li>javax.naming.ldap.LdapName (since Java5)</li>
     * <li>org.apache.directory.shared.ldap.name.LdapDN</li>
     * </ul>
     * <p>
     * There are some drawbacks when using this classes:
     * <ul>
     * <li>When passing DN as String, JNDI doesn't handle slashes '/' correctly.
     * So we must use a Name object here.</li>
     * <li>With CompositeName we have the same problem with slashes '/'.</li>
     * <li>When using LdapDN from shared-ldap, JNDI uses the toString() method
     * and LdapDN.toString() returns the normalized ATAV, but we need the
     * user provided ATAV.</li>
     * <li>When using LdapName for the empty DN (Root DSE) JNDI _sometimes_ throws
     * an Exception (java.lang.IndexOutOfBoundsException: Posn: -1, Size: 0
     * at javax.naming.ldap.LdapName.getPrefix(LdapName.java:240)).</li>
     * <li>Using LdapDN for the RootDSE doesn't work with Apache Harmony because
     * its JNDI provider only accepts intstances of CompositeName or LdapName.</li>
     * </ul>
     * <p>
     * So we use LdapName as default and the CompositeName for the empty DN.
     *
     * @param name the DN
     *
     * @return the save JNDI name
     *
     * @throws InvalidNameException the invalid name exception
     */
    static Name getSaveJndiName( String name ) throws InvalidNameException
    {
        if ( name == null || StringUtils.isEmpty( name ) ) //$NON-NLS-1$
        {
            return new CompositeName();
        }
        else
        {
            return new LdapName( name );
        }
    }


    /**
     * Gets the referral connection from the given URL.
     *
     * @param url the URL
     * @param monitor the progress monitor
     * @param source the source
     *
     * @return the referral connection
     */
    static Connection getReferralConnection( Referral referral, StudioProgressMonitor monitor, Object source )
    {
        Connection referralConnection = null;
        IReferralHandler referralHandler = ConnectionCorePlugin.getDefault().getReferralHandler();
        if ( referralHandler != null )
        {
            referralConnection = referralHandler.getReferralConnection( referral.getLdapURLs() );

            // open connection if not yet open
            if ( referralConnection != null && !referralConnection.getJNDIConnectionWrapper().isConnected() )
            {
                referralConnection.getJNDIConnectionWrapper().connect( monitor );
                referralConnection.getJNDIConnectionWrapper().bind( monitor );
                for ( IConnectionListener listener : ConnectionCorePlugin.getDefault().getConnectionListeners() )
                {
                    listener.connectionOpened( referralConnection, monitor );
                }
                ConnectionEventRegistry.fireConnectionOpened( referralConnection, source );
            }
        }
        return referralConnection;
    }


    /**
     * Retrieves all referrals from the ReferralException and
     * creates or updates the ReferralsInfo.
     *
     * @param referralException the referral exception
     * @param initialReferralsInfo the initial referrals info, may be null
     *
     * @return the created or updated referrals info
     *
     * @throws NamingException if a loop was encountered.
     */
    static ReferralsInfo handleReferralException( ReferralException referralException,
        ReferralsInfo initialReferralsInfo ) throws NamingException
    {
        if ( initialReferralsInfo == null )
        {
            initialReferralsInfo = new ReferralsInfo();
        }

        Referral referral = handleReferralException( referralException, initialReferralsInfo, null );

        while ( referralException.skipReferral() )
        {
            try
            {
                Context ctx = referralException.getReferralContext();
                ctx.list( StringUtils.EMPTY ); //$NON-NLS-1$
            }
            catch ( NamingException ne )
            {
                if ( ne instanceof ReferralException )
                {
                    if ( ne != referralException )
                    {
                        // what a hack:
                        // if the same exception is throw, we have another URL for the same Referral/SearchResultReference
                        // if another exception is throws, we have a new Referral/SearchResultReference
                        // in the latter case we null out the reference, a new one will be created by handleReferral()
                        referral = null;
                    }
                    referralException = ( ReferralException ) ne;

                    referral = handleReferralException( referralException, initialReferralsInfo, referral );
                }
                else
                {
                    break;
                }
            }
        }

        return initialReferralsInfo;
    }


    private static Referral handleReferralException( ReferralException referralException,
        ReferralsInfo initialReferralsInfo, Referral referral ) throws NamingException
    {
        try
        {
            String info = ( String ) referralException.getReferralInfo();
            String name = referralException.getRemainingName().toString();
            LdapURL url = new LdapURL( info );
            LdapDN dn = new LdapDN( name );

            if ( referral == null )
            {
                referral = initialReferralsInfo.new Referral( dn );
                initialReferralsInfo.addReferral( referral );
            }
            referral.addUrl( url );
        }
        catch ( LdapURLEncodingException e )
        {
        }

        return referral;
    }

}
TOP

Related Classes of org.apache.directory.studio.connection.core.io.jndi.JNDIConnectionWrapper

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.