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