001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2006-2010 Sun Microsystems, Inc. 025 * Portions Copyright 2010-2015 ForgeRock AS. 026 */ 027package org.opends.server.protocols.ldap; 028 029import java.io.Closeable; 030import java.io.IOException; 031import java.net.InetAddress; 032import java.net.Socket; 033import java.nio.ByteBuffer; 034import java.nio.channels.*; 035import java.security.cert.Certificate; 036import java.util.Collection; 037import java.util.Iterator; 038import java.util.List; 039import java.util.concurrent.ConcurrentHashMap; 040import java.util.concurrent.atomic.AtomicLong; 041import java.util.concurrent.atomic.AtomicReference; 042import java.util.concurrent.locks.Lock; 043import java.util.concurrent.locks.ReentrantLock; 044 045import javax.net.ssl.SSLException; 046 047import org.forgerock.i18n.LocalizableMessage; 048import org.forgerock.i18n.LocalizableMessageBuilder; 049import org.forgerock.i18n.slf4j.LocalizedLogger; 050import org.forgerock.opendj.io.ASN1; 051import org.forgerock.opendj.io.ASN1Writer; 052import org.forgerock.opendj.ldap.ByteString; 053import org.forgerock.opendj.ldap.ByteStringBuilder; 054import org.forgerock.opendj.ldap.ResultCode; 055import org.opends.server.api.ClientConnection; 056import org.opends.server.api.ConnectionHandler; 057import org.opends.server.core.*; 058import org.opends.server.extensions.ConnectionSecurityProvider; 059import org.opends.server.extensions.RedirectingByteChannel; 060import org.opends.server.extensions.TLSByteChannel; 061import org.opends.server.extensions.TLSCapableConnection; 062import org.opends.server.types.*; 063import org.opends.server.util.StaticUtils; 064import org.opends.server.util.TimeThread; 065 066import static org.opends.messages.CoreMessages.*; 067import static org.opends.messages.ProtocolMessages.*; 068import static org.opends.server.core.DirectoryServer.*; 069import static org.opends.server.loggers.AccessLogger.*; 070import static org.opends.server.protocols.ldap.LDAPConstants.*; 071import static org.opends.server.util.ServerConstants.*; 072import static org.opends.server.util.StaticUtils.*; 073 074/** 075 * This class defines an LDAP client connection, which is a type of 076 * client connection that will be accepted by an instance of the LDAP 077 * connection handler and have its requests decoded by an LDAP request 078 * handler. 079 */ 080public final class LDAPClientConnection extends ClientConnection implements 081 TLSCapableConnection 082{ 083 084 /** 085 * A runnable whose task is to close down all IO related channels 086 * associated with a client connection after a small delay. 087 */ 088 private static final class ConnectionFinalizerJob implements Runnable 089 { 090 /** The client connection ASN1 reader. */ 091 private final ASN1ByteChannelReader asn1Reader; 092 093 /** The client connection socket channel. */ 094 private final SocketChannel socketChannel; 095 096 /** Creates a new connection finalizer job. */ 097 private ConnectionFinalizerJob(ASN1ByteChannelReader asn1Reader, 098 SocketChannel socketChannel) 099 { 100 this.asn1Reader = asn1Reader; 101 this.socketChannel = socketChannel; 102 } 103 104 105 106 /** {@inheritDoc} */ 107 @Override 108 public void run() 109 { 110 try 111 { 112 asn1Reader.close(); 113 } 114 catch (Exception e) 115 { 116 // In general, we don't care about any exception that might be 117 // thrown here. 118 logger.traceException(e); 119 } 120 121 try 122 { 123 socketChannel.close(); 124 } 125 catch (Exception e) 126 { 127 // In general, we don't care about any exception that might be 128 // thrown here. 129 logger.traceException(e); 130 } 131 } 132 } 133 134 /** 135 * Channel that writes the contents of the provided buffer to the client, 136 * throwing an exception if the write is unsuccessful for too 137 * long (e.g., if the client is unresponsive or there is a network 138 * problem). If possible, it will attempt to use the selector returned 139 * by the {@code ClientConnection.getWriteSelector} method, but it is 140 * capable of working even if that method returns {@code null}. <BR> 141 * 142 * Note that the original position and limit values will not be 143 * preserved, so if that is important to the caller, then it should 144 * record them before calling this method and restore them after it 145 * returns. 146 */ 147 private class TimeoutWriteByteChannel implements ByteChannel 148 { 149 /** Synchronize concurrent writes to the same connection. */ 150 private final Lock writeLock = new ReentrantLock(); 151 152 @Override 153 public int read(ByteBuffer byteBuffer) throws IOException 154 { 155 int bytesRead = clientChannel.read(byteBuffer); 156 if (bytesRead > 0 && keepStats) 157 { 158 statTracker.updateBytesRead(bytesRead); 159 } 160 return bytesRead; 161 } 162 163 @Override 164 public boolean isOpen() 165 { 166 return clientChannel.isOpen(); 167 } 168 169 @Override 170 public void close() throws IOException 171 { 172 clientChannel.close(); 173 } 174 175 176 177 @Override 178 public int write(ByteBuffer byteBuffer) throws IOException 179 { 180 writeLock.lock(); 181 try 182 { 183 int bytesToWrite = byteBuffer.remaining(); 184 int bytesWritten = clientChannel.write(byteBuffer); 185 if (bytesWritten > 0 && keepStats) 186 { 187 statTracker.updateBytesWritten(bytesWritten); 188 } 189 if (!byteBuffer.hasRemaining()) 190 { 191 return bytesToWrite; 192 } 193 194 long startTime = System.currentTimeMillis(); 195 long waitTime = getMaxBlockedWriteTimeLimit(); 196 if (waitTime <= 0) 197 { 198 // We won't support an infinite time limit, so fall back to using 199 // five minutes, which is a very long timeout given that we're 200 // blocking a worker thread. 201 waitTime = 300000L; 202 } 203 long stopTime = startTime + waitTime; 204 205 Selector selector = getWriteSelector(); 206 if (selector == null) 207 { 208 // The client connection does not provide a selector, so we'll 209 // fall back to a more inefficient way that will work without a 210 // selector. 211 while (byteBuffer.hasRemaining() 212 && System.currentTimeMillis() < stopTime) 213 { 214 bytesWritten = clientChannel.write(byteBuffer); 215 if (bytesWritten < 0) 216 { 217 // The client connection has been closed. 218 throw new ClosedChannelException(); 219 } 220 if (bytesWritten > 0 && keepStats) 221 { 222 statTracker.updateBytesWritten(bytesWritten); 223 } 224 } 225 226 if (byteBuffer.hasRemaining()) 227 { 228 // If we've gotten here, then the write timed out. 229 throw new ClosedChannelException(); 230 } 231 232 return bytesToWrite; 233 } 234 235 // Register with the selector for handling write operations. 236 SelectionKey key = clientChannel.register(selector, 237 SelectionKey.OP_WRITE); 238 try 239 { 240 selector.select(waitTime); 241 while (byteBuffer.hasRemaining()) 242 { 243 long currentTime = System.currentTimeMillis(); 244 if (currentTime >= stopTime) 245 { 246 // We've been blocked for too long. 247 throw new ClosedChannelException(); 248 } 249 else 250 { 251 waitTime = stopTime - currentTime; 252 } 253 254 Iterator<SelectionKey> iterator = selector.selectedKeys() 255 .iterator(); 256 while (iterator.hasNext()) 257 { 258 SelectionKey k = iterator.next(); 259 if (k.isWritable()) 260 { 261 bytesWritten = clientChannel.write(byteBuffer); 262 if (bytesWritten < 0) 263 { 264 // The client connection has been closed. 265 throw new ClosedChannelException(); 266 } 267 if (bytesWritten > 0 && keepStats) 268 { 269 statTracker.updateBytesWritten(bytesWritten); 270 } 271 272 iterator.remove(); 273 } 274 } 275 276 if (byteBuffer.hasRemaining()) 277 { 278 selector.select(waitTime); 279 } 280 } 281 282 return bytesToWrite; 283 } 284 finally 285 { 286 if (key.isValid()) 287 { 288 key.cancel(); 289 selector.selectNow(); 290 } 291 } 292 } 293 finally 294 { 295 writeLock.unlock(); 296 } 297 } 298 } 299 300 301 /** The tracer object for the debug logger. */ 302 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 303 304 /** 305 * Thread local ASN1Writer and buffer. 306 */ 307 private static final class ASN1WriterHolder implements Closeable 308 { 309 private final ASN1Writer writer; 310 private final ByteStringBuilder buffer; 311 private final int maxBufferSize; 312 313 private ASN1WriterHolder() 314 { 315 this.buffer = new ByteStringBuilder(); 316 this.maxBufferSize = getMaxInternalBufferSize(); 317 this.writer = ASN1.getWriter(buffer, maxBufferSize); 318 } 319 320 /** {@inheritDoc} */ 321 @Override 322 public void close() throws IOException 323 { 324 StaticUtils.close(writer); 325 buffer.clearAndTruncate(maxBufferSize, maxBufferSize); 326 } 327 } 328 329 /** 330 * Cached ASN1 writer: a thread can only write to one connection at a time. 331 */ 332 private static final ThreadLocal<ASN1WriterHolder> ASN1_WRITER_CACHE = 333 new ThreadLocal<ASN1WriterHolder>() 334 { 335 /** {@inheritDoc} */ 336 @Override 337 protected ASN1WriterHolder initialValue() 338 { 339 return new ASN1WriterHolder(); 340 } 341 }; 342 343 private ASN1WriterHolder getASN1Writer() 344 { 345 ASN1WriterHolder holder = ASN1_WRITER_CACHE.get(); 346 if (holder.maxBufferSize != getMaxInternalBufferSize()) 347 { 348 // Setting has changed, so recreate the holder. 349 holder = new ASN1WriterHolder(); 350 ASN1_WRITER_CACHE.set(holder); 351 } 352 return holder; 353 } 354 355 /** The time that the last operation was completed. */ 356 private final AtomicLong lastCompletionTime; 357 358 /** The next operation ID that should be used for this connection. */ 359 private final AtomicLong nextOperationID; 360 361 /** The selector that may be used for write operations. */ 362 private final AtomicReference<Selector> writeSelector; 363 364 /** 365 * Indicates whether the Directory Server believes this connection to be valid 366 * and available for communication. 367 */ 368 private volatile boolean connectionValid; 369 370 /** 371 * Indicates whether this connection is about to be closed. This will be used 372 * to prevent accepting new requests while a disconnect is in progress. 373 */ 374 private boolean disconnectRequested; 375 376 /** 377 * Indicates whether the connection should keep statistics regarding the 378 * operations that it is performing. 379 */ 380 private final boolean keepStats; 381 382 /** The set of all operations currently in progress on this connection. */ 383 private final ConcurrentHashMap<Integer, Operation> operationsInProgress; 384 385 /** 386 * The number of operations performed on this connection. Used to compare with 387 * the resource limits of the network group. 388 */ 389 private final AtomicLong operationsPerformed; 390 391 /** The port on the client from which this connection originated. */ 392 private final int clientPort; 393 394 /** 395 * The LDAP version that the client is using to communicate with the server. 396 */ 397 private int ldapVersion; 398 399 /** The port on the server to which this client has connected. */ 400 private final int serverPort; 401 402 /** The reference to the connection handler that accepted this connection. */ 403 private final LDAPConnectionHandler connectionHandler; 404 405 /** The statistics tracker associated with this client connection. */ 406 private final LDAPStatistics statTracker; 407 private boolean useNanoTime; 408 409 410 /** The connection ID assigned to this connection. */ 411 private final long connectionID; 412 413 /** 414 * The lock used to provide threadsafe access to the set of operations in 415 * progress. 416 */ 417 private final Object opsInProgressLock; 418 419 /** The socket channel with which this client connection is associated. */ 420 private final SocketChannel clientChannel; 421 422 /** The byte channel used for blocking writes with time out. */ 423 private final ByteChannel timeoutClientChannel; 424 425 /** The string representation of the address of the client. */ 426 private final String clientAddress; 427 428 /** 429 * The name of the protocol that the client is using to communicate with the 430 * server. 431 */ 432 private final String protocol; 433 434 /** 435 * The string representation of the address of the server to which the client 436 * has connected. 437 */ 438 private final String serverAddress; 439 440 441 442 private ASN1ByteChannelReader asn1Reader; 443 private final int bufferSize; 444 private final RedirectingByteChannel saslChannel; 445 private final RedirectingByteChannel tlsChannel; 446 private volatile ConnectionSecurityProvider saslActiveProvider; 447 private volatile ConnectionSecurityProvider tlsActiveProvider; 448 private volatile ConnectionSecurityProvider saslPendingProvider; 449 private volatile ConnectionSecurityProvider tlsPendingProvider; 450 451 452 /** 453 * Creates a new LDAP client connection with the provided information. 454 * 455 * @param connectionHandler 456 * The connection handler that accepted this connection. 457 * @param clientChannel 458 * The socket channel that may be used to communicate with 459 * the client. 460 * @param protocol String representing the protocol (LDAP or LDAP+SSL). 461 * @throws DirectoryException If SSL initialisation fails. 462 */ 463 LDAPClientConnection(LDAPConnectionHandler connectionHandler, 464 SocketChannel clientChannel, String protocol) throws DirectoryException 465 { 466 this.connectionHandler = connectionHandler; 467 this.clientChannel = clientChannel; 468 timeoutClientChannel = new TimeoutWriteByteChannel(); 469 opsInProgressLock = new Object(); 470 ldapVersion = 3; 471 lastCompletionTime = new AtomicLong(TimeThread.getTime()); 472 nextOperationID = new AtomicLong(0); 473 connectionValid = true; 474 disconnectRequested = false; 475 operationsInProgress = new ConcurrentHashMap<>(); 476 operationsPerformed = new AtomicLong(0); 477 keepStats = connectionHandler.keepStats(); 478 this.protocol = protocol; 479 writeSelector = new AtomicReference<>(); 480 481 final Socket socket = clientChannel.socket(); 482 clientAddress = socket.getInetAddress().getHostAddress(); 483 clientPort = socket.getPort(); 484 serverAddress = socket.getLocalAddress().getHostAddress(); 485 serverPort = socket.getLocalPort(); 486 487 statTracker = this.connectionHandler.getStatTracker(); 488 489 if (keepStats) 490 { 491 statTracker.updateConnect(); 492 this.useNanoTime=DirectoryServer.getUseNanoTime(); 493 } 494 495 bufferSize = connectionHandler.getBufferSize(); 496 497 tlsChannel = 498 RedirectingByteChannel.getRedirectingByteChannel( 499 timeoutClientChannel); 500 saslChannel = 501 RedirectingByteChannel.getRedirectingByteChannel(tlsChannel); 502 this.asn1Reader = new ASN1ByteChannelReader(saslChannel, bufferSize, connectionHandler.getMaxRequestSize()); 503 504 if (connectionHandler.useSSL()) 505 { 506 enableSSL(connectionHandler.getTLSByteChannel(timeoutClientChannel)); 507 } 508 509 connectionID = DirectoryServer.newConnectionAccepted(this); 510 } 511 512 /** 513 * Retrieves the connection ID assigned to this connection. 514 * 515 * @return The connection ID assigned to this connection. 516 */ 517 @Override 518 public long getConnectionID() 519 { 520 return connectionID; 521 } 522 523 524 525 /** 526 * Retrieves the connection handler that accepted this client 527 * connection. 528 * 529 * @return The connection handler that accepted this client 530 * connection. 531 */ 532 @Override 533 public ConnectionHandler<?> getConnectionHandler() 534 { 535 return connectionHandler; 536 } 537 538 539 540 /** 541 * Retrieves the socket channel that can be used to communicate with 542 * the client. 543 * 544 * @return The socket channel that can be used to communicate with the 545 * client. 546 */ 547 @Override 548 public SocketChannel getSocketChannel() 549 { 550 return clientChannel; 551 } 552 553 554 555 /** 556 * Retrieves the protocol that the client is using to communicate with 557 * the Directory Server. 558 * 559 * @return The protocol that the client is using to communicate with 560 * the Directory Server. 561 */ 562 @Override 563 public String getProtocol() 564 { 565 return protocol; 566 } 567 568 569 570 /** 571 * Retrieves a string representation of the address of the client. 572 * 573 * @return A string representation of the address of the client. 574 */ 575 @Override 576 public String getClientAddress() 577 { 578 return clientAddress; 579 } 580 581 582 583 /** 584 * Retrieves the port number for this connection on the client system. 585 * 586 * @return The port number for this connection on the client system. 587 */ 588 @Override 589 public int getClientPort() 590 { 591 return clientPort; 592 } 593 594 595 596 /** 597 * Retrieves a string representation of the address on the server to 598 * which the client connected. 599 * 600 * @return A string representation of the address on the server to 601 * which the client connected. 602 */ 603 @Override 604 public String getServerAddress() 605 { 606 return serverAddress; 607 } 608 609 610 611 /** 612 * Retrieves the port number for this connection on the server system. 613 * 614 * @return The port number for this connection on the server system. 615 */ 616 @Override 617 public int getServerPort() 618 { 619 return serverPort; 620 } 621 622 623 624 /** 625 * Retrieves the <CODE>java.net.InetAddress</CODE> associated with the 626 * remote client system. 627 * 628 * @return The <CODE>java.net.InetAddress</CODE> associated with the 629 * remote client system. It may be <CODE>null</CODE> if the 630 * client is not connected over an IP-based connection. 631 */ 632 @Override 633 public InetAddress getRemoteAddress() 634 { 635 return clientChannel.socket().getInetAddress(); 636 } 637 638 639 640 /** 641 * Retrieves the <CODE>java.net.InetAddress</CODE> for the Directory 642 * Server system to which the client has established the connection. 643 * 644 * @return The <CODE>java.net.InetAddress</CODE> for the Directory 645 * Server system to which the client has established the 646 * connection. It may be <CODE>null</CODE> if the client is 647 * not connected over an IP-based connection. 648 */ 649 @Override 650 public InetAddress getLocalAddress() 651 { 652 return clientChannel.socket().getLocalAddress(); 653 } 654 655 /** {@inheritDoc} */ 656 @Override 657 public boolean isConnectionValid() 658 { 659 return this.connectionValid; 660 } 661 662 /** 663 * Indicates whether this client connection is currently using a 664 * secure mechanism to communicate with the server. Note that this may 665 * change over time based on operations performed by the client or 666 * server (e.g., it may go from <CODE>false</CODE> to 667 * <CODE>true</CODE> if the client uses the StartTLS extended 668 * operation). 669 * 670 * @return <CODE>true</CODE> if the client connection is currently 671 * using a secure mechanism to communicate with the server, or 672 * <CODE>false</CODE> if not. 673 */ 674 @Override 675 public boolean isSecure() 676 { 677 boolean secure = false; 678 if (tlsActiveProvider != null) 679 { 680 secure = tlsActiveProvider.isSecure(); 681 } 682 if (!secure && saslActiveProvider != null) 683 { 684 secure = saslActiveProvider.isSecure(); 685 } 686 return secure; 687 } 688 689 690 691 /** 692 * Sends a response to the client based on the information in the 693 * provided operation. 694 * 695 * @param operation 696 * The operation for which to send the response. 697 */ 698 @Override 699 public void sendResponse(Operation operation) 700 { 701 // Since this is the final response for this operation, we can go 702 // ahead and remove it from the "operations in progress" list. It 703 // can't be canceled after this point, and this will avoid potential 704 // race conditions in which the client immediately sends another 705 // request with the same message ID as was used for this operation. 706 707 if (keepStats) { 708 long time; 709 if (useNanoTime) { 710 time = operation.getProcessingNanoTime(); 711 } else { 712 time = operation.getProcessingTime(); 713 } 714 this.statTracker.updateOperationMonitoringData( 715 operation.getOperationType(), 716 time); 717 } 718 719 // Avoid sending the response if one has already been sent. This may happen 720 // if operation processing encounters a run-time exception after sending the 721 // response: the worker thread exception handling code will attempt to send 722 // an error result to the client indicating that a problem occurred. 723 if (removeOperationInProgress(operation.getMessageID())) 724 { 725 LDAPMessage message = operationToResponseLDAPMessage(operation); 726 if (message != null) 727 { 728 sendLDAPMessage(message); 729 } 730 } 731 } 732 733 734 735 /** 736 * Retrieves an LDAPMessage containing a response generated from the 737 * provided operation. 738 * 739 * @param operation 740 * The operation to use to generate the response LDAPMessage. 741 * @return An LDAPMessage containing a response generated from the 742 * provided operation. 743 */ 744 private LDAPMessage operationToResponseLDAPMessage(Operation operation) 745 { 746 ResultCode resultCode = operation.getResultCode(); 747 if (resultCode == null) 748 { 749 // This must mean that the operation has either not yet completed 750 // or that it completed without a result for some reason. In any 751 // case, log a message and set the response to "operations error". 752 logger.error(ERR_LDAP_CLIENT_SEND_RESPONSE_NO_RESULT_CODE, operation.getOperationType(), 753 operation.getConnectionID(), operation.getOperationID()); 754 resultCode = DirectoryServer.getServerErrorResultCode(); 755 } 756 757 LocalizableMessageBuilder errorMessage = operation.getErrorMessage(); 758 DN matchedDN = operation.getMatchedDN(); 759 760 // Referrals are not allowed for LDAPv2 clients. 761 List<String> referralURLs; 762 if (ldapVersion == 2) 763 { 764 referralURLs = null; 765 766 if (resultCode == ResultCode.REFERRAL) 767 { 768 resultCode = ResultCode.CONSTRAINT_VIOLATION; 769 errorMessage.append(ERR_LDAPV2_REFERRAL_RESULT_CHANGED.get()); 770 } 771 772 List<String> opReferrals = operation.getReferralURLs(); 773 if (opReferrals != null && !opReferrals.isEmpty()) 774 { 775 StringBuilder referralsStr = new StringBuilder(); 776 Iterator<String> iterator = opReferrals.iterator(); 777 referralsStr.append(iterator.next()); 778 779 while (iterator.hasNext()) 780 { 781 referralsStr.append(", "); 782 referralsStr.append(iterator.next()); 783 } 784 785 errorMessage.append(ERR_LDAPV2_REFERRALS_OMITTED.get(referralsStr)); 786 } 787 } 788 else 789 { 790 referralURLs = operation.getReferralURLs(); 791 } 792 793 ProtocolOp protocolOp; 794 switch (operation.getOperationType()) 795 { 796 case ADD: 797 protocolOp = 798 new AddResponseProtocolOp(resultCode.intValue(), 799 errorMessage.toMessage(), matchedDN, referralURLs); 800 break; 801 case BIND: 802 ByteString serverSASLCredentials = 803 ((BindOperationBasis) operation).getServerSASLCredentials(); 804 protocolOp = 805 new BindResponseProtocolOp(resultCode.intValue(), 806 errorMessage.toMessage(), matchedDN, referralURLs, 807 serverSASLCredentials); 808 break; 809 case COMPARE: 810 protocolOp = 811 new CompareResponseProtocolOp(resultCode.intValue(), 812 errorMessage.toMessage(), matchedDN, referralURLs); 813 break; 814 case DELETE: 815 protocolOp = 816 new DeleteResponseProtocolOp(resultCode.intValue(), 817 errorMessage.toMessage(), matchedDN, referralURLs); 818 break; 819 case EXTENDED: 820 // If this an LDAPv2 client, then we can't send this. 821 if (ldapVersion == 2) 822 { 823 logger.error(ERR_LDAPV2_SKIPPING_EXTENDED_RESPONSE, 824 getConnectionID(), operation.getOperationID(), operation); 825 return null; 826 } 827 828 ExtendedOperationBasis extOp = (ExtendedOperationBasis) operation; 829 protocolOp = 830 new ExtendedResponseProtocolOp(resultCode.intValue(), 831 errorMessage.toMessage(), matchedDN, referralURLs, extOp 832 .getResponseOID(), extOp.getResponseValue()); 833 break; 834 case MODIFY: 835 protocolOp = 836 new ModifyResponseProtocolOp(resultCode.intValue(), 837 errorMessage.toMessage(), matchedDN, referralURLs); 838 break; 839 case MODIFY_DN: 840 protocolOp = 841 new ModifyDNResponseProtocolOp(resultCode.intValue(), 842 errorMessage.toMessage(), matchedDN, referralURLs); 843 break; 844 case SEARCH: 845 protocolOp = 846 new SearchResultDoneProtocolOp(resultCode.intValue(), 847 errorMessage.toMessage(), matchedDN, referralURLs); 848 break; 849 default: 850 // This must be a type of operation that doesn't have a response. 851 // This shouldn't happen, so log a message and return. 852 logger.error(ERR_LDAP_CLIENT_SEND_RESPONSE_INVALID_OP, operation.getOperationType(), getConnectionID(), 853 operation.getOperationID(), operation); 854 return null; 855 } 856 857 // Controls are not allowed for LDAPv2 clients. 858 List<Control> controls; 859 if (ldapVersion == 2) 860 { 861 controls = null; 862 } 863 else 864 { 865 controls = operation.getResponseControls(); 866 } 867 868 return new LDAPMessage(operation.getMessageID(), protocolOp, 869 controls); 870 } 871 872 873 874 /** 875 * Sends the provided search result entry to the client. 876 * 877 * @param searchOperation 878 * The search operation with which the entry is associated. 879 * @param searchEntry 880 * The search result entry to be sent to the client. 881 */ 882 @Override 883 public void sendSearchEntry(SearchOperation searchOperation, 884 SearchResultEntry searchEntry) 885 { 886 SearchResultEntryProtocolOp protocolOp = 887 new SearchResultEntryProtocolOp(searchEntry, ldapVersion); 888 889 sendLDAPMessage(new LDAPMessage(searchOperation.getMessageID(), 890 protocolOp, searchEntry.getControls())); 891 } 892 893 894 895 /** 896 * Sends the provided search result reference to the client. 897 * 898 * @param searchOperation 899 * The search operation with which the reference is 900 * associated. 901 * @param searchReference 902 * The search result reference to be sent to the client. 903 * @return <CODE>true</CODE> if the client is able to accept 904 * referrals, or <CODE>false</CODE> if the client cannot 905 * handle referrals and no more attempts should be made to 906 * send them for the associated search operation. 907 */ 908 @Override 909 public boolean sendSearchReference(SearchOperation searchOperation, 910 SearchResultReference searchReference) 911 { 912 // Make sure this is not an LDAPv2 client. If it is, then they can't 913 // see referrals so we'll not send anything. Also, throw an 914 // exception so that the core server will know not to try sending 915 // any more referrals to this client for the rest of the operation. 916 if (ldapVersion == 2) 917 { 918 logger.error(ERR_LDAPV2_SKIPPING_SEARCH_REFERENCE, getConnectionID(), 919 searchOperation.getOperationID(), searchReference); 920 return false; 921 } 922 923 SearchResultReferenceProtocolOp protocolOp = 924 new SearchResultReferenceProtocolOp(searchReference); 925 926 sendLDAPMessage(new LDAPMessage(searchOperation.getMessageID(), 927 protocolOp, searchReference.getControls())); 928 return true; 929 } 930 931 932 933 /** 934 * Sends the provided intermediate response message to the client. 935 * 936 * @param intermediateResponse 937 * The intermediate response message to be sent. 938 * @return <CODE>true</CODE> if processing on the associated operation 939 * should continue, or <CODE>false</CODE> if not. 940 */ 941 @Override 942 protected boolean sendIntermediateResponseMessage( 943 IntermediateResponse intermediateResponse) 944 { 945 IntermediateResponseProtocolOp protocolOp = 946 new IntermediateResponseProtocolOp(intermediateResponse 947 .getOID(), intermediateResponse.getValue()); 948 949 Operation operation = intermediateResponse.getOperation(); 950 951 LDAPMessage message = 952 new LDAPMessage(operation.getMessageID(), protocolOp, 953 intermediateResponse.getControls()); 954 sendLDAPMessage(message); 955 956 // The only reason we shouldn't continue processing is if the 957 // connection is closed. 958 return connectionValid; 959 } 960 961 962 963 /** 964 * Sends the provided LDAP message to the client. 965 * 966 * @param message 967 * The LDAP message to send to the client. 968 */ 969 private void sendLDAPMessage(LDAPMessage message) 970 { 971 // Use a thread local writer. 972 final ASN1WriterHolder holder = getASN1Writer(); 973 try 974 { 975 message.write(holder.writer); 976 holder.buffer.copyTo(saslChannel); 977 978 if (logger.isTraceEnabled()) 979 { 980 logger.trace("LDAPMessage=%s", message); 981 } 982 983 if (keepStats) 984 { 985 statTracker.updateMessageWritten(message); 986 } 987 } 988 catch (ClosedChannelException e) 989 { 990 logger.traceException(e); 991 disconnect(DisconnectReason.IO_ERROR, false, 992 ERR_IO_ERROR_ON_CLIENT_CONNECTION.get(getExceptionMessage(e))); 993 return; 994 } 995 catch (Exception e) 996 { 997 logger.traceException(e); 998 disconnect(DisconnectReason.SERVER_ERROR, false, 999 ERR_UNEXPECTED_EXCEPTION_ON_CLIENT_CONNECTION.get(getExceptionMessage(e))); 1000 return; 1001 } 1002 finally 1003 { 1004 // Clear and reset all of the internal buffers ready for the next usage. 1005 // The ASN1Writer is based on a ByteStringBuilder so closing will cause 1006 // the internal buffers to be resized if needed. 1007 close(holder); 1008 } 1009 } 1010 1011 1012 1013 /** 1014 * Closes the connection to the client, optionally sending it a 1015 * message indicating the reason for the closure. Note that the 1016 * ability to send a notice of disconnection may not be available for 1017 * all protocols or under all circumstances. 1018 * 1019 * @param disconnectReason 1020 * The disconnect reason that provides the generic cause for 1021 * the disconnect. 1022 * @param sendNotification 1023 * Indicates whether to try to provide notification to the 1024 * client that the connection will be closed. 1025 * @param message 1026 * The message to include in the disconnect notification 1027 * response. It may be <CODE>null</CODE> if no message is to 1028 * be sent. 1029 */ 1030 @Override 1031 public void disconnect(DisconnectReason disconnectReason, 1032 boolean sendNotification, LocalizableMessage message) 1033 { 1034 // Set a flag indicating that the connection is being terminated so 1035 // that no new requests will be accepted. Also cancel all operations 1036 // in progress. 1037 synchronized (opsInProgressLock) 1038 { 1039 // If we are already in the middle of a disconnect, then don't 1040 // do anything. 1041 if (disconnectRequested) 1042 { 1043 return; 1044 } 1045 1046 disconnectRequested = true; 1047 } 1048 1049 if (keepStats) 1050 { 1051 statTracker.updateDisconnect(); 1052 } 1053 1054 if (connectionID >= 0) 1055 { 1056 DirectoryServer.connectionClosed(this); 1057 } 1058 1059 // Indicate that this connection is no longer valid. 1060 connectionValid = false; 1061 1062 if (message != null) 1063 { 1064 LocalizableMessageBuilder msgBuilder = new LocalizableMessageBuilder(); 1065 msgBuilder.append(disconnectReason.getClosureMessage()); 1066 msgBuilder.append(": "); 1067 msgBuilder.append(message); 1068 cancelAllOperations(new CancelRequest(true, msgBuilder 1069 .toMessage())); 1070 } 1071 else 1072 { 1073 cancelAllOperations(new CancelRequest(true, disconnectReason 1074 .getClosureMessage())); 1075 } 1076 finalizeConnectionInternal(); 1077 1078 // If there is a write selector for this connection, then close it. 1079 Selector selector = writeSelector.get(); 1080 close(selector); 1081 1082 // See if we should send a notification to the client. If so, then 1083 // construct and send a notice of disconnection unsolicited 1084 // response. Note that we cannot send this notification to an LDAPv2 client. 1085 if (sendNotification && ldapVersion != 2) 1086 { 1087 try 1088 { 1089 int resultCode; 1090 switch (disconnectReason) 1091 { 1092 case PROTOCOL_ERROR: 1093 resultCode = LDAPResultCode.PROTOCOL_ERROR; 1094 break; 1095 case SERVER_SHUTDOWN: 1096 resultCode = LDAPResultCode.UNAVAILABLE; 1097 break; 1098 case SERVER_ERROR: 1099 resultCode = DirectoryServer.getServerErrorResultCode().intValue(); 1100 break; 1101 case ADMIN_LIMIT_EXCEEDED: 1102 case IDLE_TIME_LIMIT_EXCEEDED: 1103 case MAX_REQUEST_SIZE_EXCEEDED: 1104 case IO_TIMEOUT: 1105 resultCode = LDAPResultCode.ADMIN_LIMIT_EXCEEDED; 1106 break; 1107 case CONNECTION_REJECTED: 1108 resultCode = LDAPResultCode.CONSTRAINT_VIOLATION; 1109 break; 1110 case INVALID_CREDENTIALS: 1111 resultCode = LDAPResultCode.INVALID_CREDENTIALS; 1112 break; 1113 default: 1114 resultCode = LDAPResultCode.OTHER; 1115 break; 1116 } 1117 1118 LocalizableMessage errMsg; 1119 if (message == null) 1120 { 1121 errMsg = 1122 INFO_LDAP_CLIENT_GENERIC_NOTICE_OF_DISCONNECTION.get(); 1123 } 1124 else 1125 { 1126 errMsg = message; 1127 } 1128 1129 ExtendedResponseProtocolOp notificationOp = 1130 new ExtendedResponseProtocolOp(resultCode, errMsg, null, 1131 null, OID_NOTICE_OF_DISCONNECTION, null); 1132 1133 sendLDAPMessage(new LDAPMessage(0, notificationOp, null)); 1134 } 1135 catch (Exception e) 1136 { 1137 // NYI -- Log a message indicating that we couldn't send the 1138 // notice of disconnection. 1139 logger.traceException(e); 1140 } 1141 } 1142 1143 // Enqueue the connection channels for closing by the finalizer. 1144 Runnable r = new ConnectionFinalizerJob(asn1Reader, clientChannel); 1145 connectionHandler.registerConnectionFinalizer(r); 1146 1147 // NYI -- Deregister the client connection from any server components that 1148 // might know about it. 1149 1150 // Log a disconnect message. 1151 logDisconnect(this, disconnectReason, message); 1152 1153 try 1154 { 1155 PluginConfigManager pluginManager = 1156 DirectoryServer.getPluginConfigManager(); 1157 pluginManager.invokePostDisconnectPlugins(this, disconnectReason, 1158 message); 1159 } 1160 catch (Exception e) 1161 { 1162 logger.traceException(e); 1163 } 1164 } 1165 1166 1167 1168 /** 1169 * Retrieves the set of operations in progress for this client 1170 * connection. This list must not be altered by any caller. 1171 * 1172 * @return The set of operations in progress for this client 1173 * connection. 1174 */ 1175 @Override 1176 public Collection<Operation> getOperationsInProgress() 1177 { 1178 return operationsInProgress.values(); 1179 } 1180 1181 1182 1183 /** 1184 * Retrieves the operation in progress with the specified message ID. 1185 * 1186 * @param messageID 1187 * The message ID for the operation to retrieve. 1188 * @return The operation in progress with the specified message ID, or 1189 * <CODE>null</CODE> if no such operation could be found. 1190 */ 1191 @Override 1192 public Operation getOperationInProgress(int messageID) 1193 { 1194 return operationsInProgress.get(messageID); 1195 } 1196 1197 1198 1199 /** 1200 * Adds the provided operation to the set of operations in progress 1201 * for this client connection. 1202 * 1203 * @param operation 1204 * The operation to add to the set of operations in progress 1205 * for this client connection. 1206 * @throws DirectoryException 1207 * If the operation is not added for some reason (e.g., the 1208 * client already has reached the maximum allowed concurrent 1209 * requests). 1210 */ 1211 private void addOperationInProgress(Operation operation) 1212 throws DirectoryException 1213 { 1214 int messageID = operation.getMessageID(); 1215 1216 // We need to grab a lock to ensure that no one else can add 1217 // operations to the queue while we are performing some preliminary 1218 // checks. 1219 try 1220 { 1221 synchronized (opsInProgressLock) 1222 { 1223 // If we're already in the process of disconnecting the client, 1224 // then reject the operation. 1225 if (disconnectRequested) 1226 { 1227 LocalizableMessage message = WARN_CLIENT_DISCONNECT_IN_PROGRESS.get(); 1228 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 1229 message); 1230 } 1231 1232 // Add the operation to the list of operations in progress for 1233 // this connection. 1234 Operation op = operationsInProgress.putIfAbsent(messageID, operation); 1235 1236 // See if there is already an operation in progress with the 1237 // same message ID. If so, then we can't allow it. 1238 if (op != null) 1239 { 1240 LocalizableMessage message = 1241 WARN_LDAP_CLIENT_DUPLICATE_MESSAGE_ID.get(messageID); 1242 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, 1243 message); 1244 } 1245 } 1246 1247 // Try to add the operation to the work queue, 1248 // or run it synchronously (typically for the administration 1249 // connector) 1250 connectionHandler.getQueueingStrategy().enqueueRequest( 1251 operation); 1252 } 1253 catch (DirectoryException de) 1254 { 1255 logger.traceException(de); 1256 1257 operationsInProgress.remove(messageID); 1258 lastCompletionTime.set(TimeThread.getTime()); 1259 1260 throw de; 1261 } 1262 catch (Exception e) 1263 { 1264 logger.traceException(e); 1265 1266 LocalizableMessage message = 1267 WARN_LDAP_CLIENT_CANNOT_ENQUEUE.get(getExceptionMessage(e)); 1268 throw new DirectoryException(DirectoryServer 1269 .getServerErrorResultCode(), message, e); 1270 } 1271 } 1272 1273 1274 1275 /** 1276 * Removes the provided operation from the set of operations in 1277 * progress for this client connection. Note that this does not make 1278 * any attempt to cancel any processing that may already be in 1279 * progress for the operation. 1280 * 1281 * @param messageID 1282 * The message ID of the operation to remove from the set of 1283 * operations in progress. 1284 * @return <CODE>true</CODE> if the operation was found and removed 1285 * from the set of operations in progress, or 1286 * <CODE>false</CODE> if not. 1287 */ 1288 @Override 1289 public boolean removeOperationInProgress(int messageID) 1290 { 1291 Operation operation = operationsInProgress.remove(messageID); 1292 if (operation == null) 1293 { 1294 return false; 1295 } 1296 1297 if (operation.getOperationType() == OperationType.ABANDON 1298 && keepStats 1299 && operation.getResultCode() == ResultCode.CANCELLED) 1300 { 1301 statTracker.updateAbandonedOperation(); 1302 } 1303 1304 lastCompletionTime.set(TimeThread.getTime()); 1305 return true; 1306 } 1307 1308 1309 1310 /** 1311 * Attempts to cancel the specified operation. 1312 * 1313 * @param messageID 1314 * The message ID of the operation to cancel. 1315 * @param cancelRequest 1316 * An object providing additional information about how the 1317 * cancel should be processed. 1318 * @return A cancel result that either indicates that the cancel was 1319 * successful or provides a reason that it was not. 1320 */ 1321 @Override 1322 public CancelResult cancelOperation(int messageID, 1323 CancelRequest cancelRequest) 1324 { 1325 Operation op = operationsInProgress.get(messageID); 1326 if (op == null) 1327 { 1328 // See if the operation is in the list of persistent searches. 1329 for (PersistentSearch ps : getPersistentSearches()) 1330 { 1331 if (ps.getMessageID() == messageID) 1332 { 1333 // We only need to find the first persistent search 1334 // associated with the provided message ID. The persistent 1335 // search will ensure that all other related persistent 1336 // searches are cancelled. 1337 return ps.cancel(); 1338 } 1339 } 1340 1341 return new CancelResult(ResultCode.NO_SUCH_OPERATION, null); 1342 } 1343 else 1344 { 1345 return op.cancel(cancelRequest); 1346 } 1347 } 1348 1349 1350 1351 /** 1352 * Attempts to cancel all operations in progress on this connection. 1353 * 1354 * @param cancelRequest 1355 * An object providing additional information about how the 1356 * cancel should be processed. 1357 */ 1358 @Override 1359 public void cancelAllOperations(CancelRequest cancelRequest) 1360 { 1361 // Make sure that no one can add any new operations. 1362 synchronized (opsInProgressLock) 1363 { 1364 try 1365 { 1366 for (Operation o : operationsInProgress.values()) 1367 { 1368 try 1369 { 1370 o.abort(cancelRequest); 1371 1372 // TODO: Assume its cancelled? 1373 if (keepStats) 1374 { 1375 statTracker.updateAbandonedOperation(); 1376 } 1377 } 1378 catch (Exception e) 1379 { 1380 logger.traceException(e); 1381 } 1382 } 1383 1384 if (!operationsInProgress.isEmpty() 1385 || !getPersistentSearches().isEmpty()) 1386 { 1387 lastCompletionTime.set(TimeThread.getTime()); 1388 } 1389 1390 operationsInProgress.clear(); 1391 1392 for (PersistentSearch persistentSearch : getPersistentSearches()) 1393 { 1394 persistentSearch.cancel(); 1395 } 1396 } 1397 catch (Exception e) 1398 { 1399 logger.traceException(e); 1400 } 1401 } 1402 } 1403 1404 1405 1406 /** 1407 * Attempts to cancel all operations in progress on this connection 1408 * except the operation with the specified message ID. 1409 * 1410 * @param cancelRequest 1411 * An object providing additional information about how the 1412 * cancel should be processed. 1413 * @param messageID 1414 * The message ID of the operation that should not be 1415 * canceled. 1416 */ 1417 @Override 1418 public void cancelAllOperationsExcept(CancelRequest cancelRequest, 1419 int messageID) 1420 { 1421 // Make sure that no one can add any new operations. 1422 synchronized (opsInProgressLock) 1423 { 1424 try 1425 { 1426 for (int msgID : operationsInProgress.keySet()) 1427 { 1428 if (msgID == messageID) 1429 { 1430 continue; 1431 } 1432 1433 Operation o = operationsInProgress.get(msgID); 1434 if (o != null) 1435 { 1436 try 1437 { 1438 o.abort(cancelRequest); 1439 1440 // TODO: Assume its cancelled? 1441 if (keepStats) 1442 { 1443 statTracker.updateAbandonedOperation(); 1444 } 1445 } 1446 catch (Exception e) 1447 { 1448 logger.traceException(e); 1449 } 1450 } 1451 1452 operationsInProgress.remove(msgID); 1453 lastCompletionTime.set(TimeThread.getTime()); 1454 } 1455 1456 for (PersistentSearch persistentSearch : getPersistentSearches()) 1457 { 1458 if (persistentSearch.getMessageID() == messageID) 1459 { 1460 continue; 1461 } 1462 1463 persistentSearch.cancel(); 1464 lastCompletionTime.set(TimeThread.getTime()); 1465 } 1466 } 1467 catch (Exception e) 1468 { 1469 logger.traceException(e); 1470 } 1471 } 1472 } 1473 1474 1475 1476 /** {@inheritDoc} */ 1477 @Override 1478 public Selector getWriteSelector() 1479 { 1480 Selector selector = writeSelector.get(); 1481 if (selector == null) 1482 { 1483 try 1484 { 1485 selector = Selector.open(); 1486 if (!writeSelector.compareAndSet(null, selector)) 1487 { 1488 selector.close(); 1489 selector = writeSelector.get(); 1490 } 1491 } 1492 catch (Exception e) 1493 { 1494 logger.traceException(e); 1495 } 1496 } 1497 1498 return selector; 1499 } 1500 1501 1502 1503 /** {@inheritDoc} */ 1504 @Override 1505 public long getMaxBlockedWriteTimeLimit() 1506 { 1507 return connectionHandler.getMaxBlockedWriteTimeLimit(); 1508 } 1509 1510 1511 1512 /** 1513 * Returns the total number of operations initiated on this 1514 * connection. 1515 * 1516 * @return the total number of operations on this connection 1517 */ 1518 @Override 1519 public long getNumberOfOperations() 1520 { 1521 return operationsPerformed.get(); 1522 } 1523 1524 1525 1526 /** 1527 * Returns the ASN1 reader for this connection. 1528 * 1529 * @return the ASN1 reader for this connection 1530 */ 1531 ASN1ByteChannelReader getASN1Reader() 1532 { 1533 return asn1Reader; 1534 } 1535 1536 1537 1538 /** 1539 * Process data read. 1540 * 1541 * @return number of bytes read if this connection is still valid 1542 * or negative integer to indicate an error otherwise 1543 */ 1544 int processDataRead() 1545 { 1546 if (bindInProgress.get() || startTLSInProgress.get()) 1547 { 1548 // We should wait for the bind or startTLS to finish before 1549 // reading any more data off the socket. 1550 return 0; 1551 } 1552 1553 try 1554 { 1555 int result = asn1Reader.processChannelData(); 1556 if (result < 0) 1557 { 1558 // The connection has been closed by the client. Disconnect 1559 // and return. 1560 disconnect(DisconnectReason.CLIENT_DISCONNECT, false, null); 1561 return -1; 1562 } 1563 return result; 1564 } 1565 catch (Exception e) 1566 { 1567 logger.traceException(e); 1568 1569 if (asn1Reader.hasRemainingData() || e instanceof SSLException) 1570 { 1571 // The connection failed, but there was an unread partial message so 1572 // interpret this as an IO error. 1573 LocalizableMessage m = ERR_LDAP_CLIENT_IO_ERROR_DURING_READ.get(e); 1574 disconnect(DisconnectReason.IO_ERROR, true, m); 1575 } 1576 else 1577 { 1578 // The connection failed and there was no unread data, so interpret this 1579 // as indicating that the client aborted (reset) the connection. This 1580 // happens when a client configures closes a connection which has been 1581 // configured with SO_LINGER set to 0. 1582 LocalizableMessage m = ERR_LDAP_CLIENT_IO_ERROR_BEFORE_READ.get(); 1583 disconnect(DisconnectReason.CLIENT_DISCONNECT, true, m); 1584 } 1585 1586 return -1; 1587 } 1588 } 1589 1590 1591 1592 /** 1593 * Processes the provided LDAP message read from the client and takes 1594 * whatever action is appropriate. For most requests, this will 1595 * include placing the operation in the work queue. Certain requests 1596 * (in particular, abandons and unbinds) will be processed directly. 1597 * 1598 * @param message 1599 * The LDAP message to process. 1600 * @return <CODE>true</CODE> if the appropriate action was taken for 1601 * the request, or <CODE>false</CODE> if there was a fatal 1602 * error and the client has been disconnected as a result, or 1603 * if the client unbound from the server. 1604 */ 1605 boolean processLDAPMessage(LDAPMessage message) 1606 { 1607 if (keepStats) 1608 { 1609 statTracker.updateMessageRead(message); 1610 } 1611 operationsPerformed.getAndIncrement(); 1612 1613 List<Control> opControls = message.getControls(); 1614 1615 // FIXME -- See if there is a bind in progress. If so, then deny 1616 // most kinds of operations. 1617 1618 // Figure out what type of operation we're dealing with based on the 1619 // LDAP message. Abandon and unbind requests will be processed here. 1620 // All other types of requests will be encapsulated into operations 1621 // and append into the work queue to be picked up by a worker 1622 // thread. Any other kinds of LDAP messages (e.g., response 1623 // messages) are illegal and will result in the connection being 1624 // terminated. 1625 try 1626 { 1627 if (bindInProgress.get()) 1628 { 1629 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, ERR_ENQUEUE_BIND_IN_PROGRESS.get()); 1630 } 1631 else if (startTLSInProgress.get()) 1632 { 1633 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, ERR_ENQUEUE_STARTTLS_IN_PROGRESS.get()); 1634 } 1635 else if (saslBindInProgress.get() && message.getProtocolOpType() != OP_TYPE_BIND_REQUEST) 1636 { 1637 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, ERR_ENQUEUE_SASLBIND_IN_PROGRESS.get()); 1638 } 1639 1640 boolean result; 1641 switch (message.getProtocolOpType()) 1642 { 1643 case OP_TYPE_ABANDON_REQUEST: 1644 result = processAbandonRequest(message, opControls); 1645 return result; 1646 case OP_TYPE_ADD_REQUEST: 1647 result = processAddRequest(message, opControls); 1648 return result; 1649 case OP_TYPE_BIND_REQUEST: 1650 bindInProgress.set(true); 1651 if(message.getBindRequestProtocolOp(). 1652 getAuthenticationType() == AuthenticationType.SASL) 1653 { 1654 saslBindInProgress.set(true); 1655 } 1656 result = processBindRequest(message, opControls); 1657 if(!result) 1658 { 1659 bindInProgress.set(false); 1660 if(message.getBindRequestProtocolOp(). 1661 getAuthenticationType() == AuthenticationType.SASL) 1662 { 1663 saslBindInProgress.set(false); 1664 } 1665 } 1666 return result; 1667 case OP_TYPE_COMPARE_REQUEST: 1668 result = processCompareRequest(message, opControls); 1669 return result; 1670 case OP_TYPE_DELETE_REQUEST: 1671 result = processDeleteRequest(message, opControls); 1672 return result; 1673 case OP_TYPE_EXTENDED_REQUEST: 1674 if(message.getExtendedRequestProtocolOp().getOID().equals( 1675 OID_START_TLS_REQUEST)) 1676 { 1677 startTLSInProgress.set(true); 1678 } 1679 result = processExtendedRequest(message, opControls); 1680 if(!result && 1681 message.getExtendedRequestProtocolOp().getOID().equals( 1682 OID_START_TLS_REQUEST)) 1683 { 1684 startTLSInProgress.set(false); 1685 } 1686 return result; 1687 case OP_TYPE_MODIFY_REQUEST: 1688 result = processModifyRequest(message, opControls); 1689 return result; 1690 case OP_TYPE_MODIFY_DN_REQUEST: 1691 result = processModifyDNRequest(message, opControls); 1692 return result; 1693 case OP_TYPE_SEARCH_REQUEST: 1694 result = processSearchRequest(message, opControls); 1695 return result; 1696 case OP_TYPE_UNBIND_REQUEST: 1697 result = processUnbindRequest(message, opControls); 1698 return result; 1699 default: 1700 LocalizableMessage msg = 1701 ERR_LDAP_DISCONNECT_DUE_TO_INVALID_REQUEST_TYPE.get(message 1702 .getProtocolOpName(), message.getMessageID()); 1703 disconnect(DisconnectReason.PROTOCOL_ERROR, true, msg); 1704 return false; 1705 } 1706 } 1707 catch (Exception e) 1708 { 1709 logger.traceException(e); 1710 1711 LocalizableMessage msg = 1712 ERR_LDAP_DISCONNECT_DUE_TO_PROCESSING_FAILURE.get(message 1713 .getProtocolOpName(), message.getMessageID(), e); 1714 disconnect(DisconnectReason.SERVER_ERROR, true, msg); 1715 return false; 1716 } 1717 } 1718 1719 1720 1721 /** 1722 * Processes the provided LDAP message as an abandon request. 1723 * 1724 * @param message 1725 * The LDAP message containing the abandon request to 1726 * process. 1727 * @param controls 1728 * The set of pre-decoded request controls contained in the 1729 * message. 1730 * @return <CODE>true</CODE> if the request was processed 1731 * successfully, or <CODE>false</CODE> if not and the 1732 * connection has been closed as a result (it is the 1733 * responsibility of this method to close the connection). 1734 */ 1735 private boolean processAbandonRequest(LDAPMessage message, List<Control> controls) 1736 { 1737 if (ldapVersion == 2 && controls != null && !controls.isEmpty()) 1738 { 1739 // LDAPv2 clients aren't allowed to send controls. 1740 disconnect(DisconnectReason.PROTOCOL_ERROR, false, 1741 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get()); 1742 return false; 1743 } 1744 1745 // Create the abandon operation and add it into the work queue. 1746 AbandonRequestProtocolOp protocolOp = 1747 message.getAbandonRequestProtocolOp(); 1748 AbandonOperationBasis abandonOp = 1749 new AbandonOperationBasis(this, nextOperationID 1750 .getAndIncrement(), message.getMessageID(), controls, 1751 protocolOp.getIDToAbandon()); 1752 1753 try 1754 { 1755 addOperationInProgress(abandonOp); 1756 } 1757 catch (DirectoryException de) 1758 { 1759 logger.traceException(de); 1760 1761 // Don't send an error response since abandon operations 1762 // don't have a response. 1763 } 1764 1765 return connectionValid; 1766 } 1767 1768 1769 1770 /** 1771 * Processes the provided LDAP message as an add request. 1772 * 1773 * @param message 1774 * The LDAP message containing the add request to process. 1775 * @param controls 1776 * The set of pre-decoded request controls contained in the 1777 * message. 1778 * @return <CODE>true</CODE> if the request was processed 1779 * successfully, or <CODE>false</CODE> if not and the 1780 * connection has been closed as a result (it is the 1781 * responsibility of this method to close the connection). 1782 */ 1783 private boolean processAddRequest(LDAPMessage message, List<Control> controls) 1784 { 1785 if (ldapVersion == 2 && controls != null && !controls.isEmpty()) 1786 { 1787 // LDAPv2 clients aren't allowed to send controls. 1788 AddResponseProtocolOp responseOp = 1789 new AddResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR, 1790 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get()); 1791 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 1792 responseOp)); 1793 disconnect(DisconnectReason.PROTOCOL_ERROR, false, 1794 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get()); 1795 return false; 1796 } 1797 1798 // Create the add operation and add it into the work queue. 1799 AddRequestProtocolOp protocolOp = message.getAddRequestProtocolOp(); 1800 AddOperationBasis addOp = 1801 new AddOperationBasis(this, nextOperationID.getAndIncrement(), 1802 message.getMessageID(), controls, protocolOp.getDN(), 1803 protocolOp.getAttributes()); 1804 1805 try 1806 { 1807 addOperationInProgress(addOp); 1808 } 1809 catch (DirectoryException de) 1810 { 1811 logger.traceException(de); 1812 1813 AddResponseProtocolOp responseOp = 1814 new AddResponseProtocolOp(de.getResultCode().intValue(), 1815 de.getMessageObject(), de.getMatchedDN(), de 1816 .getReferralURLs()); 1817 1818 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 1819 responseOp, addOp.getResponseControls())); 1820 } 1821 1822 return connectionValid; 1823 } 1824 1825 1826 1827 /** 1828 * Processes the provided LDAP message as a bind request. 1829 * 1830 * @param message 1831 * The LDAP message containing the bind request to process. 1832 * @param controls 1833 * The set of pre-decoded request controls contained in the 1834 * message. 1835 * @return <CODE>true</CODE> if the request was processed 1836 * successfully, or <CODE>false</CODE> if not and the 1837 * connection has been closed as a result (it is the 1838 * responsibility of this method to close the connection). 1839 */ 1840 private boolean processBindRequest(LDAPMessage message, 1841 List<Control> controls) 1842 { 1843 BindRequestProtocolOp protocolOp = 1844 message.getBindRequestProtocolOp(); 1845 1846 // See if this is an LDAPv2 bind request, and if so whether that 1847 // should be allowed. 1848 String versionString; 1849 switch (ldapVersion = protocolOp.getProtocolVersion()) 1850 { 1851 case 2: 1852 versionString = "2"; 1853 1854 if (!connectionHandler.allowLDAPv2()) 1855 { 1856 BindResponseProtocolOp responseOp = 1857 new BindResponseProtocolOp( 1858 LDAPResultCode.PROTOCOL_ERROR, 1859 ERR_LDAPV2_CLIENTS_NOT_ALLOWED.get()); 1860 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 1861 responseOp)); 1862 disconnect(DisconnectReason.PROTOCOL_ERROR, false, 1863 ERR_LDAPV2_CLIENTS_NOT_ALLOWED.get()); 1864 return false; 1865 } 1866 1867 if (controls != null && !controls.isEmpty()) 1868 { 1869 // LDAPv2 clients aren't allowed to send controls. 1870 BindResponseProtocolOp responseOp = 1871 new BindResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR, 1872 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get()); 1873 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 1874 responseOp)); 1875 disconnect(DisconnectReason.PROTOCOL_ERROR, false, 1876 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get()); 1877 return false; 1878 } 1879 1880 break; 1881 case 3: 1882 versionString = "3"; 1883 break; 1884 default: 1885 // Unsupported protocol version. RFC4511 states that we MUST send 1886 // a protocol error back to the client. 1887 BindResponseProtocolOp responseOp = 1888 new BindResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR, 1889 ERR_LDAP_UNSUPPORTED_PROTOCOL_VERSION.get(ldapVersion)); 1890 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 1891 responseOp)); 1892 disconnect(DisconnectReason.PROTOCOL_ERROR, false, 1893 ERR_LDAP_UNSUPPORTED_PROTOCOL_VERSION.get(ldapVersion)); 1894 return false; 1895 } 1896 1897 ByteString bindDN = protocolOp.getDN(); 1898 1899 BindOperationBasis bindOp; 1900 switch (protocolOp.getAuthenticationType()) 1901 { 1902 case SIMPLE: 1903 bindOp = 1904 new BindOperationBasis(this, nextOperationID 1905 .getAndIncrement(), message.getMessageID(), controls, 1906 versionString, bindDN, protocolOp.getSimplePassword()); 1907 break; 1908 case SASL: 1909 bindOp = 1910 new BindOperationBasis(this, nextOperationID 1911 .getAndIncrement(), message.getMessageID(), controls, 1912 versionString, bindDN, protocolOp.getSASLMechanism(), 1913 protocolOp.getSASLCredentials()); 1914 break; 1915 default: 1916 // This is an invalid authentication type, and therefore a 1917 // protocol error. As per RFC 2251, a protocol error in a bind 1918 // request must result in terminating the connection. 1919 LocalizableMessage msg = 1920 ERR_LDAP_INVALID_BIND_AUTH_TYPE.get(message.getMessageID(), 1921 protocolOp.getAuthenticationType()); 1922 disconnect(DisconnectReason.PROTOCOL_ERROR, true, msg); 1923 return false; 1924 } 1925 1926 // Add the operation into the work queue. 1927 try 1928 { 1929 addOperationInProgress(bindOp); 1930 } 1931 catch (DirectoryException de) 1932 { 1933 logger.traceException(de); 1934 1935 BindResponseProtocolOp responseOp = 1936 new BindResponseProtocolOp(de.getResultCode().intValue(), 1937 de.getMessageObject(), de.getMatchedDN(), de 1938 .getReferralURLs()); 1939 1940 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 1941 responseOp, bindOp.getResponseControls())); 1942 1943 // If it was a protocol error, then terminate the connection. 1944 if (de.getResultCode() == ResultCode.PROTOCOL_ERROR) 1945 { 1946 LocalizableMessage msg = 1947 ERR_LDAP_DISCONNECT_DUE_TO_BIND_PROTOCOL_ERROR.get(message 1948 .getMessageID(), de.getMessageObject()); 1949 disconnect(DisconnectReason.PROTOCOL_ERROR, true, msg); 1950 } 1951 } 1952 1953 return connectionValid; 1954 } 1955 1956 1957 1958 /** 1959 * Processes the provided LDAP message as a compare request. 1960 * 1961 * @param message 1962 * The LDAP message containing the compare request to 1963 * process. 1964 * @param controls 1965 * The set of pre-decoded request controls contained in the 1966 * message. 1967 * @return <CODE>true</CODE> if the request was processed 1968 * successfully, or <CODE>false</CODE> if not and the 1969 * connection has been closed as a result (it is the 1970 * responsibility of this method to close the connection). 1971 */ 1972 private boolean processCompareRequest(LDAPMessage message, List<Control> controls) 1973 { 1974 if (ldapVersion == 2 && controls != null && !controls.isEmpty()) 1975 { 1976 // LDAPv2 clients aren't allowed to send controls. 1977 CompareResponseProtocolOp responseOp = 1978 new CompareResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR, 1979 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get()); 1980 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 1981 responseOp)); 1982 disconnect(DisconnectReason.PROTOCOL_ERROR, false, 1983 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get()); 1984 return false; 1985 } 1986 1987 CompareRequestProtocolOp protocolOp = 1988 message.getCompareRequestProtocolOp(); 1989 CompareOperationBasis compareOp = 1990 new CompareOperationBasis(this, nextOperationID 1991 .getAndIncrement(), message.getMessageID(), controls, 1992 protocolOp.getDN(), protocolOp.getAttributeType(), 1993 protocolOp.getAssertionValue()); 1994 1995 // Add the operation into the work queue. 1996 try 1997 { 1998 addOperationInProgress(compareOp); 1999 } 2000 catch (DirectoryException de) 2001 { 2002 logger.traceException(de); 2003 2004 CompareResponseProtocolOp responseOp = 2005 new CompareResponseProtocolOp(de.getResultCode().intValue(), 2006 de.getMessageObject(), de.getMatchedDN(), de.getReferralURLs()); 2007 2008 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 2009 responseOp, compareOp.getResponseControls())); 2010 } 2011 2012 return connectionValid; 2013 } 2014 2015 2016 2017 /** 2018 * Processes the provided LDAP message as a delete request. 2019 * 2020 * @param message 2021 * The LDAP message containing the delete request to process. 2022 * @param controls 2023 * The set of pre-decoded request controls contained in the 2024 * message. 2025 * @return <CODE>true</CODE> if the request was processed 2026 * successfully, or <CODE>false</CODE> if not and the 2027 * connection has been closed as a result (it is the 2028 * responsibility of this method to close the connection). 2029 */ 2030 private boolean processDeleteRequest(LDAPMessage message, List<Control> controls) 2031 { 2032 if (ldapVersion == 2 && controls != null && !controls.isEmpty()) 2033 { 2034 // LDAPv2 clients aren't allowed to send controls. 2035 DeleteResponseProtocolOp responseOp = 2036 new DeleteResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR, 2037 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get()); 2038 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 2039 responseOp)); 2040 disconnect(DisconnectReason.PROTOCOL_ERROR, false, 2041 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get()); 2042 return false; 2043 } 2044 2045 DeleteRequestProtocolOp protocolOp = 2046 message.getDeleteRequestProtocolOp(); 2047 DeleteOperationBasis deleteOp = 2048 new DeleteOperationBasis(this, nextOperationID 2049 .getAndIncrement(), message.getMessageID(), controls, 2050 protocolOp.getDN()); 2051 2052 // Add the operation into the work queue. 2053 try 2054 { 2055 addOperationInProgress(deleteOp); 2056 } 2057 catch (DirectoryException de) 2058 { 2059 logger.traceException(de); 2060 2061 DeleteResponseProtocolOp responseOp = 2062 new DeleteResponseProtocolOp( 2063 de.getResultCode().intValue(), de.getMessageObject(), 2064 de.getMatchedDN(), de.getReferralURLs()); 2065 2066 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 2067 responseOp, deleteOp.getResponseControls())); 2068 } 2069 2070 return connectionValid; 2071 } 2072 2073 2074 2075 /** 2076 * Processes the provided LDAP message as an extended request. 2077 * 2078 * @param message 2079 * The LDAP message containing the extended request to 2080 * process. 2081 * @param controls 2082 * The set of pre-decoded request controls contained in the 2083 * message. 2084 * @return <CODE>true</CODE> if the request was processed 2085 * successfully, or <CODE>false</CODE> if not and the 2086 * connection has been closed as a result (it is the 2087 * responsibility of this method to close the connection). 2088 */ 2089 private boolean processExtendedRequest(LDAPMessage message, 2090 List<Control> controls) 2091 { 2092 // See if this is an LDAPv2 client. If it is, then they should not 2093 // be issuing extended requests. We can't send a response that we 2094 // can be sure they can understand, so we have no choice but to 2095 // close the connection. 2096 if (ldapVersion == 2) 2097 { 2098 LocalizableMessage msg = 2099 ERR_LDAPV2_EXTENDED_REQUEST_NOT_ALLOWED.get( 2100 getConnectionID(), message.getMessageID()); 2101 logger.error(msg); 2102 disconnect(DisconnectReason.PROTOCOL_ERROR, false, msg); 2103 return false; 2104 } 2105 2106 // FIXME -- Do we need to handle certain types of request here? 2107 // -- StartTLS requests 2108 // -- Cancel requests 2109 2110 ExtendedRequestProtocolOp protocolOp = 2111 message.getExtendedRequestProtocolOp(); 2112 ExtendedOperationBasis extendedOp = 2113 new ExtendedOperationBasis(this, nextOperationID 2114 .getAndIncrement(), message.getMessageID(), controls, 2115 protocolOp.getOID(), protocolOp.getValue()); 2116 2117 // Add the operation into the work queue. 2118 try 2119 { 2120 addOperationInProgress(extendedOp); 2121 } 2122 catch (DirectoryException de) 2123 { 2124 logger.traceException(de); 2125 2126 ExtendedResponseProtocolOp responseOp = 2127 new ExtendedResponseProtocolOp(de.getResultCode().intValue(), 2128 de.getMessageObject(), de.getMatchedDN(), de.getReferralURLs()); 2129 2130 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 2131 responseOp, extendedOp.getResponseControls())); 2132 } 2133 2134 return connectionValid; 2135 } 2136 2137 2138 2139 /** 2140 * Processes the provided LDAP message as a modify request. 2141 * 2142 * @param message 2143 * The LDAP message containing the modify request to process. 2144 * @param controls 2145 * The set of pre-decoded request controls contained in the 2146 * message. 2147 * @return <CODE>true</CODE> if the request was processed 2148 * successfully, or <CODE>false</CODE> if not and the 2149 * connection has been closed as a result (it is the 2150 * responsibility of this method to close the connection). 2151 */ 2152 private boolean processModifyRequest(LDAPMessage message, List<Control> controls) 2153 { 2154 if (ldapVersion == 2 && controls != null && !controls.isEmpty()) 2155 { 2156 // LDAPv2 clients aren't allowed to send controls. 2157 ModifyResponseProtocolOp responseOp = 2158 new ModifyResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR, 2159 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get()); 2160 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 2161 responseOp)); 2162 disconnect(DisconnectReason.PROTOCOL_ERROR, false, 2163 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get()); 2164 return false; 2165 } 2166 2167 ModifyRequestProtocolOp protocolOp = 2168 message.getModifyRequestProtocolOp(); 2169 ModifyOperationBasis modifyOp = 2170 new ModifyOperationBasis(this, nextOperationID 2171 .getAndIncrement(), message.getMessageID(), controls, 2172 protocolOp.getDN(), protocolOp.getModifications()); 2173 2174 // Add the operation into the work queue. 2175 try 2176 { 2177 addOperationInProgress(modifyOp); 2178 } 2179 catch (DirectoryException de) 2180 { 2181 logger.traceException(de); 2182 2183 ModifyResponseProtocolOp responseOp = 2184 new ModifyResponseProtocolOp( 2185 de.getResultCode().intValue(), de.getMessageObject(), 2186 de.getMatchedDN(), de.getReferralURLs()); 2187 2188 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 2189 responseOp, modifyOp.getResponseControls())); 2190 } 2191 2192 return connectionValid; 2193 } 2194 2195 2196 2197 /** 2198 * Processes the provided LDAP message as a modify DN request. 2199 * 2200 * @param message 2201 * The LDAP message containing the modify DN request to 2202 * process. 2203 * @param controls 2204 * The set of pre-decoded request controls contained in the 2205 * message. 2206 * @return <CODE>true</CODE> if the request was processed 2207 * successfully, or <CODE>false</CODE> if not and the 2208 * connection has been closed as a result (it is the 2209 * responsibility of this method to close the connection). 2210 */ 2211 private boolean processModifyDNRequest(LDAPMessage message, List<Control> controls) 2212 { 2213 if (ldapVersion == 2 && controls != null && !controls.isEmpty()) 2214 { 2215 // LDAPv2 clients aren't allowed to send controls. 2216 ModifyDNResponseProtocolOp responseOp = 2217 new ModifyDNResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR, 2218 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get()); 2219 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 2220 responseOp)); 2221 disconnect(DisconnectReason.PROTOCOL_ERROR, false, 2222 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get()); 2223 return false; 2224 } 2225 2226 ModifyDNRequestProtocolOp protocolOp = 2227 message.getModifyDNRequestProtocolOp(); 2228 ModifyDNOperationBasis modifyDNOp = 2229 new ModifyDNOperationBasis(this, nextOperationID 2230 .getAndIncrement(), message.getMessageID(), controls, 2231 protocolOp.getEntryDN(), protocolOp.getNewRDN(), protocolOp 2232 .deleteOldRDN(), protocolOp.getNewSuperior()); 2233 2234 // Add the operation into the work queue. 2235 try 2236 { 2237 addOperationInProgress(modifyDNOp); 2238 } 2239 catch (DirectoryException de) 2240 { 2241 logger.traceException(de); 2242 2243 ModifyDNResponseProtocolOp responseOp = 2244 new ModifyDNResponseProtocolOp(de.getResultCode().intValue(), 2245 de.getMessageObject(), de.getMatchedDN(), de.getReferralURLs()); 2246 2247 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 2248 responseOp, modifyDNOp.getResponseControls())); 2249 } 2250 2251 return connectionValid; 2252 } 2253 2254 2255 2256 /** 2257 * Processes the provided LDAP message as a search request. 2258 * 2259 * @param message 2260 * The LDAP message containing the search request to process. 2261 * @param controls 2262 * The set of pre-decoded request controls contained in the 2263 * message. 2264 * @return <CODE>true</CODE> if the request was processed 2265 * successfully, or <CODE>false</CODE> if not and the 2266 * connection has been closed as a result (it is the 2267 * responsibility of this method to close the connection). 2268 */ 2269 private boolean processSearchRequest(LDAPMessage message, 2270 List<Control> controls) 2271 { 2272 if (ldapVersion == 2 && controls != null && !controls.isEmpty()) 2273 { 2274 // LDAPv2 clients aren't allowed to send controls. 2275 SearchResultDoneProtocolOp responseOp = 2276 new SearchResultDoneProtocolOp(LDAPResultCode.PROTOCOL_ERROR, 2277 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get()); 2278 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 2279 responseOp)); 2280 disconnect(DisconnectReason.PROTOCOL_ERROR, false, 2281 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get()); 2282 return false; 2283 } 2284 2285 SearchRequestProtocolOp protocolOp = 2286 message.getSearchRequestProtocolOp(); 2287 SearchOperationBasis searchOp = 2288 new SearchOperationBasis(this, nextOperationID 2289 .getAndIncrement(), message.getMessageID(), controls, 2290 protocolOp.getBaseDN(), protocolOp.getScope(), protocolOp 2291 .getDereferencePolicy(), protocolOp.getSizeLimit(), 2292 protocolOp.getTimeLimit(), protocolOp.getTypesOnly(), 2293 protocolOp.getFilter(), protocolOp.getAttributes()); 2294 2295 // Add the operation into the work queue. 2296 try 2297 { 2298 addOperationInProgress(searchOp); 2299 } 2300 catch (DirectoryException de) 2301 { 2302 logger.traceException(de); 2303 2304 SearchResultDoneProtocolOp responseOp = 2305 new SearchResultDoneProtocolOp(de.getResultCode().intValue(), 2306 de.getMessageObject(), de.getMatchedDN(), de.getReferralURLs()); 2307 2308 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 2309 responseOp, searchOp.getResponseControls())); 2310 } 2311 2312 return connectionValid; 2313 } 2314 2315 2316 2317 /** 2318 * Processes the provided LDAP message as an unbind request. 2319 * 2320 * @param message 2321 * The LDAP message containing the unbind request to process. 2322 * @param controls 2323 * The set of pre-decoded request controls contained in the 2324 * message. 2325 * @return <CODE>true</CODE> if the request was processed 2326 * successfully, or <CODE>false</CODE> if not and the 2327 * connection has been closed as a result (it is the 2328 * responsibility of this method to close the connection). 2329 */ 2330 private boolean processUnbindRequest(LDAPMessage message, 2331 List<Control> controls) 2332 { 2333 UnbindOperationBasis unbindOp = 2334 new UnbindOperationBasis(this, nextOperationID 2335 .getAndIncrement(), message.getMessageID(), controls); 2336 2337 unbindOp.run(); 2338 2339 // The client connection will never be valid after an unbind. 2340 return false; 2341 } 2342 2343 2344 2345 /** {@inheritDoc} */ 2346 @Override 2347 public String getMonitorSummary() 2348 { 2349 StringBuilder buffer = new StringBuilder(); 2350 buffer.append("connID=\""); 2351 buffer.append(connectionID); 2352 buffer.append("\" connectTime=\""); 2353 buffer.append(getConnectTimeString()); 2354 buffer.append("\" source=\""); 2355 buffer.append(clientAddress); 2356 buffer.append(":"); 2357 buffer.append(clientPort); 2358 buffer.append("\" destination=\""); 2359 buffer.append(serverAddress); 2360 buffer.append(":"); 2361 buffer.append(connectionHandler.getListenPort()); 2362 buffer.append("\" ldapVersion=\""); 2363 buffer.append(ldapVersion); 2364 buffer.append("\" authDN=\""); 2365 2366 DN authDN = getAuthenticationInfo().getAuthenticationDN(); 2367 if (authDN != null) 2368 { 2369 authDN.toString(buffer); 2370 } 2371 2372 buffer.append("\" security=\""); 2373 if (isSecure()) 2374 { 2375 if (tlsActiveProvider != null) 2376 { 2377 buffer.append(tlsActiveProvider.getName()); 2378 } 2379 if (saslActiveProvider != null) 2380 { 2381 if (tlsActiveProvider != null) 2382 { 2383 buffer.append(","); 2384 } 2385 buffer.append(saslActiveProvider.getName()); 2386 } 2387 } 2388 else 2389 { 2390 buffer.append("none"); 2391 } 2392 2393 buffer.append("\" opsInProgress=\""); 2394 buffer.append(operationsInProgress.size()); 2395 buffer.append("\""); 2396 2397 int countPSearch = getPersistentSearches().size(); 2398 if (countPSearch > 0) 2399 { 2400 buffer.append(" persistentSearches=\""); 2401 buffer.append(countPSearch); 2402 buffer.append("\""); 2403 } 2404 return buffer.toString(); 2405 } 2406 2407 2408 2409 /** 2410 * Appends a string representation of this client connection to the 2411 * provided buffer. 2412 * 2413 * @param buffer 2414 * The buffer to which the information should be appended. 2415 */ 2416 @Override 2417 public void toString(StringBuilder buffer) 2418 { 2419 buffer.append("LDAP client connection from "); 2420 buffer.append(clientAddress); 2421 buffer.append(":"); 2422 buffer.append(clientPort); 2423 buffer.append(" to "); 2424 buffer.append(serverAddress); 2425 buffer.append(":"); 2426 buffer.append(serverPort); 2427 } 2428 2429 2430 2431 /** {@inheritDoc} */ 2432 @Override 2433 public boolean prepareTLS(LocalizableMessageBuilder unavailableReason) 2434 { 2435 if (tlsActiveProvider != null) 2436 { 2437 unavailableReason.append(ERR_LDAP_TLS_EXISTING_SECURITY_PROVIDER 2438 .get(tlsActiveProvider.getName())); 2439 return false; 2440 } 2441 // Make sure that the connection handler allows the use of the 2442 // StartTLS operation. 2443 if (!connectionHandler.allowStartTLS()) 2444 { 2445 unavailableReason.append(ERR_LDAP_TLS_STARTTLS_NOT_ALLOWED.get()); 2446 return false; 2447 } 2448 try 2449 { 2450 TLSByteChannel tlsByteChannel = 2451 connectionHandler.getTLSByteChannel(timeoutClientChannel); 2452 setTLSPendingProvider(tlsByteChannel); 2453 } 2454 catch (DirectoryException de) 2455 { 2456 logger.traceException(de); 2457 unavailableReason.append(ERR_LDAP_TLS_CANNOT_CREATE_TLS_PROVIDER 2458 .get(stackTraceToSingleLineString(de))); 2459 return false; 2460 } 2461 return true; 2462 } 2463 2464 2465 2466 /** 2467 * Retrieves the length of time in milliseconds that this client 2468 * connection has been idle. <BR> 2469 * <BR> 2470 * Note that the default implementation will always return zero. 2471 * Subclasses associated with connection handlers should override this 2472 * method if they wish to provided idle time limit functionality. 2473 * 2474 * @return The length of time in milliseconds that this client 2475 * connection has been idle. 2476 */ 2477 @Override 2478 public long getIdleTime() 2479 { 2480 if (operationsInProgress.isEmpty() 2481 && getPersistentSearches().isEmpty()) 2482 { 2483 return TimeThread.getTime() - lastCompletionTime.get(); 2484 } 2485 else 2486 { 2487 // There's at least one operation in progress, so it's not idle. 2488 return 0L; 2489 } 2490 } 2491 2492 2493 2494 /** 2495 * Set the connection provider that is not in use yet. Used in TLS 2496 * negotiation when a clear response is needed before the connection 2497 * provider is active. 2498 * 2499 * @param provider 2500 * The provider that needs to be activated. 2501 */ 2502 public void setTLSPendingProvider(ConnectionSecurityProvider provider) 2503 { 2504 tlsPendingProvider = provider; 2505 } 2506 2507 2508 2509 /** 2510 * Set the connection provider that is not in use. Used in SASL 2511 * negotiation when a clear response is needed before the connection 2512 * provider is active. 2513 * 2514 * @param provider 2515 * The provider that needs to be activated. 2516 */ 2517 public void setSASLPendingProvider(ConnectionSecurityProvider provider) 2518 { 2519 saslPendingProvider = provider; 2520 } 2521 2522 2523 2524 /** 2525 * Enable the provider that is inactive. 2526 */ 2527 private void enableTLS() 2528 { 2529 tlsActiveProvider = tlsPendingProvider; 2530 tlsChannel.redirect(tlsPendingProvider); 2531 tlsPendingProvider = null; 2532 } 2533 2534 2535 2536 /** 2537 * Set the security provider to the specified provider. 2538 * 2539 * @param sslProvider 2540 * The provider to set the security provider to. 2541 */ 2542 private void enableSSL(ConnectionSecurityProvider sslProvider) 2543 { 2544 tlsActiveProvider = sslProvider; 2545 tlsChannel.redirect(sslProvider); 2546 } 2547 2548 2549 2550 /** 2551 * Enable the SASL provider that is currently inactive or pending. 2552 */ 2553 private void enableSASL() 2554 { 2555 saslActiveProvider = saslPendingProvider; 2556 saslChannel.redirect(saslPendingProvider); 2557 saslPendingProvider = null; 2558 } 2559 2560 2561 2562 /** 2563 * Return the certificate chain array associated with a connection. 2564 * 2565 * @return The array of certificates associated with a connection. 2566 */ 2567 public Certificate[] getClientCertificateChain() 2568 { 2569 if (tlsActiveProvider != null) 2570 { 2571 return tlsActiveProvider.getClientCertificateChain(); 2572 } 2573 if (saslActiveProvider != null) 2574 { 2575 return saslActiveProvider.getClientCertificateChain(); 2576 } 2577 return new Certificate[0]; 2578 } 2579 2580 2581 2582 /** 2583 * Retrieves the TLS redirecting byte channel used in a LDAP client 2584 * connection. 2585 * 2586 * @return The TLS redirecting byte channel. 2587 */ 2588 @Override 2589 public ByteChannel getChannel() { 2590 return this.tlsChannel; 2591 } 2592 2593 2594 2595 /** {@inheritDoc} */ 2596 @Override 2597 public int getSSF() 2598 { 2599 int tlsSSF = tlsActiveProvider != null ? tlsActiveProvider.getSSF() : 0; 2600 int saslSSF = saslActiveProvider != null ? saslActiveProvider.getSSF() : 0; 2601 return Math.max(tlsSSF, saslSSF); 2602 } 2603 2604 2605 2606 /** {@inheritDoc} */ 2607 @Override 2608 public void finishBind() 2609 { 2610 if (this.saslPendingProvider != null) 2611 { 2612 enableSASL(); 2613 } 2614 2615 super.finishBind(); 2616 } 2617 2618 2619 2620 /** {@inheritDoc} */ 2621 @Override 2622 public void finishStartTLS() 2623 { 2624 if(this.tlsPendingProvider != null) 2625 { 2626 enableTLS(); 2627 } 2628 2629 super.finishStartTLS(); 2630 } 2631}