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