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