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}