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