001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 *
019 */
020package org.apache.directory.ldap.client.api;
021
022
023import static org.apache.directory.api.ldap.model.message.ResultCodeEnum.processResponse;
024
025import java.io.File;
026import java.io.IOException;
027import java.io.OutputStreamWriter;
028import java.io.Writer;
029import java.net.ConnectException;
030import java.net.InetSocketAddress;
031import java.net.SocketAddress;
032import java.nio.channels.UnresolvedAddressException;
033import java.nio.charset.Charset;
034import java.nio.file.Files;
035import java.nio.file.Paths;
036import java.security.PrivilegedExceptionAction;
037import java.util.ArrayList;
038import java.util.HashMap;
039import java.util.Iterator;
040import java.util.List;
041import java.util.Map;
042import java.util.concurrent.ConcurrentHashMap;
043import java.util.concurrent.TimeUnit;
044import java.util.concurrent.atomic.AtomicBoolean;
045import java.util.concurrent.locks.ReentrantLock;
046
047import javax.net.ssl.SSLContext;
048import javax.net.ssl.TrustManager;
049import javax.security.auth.Subject;
050import javax.security.auth.login.Configuration;
051import javax.security.auth.login.LoginContext;
052import javax.security.sasl.Sasl;
053import javax.security.sasl.SaslClient;
054
055import org.apache.directory.api.asn1.DecoderException;
056import org.apache.directory.api.asn1.util.Oid;
057import org.apache.directory.api.i18n.I18n;
058import org.apache.directory.api.ldap.codec.api.BinaryAttributeDetector;
059import org.apache.directory.api.ldap.codec.api.DefaultConfigurableBinaryAttributeDetector;
060import org.apache.directory.api.ldap.codec.api.LdapApiService;
061import org.apache.directory.api.ldap.codec.api.LdapApiServiceFactory;
062import org.apache.directory.api.ldap.codec.api.LdapDecoder;
063import org.apache.directory.api.ldap.codec.api.LdapMessageContainer;
064import org.apache.directory.api.ldap.codec.api.MessageDecorator;
065import org.apache.directory.api.ldap.codec.api.MessageEncoderException;
066import org.apache.directory.api.ldap.codec.api.SchemaBinaryAttributeDetector;
067import org.apache.directory.api.ldap.extras.extended.startTls.StartTlsRequestImpl;
068import org.apache.directory.api.ldap.model.constants.LdapConstants;
069import org.apache.directory.api.ldap.model.constants.SchemaConstants;
070import org.apache.directory.api.ldap.model.cursor.Cursor;
071import org.apache.directory.api.ldap.model.cursor.CursorException;
072import org.apache.directory.api.ldap.model.cursor.EntryCursor;
073import org.apache.directory.api.ldap.model.cursor.SearchCursor;
074import org.apache.directory.api.ldap.model.entry.Attribute;
075import org.apache.directory.api.ldap.model.entry.DefaultEntry;
076import org.apache.directory.api.ldap.model.entry.Entry;
077import org.apache.directory.api.ldap.model.entry.Modification;
078import org.apache.directory.api.ldap.model.entry.ModificationOperation;
079import org.apache.directory.api.ldap.model.entry.Value;
080import org.apache.directory.api.ldap.model.exception.LdapAuthenticationException;
081import org.apache.directory.api.ldap.model.exception.LdapException;
082import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
083import org.apache.directory.api.ldap.model.exception.LdapNoPermissionException;
084import org.apache.directory.api.ldap.model.exception.LdapOperationException;
085import org.apache.directory.api.ldap.model.exception.LdapOtherException;
086import org.apache.directory.api.ldap.model.message.AbandonRequest;
087import org.apache.directory.api.ldap.model.message.AbandonRequestImpl;
088import org.apache.directory.api.ldap.model.message.AddRequest;
089import org.apache.directory.api.ldap.model.message.AddRequestImpl;
090import org.apache.directory.api.ldap.model.message.AddResponse;
091import org.apache.directory.api.ldap.model.message.AliasDerefMode;
092import org.apache.directory.api.ldap.model.message.BindRequest;
093import org.apache.directory.api.ldap.model.message.BindRequestImpl;
094import org.apache.directory.api.ldap.model.message.BindResponse;
095import org.apache.directory.api.ldap.model.message.CompareRequest;
096import org.apache.directory.api.ldap.model.message.CompareRequestImpl;
097import org.apache.directory.api.ldap.model.message.CompareResponse;
098import org.apache.directory.api.ldap.model.message.Control;
099import org.apache.directory.api.ldap.model.message.DeleteRequest;
100import org.apache.directory.api.ldap.model.message.DeleteRequestImpl;
101import org.apache.directory.api.ldap.model.message.DeleteResponse;
102import org.apache.directory.api.ldap.model.message.ExtendedRequest;
103import org.apache.directory.api.ldap.model.message.ExtendedResponse;
104import org.apache.directory.api.ldap.model.message.IntermediateResponse;
105import org.apache.directory.api.ldap.model.message.IntermediateResponseImpl;
106import org.apache.directory.api.ldap.model.message.LdapResult;
107import org.apache.directory.api.ldap.model.message.Message;
108import org.apache.directory.api.ldap.model.message.ModifyDnRequest;
109import org.apache.directory.api.ldap.model.message.ModifyDnRequestImpl;
110import org.apache.directory.api.ldap.model.message.ModifyDnResponse;
111import org.apache.directory.api.ldap.model.message.ModifyRequest;
112import org.apache.directory.api.ldap.model.message.ModifyRequestImpl;
113import org.apache.directory.api.ldap.model.message.ModifyResponse;
114import org.apache.directory.api.ldap.model.message.Request;
115import org.apache.directory.api.ldap.model.message.Response;
116import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
117import org.apache.directory.api.ldap.model.message.SearchRequest;
118import org.apache.directory.api.ldap.model.message.SearchRequestImpl;
119import org.apache.directory.api.ldap.model.message.SearchResultDone;
120import org.apache.directory.api.ldap.model.message.SearchResultEntry;
121import org.apache.directory.api.ldap.model.message.SearchResultReference;
122import org.apache.directory.api.ldap.model.message.SearchScope;
123import org.apache.directory.api.ldap.model.message.UnbindRequest;
124import org.apache.directory.api.ldap.model.message.UnbindRequestImpl;
125import org.apache.directory.api.ldap.model.message.controls.ManageDsaITImpl;
126import org.apache.directory.api.ldap.model.message.controls.OpaqueControl;
127import org.apache.directory.api.ldap.model.message.extended.AddNoDResponse;
128import org.apache.directory.api.ldap.model.message.extended.BindNoDResponse;
129import org.apache.directory.api.ldap.model.message.extended.CompareNoDResponse;
130import org.apache.directory.api.ldap.model.message.extended.DeleteNoDResponse;
131import org.apache.directory.api.ldap.model.message.extended.ExtendedNoDResponse;
132import org.apache.directory.api.ldap.model.message.extended.ModifyDnNoDResponse;
133import org.apache.directory.api.ldap.model.message.extended.ModifyNoDResponse;
134import org.apache.directory.api.ldap.model.message.extended.NoticeOfDisconnect;
135import org.apache.directory.api.ldap.model.message.extended.SearchNoDResponse;
136import org.apache.directory.api.ldap.model.name.Dn;
137import org.apache.directory.api.ldap.model.name.Rdn;
138import org.apache.directory.api.ldap.model.schema.AttributeType;
139import org.apache.directory.api.ldap.model.schema.ObjectClass;
140import org.apache.directory.api.ldap.model.schema.SchemaManager;
141import org.apache.directory.api.ldap.model.schema.parsers.OpenLdapSchemaParser;
142import org.apache.directory.api.ldap.model.schema.registries.Registries;
143import org.apache.directory.api.ldap.model.schema.registries.SchemaLoader;
144import org.apache.directory.api.ldap.schema.manager.impl.DefaultSchemaManager;
145import org.apache.directory.api.util.Network;
146import org.apache.directory.api.util.StringConstants;
147import org.apache.directory.api.util.Strings;
148import org.apache.directory.ldap.client.api.callback.SaslCallbackHandler;
149import org.apache.directory.ldap.client.api.exception.InvalidConnectionException;
150import org.apache.directory.ldap.client.api.future.AddFuture;
151import org.apache.directory.ldap.client.api.future.BindFuture;
152import org.apache.directory.ldap.client.api.future.CompareFuture;
153import org.apache.directory.ldap.client.api.future.DeleteFuture;
154import org.apache.directory.ldap.client.api.future.ExtendedFuture;
155import org.apache.directory.ldap.client.api.future.HandshakeFuture;
156import org.apache.directory.ldap.client.api.future.ModifyDnFuture;
157import org.apache.directory.ldap.client.api.future.ModifyFuture;
158import org.apache.directory.ldap.client.api.future.ResponseFuture;
159import org.apache.directory.ldap.client.api.future.SearchFuture;
160import org.apache.mina.core.filterchain.IoFilter;
161import org.apache.mina.core.future.CloseFuture;
162import org.apache.mina.core.future.ConnectFuture;
163import org.apache.mina.core.future.IoFuture;
164import org.apache.mina.core.future.IoFutureListener;
165import org.apache.mina.core.future.WriteFuture;
166import org.apache.mina.core.service.IoConnector;
167import org.apache.mina.core.session.IoSession;
168import org.apache.mina.filter.FilterEvent;
169import org.apache.mina.filter.codec.ProtocolCodecFilter;
170import org.apache.mina.filter.codec.ProtocolEncoderException;
171import org.apache.mina.filter.ssl.SslEvent;
172import org.apache.mina.filter.ssl.SslFilter;
173import org.apache.mina.transport.socket.SocketSessionConfig;
174import org.apache.mina.transport.socket.nio.NioSocketConnector;
175import org.slf4j.Logger;
176import org.slf4j.LoggerFactory;
177
178
179/**
180 * This class is the base for every operations sent or received to and
181 * from a LDAP server.
182 *
183 * A connection instance is necessary to send requests to the server. The connection
184 * is valid until either the client closes it, the server closes it or the
185 * client does an unbind.
186 *
187 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
188 */
189public class LdapNetworkConnection extends AbstractLdapConnection implements LdapAsyncConnection
190{
191    /** logger for reporting errors that might not be handled properly upstream */
192    private static final Logger LOG = LoggerFactory.getLogger( LdapNetworkConnection.class );
193
194    /** The timeout used for response we are waiting for */
195    private long timeout = LdapConnectionConfig.DEFAULT_TIMEOUT;
196
197    /** configuration object for the connection */
198    private LdapConnectionConfig config;
199    
200    /** The Sockect configuratio */
201    private SocketSessionConfig connectionConfig;
202
203    /** The connector open with the remote server */
204    private IoConnector connector;
205
206    /** A mutex used to avoid a double close of the connector */
207    private ReentrantLock connectorMutex = new ReentrantLock();
208
209    /**
210     * The created session, created when we open a connection with
211     * the Ldap server.
212     */
213    private IoSession ldapSession;
214
215    /** a map to hold the ResponseFutures for all operations */
216    private Map<Integer, ResponseFuture<? extends Response>> futureMap = new ConcurrentHashMap<>();
217
218    /** list of controls supported by the server */
219    private List<String> supportedControls;
220
221    /** The ROOT DSE entry */
222    private Entry rootDse;
223
224    /** A flag indicating that the BindRequest has been issued and successfully authenticated the user */
225    private AtomicBoolean authenticated = new AtomicBoolean( false );
226
227    /** A flag indicating that the connection is connected or not */
228    private AtomicBoolean connected = new AtomicBoolean( false );
229
230    /** a list of listeners interested in getting notified when the
231     *  connection's session gets closed cause of network issues
232     */
233    private List<ConnectionClosedEventListener> conCloseListeners;
234
235    /** The Ldap codec protocol filter */
236    private IoFilter ldapProtocolFilter = new ProtocolCodecFilter( codec.getProtocolCodecFactory() );
237
238    /** the SslFilter key */
239    private static final String SSL_FILTER_KEY = "sslFilter";
240
241    /** The exception stored in the session if we've got one */
242    private static final String EXCEPTION_KEY = "sessionException";
243    
244    /** A future used to block any action until the handhake is completed */
245    private HandshakeFuture handshakeFuture;
246
247    // ~~~~~~~~~~~~~~~~~ common error messages ~~~~~~~~~~~~~~~~~~~~~~~~~~
248
249    static final String TIME_OUT_ERROR = "TimeOut occurred";
250
251    static final String NO_RESPONSE_ERROR = "The response queue has been emptied, no response was found.";
252    
253    
254    //------------------------- The constructors --------------------------//
255    /**
256     * Create a new instance of a LdapConnection on localhost,
257     * port 389.
258     */
259    public LdapNetworkConnection()
260    {
261        this( null, -1, false );
262    }
263
264
265    /**
266     *
267     * Creates a new instance of LdapConnection with the given connection configuration.
268     *
269     * @param config the configuration of the LdapConnection
270     */
271    public LdapNetworkConnection( LdapConnectionConfig config )
272    {
273        this( config, LdapApiServiceFactory.getSingleton() );
274    }
275
276
277    /**
278     * Creates a new LdapNetworkConnection instance
279     * 
280     * @param config The configuration to use
281     * @param ldapApiService The LDAP API Service to use
282     */
283    public LdapNetworkConnection( LdapConnectionConfig config, LdapApiService ldapApiService )
284    {
285        super( ldapApiService );
286        this.config = config;
287
288        if ( config.getBinaryAttributeDetector() == null )
289        {
290            config.setBinaryAttributeDetector( new DefaultConfigurableBinaryAttributeDetector() );
291        }
292    }
293
294
295    /**
296     * Create a new instance of a LdapConnection on localhost,
297     * port 389 if the SSL flag is off, or 636 otherwise.
298     *
299     * @param useSsl A flag to tell if it's a SSL connection or not.
300     */
301    public LdapNetworkConnection( boolean useSsl )
302    {
303        this( null, -1, useSsl );
304    }
305
306
307    /**
308     * Creates a new LdapNetworkConnection instance
309     * 
310     * @param useSsl If we are going to create a secure connection or not
311     * @param ldapApiService The LDAP API Service to use
312     */
313    public LdapNetworkConnection( boolean useSsl, LdapApiService ldapApiService )
314    {
315        this( null, -1, useSsl, ldapApiService );
316    }
317
318
319    /**
320     * Create a new instance of a LdapConnection on a given
321     * server, using the default port (389).
322     *
323     * @param server The server we want to be connected to. If null or empty,
324     * we will default to LocalHost.
325     */
326    public LdapNetworkConnection( String server )
327    {
328        this( server, -1, false );
329    }
330
331
332    /**
333     * Creates a new LdapNetworkConnection instance
334     * 
335     * @param server The server we want to be connected to. If null or empty,
336     * we will default to LocalHost.
337     * @param ldapApiService The LDAP API Service to use
338     */
339    public LdapNetworkConnection( String server, LdapApiService ldapApiService )
340    {
341        this( server, -1, false, ldapApiService );
342    }
343
344
345    /**
346     * Create a new instance of a LdapConnection on a given
347     * server, using the default port (389) if the SSL flag
348     * is off, or 636 otherwise.
349     *
350     * @param server The server we want to be connected to. If null or empty,
351     * we will default to LocalHost.
352     * @param useSsl A flag to tell if it's a SSL connection or not.
353     */
354    public LdapNetworkConnection( String server, boolean useSsl )
355    {
356        this( server, -1, useSsl );
357    }
358
359
360    /**
361     * Creates a new LdapNetworkConnection instance
362     * 
363     * @param server The server we want to be connected to. If null or empty,
364     * we will default to LocalHost.
365     * @param useSsl A flag to tell if it's a SSL connection or not.
366     * @param ldapApiService The LDAP API Service to use
367     */
368    public LdapNetworkConnection( String server, boolean useSsl, LdapApiService ldapApiService )
369    {
370        this( server, -1, useSsl, ldapApiService );
371    }
372
373
374    /**
375     * Create a new instance of a LdapConnection on a
376     * given server and a given port. We don't use ssl.
377     *
378     * @param server The server we want to be connected to. If null or empty,
379     * we will default to LocalHost.
380     * @param port The port the server is listening on
381     */
382    public LdapNetworkConnection( String server, int port )
383    {
384        this( server, port, false );
385    }
386
387
388    /**
389     * Create a new instance of a LdapConnection on a
390     * given server and a given port. We don't use ssl.
391     *
392     * @param server The server we want to be connected to. If null or empty,
393     * we will default to LocalHost.
394     * @param port The port the server is listening on
395     * @param ldapApiService The LDAP API Service to use
396     */
397    public LdapNetworkConnection( String server, int port, LdapApiService ldapApiService )
398    {
399        this( server, port, false, ldapApiService );
400    }
401
402
403    /**
404     * Create a new instance of a LdapConnection on a given
405     * server, and a give port. We set the SSL flag accordingly
406     * to the last parameter.
407     *
408     * @param server The server we want to be connected to. If null or empty,
409     * we will default to LocalHost.
410     * @param port The port the server is listening to
411     * @param useSsl A flag to tell if it's a SSL connection or not.
412     */
413    public LdapNetworkConnection( String server, int port, boolean useSsl )
414    {
415        this( buildConfig( server, port, useSsl ) );
416    }
417
418
419    /**
420     * Create a new instance of a LdapConnection on a given
421     * server, and a give port. This SSL connection will use the provided
422     * TrustManagers
423     *
424     * @param server The server we want to be connected to. If null or empty,
425     * we will default to LocalHost.
426     * @param port The port the server is listening to
427     * @param trustManagers The TrustManager to use
428     */
429    public LdapNetworkConnection( String server, int port, TrustManager... trustManagers )
430    {
431        this( buildConfig( server, port, true ) );
432        
433        config.setTrustManagers( trustManagers );
434    }
435
436
437    /**
438     * Create a new instance of a LdapConnection on a
439     * given server and a given port. We don't use ssl.
440     *
441     * @param server The server we want to be connected to. If null or empty,
442     * we will default to LocalHost.
443     * @param port The port the server is listening on
444     * @param useSsl A flag to tell if it's a SSL connection or not.
445     * @param ldapApiService The LDAP API Service to use
446     */
447    public LdapNetworkConnection( String server, int port, boolean useSsl, LdapApiService ldapApiService )
448    {
449        this( buildConfig( server, port, useSsl ), ldapApiService );
450    }
451
452
453    //--------------------------- Helper methods ---------------------------//
454    /**
455     * {@inheritDoc}
456     */
457    @Override
458    public boolean isConnected()
459    {
460        return ( ldapSession != null ) && connected.get() && !ldapSession.isClosing();
461    }
462
463
464    /**
465     * {@inheritDoc}
466     */
467    @Override
468    public boolean isAuthenticated()
469    {
470        return isConnected() && authenticated.get();
471    }
472
473
474    /**
475     * Tells if the connection is using a secured channel
476     * 
477     * @return <tt>true</tt> if the session is using a secured channel
478     */
479    public boolean isSecured()
480    {
481        return isConnected() && ldapSession.isSecured();
482    }
483
484
485    /**
486     * Check that a session is valid, ie we can send requests to the
487     * server
488     *
489     * @throws Exception If the session is not valid
490     */
491    private void checkSession() throws InvalidConnectionException
492    {
493        if ( ldapSession == null )
494        {
495            throw new InvalidConnectionException( "Cannot connect on the server, the connection is null" );
496        }
497
498        if ( !connected.get() )
499        {
500            throw new InvalidConnectionException( "Cannot connect on the server, the connection is invalid" );
501        }
502    }
503
504
505    private void addToFutureMap( int messageId, ResponseFuture<? extends Response> future )
506    {
507        LOG.debug( "Adding <" + messageId + ", " + future.getClass().getName() + ">" );
508        futureMap.put( messageId, future );
509    }
510
511
512    private ResponseFuture<? extends Response> getFromFutureMap( int messageId )
513    {
514        ResponseFuture<? extends Response> future = futureMap.remove( messageId );
515
516        if ( future != null )
517        {
518            LOG.debug( "Removing <" + messageId + ", " + future.getClass().getName() + ">" );
519        }
520
521        return future;
522    }
523
524
525    private ResponseFuture<? extends Response> peekFromFutureMap( int messageId )
526    {
527        ResponseFuture<? extends Response> future = futureMap.get( messageId );
528
529        // future can be null if there was a abandon operation on that messageId
530        if ( future != null )
531        {
532            LOG.debug( "Getting <" + messageId + ", " + future.getClass().getName() + ">" );
533        }
534
535        return future;
536    }
537
538
539    /**
540     * Get the largest timeout from the search time limit and the connection
541     * timeout.
542     * 
543     * @param connectionTimoutInMS Connection timeout
544     * @param searchTimeLimitInSeconds Search timeout
545     * @return The largest timeout
546     */
547    public long getTimeout( long connectionTimoutInMS, int searchTimeLimitInSeconds )
548    {
549        if ( searchTimeLimitInSeconds < 0 )
550        {
551            return connectionTimoutInMS;
552        }
553        else if ( searchTimeLimitInSeconds == 0 )
554        {
555            if ( config.getTimeout() == 0 )
556            {
557                return Long.MAX_VALUE;
558            }
559            else
560            {
561                return config.getTimeout();
562            }
563        }
564        else
565        {
566            long searchTimeLimitInMS = searchTimeLimitInSeconds * 1000L;
567            return Math.max( searchTimeLimitInMS, connectionTimoutInMS );
568        }
569    }
570
571
572    private static LdapConnectionConfig buildConfig( String server, int port, boolean useSsl )
573    {
574        LdapConnectionConfig config = new LdapConnectionConfig();
575        config.setUseSsl( useSsl );
576
577        if ( port != -1 )
578        {
579            config.setLdapPort( port );
580        }
581        else
582        {
583            if ( useSsl )
584            {
585                config.setLdapPort( config.getDefaultLdapsPort() );
586            }
587            else
588            {
589                config.setLdapPort( config.getDefaultLdapPort() );
590            }
591        }
592
593        // Default to localhost if null
594        if ( Strings.isEmpty( server ) )
595        {
596            config.setLdapHost( Network.LOOPBACK_HOSTNAME );
597            
598        }
599        else
600        {
601            config.setLdapHost( server );
602        }
603
604        config.setBinaryAttributeDetector( new DefaultConfigurableBinaryAttributeDetector() );
605
606        return config;
607    }
608
609
610    /**
611     * Create the connector
612     */
613    private void createConnector() throws LdapException
614    {
615        // Use only one thread inside the connector
616        connector = new NioSocketConnector( 1 );
617        
618        if ( connectionConfig != null )
619        {
620            ( ( SocketSessionConfig ) connector.getSessionConfig() ).setAll( connectionConfig );
621        }
622        else
623        {
624            ( ( SocketSessionConfig ) connector.getSessionConfig() ).setReuseAddress( true );
625        }
626
627        // Add the codec to the chain
628        connector.getFilterChain().addLast( "ldapCodec", ldapProtocolFilter );
629
630        // If we use SSL, we have to add the SslFilter to the chain
631        if ( config.isUseSsl() )
632        {
633            addSslFilter();
634        }
635
636        // Inject the protocolHandler
637        connector.setHandler( this );
638    }
639
640
641    //-------------------------- The methods ---------------------------//
642    /**
643     * {@inheritDoc}
644     */
645    @Override
646    public boolean connect() throws LdapException
647    {
648        if ( ( ldapSession != null ) && connected.get() )
649        {
650            // No need to connect if we already have a connected session
651            return true;
652        }
653        
654        // Create the connector if needed
655        if ( connector == null )
656        {
657            createConnector();
658        }
659
660        // Build the connection address
661        SocketAddress address = new InetSocketAddress( config.getLdapHost(), config.getLdapPort() );
662
663        // And create the connection future
664        timeout = config.getTimeout();
665        long maxRetry = System.currentTimeMillis() + timeout;
666        ConnectFuture connectionFuture = null;
667
668        while ( maxRetry > System.currentTimeMillis() )
669        {
670            connectionFuture = connector.connect( address );
671
672            if ( config.isUseSsl() )
673            {
674                try
675                {
676                    boolean isSecured = handshakeFuture.get( timeout, TimeUnit.MILLISECONDS );
677                
678                    if ( !isSecured )
679                    {
680                        throw new LdapOperationException( ResultCodeEnum.OTHER, I18n.err( I18n.ERR_4100_TLS_HANDSHAKE_ERROR ) );
681                    }
682                }
683                catch ( Exception e )
684                {
685                    String msg = "Failed to initialize the SSL context";
686                    LOG.error( msg, e );
687                    throw new LdapException( msg, e );
688                }
689            }
690
691            boolean result = false;
692
693            // Wait until it's established
694            try
695            {
696                result = connectionFuture.await( timeout );
697            }
698            catch ( InterruptedException e )
699            {
700                connector.dispose();
701                connector = null;
702                LOG.debug( "Interrupted while waiting for connection to establish with server {}:{}",
703                    config.getLdapHost(),
704                    config.getLdapPort(), e );
705                throw new LdapOtherException( e.getMessage(), e );
706            }
707            finally
708            {
709                if ( result )
710                {
711                    boolean isConnected = connectionFuture.isConnected();
712
713                    if ( !isConnected )
714                    {
715                        Throwable connectionException = connectionFuture.getException();
716
717                        if ( ( connectionException instanceof ConnectException )
718                            || ( connectionException instanceof UnresolvedAddressException ) )
719                        {
720                            // No need to wait
721                            // We know that there was a permanent error such as "connection refused".
722                            LOG.debug( "------>> Connection error: {}", connectionFuture.getException().getMessage() );
723                        }
724
725                        LOG.debug( "------>>   Cannot get the connection... Retrying" );
726
727                        // Wait 500 ms and retry
728                        try
729                        {
730                            Thread.sleep( 500 );
731                        }
732                        catch ( InterruptedException e )
733                        {
734                            connector = null;
735                            LOG.debug( "Interrupted while waiting for connection to establish with server {}:{}",
736                                config.getLdapHost(),
737                                config.getLdapPort(), e );
738                            throw new LdapOtherException( e.getMessage(), e );
739                        }
740                    }
741                    else
742                    {
743                        break;
744                    }
745                }
746            }
747        }
748
749        if ( connectionFuture == null )
750        {
751            connector.dispose();
752            throw new InvalidConnectionException( "Cannot connect" );
753        }
754
755        boolean isConnected = connectionFuture.isConnected();
756
757        if ( !isConnected )
758        {
759            // disposing connector if not connected
760            try
761            {
762                close();
763            }
764            catch ( IOException ioe )
765            {
766                // Nothing to do
767            }
768
769            Throwable e = connectionFuture.getException();
770
771            if ( e != null )
772            {
773                StringBuilder message = new StringBuilder( "Cannot connect to the server: " );
774
775                // Special case for UnresolvedAddressException
776                // (most of the time no message is associated with this exception)
777                if ( ( e instanceof UnresolvedAddressException ) && ( e.getMessage() == null ) )
778                {
779                    message.append( "Hostname '" );
780                    message.append( config.getLdapHost() );
781                    message.append( "' could not be resolved." );
782                    throw new InvalidConnectionException( message.toString(), e );
783                }
784
785                // Default case
786                message.append( e.getMessage() );
787                throw new InvalidConnectionException( message.toString(), e );
788            }
789
790            return false;
791        }
792
793        // Get the close future for this session
794        CloseFuture closeFuture = connectionFuture.getSession().getCloseFuture();
795
796        // Add a listener to close the session in the session.
797        closeFuture.addListener( new IoFutureListener<IoFuture>()
798        {
799            @Override
800            public void operationComplete( IoFuture future )
801            {
802                // Process all the waiting operations and cancel them
803                LOG.debug( "received a NoD, closing everything" );
804
805                for ( Map.Entry<Integer, ResponseFuture<? extends Response>> entry : futureMap.entrySet() )
806                {
807                    int messageId = entry.getKey();
808                    ResponseFuture<?> responseFuture = entry.getValue();
809                    LOG.debug( "closing {}", responseFuture );
810
811                    responseFuture.cancel();
812
813                    try
814                    {
815                        if ( responseFuture instanceof AddFuture )
816                        {
817                            ( ( AddFuture ) responseFuture ).set( AddNoDResponse.PROTOCOLERROR );
818                        }
819                        else if ( responseFuture instanceof BindFuture )
820                        {
821                            ( ( BindFuture ) responseFuture ).set( BindNoDResponse.PROTOCOLERROR );
822                        }
823                        else if ( responseFuture instanceof CompareFuture )
824                        {
825                            ( ( CompareFuture ) responseFuture ).set( CompareNoDResponse.PROTOCOLERROR );
826                        }
827                        else if ( responseFuture instanceof DeleteFuture )
828                        {
829                            ( ( DeleteFuture ) responseFuture ).set( DeleteNoDResponse.PROTOCOLERROR );
830                        }
831                        else if ( responseFuture instanceof ExtendedFuture )
832                        {
833                            ( ( ExtendedFuture ) responseFuture ).set( ExtendedNoDResponse.PROTOCOLERROR );
834                        }
835                        else if ( responseFuture instanceof ModifyFuture )
836                        {
837                            ( ( ModifyFuture ) responseFuture ).set( ModifyNoDResponse.PROTOCOLERROR );
838                        }
839                        else if ( responseFuture instanceof ModifyDnFuture )
840                        {
841                            ( ( ModifyDnFuture ) responseFuture ).set( ModifyDnNoDResponse.PROTOCOLERROR );
842                        }
843                        else if ( responseFuture instanceof SearchFuture )
844                        {
845                            ( ( SearchFuture ) responseFuture ).set( SearchNoDResponse.PROTOCOLERROR );
846                        }
847                    }
848                    catch ( InterruptedException e )
849                    {
850                        LOG.error( "Error while processing the NoD for {}", responseFuture );
851                    }
852
853                    futureMap.remove( messageId );
854                }
855
856                futureMap.clear();
857            }
858        } );
859
860        // Get back the session
861        ldapSession = connectionFuture.getSession();
862
863        // Store the container into the session if we don't have one
864        @SuppressWarnings("unchecked")
865        LdapMessageContainer<MessageDecorator<? extends Message>> container =
866            ( LdapMessageContainer<MessageDecorator<? extends Message>> ) ldapSession
867                .getAttribute( LdapDecoder.MESSAGE_CONTAINER_ATTR );
868
869        if ( container != null )
870        {
871            if ( ( schemaManager != null ) && !( container.getBinaryAttributeDetector() instanceof SchemaBinaryAttributeDetector ) )
872            {
873                container.setBinaryAttributeDetector( new SchemaBinaryAttributeDetector( schemaManager ) );
874            }
875        }
876        else
877        {
878            BinaryAttributeDetector atDetector = new DefaultConfigurableBinaryAttributeDetector();
879
880            if ( schemaManager != null )
881            {
882                atDetector = new SchemaBinaryAttributeDetector( schemaManager );
883            }
884
885            ldapSession.setAttribute( LdapDecoder.MESSAGE_CONTAINER_ATTR,
886                new LdapMessageContainer<MessageDecorator<? extends Message>>( codec, atDetector ) );
887        }
888
889        // Initialize the MessageId
890        messageId.set( 0 );
891
892        // And return
893        return true;
894    }
895
896
897    /**
898     * {@inheritDoc}
899     */
900    @Override
901    public void close() throws IOException
902    {
903        // Close the session
904        if ( ( ldapSession != null ) && connected.get() )
905        {
906            ldapSession.closeNow();
907        }
908
909        connected.set( false );
910
911        // And close the connector if it has been created locally
912        // Release the connector
913        connectorMutex.lock();
914
915        try
916        {
917            if ( connector != null )
918            {
919                connector.dispose();
920                connector = null;
921            }
922        }
923        finally
924        {
925            connectorMutex.unlock();
926        }
927
928        // Reset the messageId
929        messageId.set( 0 );
930    }
931
932
933    //------------------------ The LDAP operations ------------------------//
934    // Add operations                                                      //
935    //---------------------------------------------------------------------//
936    /**
937     * {@inheritDoc}
938     */
939    @Override
940    public void add( Entry entry ) throws LdapException
941    {
942        if ( entry == null )
943        {
944            String msg = "Cannot add an empty entry";
945            LOG.debug( msg );
946            throw new IllegalArgumentException( msg );
947        }
948
949        AddRequest addRequest = new AddRequestImpl();
950        addRequest.setEntry( entry );
951
952        AddResponse addResponse = add( addRequest );
953
954        processResponse( addResponse );
955    }
956
957
958    /**
959     * {@inheritDoc}
960     */
961    @Override
962    public AddFuture addAsync( Entry entry ) throws LdapException
963    {
964        if ( entry == null )
965        {
966            String msg = "Cannot add null entry";
967            LOG.debug( msg );
968            throw new IllegalArgumentException( msg );
969        }
970
971        AddRequest addRequest = new AddRequestImpl();
972        addRequest.setEntry( entry );
973
974        return addAsync( addRequest );
975    }
976
977
978    /**
979     * {@inheritDoc}
980     */
981    @Override
982    public AddResponse add( AddRequest addRequest ) throws LdapException
983    {
984        if ( addRequest == null )
985        {
986            String msg = "Cannot process a null addRequest";
987            LOG.debug( msg );
988            throw new IllegalArgumentException( msg );
989        }
990
991        if ( addRequest.getEntry() == null )
992        {
993            String msg = "Cannot add a null entry";
994            LOG.debug( msg );
995            throw new IllegalArgumentException( msg );
996        }
997
998        AddFuture addFuture = addAsync( addRequest );
999
1000        // Get the result from the future
1001        try
1002        {
1003            // Read the response, waiting for it if not available immediately
1004            // Get the response, blocking
1005            AddResponse addResponse = addFuture.get( timeout, TimeUnit.MILLISECONDS );
1006
1007            if ( addResponse == null )
1008            {
1009                // We didn't received anything : this is an error
1010                LOG.error( "Add failed : timeout occurred" );
1011                throw new LdapException( TIME_OUT_ERROR );
1012            }
1013
1014            if ( addResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
1015            {
1016                // Everything is fine, return the response
1017                LOG.debug( "Add successful : {}", addResponse );
1018            }
1019            else
1020            {
1021                // We have had an error
1022                LOG.debug( "Add failed : {}", addResponse );
1023            }
1024
1025            return addResponse;
1026        }
1027        catch ( Exception ie )
1028        {
1029            // Catch all other exceptions
1030            LOG.error( NO_RESPONSE_ERROR, ie );
1031
1032            // Send an abandon request
1033            if ( !addFuture.isCancelled() )
1034            {
1035                abandon( addRequest.getMessageId() );
1036            }
1037
1038            throw new LdapException( NO_RESPONSE_ERROR, ie );
1039        }
1040    }
1041
1042
1043    /**
1044     * {@inheritDoc}
1045     */
1046    @Override
1047    public AddFuture addAsync( AddRequest addRequest ) throws LdapException
1048    {
1049        if ( addRequest == null )
1050        {
1051            String msg = "Cannot process a null addRequest";
1052            LOG.debug( msg );
1053            throw new IllegalArgumentException( msg );
1054        }
1055
1056        if ( addRequest.getEntry() == null )
1057        {
1058            String msg = "Cannot add a null entry";
1059            LOG.debug( msg );
1060            throw new IllegalArgumentException( msg );
1061        }
1062
1063        // try to connect, if we aren't already connected.
1064        connect();
1065
1066        checkSession();
1067
1068        int newId = messageId.incrementAndGet();
1069
1070        addRequest.setMessageId( newId );
1071        AddFuture addFuture = new AddFuture( this, newId );
1072        addToFutureMap( newId, addFuture );
1073
1074        // Send the request to the server
1075        writeRequest( addRequest );
1076
1077        // Ok, done return the future
1078        return addFuture;
1079    }
1080
1081
1082    //------------------------ The LDAP operations ------------------------//
1083
1084    /**
1085     * {@inheritDoc}
1086     */
1087    @Override
1088    public void abandon( int messageId )
1089    {
1090        if ( messageId < 0 )
1091        {
1092            String msg = "Cannot abandon a negative message ID";
1093            LOG.debug( msg );
1094            throw new IllegalArgumentException( msg );
1095        }
1096
1097        AbandonRequest abandonRequest = new AbandonRequestImpl();
1098        abandonRequest.setAbandoned( messageId );
1099
1100        abandonInternal( abandonRequest );
1101    }
1102
1103
1104    /**
1105     * {@inheritDoc}
1106     */
1107    @Override
1108    public void abandon( AbandonRequest abandonRequest )
1109    {
1110        if ( abandonRequest == null )
1111        {
1112            String msg = "Cannot process a null abandonRequest";
1113            LOG.debug( msg );
1114            throw new IllegalArgumentException( msg );
1115        }
1116
1117        abandonInternal( abandonRequest );
1118    }
1119
1120
1121    /**
1122     * Internal AbandonRequest handling
1123     */
1124    private void abandonInternal( AbandonRequest abandonRequest )
1125    {
1126        LOG.debug( "Sending request \n{}", abandonRequest );
1127
1128        int newId = messageId.incrementAndGet();
1129        abandonRequest.setMessageId( newId );
1130
1131        // Send the request to the server
1132        ldapSession.write( abandonRequest );
1133
1134        // remove the associated listener if any
1135        int abandonId = abandonRequest.getAbandoned();
1136
1137        ResponseFuture<? extends Response> rf = getFromFutureMap( abandonId );
1138
1139        // if the listener is not null, this is a async operation and no need to
1140        // send cancel signal on future, sending so will leave a dangling poision object in the corresponding queue
1141        // this is a sync operation send cancel signal to the corresponding ResponseFuture
1142        if ( rf != null )
1143        {
1144            LOG.debug( "sending cancel signal to future" );
1145            rf.cancel( true );
1146        }
1147        else
1148        {
1149            // this shouldn't happen
1150            LOG
1151                .warn(
1152                    "There is no future associated with operation message ID {}, the operation has been completed.",
1153                    abandonId );
1154        }
1155    }
1156
1157
1158    /**
1159     * {@inheritDoc}
1160     */
1161    @Override
1162    public void bind() throws LdapException
1163    {
1164        LOG.debug( "Bind request" );
1165
1166        // Create the BindRequest
1167        BindRequest bindRequest = createBindRequest( config.getName(), Strings.getBytesUtf8( config.getCredentials() ) );
1168
1169        BindResponse bindResponse = bind( bindRequest );
1170
1171        processResponse( bindResponse );
1172    }
1173
1174
1175    /**
1176     * {@inheritDoc}
1177     */
1178    @Override
1179    public void anonymousBind() throws LdapException
1180    {
1181        LOG.debug( "Anonymous Bind request" );
1182
1183        // Create the BindRequest
1184        BindRequest bindRequest = createBindRequest( StringConstants.EMPTY, Strings.EMPTY_BYTES );
1185
1186        BindResponse bindResponse = bind( bindRequest );
1187
1188        processResponse( bindResponse );
1189    }
1190
1191
1192    /**
1193     * {@inheritDoc}
1194     */
1195    @Override
1196    public BindFuture bindAsync() throws LdapException
1197    {
1198        LOG.debug( "Asynchronous Bind request" );
1199
1200        // Create the BindRequest
1201        BindRequest bindRequest = createBindRequest( config.getName(), Strings.getBytesUtf8( config.getCredentials() ) );
1202
1203        return bindAsync( bindRequest );
1204    }
1205
1206
1207    /**
1208     * {@inheritDoc}
1209     */
1210    @Override
1211    public BindFuture anonymousBindAsync() throws LdapException
1212    {
1213        LOG.debug( "Anonymous asynchronous Bind request" );
1214
1215        // Create the BindRequest
1216        BindRequest bindRequest = createBindRequest( StringConstants.EMPTY, Strings.EMPTY_BYTES );
1217
1218        return bindAsync( bindRequest );
1219    }
1220
1221
1222    /**
1223     * Asynchronous unauthenticated authentication bind
1224     *
1225     * @param name The name we use to authenticate the user. It must be a
1226     * valid Dn
1227     * @return The BindResponse LdapResponse
1228     * @throws LdapException if some error occurred
1229     */
1230    public BindFuture bindAsync( String name ) throws LdapException
1231    {
1232        LOG.debug( "Bind request : {}", name );
1233
1234        // Create the BindRequest
1235        BindRequest bindRequest = createBindRequest( name, Strings.EMPTY_BYTES );
1236
1237        return bindAsync( bindRequest );
1238    }
1239
1240
1241    /**
1242     * {@inheritDoc}
1243     */
1244    @Override
1245    public BindFuture bindAsync( String name, String credentials ) throws LdapException
1246    {
1247        LOG.debug( "Bind request : {}", name );
1248
1249        // The password must not be empty or null
1250        if ( Strings.isEmpty( credentials ) && Strings.isNotEmpty( name ) )
1251        {
1252            LOG.debug( "The password is missing" );
1253            throw new LdapAuthenticationException( "The password is missing" );
1254        }
1255
1256        // Create the BindRequest
1257        BindRequest bindRequest = createBindRequest( name, Strings.getBytesUtf8( credentials ) );
1258
1259        return bindAsync( bindRequest );
1260    }
1261
1262
1263    /**
1264     * Asynchronous unauthenticated authentication Bind on a server.
1265     *
1266     * @param name The name we use to authenticate the user. It must be a
1267     * valid Dn
1268     * @return The BindResponse LdapResponse
1269     * @throws LdapException if some error occurred
1270     */
1271    public BindFuture bindAsync( Dn name ) throws LdapException
1272    {
1273        LOG.debug( "Bind request : {}", name );
1274
1275        // Create the BindRequest
1276        BindRequest bindRequest = createBindRequest( name, Strings.EMPTY_BYTES );
1277
1278        return bindAsync( bindRequest );
1279    }
1280
1281
1282    /**
1283     * {@inheritDoc}
1284     */
1285    @Override
1286    public BindFuture bindAsync( Dn name, String credentials ) throws LdapException
1287    {
1288        LOG.debug( "Bind request : {}", name );
1289
1290        // The password must not be empty or null
1291        if ( Strings.isEmpty( credentials ) && ( !Dn.EMPTY_DN.equals( name ) ) )
1292        {
1293            LOG.debug( "The password is missing" );
1294            throw new LdapAuthenticationException( "The password is missing" );
1295        }
1296
1297        // Create the BindRequest
1298        BindRequest bindRequest = createBindRequest( name, Strings.getBytesUtf8( credentials ) );
1299
1300        return bindAsync( bindRequest );
1301    }
1302
1303
1304    /**
1305     * {@inheritDoc}
1306     */
1307    @Override
1308    public BindResponse bind( BindRequest bindRequest ) throws LdapException
1309    {
1310        if ( bindRequest == null )
1311        {
1312            String msg = "Cannot process a null bindRequest";
1313            LOG.debug( msg );
1314            throw new IllegalArgumentException( msg );
1315        }
1316
1317        BindFuture bindFuture = bindAsync( bindRequest );
1318
1319        // Get the result from the future
1320        try
1321        {
1322            // Read the response, waiting for it if not available immediately
1323            // Get the response, blocking
1324            BindResponse bindResponse = bindFuture.get( timeout, TimeUnit.MILLISECONDS );
1325
1326            if ( bindResponse == null )
1327            {
1328                // We didn't received anything : this is an error
1329                LOG.error( "Bind failed : timeout occurred" );
1330                throw new LdapException( TIME_OUT_ERROR );
1331            }
1332
1333            if ( bindResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
1334            {
1335                authenticated.set( true );
1336
1337                // Everything is fine, return the response
1338                LOG.debug( "Bind successful : {}", bindResponse );
1339            }
1340            else
1341            {
1342                // We have had an error
1343                LOG.debug( "Bind failed : {}", bindResponse );
1344            }
1345
1346            return bindResponse;
1347        }
1348        catch ( Exception ie )
1349        {
1350            // Catch all other exceptions
1351            LOG.error( NO_RESPONSE_ERROR, ie );
1352            throw new LdapException( NO_RESPONSE_ERROR, ie );
1353        }
1354    }
1355
1356
1357    /**
1358     * Create a Simple BindRequest ready to be sent.
1359     */
1360    private BindRequest createBindRequest( String name, byte[] credentials ) throws LdapException
1361    {
1362        return createBindRequest( name, credentials, null, ( Control[] ) null );
1363    }
1364
1365
1366    /**
1367     * Create a Simple BindRequest ready to be sent.
1368     */
1369    private BindRequest createBindRequest( Dn name, byte[] credentials ) throws LdapException
1370    {
1371        return createBindRequest( name.getName(), credentials, null, ( Control[] ) null );
1372    }
1373
1374
1375    /**
1376     * {@inheritDoc}
1377     */
1378    @Override
1379    public BindFuture bindAsync( BindRequest bindRequest ) throws LdapException
1380    {
1381        if ( bindRequest == null )
1382        {
1383            String msg = "Cannot process a null bindRequest";
1384            LOG.debug( msg );
1385            throw new IllegalArgumentException( msg );
1386        }
1387
1388        // First switch to anonymous state
1389        authenticated.set( false );
1390
1391        // try to connect, if we aren't already connected.
1392        connect();
1393
1394        // establish TLS layer if TLS is enabled and SSL is NOT
1395        if ( config.isUseTls() && !config.isUseSsl() )
1396        {
1397            startTls();
1398        }
1399
1400        // If the session has not been establish, or is closed, we get out immediately
1401        checkSession();
1402
1403        // Update the messageId
1404        int newId = messageId.incrementAndGet();
1405        bindRequest.setMessageId( newId );
1406
1407        LOG.debug( "Sending request \n{}", bindRequest );
1408
1409        // Create a future for this Bind operation
1410        BindFuture bindFuture = new BindFuture( this, newId );
1411
1412        addToFutureMap( newId, bindFuture );
1413
1414        writeRequest( bindRequest );
1415
1416        // Ok, done return the future
1417        return bindFuture;
1418    }
1419
1420
1421    /**
1422     * SASL PLAIN Bind on a server.
1423     *
1424     * @param authcid The Authentication identity
1425     * @param credentials The password. It can't be null
1426     * @return The BindResponse LdapResponse
1427     * @throws LdapException if some error occurred
1428     */
1429    public BindResponse bindSaslPlain( String authcid, String credentials ) throws LdapException
1430    {
1431        return bindSaslPlain( null, authcid, credentials );
1432    }
1433
1434
1435    /**
1436     * SASL PLAIN Bind on a server.
1437     *
1438     * @param authzid The Authorization identity
1439     * @param authcid The Authentication identity
1440     * @param credentials The password. It can't be null
1441     * @return The BindResponse LdapResponse
1442     * @throws LdapException if some error occurred
1443     */
1444    public BindResponse bindSaslPlain( String authzid, String authcid, String credentials ) throws LdapException
1445    {
1446        LOG.debug( "SASL PLAIN Bind request" );
1447
1448        // Create the BindRequest
1449        SaslPlainRequest saslRequest = new SaslPlainRequest();
1450        saslRequest.setAuthorizationId( authzid );
1451        saslRequest.setUsername( authcid );
1452        saslRequest.setCredentials( credentials );
1453
1454        BindFuture bindFuture = bindAsync( saslRequest );
1455
1456        // Get the result from the future
1457        try
1458        {
1459            // Read the response, waiting for it if not available immediately
1460            // Get the response, blocking
1461            BindResponse bindResponse = bindFuture.get( timeout, TimeUnit.MILLISECONDS );
1462
1463            if ( bindResponse == null )
1464            {
1465                // We didn't received anything : this is an error
1466                LOG.error( "Bind failed : timeout occurred" );
1467                throw new LdapException( TIME_OUT_ERROR );
1468            }
1469
1470            if ( bindResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
1471            {
1472                authenticated.set( true );
1473
1474                // Everything is fine, return the response
1475                LOG.debug( "Bind successful : {}", bindResponse );
1476            }
1477            else
1478            {
1479                // We have had an error
1480                LOG.debug( "Bind failed : {}", bindResponse );
1481            }
1482
1483            return bindResponse;
1484        }
1485        catch ( Exception ie )
1486        {
1487            // Catch all other exceptions
1488            LOG.error( NO_RESPONSE_ERROR, ie );
1489
1490            throw new LdapException( NO_RESPONSE_ERROR, ie );
1491        }
1492    }
1493
1494
1495    /**
1496     * Bind to the server using a CramMd5Request object.
1497     *
1498     * @param request The CramMd5Request POJO containing all the needed parameters
1499     * @return A LdapResponse containing the result
1500     * @throws LdapException if some error occurred
1501     */
1502    public BindResponse bind( SaslCramMd5Request request ) throws LdapException
1503    {
1504        if ( request == null )
1505        {
1506            String msg = "Cannot process a null request";
1507            LOG.debug( msg );
1508            throw new IllegalArgumentException( msg );
1509        }
1510
1511        BindFuture bindFuture = bindAsync( request );
1512
1513        // Get the result from the future
1514        try
1515        {
1516            // Read the response, waiting for it if not available immediately
1517            // Get the response, blocking
1518            BindResponse bindResponse = bindFuture.get( timeout, TimeUnit.MILLISECONDS );
1519
1520            if ( bindResponse == null )
1521            {
1522                // We didn't received anything : this is an error
1523                LOG.error( "Bind failed : timeout occurred" );
1524                throw new LdapException( TIME_OUT_ERROR );
1525            }
1526
1527            if ( bindResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
1528            {
1529                authenticated.set( true );
1530
1531                // Everything is fine, return the response
1532                LOG.debug( "Bind successful : {}", bindResponse );
1533            }
1534            else
1535            {
1536                // We have had an error
1537                LOG.debug( "Bind failed : {}", bindResponse );
1538            }
1539
1540            return bindResponse;
1541        }
1542        catch ( Exception ie )
1543        {
1544            // Catch all other exceptions
1545            LOG.error( NO_RESPONSE_ERROR, ie );
1546
1547            throw new LdapException( NO_RESPONSE_ERROR, ie );
1548        }
1549    }
1550
1551
1552    /**
1553     * Do an asynchronous bind, based on a SaslPlainRequest.
1554     *
1555     * @param request The SaslPlainRequest POJO containing all the needed parameters
1556     * @return The bind operation's future
1557     * @throws LdapException if some error occurred
1558     */
1559    public BindFuture bindAsync( SaslRequest request )
1560        throws LdapException
1561    {
1562        return bindSasl( request );
1563    }
1564
1565
1566    /**
1567     * Bind to the server using a DigestMd5Request object.
1568     *
1569     * @param request The DigestMd5Request POJO containing all the needed parameters
1570     * @return A LdapResponse containing the result
1571     * @throws LdapException if some error occurred
1572     */
1573    public BindResponse bind( SaslDigestMd5Request request ) throws LdapException
1574    {
1575        if ( request == null )
1576        {
1577            String msg = "Cannot process a null request";
1578            LOG.debug( msg );
1579            throw new IllegalArgumentException( msg );
1580        }
1581
1582        BindFuture bindFuture = bindAsync( request );
1583
1584        // Get the result from the future
1585        try
1586        {
1587            // Read the response, waiting for it if not available immediately
1588            // Get the response, blocking
1589            BindResponse bindResponse = bindFuture.get( timeout, TimeUnit.MILLISECONDS );
1590
1591            if ( bindResponse == null )
1592            {
1593                // We didn't received anything : this is an error
1594                LOG.error( "Bind failed : timeout occurred" );
1595                throw new LdapException( TIME_OUT_ERROR );
1596            }
1597
1598            if ( bindResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
1599            {
1600                authenticated.set( true );
1601
1602                // Everything is fine, return the response
1603                LOG.debug( "Bind successful : {}", bindResponse );
1604            }
1605            else
1606            {
1607                // We have had an error
1608                LOG.debug( "Bind failed : {}", bindResponse );
1609            }
1610
1611            return bindResponse;
1612        }
1613        catch ( Exception ie )
1614        {
1615            // Catch all other exceptions
1616            LOG.error( NO_RESPONSE_ERROR, ie );
1617
1618            throw new LdapException( NO_RESPONSE_ERROR, ie );
1619        }
1620    }
1621
1622
1623    /**
1624     * Bind to the server using a GssApiRequest object.
1625     *
1626     * @param request The GssApiRequest POJO containing all the needed parameters
1627     * @return A LdapResponse containing the result
1628     * @throws LdapException if some error occurred
1629     */
1630    public BindResponse bind( SaslGssApiRequest request ) throws LdapException
1631    {
1632        if ( request == null )
1633        {
1634            String msg = "Cannot process a null request";
1635            LOG.debug( msg );
1636            throw new IllegalArgumentException( msg );
1637        }
1638
1639        BindFuture bindFuture = bindAsync( request );
1640
1641        // Get the result from the future
1642        try
1643        {
1644            // Read the response, waiting for it if not available immediately
1645            // Get the response, blocking
1646            BindResponse bindResponse = bindFuture.get( timeout, TimeUnit.MILLISECONDS );
1647
1648            if ( bindResponse == null )
1649            {
1650                // We didn't received anything : this is an error
1651                LOG.error( "Bind failed : timeout occurred" );
1652                throw new LdapException( TIME_OUT_ERROR );
1653            }
1654
1655            if ( bindResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
1656            {
1657                authenticated.set( true );
1658
1659                // Everything is fine, return the response
1660                LOG.debug( "Bind successful : {}", bindResponse );
1661            }
1662            else
1663            {
1664                // We have had an error
1665                LOG.debug( "Bind failed : {}", bindResponse );
1666            }
1667
1668            return bindResponse;
1669        }
1670        catch ( Exception ie )
1671        {
1672            // Catch all other exceptions
1673            LOG.error( NO_RESPONSE_ERROR, ie );
1674
1675            throw new LdapException( NO_RESPONSE_ERROR, ie );
1676        }
1677    }
1678
1679
1680    /**
1681     * Do an asynchronous bind, based on a GssApiRequest.
1682     *
1683     * @param request The GssApiRequest POJO containing all the needed parameters
1684     * @return The bind operation's future
1685     * @throws LdapException if some error occurred
1686     */
1687    public BindFuture bindAsync( SaslGssApiRequest request )
1688        throws LdapException
1689    {
1690        // Krb5.conf file
1691        if ( request.getKrb5ConfFilePath() != null )
1692        {
1693            // Using the krb5.conf file provided by the user
1694            System.setProperty( "java.security.krb5.conf", request.getKrb5ConfFilePath() );
1695        }
1696        else if ( ( request.getRealmName() != null ) && ( request.getKdcHost() != null )
1697            && ( request.getKdcPort() != 0 ) )
1698        {
1699            try
1700            {
1701                // Using a custom krb5.conf we create from the settings provided by the user
1702                String krb5ConfPath = createKrb5ConfFile( request.getRealmName(), request.getKdcHost(),
1703                    request.getKdcPort() );
1704                System.setProperty( "java.security.krb5.conf", krb5ConfPath );
1705            }
1706            catch ( IOException ioe )
1707            {
1708                throw new LdapException( ioe );
1709            }
1710        }
1711        else
1712        {
1713            // Using the system Kerberos configuration
1714            System.clearProperty( "java.security.krb5.conf" );
1715        }
1716
1717        // Login Module configuration
1718        if ( request.getLoginModuleConfiguration() != null )
1719        {
1720            // Using the configuration provided by the user
1721            Configuration.setConfiguration( request.getLoginModuleConfiguration() );
1722        }
1723        else
1724        {
1725            // Using the default configuration
1726            Configuration.setConfiguration( new Krb5LoginConfiguration() );
1727        }
1728
1729        try
1730        {
1731            System.setProperty( "javax.security.auth.useSubjectCredsOnly", "true" );
1732            LoginContext loginContext = new LoginContext( request.getLoginContextName(),
1733                new SaslCallbackHandler( request ) );
1734            loginContext.login();
1735
1736            final SaslGssApiRequest requetFinal = request;
1737            return ( BindFuture ) Subject.doAs( loginContext.getSubject(), new PrivilegedExceptionAction<Object>()
1738            {
1739                @Override
1740                public Object run() throws Exception
1741                {
1742                    return bindSasl( requetFinal );
1743                }
1744            } );
1745        }
1746        catch ( Exception e )
1747        {
1748            throw new LdapException( e );
1749        }
1750    }
1751
1752
1753    /**
1754     * {@inheritDoc}
1755     */
1756    @Override
1757    public EntryCursor search( Dn baseDn, String filter, SearchScope scope, String... attributes )
1758        throws LdapException
1759    {
1760        if ( baseDn == null )
1761        {
1762            LOG.debug( "received a null dn for a search" );
1763            throw new IllegalArgumentException( "The base Dn cannot be null" );
1764        }
1765
1766        // Create a new SearchRequest object
1767        SearchRequest searchRequest = new SearchRequestImpl();
1768
1769        searchRequest.setBase( baseDn );
1770        searchRequest.setFilter( filter );
1771        searchRequest.setScope( scope );
1772        searchRequest.addAttributes( attributes );
1773        searchRequest.setDerefAliases( AliasDerefMode.DEREF_ALWAYS );
1774
1775        // Process the request in blocking mode
1776        return new EntryCursorImpl( search( searchRequest ) );
1777    }
1778
1779
1780    /**
1781     * {@inheritDoc}
1782     */
1783    @Override
1784    public EntryCursor search( String baseDn, String filter, SearchScope scope, String... attributes )
1785        throws LdapException
1786    {
1787        return search( new Dn( baseDn ), filter, scope, attributes );
1788    }
1789
1790
1791    /**
1792     * {@inheritDoc}
1793     */
1794    @Override
1795    public SearchFuture searchAsync( Dn baseDn, String filter, SearchScope scope, String... attributes )
1796        throws LdapException
1797    {
1798        // Create a new SearchRequest object
1799        SearchRequest searchRequest = new SearchRequestImpl();
1800
1801        searchRequest.setBase( baseDn );
1802        searchRequest.setFilter( filter );
1803        searchRequest.setScope( scope );
1804        searchRequest.addAttributes( attributes );
1805        searchRequest.setDerefAliases( AliasDerefMode.DEREF_ALWAYS );
1806
1807        // Process the request in blocking mode
1808        return searchAsync( searchRequest );
1809    }
1810
1811
1812    /**
1813     * {@inheritDoc}
1814     */
1815    @Override
1816    public SearchFuture searchAsync( String baseDn, String filter, SearchScope scope, String... attributes )
1817        throws LdapException
1818    {
1819        return searchAsync( new Dn( baseDn ), filter, scope, attributes );
1820    }
1821
1822
1823    /**
1824     * {@inheritDoc}
1825     */
1826    @Override
1827    public SearchFuture searchAsync( SearchRequest searchRequest ) throws LdapException
1828    {
1829        if ( searchRequest == null )
1830        {
1831            String msg = "Cannot process a null searchRequest";
1832            LOG.debug( msg );
1833            throw new IllegalArgumentException( msg );
1834        }
1835
1836        if ( searchRequest.getBase() == null )
1837        {
1838            String msg = "Cannot process a searchRequest which base DN is null";
1839            LOG.debug( msg );
1840            throw new IllegalArgumentException( msg );
1841        }
1842
1843        // try to connect, if we aren't already connected.
1844        connect();
1845
1846        // If the session has not been establish, or is closed, we get out immediately
1847        checkSession();
1848
1849        int newId = messageId.incrementAndGet();
1850        searchRequest.setMessageId( newId );
1851
1852        if ( searchRequest.isIgnoreReferrals() )
1853        {
1854            // We want to ignore the referral, inject the ManageDSAIT control in the request
1855            searchRequest.addControl( new ManageDsaITImpl() );
1856        }
1857
1858        LOG.debug( "Sending request \n{}", searchRequest );
1859
1860        SearchFuture searchFuture = new SearchFuture( this, searchRequest.getMessageId() );
1861        addToFutureMap( searchRequest.getMessageId(), searchFuture );
1862
1863        // Send the request to the server
1864        writeRequest( searchRequest );
1865
1866        // Check that the future hasn't be canceled
1867        if ( searchFuture.isCancelled() )
1868        {
1869            // Throw an exception here
1870            throw new LdapException( searchFuture.getCause() );
1871        }
1872
1873        // Ok, done return the future
1874        return searchFuture;
1875    }
1876
1877
1878    /**
1879     * {@inheritDoc}
1880     */
1881    @Override
1882    public SearchCursor search( SearchRequest searchRequest ) throws LdapException
1883    {
1884        if ( searchRequest == null )
1885        {
1886            String msg = "Cannot process a null searchRequest";
1887            LOG.debug( msg );
1888            throw new IllegalArgumentException( msg );
1889        }
1890
1891        SearchFuture searchFuture = searchAsync( searchRequest );
1892
1893        long searchTimeout = getTimeout( timeout, searchRequest.getTimeLimit() );
1894
1895        return new SearchCursorImpl( searchFuture, searchTimeout, TimeUnit.MILLISECONDS );
1896    }
1897
1898
1899    //------------------------ The LDAP operations ------------------------//
1900    // Unbind operations                                                   //
1901    //---------------------------------------------------------------------//
1902    /**
1903     * {@inheritDoc}
1904     */
1905    @Override
1906    public void unBind() throws LdapException
1907    {
1908        // If the session has not been establish, or is closed, we get out immediately
1909        checkSession();
1910
1911        // Creates the messageID and stores it into the
1912        // initial message and the transmitted message.
1913        int newId = messageId.incrementAndGet();
1914
1915        // Create the UnbindRequest
1916        UnbindRequest unbindRequest = new UnbindRequestImpl();
1917        unbindRequest.setMessageId( newId );
1918
1919        LOG.debug( "Sending Unbind request \n{}", unbindRequest );
1920
1921        // Send the request to the server
1922        // Use this for logging instead: WriteFuture unbindFuture = ldapSession.write( unbindRequest );
1923        WriteFuture unbindFuture = ldapSession.write( unbindRequest );
1924
1925        unbindFuture.awaitUninterruptibly( timeout );
1926
1927        authenticated.set( false );
1928
1929        // Close all the Future for this session
1930        for ( ResponseFuture<? extends Response> responseFuture : futureMap.values() )
1931        {
1932            responseFuture.cancel();
1933        }
1934
1935        // clear the mappings
1936        clearMaps();
1937
1938        //  We now have to close the session
1939        try
1940        {
1941            close();
1942        }
1943        catch ( IOException e )
1944        {
1945            LOG.error( e.getMessage() );
1946            throw new LdapException( e.getMessage() );
1947        }
1948
1949        connected.set( false );
1950
1951        // Last, not least, reset the MessageId value
1952        messageId.set( 0 );
1953
1954        // And get out
1955        LOG.debug( "Unbind successful" );
1956    }
1957
1958
1959    /**
1960     * Set the connector to use.
1961     *
1962     * @param connector The connector to use
1963     */
1964    public void setConnector( IoConnector connector )
1965    {
1966        this.connector = connector;
1967    }
1968
1969
1970    /**
1971     * {@inheritDoc}
1972     */
1973    @Override
1974    public void setTimeOut( long timeout )
1975    {
1976        if ( timeout <= 0 )
1977        {
1978            // Set a date in the far future : 100 years
1979            this.timeout = 1000L * 60L * 60L * 24L * 365L * 100L;
1980        }
1981        else
1982        {
1983            this.timeout = timeout;
1984        }
1985    }
1986
1987
1988    /**
1989     * Handle the exception we got.
1990     *
1991     * @param session The session we got the exception on
1992     * @param cause The exception cause
1993     * @throws Exception The t
1994     */
1995    @Override
1996    public void exceptionCaught( IoSession session, Throwable cause ) throws Exception
1997    {
1998        LOG.warn( cause.getMessage(), cause );
1999        session.setAttribute( EXCEPTION_KEY, cause );
2000
2001        if ( cause instanceof ProtocolEncoderException )
2002        {
2003            Throwable realCause = ( ( ProtocolEncoderException ) cause ).getCause();
2004
2005            if ( realCause instanceof MessageEncoderException )
2006            {
2007                int messageId = ( ( MessageEncoderException ) realCause ).getMessageId();
2008
2009                ResponseFuture<?> response = futureMap.get( messageId );
2010                response.cancel( true );
2011                response.setCause( realCause );
2012            }
2013        }
2014
2015        session.closeNow();
2016    }
2017
2018
2019    /**
2020     * Check if the message is a NoticeOfDisconnect message
2021     */
2022    private boolean isNoticeOfDisconnect( Message message )
2023    {
2024        if ( message instanceof ExtendedResponse )
2025        {
2026            String responseName = ( ( ExtendedResponse ) message ).getResponseName();
2027
2028            if ( NoticeOfDisconnect.EXTENSION_OID.equals( responseName ) )
2029            {
2030                return true;
2031            }
2032        }
2033
2034        return false;
2035    }
2036
2037
2038    /**
2039     * Handle the incoming LDAP messages. This is where we feed the cursor for search
2040     * requests, or call the listener.
2041     *
2042     * @param session The session that received a message
2043     * @param message The received message
2044     * @throws Exception If there is some error while processing the message
2045     */
2046    @Override
2047    public void messageReceived( IoSession session, Object message ) throws Exception
2048    {
2049        // Feed the response and store it into the session
2050        Message response = ( Message ) message;
2051        LOG.debug( "-------> {} Message received <-------", response );
2052        int messageId = response.getMessageId();
2053
2054        // this check is necessary to prevent adding an abandoned operation's
2055        // result(s) to corresponding queue
2056        ResponseFuture<? extends Response> responseFuture = peekFromFutureMap( messageId );
2057
2058        boolean isNoD = isNoticeOfDisconnect( response );
2059
2060        if ( ( responseFuture == null ) && !isNoD )
2061        {
2062            LOG.info( "There is no future associated with the messageId {}, ignoring the message", messageId );
2063            return;
2064        }
2065
2066        if ( isNoD )
2067        {
2068            // close the session
2069            session.closeNow();
2070
2071            return;
2072        }
2073
2074        switch ( response.getType() )
2075        {
2076            case ADD_RESPONSE:
2077                // Transform the response
2078                AddResponse addResponse = ( AddResponse ) response;
2079
2080                AddFuture addFuture = ( AddFuture ) responseFuture;
2081
2082                // remove the listener from the listener map
2083                if ( LOG.isDebugEnabled() )
2084                {
2085                    if ( addResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
2086                    {
2087                        // Everything is fine, return the response
2088                        LOG.debug( "Add successful : {}", addResponse );
2089                    }
2090                    else
2091                    {
2092                        // We have had an error
2093                        LOG.debug( "Add failed : {}", addResponse );
2094                    }
2095                }
2096
2097                // Store the response into the future
2098                addFuture.set( addResponse );
2099
2100                // Remove the future from the map
2101                removeFromFutureMaps( messageId );
2102
2103                break;
2104
2105            case BIND_RESPONSE:
2106                // Transform the response
2107                BindResponse bindResponse = ( BindResponse ) response;
2108
2109                BindFuture bindFuture = ( BindFuture ) responseFuture;
2110
2111                // remove the listener from the listener map
2112                if ( bindResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
2113                {
2114                    authenticated.set( true );
2115
2116                    // Everything is fine, return the response
2117                    LOG.debug( "Bind successful : {}", bindResponse );
2118                }
2119                else
2120                {
2121                    // We have had an error
2122                    LOG.debug( "Bind failed : {}", bindResponse );
2123                }
2124
2125                // Store the response into the future
2126                bindFuture.set( bindResponse );
2127
2128                // Remove the future from the map
2129                removeFromFutureMaps( messageId );
2130
2131                break;
2132
2133            case COMPARE_RESPONSE:
2134                // Transform the response
2135                CompareResponse compareResponse = ( CompareResponse ) response;
2136
2137                CompareFuture compareFuture = ( CompareFuture ) responseFuture;
2138
2139                // remove the listener from the listener map
2140                if ( LOG.isDebugEnabled() )
2141                {
2142                    if ( compareResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
2143                    {
2144                        // Everything is fine, return the response
2145                        LOG.debug( "Compare successful : {}", compareResponse );
2146                    }
2147                    else
2148                    {
2149                        // We have had an error
2150                        LOG.debug( "Compare failed : {}", compareResponse );
2151                    }
2152                }
2153
2154                // Store the response into the future
2155                compareFuture.set( compareResponse );
2156
2157                // Remove the future from the map
2158                removeFromFutureMaps( messageId );
2159
2160                break;
2161
2162            case DEL_RESPONSE:
2163                // Transform the response
2164                DeleteResponse deleteResponse = ( DeleteResponse ) response;
2165
2166                DeleteFuture deleteFuture = ( DeleteFuture ) responseFuture;
2167
2168                if ( LOG.isDebugEnabled() )
2169                {
2170                    if ( deleteResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
2171                    {
2172                        // Everything is fine, return the response
2173                        LOG.debug( "Delete successful : {}", deleteResponse );
2174                    }
2175                    else
2176                    {
2177                        // We have had an error
2178                        LOG.debug( "Delete failed : {}", deleteResponse );
2179                    }
2180                }
2181
2182                // Store the response into the future
2183                deleteFuture.set( deleteResponse );
2184
2185                // Remove the future from the map
2186                removeFromFutureMaps( messageId );
2187
2188                break;
2189
2190            case EXTENDED_RESPONSE:
2191                // Transform the response
2192                ExtendedResponse extendedResponse = ( ExtendedResponse ) response;
2193
2194                ExtendedFuture extendedFuture = ( ExtendedFuture ) responseFuture;
2195
2196                // remove the listener from the listener map
2197                if ( LOG.isDebugEnabled() )
2198                {
2199                    if ( extendedResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
2200                    {
2201                        // Everything is fine, return the response
2202                        LOG.debug( "Extended successful : {}", extendedResponse );
2203                    }
2204                    else
2205                    {
2206                        // We have had an error
2207                        LOG.debug( "Extended failed : {}", extendedResponse );
2208                    }
2209                }
2210
2211                // Store the response into the future
2212                extendedFuture.set( extendedResponse );
2213
2214                // Remove the future from the map
2215                removeFromFutureMaps( messageId );
2216
2217                break;
2218
2219            case INTERMEDIATE_RESPONSE:
2220                IntermediateResponse intermediateResponse;
2221
2222                if ( responseFuture instanceof SearchFuture )
2223                {
2224                    intermediateResponse = new IntermediateResponseImpl( messageId );
2225                    addControls( intermediateResponse, response );
2226                    ( ( SearchFuture ) responseFuture ).set( intermediateResponse );
2227                }
2228                else if ( responseFuture instanceof ExtendedFuture )
2229                {
2230                    intermediateResponse = new IntermediateResponseImpl( messageId );
2231                    addControls( intermediateResponse, response );
2232                    ( ( ExtendedFuture ) responseFuture ).set( intermediateResponse );
2233                }
2234                else
2235                {
2236                    // currently we only support IR for search and extended operations
2237                    throw new UnsupportedOperationException( "Unknown ResponseFuture type "
2238                        + responseFuture.getClass().getName() );
2239                }
2240
2241                intermediateResponse.setResponseName( ( ( IntermediateResponse ) response ).getResponseName() );
2242                intermediateResponse.setResponseValue( ( ( IntermediateResponse ) response ).getResponseValue() );
2243
2244                break;
2245
2246            case MODIFY_RESPONSE:
2247                // Transform the response
2248                ModifyResponse modifyResponse = ( ModifyResponse ) response;
2249
2250                ModifyFuture modifyFuture = ( ModifyFuture ) responseFuture;
2251
2252                if ( LOG.isDebugEnabled() )
2253                {
2254                    if ( modifyResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
2255                    {
2256                        // Everything is fine, return the response
2257                        LOG.debug( "ModifyFuture successful : {}", modifyResponse );
2258                    }
2259                    else
2260                    {
2261                        // We have had an error
2262                        LOG.debug( "ModifyFuture failed : {}", modifyResponse );
2263                    }
2264                }
2265
2266                // Store the response into the future
2267                modifyFuture.set( modifyResponse );
2268
2269                // Remove the future from the map
2270                removeFromFutureMaps( messageId );
2271
2272                break;
2273
2274            case MODIFYDN_RESPONSE:
2275                // Transform the response
2276                ModifyDnResponse modifyDnResponse = ( ModifyDnResponse ) response;
2277
2278                ModifyDnFuture modifyDnFuture = ( ModifyDnFuture ) responseFuture;
2279
2280                if ( LOG.isDebugEnabled() )
2281                {
2282                    if ( modifyDnResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
2283                    {
2284                        // Everything is fine, return the response
2285                        LOG.debug( "ModifyDN successful : {}", modifyDnResponse );
2286                    }
2287                    else
2288                    {
2289                        // We have had an error
2290                        LOG.debug( "ModifyDN failed : {}", modifyDnResponse );
2291                    }
2292                }
2293
2294                // Store the response into the future
2295                modifyDnFuture.set( modifyDnResponse );
2296
2297                // Remove the future from the map
2298                removeFromFutureMaps( messageId );
2299
2300                break;
2301
2302            case SEARCH_RESULT_DONE:
2303                // Store the response into the responseQueue
2304                SearchResultDone searchResultDone = ( SearchResultDone ) response;
2305
2306                SearchFuture searchFuture = ( SearchFuture ) responseFuture;
2307
2308                if ( LOG.isDebugEnabled() )
2309                {
2310                    if ( searchResultDone.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
2311                    {
2312                        // Everything is fine, return the response
2313                        LOG.debug( "Search successful : {}", searchResultDone );
2314                    }
2315                    else
2316                    {
2317                        // We have had an error
2318                        LOG.debug( "Search failed : {}", searchResultDone );
2319                    }
2320                }
2321
2322                // Store the response into the future
2323                searchFuture.set( searchResultDone );
2324
2325                // Remove the future from the map
2326                removeFromFutureMaps( messageId );
2327
2328                break;
2329
2330            case SEARCH_RESULT_ENTRY:
2331                // Store the response into the responseQueue
2332                SearchResultEntry searchResultEntry = ( SearchResultEntry ) response;
2333
2334                if ( schemaManager != null )
2335                {
2336                    searchResultEntry.setEntry( new DefaultEntry( schemaManager, searchResultEntry.getEntry() ) );
2337                }
2338
2339                searchFuture = ( SearchFuture ) responseFuture;
2340
2341                if ( LOG.isDebugEnabled() )
2342                {
2343                    LOG.debug( "Search entry found : {}", searchResultEntry );
2344                }
2345
2346                // Store the response into the future
2347                searchFuture.set( searchResultEntry );
2348
2349                break;
2350
2351            case SEARCH_RESULT_REFERENCE:
2352                // Store the response into the responseQueue
2353                SearchResultReference searchResultReference = ( SearchResultReference ) response;
2354
2355                searchFuture = ( SearchFuture ) responseFuture;
2356
2357                if ( LOG.isDebugEnabled() )
2358                {
2359                    LOG.debug( "Search reference found : {}", searchResultReference );
2360                }
2361
2362                // Store the response into the future
2363                searchFuture.set( searchResultReference );
2364
2365                break;
2366
2367            default:
2368                throw new IllegalStateException( "Unexpected response type " + response.getType() );
2369        }
2370    }
2371
2372
2373    /**
2374     * {@inheritDoc}
2375     */
2376    @Override
2377    public void modify( Entry entry, ModificationOperation modOp ) throws LdapException
2378    {
2379        if ( entry == null )
2380        {
2381            LOG.debug( "received a null entry for modification" );
2382            throw new IllegalArgumentException( "Entry to be modified cannot be null" );
2383        }
2384
2385        ModifyRequest modReq = new ModifyRequestImpl();
2386        modReq.setName( entry.getDn() );
2387
2388        Iterator<Attribute> itr = entry.iterator();
2389
2390        while ( itr.hasNext() )
2391        {
2392            modReq.addModification( itr.next(), modOp );
2393        }
2394
2395        ModifyResponse modifyResponse = modify( modReq );
2396
2397        processResponse( modifyResponse );
2398    }
2399
2400
2401    /**
2402     * {@inheritDoc}
2403     */
2404    @Override
2405    public void modify( Dn dn, Modification... modifications ) throws LdapException
2406    {
2407        if ( dn == null )
2408        {
2409            LOG.debug( "received a null dn for modification" );
2410            throw new IllegalArgumentException( "The Dn to be modified cannot be null" );
2411        }
2412
2413        if ( ( modifications == null ) || ( modifications.length == 0 ) )
2414        {
2415            String msg = "Cannot process a ModifyRequest without any modification";
2416            LOG.debug( msg );
2417            throw new IllegalArgumentException( msg );
2418        }
2419
2420        ModifyRequest modReq = new ModifyRequestImpl();
2421        modReq.setName( dn );
2422
2423        for ( Modification modification : modifications )
2424        {
2425            modReq.addModification( modification );
2426        }
2427
2428        ModifyResponse modifyResponse = modify( modReq );
2429
2430        processResponse( modifyResponse );
2431    }
2432
2433
2434    /**
2435     * {@inheritDoc}
2436     */
2437    @Override
2438    public void modify( String dn, Modification... modifications ) throws LdapException
2439    {
2440        modify( new Dn( dn ), modifications );
2441    }
2442
2443
2444    /**
2445     * {@inheritDoc}
2446     */
2447    @Override
2448    public ModifyResponse modify( ModifyRequest modRequest ) throws LdapException
2449    {
2450        if ( modRequest == null )
2451        {
2452            String msg = "Cannot process a null modifyRequest";
2453            LOG.debug( msg );
2454            throw new IllegalArgumentException( msg );
2455        }
2456
2457        ModifyFuture modifyFuture = modifyAsync( modRequest );
2458
2459        // Get the result from the future
2460        try
2461        {
2462            // Read the response, waiting for it if not available immediately
2463            // Get the response, blocking
2464            ModifyResponse modifyResponse = modifyFuture.get( timeout, TimeUnit.MILLISECONDS );
2465
2466            if ( modifyResponse == null )
2467            {
2468                // We didn't received anything : this is an error
2469                LOG.error( "Modify failed : timeout occurred" );
2470                throw new LdapException( TIME_OUT_ERROR );
2471            }
2472
2473            if ( modifyResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
2474            {
2475                // Everything is fine, return the response
2476                LOG.debug( "Modify successful : {}", modifyResponse );
2477            }
2478            else
2479            {
2480                if ( modifyResponse instanceof ModifyNoDResponse )
2481                {
2482                    // A NoticeOfDisconnect : deserves a special treatment
2483                    throw new LdapException( modifyResponse.getLdapResult().getDiagnosticMessage() );
2484                }
2485
2486                // We have had an error
2487                LOG.debug( "Modify failed : {}", modifyResponse );
2488            }
2489
2490            return modifyResponse;
2491        }
2492        catch ( Exception ie )
2493        {
2494            // Catch all other exceptions
2495            LOG.error( NO_RESPONSE_ERROR, ie );
2496
2497            // Send an abandon request
2498            if ( !modifyFuture.isCancelled() )
2499            {
2500                abandon( modRequest.getMessageId() );
2501            }
2502
2503            throw new LdapException( ie.getMessage(), ie );
2504        }
2505    }
2506
2507
2508    /**
2509     * {@inheritDoc}
2510     */
2511    @Override
2512    public ModifyFuture modifyAsync( ModifyRequest modRequest ) throws LdapException
2513    {
2514        if ( modRequest == null )
2515        {
2516            String msg = "Cannot process a null modifyRequest";
2517            LOG.debug( msg );
2518            throw new IllegalArgumentException( msg );
2519        }
2520
2521        if ( modRequest.getName() == null )
2522        {
2523            String msg = "Cannot process a modifyRequest which DN is null";
2524            LOG.debug( msg );
2525            throw new IllegalArgumentException( msg );
2526        }
2527
2528        // try to connect, if we aren't already connected.
2529        connect();
2530
2531        checkSession();
2532
2533        int newId = messageId.incrementAndGet();
2534        modRequest.setMessageId( newId );
2535
2536        ModifyFuture modifyFuture = new ModifyFuture( this, newId );
2537        addToFutureMap( newId, modifyFuture );
2538
2539        // Send the request to the server
2540        writeRequest( modRequest );
2541
2542        // Ok, done return the future
2543        return modifyFuture;
2544    }
2545
2546
2547    /**
2548     * {@inheritDoc}
2549     */
2550    @Override
2551    public void rename( String entryDn, String newRdn ) throws LdapException
2552    {
2553        rename( entryDn, newRdn, true );
2554    }
2555
2556
2557    /**
2558     * {@inheritDoc}
2559     */
2560    @Override
2561    public void rename( Dn entryDn, Rdn newRdn ) throws LdapException
2562    {
2563        rename( entryDn, newRdn, true );
2564    }
2565
2566
2567    /**
2568     * {@inheritDoc}
2569     */
2570    @Override
2571    public void rename( String entryDn, String newRdn, boolean deleteOldRdn ) throws LdapException
2572    {
2573        if ( entryDn == null )
2574        {
2575            String msg = "Cannot process a rename of a null Dn";
2576            LOG.debug( msg );
2577            throw new IllegalArgumentException( msg );
2578        }
2579
2580        if ( newRdn == null )
2581        {
2582            String msg = "Cannot process a rename with a null Rdn";
2583            LOG.debug( msg );
2584            throw new IllegalArgumentException( msg );
2585        }
2586
2587        try
2588        {
2589            rename( new Dn( entryDn ), new Rdn( newRdn ), deleteOldRdn );
2590        }
2591        catch ( LdapInvalidDnException e )
2592        {
2593            LOG.error( e.getMessage(), e );
2594            throw new LdapException( e.getMessage(), e );
2595        }
2596    }
2597
2598
2599    /**
2600     * {@inheritDoc}
2601     */
2602    @Override
2603    public void rename( Dn entryDn, Rdn newRdn, boolean deleteOldRdn ) throws LdapException
2604    {
2605        if ( entryDn == null )
2606        {
2607            String msg = "Cannot process a rename of a null Dn";
2608            LOG.debug( msg );
2609            throw new IllegalArgumentException( msg );
2610        }
2611
2612        if ( newRdn == null )
2613        {
2614            String msg = "Cannot process a rename with a null Rdn";
2615            LOG.debug( msg );
2616            throw new IllegalArgumentException( msg );
2617        }
2618
2619        ModifyDnRequest modDnRequest = new ModifyDnRequestImpl();
2620        modDnRequest.setName( entryDn );
2621        modDnRequest.setNewRdn( newRdn );
2622        modDnRequest.setDeleteOldRdn( deleteOldRdn );
2623
2624        ModifyDnResponse modifyDnResponse = modifyDn( modDnRequest );
2625
2626        processResponse( modifyDnResponse );
2627    }
2628
2629
2630    /**
2631     * {@inheritDoc}
2632     */
2633    @Override
2634    public void move( String entryDn, String newSuperiorDn ) throws LdapException
2635    {
2636        if ( entryDn == null )
2637        {
2638            String msg = "Cannot process a move of a null Dn";
2639            LOG.debug( msg );
2640            throw new IllegalArgumentException( msg );
2641        }
2642
2643        if ( newSuperiorDn == null )
2644        {
2645            String msg = "Cannot process a move to a null newSuperior";
2646            LOG.debug( msg );
2647            throw new IllegalArgumentException( msg );
2648        }
2649
2650        try
2651        {
2652            move( new Dn( entryDn ), new Dn( newSuperiorDn ) );
2653        }
2654        catch ( LdapInvalidDnException e )
2655        {
2656            LOG.error( e.getMessage(), e );
2657            throw new LdapException( e.getMessage(), e );
2658        }
2659    }
2660
2661
2662    /**
2663     * {@inheritDoc}
2664     */
2665    @Override
2666    public void move( Dn entryDn, Dn newSuperiorDn ) throws LdapException
2667    {
2668        if ( entryDn == null )
2669        {
2670            String msg = "Cannot process a move of a null Dn";
2671            LOG.debug( msg );
2672            throw new IllegalArgumentException( msg );
2673        }
2674
2675        if ( newSuperiorDn == null )
2676        {
2677            String msg = "Cannot process a move to a null newSuperior";
2678            LOG.debug( msg );
2679            throw new IllegalArgumentException( msg );
2680        }
2681
2682        ModifyDnRequest modDnRequest = new ModifyDnRequestImpl();
2683        modDnRequest.setName( entryDn );
2684        modDnRequest.setNewSuperior( newSuperiorDn );
2685
2686        modDnRequest.setNewRdn( entryDn.getRdn() );
2687
2688        ModifyDnResponse modifyDnResponse = modifyDn( modDnRequest );
2689
2690        processResponse( modifyDnResponse );
2691    }
2692
2693
2694    /**
2695     * {@inheritDoc}
2696     */
2697    @Override
2698    public void moveAndRename( Dn entryDn, Dn newDn ) throws LdapException
2699    {
2700        moveAndRename( entryDn, newDn, true );
2701    }
2702
2703
2704    /**
2705     * {@inheritDoc}
2706     */
2707    @Override
2708    public void moveAndRename( String entryDn, String newDn ) throws LdapException
2709    {
2710        moveAndRename( new Dn( entryDn ), new Dn( newDn ), true );
2711    }
2712
2713
2714    /**
2715     * {@inheritDoc}
2716     */
2717    @Override
2718    public void moveAndRename( Dn entryDn, Dn newDn, boolean deleteOldRdn ) throws LdapException
2719    {
2720        // Check the parameters first
2721        if ( entryDn == null )
2722        {
2723            throw new IllegalArgumentException( "The entry Dn must not be null" );
2724        }
2725
2726        if ( entryDn.isRootDse() )
2727        {
2728            throw new IllegalArgumentException( "The RootDSE cannot be moved" );
2729        }
2730
2731        if ( newDn == null )
2732        {
2733            throw new IllegalArgumentException( "The new Dn must not be null" );
2734        }
2735
2736        if ( newDn.isRootDse() )
2737        {
2738            throw new IllegalArgumentException( "The RootDSE cannot be the target" );
2739        }
2740
2741        // Create the request
2742        ModifyDnRequest modDnRequest = new ModifyDnRequestImpl();
2743        modDnRequest.setName( entryDn );
2744        modDnRequest.setNewRdn( newDn.getRdn() );
2745        
2746        // Check if we really need to specify newSuperior.
2747        // newSuperior is optional [RFC4511, section 4.9]
2748        // Some servers (e.g. OpenDJ 2.6) require a special privilege if
2749        // newSuperior is specified even if it is the same as the old one. Therefore let's not
2750        // specify it if we do not need it. This is better interoperability. 
2751        Dn newDnParent = newDn.getParent();
2752        if ( newDnParent != null && !newDnParent.equals( entryDn.getParent() ) )
2753        {
2754            modDnRequest.setNewSuperior( newDnParent );
2755        }
2756        
2757        modDnRequest.setDeleteOldRdn( deleteOldRdn );
2758
2759        ModifyDnResponse modifyDnResponse = modifyDn( modDnRequest );
2760
2761        processResponse( modifyDnResponse );
2762    }
2763
2764
2765    /**
2766     * {@inheritDoc}
2767     */
2768    @Override
2769    public void moveAndRename( String entryDn, String newDn, boolean deleteOldRdn ) throws LdapException
2770    {
2771        moveAndRename( new Dn( entryDn ), new Dn( newDn ), true );
2772    }
2773
2774
2775    /**
2776     * {@inheritDoc}
2777     */
2778    @Override
2779    public ModifyDnResponse modifyDn( ModifyDnRequest modDnRequest ) throws LdapException
2780    {
2781        if ( modDnRequest == null )
2782        {
2783            String msg = "Cannot process a null modDnRequest";
2784            LOG.debug( msg );
2785            throw new IllegalArgumentException( msg );
2786        }
2787
2788        ModifyDnFuture modifyDnFuture = modifyDnAsync( modDnRequest );
2789
2790        // Get the result from the future
2791        try
2792        {
2793            // Read the response, waiting for it if not available immediately
2794            // Get the response, blocking
2795            ModifyDnResponse modifyDnResponse = modifyDnFuture.get( timeout, TimeUnit.MILLISECONDS );
2796
2797            if ( modifyDnResponse == null )
2798            {
2799                // We didn't received anything : this is an error
2800                LOG.error( "ModifyDN failed : timeout occurred" );
2801                throw new LdapException( TIME_OUT_ERROR );
2802            }
2803
2804            if ( modifyDnResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
2805            {
2806                // Everything is fine, return the response
2807                LOG.debug( "ModifyDN successful : {}", modifyDnResponse );
2808            }
2809            else
2810            {
2811                // We have had an error
2812                LOG.debug( "Modify failed : {}", modifyDnResponse );
2813            }
2814
2815            return modifyDnResponse;
2816        }
2817        catch ( Exception ie )
2818        {
2819            // Catch all other exceptions
2820            LOG.error( NO_RESPONSE_ERROR, ie );
2821
2822            // Send an abandon request
2823            if ( !modifyDnFuture.isCancelled() )
2824            {
2825                abandon( modDnRequest.getMessageId() );
2826            }
2827
2828            throw new LdapException( NO_RESPONSE_ERROR, ie );
2829        }
2830    }
2831
2832
2833    /**
2834     * {@inheritDoc}
2835     */
2836    @Override
2837    public ModifyDnFuture modifyDnAsync( ModifyDnRequest modDnRequest ) throws LdapException
2838    {
2839        if ( modDnRequest == null )
2840        {
2841            String msg = "Cannot process a null modDnRequest";
2842            LOG.debug( msg );
2843            throw new IllegalArgumentException( msg );
2844        }
2845
2846        if ( modDnRequest.getName() == null )
2847        {
2848            String msg = "Cannot process a modifyRequest which DN is null";
2849            LOG.debug( msg );
2850            throw new IllegalArgumentException( msg );
2851        }
2852
2853        if ( ( modDnRequest.getNewSuperior() == null ) && ( modDnRequest.getNewRdn() == null ) )
2854        {
2855            String msg = "Cannot process a modifyRequest which new superior and new Rdn are null";
2856            LOG.debug( msg );
2857            throw new IllegalArgumentException( msg );
2858        }
2859
2860        // try to connect, if we aren't already connected.
2861        connect();
2862
2863        checkSession();
2864
2865        int newId = messageId.incrementAndGet();
2866        modDnRequest.setMessageId( newId );
2867
2868        ModifyDnFuture modifyDnFuture = new ModifyDnFuture( this, newId );
2869        addToFutureMap( newId, modifyDnFuture );
2870
2871        // Send the request to the server
2872        writeRequest( modDnRequest );
2873
2874        // Ok, done return the future
2875        return modifyDnFuture;
2876    }
2877
2878
2879    /**
2880     * {@inheritDoc}
2881     */
2882    @Override
2883    public void delete( String dn ) throws LdapException
2884    {
2885        delete( new Dn( dn ) );
2886    }
2887
2888
2889    /**
2890     * {@inheritDoc}
2891     */
2892    @Override
2893    public void delete( Dn dn ) throws LdapException
2894    {
2895        DeleteRequest deleteRequest = new DeleteRequestImpl();
2896        deleteRequest.setName( dn );
2897
2898        DeleteResponse deleteResponse = delete( deleteRequest );
2899
2900        processResponse( deleteResponse );
2901    }
2902
2903
2904    /**
2905     * deletes the entry with the given Dn, and all its children
2906     *
2907     * @param dn the target entry's Dn
2908     * @throws LdapException If the Dn is not valid or if the deletion failed
2909     */
2910    public void deleteTree( Dn dn ) throws LdapException
2911    {
2912        String treeDeleteOid = "1.2.840.113556.1.4.805";
2913
2914        if ( isControlSupported( treeDeleteOid ) )
2915        {
2916            DeleteRequest deleteRequest = new DeleteRequestImpl();
2917            deleteRequest.setName( dn );
2918            deleteRequest.addControl( new OpaqueControl( treeDeleteOid ) );
2919            DeleteResponse deleteResponse = delete( deleteRequest );
2920
2921            processResponse( deleteResponse );
2922        }
2923        else
2924        {
2925            String msg = "The subtreeDelete control (1.2.840.113556.1.4.805) is not supported by the server\n"
2926                + " The deletion has been aborted";
2927            LOG.error( msg );
2928            throw new LdapException( msg );
2929        }
2930    }
2931
2932
2933    /**
2934     * deletes the entry with the given Dn, and all its children
2935     *
2936     * @param dn the target entry's Dn as a String
2937     * @throws LdapException If the Dn is not valid or if the deletion failed
2938     */
2939    public void deleteTree( String dn ) throws LdapException
2940    {
2941        try
2942        {
2943            String treeDeleteOid = "1.2.840.113556.1.4.805";
2944            Dn newDn = new Dn( dn );
2945
2946            if ( isControlSupported( treeDeleteOid ) )
2947            {
2948                DeleteRequest deleteRequest = new DeleteRequestImpl();
2949                deleteRequest.setName( newDn );
2950                deleteRequest.addControl( new OpaqueControl( treeDeleteOid ) );
2951                DeleteResponse deleteResponse = delete( deleteRequest );
2952
2953                processResponse( deleteResponse );
2954            }
2955            else
2956            {
2957                String msg = "The subtreeDelete control (1.2.840.113556.1.4.805) is not supported by the server\n"
2958                    + " The deletion has been aborted";
2959                LOG.error( msg );
2960                throw new LdapException( msg );
2961            }
2962        }
2963        catch ( LdapInvalidDnException e )
2964        {
2965            LOG.error( e.getMessage(), e );
2966            throw new LdapException( e.getMessage(), e );
2967        }
2968    }
2969
2970
2971    /**
2972     * {@inheritDoc}
2973     */
2974    @Override
2975    public DeleteResponse delete( DeleteRequest deleteRequest ) throws LdapException
2976    {
2977        if ( deleteRequest == null )
2978        {
2979            String msg = "Cannot process a null deleteRequest";
2980            LOG.debug( msg );
2981            throw new IllegalArgumentException( msg );
2982        }
2983
2984        DeleteFuture deleteFuture = deleteAsync( deleteRequest );
2985
2986        // Get the result from the future
2987        try
2988        {
2989            // Read the response, waiting for it if not available immediately
2990            // Get the response, blocking
2991            DeleteResponse delResponse = deleteFuture.get( timeout, TimeUnit.MILLISECONDS );
2992
2993            if ( delResponse == null )
2994            {
2995                // We didn't received anything : this is an error
2996                LOG.error( "Delete failed : timeout occurred" );
2997                throw new LdapException( TIME_OUT_ERROR );
2998            }
2999
3000            if ( delResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
3001            {
3002                // Everything is fine, return the response
3003                LOG.debug( "Delete successful : {}", delResponse );
3004            }
3005            else
3006            {
3007                // We have had an error
3008                LOG.debug( "Delete failed : {}", delResponse );
3009            }
3010
3011            return delResponse;
3012        }
3013        catch ( Exception ie )
3014        {
3015            // Catch all other exceptions
3016            LOG.error( NO_RESPONSE_ERROR, ie );
3017
3018            // Send an abandon request
3019            if ( !deleteFuture.isCancelled() )
3020            {
3021                abandon( deleteRequest.getMessageId() );
3022            }
3023
3024            throw new LdapException( NO_RESPONSE_ERROR, ie );
3025        }
3026    }
3027
3028
3029    /**
3030     * {@inheritDoc}
3031     */
3032    @Override
3033    public DeleteFuture deleteAsync( DeleteRequest deleteRequest ) throws LdapException
3034    {
3035        if ( deleteRequest == null )
3036        {
3037            String msg = "Cannot process a null deleteRequest";
3038            LOG.debug( msg );
3039            throw new IllegalArgumentException( msg );
3040        }
3041
3042        if ( deleteRequest.getName() == null )
3043        {
3044            String msg = "Cannot process a deleteRequest which DN is null";
3045            LOG.debug( msg );
3046            throw new IllegalArgumentException( msg );
3047        }
3048
3049        // try to connect, if we aren't already connected.
3050        connect();
3051
3052        checkSession();
3053
3054        int newId = messageId.incrementAndGet();
3055
3056        deleteRequest.setMessageId( newId );
3057
3058        DeleteFuture deleteFuture = new DeleteFuture( this, newId );
3059        addToFutureMap( newId, deleteFuture );
3060
3061        // Send the request to the server
3062        writeRequest( deleteRequest );
3063
3064        // Ok, done return the future
3065        return deleteFuture;
3066    }
3067
3068
3069    /**
3070     * {@inheritDoc}
3071     */
3072    @Override
3073    public boolean compare( String dn, String attributeName, String value ) throws LdapException
3074    {
3075        return compare( new Dn( dn ), attributeName, value );
3076    }
3077
3078
3079    /**
3080     * {@inheritDoc}
3081     */
3082    @Override
3083    public boolean compare( String dn, String attributeName, byte[] value ) throws LdapException
3084    {
3085        return compare( new Dn( dn ), attributeName, value );
3086    }
3087
3088
3089    /**
3090     * {@inheritDoc}
3091     */
3092    @Override
3093    public boolean compare( String dn, String attributeName, Value<?> value ) throws LdapException
3094    {
3095        return compare( new Dn( dn ), attributeName, value );
3096    }
3097
3098
3099    /**
3100     * {@inheritDoc}
3101     */
3102    @Override
3103    public boolean compare( Dn dn, String attributeName, String value ) throws LdapException
3104    {
3105        CompareRequest compareRequest = new CompareRequestImpl();
3106        compareRequest.setName( dn );
3107        compareRequest.setAttributeId( attributeName );
3108        compareRequest.setAssertionValue( value );
3109
3110        CompareResponse compareResponse = compare( compareRequest );
3111
3112        return processResponse( compareResponse );
3113    }
3114
3115
3116    /**
3117     * {@inheritDoc}
3118     */
3119    @Override
3120    public boolean compare( Dn dn, String attributeName, byte[] value ) throws LdapException
3121    {
3122        CompareRequest compareRequest = new CompareRequestImpl();
3123        compareRequest.setName( dn );
3124        compareRequest.setAttributeId( attributeName );
3125        compareRequest.setAssertionValue( value );
3126
3127        CompareResponse compareResponse = compare( compareRequest );
3128
3129        return processResponse( compareResponse );
3130    }
3131
3132
3133    /**
3134     * {@inheritDoc}
3135     */
3136    @Override
3137    public boolean compare( Dn dn, String attributeName, Value<?> value ) throws LdapException
3138    {
3139        CompareRequest compareRequest = new CompareRequestImpl();
3140        compareRequest.setName( dn );
3141        compareRequest.setAttributeId( attributeName );
3142
3143        if ( value.isHumanReadable() )
3144        {
3145            compareRequest.setAssertionValue( value.getString() );
3146        }
3147        else
3148        {
3149            compareRequest.setAssertionValue( value.getBytes() );
3150        }
3151
3152        CompareResponse compareResponse = compare( compareRequest );
3153
3154        return processResponse( compareResponse );
3155    }
3156
3157
3158    /**
3159     * {@inheritDoc}
3160     */
3161    @Override
3162    public CompareResponse compare( CompareRequest compareRequest ) throws LdapException
3163    {
3164        if ( compareRequest == null )
3165        {
3166            String msg = "Cannot process a null compareRequest";
3167            LOG.debug( msg );
3168            throw new IllegalArgumentException( msg );
3169        }
3170
3171        CompareFuture compareFuture = compareAsync( compareRequest );
3172
3173        // Get the result from the future
3174        try
3175        {
3176            // Read the response, waiting for it if not available immediately
3177            // Get the response, blocking
3178            CompareResponse compareResponse = compareFuture.get( timeout, TimeUnit.MILLISECONDS );
3179
3180            if ( compareResponse == null )
3181            {
3182                // We didn't received anything : this is an error
3183                LOG.error( "Compare failed : timeout occurred" );
3184                throw new LdapException( TIME_OUT_ERROR );
3185            }
3186
3187            if ( compareResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
3188            {
3189                // Everything is fine, return the response
3190                LOG.debug( "Compare successful : {}", compareResponse );
3191            }
3192            else
3193            {
3194                // We have had an error
3195                LOG.debug( "Compare failed : {}", compareResponse );
3196            }
3197
3198            return compareResponse;
3199        }
3200        catch ( Exception ie )
3201        {
3202            // Catch all other exceptions
3203            LOG.error( NO_RESPONSE_ERROR, ie );
3204
3205            // Send an abandon request
3206            if ( !compareFuture.isCancelled() )
3207            {
3208                abandon( compareRequest.getMessageId() );
3209            }
3210
3211            throw new LdapException( NO_RESPONSE_ERROR, ie );
3212        }
3213    }
3214
3215
3216    /**
3217     * {@inheritDoc}
3218     */
3219    @Override
3220    public CompareFuture compareAsync( CompareRequest compareRequest ) throws LdapException
3221    {
3222        if ( compareRequest == null )
3223        {
3224            String msg = "Cannot process a null compareRequest";
3225            LOG.debug( msg );
3226            throw new IllegalArgumentException( msg );
3227        }
3228
3229        if ( compareRequest.getName() == null )
3230        {
3231            String msg = "Cannot process a compareRequest which DN is null";
3232            LOG.debug( msg );
3233            throw new IllegalArgumentException( msg );
3234        }
3235        
3236        // try to connect, if we aren't already connected.
3237        connect();
3238
3239        checkSession();
3240
3241        int newId = messageId.incrementAndGet();
3242
3243        compareRequest.setMessageId( newId );
3244
3245        CompareFuture compareFuture = new CompareFuture( this, newId );
3246        addToFutureMap( newId, compareFuture );
3247
3248        // Send the request to the server
3249        writeRequest( compareRequest );
3250
3251        // Ok, done return the future
3252        return compareFuture;
3253    }
3254
3255
3256    /**
3257     * {@inheritDoc}
3258     */
3259    @Override
3260    public ExtendedResponse extended( String oid ) throws LdapException
3261    {
3262        return extended( oid, null );
3263    }
3264
3265
3266    /**
3267     * {@inheritDoc}
3268     */
3269    @Override
3270    public ExtendedResponse extended( String oid, byte[] value ) throws LdapException
3271    {
3272        try
3273        {
3274            return extended( Oid.fromString( oid ), value );
3275        }
3276        catch ( DecoderException e )
3277        {
3278            String msg = "Failed to decode the OID " + oid;
3279            LOG.error( msg );
3280            throw new LdapException( msg, e );
3281        }
3282    }
3283
3284
3285    /**
3286     * {@inheritDoc}
3287     */
3288    @Override
3289    public ExtendedResponse extended( Oid oid ) throws LdapException
3290    {
3291        return extended( oid, null );
3292    }
3293
3294
3295    /**
3296     * {@inheritDoc}
3297     */
3298    @Override
3299    public ExtendedResponse extended( Oid oid, byte[] value ) throws LdapException
3300    {
3301        ExtendedRequest extendedRequest =
3302            LdapApiServiceFactory.getSingleton().newExtendedRequest( oid.toString(), value );
3303        return extended( extendedRequest );
3304    }
3305
3306
3307    /**
3308     * {@inheritDoc}
3309     */
3310    @Override
3311    public ExtendedResponse extended( ExtendedRequest extendedRequest ) throws LdapException
3312    {
3313        if ( extendedRequest == null )
3314        {
3315            String msg = "Cannot process a null extendedRequest";
3316            LOG.debug( msg );
3317            throw new IllegalArgumentException( msg );
3318        }
3319
3320        ExtendedFuture extendedFuture = extendedAsync( extendedRequest );
3321
3322        // Get the result from the future
3323        try
3324        {
3325            // Read the response, waiting for it if not available immediately
3326            // Get the response, blocking
3327            ExtendedResponse response = ( ExtendedResponse ) extendedFuture
3328                .get( timeout, TimeUnit.MILLISECONDS );
3329
3330            if ( response == null )
3331            {
3332                // We didn't received anything : this is an error
3333                LOG.error( "Extended failed : timeout occurred" );
3334                throw new LdapException( TIME_OUT_ERROR );
3335            }
3336
3337            if ( response.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
3338            {
3339                // Everything is fine, return the response
3340                LOG.debug( "Extended successful : {}", response );
3341            }
3342            else
3343            {
3344                // We have had an error
3345                LOG.debug( "Extended failed : {}", response );
3346            }
3347
3348            // Get back the response. It's still an opaque response
3349            if ( Strings.isEmpty( response.getResponseName() ) )
3350            {
3351                response.setResponseName( extendedRequest.getRequestName() );
3352            }
3353
3354            // Decode the payload now
3355            return codec.decorate( response );
3356        }
3357        catch ( Exception ie )
3358        {
3359            // Catch all other exceptions
3360            LOG.error( NO_RESPONSE_ERROR, ie );
3361
3362            // Send an abandon request
3363            if ( !extendedFuture.isCancelled() )
3364            {
3365                abandon( extendedRequest.getMessageId() );
3366            }
3367
3368            throw new LdapException( NO_RESPONSE_ERROR, ie );
3369        }
3370    }
3371
3372
3373    /**
3374     * {@inheritDoc}
3375     */
3376    @Override
3377    public ExtendedFuture extendedAsync( ExtendedRequest extendedRequest ) throws LdapException
3378    {
3379        if ( extendedRequest == null )
3380        {
3381            String msg = "Cannot process a null extendedRequest";
3382            LOG.debug( msg );
3383            throw new IllegalArgumentException( msg );
3384        }
3385
3386        // try to connect, if we aren't already connected.
3387        connect();
3388
3389        checkSession();
3390
3391        int newId = messageId.incrementAndGet();
3392
3393        extendedRequest.setMessageId( newId );
3394        ExtendedFuture extendedFuture = new ExtendedFuture( this, newId );
3395        addToFutureMap( newId, extendedFuture );
3396
3397        // Send the request to the server
3398        writeRequest( extendedRequest );
3399
3400        // Ok, done return the future
3401        return extendedFuture;
3402    }
3403
3404
3405    /**
3406     * {@inheritDoc}
3407     */
3408    @Override
3409    public boolean exists( String dn ) throws LdapException
3410    {
3411        return exists( new Dn( dn ) );
3412    }
3413
3414
3415    /**
3416     * {@inheritDoc}
3417     */
3418    @Override
3419    public boolean exists( Dn dn ) throws LdapException
3420    {
3421        try
3422        {
3423            Entry entry = lookup( dn, SchemaConstants.NO_ATTRIBUTE_ARRAY );
3424
3425            return entry != null;
3426        }
3427        catch ( LdapNoPermissionException lnpe )
3428        {
3429            // Special case to deal with insufficient permissions
3430            return false;
3431        }
3432        catch ( LdapException le )
3433        {
3434            throw le;
3435        }
3436    }
3437
3438
3439    /**
3440     * {@inheritDoc}
3441     */
3442    @Override
3443    public Entry getRootDse() throws LdapException
3444    {
3445        return lookup( Dn.ROOT_DSE, SchemaConstants.ALL_USER_ATTRIBUTES_ARRAY );
3446    }
3447
3448
3449    /**
3450     * {@inheritDoc}
3451     */
3452    @Override
3453    public Entry getRootDse( String... attributes ) throws LdapException
3454    {
3455        return lookup( Dn.ROOT_DSE, attributes );
3456    }
3457
3458
3459    /**
3460     * {@inheritDoc}
3461     */
3462    @Override
3463    public Entry lookup( Dn dn ) throws LdapException
3464    {
3465        return lookup( dn, SchemaConstants.ALL_USER_ATTRIBUTES_ARRAY );
3466    }
3467
3468
3469    /**
3470     * {@inheritDoc}
3471     */
3472    @Override
3473    public Entry lookup( String dn ) throws LdapException
3474    {
3475        return lookup( dn, SchemaConstants.ALL_USER_ATTRIBUTES_ARRAY );
3476    }
3477
3478
3479    /**
3480     * {@inheritDoc}
3481     */
3482    @Override
3483    public Entry lookup( Dn dn, String... attributes ) throws LdapException
3484    {
3485        return lookup( dn, null, attributes );
3486    }
3487
3488
3489    /**
3490     * {@inheritDoc}
3491     */
3492    @Override
3493    public Entry lookup( Dn dn, Control[] controls, String... attributes ) throws LdapException
3494    {
3495        Entry entry = null;
3496
3497        try
3498        {
3499            SearchRequest searchRequest = new SearchRequestImpl();
3500            searchRequest.setBase( dn );
3501            searchRequest.setFilter( LdapConstants.OBJECT_CLASS_STAR );
3502            searchRequest.setScope( SearchScope.OBJECT );
3503            searchRequest.addAttributes( attributes );
3504            searchRequest.setDerefAliases( AliasDerefMode.DEREF_ALWAYS );
3505
3506            if ( ( controls != null ) && ( controls.length > 0 ) )
3507            {
3508                searchRequest.addAllControls( controls );
3509            }
3510
3511            Cursor<Response> cursor = search( searchRequest );
3512
3513            // Read the response
3514            if ( cursor.next() )
3515            {
3516                // cursor will always hold SearchResultEntry objects cause there is no ManageDsaITControl passed with search request
3517                entry = ( ( SearchResultEntry ) cursor.get() ).getEntry();
3518            }
3519
3520            // Pass through the SaerchResultDone, or stop
3521            // if we have other responses
3522            cursor.next();
3523
3524            // And close the cursor
3525            try
3526            { 
3527                cursor.close();
3528            }
3529            catch ( IOException ioe )
3530            {
3531                throw new LdapException( ioe.getMessage(), ioe );
3532            }
3533
3534        }
3535        catch ( CursorException e )
3536        {
3537            throw new LdapException( e );
3538        }
3539
3540        return entry;
3541    }
3542
3543
3544    /**
3545     * {@inheritDoc}
3546     */
3547    @Override
3548    public Entry lookup( String dn, String... attributes ) throws LdapException
3549    {
3550        return lookup( new Dn( dn ), null, attributes );
3551    }
3552
3553
3554    /**
3555     * {@inheritDoc}
3556     */
3557    @Override
3558    public Entry lookup( String dn, Control[] controls, String... attributes ) throws LdapException
3559    {
3560        return lookup( new Dn( dn ), controls, attributes );
3561    }
3562
3563
3564    /**
3565     * {@inheritDoc}
3566     */
3567    @Override
3568    public boolean isControlSupported( String controlOID ) throws LdapException
3569    {
3570        return getSupportedControls().contains( controlOID );
3571    }
3572
3573
3574    /**
3575     * {@inheritDoc}
3576     */
3577    @Override
3578    public List<String> getSupportedControls() throws LdapException
3579    {
3580        if ( supportedControls != null )
3581        {
3582            return supportedControls;
3583        }
3584
3585        if ( rootDse == null )
3586        {
3587            fetchRootDSE();
3588        }
3589
3590        supportedControls = new ArrayList<>();
3591
3592        Attribute attr = rootDse.get( SchemaConstants.SUPPORTED_CONTROL_AT );
3593
3594        if ( attr == null )
3595        {
3596            // Unlikely. Perhaps the server does not respond properly to "+" attribute query
3597            // (such as 389ds server). So let's try again and let's be more explicit.
3598            fetchRootDSE( SchemaConstants.ALL_USER_ATTRIBUTES, 
3599                SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES, SchemaConstants.SUPPORTED_CONTROL_AT );
3600            attr = rootDse.get( SchemaConstants.SUPPORTED_CONTROL_AT );
3601            if ( attr == null )
3602            {
3603                return supportedControls;
3604            }
3605        }
3606        
3607        for ( Value<?> value : attr )
3608        {
3609            supportedControls.add( value.getString() );
3610        }
3611
3612        return supportedControls;
3613    }
3614
3615
3616    /**
3617     * {@inheritDoc}
3618     */
3619    @Override
3620    public void loadSchema() throws LdapException
3621    {
3622        loadSchema( new DefaultSchemaLoader( this ) );
3623    }
3624
3625
3626    /**
3627     * {@inheritDoc}
3628     */
3629    @Override
3630    public void loadSchemaRelaxed() throws LdapException
3631    {
3632        loadSchema( new DefaultSchemaLoader( this, true ) );
3633    }
3634
3635
3636    /**
3637     * loads schema using the specified schema loader
3638     *
3639     * @param loader the {@link SchemaLoader} to be used to load schema
3640     * @throws LdapException If the schema loading failed
3641     */
3642    public void loadSchema( SchemaLoader loader ) throws LdapException
3643    {
3644        try
3645        {
3646            SchemaManager tmp = new DefaultSchemaManager( loader );
3647
3648            tmp.loadAllEnabled();
3649
3650            if ( !tmp.getErrors().isEmpty() && loader.isStrict() )
3651            {
3652                String msg = "there are errors while loading the schema";
3653                LOG.error( msg + " {}", tmp.getErrors() );
3654                throw new LdapException( msg );
3655            }
3656
3657            schemaManager = tmp;
3658
3659            // Change the container's BinaryDetector
3660            ldapSession.setAttribute( LdapDecoder.MESSAGE_CONTAINER_ATTR,
3661                new LdapMessageContainer<MessageDecorator<? extends Message>>( codec,
3662                    new SchemaBinaryAttributeDetector( schemaManager ) ) );
3663
3664        }
3665        catch ( LdapException le )
3666        {
3667            throw le;
3668        }
3669        catch ( Exception e )
3670        {
3671            LOG.error( "failed to load the schema", e );
3672            throw new LdapException( e );
3673        }
3674    }
3675
3676
3677    /**
3678     * parses the given schema file present in OpenLDAP schema format
3679     * and adds all the SchemaObjects present in it to the SchemaManager
3680     *
3681     * @param schemaFile the schema file in OpenLDAP schema format
3682     * @throws LdapException in case of any errors while parsing
3683     */
3684    public void addSchema( File schemaFile ) throws LdapException
3685    {
3686        try
3687        {
3688            if ( schemaManager == null )
3689            {
3690                loadSchema();
3691            }
3692            
3693            if ( schemaManager == null )
3694            {
3695                throw new LdapException( "Cannot load the schema" );
3696            }
3697
3698            OpenLdapSchemaParser olsp = new OpenLdapSchemaParser();
3699            olsp.setQuirksMode( true );
3700            olsp.parse( schemaFile );
3701
3702            Registries registries = schemaManager.getRegistries();
3703            List<Throwable> errors = new ArrayList<>();
3704
3705            for ( AttributeType atType : olsp.getAttributeTypes() )
3706            {
3707                registries.buildReference( errors, atType );
3708                registries.getAttributeTypeRegistry().register( atType );
3709            }
3710
3711            for ( ObjectClass oc : olsp.getObjectClassTypes() )
3712            {
3713                registries.buildReference( errors, oc );
3714                registries.getObjectClassRegistry().register( oc );
3715            }
3716
3717            LOG.info( "successfully loaded the schema from file {}", schemaFile.getAbsolutePath() );
3718        }
3719        catch ( Exception e )
3720        {
3721            LOG.error( "failed to load the schema from file {}", schemaFile.getAbsolutePath() );
3722            throw new LdapException( e );
3723        }
3724    }
3725
3726
3727    /**
3728     * @see #addSchema(File)
3729     * @param schemaFileName The schema file name to add
3730     * @throws LdapException If the schema addition failed
3731     */
3732    public void addSchema( String schemaFileName ) throws LdapException
3733    {
3734        addSchema( new File( schemaFileName ) );
3735    }
3736
3737
3738    /**
3739     * {@inheritDoc}
3740     */
3741    @Override
3742    public LdapApiService getCodecService()
3743    {
3744        return codec;
3745    }
3746
3747
3748    /**
3749     * {@inheritDoc}
3750     */
3751    @Override
3752    public SchemaManager getSchemaManager()
3753    {
3754        return schemaManager;
3755    }
3756
3757
3758    /**
3759     * fetches the rootDSE from the server
3760     * @throws LdapException
3761     */
3762    private void fetchRootDSE( String... explicitAttributes ) throws LdapException
3763    {
3764        EntryCursor cursor = null;
3765
3766        String[] attributes = explicitAttributes;
3767        if ( attributes.length == 0 )
3768        {
3769            attributes = new String[]
3770                { SchemaConstants.ALL_USER_ATTRIBUTES, SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES };
3771        }
3772        
3773        try
3774        {
3775            cursor = search( "", LdapConstants.OBJECT_CLASS_STAR, SearchScope.OBJECT, attributes );
3776            if ( cursor.next() )
3777            {
3778                rootDse = cursor.get();
3779            }
3780            else
3781            {
3782                throw new LdapException( "Search for root DSE returned no entry" );
3783            }
3784        }
3785        catch ( Exception e )
3786        {
3787            String msg = "Failed to fetch the RootDSE";
3788            LOG.error( msg );
3789            throw new LdapException( msg, e );
3790        }
3791        finally
3792        {
3793            if ( cursor != null )
3794            {
3795                try
3796                {
3797                    cursor.close();
3798                }
3799                catch ( Exception e )
3800                {
3801                    LOG.error( "Failed to close open cursor", e );
3802                }
3803            }
3804        }
3805    }
3806
3807
3808    /**
3809     * gives the configuration information of the connection
3810     *
3811     * @return the configuration of the connection
3812     */
3813    @Override
3814    public LdapConnectionConfig getConfig()
3815    {
3816        return config;
3817    }
3818
3819
3820    private void addControls( Message codec, Message message )
3821    {
3822        Map<String, Control> controls = codec.getControls();
3823
3824        if ( controls != null )
3825        {
3826            for ( Control cc : controls.values() )
3827            {
3828                if ( cc == null )
3829                {
3830                    continue;
3831                }
3832
3833                message.addControl( cc );
3834            }
3835        }
3836    }
3837
3838
3839    /**
3840     * removes the Objects associated with the given message ID
3841     * from future and response queue maps
3842     *
3843     * @param msgId id of the message
3844     */
3845    private void removeFromFutureMaps( int msgId )
3846    {
3847        getFromFutureMap( msgId );
3848    }
3849
3850
3851    /**
3852     * clears the async listener, responseQueue and future mapppings to the corresponding request IDs
3853     */
3854    private void clearMaps()
3855    {
3856        futureMap.clear();
3857    }
3858
3859
3860    /**
3861     * {@inheritDoc}
3862     */
3863    @Override
3864    public boolean doesFutureExistFor( int messageId )
3865    {
3866        ResponseFuture<?> responseFuture = futureMap.get( messageId );
3867        return responseFuture != null;
3868    }
3869
3870
3871    /**
3872     * {@inheritDoc}
3873     */
3874    @Override
3875    public boolean isRequestCompleted( int messageId )
3876    {
3877        ResponseFuture<?> responseFuture = futureMap.get( messageId );
3878        
3879        return responseFuture == null;
3880    }
3881
3882
3883    /**
3884     * Adds the connection closed event listener.
3885     *
3886     * @param ccListener the connection closed listener
3887     */
3888    public void addConnectionClosedEventListener( ConnectionClosedEventListener ccListener )
3889    {
3890        if ( conCloseListeners == null )
3891        {
3892            conCloseListeners = new ArrayList<>();
3893        }
3894
3895        conCloseListeners.add( ccListener );
3896    }
3897    
3898    
3899    /**
3900     * {@inheritDoc}
3901     */
3902    @Override
3903    public void inputClosed( IoSession session ) throws Exception 
3904    {
3905        session.closeNow();
3906    }
3907
3908
3909    /**
3910     * This method is called when a new session is created. We will store some
3911     * informations that the session will need to process incoming requests.
3912     * 
3913     * @param session the newly created session
3914     */
3915    @Override
3916    public void sessionCreated( IoSession session ) throws Exception
3917    {
3918        // Last, store the message container
3919        LdapMessageContainer<? extends MessageDecorator<Message>> ldapMessageContainer =
3920            new LdapMessageContainer<>( codec, config.getBinaryAttributeDetector() );
3921
3922        session.setAttribute( LdapDecoder.MESSAGE_CONTAINER_ATTR, ldapMessageContainer );
3923        connected.set( true );
3924    }
3925
3926
3927    /**
3928     * {@inheritDoc}
3929     */
3930    @Override
3931    public void event( IoSession session, FilterEvent event ) throws Exception 
3932    {
3933        // Check if it's a SSLevent 
3934        if ( ( event instanceof SslEvent ) && ( ( SslEvent ) event == SslEvent.SECURED ) )
3935        {
3936            handshakeFuture.secured();
3937        }
3938    }
3939
3940
3941    /**
3942     * {@inheritDoc}
3943     */
3944    @Override
3945    public void sessionClosed( IoSession session ) throws Exception
3946    {
3947        // no need to handle if this session was closed by the user
3948        if ( !connected.get() )
3949        {
3950            return;
3951        }
3952
3953        ldapSession.closeNow();
3954        connected.set( false );
3955        // Reset the messageId
3956        messageId.set( 0 );
3957
3958        connectorMutex.lock();
3959
3960        try
3961        {
3962            if ( connector != null )
3963            {
3964                connector.dispose();
3965                connector = null;
3966            }
3967        }
3968        finally
3969        {
3970            connectorMutex.unlock();
3971        }
3972
3973        clearMaps();
3974
3975        if ( conCloseListeners != null )
3976        {
3977            LOG.debug( "notifying the registered ConnectionClosedEventListeners.." );
3978
3979            for ( ConnectionClosedEventListener listener : conCloseListeners )
3980            {
3981                listener.connectionClosed();
3982            }
3983        }
3984    }
3985
3986
3987    /**
3988     * Sends the StartTLS extended request to server and adds a security layer
3989     * upon receiving a response with successful result. Note that we will use
3990     * the default LDAP connection.
3991     *
3992     * @throws LdapException If the StartTLS operation failed
3993     */
3994    public void startTls() throws LdapException
3995    {
3996        try
3997        {
3998            if ( config.isUseSsl() )
3999            {
4000                throw new LdapException( "Cannot use TLS when the useSsl flag is set true in the configuration" );
4001            }
4002
4003            // try to connect, if we aren't already connected.
4004            connect();
4005
4006            checkSession();
4007
4008            IoFilter sslFilter = ldapSession.getFilterChain().get( SSL_FILTER_KEY );
4009            
4010            if ( sslFilter != null )
4011            {
4012                LOG.debug( "LDAP session already using startTLS" );
4013                return;
4014            }
4015
4016            ExtendedResponse resp = extended( new StartTlsRequestImpl() );
4017            LdapResult result = resp.getLdapResult();
4018
4019            if ( result.getResultCode() == ResultCodeEnum.SUCCESS )
4020            {
4021                addSslFilter();
4022            }
4023            else
4024            {
4025                throw new LdapOperationException( result.getResultCode(), result.getDiagnosticMessage() );
4026            }
4027        }
4028        catch ( LdapException e )
4029        {
4030            throw e;
4031        }
4032        catch ( Exception e )
4033        {
4034            throw new LdapException( e );
4035        }
4036    }
4037
4038
4039    /**
4040     * adds {@link SslFilter} to the IOConnector or IOSession's filter chain
4041     */
4042    private void addSslFilter() throws LdapException
4043    {
4044        try
4045        {
4046            SSLContext sslContext = SSLContext.getInstance( config.getSslProtocol() );
4047            
4048            TrustManager[] trustManagers = config.getTrustManagers();
4049            
4050            if ( ( trustManagers == null ) || ( trustManagers.length == 0 ) )
4051            {
4052                trustManagers = new TrustManager[] { new NoVerificationTrustManager() };
4053            }
4054            
4055            sslContext.init( config.getKeyManagers(), trustManagers, config.getSecureRandom() );
4056
4057            SslFilter sslFilter = new SslFilter( sslContext );
4058            sslFilter.setUseClientMode( true );
4059
4060            // Configure the enabled cipher lists
4061            String[] enabledCipherSuite = config.getEnabledCipherSuites();
4062
4063            if ( ( enabledCipherSuite != null ) && ( enabledCipherSuite.length != 0 ) )
4064            {
4065                sslFilter.setEnabledCipherSuites( enabledCipherSuite );
4066            }
4067
4068            // Be sure we disable SSLV3
4069            String[] enabledProtocols = config.getEnabledProtocols();
4070
4071            if ( ( enabledProtocols != null ) && ( enabledProtocols.length != 0 ) )
4072            {
4073                sslFilter.setEnabledProtocols( enabledProtocols );
4074            }
4075            else
4076            {
4077                // Default to TLS
4078                sslFilter.setEnabledProtocols( new String[]
4079                    { "TLSv1", "TLSv1.1", "TLSv1.2" } );
4080            }
4081
4082            // for LDAPS/startTLS
4083            handshakeFuture = new HandshakeFuture();
4084
4085            if ( ( ldapSession == null ) || !connected.get() )
4086            {
4087                connector.getFilterChain().addFirst( SSL_FILTER_KEY, sslFilter );
4088            }
4089            else
4090            // for StartTLS
4091            {
4092                ldapSession.getFilterChain().addFirst( SSL_FILTER_KEY, sslFilter );
4093
4094                boolean isSecured = handshakeFuture.get( timeout, TimeUnit.MILLISECONDS );
4095                
4096                if ( !isSecured )
4097                {
4098                    throw new LdapOperationException( ResultCodeEnum.OTHER, I18n.err( I18n.ERR_4100_TLS_HANDSHAKE_ERROR ) );
4099                }
4100            }
4101        }
4102        catch ( Exception e )
4103        {
4104            String msg = "Failed to initialize the SSL context";
4105            LOG.error( msg, e );
4106            throw new LdapException( msg, e );
4107        }
4108    }
4109
4110
4111    /**
4112     * Process the SASL Bind. It's a dialog with the server, we will send a first BindRequest, receive
4113     * a response and the, if this response is a challenge, continue by sending a new BindRequest with
4114     * the requested informations.
4115     */
4116    private BindFuture bindSasl( SaslRequest saslRequest ) throws LdapException
4117    {
4118        // First switch to anonymous state
4119        authenticated.set( false );
4120
4121        // try to connect, if we aren't already connected.
4122        connect();
4123
4124        // If the session has not been establish, or is closed, we get out immediately
4125        checkSession();
4126
4127        BindRequest bindRequest = createBindRequest( ( String ) null, null,
4128            saslRequest.getSaslMechanism(), saslRequest
4129                .getControls() );
4130
4131        // Update the messageId
4132        int newId = messageId.incrementAndGet();
4133        bindRequest.setMessageId( newId );
4134
4135        LOG.debug( "Sending request \n{}", bindRequest );
4136
4137        // Create a future for this Bind operation
4138        BindFuture bindFuture = new BindFuture( this, newId );
4139
4140        // Store it in the future Map
4141        addToFutureMap( newId, bindFuture );
4142
4143        try
4144        {
4145            BindResponse bindResponse;
4146            byte[] response;
4147            ResultCodeEnum result;
4148
4149            // Creating a map for SASL properties
4150            Map<String, Object> properties = new HashMap<>();
4151
4152            // Quality of Protection SASL property
4153            if ( saslRequest.getQualityOfProtection() != null )
4154            {
4155                properties.put( Sasl.QOP, saslRequest.getQualityOfProtection().getValue() );
4156            }
4157
4158            // Security Strength SASL property
4159            if ( saslRequest.getSecurityStrength() != null )
4160            {
4161                properties.put( Sasl.STRENGTH, saslRequest.getSecurityStrength().getValue() );
4162            }
4163
4164            // Mutual Authentication SASL property
4165            if ( saslRequest.isMutualAuthentication() )
4166            {
4167                properties.put( Sasl.SERVER_AUTH, "true" );
4168            }
4169
4170            // Creating a SASL Client
4171            SaslClient sc = Sasl.createSaslClient(
4172                new String[]
4173                    { bindRequest.getSaslMechanism() },
4174                saslRequest.getAuthorizationId(),
4175                "ldap",
4176                config.getLdapHost(),
4177                properties,
4178                new SaslCallbackHandler( saslRequest ) );
4179
4180            // If the SaslClient wasn't created, that means we can't create the SASL client
4181            // for the requested mechanism. We then produce an Exception
4182            if ( sc == null )
4183            {
4184                String message = "Cannot find a SASL factory for the " + bindRequest.getSaslMechanism() + " mechanism";
4185                LOG.error( message );
4186                throw new LdapException( message );
4187            }
4188
4189            // Corner case : the SASL mech might send an initial challenge, and we have to
4190            // deal with it immediately.
4191            if ( sc.hasInitialResponse() )
4192            {
4193                byte[] challengeResponse = sc.evaluateChallenge( Strings.EMPTY_BYTES );
4194
4195                // Stores the challenge's response, and send it to the server
4196                bindRequest.setCredentials( challengeResponse );
4197                writeRequest( bindRequest );
4198
4199                // Get the server's response, blocking
4200                bindResponse = bindFuture.get( timeout, TimeUnit.MILLISECONDS );
4201
4202                if ( bindResponse == null )
4203                {
4204                    // We didn't received anything : this is an error
4205                    LOG.error( "bind failed : timeout occurred" );
4206                    throw new LdapException( TIME_OUT_ERROR );
4207                }
4208
4209                result = bindResponse.getLdapResult().getResultCode();
4210            }
4211            else
4212            {
4213                // Copy the bindRequest without setting the credentials
4214                BindRequest bindRequestCopy = new BindRequestImpl();
4215                bindRequestCopy.setMessageId( newId );
4216
4217                bindRequestCopy.setName( bindRequest.getName() );
4218                bindRequestCopy.setSaslMechanism( bindRequest.getSaslMechanism() );
4219                bindRequestCopy.setSimple( bindRequest.isSimple() );
4220                bindRequestCopy.setVersion3( bindRequest.getVersion3() );
4221                bindRequestCopy.addAllControls( bindRequest.getControls().values().toArray( new Control[0] ) );
4222
4223                writeRequest( bindRequestCopy );
4224
4225                bindResponse = bindFuture.get( timeout, TimeUnit.MILLISECONDS );
4226
4227                if ( bindResponse == null )
4228                {
4229                    // We didn't received anything : this is an error
4230                    LOG.error( "bind failed : timeout occurred" );
4231                    throw new LdapException( TIME_OUT_ERROR );
4232                }
4233
4234                result = bindResponse.getLdapResult().getResultCode();
4235            }
4236
4237            while ( !sc.isComplete()
4238                && ( ( result == ResultCodeEnum.SASL_BIND_IN_PROGRESS ) || ( result == ResultCodeEnum.SUCCESS ) ) )
4239            {
4240                response = sc.evaluateChallenge( bindResponse.getServerSaslCreds() );
4241
4242                if ( result == ResultCodeEnum.SUCCESS )
4243                {
4244                    if ( response != null )
4245                    {
4246                        throw new LdapException( "protocol error" );
4247                    }
4248                }
4249                else
4250                {
4251                    newId = messageId.incrementAndGet();
4252                    bindRequest.setMessageId( newId );
4253                    bindRequest.setCredentials( response );
4254
4255                    addToFutureMap( newId, bindFuture );
4256
4257                    writeRequest( bindRequest );
4258
4259                    bindResponse = bindFuture.get( timeout, TimeUnit.MILLISECONDS );
4260
4261                    if ( bindResponse == null )
4262                    {
4263                        // We didn't received anything : this is an error
4264                        LOG.error( "bind failed : timeout occurred" );
4265                        throw new LdapException( TIME_OUT_ERROR );
4266                    }
4267
4268                    result = bindResponse.getLdapResult().getResultCode();
4269                }
4270            }
4271
4272            bindFuture.set( bindResponse );
4273
4274            return bindFuture;
4275        }
4276        catch ( LdapException e )
4277        {
4278            throw e;
4279        }
4280        catch ( Exception e )
4281        {
4282            LOG.error( e.getMessage() );
4283            throw new LdapException( e );
4284        }
4285    }
4286
4287
4288    /**
4289     * a reusable code block to be used in various bind methods
4290     */
4291    private void writeRequest( Request request ) throws LdapException
4292    {
4293        // Send the request to the server
4294        WriteFuture writeFuture = ldapSession.write( request );
4295
4296        long localTimeout = timeout;
4297
4298        while ( localTimeout > 0 )
4299        {
4300            // Wait only 100 ms
4301            boolean done = writeFuture.awaitUninterruptibly( 100 );
4302
4303            if ( done )
4304            {
4305                return;
4306            }
4307
4308            // Wait for the message to be sent to the server
4309            if ( !ldapSession.isConnected() )
4310            {
4311                // We didn't received anything : this is an error
4312                LOG.error( "Message failed : something wrong has occurred" );
4313
4314                Exception exception = ( Exception ) ldapSession.removeAttribute( EXCEPTION_KEY );
4315
4316                if ( exception != null )
4317                {
4318                    if ( exception instanceof LdapException )
4319                    {
4320                        throw ( LdapException ) exception;
4321                    }
4322                    else
4323                    {
4324                        throw new InvalidConnectionException( exception.getMessage(), exception );
4325                    }
4326                }
4327
4328                throw new InvalidConnectionException( "Error while sending some message : the session has been closed" );
4329            }
4330
4331            localTimeout -= 100;
4332        }
4333
4334        LOG.error( "TimeOut has occurred" );
4335        throw new LdapException( TIME_OUT_ERROR );
4336    }
4337
4338
4339    /**
4340     * method to write the kerberos config in the standard MIT kerberos format
4341     *
4342     * This is required cause the JGSS api is not able to recognize the port value set
4343     * in the system property java.security.krb5.kdc this issue makes it impossible
4344     * to set a kdc running non standard ports (other than 88)
4345     *
4346     * e.g localhost:6088
4347     *
4348     * <pre>
4349     * [libdefaults]
4350     *     default_realm = EXAMPLE.COM
4351     *
4352     * [realms]
4353     *     EXAMPLE.COM = {
4354     *         kdc = localhost:6088
4355     *     }
4356     * </pre>
4357     *
4358     * @return the full path of the config file
4359     */
4360    private String createKrb5ConfFile( String realmName, String kdcHost, int kdcPort ) throws IOException
4361    {
4362        StringBuilder sb = new StringBuilder();
4363
4364        sb.append( "[libdefaults]" )
4365            .append( "\n\t" );
4366        sb.append( "default_realm = " )
4367            .append( realmName )
4368            .append( "\n" );
4369
4370        sb.append( "[realms]" )
4371            .append( "\n\t" );
4372
4373        sb.append( realmName )
4374            .append( " = {" )
4375            .append( "\n\t\t" );
4376        sb.append( "kdc = " )
4377            .append( kdcHost )
4378            .append( ":" )
4379            .append( kdcPort )
4380            .append( "\n\t}\n" );
4381
4382        File krb5Conf = File.createTempFile( "client-api-krb5", ".conf" );
4383        krb5Conf.deleteOnExit();
4384
4385        try ( Writer writer = new OutputStreamWriter( Files.newOutputStream( Paths.get( krb5Conf.getPath() ) ), 
4386            Charset.defaultCharset() ) )
4387        {
4388            writer.write( sb.toString() );
4389        }
4390
4391        String krb5ConfPath = krb5Conf.getAbsolutePath();
4392
4393        LOG.debug( "krb 5 config file created at {}", krb5ConfPath );
4394
4395        return krb5ConfPath;
4396    }
4397
4398
4399    /**
4400     * {@inheritDoc}
4401     */
4402    @Override
4403    public BinaryAttributeDetector getBinaryAttributeDetector()
4404    {
4405        if ( config != null )
4406        {
4407            return config.getBinaryAttributeDetector();
4408        }
4409        else
4410        {
4411            return null;
4412        }
4413    }
4414
4415
4416    /**
4417     * {@inheritDoc}
4418     */
4419    @Override
4420    public void setBinaryAttributeDetector( BinaryAttributeDetector binaryAttributeDetector )
4421    {
4422        if ( config != null )
4423        {
4424            config.setBinaryAttributeDetector( binaryAttributeDetector );
4425        }
4426    }
4427
4428
4429    /**
4430     * {@inheritDoc}
4431     */
4432    @Override
4433    public void setSchemaManager( SchemaManager schemaManager )
4434    {
4435        this.schemaManager = schemaManager;
4436    }
4437
4438
4439    /**
4440     * @return the connectionConfig
4441     */
4442    public SocketSessionConfig getConnectionConfig()
4443    {
4444        return connectionConfig;
4445    }
4446
4447
4448    /**
4449     * @param connectionConfig the connectionConfig to set
4450     */
4451    public void setConnectionConfig( SocketSessionConfig connectionConfig )
4452    {
4453        this.connectionConfig = connectionConfig;
4454    }
4455}