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 (Exception e) 989 { 990 logger.traceException(e); 991 992 // FIXME -- Log a message or something 993 disconnect(DisconnectReason.SERVER_ERROR, false, null); 994 return; 995 } 996 finally 997 { 998 // Clear and reset all of the internal buffers ready for the next usage. 999 // The ASN1Writer is based on a ByteStringBuilder so closing will cause 1000 // the internal buffers to be resized if needed. 1001 close(holder); 1002 } 1003 } 1004 1005 1006 1007 /** 1008 * Closes the connection to the client, optionally sending it a 1009 * message indicating the reason for the closure. Note that the 1010 * ability to send a notice of disconnection may not be available for 1011 * all protocols or under all circumstances. 1012 * 1013 * @param disconnectReason 1014 * The disconnect reason that provides the generic cause for 1015 * the disconnect. 1016 * @param sendNotification 1017 * Indicates whether to try to provide notification to the 1018 * client that the connection will be closed. 1019 * @param message 1020 * The message to include in the disconnect notification 1021 * response. It may be <CODE>null</CODE> if no message is to 1022 * be sent. 1023 */ 1024 @Override 1025 public void disconnect(DisconnectReason disconnectReason, 1026 boolean sendNotification, LocalizableMessage message) 1027 { 1028 // Set a flag indicating that the connection is being terminated so 1029 // that no new requests will be accepted. Also cancel all operations 1030 // in progress. 1031 synchronized (opsInProgressLock) 1032 { 1033 // If we are already in the middle of a disconnect, then don't 1034 // do anything. 1035 if (disconnectRequested) 1036 { 1037 return; 1038 } 1039 1040 disconnectRequested = true; 1041 } 1042 1043 if (keepStats) 1044 { 1045 statTracker.updateDisconnect(); 1046 } 1047 1048 if (connectionID >= 0) 1049 { 1050 DirectoryServer.connectionClosed(this); 1051 } 1052 1053 // Indicate that this connection is no longer valid. 1054 connectionValid = false; 1055 1056 if (message != null) 1057 { 1058 LocalizableMessageBuilder msgBuilder = new LocalizableMessageBuilder(); 1059 msgBuilder.append(disconnectReason.getClosureMessage()); 1060 msgBuilder.append(": "); 1061 msgBuilder.append(message); 1062 cancelAllOperations(new CancelRequest(true, msgBuilder 1063 .toMessage())); 1064 } 1065 else 1066 { 1067 cancelAllOperations(new CancelRequest(true, disconnectReason 1068 .getClosureMessage())); 1069 } 1070 finalizeConnectionInternal(); 1071 1072 // If there is a write selector for this connection, then close it. 1073 Selector selector = writeSelector.get(); 1074 close(selector); 1075 1076 // See if we should send a notification to the client. If so, then 1077 // construct and send a notice of disconnection unsolicited 1078 // response. Note that we cannot send this notification to an LDAPv2 client. 1079 if (sendNotification && ldapVersion != 2) 1080 { 1081 try 1082 { 1083 int resultCode; 1084 switch (disconnectReason) 1085 { 1086 case PROTOCOL_ERROR: 1087 resultCode = LDAPResultCode.PROTOCOL_ERROR; 1088 break; 1089 case SERVER_SHUTDOWN: 1090 resultCode = LDAPResultCode.UNAVAILABLE; 1091 break; 1092 case SERVER_ERROR: 1093 resultCode = DirectoryServer.getServerErrorResultCode().intValue(); 1094 break; 1095 case ADMIN_LIMIT_EXCEEDED: 1096 case IDLE_TIME_LIMIT_EXCEEDED: 1097 case MAX_REQUEST_SIZE_EXCEEDED: 1098 case IO_TIMEOUT: 1099 resultCode = LDAPResultCode.ADMIN_LIMIT_EXCEEDED; 1100 break; 1101 case CONNECTION_REJECTED: 1102 resultCode = LDAPResultCode.CONSTRAINT_VIOLATION; 1103 break; 1104 case INVALID_CREDENTIALS: 1105 resultCode = LDAPResultCode.INVALID_CREDENTIALS; 1106 break; 1107 default: 1108 resultCode = LDAPResultCode.OTHER; 1109 break; 1110 } 1111 1112 LocalizableMessage errMsg; 1113 if (message == null) 1114 { 1115 errMsg = 1116 INFO_LDAP_CLIENT_GENERIC_NOTICE_OF_DISCONNECTION.get(); 1117 } 1118 else 1119 { 1120 errMsg = message; 1121 } 1122 1123 ExtendedResponseProtocolOp notificationOp = 1124 new ExtendedResponseProtocolOp(resultCode, errMsg, null, 1125 null, OID_NOTICE_OF_DISCONNECTION, null); 1126 1127 sendLDAPMessage(new LDAPMessage(0, notificationOp, null)); 1128 } 1129 catch (Exception e) 1130 { 1131 // NYI -- Log a message indicating that we couldn't send the 1132 // notice of disconnection. 1133 logger.traceException(e); 1134 } 1135 } 1136 1137 // Enqueue the connection channels for closing by the finalizer. 1138 Runnable r = new ConnectionFinalizerJob(asn1Reader, clientChannel); 1139 connectionHandler.registerConnectionFinalizer(r); 1140 1141 // NYI -- Deregister the client connection from any server components that 1142 // might know about it. 1143 1144 // Log a disconnect message. 1145 logDisconnect(this, disconnectReason, message); 1146 1147 try 1148 { 1149 PluginConfigManager pluginManager = 1150 DirectoryServer.getPluginConfigManager(); 1151 pluginManager.invokePostDisconnectPlugins(this, disconnectReason, 1152 message); 1153 } 1154 catch (Exception e) 1155 { 1156 logger.traceException(e); 1157 } 1158 } 1159 1160 1161 1162 /** 1163 * Retrieves the set of operations in progress for this client 1164 * connection. This list must not be altered by any caller. 1165 * 1166 * @return The set of operations in progress for this client 1167 * connection. 1168 */ 1169 @Override 1170 public Collection<Operation> getOperationsInProgress() 1171 { 1172 return operationsInProgress.values(); 1173 } 1174 1175 1176 1177 /** 1178 * Retrieves the operation in progress with the specified message ID. 1179 * 1180 * @param messageID 1181 * The message ID for the operation to retrieve. 1182 * @return The operation in progress with the specified message ID, or 1183 * <CODE>null</CODE> if no such operation could be found. 1184 */ 1185 @Override 1186 public Operation getOperationInProgress(int messageID) 1187 { 1188 return operationsInProgress.get(messageID); 1189 } 1190 1191 1192 1193 /** 1194 * Adds the provided operation to the set of operations in progress 1195 * for this client connection. 1196 * 1197 * @param operation 1198 * The operation to add to the set of operations in progress 1199 * for this client connection. 1200 * @throws DirectoryException 1201 * If the operation is not added for some reason (e.g., the 1202 * client already has reached the maximum allowed concurrent 1203 * requests). 1204 */ 1205 private void addOperationInProgress(Operation operation) 1206 throws DirectoryException 1207 { 1208 int messageID = operation.getMessageID(); 1209 1210 // We need to grab a lock to ensure that no one else can add 1211 // operations to the queue while we are performing some preliminary 1212 // checks. 1213 try 1214 { 1215 synchronized (opsInProgressLock) 1216 { 1217 // If we're already in the process of disconnecting the client, 1218 // then reject the operation. 1219 if (disconnectRequested) 1220 { 1221 LocalizableMessage message = WARN_CLIENT_DISCONNECT_IN_PROGRESS.get(); 1222 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 1223 message); 1224 } 1225 1226 // Add the operation to the list of operations in progress for 1227 // this connection. 1228 Operation op = operationsInProgress.putIfAbsent(messageID, operation); 1229 1230 // See if there is already an operation in progress with the 1231 // same message ID. If so, then we can't allow it. 1232 if (op != null) 1233 { 1234 LocalizableMessage message = 1235 WARN_LDAP_CLIENT_DUPLICATE_MESSAGE_ID.get(messageID); 1236 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, 1237 message); 1238 } 1239 } 1240 1241 // Try to add the operation to the work queue, 1242 // or run it synchronously (typically for the administration 1243 // connector) 1244 connectionHandler.getQueueingStrategy().enqueueRequest( 1245 operation); 1246 } 1247 catch (DirectoryException de) 1248 { 1249 logger.traceException(de); 1250 1251 operationsInProgress.remove(messageID); 1252 lastCompletionTime.set(TimeThread.getTime()); 1253 1254 throw de; 1255 } 1256 catch (Exception e) 1257 { 1258 logger.traceException(e); 1259 1260 LocalizableMessage message = 1261 WARN_LDAP_CLIENT_CANNOT_ENQUEUE.get(getExceptionMessage(e)); 1262 throw new DirectoryException(DirectoryServer 1263 .getServerErrorResultCode(), message, e); 1264 } 1265 } 1266 1267 1268 1269 /** 1270 * Removes the provided operation from the set of operations in 1271 * progress for this client connection. Note that this does not make 1272 * any attempt to cancel any processing that may already be in 1273 * progress for the operation. 1274 * 1275 * @param messageID 1276 * The message ID of the operation to remove from the set of 1277 * operations in progress. 1278 * @return <CODE>true</CODE> if the operation was found and removed 1279 * from the set of operations in progress, or 1280 * <CODE>false</CODE> if not. 1281 */ 1282 @Override 1283 public boolean removeOperationInProgress(int messageID) 1284 { 1285 Operation operation = operationsInProgress.remove(messageID); 1286 if (operation == null) 1287 { 1288 return false; 1289 } 1290 1291 if (operation.getOperationType() == OperationType.ABANDON 1292 && keepStats 1293 && operation.getResultCode() == ResultCode.CANCELLED) 1294 { 1295 statTracker.updateAbandonedOperation(); 1296 } 1297 1298 lastCompletionTime.set(TimeThread.getTime()); 1299 return true; 1300 } 1301 1302 1303 1304 /** 1305 * Attempts to cancel the specified operation. 1306 * 1307 * @param messageID 1308 * The message ID of the operation to cancel. 1309 * @param cancelRequest 1310 * An object providing additional information about how the 1311 * cancel should be processed. 1312 * @return A cancel result that either indicates that the cancel was 1313 * successful or provides a reason that it was not. 1314 */ 1315 @Override 1316 public CancelResult cancelOperation(int messageID, 1317 CancelRequest cancelRequest) 1318 { 1319 Operation op = operationsInProgress.get(messageID); 1320 if (op == null) 1321 { 1322 // See if the operation is in the list of persistent searches. 1323 for (PersistentSearch ps : getPersistentSearches()) 1324 { 1325 if (ps.getMessageID() == messageID) 1326 { 1327 // We only need to find the first persistent search 1328 // associated with the provided message ID. The persistent 1329 // search will ensure that all other related persistent 1330 // searches are cancelled. 1331 return ps.cancel(); 1332 } 1333 } 1334 1335 return new CancelResult(ResultCode.NO_SUCH_OPERATION, null); 1336 } 1337 else 1338 { 1339 return op.cancel(cancelRequest); 1340 } 1341 } 1342 1343 1344 1345 /** 1346 * Attempts to cancel all operations in progress on this connection. 1347 * 1348 * @param cancelRequest 1349 * An object providing additional information about how the 1350 * cancel should be processed. 1351 */ 1352 @Override 1353 public void cancelAllOperations(CancelRequest cancelRequest) 1354 { 1355 // Make sure that no one can add any new operations. 1356 synchronized (opsInProgressLock) 1357 { 1358 try 1359 { 1360 for (Operation o : operationsInProgress.values()) 1361 { 1362 try 1363 { 1364 o.abort(cancelRequest); 1365 1366 // TODO: Assume its cancelled? 1367 if (keepStats) 1368 { 1369 statTracker.updateAbandonedOperation(); 1370 } 1371 } 1372 catch (Exception e) 1373 { 1374 logger.traceException(e); 1375 } 1376 } 1377 1378 if (!operationsInProgress.isEmpty() 1379 || !getPersistentSearches().isEmpty()) 1380 { 1381 lastCompletionTime.set(TimeThread.getTime()); 1382 } 1383 1384 operationsInProgress.clear(); 1385 1386 for (PersistentSearch persistentSearch : getPersistentSearches()) 1387 { 1388 persistentSearch.cancel(); 1389 } 1390 } 1391 catch (Exception e) 1392 { 1393 logger.traceException(e); 1394 } 1395 } 1396 } 1397 1398 1399 1400 /** 1401 * Attempts to cancel all operations in progress on this connection 1402 * except the operation with the specified message ID. 1403 * 1404 * @param cancelRequest 1405 * An object providing additional information about how the 1406 * cancel should be processed. 1407 * @param messageID 1408 * The message ID of the operation that should not be 1409 * canceled. 1410 */ 1411 @Override 1412 public void cancelAllOperationsExcept(CancelRequest cancelRequest, 1413 int messageID) 1414 { 1415 // Make sure that no one can add any new operations. 1416 synchronized (opsInProgressLock) 1417 { 1418 try 1419 { 1420 for (int msgID : operationsInProgress.keySet()) 1421 { 1422 if (msgID == messageID) 1423 { 1424 continue; 1425 } 1426 1427 Operation o = operationsInProgress.get(msgID); 1428 if (o != null) 1429 { 1430 try 1431 { 1432 o.abort(cancelRequest); 1433 1434 // TODO: Assume its cancelled? 1435 if (keepStats) 1436 { 1437 statTracker.updateAbandonedOperation(); 1438 } 1439 } 1440 catch (Exception e) 1441 { 1442 logger.traceException(e); 1443 } 1444 } 1445 1446 operationsInProgress.remove(msgID); 1447 lastCompletionTime.set(TimeThread.getTime()); 1448 } 1449 1450 for (PersistentSearch persistentSearch : getPersistentSearches()) 1451 { 1452 if (persistentSearch.getMessageID() == messageID) 1453 { 1454 continue; 1455 } 1456 1457 persistentSearch.cancel(); 1458 lastCompletionTime.set(TimeThread.getTime()); 1459 } 1460 } 1461 catch (Exception e) 1462 { 1463 logger.traceException(e); 1464 } 1465 } 1466 } 1467 1468 1469 1470 /** {@inheritDoc} */ 1471 @Override 1472 public Selector getWriteSelector() 1473 { 1474 Selector selector = writeSelector.get(); 1475 if (selector == null) 1476 { 1477 try 1478 { 1479 selector = Selector.open(); 1480 if (!writeSelector.compareAndSet(null, selector)) 1481 { 1482 selector.close(); 1483 selector = writeSelector.get(); 1484 } 1485 } 1486 catch (Exception e) 1487 { 1488 logger.traceException(e); 1489 } 1490 } 1491 1492 return selector; 1493 } 1494 1495 1496 1497 /** {@inheritDoc} */ 1498 @Override 1499 public long getMaxBlockedWriteTimeLimit() 1500 { 1501 return connectionHandler.getMaxBlockedWriteTimeLimit(); 1502 } 1503 1504 1505 1506 /** 1507 * Returns the total number of operations initiated on this 1508 * connection. 1509 * 1510 * @return the total number of operations on this connection 1511 */ 1512 @Override 1513 public long getNumberOfOperations() 1514 { 1515 return operationsPerformed.get(); 1516 } 1517 1518 1519 1520 /** 1521 * Returns the ASN1 reader for this connection. 1522 * 1523 * @return the ASN1 reader for this connection 1524 */ 1525 ASN1ByteChannelReader getASN1Reader() 1526 { 1527 return asn1Reader; 1528 } 1529 1530 1531 1532 /** 1533 * Process data read. 1534 * 1535 * @return number of bytes read if this connection is still valid 1536 * or negative integer to indicate an error otherwise 1537 */ 1538 int processDataRead() 1539 { 1540 if (bindOrStartTLSInProgress.get()) 1541 { 1542 // We should wait for the bind or startTLS to finish before 1543 // reading any more data off the socket. 1544 return 0; 1545 } 1546 1547 try 1548 { 1549 int result = asn1Reader.processChannelData(); 1550 if (result < 0) 1551 { 1552 // The connection has been closed by the client. Disconnect 1553 // and return. 1554 disconnect(DisconnectReason.CLIENT_DISCONNECT, false, null); 1555 return -1; 1556 } 1557 return result; 1558 } 1559 catch (Exception e) 1560 { 1561 logger.traceException(e); 1562 1563 if (asn1Reader.hasRemainingData() || e instanceof SSLException) 1564 { 1565 // The connection failed, but there was an unread partial message so 1566 // interpret this as an IO error. 1567 LocalizableMessage m = ERR_LDAP_CLIENT_IO_ERROR_DURING_READ.get(e); 1568 disconnect(DisconnectReason.IO_ERROR, true, m); 1569 } 1570 else 1571 { 1572 // The connection failed and there was no unread data, so interpret this 1573 // as indicating that the client aborted (reset) the connection. This 1574 // happens when a client configures closes a connection which has been 1575 // configured with SO_LINGER set to 0. 1576 LocalizableMessage m = ERR_LDAP_CLIENT_IO_ERROR_BEFORE_READ.get(); 1577 disconnect(DisconnectReason.CLIENT_DISCONNECT, true, m); 1578 } 1579 1580 return -1; 1581 } 1582 } 1583 1584 1585 1586 /** 1587 * Processes the provided LDAP message read from the client and takes 1588 * whatever action is appropriate. For most requests, this will 1589 * include placing the operation in the work queue. Certain requests 1590 * (in particular, abandons and unbinds) will be processed directly. 1591 * 1592 * @param message 1593 * The LDAP message to process. 1594 * @return <CODE>true</CODE> if the appropriate action was taken for 1595 * the request, or <CODE>false</CODE> if there was a fatal 1596 * error and the client has been disconnected as a result, or 1597 * if the client unbound from the server. 1598 */ 1599 boolean processLDAPMessage(LDAPMessage message) 1600 { 1601 if (keepStats) 1602 { 1603 statTracker.updateMessageRead(message); 1604 } 1605 operationsPerformed.getAndIncrement(); 1606 1607 List<Control> opControls = message.getControls(); 1608 1609 // FIXME -- See if there is a bind in progress. If so, then deny 1610 // most kinds of operations. 1611 1612 // Figure out what type of operation we're dealing with based on the 1613 // LDAP message. Abandon and unbind requests will be processed here. 1614 // All other types of requests will be encapsulated into operations 1615 // and append into the work queue to be picked up by a worker 1616 // thread. Any other kinds of LDAP messages (e.g., response 1617 // messages) are illegal and will result in the connection being 1618 // terminated. 1619 try 1620 { 1621 if(bindOrStartTLSInProgress.get() || 1622 (saslBindInProgress.get() && 1623 message.getProtocolOpType() != OP_TYPE_BIND_REQUEST)) 1624 { 1625 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 1626 ERR_ENQUEUE_BIND_IN_PROGRESS.get()); 1627 } 1628 1629 boolean result; 1630 switch (message.getProtocolOpType()) 1631 { 1632 case OP_TYPE_ABANDON_REQUEST: 1633 result = processAbandonRequest(message, opControls); 1634 return result; 1635 case OP_TYPE_ADD_REQUEST: 1636 result = processAddRequest(message, opControls); 1637 return result; 1638 case OP_TYPE_BIND_REQUEST: 1639 bindOrStartTLSInProgress.set(true); 1640 if(message.getBindRequestProtocolOp(). 1641 getAuthenticationType() == AuthenticationType.SASL) 1642 { 1643 saslBindInProgress.set(true); 1644 } 1645 result = processBindRequest(message, opControls); 1646 if(!result) 1647 { 1648 bindOrStartTLSInProgress.set(false); 1649 if(message.getBindRequestProtocolOp(). 1650 getAuthenticationType() == AuthenticationType.SASL) 1651 { 1652 saslBindInProgress.set(false); 1653 } 1654 } 1655 return result; 1656 case OP_TYPE_COMPARE_REQUEST: 1657 result = processCompareRequest(message, opControls); 1658 return result; 1659 case OP_TYPE_DELETE_REQUEST: 1660 result = processDeleteRequest(message, opControls); 1661 return result; 1662 case OP_TYPE_EXTENDED_REQUEST: 1663 if(message.getExtendedRequestProtocolOp().getOID().equals( 1664 OID_START_TLS_REQUEST)) 1665 { 1666 bindOrStartTLSInProgress.set(true); 1667 } 1668 result = processExtendedRequest(message, opControls); 1669 if(!result && 1670 message.getExtendedRequestProtocolOp().getOID().equals( 1671 OID_START_TLS_REQUEST)) 1672 { 1673 bindOrStartTLSInProgress.set(false); 1674 } 1675 return result; 1676 case OP_TYPE_MODIFY_REQUEST: 1677 result = processModifyRequest(message, opControls); 1678 return result; 1679 case OP_TYPE_MODIFY_DN_REQUEST: 1680 result = processModifyDNRequest(message, opControls); 1681 return result; 1682 case OP_TYPE_SEARCH_REQUEST: 1683 result = processSearchRequest(message, opControls); 1684 return result; 1685 case OP_TYPE_UNBIND_REQUEST: 1686 result = processUnbindRequest(message, opControls); 1687 return result; 1688 default: 1689 LocalizableMessage msg = 1690 ERR_LDAP_DISCONNECT_DUE_TO_INVALID_REQUEST_TYPE.get(message 1691 .getProtocolOpName(), message.getMessageID()); 1692 disconnect(DisconnectReason.PROTOCOL_ERROR, true, msg); 1693 return false; 1694 } 1695 } 1696 catch (Exception e) 1697 { 1698 logger.traceException(e); 1699 1700 LocalizableMessage msg = 1701 ERR_LDAP_DISCONNECT_DUE_TO_PROCESSING_FAILURE.get(message 1702 .getProtocolOpName(), message.getMessageID(), e); 1703 disconnect(DisconnectReason.SERVER_ERROR, true, msg); 1704 return false; 1705 } 1706 } 1707 1708 1709 1710 /** 1711 * Processes the provided LDAP message as an abandon request. 1712 * 1713 * @param message 1714 * The LDAP message containing the abandon request to 1715 * process. 1716 * @param controls 1717 * The set of pre-decoded request controls contained in the 1718 * message. 1719 * @return <CODE>true</CODE> if the request was processed 1720 * successfully, or <CODE>false</CODE> if not and the 1721 * connection has been closed as a result (it is the 1722 * responsibility of this method to close the connection). 1723 */ 1724 private boolean processAbandonRequest(LDAPMessage message, List<Control> controls) 1725 { 1726 if (ldapVersion == 2 && controls != null && !controls.isEmpty()) 1727 { 1728 // LDAPv2 clients aren't allowed to send controls. 1729 disconnect(DisconnectReason.PROTOCOL_ERROR, false, 1730 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get()); 1731 return false; 1732 } 1733 1734 // Create the abandon operation and add it into the work queue. 1735 AbandonRequestProtocolOp protocolOp = 1736 message.getAbandonRequestProtocolOp(); 1737 AbandonOperationBasis abandonOp = 1738 new AbandonOperationBasis(this, nextOperationID 1739 .getAndIncrement(), message.getMessageID(), controls, 1740 protocolOp.getIDToAbandon()); 1741 1742 try 1743 { 1744 addOperationInProgress(abandonOp); 1745 } 1746 catch (DirectoryException de) 1747 { 1748 logger.traceException(de); 1749 1750 // Don't send an error response since abandon operations 1751 // don't have a response. 1752 } 1753 1754 return connectionValid; 1755 } 1756 1757 1758 1759 /** 1760 * Processes the provided LDAP message as an add request. 1761 * 1762 * @param message 1763 * The LDAP message containing the add request to process. 1764 * @param controls 1765 * The set of pre-decoded request controls contained in the 1766 * message. 1767 * @return <CODE>true</CODE> if the request was processed 1768 * successfully, or <CODE>false</CODE> if not and the 1769 * connection has been closed as a result (it is the 1770 * responsibility of this method to close the connection). 1771 */ 1772 private boolean processAddRequest(LDAPMessage message, List<Control> controls) 1773 { 1774 if (ldapVersion == 2 && controls != null && !controls.isEmpty()) 1775 { 1776 // LDAPv2 clients aren't allowed to send controls. 1777 AddResponseProtocolOp responseOp = 1778 new AddResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR, 1779 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get()); 1780 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 1781 responseOp)); 1782 disconnect(DisconnectReason.PROTOCOL_ERROR, false, 1783 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get()); 1784 return false; 1785 } 1786 1787 // Create the add operation and add it into the work queue. 1788 AddRequestProtocolOp protocolOp = message.getAddRequestProtocolOp(); 1789 AddOperationBasis addOp = 1790 new AddOperationBasis(this, nextOperationID.getAndIncrement(), 1791 message.getMessageID(), controls, protocolOp.getDN(), 1792 protocolOp.getAttributes()); 1793 1794 try 1795 { 1796 addOperationInProgress(addOp); 1797 } 1798 catch (DirectoryException de) 1799 { 1800 logger.traceException(de); 1801 1802 AddResponseProtocolOp responseOp = 1803 new AddResponseProtocolOp(de.getResultCode().intValue(), 1804 de.getMessageObject(), de.getMatchedDN(), de 1805 .getReferralURLs()); 1806 1807 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 1808 responseOp, addOp.getResponseControls())); 1809 } 1810 1811 return connectionValid; 1812 } 1813 1814 1815 1816 /** 1817 * Processes the provided LDAP message as a bind request. 1818 * 1819 * @param message 1820 * The LDAP message containing the bind request to process. 1821 * @param controls 1822 * The set of pre-decoded request controls contained in the 1823 * message. 1824 * @return <CODE>true</CODE> if the request was processed 1825 * successfully, or <CODE>false</CODE> if not and the 1826 * connection has been closed as a result (it is the 1827 * responsibility of this method to close the connection). 1828 */ 1829 private boolean processBindRequest(LDAPMessage message, 1830 List<Control> controls) 1831 { 1832 BindRequestProtocolOp protocolOp = 1833 message.getBindRequestProtocolOp(); 1834 1835 // See if this is an LDAPv2 bind request, and if so whether that 1836 // should be allowed. 1837 String versionString; 1838 switch (ldapVersion = protocolOp.getProtocolVersion()) 1839 { 1840 case 2: 1841 versionString = "2"; 1842 1843 if (!connectionHandler.allowLDAPv2()) 1844 { 1845 BindResponseProtocolOp responseOp = 1846 new BindResponseProtocolOp( 1847 LDAPResultCode.PROTOCOL_ERROR, 1848 ERR_LDAPV2_CLIENTS_NOT_ALLOWED.get()); 1849 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 1850 responseOp)); 1851 disconnect(DisconnectReason.PROTOCOL_ERROR, false, 1852 ERR_LDAPV2_CLIENTS_NOT_ALLOWED.get()); 1853 return false; 1854 } 1855 1856 if (controls != null && !controls.isEmpty()) 1857 { 1858 // LDAPv2 clients aren't allowed to send controls. 1859 BindResponseProtocolOp responseOp = 1860 new BindResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR, 1861 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get()); 1862 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 1863 responseOp)); 1864 disconnect(DisconnectReason.PROTOCOL_ERROR, false, 1865 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get()); 1866 return false; 1867 } 1868 1869 break; 1870 case 3: 1871 versionString = "3"; 1872 break; 1873 default: 1874 // Unsupported protocol version. RFC4511 states that we MUST send 1875 // a protocol error back to the client. 1876 BindResponseProtocolOp responseOp = 1877 new BindResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR, 1878 ERR_LDAP_UNSUPPORTED_PROTOCOL_VERSION.get(ldapVersion)); 1879 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 1880 responseOp)); 1881 disconnect(DisconnectReason.PROTOCOL_ERROR, false, 1882 ERR_LDAP_UNSUPPORTED_PROTOCOL_VERSION.get(ldapVersion)); 1883 return false; 1884 } 1885 1886 ByteString bindDN = protocolOp.getDN(); 1887 1888 BindOperationBasis bindOp; 1889 switch (protocolOp.getAuthenticationType()) 1890 { 1891 case SIMPLE: 1892 bindOp = 1893 new BindOperationBasis(this, nextOperationID 1894 .getAndIncrement(), message.getMessageID(), controls, 1895 versionString, bindDN, protocolOp.getSimplePassword()); 1896 break; 1897 case SASL: 1898 bindOp = 1899 new BindOperationBasis(this, nextOperationID 1900 .getAndIncrement(), message.getMessageID(), controls, 1901 versionString, bindDN, protocolOp.getSASLMechanism(), 1902 protocolOp.getSASLCredentials()); 1903 break; 1904 default: 1905 // This is an invalid authentication type, and therefore a 1906 // protocol error. As per RFC 2251, a protocol error in a bind 1907 // request must result in terminating the connection. 1908 LocalizableMessage msg = 1909 ERR_LDAP_INVALID_BIND_AUTH_TYPE.get(message.getMessageID(), 1910 protocolOp.getAuthenticationType()); 1911 disconnect(DisconnectReason.PROTOCOL_ERROR, true, msg); 1912 return false; 1913 } 1914 1915 // Add the operation into the work queue. 1916 try 1917 { 1918 addOperationInProgress(bindOp); 1919 } 1920 catch (DirectoryException de) 1921 { 1922 logger.traceException(de); 1923 1924 BindResponseProtocolOp responseOp = 1925 new BindResponseProtocolOp(de.getResultCode().intValue(), 1926 de.getMessageObject(), de.getMatchedDN(), de 1927 .getReferralURLs()); 1928 1929 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 1930 responseOp, bindOp.getResponseControls())); 1931 1932 // If it was a protocol error, then terminate the connection. 1933 if (de.getResultCode() == ResultCode.PROTOCOL_ERROR) 1934 { 1935 LocalizableMessage msg = 1936 ERR_LDAP_DISCONNECT_DUE_TO_BIND_PROTOCOL_ERROR.get(message 1937 .getMessageID(), de.getMessageObject()); 1938 disconnect(DisconnectReason.PROTOCOL_ERROR, true, msg); 1939 } 1940 } 1941 1942 return connectionValid; 1943 } 1944 1945 1946 1947 /** 1948 * Processes the provided LDAP message as a compare request. 1949 * 1950 * @param message 1951 * The LDAP message containing the compare request to 1952 * process. 1953 * @param controls 1954 * The set of pre-decoded request controls contained in the 1955 * message. 1956 * @return <CODE>true</CODE> if the request was processed 1957 * successfully, or <CODE>false</CODE> if not and the 1958 * connection has been closed as a result (it is the 1959 * responsibility of this method to close the connection). 1960 */ 1961 private boolean processCompareRequest(LDAPMessage message, List<Control> controls) 1962 { 1963 if (ldapVersion == 2 && controls != null && !controls.isEmpty()) 1964 { 1965 // LDAPv2 clients aren't allowed to send controls. 1966 CompareResponseProtocolOp responseOp = 1967 new CompareResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR, 1968 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get()); 1969 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 1970 responseOp)); 1971 disconnect(DisconnectReason.PROTOCOL_ERROR, false, 1972 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get()); 1973 return false; 1974 } 1975 1976 CompareRequestProtocolOp protocolOp = 1977 message.getCompareRequestProtocolOp(); 1978 CompareOperationBasis compareOp = 1979 new CompareOperationBasis(this, nextOperationID 1980 .getAndIncrement(), message.getMessageID(), controls, 1981 protocolOp.getDN(), protocolOp.getAttributeType(), 1982 protocolOp.getAssertionValue()); 1983 1984 // Add the operation into the work queue. 1985 try 1986 { 1987 addOperationInProgress(compareOp); 1988 } 1989 catch (DirectoryException de) 1990 { 1991 logger.traceException(de); 1992 1993 CompareResponseProtocolOp responseOp = 1994 new CompareResponseProtocolOp(de.getResultCode().intValue(), 1995 de.getMessageObject(), de.getMatchedDN(), de.getReferralURLs()); 1996 1997 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 1998 responseOp, compareOp.getResponseControls())); 1999 } 2000 2001 return connectionValid; 2002 } 2003 2004 2005 2006 /** 2007 * Processes the provided LDAP message as a delete request. 2008 * 2009 * @param message 2010 * The LDAP message containing the delete request to process. 2011 * @param controls 2012 * The set of pre-decoded request controls contained in the 2013 * message. 2014 * @return <CODE>true</CODE> if the request was processed 2015 * successfully, or <CODE>false</CODE> if not and the 2016 * connection has been closed as a result (it is the 2017 * responsibility of this method to close the connection). 2018 */ 2019 private boolean processDeleteRequest(LDAPMessage message, List<Control> controls) 2020 { 2021 if (ldapVersion == 2 && controls != null && !controls.isEmpty()) 2022 { 2023 // LDAPv2 clients aren't allowed to send controls. 2024 DeleteResponseProtocolOp responseOp = 2025 new DeleteResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR, 2026 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get()); 2027 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 2028 responseOp)); 2029 disconnect(DisconnectReason.PROTOCOL_ERROR, false, 2030 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get()); 2031 return false; 2032 } 2033 2034 DeleteRequestProtocolOp protocolOp = 2035 message.getDeleteRequestProtocolOp(); 2036 DeleteOperationBasis deleteOp = 2037 new DeleteOperationBasis(this, nextOperationID 2038 .getAndIncrement(), message.getMessageID(), controls, 2039 protocolOp.getDN()); 2040 2041 // Add the operation into the work queue. 2042 try 2043 { 2044 addOperationInProgress(deleteOp); 2045 } 2046 catch (DirectoryException de) 2047 { 2048 logger.traceException(de); 2049 2050 DeleteResponseProtocolOp responseOp = 2051 new DeleteResponseProtocolOp( 2052 de.getResultCode().intValue(), de.getMessageObject(), 2053 de.getMatchedDN(), de.getReferralURLs()); 2054 2055 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 2056 responseOp, deleteOp.getResponseControls())); 2057 } 2058 2059 return connectionValid; 2060 } 2061 2062 2063 2064 /** 2065 * Processes the provided LDAP message as an extended request. 2066 * 2067 * @param message 2068 * The LDAP message containing the extended request to 2069 * process. 2070 * @param controls 2071 * The set of pre-decoded request controls contained in the 2072 * message. 2073 * @return <CODE>true</CODE> if the request was processed 2074 * successfully, or <CODE>false</CODE> if not and the 2075 * connection has been closed as a result (it is the 2076 * responsibility of this method to close the connection). 2077 */ 2078 private boolean processExtendedRequest(LDAPMessage message, 2079 List<Control> controls) 2080 { 2081 // See if this is an LDAPv2 client. If it is, then they should not 2082 // be issuing extended requests. We can't send a response that we 2083 // can be sure they can understand, so we have no choice but to 2084 // close the connection. 2085 if (ldapVersion == 2) 2086 { 2087 LocalizableMessage msg = 2088 ERR_LDAPV2_EXTENDED_REQUEST_NOT_ALLOWED.get( 2089 getConnectionID(), message.getMessageID()); 2090 logger.error(msg); 2091 disconnect(DisconnectReason.PROTOCOL_ERROR, false, msg); 2092 return false; 2093 } 2094 2095 // FIXME -- Do we need to handle certain types of request here? 2096 // -- StartTLS requests 2097 // -- Cancel requests 2098 2099 ExtendedRequestProtocolOp protocolOp = 2100 message.getExtendedRequestProtocolOp(); 2101 ExtendedOperationBasis extendedOp = 2102 new ExtendedOperationBasis(this, nextOperationID 2103 .getAndIncrement(), message.getMessageID(), controls, 2104 protocolOp.getOID(), protocolOp.getValue()); 2105 2106 // Add the operation into the work queue. 2107 try 2108 { 2109 addOperationInProgress(extendedOp); 2110 } 2111 catch (DirectoryException de) 2112 { 2113 logger.traceException(de); 2114 2115 ExtendedResponseProtocolOp responseOp = 2116 new ExtendedResponseProtocolOp(de.getResultCode().intValue(), 2117 de.getMessageObject(), de.getMatchedDN(), de.getReferralURLs()); 2118 2119 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 2120 responseOp, extendedOp.getResponseControls())); 2121 } 2122 2123 return connectionValid; 2124 } 2125 2126 2127 2128 /** 2129 * Processes the provided LDAP message as a modify request. 2130 * 2131 * @param message 2132 * The LDAP message containing the modify request to process. 2133 * @param controls 2134 * The set of pre-decoded request controls contained in the 2135 * message. 2136 * @return <CODE>true</CODE> if the request was processed 2137 * successfully, or <CODE>false</CODE> if not and the 2138 * connection has been closed as a result (it is the 2139 * responsibility of this method to close the connection). 2140 */ 2141 private boolean processModifyRequest(LDAPMessage message, List<Control> controls) 2142 { 2143 if (ldapVersion == 2 && controls != null && !controls.isEmpty()) 2144 { 2145 // LDAPv2 clients aren't allowed to send controls. 2146 ModifyResponseProtocolOp responseOp = 2147 new ModifyResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR, 2148 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get()); 2149 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 2150 responseOp)); 2151 disconnect(DisconnectReason.PROTOCOL_ERROR, false, 2152 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get()); 2153 return false; 2154 } 2155 2156 ModifyRequestProtocolOp protocolOp = 2157 message.getModifyRequestProtocolOp(); 2158 ModifyOperationBasis modifyOp = 2159 new ModifyOperationBasis(this, nextOperationID 2160 .getAndIncrement(), message.getMessageID(), controls, 2161 protocolOp.getDN(), protocolOp.getModifications()); 2162 2163 // Add the operation into the work queue. 2164 try 2165 { 2166 addOperationInProgress(modifyOp); 2167 } 2168 catch (DirectoryException de) 2169 { 2170 logger.traceException(de); 2171 2172 ModifyResponseProtocolOp responseOp = 2173 new ModifyResponseProtocolOp( 2174 de.getResultCode().intValue(), de.getMessageObject(), 2175 de.getMatchedDN(), de.getReferralURLs()); 2176 2177 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 2178 responseOp, modifyOp.getResponseControls())); 2179 } 2180 2181 return connectionValid; 2182 } 2183 2184 2185 2186 /** 2187 * Processes the provided LDAP message as a modify DN request. 2188 * 2189 * @param message 2190 * The LDAP message containing the modify DN request to 2191 * process. 2192 * @param controls 2193 * The set of pre-decoded request controls contained in the 2194 * message. 2195 * @return <CODE>true</CODE> if the request was processed 2196 * successfully, or <CODE>false</CODE> if not and the 2197 * connection has been closed as a result (it is the 2198 * responsibility of this method to close the connection). 2199 */ 2200 private boolean processModifyDNRequest(LDAPMessage message, List<Control> controls) 2201 { 2202 if (ldapVersion == 2 && controls != null && !controls.isEmpty()) 2203 { 2204 // LDAPv2 clients aren't allowed to send controls. 2205 ModifyDNResponseProtocolOp responseOp = 2206 new ModifyDNResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR, 2207 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get()); 2208 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 2209 responseOp)); 2210 disconnect(DisconnectReason.PROTOCOL_ERROR, false, 2211 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get()); 2212 return false; 2213 } 2214 2215 ModifyDNRequestProtocolOp protocolOp = 2216 message.getModifyDNRequestProtocolOp(); 2217 ModifyDNOperationBasis modifyDNOp = 2218 new ModifyDNOperationBasis(this, nextOperationID 2219 .getAndIncrement(), message.getMessageID(), controls, 2220 protocolOp.getEntryDN(), protocolOp.getNewRDN(), protocolOp 2221 .deleteOldRDN(), protocolOp.getNewSuperior()); 2222 2223 // Add the operation into the work queue. 2224 try 2225 { 2226 addOperationInProgress(modifyDNOp); 2227 } 2228 catch (DirectoryException de) 2229 { 2230 logger.traceException(de); 2231 2232 ModifyDNResponseProtocolOp responseOp = 2233 new ModifyDNResponseProtocolOp(de.getResultCode().intValue(), 2234 de.getMessageObject(), de.getMatchedDN(), de.getReferralURLs()); 2235 2236 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 2237 responseOp, modifyDNOp.getResponseControls())); 2238 } 2239 2240 return connectionValid; 2241 } 2242 2243 2244 2245 /** 2246 * Processes the provided LDAP message as a search request. 2247 * 2248 * @param message 2249 * The LDAP message containing the search request to process. 2250 * @param controls 2251 * The set of pre-decoded request controls contained in the 2252 * message. 2253 * @return <CODE>true</CODE> if the request was processed 2254 * successfully, or <CODE>false</CODE> if not and the 2255 * connection has been closed as a result (it is the 2256 * responsibility of this method to close the connection). 2257 */ 2258 private boolean processSearchRequest(LDAPMessage message, 2259 List<Control> controls) 2260 { 2261 if (ldapVersion == 2 && controls != null && !controls.isEmpty()) 2262 { 2263 // LDAPv2 clients aren't allowed to send controls. 2264 SearchResultDoneProtocolOp responseOp = 2265 new SearchResultDoneProtocolOp(LDAPResultCode.PROTOCOL_ERROR, 2266 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get()); 2267 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 2268 responseOp)); 2269 disconnect(DisconnectReason.PROTOCOL_ERROR, false, 2270 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get()); 2271 return false; 2272 } 2273 2274 SearchRequestProtocolOp protocolOp = 2275 message.getSearchRequestProtocolOp(); 2276 SearchOperationBasis searchOp = 2277 new SearchOperationBasis(this, nextOperationID 2278 .getAndIncrement(), message.getMessageID(), controls, 2279 protocolOp.getBaseDN(), protocolOp.getScope(), protocolOp 2280 .getDereferencePolicy(), protocolOp.getSizeLimit(), 2281 protocolOp.getTimeLimit(), protocolOp.getTypesOnly(), 2282 protocolOp.getFilter(), protocolOp.getAttributes()); 2283 2284 // Add the operation into the work queue. 2285 try 2286 { 2287 addOperationInProgress(searchOp); 2288 } 2289 catch (DirectoryException de) 2290 { 2291 logger.traceException(de); 2292 2293 SearchResultDoneProtocolOp responseOp = 2294 new SearchResultDoneProtocolOp(de.getResultCode().intValue(), 2295 de.getMessageObject(), de.getMatchedDN(), de.getReferralURLs()); 2296 2297 sendLDAPMessage(new LDAPMessage(message.getMessageID(), 2298 responseOp, searchOp.getResponseControls())); 2299 } 2300 2301 return connectionValid; 2302 } 2303 2304 2305 2306 /** 2307 * Processes the provided LDAP message as an unbind request. 2308 * 2309 * @param message 2310 * The LDAP message containing the unbind request to process. 2311 * @param controls 2312 * The set of pre-decoded request controls contained in the 2313 * message. 2314 * @return <CODE>true</CODE> if the request was processed 2315 * successfully, or <CODE>false</CODE> if not and the 2316 * connection has been closed as a result (it is the 2317 * responsibility of this method to close the connection). 2318 */ 2319 private boolean processUnbindRequest(LDAPMessage message, 2320 List<Control> controls) 2321 { 2322 UnbindOperationBasis unbindOp = 2323 new UnbindOperationBasis(this, nextOperationID 2324 .getAndIncrement(), message.getMessageID(), controls); 2325 2326 unbindOp.run(); 2327 2328 // The client connection will never be valid after an unbind. 2329 return false; 2330 } 2331 2332 2333 2334 /** {@inheritDoc} */ 2335 @Override 2336 public String getMonitorSummary() 2337 { 2338 StringBuilder buffer = new StringBuilder(); 2339 buffer.append("connID=\""); 2340 buffer.append(connectionID); 2341 buffer.append("\" connectTime=\""); 2342 buffer.append(getConnectTimeString()); 2343 buffer.append("\" source=\""); 2344 buffer.append(clientAddress); 2345 buffer.append(":"); 2346 buffer.append(clientPort); 2347 buffer.append("\" destination=\""); 2348 buffer.append(serverAddress); 2349 buffer.append(":"); 2350 buffer.append(connectionHandler.getListenPort()); 2351 buffer.append("\" ldapVersion=\""); 2352 buffer.append(ldapVersion); 2353 buffer.append("\" authDN=\""); 2354 2355 DN authDN = getAuthenticationInfo().getAuthenticationDN(); 2356 if (authDN != null) 2357 { 2358 authDN.toString(buffer); 2359 } 2360 2361 buffer.append("\" security=\""); 2362 if (isSecure()) 2363 { 2364 if (tlsActiveProvider != null) 2365 { 2366 buffer.append(tlsActiveProvider.getName()); 2367 } 2368 if (saslActiveProvider != null) 2369 { 2370 if (tlsActiveProvider != null) 2371 { 2372 buffer.append(","); 2373 } 2374 buffer.append(saslActiveProvider.getName()); 2375 } 2376 } 2377 else 2378 { 2379 buffer.append("none"); 2380 } 2381 2382 buffer.append("\" opsInProgress=\""); 2383 buffer.append(operationsInProgress.size()); 2384 buffer.append("\""); 2385 2386 int countPSearch = getPersistentSearches().size(); 2387 if (countPSearch > 0) 2388 { 2389 buffer.append(" persistentSearches=\""); 2390 buffer.append(countPSearch); 2391 buffer.append("\""); 2392 } 2393 return buffer.toString(); 2394 } 2395 2396 2397 2398 /** 2399 * Appends a string representation of this client connection to the 2400 * provided buffer. 2401 * 2402 * @param buffer 2403 * The buffer to which the information should be appended. 2404 */ 2405 @Override 2406 public void toString(StringBuilder buffer) 2407 { 2408 buffer.append("LDAP client connection from "); 2409 buffer.append(clientAddress); 2410 buffer.append(":"); 2411 buffer.append(clientPort); 2412 buffer.append(" to "); 2413 buffer.append(serverAddress); 2414 buffer.append(":"); 2415 buffer.append(serverPort); 2416 } 2417 2418 2419 2420 /** {@inheritDoc} */ 2421 @Override 2422 public boolean prepareTLS(LocalizableMessageBuilder unavailableReason) 2423 { 2424 if (tlsActiveProvider != null) 2425 { 2426 unavailableReason.append(ERR_LDAP_TLS_EXISTING_SECURITY_PROVIDER 2427 .get(tlsActiveProvider.getName())); 2428 return false; 2429 } 2430 // Make sure that the connection handler allows the use of the 2431 // StartTLS operation. 2432 if (!connectionHandler.allowStartTLS()) 2433 { 2434 unavailableReason.append(ERR_LDAP_TLS_STARTTLS_NOT_ALLOWED.get()); 2435 return false; 2436 } 2437 try 2438 { 2439 TLSByteChannel tlsByteChannel = 2440 connectionHandler.getTLSByteChannel(timeoutClientChannel); 2441 setTLSPendingProvider(tlsByteChannel); 2442 } 2443 catch (DirectoryException de) 2444 { 2445 logger.traceException(de); 2446 unavailableReason.append(ERR_LDAP_TLS_CANNOT_CREATE_TLS_PROVIDER 2447 .get(stackTraceToSingleLineString(de))); 2448 return false; 2449 } 2450 return true; 2451 } 2452 2453 2454 2455 /** 2456 * Retrieves the length of time in milliseconds that this client 2457 * connection has been idle. <BR> 2458 * <BR> 2459 * Note that the default implementation will always return zero. 2460 * Subclasses associated with connection handlers should override this 2461 * method if they wish to provided idle time limit functionality. 2462 * 2463 * @return The length of time in milliseconds that this client 2464 * connection has been idle. 2465 */ 2466 @Override 2467 public long getIdleTime() 2468 { 2469 if (operationsInProgress.isEmpty() 2470 && getPersistentSearches().isEmpty()) 2471 { 2472 return TimeThread.getTime() - lastCompletionTime.get(); 2473 } 2474 else 2475 { 2476 // There's at least one operation in progress, so it's not idle. 2477 return 0L; 2478 } 2479 } 2480 2481 2482 2483 /** 2484 * Set the connection provider that is not in use yet. Used in TLS 2485 * negotiation when a clear response is needed before the connection 2486 * provider is active. 2487 * 2488 * @param provider 2489 * The provider that needs to be activated. 2490 */ 2491 public void setTLSPendingProvider(ConnectionSecurityProvider provider) 2492 { 2493 tlsPendingProvider = provider; 2494 } 2495 2496 2497 2498 /** 2499 * Set the connection provider that is not in use. Used in SASL 2500 * negotiation when a clear response is needed before the connection 2501 * provider is active. 2502 * 2503 * @param provider 2504 * The provider that needs to be activated. 2505 */ 2506 public void setSASLPendingProvider(ConnectionSecurityProvider provider) 2507 { 2508 saslPendingProvider = provider; 2509 } 2510 2511 2512 2513 /** 2514 * Enable the provider that is inactive. 2515 */ 2516 private void enableTLS() 2517 { 2518 tlsActiveProvider = tlsPendingProvider; 2519 tlsChannel.redirect(tlsPendingProvider); 2520 tlsPendingProvider = null; 2521 } 2522 2523 2524 2525 /** 2526 * Set the security provider to the specified provider. 2527 * 2528 * @param sslProvider 2529 * The provider to set the security provider to. 2530 */ 2531 private void enableSSL(ConnectionSecurityProvider sslProvider) 2532 { 2533 tlsActiveProvider = sslProvider; 2534 tlsChannel.redirect(sslProvider); 2535 } 2536 2537 2538 2539 /** 2540 * Enable the SASL provider that is currently inactive or pending. 2541 */ 2542 private void enableSASL() 2543 { 2544 saslActiveProvider = saslPendingProvider; 2545 saslChannel.redirect(saslPendingProvider); 2546 saslPendingProvider = null; 2547 } 2548 2549 2550 2551 /** 2552 * Return the certificate chain array associated with a connection. 2553 * 2554 * @return The array of certificates associated with a connection. 2555 */ 2556 public Certificate[] getClientCertificateChain() 2557 { 2558 if (tlsActiveProvider != null) 2559 { 2560 return tlsActiveProvider.getClientCertificateChain(); 2561 } 2562 if (saslActiveProvider != null) 2563 { 2564 return saslActiveProvider.getClientCertificateChain(); 2565 } 2566 return new Certificate[0]; 2567 } 2568 2569 2570 2571 /** 2572 * Retrieves the TLS redirecting byte channel used in a LDAP client 2573 * connection. 2574 * 2575 * @return The TLS redirecting byte channel. 2576 */ 2577 @Override 2578 public ByteChannel getChannel() { 2579 return this.tlsChannel; 2580 } 2581 2582 2583 2584 /** {@inheritDoc} */ 2585 @Override 2586 public int getSSF() 2587 { 2588 int tlsSSF = tlsActiveProvider != null ? tlsActiveProvider.getSSF() : 0; 2589 int saslSSF = saslActiveProvider != null ? saslActiveProvider.getSSF() : 0; 2590 return Math.max(tlsSSF, saslSSF); 2591 } 2592 2593 2594 2595 /** {@inheritDoc} */ 2596 @Override 2597 public void finishBindOrStartTLS() 2598 { 2599 if(this.tlsPendingProvider != null) 2600 { 2601 enableTLS(); 2602 } 2603 2604 if (this.saslPendingProvider != null) 2605 { 2606 enableSASL(); 2607 } 2608 2609 super.finishBindOrStartTLS(); 2610 } 2611}