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 *    https://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.CompletableFuture;
043import java.util.concurrent.ConcurrentHashMap;
044import java.util.concurrent.ExecutionException;
045import java.util.concurrent.TimeUnit;
046import java.util.concurrent.TimeoutException;
047import java.util.concurrent.atomic.AtomicBoolean;
048import java.util.concurrent.locks.ReentrantLock;
049
050import javax.net.ssl.SSLContext;
051import javax.net.ssl.SSLSession;
052import javax.net.ssl.TrustManager;
053import javax.security.auth.Subject;
054import javax.security.auth.login.Configuration;
055import javax.security.auth.login.LoginContext;
056import javax.security.sasl.Sasl;
057import javax.security.sasl.SaslClient;
058
059import org.apache.directory.api.asn1.DecoderException;
060import org.apache.directory.api.asn1.util.Oid;
061import org.apache.directory.api.i18n.I18n;
062import org.apache.directory.api.ldap.codec.api.BinaryAttributeDetector;
063import org.apache.directory.api.ldap.codec.api.DefaultConfigurableBinaryAttributeDetector;
064import org.apache.directory.api.ldap.codec.api.ExtendedOperationFactory;
065import org.apache.directory.api.ldap.codec.api.LdapApiService;
066import org.apache.directory.api.ldap.codec.api.LdapApiServiceFactory;
067import org.apache.directory.api.ldap.codec.api.LdapDecoder;
068import org.apache.directory.api.ldap.codec.api.LdapMessageContainer;
069import org.apache.directory.api.ldap.codec.api.MessageEncoderException;
070import org.apache.directory.api.ldap.codec.api.SaslFilter;
071import org.apache.directory.api.ldap.codec.api.SchemaBinaryAttributeDetector;
072import org.apache.directory.api.ldap.extras.controls.ad.TreeDelete;
073import org.apache.directory.api.ldap.extras.controls.ad.TreeDeleteImpl;
074import org.apache.directory.api.ldap.extras.extended.startTls.StartTlsRequestImpl;
075import org.apache.directory.api.ldap.model.constants.LdapConstants;
076import org.apache.directory.api.ldap.model.constants.SchemaConstants;
077import org.apache.directory.api.ldap.model.cursor.Cursor;
078import org.apache.directory.api.ldap.model.cursor.CursorException;
079import org.apache.directory.api.ldap.model.cursor.EntryCursor;
080import org.apache.directory.api.ldap.model.cursor.SearchCursor;
081import org.apache.directory.api.ldap.model.entry.Attribute;
082import org.apache.directory.api.ldap.model.entry.DefaultEntry;
083import org.apache.directory.api.ldap.model.entry.Entry;
084import org.apache.directory.api.ldap.model.entry.Modification;
085import org.apache.directory.api.ldap.model.entry.ModificationOperation;
086import org.apache.directory.api.ldap.model.entry.Value;
087import org.apache.directory.api.ldap.model.exception.LdapAuthenticationException;
088import org.apache.directory.api.ldap.model.exception.LdapException;
089import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
090import org.apache.directory.api.ldap.model.exception.LdapNoPermissionException;
091import org.apache.directory.api.ldap.model.exception.LdapNoSuchObjectException;
092import org.apache.directory.api.ldap.model.exception.LdapOperationException;
093import org.apache.directory.api.ldap.model.exception.LdapOtherException;
094import org.apache.directory.api.ldap.model.exception.LdapTlsHandshakeException;
095import org.apache.directory.api.ldap.model.message.AbandonRequest;
096import org.apache.directory.api.ldap.model.message.AbandonRequestImpl;
097import org.apache.directory.api.ldap.model.message.AddRequest;
098import org.apache.directory.api.ldap.model.message.AddRequestImpl;
099import org.apache.directory.api.ldap.model.message.AddResponse;
100import org.apache.directory.api.ldap.model.message.AliasDerefMode;
101import org.apache.directory.api.ldap.model.message.BindRequest;
102import org.apache.directory.api.ldap.model.message.BindRequestImpl;
103import org.apache.directory.api.ldap.model.message.BindResponse;
104import org.apache.directory.api.ldap.model.message.CompareRequest;
105import org.apache.directory.api.ldap.model.message.CompareRequestImpl;
106import org.apache.directory.api.ldap.model.message.CompareResponse;
107import org.apache.directory.api.ldap.model.message.Control;
108import org.apache.directory.api.ldap.model.message.DeleteRequest;
109import org.apache.directory.api.ldap.model.message.DeleteRequestImpl;
110import org.apache.directory.api.ldap.model.message.DeleteResponse;
111import org.apache.directory.api.ldap.model.message.ExtendedRequest;
112import org.apache.directory.api.ldap.model.message.ExtendedResponse;
113import org.apache.directory.api.ldap.model.message.IntermediateResponse;
114import org.apache.directory.api.ldap.model.message.LdapResult;
115import org.apache.directory.api.ldap.model.message.Message;
116import org.apache.directory.api.ldap.model.message.ModifyDnRequest;
117import org.apache.directory.api.ldap.model.message.ModifyDnRequestImpl;
118import org.apache.directory.api.ldap.model.message.ModifyDnResponse;
119import org.apache.directory.api.ldap.model.message.ModifyRequest;
120import org.apache.directory.api.ldap.model.message.ModifyRequestImpl;
121import org.apache.directory.api.ldap.model.message.ModifyResponse;
122import org.apache.directory.api.ldap.model.message.OpaqueExtendedRequest;
123import org.apache.directory.api.ldap.model.message.OpaqueExtendedResponse;
124import org.apache.directory.api.ldap.model.message.Request;
125import org.apache.directory.api.ldap.model.message.Response;
126import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
127import org.apache.directory.api.ldap.model.message.SearchRequest;
128import org.apache.directory.api.ldap.model.message.SearchRequestImpl;
129import org.apache.directory.api.ldap.model.message.SearchResultDone;
130import org.apache.directory.api.ldap.model.message.SearchResultEntry;
131import org.apache.directory.api.ldap.model.message.SearchResultReference;
132import org.apache.directory.api.ldap.model.message.SearchScope;
133import org.apache.directory.api.ldap.model.message.UnbindRequest;
134import org.apache.directory.api.ldap.model.message.UnbindRequestImpl;
135import org.apache.directory.api.ldap.model.message.controls.ManageDsaITImpl;
136import org.apache.directory.api.ldap.model.message.controls.OpaqueControl;
137import org.apache.directory.api.ldap.model.message.extended.AddNoDResponse;
138import org.apache.directory.api.ldap.model.message.extended.BindNoDResponse;
139import org.apache.directory.api.ldap.model.message.extended.CompareNoDResponse;
140import org.apache.directory.api.ldap.model.message.extended.DeleteNoDResponse;
141import org.apache.directory.api.ldap.model.message.extended.ExtendedNoDResponse;
142import org.apache.directory.api.ldap.model.message.extended.ModifyDnNoDResponse;
143import org.apache.directory.api.ldap.model.message.extended.ModifyNoDResponse;
144import org.apache.directory.api.ldap.model.message.extended.NoticeOfDisconnect;
145import org.apache.directory.api.ldap.model.message.extended.SearchNoDResponse;
146import org.apache.directory.api.ldap.model.name.Dn;
147import org.apache.directory.api.ldap.model.name.Rdn;
148import org.apache.directory.api.ldap.model.schema.AttributeType;
149import org.apache.directory.api.ldap.model.schema.ObjectClass;
150import org.apache.directory.api.ldap.model.schema.SchemaManager;
151import org.apache.directory.api.ldap.model.schema.parsers.OpenLdapSchemaParser;
152import org.apache.directory.api.ldap.model.schema.registries.Registries;
153import org.apache.directory.api.ldap.model.schema.registries.SchemaLoader;
154import org.apache.directory.api.ldap.schema.manager.impl.DefaultSchemaManager;
155import org.apache.directory.api.util.Network;
156import org.apache.directory.api.util.StringConstants;
157import org.apache.directory.api.util.Strings;
158import org.apache.directory.ldap.client.api.callback.SaslCallbackHandler;
159import org.apache.directory.ldap.client.api.exception.InvalidConnectionException;
160import org.apache.directory.ldap.client.api.exception.LdapConnectionTimeOutException;
161import org.apache.directory.ldap.client.api.future.AddFuture;
162import org.apache.directory.ldap.client.api.future.BindFuture;
163import org.apache.directory.ldap.client.api.future.CompareFuture;
164import org.apache.directory.ldap.client.api.future.DeleteFuture;
165import org.apache.directory.ldap.client.api.future.ExtendedFuture;
166import org.apache.directory.ldap.client.api.future.HandshakeFuture;
167import org.apache.directory.ldap.client.api.future.ModifyDnFuture;
168import org.apache.directory.ldap.client.api.future.ModifyFuture;
169import org.apache.directory.ldap.client.api.future.ResponseFuture;
170import org.apache.directory.ldap.client.api.future.SearchFuture;
171import org.apache.mina.core.filterchain.IoFilter;
172import org.apache.mina.core.filterchain.IoFilterChain;
173import org.apache.mina.core.future.CloseFuture;
174import org.apache.mina.core.future.ConnectFuture;
175import org.apache.mina.core.future.WriteFuture;
176import org.apache.mina.core.service.IoConnector;
177import org.apache.mina.core.session.IoSession;
178import org.apache.mina.filter.FilterEvent;
179import org.apache.mina.filter.codec.ProtocolCodecFilter;
180import org.apache.mina.filter.codec.ProtocolEncoderException;
181import org.apache.mina.filter.ssl.SslEvent;
182import org.apache.mina.filter.ssl.SslFilter;
183import org.apache.mina.transport.socket.SocketSessionConfig;
184import org.apache.mina.transport.socket.nio.NioSocketConnector;
185import org.slf4j.Logger;
186import org.slf4j.LoggerFactory;
187
188
189/**
190 * This class is the base for every operations sent or received to and
191 * from a LDAP server.
192 *
193 * A connection instance is necessary to send requests to the server. The connection
194 * is valid until either the client closes it, the server closes it or the
195 * client does an unbind.
196 *
197 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
198 */
199public class LdapNetworkConnection extends AbstractLdapConnection implements LdapAsyncConnection
200{
201
202    /** logger for reporting errors that might not be handled properly upstream */
203    private static final Logger LOG = LoggerFactory.getLogger( LdapNetworkConnection.class );
204
205    /** The timeout used for response we are waiting for */
206    private long timeout = LdapConnectionConfig.DEFAULT_TIMEOUT;
207
208    /** Timeout for connect and bind operations */
209    private long connectTimeout = LdapConnectionConfig.DEFAULT_TIMEOUT;
210
211    /** Timeout for write operations, such as add, modify and delete */
212    private long writeOperationTimeout = LdapConnectionConfig.DEFAULT_TIMEOUT;
213
214    /** Timeout for read operations, suc as search and compare */
215    private long readOperationTimeout = LdapConnectionConfig.DEFAULT_TIMEOUT;
216
217    /** Timeout for close and unbind operations */
218    private long closeTimeout = LdapConnectionConfig.DEFAULT_TIMEOUT;
219
220    /** Timeout for I/O (TCP) writes */
221    private long sendTimeout = LdapConnectionConfig.DEFAULT_TIMEOUT;
222
223    /** configuration object for the connection */
224    private LdapConnectionConfig config;
225    
226    /** The Socket configuration */
227    private SocketSessionConfig socketSessionConfig;
228
229    /** The connector open with the remote server */
230    private IoConnector connector;
231
232    /** A mutex used to avoid a double close of the connector */
233    private ReentrantLock connectorMutex = new ReentrantLock();
234
235    /**
236     * The created session, created when we open a connection with
237     * the Ldap server.
238     */
239    private IoSession ioSession;
240
241    /** a map to hold the ResponseFutures for all operations */
242    private Map<Integer, ResponseFuture<? extends Response>> futureMap = new ConcurrentHashMap<>();
243
244    /** list of controls supported by the server */
245    private List<String> supportedControls;
246
247    /** The ROOT DSE entry */
248    private Entry rootDse;
249
250    /** A flag indicating that the BindRequest has been issued and successfully authenticated the user */
251    private AtomicBoolean authenticated = new AtomicBoolean( false );
252
253    /** a list of listeners interested in getting notified when the
254     *  connection's session gets closed cause of network issues
255     */
256    private List<ConnectionClosedEventListener> conCloseListeners;
257
258    /** The LDAP codec protocol filter */
259    private IoFilter ldapProtocolFilter = new ProtocolCodecFilter( codec.getProtocolCodecFactory() );
260
261    /** The LDAP coded protocol filter key */
262    private static final String LDAP_CODEC_FILTER_KEY = "ldapCodec";
263
264    /** The SslFilter key */
265    private static final String SSL_FILTER_KEY = "sslFilter";
266
267    /** The SaslFilter key */
268    private static final String SASL_FILTER_KEY = "saslFilter";
269
270    /** The exception stored in the session if we've got one */
271    private static final String EXCEPTION_KEY = "sessionException";
272
273    /** The krb5 configuration property */
274    private static final String KRB5_CONF = "java.security.krb5.conf";
275    
276    /** A future used to block any action until the handshake is completed */
277    private HandshakeFuture handshakeFuture;
278    
279    /** A future used to wait for a connection to be closed */
280    private CompletableFuture<Integer> connectionCloseFuture = new CompletableFuture<>(); 
281    
282    // ~~~~~~~~~~~~~~~~~ common error messages ~~~~~~~~~~~~~~~~~~~~~~~~~~
283    static final String TIME_OUT_ERROR = I18n.err( I18n.ERR_04170_TIMEOUT_OCCURED );
284
285    static final String NO_RESPONSE_ERROR = I18n.err( I18n.ERR_04169_RESPONSE_QUEUE_EMPTIED );
286    
287   //------------------------- The constructors --------------------------//
288    /**
289     * Create a new instance of a LdapConnection on localhost,
290     * port 389.
291     */
292    public LdapNetworkConnection()
293    {
294        this( null, -1, false );
295    }
296
297
298    /**
299     *
300     * Creates a new instance of LdapConnection with the given connection configuration.
301     *
302     * @param config the configuration of the LdapConnection
303     */
304    public LdapNetworkConnection( LdapConnectionConfig config )
305    {
306        this( config, LdapApiServiceFactory.getSingleton() );
307    }
308
309
310    /**
311     * Creates a new LdapNetworkConnection instance
312     * 
313     * @param config The configuration to use
314     * @param ldapApiService The LDAP API Service to use
315     */
316    public LdapNetworkConnection( LdapConnectionConfig config, LdapApiService ldapApiService )
317    {
318        super( ldapApiService );
319        this.config = config;
320
321        if ( config.getBinaryAttributeDetector() == null )
322        {
323            config.setBinaryAttributeDetector( new DefaultConfigurableBinaryAttributeDetector() );
324        }
325        
326        this.timeout = config.getTimeout();
327        this.connectTimeout = determineTimeoutConfiguration( config.getConnectTimeout() );
328        this.writeOperationTimeout = determineTimeoutConfiguration( config.getWriteOperationTimeout() );
329        this.readOperationTimeout = determineTimeoutConfiguration( config.getReadOperationTimeout() );
330        this.closeTimeout = determineTimeoutConfiguration( config.getCloseTimeout() );
331        this.sendTimeout = determineTimeoutConfiguration( config.getSendTimeout() );
332    }
333
334    private long determineTimeoutConfiguration( Long localTimeout )
335    {
336        return localTimeout == null ? this.timeout : localTimeout;
337    }
338
339    /**
340     * Create a new instance of a LdapConnection on localhost,
341     * port 389 if the SSL flag is off, or 636 otherwise.
342     *
343     * @param useSsl A flag to tell if it's a SSL connection or not.
344     */
345    public LdapNetworkConnection( boolean useSsl )
346    {
347        this( null, -1, useSsl );
348    }
349
350
351    /**
352     * Creates a new LdapNetworkConnection instance
353     * 
354     * @param useSsl If we are going to create a secure connection or not
355     * @param ldapApiService The LDAP API Service to use
356     */
357    public LdapNetworkConnection( boolean useSsl, LdapApiService ldapApiService )
358    {
359        this( null, -1, useSsl, ldapApiService );
360    }
361
362
363    /**
364     * Create a new instance of a LdapConnection on a given
365     * server, using the default port (389).
366     *
367     * @param server The server we want to be connected to. If null or empty,
368     * we will default to LocalHost.
369     */
370    public LdapNetworkConnection( String server )
371    {
372        this( server, -1, false );
373    }
374
375
376    /**
377     * Creates a new LdapNetworkConnection instance
378     * 
379     * @param server The server we want to be connected to. If null or empty,
380     * we will default to LocalHost.
381     * @param ldapApiService The LDAP API Service to use
382     */
383    public LdapNetworkConnection( String server, LdapApiService ldapApiService )
384    {
385        this( server, -1, false, ldapApiService );
386    }
387
388
389    /**
390     * Create a new instance of a LdapConnection on a given
391     * server, using the default port (389) if the SSL flag
392     * is off, or 636 otherwise.
393     *
394     * @param server The server we want to be connected to. If null or empty,
395     * we will default to LocalHost.
396     * @param useSsl A flag to tell if it's a SSL connection or not.
397     */
398    public LdapNetworkConnection( String server, boolean useSsl )
399    {
400        this( server, -1, useSsl );
401    }
402
403
404    /**
405     * Creates a new LdapNetworkConnection instance
406     * 
407     * @param server The server we want to be connected to. If null or empty,
408     * we will default to LocalHost.
409     * @param useSsl A flag to tell if it's a SSL connection or not.
410     * @param ldapApiService The LDAP API Service to use
411     */
412    public LdapNetworkConnection( String server, boolean useSsl, LdapApiService ldapApiService )
413    {
414        this( server, -1, useSsl, ldapApiService );
415    }
416
417
418    /**
419     * Create a new instance of a LdapConnection on a
420     * given server and a given port. We don't use ssl.
421     *
422     * @param server The server we want to be connected to
423     * @param port The port the server is listening to
424     */
425    public LdapNetworkConnection( String server, int port )
426    {
427        this( server, port, false );
428    }
429
430
431    /**
432     * Create a new instance of a LdapConnection on a
433     * given server and a given port. We don't use ssl.
434     *
435     * @param server The server we want to be connected to. If null or empty,
436     * we will default to LocalHost.
437     * @param port The port the server is listening on
438     * @param ldapApiService The LDAP API Service to use
439     */
440    public LdapNetworkConnection( String server, int port, LdapApiService ldapApiService )
441    {
442        this( server, port, false, ldapApiService );
443    }
444
445
446    /**
447     * Create a new instance of a LdapConnection on a given
448     * server, and a give port. We set the SSL flag accordingly
449     * to the last parameter.
450     *
451     * @param server The server we want to be connected to. If null or empty,
452     * we will default to LocalHost.
453     * @param port The port the server is listening to
454     * @param useSsl A flag to tell if it's a SSL connection or not.
455     */
456    public LdapNetworkConnection( String server, int port, boolean useSsl )
457    {
458        this( buildConfig( server, port, useSsl ) );
459    }
460    
461    
462    /**
463     * Create a new instance of a LdapConnection on a given
464     * server, and a give port. This SSL connection will use the provided
465     * TrustManagers
466     *
467     * @param server The server we want to be connected to. If null or empty,
468     * we will default to LocalHost.
469     * @param port The port the server is listening to
470     * @param trustManagers The TrustManager to use
471     */
472    public LdapNetworkConnection( String server, int port, TrustManager... trustManagers )
473    {
474        this( buildConfig( server, port, true ) );
475        
476        config.setTrustManagers( trustManagers );
477    }
478
479
480    /**
481     * Create a new instance of a LdapConnection on a
482     * given server and a given port. We don't use ssl.
483     *
484     * @param server The server we want to be connected to. If null or empty,
485     * we will default to LocalHost.
486     * @param port The port the server is listening on
487     * @param useSsl A flag to tell if it's a SSL connection or not.
488     * @param ldapApiService The LDAP API Service to use
489     */
490    public LdapNetworkConnection( String server, int port, boolean useSsl, LdapApiService ldapApiService )
491    {
492        this( buildConfig( server, port, useSsl ), ldapApiService );
493    }
494
495
496    private static LdapConnectionConfig buildConfig( String server, int port, boolean useSsl )
497    {
498        LdapConnectionConfig config = new LdapConnectionConfig();
499        config.setUseSsl( useSsl );
500
501        if ( port != -1 )
502        {
503            config.setLdapPort( port );
504        }
505        else
506        {
507            if ( useSsl )
508            {
509                config.setLdapPort( config.getDefaultLdapsPort() );
510            }
511            else
512            {
513                config.setLdapPort( config.getDefaultLdapPort() );
514            }
515        }
516
517        // Default to localhost if null
518        if ( Strings.isEmpty( server ) )
519        {
520            config.setLdapHost( Network.LOOPBACK_HOSTNAME );
521            
522        }
523        else
524        {
525            config.setLdapHost( server );
526        }
527
528        config.setBinaryAttributeDetector( new DefaultConfigurableBinaryAttributeDetector() );
529
530        return config;
531    }
532
533
534    /**
535     * Create the connector
536     * 
537     * @throws LdapException If the connector can't be created
538     */
539    private void createConnector() throws LdapException
540    {
541        // Use only one thread inside the connector
542        connector = new NioSocketConnector( 1 );
543        
544        if ( socketSessionConfig != null )
545        {
546            ( ( SocketSessionConfig ) connector.getSessionConfig() ).setAll( socketSessionConfig );
547        }
548        else
549        {
550            ( ( SocketSessionConfig ) connector.getSessionConfig() ).setReuseAddress( true );
551        }
552
553        // Add the codec to the chain
554        connector.getFilterChain().addLast( LDAP_CODEC_FILTER_KEY, ldapProtocolFilter );
555
556        // If we use SSL, we have to add the SslFilter to the chain
557        if ( config.isUseSsl() )
558        {
559            addSslFilter();
560        }
561
562        // Inject the protocolHandler
563        connector.setHandler( this );
564    }
565
566
567    //--------------------------- Helper methods ---------------------------//
568    /**
569     * {@inheritDoc}
570     */
571    @Override
572    public boolean isConnected()
573    {
574        return ( ioSession != null ) && ioSession.isConnected() && !ioSession.isClosing();
575        
576    }
577
578
579    /**
580     * {@inheritDoc}
581     */
582    @Override
583    public boolean isAuthenticated()
584    {
585        return isConnected() && authenticated.get();
586    }
587
588
589    /**
590     * Tells if the connection is using a secured channel
591     * 
592     * @return <tt>true</tt> if the session is using a secured channel
593     */
594    public boolean isSecured()
595    {
596        return isConnected() && ioSession.isSecured();
597    }
598
599    
600    /**
601     * {@inheritDoc}
602     */
603    @Override
604    public Throwable exceptionCaught()
605    {
606        return ( Throwable ) ioSession.getAttribute( EXCEPTION_KEY );
607    }
608    
609    
610    /**
611     * Check that a session is valid, ie we can send requests to the
612     * server
613     *
614     * @throws InvalidConnectionException If the session is not valid
615     */
616    private void checkSession() throws InvalidConnectionException
617    {
618        if ( ioSession == null )
619        {
620            throw new InvalidConnectionException( I18n.err( I18n.ERR_04104_NULL_CONNECTION_CANNOT_CONNECT ) );
621        }
622
623        if ( !isConnected() )
624        {
625            throw new InvalidConnectionException( I18n.err( I18n.ERR_04108_INVALID_CONNECTION ) );
626        }
627    }
628
629
630    private void addToFutureMap( int messageId, ResponseFuture<? extends Response> future )
631    {
632        if ( LOG.isDebugEnabled() )
633        {
634            LOG.debug( I18n.msg( I18n.MSG_04106_ADDING, messageId, future.getClass().getName() ) );
635        }
636        
637        futureMap.put( messageId, future );
638    }
639
640
641    private ResponseFuture<? extends Response> getFromFutureMap( int messageId )
642    {
643        ResponseFuture<? extends Response> future = futureMap.remove( messageId );
644
645        if ( LOG.isDebugEnabled() && ( future != null ) )
646        {
647            LOG.debug( I18n.msg( I18n.MSG_04126_REMOVING, messageId, future.getClass().getName() ) );
648        }
649
650        return future;
651    }
652
653
654    private ResponseFuture<? extends Response> peekFromFutureMap( int messageId )
655    {
656        ResponseFuture<? extends Response> future = futureMap.get( messageId );
657
658        // future can be null if there was a abandon operation on that messageId
659        if ( LOG.isDebugEnabled() && ( future != null ) )
660        {
661            LOG.debug( I18n.msg( I18n.MSG_04119_GETTING, messageId, future.getClass().getName() ) );
662        }
663
664        return future;
665    }
666
667
668    /**
669     * Get the largest timeout from the search time limit and the connection
670     * timeout.
671     * 
672     * @param configuredTimeout Timeout configured in LdapNetworkConnection
673     * @param searchTimeLimitInSeconds Search timeout
674     * @return The largest timeout
675     */
676    public long getTimeout( long configuredTimeout, int searchTimeLimitInSeconds )
677    {
678        if ( searchTimeLimitInSeconds < 0 )
679        {
680            return configuredTimeout;
681        }
682        else if ( searchTimeLimitInSeconds == 0 )
683        {
684            if ( config.getTimeout() == 0 )
685            {
686                return Long.MAX_VALUE;
687            }
688            else
689            {
690                return configuredTimeout;
691            }
692        }
693        else
694        {
695            long searchTimeLimitInMS = searchTimeLimitInSeconds * 1000L;
696            return Math.max( searchTimeLimitInMS, configuredTimeout );
697        }
698    }
699
700    
701    /**
702     * Process the connect. 
703     * 
704     * @exception LdapException If we weren't able to connect
705     * @return A Future that can be used to check the status of the connection
706     */
707    public ConnectFuture tryConnect() throws LdapException
708    {
709        // Build the connection address
710        SocketAddress address = new InetSocketAddress( config.getLdapHost(), config.getLdapPort() );
711        ConnectFuture connectionFuture = connector.connect( address );
712        boolean result = false;
713
714        // Wait until it's established
715        try
716        {
717            result = connectionFuture.await( connectTimeout );
718        }
719        catch ( InterruptedException e )
720        {
721            connector.dispose();
722            connector = null;
723
724            if ( LOG.isDebugEnabled() )
725            {
726                LOG.debug( I18n.msg( I18n.MSG_04120_INTERRUPTED_WAITING_FOR_CONNECTION, 
727                    config.getLdapHost(),
728                    config.getLdapPort() ), e );
729            }
730            
731            throw new LdapOtherException( e.getMessage(), e );
732        }
733
734        if ( !result )
735        {
736            // It may be an exception, or a timeout
737            Throwable connectionException = connectionFuture.getException();
738
739            if ( ( connector != null ) && !connector.isDisposing() && !connector.isDisposed() )
740            { 
741                connector.dispose();
742            }
743
744            connector = null;
745
746            if ( connectionException == null )
747            {
748                // This was a timeout
749                String message = I18n.msg( I18n.MSG_04177_CONNECTION_TIMEOUT, connectTimeout );
750                
751                if ( LOG.isDebugEnabled() )
752                {
753                    LOG.debug( message );
754                }
755                
756                throw new LdapConnectionTimeOutException( message );
757            }
758            else
759            {
760                if ( LOG.isDebugEnabled() )
761                {
762                    if ( ( connectionException instanceof ConnectException )
763                        || ( connectionException instanceof UnresolvedAddressException ) )
764                    {
765                        // No need to wait
766                        // We know that there was a permanent error such as "connection refused".
767                        LOG.debug( I18n.msg( I18n.MSG_04144_CONNECTION_ERROR, connectionFuture.getException().getMessage() ) );
768                    }
769    
770                    LOG.debug( I18n.msg( I18n.MSG_04120_INTERRUPTED_WAITING_FOR_CONNECTION, 
771                        config.getLdapHost(),
772                        config.getLdapPort() ), connectionException );
773                }
774                
775                throw new LdapOtherException( connectionException.getMessage(), connectionException );
776            }
777        }
778        
779        return connectionFuture;
780    }
781    
782    
783    /**
784     * Close the connection and generate the appropriate exception
785     * 
786     * @exception LdapException If we weren't able to close the connection
787     */
788    private void close( ConnectFuture connectionFuture ) throws LdapException
789    {
790        // disposing connector if not connected
791        close();
792
793        Throwable e = connectionFuture.getException();
794
795        if ( e != null )
796        {
797            // Special case for UnresolvedAddressException
798            // (most of the time no message is associated with this exception)
799            if ( ( e instanceof UnresolvedAddressException ) && ( e.getMessage() == null ) )
800            {
801                throw new InvalidConnectionException( I18n.err( I18n.ERR_04121_CANNOT_RESOLVE_HOSTNAME, config.getLdapHost() ), e );
802            }
803
804            // Default case
805            throw new InvalidConnectionException( I18n.err( I18n.ERR_04110_CANNOT_CONNECT_TO_SERVER, e.getMessage() ), e );
806        }
807
808        // We didn't received anything : this is an error
809        if ( LOG.isErrorEnabled() )
810        {
811            LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Connect" ) );
812        }
813
814        throw new LdapException( TIME_OUT_ERROR );
815    }
816    
817    
818    /**
819     * Verify that the connection has been secured, otherwise throw a meaningful exception
820     * 
821     * @exception LdapException If we weren't able to check that the connection is secured
822     */
823    private void checkSecured( ConnectFuture connectionFuture ) throws LdapException
824    {
825        try
826        {
827            boolean isSecured = handshakeFuture.get( connectTimeout, TimeUnit.MILLISECONDS );
828
829            if ( !isSecured )
830            {
831                // check for a specific cause
832                Throwable cause = connectionFuture.getException();
833                
834                if ( cause == null && connectionFuture.getSession() != null )
835                {
836                    cause = ( Throwable ) connectionFuture.getSession().getAttribute( EXCEPTION_KEY );
837                }
838                
839                // Cancel the latch
840                connectionCloseFuture.complete( 0 );
841
842                // if there is no cause assume timeout
843                if ( cause == null )
844                {
845                    throw new LdapException( TIME_OUT_ERROR );
846                }
847
848                throw new LdapTlsHandshakeException( I18n.err( I18n.ERR_04120_TLS_HANDSHAKE_ERROR ), cause );
849            }
850        }
851        catch ( Exception e )
852        {
853            if ( e instanceof LdapException )
854            {
855                throw ( LdapException ) e;
856            }
857
858            String msg = I18n.err( I18n.ERR_04122_SSL_CONTEXT_INIT_FAILURE );
859            LOG.error( msg, e );
860            throw new LdapException( msg, e );
861        }
862    }
863    
864    
865    /**
866     * Set a listener associated to the closeFuture
867     * 
868     * @param connectionFuture A Future for which we want to set a listener
869     */
870    private void setCloseListener( ConnectFuture connectionFuture )
871    {
872        // Get the close future for this session
873        CloseFuture closeFuture = connectionFuture.getSession().getCloseFuture();
874        
875        closeFuture.addListener( future -> 
876        {
877            // Process all the waiting operations and cancel them
878            if ( LOG.isDebugEnabled() )
879            {
880                LOG.debug( I18n.msg( I18n.MSG_04137_NOD_RECEIVED ) );
881            }
882
883            for ( ResponseFuture<?> responseFuture : futureMap.values() )
884            {
885                if ( LOG.isDebugEnabled() )
886                {
887                    LOG.debug( I18n.msg( I18n.MSG_04137_NOD_RECEIVED ) );
888                }
889
890                responseFuture.cancel();
891
892                try
893                {
894                    if ( responseFuture instanceof AddFuture )
895                    {
896                        ( ( AddFuture ) responseFuture ).set( AddNoDResponse.PROTOCOLERROR );
897                    }
898                    else if ( responseFuture instanceof BindFuture )
899                    {
900                        ( ( BindFuture ) responseFuture ).set( BindNoDResponse.PROTOCOLERROR );
901                    }
902                    else if ( responseFuture instanceof CompareFuture )
903                    {
904                        ( ( CompareFuture ) responseFuture ).set( CompareNoDResponse.PROTOCOLERROR );
905                    }
906                    else if ( responseFuture instanceof DeleteFuture )
907                    {
908                        ( ( DeleteFuture ) responseFuture ).set( DeleteNoDResponse.PROTOCOLERROR );
909                    }
910                    else if ( responseFuture instanceof ExtendedFuture )
911                    {
912                        ( ( ExtendedFuture ) responseFuture ).set( ExtendedNoDResponse.PROTOCOLERROR );
913                    }
914                    else if ( responseFuture instanceof ModifyFuture )
915                    {
916                        ( ( ModifyFuture ) responseFuture ).set( ModifyNoDResponse.PROTOCOLERROR );
917                    }
918                    else if ( responseFuture instanceof ModifyDnFuture )
919                    {
920                        ( ( ModifyDnFuture ) responseFuture ).set( ModifyDnNoDResponse.PROTOCOLERROR );
921                    }
922                    else if ( responseFuture instanceof SearchFuture )
923                    {
924                        ( ( SearchFuture ) responseFuture ).set( SearchNoDResponse.PROTOCOLERROR );
925                    }
926                }
927                catch ( InterruptedException e )
928                {
929                    LOG.error( I18n.err( I18n.ERR_04113_ERROR_PROCESSING_NOD, responseFuture ), e );
930                }
931
932                futureMap.remove( messageId.get() );
933            }
934
935            futureMap.clear();
936        } );
937    }
938    
939    
940    /**
941     * Set the BinaryDetector instance in the session
942     */
943    private void setBinaryDetector()
944    {
945        @SuppressWarnings("unchecked")
946        LdapMessageContainer<? extends Message> container =
947            ( LdapMessageContainer<? extends Message> ) ioSession
948                .getAttribute( LdapDecoder.MESSAGE_CONTAINER_ATTR );
949
950        if ( container != null )
951        {
952            if ( ( schemaManager != null ) && !( container.getBinaryAttributeDetector() instanceof SchemaBinaryAttributeDetector ) )
953            {
954                container.setBinaryAttributeDetector( new SchemaBinaryAttributeDetector( schemaManager ) );
955            }
956        }
957        else
958        {
959            BinaryAttributeDetector atDetector = new DefaultConfigurableBinaryAttributeDetector();
960
961            if ( schemaManager != null )
962            {
963                atDetector = new SchemaBinaryAttributeDetector( schemaManager );
964            }
965
966            ioSession.setAttribute( LdapDecoder.MESSAGE_CONTAINER_ATTR,
967                new LdapMessageContainer<Message>( codec, atDetector ) );
968        }
969    }
970    
971
972    //-------------------------- The methods ---------------------------//
973    /**
974     * {@inheritDoc}
975     */
976    @Override
977    public boolean connect() throws LdapException
978    {
979        if ( isConnected() )
980        {
981            // No need to connect if we already have a connected session
982            return true;
983        }
984        
985        try
986        {
987            // Create the connector if needed
988            if ( connector == null )
989            {
990                createConnector();
991            }
992    
993            // And create the connection future
994            ConnectFuture connectionFuture = tryConnect();
995    
996            // Check if we are good to go
997            if ( !connectionFuture.isConnected() )
998            {
999                // Release the latch
1000                connectionCloseFuture.cancel( true );
1001                
1002                close( connectionFuture );
1003            }
1004    
1005            // Check if we are secured if requested
1006            if ( config.isUseSsl() )
1007            {
1008                checkSecured( connectionFuture );
1009            }
1010    
1011            // Add a listener to close the session in the session.
1012            setCloseListener( connectionFuture );
1013    
1014            // Get back the session
1015            ioSession = connectionFuture.getSession();
1016    
1017            // Store the container into the session if we don't have one
1018            setBinaryDetector();
1019    
1020            // Initialize the MessageId
1021            messageId.set( 0 );
1022            
1023            connectionCloseFuture = new CompletableFuture<>();
1024
1025            // establish TLS layer if TLS is enabled and SSL is NOT
1026            if ( config.isUseTls() && !config.isUseSsl() )
1027            {
1028                startTls();
1029            }
1030
1031            // And return
1032            return true;
1033        }
1034        catch ( Exception e )
1035        {
1036            if ( ( connector != null ) && !connector.isDisposing() && !connector.isDisposed() ) 
1037            {
1038                connector.dispose();
1039                connector = null;
1040            }
1041
1042            throw e;
1043        }
1044    }
1045
1046
1047    /**
1048     * {@inheritDoc}
1049     */
1050    @Override
1051    public void close()
1052    {
1053        // Close the session
1054        if ( isConnected() )
1055        {
1056            ioSession.closeNow();
1057        }
1058
1059        try
1060        {
1061            if ( ( ioSession != null ) && ioSession.isConnected() )
1062            { 
1063                connectionCloseFuture.get( closeTimeout, TimeUnit.MILLISECONDS );
1064            }
1065        }
1066        catch ( TimeoutException | ExecutionException | InterruptedException e )
1067        {
1068            if ( LOG.isDebugEnabled() )
1069            {
1070                LOG.debug( I18n.msg( I18n.MSH_04178_CLOSE_LATCH_ABORTED ) );
1071            }
1072        }
1073    }
1074
1075
1076    //------------------------ The LDAP operations ------------------------//
1077    // Add operations                                                      //
1078    //---------------------------------------------------------------------//
1079    /**
1080     * {@inheritDoc}
1081     */
1082    @Override
1083    public void add( Entry entry ) throws LdapException
1084    {
1085        if ( entry == null )
1086        {
1087            String msg = I18n.err( I18n.ERR_04123_CANNOT_ADD_EMPTY_ENTRY );
1088            
1089            if ( LOG.isDebugEnabled() )
1090            {
1091                LOG.debug( msg );
1092            }
1093            
1094            throw new IllegalArgumentException( msg );
1095        }
1096
1097        AddRequest addRequest = new AddRequestImpl();
1098        addRequest.setEntry( entry );
1099
1100        AddResponse addResponse = add( addRequest );
1101
1102        processResponse( addResponse );
1103    }
1104
1105
1106    /**
1107     * {@inheritDoc}
1108     */
1109    @Override
1110    public AddFuture addAsync( Entry entry ) throws LdapException
1111    {
1112        if ( entry == null )
1113        {
1114            String msg = I18n.err( I18n.ERR_04125_CANNOT_ADD_NULL_ENTRY );
1115            
1116            if ( LOG.isDebugEnabled() )
1117            {
1118                LOG.debug( msg );
1119            }
1120            
1121            throw new IllegalArgumentException( msg );
1122        }
1123
1124        AddRequest addRequest = new AddRequestImpl();
1125        addRequest.setEntry( entry );
1126
1127        return addAsync( addRequest );
1128    }
1129
1130
1131    /**
1132     * {@inheritDoc}
1133     */
1134    @Override
1135    public AddResponse add( AddRequest addRequest ) throws LdapException
1136    {
1137        if ( addRequest == null )
1138        {
1139            String msg = I18n.err( I18n.ERR_04124_CANNOT_PROCESS_NULL_ADD_REQUEST );
1140
1141            if ( LOG.isDebugEnabled() )
1142            {
1143                LOG.debug( msg );
1144            }
1145            
1146            throw new IllegalArgumentException( msg );
1147        }
1148
1149        if ( addRequest.getEntry() == null )
1150        {
1151            String msg = I18n.err( I18n.ERR_04125_CANNOT_ADD_NULL_ENTRY );
1152
1153            if ( LOG.isDebugEnabled() )
1154            {
1155                LOG.debug( msg );
1156            }
1157            
1158            throw new IllegalArgumentException( msg );
1159        }
1160
1161        AddFuture addFuture = addAsync( addRequest );
1162
1163        // Get the result from the future
1164        try
1165        {
1166            // Read the response, waiting for it if not available immediately
1167            // Get the response, blocking
1168            AddResponse addResponse = addFuture.get( writeOperationTimeout, TimeUnit.MILLISECONDS );
1169
1170            if ( addResponse == null )
1171            {
1172                // We didn't received anything : this is an error
1173                if ( LOG.isErrorEnabled() )
1174                {
1175                    LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Add" ) );
1176                }
1177                
1178                throw new LdapException( TIME_OUT_ERROR );
1179            }
1180
1181            if ( addResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
1182            {
1183                // Everything is fine, return the response
1184                if ( LOG.isDebugEnabled() )
1185                { 
1186                    LOG.debug( I18n.msg( I18n.MSG_04108_ADD_SUCCESSFUL, addResponse ) );
1187                }
1188            }
1189            else
1190            {
1191                // We have had an error
1192                if ( LOG.isDebugEnabled() )
1193                { 
1194                    LOG.debug( I18n.msg( I18n.MSG_04107_ADD_FAILED, addResponse ) );
1195                }
1196            }
1197
1198            return addResponse;
1199        }
1200        catch ( Exception ie )
1201        {
1202            // Catch all other exceptions
1203            LOG.error( NO_RESPONSE_ERROR, ie );
1204
1205            // Send an abandon request
1206            if ( !addFuture.isCancelled() )
1207            {
1208                abandon( addRequest.getMessageId() );
1209            }
1210
1211            throw new LdapException( NO_RESPONSE_ERROR, ie );
1212        }
1213    }
1214
1215
1216    /**
1217     * {@inheritDoc}
1218     */
1219    @Override
1220    public AddFuture addAsync( AddRequest addRequest ) throws LdapException
1221    {
1222        if ( addRequest == null )
1223        {
1224            String msg = I18n.err( I18n.ERR_04124_CANNOT_PROCESS_NULL_ADD_REQUEST );
1225
1226            if ( LOG.isDebugEnabled() )
1227            {
1228                LOG.debug( msg );
1229            }
1230            
1231            throw new IllegalArgumentException( msg );
1232        }
1233
1234        if ( addRequest.getEntry() == null )
1235        {
1236            String msg = I18n.err( I18n.ERR_04125_CANNOT_ADD_NULL_ENTRY );
1237
1238            if ( LOG.isDebugEnabled() )
1239            {
1240                LOG.debug( msg );
1241            }
1242            
1243            throw new IllegalArgumentException( msg );
1244        }
1245
1246        // try to connect, if we aren't already connected.
1247        connect();
1248
1249        checkSession();
1250
1251        int newId = messageId.incrementAndGet();
1252
1253        addRequest.setMessageId( newId );
1254        AddFuture addFuture = new AddFuture( this, newId );
1255        addToFutureMap( newId, addFuture );
1256
1257        // Send the request to the server
1258        writeRequest( addRequest );
1259
1260        // Ok, done return the future
1261        return addFuture;
1262    }
1263
1264
1265    //------------------------ The LDAP operations ------------------------//
1266
1267    /**
1268     * {@inheritDoc}
1269     */
1270    @Override
1271    public void abandon( int messageId )
1272    {
1273        if ( messageId < 0 )
1274        {
1275            String msg = I18n.err( I18n.ERR_04126_CANNOT_ABANDON_NEG_MSG_ID );
1276
1277            if ( LOG.isDebugEnabled() )
1278            {
1279                LOG.debug( msg );
1280            }
1281            
1282            throw new IllegalArgumentException( msg );
1283        }
1284
1285        AbandonRequest abandonRequest = new AbandonRequestImpl();
1286        abandonRequest.setAbandoned( messageId );
1287
1288        abandonInternal( abandonRequest );
1289    }
1290
1291
1292    /**
1293     * {@inheritDoc}
1294     */
1295    @Override
1296    public void abandon( AbandonRequest abandonRequest )
1297    {
1298        if ( abandonRequest == null )
1299        {
1300            String msg = I18n.err( I18n.ERR_04127_CANNOT_PROCESS_NULL_ABANDON_REQ );
1301
1302            if ( LOG.isDebugEnabled() )
1303            {
1304                LOG.debug( msg );
1305            }
1306            
1307            throw new IllegalArgumentException( msg );
1308        }
1309
1310        abandonInternal( abandonRequest );
1311    }
1312
1313
1314    /**
1315     * Internal AbandonRequest handling
1316     * 
1317     * @param abandonRequest The request to abandon
1318     */
1319    private void abandonInternal( AbandonRequest abandonRequest )
1320    {
1321        if ( LOG.isDebugEnabled() )
1322        {
1323            LOG.debug( I18n.msg( I18n.MSG_04104_SENDING_REQUEST, abandonRequest ) );
1324        }
1325
1326        int newId = messageId.incrementAndGet();
1327        abandonRequest.setMessageId( newId );
1328
1329        // Send the request to the server
1330        ioSession.write( abandonRequest );
1331
1332        // remove the associated listener if any
1333        int abandonId = abandonRequest.getAbandoned();
1334
1335        ResponseFuture<? extends Response> rf = getFromFutureMap( abandonId );
1336
1337        // if the listener is not null, this is a async operation and no need to
1338        // send cancel signal on future, sending so will leave a dangling poision object in the corresponding queue
1339        // this is a sync operation send cancel signal to the corresponding ResponseFuture
1340        if ( rf != null )
1341        {
1342            if ( LOG.isDebugEnabled() )
1343            {
1344                LOG.debug( I18n.msg( I18n.MSG_04141_SENDING_CANCEL ) );
1345            }
1346            
1347            rf.cancel( true );
1348        }
1349        else
1350        {
1351            // this shouldn't happen
1352            if ( LOG.isWarnEnabled() )
1353            {
1354                LOG.warn( I18n.msg( I18n.MSG_04165_NO_FUTURE_ASSOCIATED_TO_MSG_ID_COMPLETED, abandonId ) );
1355            }
1356        }
1357    }
1358
1359
1360    /**
1361     * {@inheritDoc}
1362     */
1363    @Override
1364    public void bind() throws LdapException
1365    {
1366        if ( LOG.isDebugEnabled() )
1367        {
1368            LOG.debug( I18n.msg(  I18n.MSG_04112_BIND ) );
1369        }
1370
1371        // Create the BindRequest
1372        BindRequest bindRequest = createBindRequest( config.getName(), Strings.getBytesUtf8( config.getCredentials() ) );
1373
1374        BindResponse bindResponse = bind( bindRequest );
1375
1376        processResponse( bindResponse );
1377    }
1378
1379
1380    /**
1381     * {@inheritDoc}
1382     */
1383    @Override
1384    public void anonymousBind() throws LdapException
1385    {
1386        if ( LOG.isDebugEnabled() )
1387        { 
1388            LOG.debug( I18n.msg( I18n.MSG_04109_ANONYMOUS_BIND ) );
1389        }
1390
1391        // Create the BindRequest
1392        BindRequest bindRequest = createBindRequest( StringConstants.EMPTY, Strings.EMPTY_BYTES );
1393
1394        BindResponse bindResponse = bind( bindRequest );
1395
1396        processResponse( bindResponse );
1397    }
1398
1399
1400    /**
1401     * {@inheritDoc}
1402     */
1403    @Override
1404    public BindFuture bindAsync() throws LdapException
1405    {
1406        if ( LOG.isDebugEnabled() )
1407        {
1408            LOG.debug( I18n.msg( I18n.MSG_04111_ASYNC_BIND ) );
1409        }
1410
1411        // Create the BindRequest
1412        BindRequest bindRequest = createBindRequest( config.getName(), Strings.getBytesUtf8( config.getCredentials() ) );
1413
1414        return bindAsync( bindRequest );
1415    }
1416
1417
1418    /**
1419     * {@inheritDoc}
1420     */
1421    @Override
1422    public BindFuture anonymousBindAsync() throws LdapException
1423    {
1424        if ( LOG.isDebugEnabled() )
1425        { 
1426            LOG.debug( I18n.msg( I18n.MSG_04110_ANONYMOUS_ASYNC_BIND ) );
1427        }
1428
1429        // Create the BindRequest
1430        BindRequest bindRequest = createBindRequest( StringConstants.EMPTY, Strings.EMPTY_BYTES );
1431
1432        return bindAsync( bindRequest );
1433    }
1434
1435
1436    /**
1437     * Asynchronous unauthenticated authentication bind
1438     *
1439     * @param name The name we use to authenticate the user. It must be a
1440     * valid Dn
1441     * @return The BindResponse LdapResponse
1442     * @throws LdapException if some error occurred
1443     */
1444    public BindFuture bindAsync( String name ) throws LdapException
1445    {
1446        if ( LOG.isDebugEnabled() )
1447        {
1448            LOG.debug( I18n.msg( I18n.MSG_04102_BIND_REQUEST, name ) );
1449        }
1450
1451        // Create the BindRequest
1452        BindRequest bindRequest = createBindRequest( name, Strings.EMPTY_BYTES );
1453
1454        return bindAsync( bindRequest );
1455    }
1456
1457
1458    /**
1459     * {@inheritDoc}
1460     */
1461    @Override
1462    public BindFuture bindAsync( String name, String credentials ) throws LdapException
1463    {
1464        if ( LOG.isDebugEnabled() )
1465        {
1466            LOG.debug( I18n.msg( I18n.MSG_04102_BIND_REQUEST, name ) );
1467        }
1468
1469        // The password must not be empty or null
1470        if ( Strings.isEmpty( credentials ) && Strings.isNotEmpty( name ) )
1471        {
1472            if ( LOG.isDebugEnabled() )
1473            {
1474                LOG.debug( I18n.msg( I18n.MSG_04105_MISSING_PASSWORD ) );
1475            }
1476            
1477            throw new LdapAuthenticationException( I18n.msg( I18n.MSG_04105_MISSING_PASSWORD ) );
1478        }
1479
1480        // Create the BindRequest
1481        BindRequest bindRequest = createBindRequest( name, Strings.getBytesUtf8( credentials ) );
1482
1483        return bindAsync( bindRequest );
1484    }
1485
1486
1487    /**
1488     * Asynchronous unauthenticated authentication Bind on a server.
1489     *
1490     * @param name The name we use to authenticate the user. It must be a
1491     * valid Dn
1492     * @return The BindResponse LdapResponse
1493     * @throws LdapException if some error occurred
1494     */
1495    public BindFuture bindAsync( Dn name ) throws LdapException
1496    {
1497        if ( LOG.isDebugEnabled() )
1498        {
1499            LOG.debug( I18n.msg( I18n.MSG_04102_BIND_REQUEST, name ) );
1500        }
1501
1502        // Create the BindRequest
1503        BindRequest bindRequest = createBindRequest( name, Strings.EMPTY_BYTES );
1504
1505        return bindAsync( bindRequest );
1506    }
1507
1508
1509    /**
1510     * {@inheritDoc}
1511     */
1512    @Override
1513    public BindFuture bindAsync( Dn name, String credentials ) throws LdapException
1514    {
1515        if ( LOG.isDebugEnabled() )
1516        {
1517            LOG.debug( I18n.msg( I18n.MSG_04102_BIND_REQUEST, name ) );
1518        }
1519
1520        // The password must not be empty or null
1521        if ( Strings.isEmpty( credentials ) && ( !Dn.EMPTY_DN.equals( name ) ) )
1522        {
1523            if ( LOG.isDebugEnabled() )
1524            {
1525                LOG.debug( I18n.msg( I18n.MSG_04105_MISSING_PASSWORD ) );
1526            }
1527            
1528            throw new LdapAuthenticationException( I18n.msg( I18n.MSG_04105_MISSING_PASSWORD ) );
1529        }
1530
1531        // Create the BindRequest
1532        BindRequest bindRequest = createBindRequest( name, Strings.getBytesUtf8( credentials ) );
1533
1534        return bindAsync( bindRequest );
1535    }
1536
1537
1538    /**
1539     * {@inheritDoc}
1540     */
1541    @Override
1542    public BindResponse bind( BindRequest bindRequest ) throws LdapException
1543    {
1544        if ( bindRequest == null )
1545        {
1546            String msg = I18n.err( I18n.ERR_04128_CANNOT_PROCESS_NULL_BIND_REQ );
1547
1548            if ( LOG.isDebugEnabled() )
1549            {
1550                LOG.debug( msg );
1551            }
1552            
1553            throw new IllegalArgumentException( msg );
1554        }
1555
1556        BindFuture bindFuture = bindAsync( bindRequest );
1557
1558        // Get the result from the future
1559        try
1560        {
1561            // Read the response, waiting for it if not available immediately
1562            // Get the response, blocking
1563            BindResponse bindResponse = bindFuture.get( connectTimeout, TimeUnit.MILLISECONDS );
1564
1565            if ( bindResponse == null )
1566            {
1567                // We didn't received anything : this is an error
1568                if ( LOG.isErrorEnabled() )
1569                { 
1570                    LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Bind" ) );
1571                }
1572                
1573                throw new LdapException( TIME_OUT_ERROR );
1574            }
1575
1576            if ( bindResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
1577            {
1578                authenticated.set( true );
1579
1580                // Everything is fine, return the response
1581                if ( LOG.isDebugEnabled() )
1582                { 
1583                    LOG.debug( I18n.msg( I18n.MSG_04101_BIND_SUCCESSFUL, bindResponse ) );
1584                }
1585            }
1586            else
1587            {
1588                // We have had an error
1589                if ( LOG.isDebugEnabled() )
1590                { 
1591                    LOG.debug( I18n.msg( I18n.MSG_04100_BIND_FAIL, bindResponse ) );
1592                }
1593            }
1594
1595            return bindResponse;
1596        }
1597        catch ( Exception ie )
1598        {
1599            // Catch all other exceptions
1600            LOG.error( NO_RESPONSE_ERROR, ie );
1601            
1602            throw new LdapException( NO_RESPONSE_ERROR, ie );
1603        }
1604    }
1605
1606
1607    /**
1608     * Create a Simple BindRequest ready to be sent.
1609     * 
1610     * @param name The Bind name
1611     * @param credentials The Bind credentials
1612     * @return The created BindRequest instance
1613     */
1614    private BindRequest createBindRequest( String name, byte[] credentials )
1615    {
1616        return createBindRequest( name, credentials, null, ( Control[] ) null );
1617    }
1618
1619
1620    /**
1621     * Create a Simple BindRequest ready to be sent.
1622     * 
1623     * @param name The Bind name
1624     * @param credentials The Bind credentials
1625     * @return The created BindRequest instance
1626     */
1627    private BindRequest createBindRequest( Dn name, byte[] credentials )
1628    {
1629        return createBindRequest( name.getName(), credentials, null, ( Control[] ) null );
1630    }
1631
1632
1633    /**
1634     * {@inheritDoc}
1635     */
1636    @Override
1637    public BindFuture bindAsync( BindRequest bindRequest ) throws LdapException
1638    {
1639        if ( bindRequest == null )
1640        {
1641            String msg = I18n.err( I18n.ERR_04128_CANNOT_PROCESS_NULL_BIND_REQ );
1642
1643            if ( LOG.isDebugEnabled() )
1644            {
1645                LOG.debug( msg );
1646            }
1647            
1648            throw new IllegalArgumentException( msg );
1649        }
1650
1651        // First switch to anonymous state
1652        authenticated.set( false );
1653
1654        // try to connect, if we aren't already connected.
1655        connect();
1656
1657        // If the session has not been establish, or is closed, we get out immediately
1658        checkSession();
1659
1660        // Update the messageId
1661        int newId = messageId.incrementAndGet();
1662        bindRequest.setMessageId( newId );
1663
1664        if ( LOG.isDebugEnabled() )
1665        {
1666            LOG.debug( I18n.msg( I18n.MSG_04104_SENDING_REQUEST, bindRequest ) );
1667        }
1668
1669        // Create a future for this Bind operation
1670        BindFuture bindFuture = new BindFuture( this, newId );
1671
1672        addToFutureMap( newId, bindFuture );
1673
1674        writeRequest( bindRequest );
1675
1676        // Ok, done return the future
1677        return bindFuture;
1678    }
1679
1680
1681    /**
1682     * SASL PLAIN Bind on a server.
1683     *
1684     * @param authcid The Authentication identity
1685     * @param credentials The password. It can't be null
1686     * @return The BindResponse LdapResponse
1687     * @throws LdapException if some error occurred
1688     */
1689    public BindResponse bindSaslPlain( String authcid, String credentials ) throws LdapException
1690    {
1691        return bindSaslPlain( null, authcid, credentials );
1692    }
1693
1694
1695    /**
1696     * SASL PLAIN Bind on a server.
1697     *
1698     * @param authzid The Authorization identity
1699     * @param authcid The Authentication identity
1700     * @param credentials The password. It can't be null
1701     * @return The BindResponse LdapResponse
1702     * @throws LdapException if some error occurred
1703     */
1704    public BindResponse bindSaslPlain( String authzid, String authcid, String credentials ) throws LdapException
1705    {
1706        if ( LOG.isDebugEnabled() )
1707        {
1708            LOG.debug( I18n.msg( I18n.MSG_04127_SASL_PLAIN_BIND ) );
1709        }
1710
1711        // Create the BindRequest
1712        SaslPlainRequest saslRequest = new SaslPlainRequest();
1713        saslRequest.setAuthorizationId( authzid );
1714        saslRequest.setUsername( authcid );
1715        saslRequest.setCredentials( credentials );
1716
1717        BindFuture bindFuture = bindAsync( saslRequest );
1718
1719        // Get the result from the future
1720        try
1721        {
1722            // Read the response, waiting for it if not available immediately
1723            // Get the response, blocking
1724            BindResponse bindResponse = bindFuture.get( connectTimeout, TimeUnit.MILLISECONDS );
1725
1726            if ( bindResponse == null )
1727            {
1728                // We didn't received anything : this is an error
1729                if ( LOG.isErrorEnabled() )
1730                { 
1731                    LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Bind" ) );
1732                }
1733                
1734                throw new LdapException( TIME_OUT_ERROR );
1735            }
1736
1737            if ( bindResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
1738            {
1739                authenticated.set( true );
1740
1741                // Everything is fine, return the response
1742                if ( LOG.isDebugEnabled() )
1743                { 
1744                    LOG.debug( I18n.msg( I18n.MSG_04101_BIND_SUCCESSFUL, bindResponse ) );
1745                }
1746            }
1747            else
1748            {
1749                // We have had an error
1750                if ( LOG.isDebugEnabled() )
1751                { 
1752                    LOG.debug( I18n.msg( I18n.MSG_04100_BIND_FAIL, bindResponse ) );
1753                }
1754            }
1755
1756            return bindResponse;
1757        }
1758        catch ( Exception ie )
1759        {
1760            // Catch all other exceptions
1761            LOG.error( NO_RESPONSE_ERROR, ie );
1762
1763            throw new LdapException( NO_RESPONSE_ERROR, ie );
1764        }
1765    }
1766
1767
1768    /**
1769     * Bind to the server using a SaslRequest object.
1770     *
1771     * @param request The SaslRequest POJO containing all the needed parameters
1772     * @return A LdapResponse containing the result
1773     * @throws LdapException if some error occurred
1774     */
1775    public BindResponse bind( SaslRequest request ) throws LdapException
1776    {
1777        if ( request == null )
1778        {
1779            String msg = I18n.msg( I18n.MSG_04103_NULL_REQUEST );
1780
1781            if ( LOG.isDebugEnabled() )
1782            {
1783                LOG.debug( msg );
1784            }
1785            
1786            throw new IllegalArgumentException( msg );
1787        }
1788
1789        BindFuture bindFuture = bindAsync( request );
1790
1791        // Get the result from the future
1792        try
1793        {
1794            // Read the response, waiting for it if not available immediately
1795            // Get the response, blocking
1796            BindResponse bindResponse = bindFuture.get( connectTimeout, TimeUnit.MILLISECONDS );
1797
1798            if ( bindResponse == null )
1799            {
1800                // We didn't received anything : this is an error
1801                if ( LOG.isErrorEnabled() )
1802                { 
1803                    LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Bind" ) );
1804                }
1805                
1806                throw new LdapException( TIME_OUT_ERROR );
1807            }
1808
1809            if ( bindResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
1810            {
1811                authenticated.set( true );
1812
1813                // Everything is fine, return the response
1814                if ( LOG.isDebugEnabled() )
1815                { 
1816                    LOG.debug( I18n.msg( I18n.MSG_04101_BIND_SUCCESSFUL, bindResponse ) );
1817                }
1818            }
1819            else
1820            {
1821                // We have had an error
1822                if ( LOG.isDebugEnabled() )
1823                { 
1824                    LOG.debug( I18n.msg( I18n.MSG_04100_BIND_FAIL, bindResponse ) );
1825                }
1826            }
1827
1828            return bindResponse;
1829        }
1830        catch ( Exception ie )
1831        {
1832            // Catch all other exceptions
1833            LOG.error( NO_RESPONSE_ERROR, ie );
1834
1835            throw new LdapException( NO_RESPONSE_ERROR, ie );
1836        }
1837    }
1838
1839
1840    /**
1841     * Bind to the server using the SASL CRAM-MD5 mechanism.
1842     *
1843     * @param userName The user name
1844     * @param credentials The user credentials
1845     * @return  A LdapResponse containing the result
1846     * @throws LdapException if some error occurred
1847     */
1848    public BindResponse bindSaslCramMd5( String userName, String credentials ) throws LdapException
1849    {
1850        SaslCramMd5Request request = new SaslCramMd5Request();
1851        request.setUsername( userName );
1852        request.setCredentials( "secret" );
1853
1854        return bind( request );
1855    }
1856
1857
1858    /**
1859     * Bind to the server using the SASL DIGEST-MD5 mechanism.
1860     *
1861     * @param userName The user name
1862     * @param credentials The user credentials
1863     * @return  A LdapResponse containing the result
1864     * @throws LdapException if some error occurred
1865     */
1866    public BindResponse bindSaslDigestMd5( String userName, String credentials ) throws LdapException
1867    {
1868        SaslDigestMd5Request request = new SaslDigestMd5Request();
1869        request.setUsername( userName );
1870        request.setCredentials( "secret" );
1871
1872        return bind( request );
1873    }
1874
1875
1876    /**
1877     * Bind to the server using a CramMd5Request object.
1878     *
1879     * @param request The CramMd5Request POJO containing all the needed parameters
1880     * @return A LdapResponse containing the result
1881     * @throws LdapException if some error occurred
1882     */
1883    public BindResponse bind( SaslCramMd5Request request ) throws LdapException
1884    {
1885        if ( request == null )
1886        {
1887            String msg = I18n.msg( I18n.MSG_04103_NULL_REQUEST );
1888
1889            if ( LOG.isDebugEnabled() )
1890            {
1891                LOG.debug( msg );
1892            }
1893            
1894            throw new IllegalArgumentException( msg );
1895        }
1896
1897        BindFuture bindFuture = bindAsync( request );
1898
1899        // Get the result from the future
1900        try
1901        {
1902            // Read the response, waiting for it if not available immediately
1903            // Get the response, blocking
1904            BindResponse bindResponse = bindFuture.get( connectTimeout, TimeUnit.MILLISECONDS );
1905
1906            if ( bindResponse == null )
1907            {
1908                // We didn't received anything : this is an error
1909                if ( LOG.isErrorEnabled() )
1910                { 
1911                    LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Bind" ) );
1912                }
1913                
1914                throw new LdapException( TIME_OUT_ERROR );
1915            }
1916
1917            if ( bindResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
1918            {
1919                authenticated.set( true );
1920
1921                // Everything is fine, return the response
1922                if ( LOG.isDebugEnabled() )
1923                { 
1924                    LOG.debug( I18n.msg( I18n.MSG_04101_BIND_SUCCESSFUL, bindResponse ) );
1925                }
1926            }
1927            else
1928            {
1929                // We have had an error
1930                if ( LOG.isDebugEnabled() )
1931                { 
1932                    LOG.debug( I18n.msg( I18n.MSG_04100_BIND_FAIL, bindResponse ) );
1933                }
1934            }
1935
1936            return bindResponse;
1937        }
1938        catch ( Exception ie )
1939        {
1940            // Catch all other exceptions
1941            LOG.error( NO_RESPONSE_ERROR, ie );
1942
1943            throw new LdapException( NO_RESPONSE_ERROR, ie );
1944        }
1945    }
1946
1947
1948    /**
1949     * Do an asynchronous bind, based on a SaslPlainRequest.
1950     *
1951     * @param request The SaslPlainRequest POJO containing all the needed parameters
1952     * @return The bind operation's future
1953     * @throws LdapException if some error occurred
1954     */
1955    public BindFuture bindAsync( SaslRequest request )
1956        throws LdapException
1957    {
1958        return bindSasl( request );
1959    }
1960
1961
1962    /**
1963     * Bind to the server using a DigestMd5Request object.
1964     *
1965     * @param request The DigestMd5Request POJO containing all the needed parameters
1966     * @return A LdapResponse containing the result
1967     * @throws LdapException if some error occurred
1968     */
1969    public BindResponse bind( SaslDigestMd5Request request ) throws LdapException
1970    {
1971        if ( request == null )
1972        {
1973            String msg = I18n.msg( I18n.MSG_04103_NULL_REQUEST );
1974
1975            if ( LOG.isDebugEnabled() )
1976            {
1977                LOG.debug( msg );
1978            }
1979            
1980            throw new IllegalArgumentException( msg );
1981        }
1982
1983        BindFuture bindFuture = bindAsync( request );
1984
1985        // Get the result from the future
1986        try
1987        {
1988            // Read the response, waiting for it if not available immediately
1989            // Get the response, blocking
1990            BindResponse bindResponse = bindFuture.get( connectTimeout, TimeUnit.MILLISECONDS );
1991
1992            if ( bindResponse == null )
1993            {
1994                // We didn't received anything : this is an error
1995                if ( LOG.isErrorEnabled() )
1996                { 
1997                    LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Bind" ) );
1998                }
1999                
2000                throw new LdapException( TIME_OUT_ERROR );
2001            }
2002
2003            if ( bindResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
2004            {
2005                authenticated.set( true );
2006
2007                // Everything is fine, return the response
2008                if ( LOG.isDebugEnabled() )
2009                { 
2010                    LOG.debug( I18n.msg( I18n.MSG_04101_BIND_SUCCESSFUL, bindResponse ) );
2011                }
2012            }
2013            else
2014            {
2015                // We have had an error
2016                if ( LOG.isDebugEnabled() )
2017                { 
2018                    LOG.debug( I18n.msg( I18n.MSG_04100_BIND_FAIL, bindResponse ) );
2019                }
2020            }
2021
2022            return bindResponse;
2023        }
2024        catch ( Exception ie )
2025        {
2026            // Catch all other exceptions
2027            LOG.error( NO_RESPONSE_ERROR, ie );
2028
2029            throw new LdapException( NO_RESPONSE_ERROR, ie );
2030        }
2031    }
2032
2033
2034    /**
2035     * Bind to the server using a GssApiRequest object.
2036     *
2037     * @param request The GssApiRequest POJO containing all the needed parameters
2038     * @return A LdapResponse containing the result
2039     * @throws LdapException if some error occurred
2040     */
2041    public BindResponse bind( SaslGssApiRequest request ) throws LdapException
2042    {
2043        if ( request == null )
2044        {
2045            String msg = I18n.msg( I18n.MSG_04103_NULL_REQUEST );
2046
2047            if ( LOG.isDebugEnabled() )
2048            {
2049                LOG.debug( msg );
2050            }
2051            
2052            throw new IllegalArgumentException( msg );
2053        }
2054
2055        BindFuture bindFuture = bindAsync( request );
2056
2057        // Get the result from the future
2058        try
2059        {
2060            // Read the response, waiting for it if not available immediately
2061            // Get the response, blocking
2062            BindResponse bindResponse = bindFuture.get( connectTimeout, TimeUnit.MILLISECONDS );
2063
2064            if ( bindResponse == null )
2065            {
2066                // We didn't received anything : this is an error
2067                if ( LOG.isErrorEnabled() )
2068                { 
2069                    LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Bind" ) );
2070                }
2071                
2072                throw new LdapException( TIME_OUT_ERROR );
2073            }
2074
2075            if ( bindResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
2076            {
2077                authenticated.set( true );
2078
2079                // Everything is fine, return the response
2080                if ( LOG.isDebugEnabled() )
2081                { 
2082                    LOG.debug( I18n.msg( I18n.MSG_04101_BIND_SUCCESSFUL, bindResponse ) );
2083                }
2084            }
2085            else
2086            {
2087                // We have had an error
2088                if ( LOG.isDebugEnabled() )
2089                { 
2090                    LOG.debug( I18n.msg( I18n.MSG_04100_BIND_FAIL, bindResponse ) );
2091                }
2092            }
2093
2094            return bindResponse;
2095        }
2096        catch ( Exception ie )
2097        {
2098            // Catch all other exceptions
2099            LOG.error( NO_RESPONSE_ERROR, ie );
2100
2101            throw new LdapException( NO_RESPONSE_ERROR, ie );
2102        }
2103    }
2104
2105
2106    /**
2107     * Bind to the server using a SaslExternalRequest object.
2108     *
2109     * @param request The SaslExternalRequest POJO containing all the needed parameters
2110     * @return A LdapResponse containing the result
2111     * @throws LdapException if some error occurred
2112     */
2113    public BindResponse bind( SaslExternalRequest request ) throws LdapException
2114    {
2115        if ( request == null )
2116        {
2117            String msg = I18n.msg( I18n.MSG_04103_NULL_REQUEST );
2118
2119            if ( LOG.isDebugEnabled() )
2120            {
2121                LOG.debug( msg );
2122            }
2123            
2124            throw new IllegalArgumentException( msg );
2125        }
2126
2127        BindFuture bindFuture = bindAsync( request );
2128
2129        // Get the result from the future
2130        try
2131        {
2132            // Read the response, waiting for it if not available immediately
2133            // Get the response, blocking
2134            BindResponse bindResponse = bindFuture.get( connectTimeout, TimeUnit.MILLISECONDS );
2135
2136            if ( bindResponse == null )
2137            {
2138                // We didn't received anything : this is an error
2139                if ( LOG.isErrorEnabled() )
2140                { 
2141                    LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Bind" ) );
2142                }
2143                
2144                throw new LdapException( TIME_OUT_ERROR );
2145            }
2146
2147            if ( bindResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
2148            {
2149                authenticated.set( true );
2150
2151                // Everything is fine, return the response
2152                if ( LOG.isDebugEnabled() )
2153                { 
2154                    LOG.debug( I18n.msg( I18n.MSG_04101_BIND_SUCCESSFUL, bindResponse ) );
2155                }
2156            }
2157            else
2158            {
2159                // We have had an error
2160                if ( LOG.isDebugEnabled() )
2161                { 
2162                    LOG.debug( I18n.msg( I18n.MSG_04100_BIND_FAIL, bindResponse ) );
2163                }
2164            }
2165
2166            return bindResponse;
2167        }
2168        catch ( Exception ie )
2169        {
2170            // Catch all other exceptions
2171            LOG.error( NO_RESPONSE_ERROR, ie );
2172
2173            throw new LdapException( NO_RESPONSE_ERROR, ie );
2174        }
2175    }
2176
2177
2178    /**
2179     * Do an asynchronous bind, based on a GssApiRequest.
2180     *
2181     * @param request The GssApiRequest POJO containing all the needed parameters
2182     * @return The bind operation's future
2183     * @throws LdapException if some error occurred
2184     */
2185    public BindFuture bindAsync( SaslGssApiRequest request )
2186        throws LdapException
2187    {
2188        // Krb5.conf file
2189        if ( request.getKrb5ConfFilePath() != null )
2190        {
2191            // Using the krb5.conf file provided by the user
2192            System.setProperty( KRB5_CONF, request.getKrb5ConfFilePath() );
2193        }
2194        else if ( ( request.getRealmName() != null ) && ( request.getKdcHost() != null )
2195            && ( request.getKdcPort() != 0 ) )
2196        {
2197            try
2198            {
2199                // Using a custom krb5.conf we create from the settings provided by the user
2200                String krb5ConfPath = createKrb5ConfFile( request.getRealmName(), request.getKdcHost(),
2201                    request.getKdcPort() );
2202                System.setProperty( KRB5_CONF, krb5ConfPath );
2203            }
2204            catch ( IOException ioe )
2205            {
2206                throw new LdapException( ioe );
2207            }
2208        }
2209        else
2210        {
2211            // Using the system Kerberos configuration
2212            System.clearProperty( KRB5_CONF );
2213        }
2214
2215        // Login Module configuration
2216        if ( request.getLoginModuleConfiguration() != null )
2217        {
2218            // Using the configuration provided by the user
2219            Configuration.setConfiguration( request.getLoginModuleConfiguration() );
2220        }
2221        else
2222        {
2223            // Using the default configuration
2224            Configuration.setConfiguration( new Krb5LoginConfiguration() );
2225        }
2226
2227        try
2228        {
2229            System.setProperty( "javax.security.auth.useSubjectCredsOnly", "true" );
2230            LoginContext loginContext = new LoginContext( request.getLoginContextName(),
2231                new SaslCallbackHandler( request ) );
2232            loginContext.login();
2233
2234            final SaslGssApiRequest requetFinal = request;
2235            return ( BindFuture ) Subject.doAs( loginContext.getSubject(), new PrivilegedExceptionAction<Object>()
2236            {
2237                @Override
2238                public Object run() throws Exception
2239                {
2240                    return bindSasl( requetFinal );
2241                }
2242            } );
2243        }
2244        catch ( Exception e )
2245        {
2246            connectionCloseFuture.complete( 0 );
2247            throw new LdapException( e );
2248        }
2249    }
2250
2251
2252    /**
2253     * {@inheritDoc}
2254     */
2255    @Override
2256    public EntryCursor search( Dn baseDn, String filter, SearchScope scope, String... attributes )
2257        throws LdapException
2258    {
2259        if ( baseDn == null )
2260        {
2261            if ( LOG.isDebugEnabled() )
2262            {
2263                LOG.debug( I18n.msg( I18n.MSG_04138_NULL_DN_SEARCH ) );
2264            }
2265            
2266            throw new IllegalArgumentException( I18n.err( I18n.ERR_04129_NULL_BASE_DN ) );
2267        }
2268
2269        // Create a new SearchRequest object
2270        SearchRequest searchRequest = new SearchRequestImpl();
2271
2272        searchRequest.setBase( baseDn );
2273        searchRequest.setFilter( filter );
2274        searchRequest.setScope( scope );
2275        searchRequest.addAttributes( attributes );
2276        searchRequest.setDerefAliases( AliasDerefMode.DEREF_ALWAYS );
2277
2278        // Process the request in blocking mode
2279        return new EntryCursorImpl( search( searchRequest ) );
2280    }
2281
2282
2283    /**
2284     * {@inheritDoc}
2285     */
2286    @Override
2287    public EntryCursor search( String baseDn, String filter, SearchScope scope, String... attributes )
2288        throws LdapException
2289    {
2290        return search( new Dn( baseDn ), filter, scope, attributes );
2291    }
2292
2293
2294    /**
2295     * {@inheritDoc}
2296     */
2297    @Override
2298    public SearchFuture searchAsync( Dn baseDn, String filter, SearchScope scope, String... attributes )
2299        throws LdapException
2300    {
2301        // Create a new SearchRequest object
2302        SearchRequest searchRequest = new SearchRequestImpl();
2303
2304        searchRequest.setBase( baseDn );
2305        searchRequest.setFilter( filter );
2306        searchRequest.setScope( scope );
2307        searchRequest.addAttributes( attributes );
2308        searchRequest.setDerefAliases( AliasDerefMode.DEREF_ALWAYS );
2309
2310        // Process the request in blocking mode
2311        return searchAsync( searchRequest );
2312    }
2313
2314
2315    /**
2316     * {@inheritDoc}
2317     */
2318    @Override
2319    public SearchFuture searchAsync( String baseDn, String filter, SearchScope scope, String... attributes )
2320        throws LdapException
2321    {
2322        return searchAsync( new Dn( baseDn ), filter, scope, attributes );
2323    }
2324
2325
2326    /**
2327     * {@inheritDoc}
2328     */
2329    @Override
2330    public SearchFuture searchAsync( SearchRequest searchRequest ) throws LdapException
2331    {
2332        if ( searchRequest == null )
2333        {
2334            String msg = I18n.err( I18n.ERR_04130_CANNOT_PROCESS_NULL_SEARCH_REQ );
2335
2336            if ( LOG.isDebugEnabled() )
2337            {
2338                LOG.debug( msg );
2339            }
2340            
2341            throw new IllegalArgumentException( msg );
2342        }
2343
2344        if ( searchRequest.getBase() == null )
2345        {
2346            String msg = I18n.err( I18n.ERR_04131_CANNOT_PROCESS_SEARCH_NULL_DN );
2347
2348            if ( LOG.isDebugEnabled() )
2349            {
2350                LOG.debug( msg );
2351            }
2352            
2353            throw new IllegalArgumentException( msg );
2354        }
2355
2356        // try to connect, if we aren't already connected.
2357        connect();
2358
2359        // If the session has not been establish, or is closed, we get out immediately
2360        checkSession();
2361
2362        int newId = messageId.incrementAndGet();
2363        searchRequest.setMessageId( newId );
2364
2365        if ( searchRequest.isIgnoreReferrals() )
2366        {
2367            // We want to ignore the referral, inject the ManageDSAIT control in the request
2368            searchRequest.addControl( new ManageDsaITImpl() );
2369        }
2370
2371        if ( LOG.isDebugEnabled() )
2372        {
2373            LOG.debug( I18n.msg( I18n.MSG_04104_SENDING_REQUEST, searchRequest ) );
2374        }
2375
2376        SearchFuture searchFuture = new SearchFuture( this, searchRequest.getMessageId() );
2377        addToFutureMap( searchRequest.getMessageId(), searchFuture );
2378
2379        // Send the request to the server
2380        writeRequest( searchRequest );
2381
2382        // Check that the future hasn't be canceled
2383        if ( searchFuture.isCancelled() )
2384        {
2385            // Throw an exception here
2386            throw new LdapException( searchFuture.getCause() );
2387        }
2388
2389        // Ok, done return the future
2390        return searchFuture;
2391    }
2392
2393
2394    /**
2395     * {@inheritDoc}
2396     */
2397    @Override
2398    public SearchCursor search( SearchRequest searchRequest ) throws LdapException
2399    {
2400        if ( searchRequest == null )
2401        {
2402            String msg = I18n.err( I18n.ERR_04130_CANNOT_PROCESS_NULL_SEARCH_REQ );
2403
2404            if ( LOG.isDebugEnabled() )
2405            {
2406                LOG.debug( msg );
2407            }
2408
2409            throw new IllegalArgumentException( msg );
2410        }
2411        long localSearchTimeout = getTimeout( readOperationTimeout, searchRequest.getTimeLimit() );
2412        return search( searchRequest, localSearchTimeout );
2413    }
2414
2415    /**
2416     * Search operation with explicit specification of operation timeout.
2417     * This is very useful for special-purpose search operations, such as connection liveliness test.
2418     * In that case we want to make a very quick search with a very short timeout.
2419     * We do not want to use the default search timeout in this case, as that is likely to be in order of seconds.
2420     * That is too long for quick connection check.
2421     * searchRequest.timeLimit is not going to work either, as it has very rough granularity (seconds).
2422     */
2423    public SearchCursor search( SearchRequest searchRequest, long localSearchTimeout ) throws LdapException
2424    {
2425        if ( searchRequest == null )
2426        {
2427            String msg = I18n.err( I18n.ERR_04130_CANNOT_PROCESS_NULL_SEARCH_REQ );
2428
2429            if ( LOG.isDebugEnabled() )
2430            {
2431                LOG.debug( msg );
2432            }
2433
2434            throw new IllegalArgumentException( msg );
2435        }
2436
2437        SearchFuture searchFuture = searchAsync( searchRequest );
2438
2439        return new SearchCursorImpl( searchFuture, localSearchTimeout, TimeUnit.MILLISECONDS );
2440    }
2441
2442    //------------------------ The LDAP operations ------------------------//
2443    // Unbind operations                                                   //
2444    //---------------------------------------------------------------------//
2445    /**
2446     * {@inheritDoc}
2447     */
2448    @Override
2449    public void unBind() throws LdapException
2450    {
2451        // If the session has not been establish, or is closed, we get out immediately
2452        checkSession();
2453
2454        // Creates the messageID and stores it into the
2455        // initial message and the transmitted message.
2456        int newId = messageId.incrementAndGet();
2457
2458        // Create the UnbindRequest
2459        UnbindRequest unbindRequest = new UnbindRequestImpl();
2460        unbindRequest.setMessageId( newId );
2461
2462        if ( LOG.isDebugEnabled() )
2463        {
2464            LOG.debug( I18n.msg( I18n.MSG_04132_SENDING_UNBIND, unbindRequest ) );
2465        }
2466
2467        // Send the request to the server
2468        // Use this for logging instead: WriteFuture unbindFuture = ldapSession.write( unbindRequest )
2469        WriteFuture unbindFuture = ioSession.write( unbindRequest );
2470
2471        unbindFuture.awaitUninterruptibly( sendTimeout );
2472
2473        try
2474        {
2475            connectionCloseFuture.get( closeTimeout, TimeUnit.MILLISECONDS );
2476        }
2477        catch ( TimeoutException | ExecutionException | InterruptedException e )
2478        {
2479            if ( LOG.isDebugEnabled() )
2480            {
2481                LOG.debug( I18n.msg( I18n.MSH_04178_CLOSE_LATCH_ABORTED ) );
2482            }
2483        }
2484
2485        // And get out
2486        if ( LOG.isDebugEnabled() )
2487        {
2488            LOG.debug( I18n.msg( I18n.MSG_04133_UNBINDSUCCESSFUL ) );
2489        }
2490    }
2491
2492
2493    /**
2494     * Set the connector to use.
2495     *
2496     * @param connector The connector to use
2497     */
2498    public void setConnector( IoConnector connector )
2499    {
2500        this.connector = connector;
2501    }
2502
2503
2504    /**
2505     * {@inheritDoc}
2506     */
2507    @Override
2508    public void setTimeOut( long timeout )
2509    {
2510        if ( timeout <= 0 )
2511        {
2512            // Set a date in the far future : 100 years
2513            setAllTimeOuts( 1000L * 60L * 60L * 24L * 365L * 100L );
2514        }
2515        else
2516        {
2517            setAllTimeOuts( timeout );
2518        }
2519    }
2520
2521    private void setAllTimeOuts( long timeout )
2522    {
2523        this.timeout = timeout;
2524        // For compatibility.
2525        // Set all timeouts to the same value to preserve previous behavior.
2526        this.connectTimeout = timeout;
2527        this.writeOperationTimeout = timeout;
2528        this.readOperationTimeout = timeout;
2529        this.closeTimeout = timeout;
2530        this.sendTimeout = timeout;
2531    }
2532
2533
2534    /**
2535     * Handle the exception we got.
2536     *
2537     * @param session The session we got the exception on
2538     * @param cause The exception cause
2539     * @throws Exception If we have had another exception
2540     */
2541    @Override
2542    public void exceptionCaught( IoSession session, Throwable cause ) throws Exception
2543    {
2544        if ( LOG.isWarnEnabled() )
2545        {
2546            LOG.warn( cause.getMessage(), cause );
2547        }
2548
2549        session.setAttribute( EXCEPTION_KEY, cause );
2550
2551        if ( cause instanceof ProtocolEncoderException )
2552        {
2553            Throwable realCause = ( ( ProtocolEncoderException ) cause ).getCause();
2554
2555            if ( realCause instanceof MessageEncoderException )
2556            {
2557                int messageId = ( ( MessageEncoderException ) realCause ).getMessageId();
2558
2559                ResponseFuture<?> response = futureMap.get( messageId );
2560                response.cancel( true );
2561                response.setCause( realCause );
2562            }
2563        }
2564
2565        session.closeNow();
2566    }
2567
2568
2569    /**
2570     * Check if the message is a NoticeOfDisconnect message
2571     * 
2572     * @param message The message to check
2573     * @return <tt>true</tt> if the message is a Notice of Disconnect
2574     */
2575    private boolean isNoticeOfDisconnect( Message message )
2576    {
2577        if ( message instanceof ExtendedResponse )
2578        {
2579            String responseName = ( ( ExtendedResponse ) message ).getResponseName();
2580
2581            if ( NoticeOfDisconnect.EXTENSION_OID.equals( responseName ) )
2582            {
2583                return true;
2584            }
2585        }
2586
2587        return false;
2588    }
2589
2590
2591    /**
2592     * Process the AddResponse received from the server
2593     * 
2594     * @param addResponse The AddResponse to process
2595     * @param addFuture The AddFuture to feed
2596     * @param responseId The associated request message ID
2597     * @throws InterruptedException If the Future is interrupted
2598     */
2599    private void addReceived( AddResponse addResponse, AddFuture addFuture, int responseId ) throws InterruptedException
2600    {
2601        // remove the listener from the listener map
2602        if ( LOG.isDebugEnabled() )
2603        {
2604            if ( addResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
2605            {
2606                // Everything is fine, return the response
2607                LOG.debug( I18n.msg( I18n.MSG_04108_ADD_SUCCESSFUL, addResponse ) );
2608            }
2609            else
2610            {
2611                // We have had an error
2612                LOG.debug( I18n.msg( I18n.MSG_04107_ADD_FAILED, addResponse ) );
2613            }
2614        }
2615
2616        // Store the response into the future
2617        addFuture.set( addResponse );
2618
2619        // Remove the future from the map
2620        removeFromFutureMaps( responseId );
2621    }
2622
2623
2624    /**
2625     * Process the BindResponse received from the server
2626     * 
2627     * @param bindResponse The BindResponse to process
2628     * @param bindFuture The BindFuture to feed
2629     * @param responseId The associated request message ID
2630     * @throws InterruptedException If the Future is interrupted
2631     */
2632    private void bindReceived( BindResponse bindResponse, BindFuture bindFuture, int responseId ) 
2633        throws InterruptedException
2634    {
2635        // remove the listener from the listener map
2636        if ( bindResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
2637        {
2638            authenticated.set( true );
2639
2640            // Everything is fine, return the response
2641            if ( LOG.isDebugEnabled() )
2642            { 
2643                LOG.debug( I18n.msg( I18n.MSG_04101_BIND_SUCCESSFUL, bindResponse ) );
2644            }
2645        }
2646        else
2647        {
2648            // We have had an error
2649            if ( LOG.isDebugEnabled() )
2650            { 
2651                LOG.debug( I18n.msg( I18n.MSG_04100_BIND_FAIL, bindResponse ) );
2652            }
2653        }
2654
2655        // Store the response into the future
2656        bindFuture.set( bindResponse );
2657
2658        // Remove the future from the map
2659        removeFromFutureMaps( responseId );
2660    }
2661
2662
2663    /**
2664     * Process the CompareResponse received from the server
2665     * 
2666     * @param compareResponse The CompareResponse to process
2667     * @param compareFuture The CompareFuture to feed
2668     * @param responseId The associated request message ID
2669     * @throws InterruptedException If the Future is interrupted
2670     */
2671    private void compareReceived( CompareResponse compareResponse, CompareFuture compareFuture, int responseId ) 
2672       throws InterruptedException
2673    {
2674        // remove the listener from the listener map
2675        if ( LOG.isDebugEnabled() )
2676        {
2677            if ( compareResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
2678            {
2679                // Everything is fine, return the response
2680                LOG.debug( I18n.msg( I18n.MSG_04114_COMPARE_SUCCESSFUL, compareResponse ) );
2681            }
2682            else
2683            {
2684                // We have had an error
2685                LOG.debug( I18n.msg( I18n.MSG_04113_COMPARE_FAILED, compareResponse ) );
2686            }
2687        }
2688
2689        // Store the response into the future
2690        compareFuture.set( compareResponse );
2691
2692        // Remove the future from the map
2693        removeFromFutureMaps( responseId );
2694    }
2695
2696
2697    /**
2698     * Process the DeleteResponse received from the server
2699     * 
2700     * @param deleteResponse The DeleteResponse to process
2701     * @param deleteFuture The DeleteFuture to feed
2702     * @param responseId The associated request message ID
2703     * @throws InterruptedException If the Future is interrupted
2704     */
2705    private void deleteReceived( DeleteResponse deleteResponse, DeleteFuture deleteFuture, int responseId ) 
2706        throws InterruptedException
2707    {
2708        if ( LOG.isDebugEnabled() )
2709        {
2710            if ( deleteResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
2711            {
2712                // Everything is fine, return the response
2713                LOG.debug( I18n.msg( I18n.MSG_04116_DELETE_SUCCESSFUL, deleteResponse ) );
2714            }
2715            else
2716            {
2717                // We have had an error
2718                LOG.debug( I18n.msg( I18n.MSG_04115_DELETE_FAILED, deleteResponse ) );
2719            }
2720        }
2721
2722        // Store the response into the future
2723        deleteFuture.set( deleteResponse );
2724
2725        // Remove the future from the map
2726        removeFromFutureMaps( responseId );
2727    }
2728
2729
2730    /**
2731     * Process the ExtendedResponse received from the server
2732     * 
2733     * @param extendedResponse The ExtendedResponse to process
2734     * @param extendedFuture The ExtendedFuture to feed
2735     * @param responseId The associated request message ID
2736     * @throws InterruptedException If the Future is interrupted
2737     * @throws DecoderException If the response cannot be decoded
2738     */
2739    private void extendedReceived( ExtendedResponse extendedResponse, ExtendedFuture extendedFuture, int responseId ) 
2740        throws InterruptedException, DecoderException
2741    {
2742        if ( LOG.isDebugEnabled() )
2743        {
2744            if ( extendedResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
2745            {
2746                // Everything is fine, return the response
2747                LOG.debug( I18n.msg( I18n.MSG_04118_EXTENDED_SUCCESSFUL, extendedResponse ) );
2748            }
2749            else
2750            {
2751                // We have had an error
2752                LOG.debug( I18n.msg( I18n.MSG_04117_EXTENDED_FAILED, extendedResponse ) );
2753            }
2754        }
2755        
2756        extendedResponse = handleOpaqueResponse( extendedResponse, extendedFuture );
2757
2758        // Store the response into the future
2759        extendedFuture.set( extendedResponse );
2760
2761        // Remove the future from the map
2762        removeFromFutureMaps( responseId );
2763    }
2764
2765
2766    /**
2767     * Process the IntermediateResponse received from the server
2768     * 
2769     * @param intermediateResponse The IntermediateResponse to process
2770     * @param responseFuture The ResponseFuture to feed
2771     * @throws InterruptedException If the Future is interrupted
2772     */
2773    private void intermediateReceived( IntermediateResponse intermediateResponse, ResponseFuture<? extends Response> responseFuture ) 
2774        throws InterruptedException
2775    {
2776        // Store the response into the future
2777        if ( responseFuture instanceof SearchFuture )
2778        {
2779            ( ( SearchFuture ) responseFuture ).set( intermediateResponse );
2780        }
2781        else if ( responseFuture instanceof ExtendedFuture )
2782        {
2783            ( ( ExtendedFuture ) responseFuture ).set( intermediateResponse );
2784        }
2785        else
2786        {
2787            // currently we only support IR for search and extended operations
2788            throw new UnsupportedOperationException( I18n.err( I18n.ERR_04111_UNKNOWN_RESPONSE_FUTURE_TYPE,
2789                responseFuture.getClass().getName() ) );
2790        }
2791
2792        // Do not remove the future from the map, that's done when receiving search result done
2793    }
2794
2795
2796    /**
2797     * Process the ModifyResponse received from the server
2798     * 
2799     * @param modifyResponse The ModifyResponse to process
2800     * @param modifyFuture The ModifyFuture to feed
2801     * @param responseId The associated request message ID
2802     * @throws InterruptedException If the Future is interrupted
2803     */
2804    private void modifyReceived( ModifyResponse modifyResponse, ModifyFuture modifyFuture, int responseId ) 
2805        throws InterruptedException
2806    {
2807        if ( LOG.isDebugEnabled() )
2808        {
2809            if ( modifyResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
2810            {
2811                // Everything is fine, return the response
2812                if ( LOG.isDebugEnabled() )
2813                { 
2814                    LOG.debug( I18n.msg( I18n.MSG_04123_MODIFY_SUCCESSFUL, modifyResponse ) );
2815                }
2816            }
2817            else
2818            {
2819                // We have had an error
2820                if ( LOG.isDebugEnabled() )
2821                { 
2822                    LOG.debug( I18n.msg( I18n.MSG_04122_MODIFY_FAILED, modifyResponse ) );
2823                }
2824            }
2825        }
2826
2827        // Store the response into the future
2828        modifyFuture.set( modifyResponse );
2829
2830        // Remove the future from the map
2831        removeFromFutureMaps( responseId );
2832    }
2833
2834
2835    /**
2836     * Process the ModifyDnResponse received from the server
2837     * 
2838     * @param modifyDnResponse The ModifyDnResponse to process
2839     * @param modifyDnFuture The ModifyDnFuture to feed
2840     * @param responseId The associated request message ID
2841     * @throws InterruptedException If the Future is interrupted
2842     */
2843    private void modifyDnReceived( ModifyDnResponse modifyDnResponse, ModifyDnFuture modifyDnFuture, int responseId ) 
2844        throws InterruptedException
2845    {
2846        if ( LOG.isDebugEnabled() )
2847        {
2848            if ( modifyDnResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
2849            {
2850                // Everything is fine, return the response
2851                LOG.debug( I18n.msg( I18n.MSG_04125_MODIFYDN_SUCCESSFUL, modifyDnResponse ) );
2852            }
2853            else
2854            {
2855                // We have had an error
2856                LOG.debug( I18n.msg( I18n.MSG_04124_MODIFYDN_FAILED, modifyDnResponse ) );
2857            }
2858        }
2859
2860        // Store the response into the future
2861        modifyDnFuture.set( modifyDnResponse );
2862
2863        // Remove the future from the map
2864        removeFromFutureMaps( responseId );
2865    }
2866
2867
2868    /**
2869     * Process the SearchResultDone received from the server
2870     * 
2871     * @param searchResultDone The SearchResultDone to process
2872     * @param searchFuture The SearchFuture to feed
2873     * @param responseId The associated request message ID
2874     * @throws InterruptedException If the Future is interrupted
2875     */
2876    private void searchResultDoneReceived( SearchResultDone searchResultDone, SearchFuture searchFuture, 
2877        int responseId ) throws InterruptedException
2878    {
2879        if ( LOG.isDebugEnabled() )
2880        {
2881            if ( searchResultDone.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
2882            {
2883                // Everything is fine, return the response
2884                LOG.debug( I18n.msg( I18n.MSG_04131_SEARCH_SUCCESSFUL, searchResultDone ) );
2885            }
2886            else
2887            {
2888                // We have had an error
2889                LOG.debug( I18n.msg( I18n.MSG_04129_SEARCH_FAILED, searchResultDone ) );
2890            }
2891        }
2892
2893        // Store the response into the future
2894        searchFuture.set( searchResultDone );
2895
2896        // Remove the future from the map
2897        removeFromFutureMaps( responseId );
2898    }
2899
2900
2901    /**
2902     * Process the SearchResultEntry received from the server
2903     * 
2904     * @param searchResultEntry The SearchResultEntry to process
2905     * @param searchFuture The SearchFuture to feed
2906     * @throws InterruptedException If the Future is interrupted
2907     * @throws LdapException If we weren't able to create a new Entry
2908     */
2909    private void searchResultEntryReceived( SearchResultEntry searchResultEntry, SearchFuture searchFuture ) 
2910        throws InterruptedException, LdapException
2911    {
2912        if ( schemaManager != null )
2913        {
2914            searchResultEntry.setEntry( new DefaultEntry( schemaManager, searchResultEntry.getEntry() ) );
2915        }
2916
2917        if ( LOG.isDebugEnabled() )
2918        {
2919            LOG.debug( I18n.msg( I18n.MSG_04128_SEARCH_ENTRY_FOUND, searchResultEntry ) );
2920        }
2921
2922        // Store the response into the future
2923        searchFuture.set( searchResultEntry );
2924    }
2925    
2926    
2927    /**
2928     * Process the SearchResultEntry received from the server
2929     * 
2930     * @param searchResultReference The SearchResultReference to process
2931     * @param searchFuture The SearchFuture to feed
2932     * @throws InterruptedException If the Future is interrupted
2933     */
2934    private void searchResultReferenceReceived( SearchResultReference searchResultReference, SearchFuture searchFuture ) 
2935        throws InterruptedException
2936    {
2937        if ( LOG.isDebugEnabled() )
2938        {
2939            LOG.debug( I18n.msg( I18n.MSG_04130_SEARCH_REFERENCE_FOUND, searchResultReference ) );
2940        }
2941
2942        // Store the response into the future
2943        searchFuture.set( searchResultReference );
2944    }
2945    
2946
2947    /**
2948     * Handle the incoming LDAP messages. This is where we feed the cursor for search
2949     * requests, or call the listener.
2950     *
2951     * @param session The session that received a message
2952     * @param message The received message
2953     * @throws Exception If there is some error while processing the message
2954     */
2955    @Override
2956    public void messageReceived( IoSession session, Object message ) throws Exception
2957    {
2958        // Feed the response and store it into the session
2959        Response response = ( Response ) message;
2960
2961        if ( LOG.isDebugEnabled() )
2962        {
2963            LOG.debug( I18n.msg( I18n.MSG_04142_MESSAGE_RECEIVED, response ) );
2964        }
2965        
2966        int responseId = response.getMessageId();
2967
2968        // this check is necessary to prevent adding an abandoned operation's
2969        // result(s) to corresponding queue
2970        ResponseFuture<? extends Response> responseFuture = peekFromFutureMap( responseId );
2971
2972        boolean isNoD = isNoticeOfDisconnect( response );
2973
2974        if ( ( responseFuture == null ) && !isNoD )
2975        {
2976            if ( LOG.isInfoEnabled() )
2977            {
2978                LOG.info( I18n.msg( I18n.MSG_04166_NO_FUTURE_ASSOCIATED_TO_MSG_ID_IGNORING, responseId ) );
2979            }
2980            
2981            return;
2982        }
2983
2984        if ( isNoD )
2985        {
2986            // close the session
2987            session.closeNow();
2988
2989            return;
2990        }
2991
2992        switch ( response.getType() )
2993        {
2994            case ADD_RESPONSE:
2995                addReceived( ( AddResponse ) response, ( AddFuture ) responseFuture, responseId );
2996
2997                break;
2998
2999            case BIND_RESPONSE:
3000                bindReceived( ( BindResponse ) response, ( BindFuture ) responseFuture, responseId );
3001
3002                break;
3003
3004            case COMPARE_RESPONSE:
3005                compareReceived( ( CompareResponse ) response, ( CompareFuture ) responseFuture, responseId );
3006
3007                break;
3008
3009            case DEL_RESPONSE:
3010                deleteReceived( ( DeleteResponse ) response, ( DeleteFuture ) responseFuture, responseId );
3011
3012                break;
3013
3014            case EXTENDED_RESPONSE:
3015                extendedReceived( ( ExtendedResponse ) response, ( ExtendedFuture ) responseFuture, responseId );
3016
3017                break;
3018
3019            case INTERMEDIATE_RESPONSE:
3020                intermediateReceived( ( IntermediateResponse ) response, responseFuture );
3021
3022                break;
3023
3024            case MODIFY_RESPONSE:
3025                modifyReceived( ( ModifyResponse ) response, ( ModifyFuture ) responseFuture, responseId );
3026
3027                break;
3028
3029            case MODIFYDN_RESPONSE:
3030                modifyDnReceived( ( ModifyDnResponse ) response, ( ModifyDnFuture ) responseFuture, responseId );
3031
3032                break;
3033
3034            case SEARCH_RESULT_DONE:
3035                searchResultDoneReceived( ( SearchResultDone ) response, ( SearchFuture ) responseFuture, responseId );
3036
3037                break;
3038
3039            case SEARCH_RESULT_ENTRY:
3040                searchResultEntryReceived( ( SearchResultEntry ) response, ( SearchFuture ) responseFuture );
3041
3042                break;
3043
3044            case SEARCH_RESULT_REFERENCE:
3045                searchResultReferenceReceived( ( SearchResultReference ) response, ( SearchFuture ) responseFuture );
3046
3047                break;
3048
3049            default:
3050                throw new IllegalStateException( I18n.err( I18n.ERR_04132_UNEXPECTED_RESPONSE_TYPE, response.getType() ) );
3051        }
3052    }
3053
3054    
3055    private ExtendedResponse handleOpaqueResponse( ExtendedResponse extendedResponse, ExtendedFuture extendedFuture ) 
3056        throws DecoderException
3057    {
3058        if ( ( extendedResponse instanceof OpaqueExtendedResponse ) 
3059            && ( Strings.isEmpty( extendedResponse.getResponseName() ) ) ) 
3060        {
3061            ExtendedOperationFactory factory = codec.getExtendedResponseFactories().
3062                get( extendedFuture.getExtendedRequest().getRequestName() );
3063
3064            byte[] responseValue = ( ( OpaqueExtendedResponse ) extendedResponse ).getResponseValue();
3065
3066            ExtendedResponse response;
3067            if ( responseValue != null )
3068            {
3069                response = factory.newResponse( responseValue );
3070            }
3071            else
3072            {
3073                response = factory.newResponse();
3074            }
3075
3076            // Copy the controls
3077            for ( Control control : extendedResponse.getControls().values() )
3078            {
3079                response.addControl( control );
3080            }
3081            
3082            // copy the LDAPResult
3083            response.getLdapResult().setDiagnosticMessage( extendedResponse.getLdapResult().getDiagnosticMessage() );
3084            response.getLdapResult().setMatchedDn( extendedResponse.getLdapResult().getMatchedDn() );
3085            response.getLdapResult().setReferral( extendedResponse.getLdapResult().getReferral() );
3086            response.getLdapResult().setResultCode( extendedResponse.getLdapResult().getResultCode() );
3087            
3088            return response;
3089        }
3090        else
3091        {
3092            return extendedResponse;
3093        }
3094    }
3095
3096    /**
3097     * {@inheritDoc}
3098     */
3099    @Override
3100    public void modify( Entry entry, ModificationOperation modOp ) throws LdapException
3101    {
3102        if ( entry == null )
3103        {
3104            if ( LOG.isDebugEnabled() )
3105            {
3106                LOG.debug( I18n.msg( I18n.MSG_04140_NULL_ENTRY_MODIFY ) );
3107            }
3108            
3109            throw new IllegalArgumentException( I18n.err( I18n.ERR_04133_NULL_MODIFIED_ENTRY ) );
3110        }
3111
3112        ModifyRequest modReq = new ModifyRequestImpl();
3113        modReq.setName( entry.getDn() );
3114
3115        Iterator<Attribute> itr = entry.iterator();
3116
3117        while ( itr.hasNext() )
3118        {
3119            modReq.addModification( itr.next(), modOp );
3120        }
3121
3122        ModifyResponse modifyResponse = modify( modReq );
3123
3124        processResponse( modifyResponse );
3125    }
3126
3127
3128    /**
3129     * {@inheritDoc}
3130     */
3131    @Override
3132    public void modify( Dn dn, Modification... modifications ) throws LdapException
3133    {
3134        if ( dn == null )
3135        {
3136            if ( LOG.isDebugEnabled() )
3137            {
3138                LOG.debug( I18n.msg( I18n.MSG_04139_NULL_DN_MODIFY ) );
3139            }
3140            
3141            throw new IllegalArgumentException( I18n.err( I18n.ERR_04134_NULL_MODIFIED_DN ) );
3142        }
3143
3144        if ( ( modifications == null ) || ( modifications.length == 0 ) )
3145        {
3146            String msg = I18n.err( I18n.ERR_04135_CANNOT_PROCESS_NO_MODIFICATION_MOD );
3147
3148            if ( LOG.isDebugEnabled() )
3149            {
3150                LOG.debug( msg );
3151            }
3152            
3153            throw new IllegalArgumentException( msg );
3154        }
3155
3156        ModifyRequest modReq = new ModifyRequestImpl();
3157        modReq.setName( dn );
3158
3159        for ( Modification modification : modifications )
3160        {
3161            modReq.addModification( modification );
3162        }
3163
3164        ModifyResponse modifyResponse = modify( modReq );
3165
3166        processResponse( modifyResponse );
3167    }
3168
3169
3170    /**
3171     * {@inheritDoc}
3172     */
3173    @Override
3174    public void modify( String dn, Modification... modifications ) throws LdapException
3175    {
3176        modify( new Dn( dn ), modifications );
3177    }
3178
3179
3180    /**
3181     * {@inheritDoc}
3182     */
3183    @Override
3184    public ModifyResponse modify( ModifyRequest modRequest ) throws LdapException
3185    {
3186        if ( modRequest == null )
3187        {
3188            String msg = I18n.err( I18n.ERR_04136_CANNOT_PROCESS_NULL_MOD_REQ );
3189
3190            if ( LOG.isDebugEnabled() )
3191            {
3192                LOG.debug( msg );
3193            }
3194            
3195            throw new IllegalArgumentException( msg );
3196        }
3197
3198        ModifyFuture modifyFuture = modifyAsync( modRequest );
3199
3200        // Get the result from the future
3201        try
3202        {
3203            // Read the response, waiting for it if not available immediately
3204            // Get the response, blocking
3205            ModifyResponse modifyResponse = modifyFuture.get( writeOperationTimeout, TimeUnit.MILLISECONDS );
3206
3207            if ( modifyResponse == null )
3208            {
3209                // We didn't received anything : this is an error
3210                if ( LOG.isErrorEnabled() )
3211                {
3212                    LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Modify" ) );
3213                }
3214                
3215                throw new LdapException( TIME_OUT_ERROR );
3216            }
3217
3218            if ( modifyResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
3219            {
3220                // Everything is fine, return the response
3221                if ( LOG.isDebugEnabled() )
3222                { 
3223                    LOG.debug( I18n.msg( I18n.MSG_04123_MODIFY_SUCCESSFUL, modifyResponse ) );
3224                }
3225            }
3226            else
3227            {
3228                if ( modifyResponse instanceof ModifyNoDResponse )
3229                {
3230                    // A NoticeOfDisconnect : deserves a special treatment
3231                    throw new LdapException( modifyResponse.getLdapResult().getDiagnosticMessage() );
3232                }
3233
3234                // We have had an error
3235                if ( LOG.isDebugEnabled() )
3236                { 
3237                    LOG.debug( I18n.msg( I18n.MSG_04122_MODIFY_FAILED, modifyResponse ) );
3238                }
3239            }
3240
3241            return modifyResponse;
3242        }
3243        catch ( Exception ie )
3244        {
3245            // Catch all other exceptions
3246            LOG.error( NO_RESPONSE_ERROR, ie );
3247
3248            // Send an abandon request
3249            if ( !modifyFuture.isCancelled() )
3250            {
3251                abandon( modRequest.getMessageId() );
3252            }
3253
3254            throw new LdapException( ie.getMessage(), ie );
3255        }
3256    }
3257
3258
3259    /**
3260     * {@inheritDoc}
3261     */
3262    @Override
3263    public ModifyFuture modifyAsync( ModifyRequest modRequest ) throws LdapException
3264    {
3265        if ( modRequest == null )
3266        {
3267            String msg = I18n.err( I18n.ERR_04136_CANNOT_PROCESS_NULL_MOD_REQ );
3268
3269            if ( LOG.isDebugEnabled() )
3270            {
3271                LOG.debug( msg );
3272            }
3273            
3274            throw new IllegalArgumentException( msg );
3275        }
3276
3277        if ( modRequest.getName() == null )
3278        {
3279            String msg = I18n.err( I18n.ERR_04137_CANNOT_PROCESS_MOD_NULL_DN );
3280
3281            if ( LOG.isDebugEnabled() )
3282            {
3283                LOG.debug( msg );
3284            }
3285            
3286            throw new IllegalArgumentException( msg );
3287        }
3288
3289        // try to connect, if we aren't already connected.
3290        connect();
3291
3292        checkSession();
3293
3294        int newId = messageId.incrementAndGet();
3295        modRequest.setMessageId( newId );
3296
3297        ModifyFuture modifyFuture = new ModifyFuture( this, newId );
3298        addToFutureMap( newId, modifyFuture );
3299
3300        // Send the request to the server
3301        writeRequest( modRequest );
3302
3303        // Ok, done return the future
3304        return modifyFuture;
3305    }
3306
3307
3308    /**
3309     * {@inheritDoc}
3310     */
3311    @Override
3312    public void rename( String entryDn, String newRdn ) throws LdapException
3313    {
3314        rename( entryDn, newRdn, true );
3315    }
3316
3317
3318    /**
3319     * {@inheritDoc}
3320     */
3321    @Override
3322    public void rename( Dn entryDn, Rdn newRdn ) throws LdapException
3323    {
3324        rename( entryDn, newRdn, true );
3325    }
3326
3327
3328    /**
3329     * {@inheritDoc}
3330     */
3331    @Override
3332    public void rename( String entryDn, String newRdn, boolean deleteOldRdn ) throws LdapException
3333    {
3334        if ( entryDn == null )
3335        {
3336            String msg = I18n.err( I18n.ERR_04138_CANNOT_PROCESS_RENAME_NULL_DN );
3337
3338            if ( LOG.isDebugEnabled() )
3339            {
3340                LOG.debug( msg );
3341            }
3342            
3343            throw new IllegalArgumentException( msg );
3344        }
3345
3346        if ( newRdn == null )
3347        {
3348            String msg = I18n.err( I18n.ERR_04139_CANNOT_PROCESS_RENAME_NULL_RDN );
3349
3350            if ( LOG.isDebugEnabled() )
3351            {
3352                LOG.debug( msg );
3353            }
3354            
3355            throw new IllegalArgumentException( msg );
3356        }
3357
3358        try
3359        {
3360            rename( new Dn( entryDn ), new Rdn( newRdn ), deleteOldRdn );
3361        }
3362        catch ( LdapInvalidDnException e )
3363        {
3364            LOG.error( e.getMessage(), e );
3365            throw new LdapException( e.getMessage(), e );
3366        }
3367    }
3368
3369
3370    /**
3371     * {@inheritDoc}
3372     */
3373    @Override
3374    public void rename( Dn entryDn, Rdn newRdn, boolean deleteOldRdn ) throws LdapException
3375    {
3376        if ( entryDn == null )
3377        {
3378            String msg = I18n.err( I18n.ERR_04138_CANNOT_PROCESS_RENAME_NULL_DN );
3379
3380            if ( LOG.isDebugEnabled() )
3381            {
3382                LOG.debug( msg );
3383            }
3384            
3385            throw new IllegalArgumentException( msg );
3386        }
3387
3388        if ( newRdn == null )
3389        {
3390            String msg = I18n.err( I18n.ERR_04139_CANNOT_PROCESS_RENAME_NULL_RDN );
3391            
3392            if ( LOG.isDebugEnabled() )
3393            {
3394                LOG.debug( msg );
3395            }
3396            
3397            throw new IllegalArgumentException( msg );
3398        }
3399
3400        ModifyDnRequest modDnRequest = new ModifyDnRequestImpl();
3401        modDnRequest.setName( entryDn );
3402        modDnRequest.setNewRdn( newRdn );
3403        modDnRequest.setDeleteOldRdn( deleteOldRdn );
3404
3405        ModifyDnResponse modifyDnResponse = modifyDn( modDnRequest );
3406
3407        processResponse( modifyDnResponse );
3408    }
3409
3410
3411    /**
3412     * {@inheritDoc}
3413     */
3414    @Override
3415    public void move( String entryDn, String newSuperiorDn ) throws LdapException
3416    {
3417        if ( entryDn == null )
3418        {
3419            String msg = I18n.err( I18n.ERR_04140_CANNOT_PROCESS_MOVE_NULL_DN );
3420
3421            if ( LOG.isDebugEnabled() )
3422            {
3423                LOG.debug( msg );
3424            }
3425            
3426            throw new IllegalArgumentException( msg );
3427        }
3428
3429        if ( newSuperiorDn == null )
3430        {
3431            String msg = I18n.err( I18n.ERR_04141_CANNOT_PROCESS_MOVE_NULL_SUPERIOR );
3432            
3433            if ( LOG.isDebugEnabled() )
3434            {
3435                LOG.debug( msg );
3436            }
3437            
3438            throw new IllegalArgumentException( msg );
3439        }
3440
3441        try
3442        {
3443            move( new Dn( entryDn ), new Dn( newSuperiorDn ) );
3444        }
3445        catch ( LdapInvalidDnException e )
3446        {
3447            LOG.error( e.getMessage(), e );
3448            throw new LdapException( e.getMessage(), e );
3449        }
3450    }
3451
3452
3453    /**
3454     * {@inheritDoc}
3455     */
3456    @Override
3457    public void move( Dn entryDn, Dn newSuperiorDn ) throws LdapException
3458    {
3459        if ( entryDn == null )
3460        {
3461            String msg = I18n.err( I18n.ERR_04140_CANNOT_PROCESS_MOVE_NULL_DN );
3462
3463            if ( LOG.isDebugEnabled() )
3464            {
3465                LOG.debug( msg );
3466            }
3467            
3468            throw new IllegalArgumentException( msg );
3469        }
3470
3471        if ( newSuperiorDn == null )
3472        {
3473            String msg = I18n.err( I18n.ERR_04141_CANNOT_PROCESS_MOVE_NULL_SUPERIOR );
3474
3475            if ( LOG.isDebugEnabled() )
3476            {
3477                LOG.debug( msg );
3478            }
3479            
3480            throw new IllegalArgumentException( msg );
3481        }
3482
3483        ModifyDnRequest modDnRequest = new ModifyDnRequestImpl();
3484        modDnRequest.setName( entryDn );
3485        modDnRequest.setNewSuperior( newSuperiorDn );
3486
3487        modDnRequest.setNewRdn( entryDn.getRdn() );
3488
3489        ModifyDnResponse modifyDnResponse = modifyDn( modDnRequest );
3490
3491        processResponse( modifyDnResponse );
3492    }
3493
3494
3495    /**
3496     * {@inheritDoc}
3497     */
3498    @Override
3499    public void moveAndRename( Dn entryDn, Dn newDn ) throws LdapException
3500    {
3501        moveAndRename( entryDn, newDn, true );
3502    }
3503
3504
3505    /**
3506     * {@inheritDoc}
3507     */
3508    @Override
3509    public void moveAndRename( String entryDn, String newDn ) throws LdapException
3510    {
3511        moveAndRename( new Dn( entryDn ), new Dn( newDn ), true );
3512    }
3513
3514
3515    /**
3516     * {@inheritDoc}
3517     */
3518    @Override
3519    public void moveAndRename( Dn entryDn, Dn newDn, boolean deleteOldRdn ) throws LdapException
3520    {
3521        // Check the parameters first
3522        if ( entryDn == null )
3523        {
3524            throw new IllegalArgumentException( I18n.err( I18n.ERR_04142_NULL_ENTRY_DN ) );
3525        }
3526
3527        if ( entryDn.isRootDse() )
3528        {
3529            throw new IllegalArgumentException( I18n.err( I18n.ERR_04143_CANNOT_MOVE_ROOT_DSE ) );
3530        }
3531
3532        if ( newDn == null )
3533        {
3534            throw new IllegalArgumentException( I18n.err( I18n.ERR_04144_NULL_NEW_DN ) );
3535        }
3536
3537        if ( newDn.isRootDse() )
3538        {
3539            throw new IllegalArgumentException( I18n.err( I18n.ERR_04145_ROOT_DSE_CANNOT_BE_TARGET ) );
3540        }
3541
3542        // Create the request
3543        ModifyDnRequest modDnRequest = new ModifyDnRequestImpl();
3544        modDnRequest.setName( entryDn );
3545        modDnRequest.setNewRdn( newDn.getRdn() );
3546        
3547        // Check if we really need to specify newSuperior.
3548        // newSuperior is optional [RFC4511, section 4.9]
3549        // Some servers (e.g. OpenDJ 2.6) require a special privilege if
3550        // newSuperior is specified even if it is the same as the old one. Therefore let's not
3551        // specify it if we do not need it. This is better interoperability. 
3552        Dn newDnParent = newDn.getParent();
3553        if ( newDnParent != null && !newDnParent.equals( entryDn.getParent() ) )
3554        {
3555            modDnRequest.setNewSuperior( newDnParent );
3556        }
3557        
3558        modDnRequest.setDeleteOldRdn( deleteOldRdn );
3559
3560        ModifyDnResponse modifyDnResponse = modifyDn( modDnRequest );
3561
3562        processResponse( modifyDnResponse );
3563    }
3564
3565
3566    /**
3567     * {@inheritDoc}
3568     */
3569    @Override
3570    public void moveAndRename( String entryDn, String newDn, boolean deleteOldRdn ) throws LdapException
3571    {
3572        moveAndRename( new Dn( entryDn ), new Dn( newDn ), true );
3573    }
3574
3575
3576    /**
3577     * {@inheritDoc}
3578     */
3579    @Override
3580    public ModifyDnResponse modifyDn( ModifyDnRequest modDnRequest ) throws LdapException
3581    {
3582        if ( modDnRequest == null )
3583        {
3584            String msg = I18n.err( I18n.ERR_04145_ROOT_DSE_CANNOT_BE_TARGET );
3585
3586            if ( LOG.isDebugEnabled() )
3587            {
3588                LOG.debug( msg );
3589            }
3590            
3591            throw new IllegalArgumentException( msg );
3592        }
3593
3594        ModifyDnFuture modifyDnFuture = modifyDnAsync( modDnRequest );
3595
3596        // Get the result from the future
3597        try
3598        {
3599            // Read the response, waiting for it if not available immediately
3600            // Get the response, blocking
3601            ModifyDnResponse modifyDnResponse = modifyDnFuture.get( writeOperationTimeout, TimeUnit.MILLISECONDS );
3602
3603            if ( modifyDnResponse == null )
3604            {
3605                // We didn't received anything : this is an error
3606                if ( LOG.isErrorEnabled() )
3607                {
3608                    LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "ModifyDn" ) );
3609                }
3610                
3611                throw new LdapException( TIME_OUT_ERROR );
3612            }
3613
3614            if ( modifyDnResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
3615            {
3616                // Everything is fine, return the response
3617                if ( LOG.isDebugEnabled() )
3618                { 
3619                    LOG.debug( I18n.msg( I18n.MSG_04125_MODIFYDN_SUCCESSFUL, modifyDnResponse ) );
3620                }
3621            }
3622            else
3623            {
3624                // We have had an error
3625                if ( LOG.isDebugEnabled() )
3626                { 
3627                    LOG.debug( I18n.msg( I18n.MSG_04124_MODIFYDN_FAILED, modifyDnResponse ) );
3628                }
3629            }
3630
3631            return modifyDnResponse;
3632        }
3633        catch ( Exception ie )
3634        {
3635            // Catch all other exceptions
3636            LOG.error( NO_RESPONSE_ERROR, ie );
3637
3638            // Send an abandon request
3639            if ( !modifyDnFuture.isCancelled() )
3640            {
3641                abandon( modDnRequest.getMessageId() );
3642            }
3643
3644            throw new LdapException( NO_RESPONSE_ERROR, ie );
3645        }
3646    }
3647
3648
3649    /**
3650     * {@inheritDoc}
3651     */
3652    @Override
3653    public ModifyDnFuture modifyDnAsync( ModifyDnRequest modDnRequest ) throws LdapException
3654    {
3655        if ( modDnRequest == null )
3656        {
3657            String msg = I18n.err( I18n.ERR_04145_ROOT_DSE_CANNOT_BE_TARGET );
3658
3659            if ( LOG.isDebugEnabled() )
3660            {
3661                LOG.debug( msg );
3662            }
3663            
3664            throw new IllegalArgumentException( msg );
3665        }
3666
3667        if ( modDnRequest.getName() == null )
3668        {
3669            String msg = I18n.err( I18n.ERR_04137_CANNOT_PROCESS_MOD_NULL_DN );
3670
3671            if ( LOG.isDebugEnabled() )
3672            {
3673                LOG.debug( msg );
3674            }
3675            
3676            throw new IllegalArgumentException( msg );
3677        }
3678
3679        if ( ( modDnRequest.getNewSuperior() == null ) && ( modDnRequest.getNewRdn() == null ) )
3680        {
3681            String msg = I18n.err( I18n.ERR_04147_CANNOT_PROCESS_MOD_NULL_DN_SUP );
3682
3683            if ( LOG.isDebugEnabled() )
3684            {
3685                LOG.debug( msg );
3686            }
3687            
3688            throw new IllegalArgumentException( msg );
3689        }
3690
3691        // try to connect, if we aren't already connected.
3692        connect();
3693
3694        checkSession();
3695
3696        int newId = messageId.incrementAndGet();
3697        modDnRequest.setMessageId( newId );
3698
3699        ModifyDnFuture modifyDnFuture = new ModifyDnFuture( this, newId );
3700        addToFutureMap( newId, modifyDnFuture );
3701
3702        // Send the request to the server
3703        writeRequest( modDnRequest );
3704
3705        // Ok, done return the future
3706        return modifyDnFuture;
3707    }
3708
3709
3710    /**
3711     * {@inheritDoc}
3712     */
3713    @Override
3714    public void delete( String dn ) throws LdapException
3715    {
3716        delete( new Dn( dn ) );
3717    }
3718
3719
3720    /**
3721     * {@inheritDoc}
3722     */
3723    @Override
3724    public void delete( Dn dn ) throws LdapException
3725    {
3726        DeleteRequest deleteRequest = new DeleteRequestImpl();
3727        deleteRequest.setName( dn );
3728
3729        DeleteResponse deleteResponse = delete( deleteRequest );
3730
3731        processResponse( deleteResponse );
3732    }
3733
3734
3735    /**
3736     * deletes the entry with the given Dn, and all its children
3737     *
3738     * @param dn the target entry's Dn
3739     * @throws LdapException If the Dn is not valid or if the deletion failed
3740     */
3741    public void deleteTree( Dn dn ) throws LdapException
3742    {
3743        if ( isControlSupported( TreeDelete.OID ) )
3744        {
3745            DeleteRequest deleteRequest = new DeleteRequestImpl();
3746            deleteRequest.setName( dn );
3747            deleteRequest.addControl( new TreeDeleteImpl() );
3748            DeleteResponse deleteResponse = delete( deleteRequest );
3749
3750            processResponse( deleteResponse );
3751        }
3752        else
3753        {
3754            String msg = I18n.err( I18n.ERR_04148_SUBTREE_CONTROL_NOT_SUPPORTED );
3755            LOG.error( msg );
3756            throw new LdapException( msg );
3757        }
3758    }
3759
3760
3761    /**
3762     * deletes the entry with the given Dn, and all its children
3763     *
3764     * @param dn the target entry's Dn as a String
3765     * @throws LdapException If the Dn is not valid or if the deletion failed
3766     */
3767    public void deleteTree( String dn ) throws LdapException
3768    {
3769        try
3770        {
3771            String treeDeleteOid = "1.2.840.113556.1.4.805";
3772            Dn newDn = new Dn( dn );
3773
3774            if ( isControlSupported( treeDeleteOid ) )
3775            {
3776                DeleteRequest deleteRequest = new DeleteRequestImpl();
3777                deleteRequest.setName( newDn );
3778                deleteRequest.addControl( new OpaqueControl( treeDeleteOid ) );
3779                DeleteResponse deleteResponse = delete( deleteRequest );
3780
3781                processResponse( deleteResponse );
3782            }
3783            else
3784            {
3785                String msg = I18n.err( I18n.ERR_04148_SUBTREE_CONTROL_NOT_SUPPORTED );
3786                LOG.error( msg );
3787                throw new LdapException( msg );
3788            }
3789        }
3790        catch ( LdapInvalidDnException e )
3791        {
3792            LOG.error( e.getMessage(), e );
3793            throw new LdapException( e.getMessage(), e );
3794        }
3795    }
3796
3797
3798    /**
3799     * {@inheritDoc}
3800     */
3801    @Override
3802    public DeleteResponse delete( DeleteRequest deleteRequest ) throws LdapException
3803    {
3804        if ( deleteRequest == null )
3805        {
3806            String msg = I18n.err( I18n.ERR_04149_CANNOT_PROCESS_NULL_DEL_REQ );
3807
3808            if ( LOG.isDebugEnabled() )
3809            {
3810                LOG.debug( msg );
3811            }
3812            
3813            throw new IllegalArgumentException( msg );
3814        }
3815
3816        DeleteFuture deleteFuture = deleteAsync( deleteRequest );
3817
3818        // Get the result from the future
3819        try
3820        {
3821            // Read the response, waiting for it if not available immediately
3822            // Get the response, blocking
3823            DeleteResponse delResponse = deleteFuture.get( writeOperationTimeout, TimeUnit.MILLISECONDS );
3824
3825            if ( delResponse == null )
3826            {
3827                // We didn't received anything : this is an error
3828                if ( LOG.isErrorEnabled() )
3829                {
3830                    LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Delete" ) );
3831                }
3832                
3833                throw new LdapException( TIME_OUT_ERROR );
3834            }
3835
3836            if ( delResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
3837            {
3838                // Everything is fine, return the response
3839                if ( LOG.isDebugEnabled() )
3840                { 
3841                    LOG.debug( I18n.msg( I18n.MSG_04116_DELETE_SUCCESSFUL, delResponse ) );
3842                }
3843            }
3844            else
3845            {
3846                // We have had an error
3847                if ( LOG.isDebugEnabled() )
3848                { 
3849                    LOG.debug( I18n.msg( I18n.MSG_04115_DELETE_FAILED, delResponse ) );
3850                }
3851            }
3852
3853            return delResponse;
3854        }
3855        catch ( Exception ie )
3856        {
3857            // Catch all other exceptions
3858            LOG.error( NO_RESPONSE_ERROR, ie );
3859
3860            // Send an abandon request
3861            if ( !deleteFuture.isCancelled() )
3862            {
3863                abandon( deleteRequest.getMessageId() );
3864            }
3865
3866            throw new LdapException( NO_RESPONSE_ERROR, ie );
3867        }
3868    }
3869
3870
3871    /**
3872     * {@inheritDoc}
3873     */
3874    @Override
3875    public DeleteFuture deleteAsync( DeleteRequest deleteRequest ) throws LdapException
3876    {
3877        if ( deleteRequest == null )
3878        {
3879            String msg = I18n.err( I18n.ERR_04149_CANNOT_PROCESS_NULL_DEL_REQ );
3880
3881            if ( LOG.isDebugEnabled() )
3882            {
3883                LOG.debug( msg );
3884            }
3885            
3886            throw new IllegalArgumentException( msg );
3887        }
3888
3889        if ( deleteRequest.getName() == null )
3890        {
3891            String msg = I18n.err( I18n.ERR_04150_CANNOT_PROCESS_NULL_DEL_NULL_DN );
3892
3893            if ( LOG.isDebugEnabled() )
3894            {
3895                LOG.debug( msg );
3896            }
3897            
3898            throw new IllegalArgumentException( msg );
3899        }
3900
3901        // try to connect, if we aren't already connected.
3902        connect();
3903
3904        checkSession();
3905
3906        int newId = messageId.incrementAndGet();
3907
3908        deleteRequest.setMessageId( newId );
3909
3910        DeleteFuture deleteFuture = new DeleteFuture( this, newId );
3911        addToFutureMap( newId, deleteFuture );
3912
3913        // Send the request to the server
3914        writeRequest( deleteRequest );
3915
3916        // Ok, done return the future
3917        return deleteFuture;
3918    }
3919
3920
3921    /**
3922     * {@inheritDoc}
3923     */
3924    @Override
3925    public boolean compare( String dn, String attributeName, String value ) throws LdapException
3926    {
3927        return compare( new Dn( dn ), attributeName, value );
3928    }
3929
3930
3931    /**
3932     * {@inheritDoc}
3933     */
3934    @Override
3935    public boolean compare( String dn, String attributeName, byte[] value ) throws LdapException
3936    {
3937        return compare( new Dn( dn ), attributeName, value );
3938    }
3939
3940
3941    /**
3942     * {@inheritDoc}
3943     */
3944    @Override
3945    public boolean compare( String dn, String attributeName, Value value ) throws LdapException
3946    {
3947        return compare( new Dn( dn ), attributeName, value );
3948    }
3949
3950
3951    /**
3952     * {@inheritDoc}
3953     */
3954    @Override
3955    public boolean compare( Dn dn, String attributeName, String value ) throws LdapException
3956    {
3957        CompareRequest compareRequest = new CompareRequestImpl();
3958        compareRequest.setName( dn );
3959        compareRequest.setAttributeId( attributeName );
3960        compareRequest.setAssertionValue( value );
3961
3962        CompareResponse compareResponse = compare( compareRequest );
3963
3964        return processResponse( compareResponse );
3965    }
3966
3967
3968    /**
3969     * {@inheritDoc}
3970     */
3971    @Override
3972    public boolean compare( Dn dn, String attributeName, byte[] value ) throws LdapException
3973    {
3974        CompareRequest compareRequest = new CompareRequestImpl();
3975        compareRequest.setName( dn );
3976        compareRequest.setAttributeId( attributeName );
3977        compareRequest.setAssertionValue( value );
3978
3979        CompareResponse compareResponse = compare( compareRequest );
3980
3981        return processResponse( compareResponse );
3982    }
3983
3984
3985    /**
3986     * {@inheritDoc}
3987     */
3988    @Override
3989    public boolean compare( Dn dn, String attributeName, Value value ) throws LdapException
3990    {
3991        CompareRequest compareRequest = new CompareRequestImpl();
3992        compareRequest.setName( dn );
3993        compareRequest.setAttributeId( attributeName );
3994
3995        if ( value.isHumanReadable() )
3996        {
3997            compareRequest.setAssertionValue( value.getString() );
3998        }
3999        else
4000        {
4001            compareRequest.setAssertionValue( value.getBytes() );
4002        }
4003
4004        CompareResponse compareResponse = compare( compareRequest );
4005
4006        return processResponse( compareResponse );
4007    }
4008
4009
4010    /**
4011     * {@inheritDoc}
4012     */
4013    @Override
4014    public CompareResponse compare( CompareRequest compareRequest ) throws LdapException
4015    {
4016        if ( compareRequest == null )
4017        {
4018            String msg = I18n.err( I18n.ERR_04151_CANNOT_PROCESS_NULL_COMP_REQ );
4019
4020            if ( LOG.isDebugEnabled() )
4021            {
4022                LOG.debug( msg );
4023            }
4024            
4025            throw new IllegalArgumentException( msg );
4026        }
4027
4028        CompareFuture compareFuture = compareAsync( compareRequest );
4029
4030        // Get the result from the future
4031        try
4032        {
4033            // Read the response, waiting for it if not available immediately
4034            // Get the response, blocking
4035            CompareResponse compareResponse = compareFuture.get( readOperationTimeout, TimeUnit.MILLISECONDS );
4036
4037            if ( compareResponse == null )
4038            {
4039                // We didn't received anything : this is an error
4040                if ( LOG.isErrorEnabled() )
4041                {
4042                    LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Compare" ) );
4043                }
4044                
4045                throw new LdapException( TIME_OUT_ERROR );
4046            }
4047
4048            if ( compareResponse.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
4049            {
4050                // Everything is fine, return the response
4051                if ( LOG.isDebugEnabled() )
4052                { 
4053                    LOG.debug( I18n.msg( I18n.MSG_04114_COMPARE_SUCCESSFUL, compareResponse ) );
4054                }
4055            }
4056            else
4057            {
4058                // We have had an error
4059                if ( LOG.isDebugEnabled() )
4060                { 
4061                    LOG.debug( I18n.msg( I18n.MSG_04113_COMPARE_FAILED, compareResponse ) );
4062                }
4063            }
4064
4065            return compareResponse;
4066        }
4067        catch ( Exception ie )
4068        {
4069            // Catch all other exceptions
4070            LOG.error( NO_RESPONSE_ERROR, ie );
4071
4072            // Send an abandon request
4073            if ( !compareFuture.isCancelled() )
4074            {
4075                abandon( compareRequest.getMessageId() );
4076            }
4077
4078            throw new LdapException( NO_RESPONSE_ERROR, ie );
4079        }
4080    }
4081
4082
4083    /**
4084     * {@inheritDoc}
4085     */
4086    @Override
4087    public CompareFuture compareAsync( CompareRequest compareRequest ) throws LdapException
4088    {
4089        if ( compareRequest == null )
4090        {
4091            String msg = I18n.err( I18n.ERR_04151_CANNOT_PROCESS_NULL_COMP_REQ );
4092
4093            if ( LOG.isDebugEnabled() )
4094            {
4095                LOG.debug( msg );
4096            }
4097            
4098            throw new IllegalArgumentException( msg );
4099        }
4100
4101        if ( compareRequest.getName() == null )
4102        {
4103            String msg = I18n.err( I18n.ERR_04152_CANNOT_PROCESS_NULL_DN_COMP_REQ );
4104
4105            if ( LOG.isDebugEnabled() )
4106            {
4107                LOG.debug( msg );
4108            }
4109            
4110            throw new IllegalArgumentException( msg );
4111        }
4112
4113        // try to connect, if we aren't already connected.
4114        connect();
4115
4116        checkSession();
4117
4118        int newId = messageId.incrementAndGet();
4119
4120        compareRequest.setMessageId( newId );
4121
4122        CompareFuture compareFuture = new CompareFuture( this, newId );
4123        addToFutureMap( newId, compareFuture );
4124
4125        // Send the request to the server
4126        writeRequest( compareRequest );
4127
4128        // Ok, done return the future
4129        return compareFuture;
4130    }
4131
4132
4133    /**
4134     * {@inheritDoc}
4135     */
4136    @Override
4137    public ExtendedResponse extended( String oid ) throws LdapException
4138    {
4139        return extended( oid, null );
4140    }
4141
4142
4143    /**
4144     * {@inheritDoc}
4145     */
4146    @Override
4147    public ExtendedResponse extended( String oid, byte[] value ) throws LdapException
4148    {
4149        try
4150        {
4151            return extended( Oid.fromString( oid ), value );
4152        }
4153        catch ( DecoderException e )
4154        {
4155            String msg = I18n.err( I18n.ERR_04153_OID_DECODING_FAILURE, oid );
4156            LOG.error( msg );
4157            throw new LdapException( msg, e );
4158        }
4159    }
4160
4161
4162    /**
4163     * {@inheritDoc}
4164     */
4165    @Override
4166    public ExtendedResponse extended( Oid oid ) throws LdapException
4167    {
4168        return extended( oid, null );
4169    }
4170
4171
4172    /**
4173     * {@inheritDoc}
4174     */
4175    @Override
4176    public ExtendedResponse extended( Oid oid, byte[] value ) throws LdapException
4177    {
4178        Map<String, ExtendedOperationFactory> factories = LdapApiServiceFactory.getSingleton().getExtendedRequestFactories();
4179        String oidStr = oid.toString();
4180        
4181        ExtendedOperationFactory factory = factories.get( oidStr );
4182        
4183        if ( factory != null )
4184        {
4185            try
4186            {
4187                if ( value == null )
4188                {
4189                    return extended( factory.newRequest() );
4190                }
4191                else
4192                {
4193                    return extended( factory.newRequest( value ) );
4194                }
4195            }
4196            catch ( DecoderException de )
4197            {
4198                throw new LdapNoSuchObjectException( de.getMessage() );
4199            }
4200        }
4201        else
4202        {
4203            return extended( new OpaqueExtendedRequest( oidStr, value ) );
4204        }
4205    }
4206
4207
4208    /**
4209     * {@inheritDoc}
4210     */
4211    @Override
4212    public ExtendedResponse extended( ExtendedRequest extendedRequest ) throws LdapException
4213    {
4214        if ( extendedRequest == null )
4215        {
4216            String msg = I18n.err( I18n.ERR_04154_CANNOT_PROCESS_NULL_EXT_REQ );
4217
4218            if ( LOG.isDebugEnabled() )
4219            {
4220                LOG.debug( msg );
4221            }
4222            
4223            throw new IllegalArgumentException( msg );
4224        }
4225
4226        ExtendedFuture extendedFuture = extendedAsync( extendedRequest );
4227
4228        // Get the result from the future
4229        try
4230        {
4231            // Read the response, waiting for it if not available immediately
4232            // Get the response, blocking
4233            ExtendedResponse response = ( ExtendedResponse ) extendedFuture
4234                .get( timeout, TimeUnit.MILLISECONDS );
4235
4236            if ( response == null )
4237            {
4238                // We didn't received anything : this is an error
4239                if ( LOG.isErrorEnabled() )
4240                {
4241                    LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Extended" ) );
4242                }
4243                
4244                throw new LdapException( TIME_OUT_ERROR );
4245            }
4246
4247            if ( response.getLdapResult().getResultCode() == ResultCodeEnum.SUCCESS )
4248            {
4249                // Everything is fine, return the response
4250                if ( LOG.isDebugEnabled() )
4251                { 
4252                    LOG.debug( I18n.msg( I18n.MSG_04118_EXTENDED_SUCCESSFUL, response ) );
4253                }
4254            }
4255            else
4256            {
4257                // We have had an error
4258                if ( LOG.isDebugEnabled() )
4259                { 
4260                    LOG.debug( I18n.msg( I18n.MSG_04117_EXTENDED_FAILED, response ) );
4261                }
4262            }
4263
4264            // Get back the response. It's still an opaque response
4265            if ( Strings.isEmpty( response.getResponseName() ) )
4266            {
4267                response.setResponseName( extendedRequest.getRequestName() );
4268            }
4269
4270            // Decode the payload now
4271            return response;
4272        }
4273        catch ( Exception ie )
4274        {
4275            if ( ie instanceof LdapException )
4276            {
4277                throw ( LdapException ) ie;
4278            }
4279
4280            // Catch all other exceptions
4281            LOG.error( NO_RESPONSE_ERROR, ie );
4282
4283            // Send an abandon request
4284            if ( !extendedFuture.isCancelled() )
4285            {
4286                abandon( extendedRequest.getMessageId() );
4287            }
4288
4289            throw new LdapException( NO_RESPONSE_ERROR, ie );
4290        }
4291    }
4292
4293
4294    /**
4295     * {@inheritDoc}
4296     */
4297    @Override
4298    public ExtendedFuture extendedAsync( ExtendedRequest extendedRequest ) throws LdapException
4299    {
4300        if ( extendedRequest == null )
4301        {
4302            String msg = I18n.err( I18n.ERR_04154_CANNOT_PROCESS_NULL_EXT_REQ );
4303
4304            if ( LOG.isDebugEnabled() )
4305            {
4306                LOG.debug( msg );
4307            }
4308            
4309            throw new IllegalArgumentException( msg );
4310        }
4311
4312        // try to connect, if we aren't already connected.
4313        connect();
4314
4315        checkSession();
4316
4317        int newId = messageId.incrementAndGet();
4318
4319        extendedRequest.setMessageId( newId );
4320        ExtendedFuture extendedFuture = new ExtendedFuture( this, newId );
4321        extendedFuture.setExtendedRequest( extendedRequest );
4322        addToFutureMap( newId, extendedFuture );
4323
4324        // Send the request to the server
4325        writeRequest( extendedRequest );
4326
4327        // Ok, done return the future
4328        return extendedFuture;
4329    }
4330
4331
4332    /**
4333     * {@inheritDoc}
4334     */
4335    @Override
4336    public boolean exists( String dn ) throws LdapException
4337    {
4338        return exists( new Dn( dn ) );
4339    }
4340
4341
4342    /**
4343     * {@inheritDoc}
4344     */
4345    @Override
4346    public boolean exists( Dn dn ) throws LdapException
4347    {
4348        try
4349        {
4350            Entry entry = lookup( dn, SchemaConstants.NO_ATTRIBUTE_ARRAY );
4351
4352            return entry != null;
4353        }
4354        catch ( LdapNoPermissionException lnpe )
4355        {
4356            // Special case to deal with insufficient permissions
4357            return false;
4358        }
4359        catch ( LdapException le )
4360        {
4361            throw le;
4362        }
4363    }
4364
4365
4366    /**
4367     * {@inheritDoc}
4368     */
4369    @Override
4370    public Entry getRootDse() throws LdapException
4371    {
4372        return lookup( Dn.ROOT_DSE, SchemaConstants.ALL_ATTRIBUTES_ARRAY );
4373    }
4374
4375
4376    /**
4377     * {@inheritDoc}
4378     */
4379    @Override
4380    public Entry getRootDse( String... attributes ) throws LdapException
4381    {
4382        return lookup( Dn.ROOT_DSE, attributes );
4383    }
4384
4385
4386    /**
4387     * {@inheritDoc}
4388     */
4389    @Override
4390    public Entry lookup( Dn dn ) throws LdapException
4391    {
4392        return lookup( dn, SchemaConstants.ALL_USER_ATTRIBUTES_ARRAY );
4393    }
4394
4395
4396    /**
4397     * {@inheritDoc}
4398     */
4399    @Override
4400    public Entry lookup( String dn ) throws LdapException
4401    {
4402        return lookup( dn, SchemaConstants.ALL_USER_ATTRIBUTES_ARRAY );
4403    }
4404
4405
4406    /**
4407     * {@inheritDoc}
4408     */
4409    @Override
4410    public Entry lookup( Dn dn, String... attributes ) throws LdapException
4411    {
4412        return lookup( dn, null, attributes );
4413    }
4414
4415
4416    /**
4417     * {@inheritDoc}
4418     */
4419    @Override
4420    public Entry lookup( Dn dn, Control[] controls, String... attributes ) throws LdapException
4421    {
4422        Entry entry = null;
4423
4424        try
4425        {
4426            SearchRequest searchRequest = new SearchRequestImpl();
4427
4428            searchRequest.setBase( dn );
4429            searchRequest.setFilter( LdapConstants.OBJECT_CLASS_STAR );
4430            searchRequest.setScope( SearchScope.OBJECT );
4431            searchRequest.addAttributes( attributes );
4432            searchRequest.setDerefAliases( AliasDerefMode.DEREF_ALWAYS );
4433
4434            if ( ( controls != null ) && ( controls.length > 0 ) )
4435            {
4436                searchRequest.addAllControls( controls );
4437            }
4438
4439            try ( Cursor<Response> cursor = search( searchRequest ) )
4440            {
4441                // Read the response
4442                if ( cursor.next() )
4443                {
4444                    // cursor will always hold SearchResultEntry objects cause there is no ManageDsaITControl passed with search request
4445                    entry = ( ( SearchResultEntry ) cursor.get() ).getEntry();
4446                }
4447    
4448                // Pass through the SaerchResultDone, or stop
4449                // if we have other responses
4450                cursor.next();
4451            }
4452        }
4453        catch ( CursorException e )
4454        {
4455            throw new LdapException( e.getMessage(), e );
4456        }
4457        catch ( IOException ioe )
4458        {
4459            throw new LdapException( ioe.getMessage(), ioe );
4460        }
4461
4462        return entry;
4463    }
4464
4465
4466    /**
4467     * {@inheritDoc}
4468     */
4469    @Override
4470    public Entry lookup( String dn, String... attributes ) throws LdapException
4471    {
4472        return lookup( new Dn( dn ), null, attributes );
4473    }
4474
4475
4476    /**
4477     * {@inheritDoc}
4478     */
4479    @Override
4480    public Entry lookup( String dn, Control[] controls, String... attributes ) throws LdapException
4481    {
4482        return lookup( new Dn( dn ), controls, attributes );
4483    }
4484
4485
4486    /**
4487     * {@inheritDoc}
4488     */
4489    @Override
4490    public boolean isControlSupported( String controlOID ) throws LdapException
4491    {
4492        return getSupportedControls().contains( controlOID );
4493    }
4494
4495
4496    /**
4497     * {@inheritDoc}
4498     */
4499    @Override
4500    public List<String> getSupportedControls() throws LdapException
4501    {
4502        if ( supportedControls != null )
4503        {
4504            return supportedControls;
4505        }
4506
4507        if ( rootDse == null )
4508        {
4509            fetchRootDSE();
4510        }
4511
4512        supportedControls = new ArrayList<>();
4513
4514        Attribute attr = rootDse.get( SchemaConstants.SUPPORTED_CONTROL_AT );
4515
4516        if ( attr == null )
4517        {
4518            // Unlikely. Perhaps the server does not respond properly to "+" attribute query
4519            // (such as 389ds server). So let's try again and let's be more explicit.
4520            fetchRootDSE( SchemaConstants.ALL_USER_ATTRIBUTES, 
4521                SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES, SchemaConstants.SUPPORTED_CONTROL_AT );
4522            attr = rootDse.get( SchemaConstants.SUPPORTED_CONTROL_AT );
4523            if ( attr == null )
4524            {
4525                return supportedControls;
4526            }
4527        }
4528        
4529        for ( Value value : attr )
4530        {
4531            supportedControls.add( value.getString() );
4532        }
4533
4534        return supportedControls;
4535    }
4536
4537
4538    /**
4539     * {@inheritDoc}
4540     */
4541    @Override
4542    public void loadSchema() throws LdapException
4543    {
4544        loadSchema( new DefaultSchemaLoader( this ) );
4545    }
4546
4547
4548    /**
4549     * {@inheritDoc}
4550     */
4551    @Override
4552    public void loadSchemaRelaxed() throws LdapException
4553    {
4554        loadSchema( new DefaultSchemaLoader( this, true ) );
4555    }
4556
4557
4558    /**
4559     * loads schema using the specified schema loader
4560     *
4561     * @param loader the {@link SchemaLoader} to be used to load schema
4562     * @throws LdapException If the schema loading failed
4563     */
4564    public void loadSchema( SchemaLoader loader ) throws LdapException
4565    {
4566        try
4567        {
4568            SchemaManager tmp = new DefaultSchemaManager( loader );
4569
4570            tmp.loadAllEnabled();
4571
4572            if ( !tmp.getErrors().isEmpty() && loader.isStrict() )
4573            {
4574                String msg = I18n.err( I18n.ERR_04115_ERROR_LOADING_SCHEMA );
4575                
4576                if ( LOG.isErrorEnabled() )
4577                {
4578                    LOG.error( I18n.err( I18n.ERR_05114_ERROR_MESSAGE, msg, Strings.listToString( tmp.getErrors() ) ) );
4579                }
4580                
4581                throw new LdapException( msg );
4582            }
4583
4584            schemaManager = tmp;
4585
4586            // Change the container's BinaryDetector
4587            ioSession.setAttribute( LdapDecoder.MESSAGE_CONTAINER_ATTR,
4588                new LdapMessageContainer<>( codec,
4589                    new SchemaBinaryAttributeDetector( schemaManager ) ) );
4590
4591        }
4592        catch ( LdapException le )
4593        {
4594            throw le;
4595        }
4596        catch ( Exception e )
4597        {
4598            LOG.error( I18n.err( I18n.ERR_04116_FAIL_LOAD_SCHEMA ), e );
4599            throw new LdapException( e );
4600        }
4601    }
4602
4603
4604    /**
4605     * parses the given schema file present in OpenLDAP schema format
4606     * and adds all the SchemaObjects present in it to the SchemaManager
4607     *
4608     * @param schemaFile the schema file in OpenLDAP schema format
4609     * @throws LdapException in case of any errors while parsing
4610     */
4611    public void addSchema( File schemaFile ) throws LdapException
4612    {
4613        try
4614        {
4615            if ( schemaManager == null )
4616            {
4617                loadSchema();
4618            }
4619            
4620            if ( schemaManager == null )
4621            {
4622                throw new LdapException( I18n.err( I18n.ERR_04116_FAIL_LOAD_SCHEMA ) );
4623            }
4624
4625            OpenLdapSchemaParser olsp = new OpenLdapSchemaParser();
4626            olsp.setQuirksMode( true );
4627            olsp.parse( schemaFile );
4628
4629            Registries registries = schemaManager.getRegistries();
4630
4631            for ( AttributeType atType : olsp.getAttributeTypes() )
4632            {
4633                registries.buildReference( atType );
4634                registries.getAttributeTypeRegistry().register( atType );
4635            }
4636
4637            for ( ObjectClass oc : olsp.getObjectClasses() )
4638            {
4639                registries.buildReference( oc );
4640                registries.getObjectClassRegistry().register( oc );
4641            }
4642
4643            if ( LOG.isInfoEnabled() )
4644            {
4645                LOG.info( I18n.msg( I18n.MSG_04167_SCHEMA_LOADED_SUCCESSFULLY, schemaFile.getAbsolutePath() ) );
4646            }
4647        }
4648        catch ( Exception e )
4649        {
4650            LOG.error( I18n.err( I18n.ERR_04117_FAIL_LOAD_SCHEMA_FILE, schemaFile.getAbsolutePath() ) );
4651            throw new LdapException( e );
4652        }
4653    }
4654
4655
4656    /**
4657     * @see #addSchema(File)
4658     * @param schemaFileName The schema file name to add
4659     * @throws LdapException If the schema addition failed
4660     */
4661    public void addSchema( String schemaFileName ) throws LdapException
4662    {
4663        addSchema( new File( schemaFileName ) );
4664    }
4665
4666
4667    /**
4668     * {@inheritDoc}
4669     */
4670    @Override
4671    public LdapApiService getCodecService()
4672    {
4673        return codec;
4674    }
4675
4676
4677    /**
4678     * {@inheritDoc}
4679     */
4680    @Override
4681    public SchemaManager getSchemaManager()
4682    {
4683        return schemaManager;
4684    }
4685
4686
4687    /**
4688     * fetches the rootDSE from the server
4689     * 
4690     * @param explicitAttributes The list of requested attributes
4691     * @throws LdapException If we weren't bale to fetch the RootDSE
4692     */
4693    private void fetchRootDSE( String... explicitAttributes ) throws LdapException
4694    {
4695        EntryCursor cursor = null;
4696
4697        String[] attributes = explicitAttributes;
4698        if ( attributes.length == 0 )
4699        {
4700            attributes = new String[]
4701                { SchemaConstants.ALL_USER_ATTRIBUTES, SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES };
4702        }
4703        
4704        try
4705        {
4706            cursor = search( "", LdapConstants.OBJECT_CLASS_STAR, SearchScope.OBJECT, attributes );
4707            if ( cursor.next() )
4708            {
4709                rootDse = cursor.get();
4710                // We have to call cursor.next() here, as we need to make sure that the "done" status of the cursor
4711                // is properly updated. Otherwise the subsequent cursor.close() initiates an ABANDON operation to
4712                // stop the search, which is in fact finished already.
4713                cursor.next();
4714            }
4715            else
4716            {
4717                throw new LdapException( I18n.err( I18n.ERR_04155_ROOT_DSE_SEARCH_FAILED ) );
4718            }
4719        }
4720        catch ( Exception e )
4721        {
4722            String msg = I18n.err( I18n.ERR_04156_FAILED_FETCHING_ROOT_DSE );
4723            LOG.error( msg );
4724            throw new LdapException( msg, e );
4725        }
4726        finally
4727        {
4728            if ( cursor != null )
4729            {
4730                try
4731                {
4732                    cursor.close();
4733                }
4734                catch ( Exception e )
4735                {
4736                    LOG.error( I18n.err( I18n.ERR_04114_CURSOR_CLOSE_FAIL ), e );
4737                }
4738            }
4739        }
4740    }
4741
4742
4743    /**
4744     * gives the configuration information of the connection
4745     *
4746     * @return the configuration of the connection
4747     */
4748    @Override
4749    public LdapConnectionConfig getConfig()
4750    {
4751        return config;
4752    }
4753
4754
4755    /**
4756     * removes the Objects associated with the given message ID
4757     * from future and response queue maps
4758     *
4759     * @param msgId id of the message
4760     */
4761    private void removeFromFutureMaps( int msgId )
4762    {
4763        getFromFutureMap( msgId );
4764    }
4765
4766
4767    /**
4768     * clears the async listener, responseQueue and future mapppings to the corresponding request IDs
4769     */
4770    private void clearMaps()
4771    {
4772        futureMap.clear();
4773    }
4774
4775
4776    /**
4777     * {@inheritDoc}
4778     */
4779    @Override
4780    public boolean isRequestCompleted( int messageId )
4781    {
4782        ResponseFuture<?> responseFuture = futureMap.get( messageId );
4783        
4784        return responseFuture == null;
4785    }
4786
4787
4788    /**
4789     * {@inheritDoc}
4790     */
4791    @Override
4792    public boolean doesFutureExistFor( int messageId )
4793    {
4794        ResponseFuture<?> responseFuture = futureMap.get( messageId );
4795        return responseFuture != null;
4796    }
4797
4798
4799    /**
4800     * Adds the connection closed event listener.
4801     *
4802     * @param ccListener the connection closed listener
4803     */
4804    public void addConnectionClosedEventListener( ConnectionClosedEventListener ccListener )
4805    {
4806        if ( conCloseListeners == null )
4807        {
4808            conCloseListeners = new ArrayList<>();
4809        }
4810
4811        conCloseListeners.add( ccListener );
4812    }
4813    
4814    
4815    /**
4816     * {@inheritDoc}
4817     */
4818    @Override
4819    public void inputClosed( IoSession session ) throws Exception 
4820    {
4821        session.closeNow();
4822    }
4823
4824
4825    /**
4826     * This method is called when a new session is created. We will store some
4827     * informations that the session will need to process incoming requests.
4828     * 
4829     * @param session the newly created session
4830     */
4831    @Override
4832    public void sessionCreated( IoSession session ) throws Exception
4833    {
4834        // Last, store the message container
4835        LdapMessageContainer<Message> ldapMessageContainer =
4836            new LdapMessageContainer<>(
4837                codec, config.getBinaryAttributeDetector() );
4838
4839        session.setAttribute( LdapDecoder.MESSAGE_CONTAINER_ATTR, ldapMessageContainer );
4840    }
4841
4842
4843    /**
4844     * {@inheritDoc}
4845     */
4846    @Override
4847    public void sessionClosed( IoSession session ) throws Exception
4848    {
4849        authenticated.set( false );
4850        
4851        if ( handshakeFuture != null )
4852        {
4853            handshakeFuture.cancel();
4854        }
4855        
4856        // Close all the Future for this session
4857        for ( ResponseFuture<? extends Response> responseFuture : futureMap.values() )
4858        {
4859            responseFuture.cancel();
4860        }
4861
4862        // clear the mappings
4863        clearMaps();
4864
4865        // Last, not least, reset the MessageId value
4866        messageId.set( 0 );
4867
4868        connectorMutex.lock();
4869
4870        try
4871        {
4872            if ( connector != null )
4873            {
4874                connector.dispose();
4875                connector = null;
4876            }
4877        }
4878        finally
4879        {
4880            connectorMutex.unlock();
4881        }
4882
4883        if ( conCloseListeners != null )
4884        {
4885            if ( LOG.isDebugEnabled() )
4886            {
4887                LOG.debug( I18n.msg( I18n.MSG_04136_NOTIFYING_CLOSE_LISTENERS ) );
4888            }
4889
4890            for ( ConnectionClosedEventListener listener : conCloseListeners )
4891            {
4892                listener.connectionClosed();
4893            }
4894        }
4895        
4896        connectionCloseFuture.complete( 0 );
4897    }
4898
4899
4900    /**
4901     * Sends the StartTLS extended request to server and adds a security layer
4902     * upon receiving a response with successful result. Note that we will use
4903     * the default LDAP connection.
4904     *
4905     * @throws LdapException If the StartTLS operation failed
4906     */
4907    public void startTls() throws LdapException
4908    {
4909        try
4910        {
4911            if ( config.isUseSsl() )
4912            {
4913                throw new LdapException( I18n.err( I18n.ERR_04157_CANNOT_USE_TLS_WITH_SSL_FLAG ) );
4914            }
4915
4916            // try to connect, if we aren't already connected.
4917            connect();
4918
4919            checkSession();
4920            
4921            if ( ioSession.isSecured() )
4922            {
4923                if ( LOG.isDebugEnabled() )
4924                { 
4925                    LOG.debug( I18n.msg( I18n.MSG_04121_LDAP_ALREADY_USING_START_TLS ) );
4926                }
4927                
4928                return;
4929            }
4930
4931            ExtendedResponse resp = extended( new StartTlsRequestImpl() );
4932            LdapResult result = resp.getLdapResult();
4933
4934            if ( result.getResultCode() == ResultCodeEnum.SUCCESS )
4935            {
4936                addSslFilter();
4937            }
4938            else
4939            {
4940                throw new LdapOperationException( result.getResultCode(), result.getDiagnosticMessage() );
4941            }
4942        }
4943        catch ( LdapException e )
4944        {
4945            throw e;
4946        }
4947        catch ( Exception e )
4948        {
4949            throw new LdapException( e );
4950        }
4951    }
4952
4953
4954    /**
4955     * Adds a {@link SaslFilter} to the session's filter chain.
4956     * 
4957     * @param saslClient The initialized SASL client
4958     * 
4959     * @throws LdapException
4960     */
4961    private void addSaslFilter( SaslClient saslClient ) throws LdapException
4962    {
4963        IoFilterChain filterChain = ioSession.getFilterChain();
4964        if ( filterChain.contains( SASL_FILTER_KEY ) )
4965        {
4966            filterChain.remove( SASL_FILTER_KEY );
4967        }
4968
4969        SaslFilter saslFilter = new SaslFilter( saslClient );
4970        filterChain.addBefore( LDAP_CODEC_FILTER_KEY, SASL_FILTER_KEY, saslFilter );
4971    }
4972
4973
4974    /**
4975     * Adds {@link SslFilter} to the IOConnector or IOSession's filter chain
4976     * 
4977     * @throws LdapException If the SSL filter addition failed
4978     */
4979    private void addSslFilter() throws LdapException
4980    {
4981        try
4982        {
4983            SSLContext sslContext = SSLContext.getInstance( config.getSslProtocol() );
4984            
4985            sslContext.init( config.getKeyManagers(), config.getTrustManagers(), config.getSecureRandom() );
4986
4987            SslFilter sslFilter = new SslFilter( sslContext );
4988            //sslFilter.setUseClientMode( true );
4989
4990            // Configure the enabled cipher lists
4991            String[] enabledCipherSuite = config.getEnabledCipherSuites();
4992
4993            if ( ( enabledCipherSuite != null ) && ( enabledCipherSuite.length != 0 ) )
4994            {
4995                sslFilter.setEnabledCipherSuites( enabledCipherSuite );
4996            }
4997
4998            // Be sure we disable SSLV3
4999            String[] enabledProtocols = config.getEnabledProtocols();
5000
5001            if ( ( enabledProtocols != null ) && ( enabledProtocols.length != 0 ) )
5002            {
5003                sslFilter.setEnabledProtocols( enabledProtocols );
5004            }
5005            else
5006            {
5007                // Default to TLS
5008                sslFilter.setEnabledProtocols( new String[]
5009                    { "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" } );
5010            }
5011
5012            // for LDAPS/TLS
5013            handshakeFuture = new HandshakeFuture();
5014            
5015            if ( ( ioSession == null ) || !isConnected() )
5016            {
5017                connector.getFilterChain().addFirst( SSL_FILTER_KEY, sslFilter );
5018            }
5019            else
5020            // for StartTLS
5021            {
5022                ioSession.getFilterChain().addFirst( SSL_FILTER_KEY, sslFilter );
5023                
5024                boolean isSecured = handshakeFuture.get( connectTimeout, TimeUnit.MILLISECONDS );
5025                
5026                if ( !isSecured )
5027                {
5028                    Throwable cause = ( Throwable ) ioSession.getAttribute( EXCEPTION_KEY );
5029                    throw new LdapTlsHandshakeException( I18n.err( I18n.ERR_04120_TLS_HANDSHAKE_ERROR ), cause );
5030                }
5031            }
5032        }
5033        catch ( Exception e )
5034        {
5035            if ( e instanceof LdapException )
5036            {
5037                throw ( LdapException ) e;
5038            }
5039
5040            String msg = I18n.err( I18n.ERR_04122_SSL_CONTEXT_INIT_FAILURE );
5041            LOG.error( msg, e );
5042            throw new LdapException( msg, e );
5043        }
5044    }
5045
5046
5047    /**
5048     * Process the SASL Bind. It's a dialog with the server, we will send a first BindRequest, receive
5049     * a response and the, if this response is a challenge, continue by sending a new BindRequest with
5050     * the requested informations.
5051     *
5052     * @param saslRequest The SASL request object containing all the needed parameters
5053     * @return A {@link BindResponse} containing the result
5054     * @throws LdapException if some error occurred
5055     */
5056    public BindFuture bindSasl( SaslRequest saslRequest ) throws LdapException
5057    {
5058        // First switch to anonymous state
5059        authenticated.set( false );
5060
5061        // try to connect, if we aren't already connected.
5062        connect();
5063
5064        // If the session has not been establish, or is closed, we get out immediately
5065        checkSession();
5066
5067        BindRequest bindRequest = createBindRequest( ( String ) null, null,
5068            saslRequest.getSaslMechanism(), saslRequest.getControls() );
5069
5070        // Update the messageId
5071        int newId = messageId.incrementAndGet();
5072        bindRequest.setMessageId( newId );
5073
5074        if ( LOG.isDebugEnabled() )
5075        {
5076            LOG.debug( I18n.msg( I18n.MSG_04104_SENDING_REQUEST, bindRequest ) );
5077        }
5078
5079        // Create a future for this Bind operation
5080        BindFuture bindFuture = new BindFuture( this, newId );
5081
5082        // Store it in the future Map
5083        addToFutureMap( newId, bindFuture );
5084
5085        try
5086        {
5087            BindResponse bindResponse;
5088            byte[] response;
5089            ResultCodeEnum result;
5090
5091            // Creating a map for SASL properties
5092            Map<String, Object> properties = new HashMap<>();
5093
5094            // Quality of Protection SASL property
5095            if ( saslRequest.getQualityOfProtection() != null )
5096            {
5097                properties.put( Sasl.QOP, saslRequest.getQualityOfProtection().getValue() );
5098            }
5099
5100            // Security Strength SASL property
5101            if ( saslRequest.getSecurityStrength() != null )
5102            {
5103                properties.put( Sasl.STRENGTH, saslRequest.getSecurityStrength().getValue() );
5104            }
5105
5106            // Mutual Authentication SASL property
5107            if ( saslRequest.isMutualAuthentication() )
5108            {
5109                properties.put( Sasl.SERVER_AUTH, "true" );
5110            }
5111
5112            // Creating a SASL Client
5113            SaslClient sc = Sasl.createSaslClient(
5114                new String[]
5115                    { bindRequest.getSaslMechanism() },
5116                saslRequest.getAuthorizationId(),
5117                "ldap",
5118                config.getLdapHost(),
5119                properties,
5120                new SaslCallbackHandler( saslRequest ) );
5121
5122            // If the SaslClient wasn't created, that means we can't create the SASL client
5123            // for the requested mechanism. We then produce an Exception
5124            if ( sc == null )
5125            {
5126                String message = I18n.err( I18n.ERR_04158_CANNOT_FIND_SASL_FACTORY_FOR_MECH, bindRequest.getSaslMechanism() );
5127                LOG.error( message );
5128                throw new LdapException( message );
5129            }
5130
5131            // Corner case : the SASL mech might send an initial challenge, and we have to
5132            // deal with it immediately.
5133            if ( sc.hasInitialResponse() )
5134            {
5135                byte[] challengeResponse = sc.evaluateChallenge( Strings.EMPTY_BYTES );
5136
5137                // Stores the challenge's response, and send it to the server
5138                bindRequest.setCredentials( challengeResponse );
5139                writeRequest( bindRequest );
5140
5141                // Get the server's response, blocking
5142                bindResponse = bindFuture.get( connectTimeout, TimeUnit.MILLISECONDS );
5143
5144                if ( bindResponse == null )
5145                {
5146                    // We didn't received anything : this is an error
5147                    if ( LOG.isErrorEnabled() )
5148                    { 
5149                        LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Bind" ) );
5150                    }
5151                    
5152                    throw new LdapException( TIME_OUT_ERROR );
5153                }
5154
5155                result = bindResponse.getLdapResult().getResultCode();
5156            }
5157            else
5158            {
5159                // Copy the bindRequest without setting the credentials
5160                BindRequest bindRequestCopy = new BindRequestImpl();
5161                bindRequestCopy.setMessageId( newId );
5162
5163                bindRequestCopy.setName( bindRequest.getName() );
5164                bindRequestCopy.setSaslMechanism( bindRequest.getSaslMechanism() );
5165                bindRequestCopy.setSimple( bindRequest.isSimple() );
5166                bindRequestCopy.setVersion3( bindRequest.getVersion3() );
5167                bindRequestCopy.addAllControls( bindRequest.getControls().values().toArray( new Control[0] ) );
5168
5169                writeRequest( bindRequestCopy );
5170
5171                bindResponse = bindFuture.get( connectTimeout, TimeUnit.MILLISECONDS );
5172
5173                if ( bindResponse == null )
5174                {
5175                    // We didn't received anything : this is an error
5176                    if ( LOG.isErrorEnabled() )
5177                    {
5178                        LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Bind" ) );
5179                    }
5180                    
5181                    throw new LdapException( TIME_OUT_ERROR );
5182                }
5183
5184                result = bindResponse.getLdapResult().getResultCode();
5185            }
5186
5187            while ( !sc.isComplete()
5188                && ( ( result == ResultCodeEnum.SASL_BIND_IN_PROGRESS ) || ( result == ResultCodeEnum.SUCCESS ) ) )
5189            {
5190                response = sc.evaluateChallenge( bindResponse.getServerSaslCreds() );
5191
5192                if ( result == ResultCodeEnum.SUCCESS )
5193                {
5194                    if ( response != null )
5195                    {
5196                        throw new LdapException( I18n.err( I18n.ERR_04159_PROTOCOL_ERROR ) );
5197                    }
5198                }
5199                else
5200                {
5201                    newId = messageId.incrementAndGet();
5202                    bindRequest.setMessageId( newId );
5203                    bindRequest.setCredentials( response );
5204
5205                    addToFutureMap( newId, bindFuture );
5206
5207                    writeRequest( bindRequest );
5208
5209                    bindResponse = bindFuture.get( connectTimeout, TimeUnit.MILLISECONDS );
5210
5211                    if ( bindResponse == null )
5212                    {
5213                        // We didn't received anything : this is an error
5214                        if ( LOG.isErrorEnabled() )
5215                        {
5216                            LOG.error( I18n.err( I18n.ERR_04112_OP_FAILED_TIMEOUT, "Bind" ) );
5217                        }
5218                        
5219                        throw new LdapException( TIME_OUT_ERROR );
5220                    }
5221
5222                    result = bindResponse.getLdapResult().getResultCode();
5223                }
5224            }
5225
5226            /*
5227             * Install the SASL filter when the SASL auth is complete.
5228             * This adds the security layer if it was negotiated.
5229             */
5230            if ( sc.isComplete() )
5231            {
5232                addSaslFilter( sc );
5233            }
5234
5235            bindFuture.set( bindResponse );
5236
5237            return bindFuture;
5238        }
5239        catch ( LdapException e )
5240        {
5241            throw e;
5242        }
5243        catch ( Exception e )
5244        {
5245            LOG.error( e.getMessage() );
5246            throw new LdapException( e );
5247        }
5248    }
5249
5250
5251    /**
5252     * A reusable code block to be used in various bind methods
5253     * 
5254     * @param request The request to send
5255     * @throws LdapException If the request was ot properly sent
5256     */
5257    private void writeRequest( Request request ) throws LdapException
5258    {
5259        // Send the request to the server
5260        WriteFuture writeFuture = ioSession.write( request );
5261
5262        long localTimeout = sendTimeout;
5263
5264        while ( localTimeout > 0 )
5265        {
5266            // Wait only 100 ms
5267            boolean done = writeFuture.awaitUninterruptibly( 100 );
5268
5269            if ( done )
5270            {
5271                return;
5272            }
5273
5274            // Wait for the message to be sent to the server
5275            if ( !ioSession.isConnected() )
5276            {
5277                // We didn't received anything : this is an error
5278                if ( LOG.isErrorEnabled() )
5279                {
5280                    LOG.error( I18n.err( I18n.ERR_04118_SOMETHING_WRONG_HAPPENED ) );
5281                }
5282
5283                Exception exception = ( Exception ) ioSession.removeAttribute( EXCEPTION_KEY );
5284
5285                if ( exception instanceof LdapException )
5286                {
5287                    throw ( LdapException ) exception;
5288                }
5289                else if ( exception != null )
5290                {
5291                    throw new InvalidConnectionException( exception.getMessage(), exception );
5292                }
5293
5294                throw new InvalidConnectionException( I18n.err( I18n.ERR_04160_SESSION_HAS_BEEN_CLOSED ) );
5295            }
5296
5297            localTimeout -= 100;
5298        }
5299
5300        if ( LOG.isErrorEnabled() )
5301        {
5302            LOG.error( I18n.err( I18n.ERR_04119_TIMEOUT ) );
5303        }
5304        
5305        throw new LdapException( TIME_OUT_ERROR );
5306    }
5307
5308
5309    /**
5310     * method to write the kerberos config in the standard MIT kerberos format
5311     *
5312     * This is required cause the JGSS api is not able to recognize the port value set
5313     * in the system property java.security.krb5.kdc this issue makes it impossible
5314     * to set a kdc running non standard ports (other than 88)
5315     *
5316     * e.g localhost:6088
5317     *
5318     * <pre>
5319     * [libdefaults]
5320     *     default_realm = EXAMPLE.COM
5321     *
5322     * [realms]
5323     *     EXAMPLE.COM = {
5324     *         kdc = localhost:6088
5325     *     }
5326     * </pre>
5327     *
5328     * @param realmName The realm name
5329     * @param kdcHost The Kerberos server host
5330     * @param kdcPort The Kerberos server port
5331     * @return the full path of the config file
5332     * @throws IOException If the config file cannot be created
5333     */
5334    private String createKrb5ConfFile( String realmName, String kdcHost, int kdcPort ) throws IOException
5335    {
5336        StringBuilder sb = new StringBuilder();
5337
5338        sb.append( "[libdefaults]" )
5339            .append( "\n\t" );
5340        sb.append( "default_realm = " )
5341            .append( realmName )
5342            .append( "\n" );
5343
5344        sb.append( "[realms]" )
5345            .append( "\n\t" );
5346
5347        sb.append( realmName )
5348            .append( " = {" )
5349            .append( "\n\t\t" );
5350        sb.append( "kdc = " )
5351            .append( kdcHost )
5352            .append( ":" )
5353            .append( kdcPort )
5354            .append( "\n\t}\n" );
5355
5356        File krb5Conf = Files.createTempFile( "client-api-krb5", ".conf" ).toFile();
5357        krb5Conf.deleteOnExit();
5358
5359        try ( Writer writer = new OutputStreamWriter( Files.newOutputStream( Paths.get( krb5Conf.getPath() ) ), 
5360            Charset.defaultCharset() ) )
5361        {
5362            writer.write( sb.toString() );
5363        }
5364
5365        String krb5ConfPath = krb5Conf.getAbsolutePath();
5366
5367        if ( LOG.isDebugEnabled() )
5368        {
5369            LOG.debug( I18n.msg( I18n.MSG_04135_KRB5_FILE_CREATED, krb5ConfPath ) );
5370        }
5371
5372        return krb5ConfPath;
5373    }
5374
5375
5376    /**
5377     * {@inheritDoc}
5378     */
5379    @Override
5380    public BinaryAttributeDetector getBinaryAttributeDetector()
5381    {
5382        if ( config != null )
5383        {
5384            return config.getBinaryAttributeDetector();
5385        }
5386        else
5387        {
5388            return null;
5389        }
5390    }
5391
5392
5393    /**
5394     * {@inheritDoc}
5395     */
5396    @Override
5397    public void setBinaryAttributeDetector( BinaryAttributeDetector binaryAttributeDetector )
5398    {
5399        if ( config != null )
5400        {
5401            config.setBinaryAttributeDetector( binaryAttributeDetector );
5402        }
5403    }
5404
5405
5406    /**
5407     * {@inheritDoc}
5408     */
5409    @Override
5410    public void setSchemaManager( SchemaManager schemaManager )
5411    {
5412        this.schemaManager = schemaManager;
5413    }
5414
5415
5416    /**
5417     * @return the socketSessionConfig
5418     */
5419    public SocketSessionConfig getSocketSessionConfig()
5420    {
5421        return socketSessionConfig;
5422    }
5423
5424
5425    /**
5426     * @param socketSessionConfig the socketSessionConfig to set
5427     */
5428    public void setSocketSessionConfig( SocketSessionConfig socketSessionConfig )
5429    {
5430        this.socketSessionConfig = socketSessionConfig;
5431    }
5432    
5433    
5434    /**
5435     * {@inheritDoc}
5436     */
5437    @Override
5438    public void event( IoSession session, FilterEvent event ) throws Exception 
5439    {
5440        // Check if it's a SSLevent 
5441        if ( ( event instanceof SslEvent ) && ( ( SslEvent ) event == SslEvent.SECURED ) )
5442        {
5443            handshakeFuture.secured();
5444        }
5445    }
5446
5447
5448    /**
5449     * Gets the {@link SSLSession} associated with the connection.
5450     * 
5451     * @return the {@link SSLSession} associated with the connection or null if the connection is not secured
5452     */
5453    public SSLSession getSslSession()
5454    {
5455        if ( isSecured() )
5456        {
5457            SSLSession sslSession = ( SSLSession ) ioSession.getAttribute( SslFilter.SSL_SECURED );
5458
5459            return sslSession;
5460        }
5461        else
5462        {
5463            return null;
5464        }
5465    }
5466}