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