001/* 002 * Copyright 2010-2014 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2010-2014 UnboundID Corp. 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.ldap.listener; 022 023 024 025import java.io.IOException; 026import java.io.OutputStream; 027import java.net.Socket; 028import java.util.ArrayList; 029import java.util.List; 030import java.util.concurrent.CopyOnWriteArrayList; 031import java.util.concurrent.atomic.AtomicBoolean; 032import javax.net.ssl.SSLSocket; 033import javax.net.ssl.SSLSocketFactory; 034 035import com.unboundid.asn1.ASN1Buffer; 036import com.unboundid.asn1.ASN1StreamReader; 037import com.unboundid.ldap.protocol.AddResponseProtocolOp; 038import com.unboundid.ldap.protocol.BindResponseProtocolOp; 039import com.unboundid.ldap.protocol.CompareResponseProtocolOp; 040import com.unboundid.ldap.protocol.DeleteResponseProtocolOp; 041import com.unboundid.ldap.protocol.ExtendedResponseProtocolOp; 042import com.unboundid.ldap.protocol.IntermediateResponseProtocolOp; 043import com.unboundid.ldap.protocol.LDAPMessage; 044import com.unboundid.ldap.protocol.ModifyResponseProtocolOp; 045import com.unboundid.ldap.protocol.ModifyDNResponseProtocolOp; 046import com.unboundid.ldap.protocol.SearchResultDoneProtocolOp; 047import com.unboundid.ldap.protocol.SearchResultEntryProtocolOp; 048import com.unboundid.ldap.protocol.SearchResultReferenceProtocolOp; 049import com.unboundid.ldap.sdk.Attribute; 050import com.unboundid.ldap.sdk.Control; 051import com.unboundid.ldap.sdk.Entry; 052import com.unboundid.ldap.sdk.ExtendedResult; 053import com.unboundid.ldap.sdk.LDAPException; 054import com.unboundid.ldap.sdk.LDAPRuntimeException; 055import com.unboundid.ldap.sdk.ResultCode; 056import com.unboundid.util.Debug; 057import com.unboundid.util.InternalUseOnly; 058import com.unboundid.util.ObjectPair; 059import com.unboundid.util.StaticUtils; 060import com.unboundid.util.ThreadSafety; 061import com.unboundid.util.ThreadSafetyLevel; 062import com.unboundid.util.Validator; 063import com.unboundid.util.ssl.SSLUtil; 064 065import static com.unboundid.ldap.listener.ListenerMessages.*; 066 067 068 069/** 070 * This class provides an object which will be used to represent a connection to 071 * a client accepted by an {@link LDAPListener}, although connections may also 072 * be created independently if they were accepted in some other way. Each 073 * connection has its own thread that will be used to read requests from the 074 * client, and connections created outside of an {@code LDAPListener} instance, 075 * then the thread must be explicitly started. 076 */ 077@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 078public final class LDAPListenerClientConnection 079 extends Thread 080{ 081 /** 082 * A pre-allocated empty array of controls. 083 */ 084 private static final Control[] EMPTY_CONTROL_ARRAY = new Control[0]; 085 086 087 088 // The buffer used to hold responses to be sent to the client. 089 private final ASN1Buffer asn1Buffer; 090 091 // The ASN.1 stream reader used to read requests from the client. 092 private volatile ASN1StreamReader asn1Reader; 093 094 // Indicates whether to suppress the next call to sendMessage to send a 095 // response to the client. 096 private final AtomicBoolean suppressNextResponse; 097 098 // The set of intermediate response transformers for this connection. 099 private final CopyOnWriteArrayList<IntermediateResponseTransformer> 100 intermediateResponseTransformers; 101 102 // The set of search result entry transformers for this connection. 103 private final CopyOnWriteArrayList<SearchEntryTransformer> 104 searchEntryTransformers; 105 106 // The set of search result reference transformers for this connection. 107 private final CopyOnWriteArrayList<SearchReferenceTransformer> 108 searchReferenceTransformers; 109 110 // The listener that accepted this connection. 111 private final LDAPListener listener; 112 113 // The exception handler to use for this connection, if any. 114 private final LDAPListenerExceptionHandler exceptionHandler; 115 116 // The request handler to use for this connection. 117 private final LDAPListenerRequestHandler requestHandler; 118 119 // The connection ID assigned to this connection. 120 private final long connectionID; 121 122 // The output stream used to write responses to the client. 123 private volatile OutputStream outputStream; 124 125 // The socket used to communicate with the client. 126 private volatile Socket socket; 127 128 129 130 /** 131 * Creates a new LDAP listener client connection that will communicate with 132 * the client using the provided socket. The {@link #start} method must be 133 * called to start listening for requests from the client. 134 * 135 * @param listener The listener that accepted this client 136 * connection. It may be {@code null} if this 137 * connection was not accepted by a listener. 138 * @param socket The socket that may be used to communicate with 139 * the client. It must not be {@code null}. 140 * @param requestHandler The request handler that will be used to process 141 * requests read from the client. The 142 * {@link LDAPListenerRequestHandler#newInstance} 143 * method will be called on the provided object to 144 * obtain a new instance to use for this connection. 145 * The provided request handler must not be 146 * {@code null}. 147 * @param exceptionHandler The disconnect handler to be notified when this 148 * connection is closed. It may be {@code null} if 149 * no disconnect handler should be used. 150 * 151 * @throws LDAPException If a problem occurs while preparing this client 152 * connection. for use. If this is thrown, then the 153 * provided socket will be closed. 154 */ 155 public LDAPListenerClientConnection(final LDAPListener listener, 156 final Socket socket, 157 final LDAPListenerRequestHandler requestHandler, 158 final LDAPListenerExceptionHandler exceptionHandler) 159 throws LDAPException 160 { 161 Validator.ensureNotNull(socket, requestHandler); 162 163 setName("LDAPListener client connection reader for connection from " + 164 socket.getInetAddress().getHostAddress() + ':' + 165 socket.getPort() + " to " + socket.getLocalAddress().getHostAddress() + 166 ':' + socket.getLocalPort()); 167 168 this.listener = listener; 169 this.socket = socket; 170 this.exceptionHandler = exceptionHandler; 171 172 intermediateResponseTransformers = 173 new CopyOnWriteArrayList<IntermediateResponseTransformer>(); 174 searchEntryTransformers = 175 new CopyOnWriteArrayList<SearchEntryTransformer>(); 176 searchReferenceTransformers = 177 new CopyOnWriteArrayList<SearchReferenceTransformer>(); 178 179 if (listener == null) 180 { 181 connectionID = -1L; 182 } 183 else 184 { 185 connectionID = listener.nextConnectionID(); 186 } 187 188 try 189 { 190 final LDAPListenerConfig config; 191 if (listener == null) 192 { 193 config = new LDAPListenerConfig(0, requestHandler); 194 } 195 else 196 { 197 config = listener.getConfig(); 198 } 199 200 socket.setKeepAlive(config.useKeepAlive()); 201 socket.setReuseAddress(config.useReuseAddress()); 202 socket.setSoLinger(config.useLinger(), config.getLingerTimeoutSeconds()); 203 socket.setTcpNoDelay(config.useTCPNoDelay()); 204 205 final int sendBufferSize = config.getSendBufferSize(); 206 if (sendBufferSize > 0) 207 { 208 socket.setSendBufferSize(sendBufferSize); 209 } 210 211 asn1Reader = new ASN1StreamReader(socket.getInputStream()); 212 } 213 catch (final IOException ioe) 214 { 215 Debug.debugException(ioe); 216 217 try 218 { 219 socket.close(); 220 } 221 catch (final Exception e) 222 { 223 Debug.debugException(e); 224 } 225 226 throw new LDAPException(ResultCode.CONNECT_ERROR, 227 ERR_CONN_CREATE_IO_EXCEPTION.get( 228 StaticUtils.getExceptionMessage(ioe)), 229 ioe); 230 } 231 232 try 233 { 234 outputStream = socket.getOutputStream(); 235 } 236 catch (final IOException ioe) 237 { 238 Debug.debugException(ioe); 239 240 try 241 { 242 asn1Reader.close(); 243 } 244 catch (final Exception e) 245 { 246 Debug.debugException(e); 247 } 248 249 try 250 { 251 socket.close(); 252 } 253 catch (final Exception e) 254 { 255 Debug.debugException(e); 256 } 257 258 throw new LDAPException(ResultCode.CONNECT_ERROR, 259 ERR_CONN_CREATE_IO_EXCEPTION.get( 260 StaticUtils.getExceptionMessage(ioe)), 261 ioe); 262 } 263 264 try 265 { 266 this.requestHandler = requestHandler.newInstance(this); 267 } 268 catch (final LDAPException le) 269 { 270 Debug.debugException(le); 271 272 try 273 { 274 asn1Reader.close(); 275 } 276 catch (final Exception e) 277 { 278 Debug.debugException(e); 279 } 280 281 try 282 { 283 outputStream.close(); 284 } 285 catch (final Exception e) 286 { 287 Debug.debugException(e); 288 } 289 290 try 291 { 292 socket.close(); 293 } 294 catch (final Exception e) 295 { 296 Debug.debugException(e); 297 } 298 299 throw le; 300 } 301 302 asn1Buffer = new ASN1Buffer(); 303 suppressNextResponse = new AtomicBoolean(false); 304 } 305 306 307 308 /** 309 * Closes the connection to the client. 310 * 311 * @throws IOException If a problem occurs while closing the socket. 312 */ 313 public synchronized void close() 314 throws IOException 315 { 316 try 317 { 318 requestHandler.closeInstance(); 319 } 320 catch (final Exception e) 321 { 322 Debug.debugException(e); 323 } 324 325 try 326 { 327 asn1Reader.close(); 328 } 329 catch (final Exception e) 330 { 331 Debug.debugException(e); 332 } 333 334 try 335 { 336 outputStream.close(); 337 } 338 catch (final Exception e) 339 { 340 Debug.debugException(e); 341 } 342 343 socket.close(); 344 } 345 346 347 348 /** 349 * Closes the connection to the client as a result of an exception encountered 350 * during processing. Any associated exception handler will be notified 351 * prior to the connection closure. 352 * 353 * @param le The exception providing information about the reason that this 354 * connection will be terminated. 355 */ 356 void close(final LDAPException le) 357 { 358 if (exceptionHandler == null) 359 { 360 Debug.debugException(le); 361 } 362 else 363 { 364 try 365 { 366 exceptionHandler.connectionTerminated(this, le); 367 } 368 catch (final Exception e) 369 { 370 Debug.debugException(e); 371 } 372 } 373 374 try 375 { 376 close(); 377 } 378 catch (final Exception e) 379 { 380 Debug.debugException(e); 381 } 382 } 383 384 385 386 /** 387 * Operates in a loop, waiting for a request to arrive from the client and 388 * handing it off to the request handler for processing. This method is for 389 * internal use only and must not be invoked by external callers. 390 */ 391 @InternalUseOnly() 392 @Override() 393 public void run() 394 { 395 try 396 { 397 while (true) 398 { 399 final LDAPMessage requestMessage; 400 try 401 { 402 requestMessage = LDAPMessage.readFrom(asn1Reader, false); 403 if (requestMessage == null) 404 { 405 // This indicates that the client has closed the connection without 406 // an unbind request. It's not all that nice, but it isn't an error 407 // so we won't notify the exception handler. 408 try 409 { 410 close(); 411 } 412 catch (final IOException ioe) 413 { 414 Debug.debugException(ioe); 415 } 416 417 return; 418 } 419 } 420 catch (final LDAPException le) 421 { 422 Debug.debugException(le); 423 close(le); 424 return; 425 } 426 427 try 428 { 429 final int messageID = requestMessage.getMessageID(); 430 final List<Control> controls = requestMessage.getControls(); 431 432 LDAPMessage responseMessage; 433 switch (requestMessage.getProtocolOpType()) 434 { 435 case LDAPMessage.PROTOCOL_OP_TYPE_ABANDON_REQUEST: 436 requestHandler.processAbandonRequest(messageID, 437 requestMessage.getAbandonRequestProtocolOp(), controls); 438 responseMessage = null; 439 break; 440 441 case LDAPMessage.PROTOCOL_OP_TYPE_ADD_REQUEST: 442 try 443 { 444 responseMessage = requestHandler.processAddRequest(messageID, 445 requestMessage.getAddRequestProtocolOp(), controls); 446 } 447 catch (final Exception e) 448 { 449 Debug.debugException(e); 450 responseMessage = new LDAPMessage(messageID, 451 new AddResponseProtocolOp( 452 ResultCode.OTHER_INT_VALUE, null, 453 ERR_CONN_REQUEST_HANDLER_FAILURE.get( 454 StaticUtils.getExceptionMessage(e)), 455 null)); 456 } 457 break; 458 459 case LDAPMessage.PROTOCOL_OP_TYPE_BIND_REQUEST: 460 try 461 { 462 responseMessage = requestHandler.processBindRequest(messageID, 463 requestMessage.getBindRequestProtocolOp(), controls); 464 } 465 catch (final Exception e) 466 { 467 Debug.debugException(e); 468 responseMessage = new LDAPMessage(messageID, 469 new BindResponseProtocolOp( 470 ResultCode.OTHER_INT_VALUE, null, 471 ERR_CONN_REQUEST_HANDLER_FAILURE.get( 472 StaticUtils.getExceptionMessage(e)), 473 null, null)); 474 } 475 break; 476 477 case LDAPMessage.PROTOCOL_OP_TYPE_COMPARE_REQUEST: 478 try 479 { 480 responseMessage = requestHandler.processCompareRequest( 481 messageID, requestMessage.getCompareRequestProtocolOp(), 482 controls); 483 } 484 catch (final Exception e) 485 { 486 Debug.debugException(e); 487 responseMessage = new LDAPMessage(messageID, 488 new CompareResponseProtocolOp( 489 ResultCode.OTHER_INT_VALUE, null, 490 ERR_CONN_REQUEST_HANDLER_FAILURE.get( 491 StaticUtils.getExceptionMessage(e)), 492 null)); 493 } 494 break; 495 496 case LDAPMessage.PROTOCOL_OP_TYPE_DELETE_REQUEST: 497 try 498 { 499 responseMessage = requestHandler.processDeleteRequest(messageID, 500 requestMessage.getDeleteRequestProtocolOp(), controls); 501 } 502 catch (final Exception e) 503 { 504 Debug.debugException(e); 505 responseMessage = new LDAPMessage(messageID, 506 new DeleteResponseProtocolOp( 507 ResultCode.OTHER_INT_VALUE, null, 508 ERR_CONN_REQUEST_HANDLER_FAILURE.get( 509 StaticUtils.getExceptionMessage(e)), 510 null)); 511 } 512 break; 513 514 case LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_REQUEST: 515 try 516 { 517 responseMessage = requestHandler.processExtendedRequest( 518 messageID, requestMessage.getExtendedRequestProtocolOp(), 519 controls); 520 } 521 catch (final Exception e) 522 { 523 Debug.debugException(e); 524 responseMessage = new LDAPMessage(messageID, 525 new ExtendedResponseProtocolOp( 526 ResultCode.OTHER_INT_VALUE, null, 527 ERR_CONN_REQUEST_HANDLER_FAILURE.get( 528 StaticUtils.getExceptionMessage(e)), 529 null, null, null)); 530 } 531 break; 532 533 case LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_REQUEST: 534 try 535 { 536 responseMessage = requestHandler.processModifyRequest(messageID, 537 requestMessage.getModifyRequestProtocolOp(), controls); 538 } 539 catch (final Exception e) 540 { 541 Debug.debugException(e); 542 responseMessage = new LDAPMessage(messageID, 543 new ModifyResponseProtocolOp( 544 ResultCode.OTHER_INT_VALUE, null, 545 ERR_CONN_REQUEST_HANDLER_FAILURE.get( 546 StaticUtils.getExceptionMessage(e)), 547 null)); 548 } 549 break; 550 551 case LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_DN_REQUEST: 552 try 553 { 554 responseMessage = requestHandler.processModifyDNRequest( 555 messageID, requestMessage.getModifyDNRequestProtocolOp(), 556 controls); 557 } 558 catch (final Exception e) 559 { 560 Debug.debugException(e); 561 responseMessage = new LDAPMessage(messageID, 562 new ModifyDNResponseProtocolOp( 563 ResultCode.OTHER_INT_VALUE, null, 564 ERR_CONN_REQUEST_HANDLER_FAILURE.get( 565 StaticUtils.getExceptionMessage(e)), 566 null)); 567 } 568 break; 569 570 case LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_REQUEST: 571 try 572 { 573 responseMessage = requestHandler.processSearchRequest(messageID, 574 requestMessage.getSearchRequestProtocolOp(), controls); 575 } 576 catch (final Exception e) 577 { 578 Debug.debugException(e); 579 responseMessage = new LDAPMessage(messageID, 580 new SearchResultDoneProtocolOp( 581 ResultCode.OTHER_INT_VALUE, null, 582 ERR_CONN_REQUEST_HANDLER_FAILURE.get( 583 StaticUtils.getExceptionMessage(e)), 584 null)); 585 } 586 break; 587 588 case LDAPMessage.PROTOCOL_OP_TYPE_UNBIND_REQUEST: 589 requestHandler.processUnbindRequest(messageID, 590 requestMessage.getUnbindRequestProtocolOp(), controls); 591 close(); 592 return; 593 594 default: 595 close(new LDAPException(ResultCode.PROTOCOL_ERROR, 596 ERR_CONN_INVALID_PROTOCOL_OP_TYPE.get(StaticUtils.toHex( 597 requestMessage.getProtocolOpType())))); 598 return; 599 } 600 601 if (responseMessage != null) 602 { 603 try 604 { 605 sendMessage(responseMessage); 606 } 607 catch (final LDAPException le) 608 { 609 Debug.debugException(le); 610 close(le); 611 return; 612 } 613 } 614 } 615 catch (final Exception e) 616 { 617 close(new LDAPException(ResultCode.LOCAL_ERROR, 618 ERR_CONN_EXCEPTION_IN_REQUEST_HANDLER.get( 619 String.valueOf(requestMessage), 620 StaticUtils.getExceptionMessage(e)))); 621 return; 622 } 623 } 624 } 625 finally 626 { 627 if (listener != null) 628 { 629 listener.connectionClosed(this); 630 } 631 } 632 } 633 634 635 636 /** 637 * Sends the provided message to the client. 638 * 639 * @param message The message to be written to the client. 640 * 641 * @throws LDAPException If a problem occurs while attempting to send the 642 * response to the client. 643 */ 644 private synchronized void sendMessage(final LDAPMessage message) 645 throws LDAPException 646 { 647 // If we should suppress this response (which will only be because the 648 // response has already been sent through some other means, for example as 649 // part of StartTLS processing), then do so. 650 if (suppressNextResponse.compareAndSet(true, false)) 651 { 652 return; 653 } 654 655 asn1Buffer.clear(); 656 657 try 658 { 659 message.writeTo(asn1Buffer); 660 } 661 catch (final LDAPRuntimeException lre) 662 { 663 Debug.debugException(lre); 664 lre.throwLDAPException(); 665 } 666 667 try 668 { 669 asn1Buffer.writeTo(outputStream); 670 } 671 catch (final IOException ioe) 672 { 673 Debug.debugException(ioe); 674 675 throw new LDAPException(ResultCode.LOCAL_ERROR, 676 ERR_CONN_SEND_MESSAGE_EXCEPTION.get( 677 StaticUtils.getExceptionMessage(ioe)), 678 ioe); 679 } 680 finally 681 { 682 if (asn1Buffer.zeroBufferOnClear()) 683 { 684 asn1Buffer.clear(); 685 } 686 } 687 } 688 689 690 691 /** 692 * Sends a search result entry message to the client with the provided 693 * information. 694 * 695 * @param messageID The message ID for the LDAP message to send to the 696 * client. It must match the message ID of the associated 697 * search request. 698 * @param protocolOp The search result entry protocol op to include in the 699 * LDAP message to send to the client. It must not be 700 * {@code null}. 701 * @param controls The set of controls to include in the response message. 702 * It may be empty or {@code null} if no controls should 703 * be included. 704 * 705 * @throws LDAPException If a problem occurs while attempting to send the 706 * provided response message. If an exception is 707 * thrown, then the client connection will have been 708 * terminated. 709 */ 710 public void sendSearchResultEntry(final int messageID, 711 final SearchResultEntryProtocolOp protocolOp, 712 final Control... controls) 713 throws LDAPException 714 { 715 if (searchEntryTransformers.isEmpty()) 716 { 717 sendMessage(new LDAPMessage(messageID, protocolOp, controls)); 718 } 719 else 720 { 721 Control[] c; 722 SearchResultEntryProtocolOp op = protocolOp; 723 if (controls == null) 724 { 725 c = EMPTY_CONTROL_ARRAY; 726 } 727 else 728 { 729 c = controls; 730 } 731 732 for (final SearchEntryTransformer t : searchEntryTransformers) 733 { 734 try 735 { 736 final ObjectPair<SearchResultEntryProtocolOp,Control[]> p = 737 t.transformEntry(messageID, op, c); 738 if (p == null) 739 { 740 return; 741 } 742 743 op = p.getFirst(); 744 c = p.getSecond(); 745 } 746 catch (final Exception e) 747 { 748 Debug.debugException(e); 749 sendMessage(new LDAPMessage(messageID, protocolOp, c)); 750 throw new LDAPException(ResultCode.LOCAL_ERROR, 751 ERR_CONN_SEARCH_ENTRY_TRANSFORMER_EXCEPTION.get( 752 t.getClass().getName(), String.valueOf(op), 753 StaticUtils.getExceptionMessage(e)), 754 e); 755 } 756 } 757 758 sendMessage(new LDAPMessage(messageID, op, c)); 759 } 760 } 761 762 763 764 /** 765 * Sends a search result entry message to the client with the provided 766 * information. 767 * 768 * @param messageID The message ID for the LDAP message to send to the 769 * client. It must match the message ID of the associated 770 * search request. 771 * @param entry The entry to return to the client. It must not be 772 * {@code null}. 773 * @param controls The set of controls to include in the response message. 774 * It may be empty or {@code null} if no controls should be 775 * included. 776 * 777 * @throws LDAPException If a problem occurs while attempting to send the 778 * provided response message. If an exception is 779 * thrown, then the client connection will have been 780 * terminated. 781 */ 782 public void sendSearchResultEntry(final int messageID, final Entry entry, 783 final Control... controls) 784 throws LDAPException 785 { 786 sendSearchResultEntry(messageID, 787 new SearchResultEntryProtocolOp(entry.getDN(), 788 new ArrayList<Attribute>(entry.getAttributes())), 789 controls); 790 } 791 792 793 794 /** 795 * Sends a search result reference message to the client with the provided 796 * information. 797 * 798 * @param messageID The message ID for the LDAP message to send to the 799 * client. It must match the message ID of the associated 800 * search request. 801 * @param protocolOp The search result reference protocol op to include in 802 * the LDAP message to send to the client. 803 * @param controls The set of controls to include in the response message. 804 * It may be empty or {@code null} if no controls should 805 * be included. 806 * 807 * @throws LDAPException If a problem occurs while attempting to send the 808 * provided response message. If an exception is 809 * thrown, then the client connection will have been 810 * terminated. 811 */ 812 public void sendSearchResultReference(final int messageID, 813 final SearchResultReferenceProtocolOp protocolOp, 814 final Control... controls) 815 throws LDAPException 816 { 817 if (searchReferenceTransformers.isEmpty()) 818 { 819 sendMessage(new LDAPMessage(messageID, protocolOp, controls)); 820 } 821 else 822 { 823 Control[] c; 824 SearchResultReferenceProtocolOp op = protocolOp; 825 if (controls == null) 826 { 827 c = EMPTY_CONTROL_ARRAY; 828 } 829 else 830 { 831 c = controls; 832 } 833 834 for (final SearchReferenceTransformer t : searchReferenceTransformers) 835 { 836 try 837 { 838 final ObjectPair<SearchResultReferenceProtocolOp,Control[]> p = 839 t.transformReference(messageID, op, c); 840 if (p == null) 841 { 842 return; 843 } 844 845 op = p.getFirst(); 846 c = p.getSecond(); 847 } 848 catch (final Exception e) 849 { 850 Debug.debugException(e); 851 sendMessage(new LDAPMessage(messageID, protocolOp, c)); 852 throw new LDAPException(ResultCode.LOCAL_ERROR, 853 ERR_CONN_SEARCH_REFERENCE_TRANSFORMER_EXCEPTION.get( 854 t.getClass().getName(), String.valueOf(op), 855 StaticUtils.getExceptionMessage(e)), 856 e); 857 } 858 } 859 860 sendMessage(new LDAPMessage(messageID, op, c)); 861 } 862 } 863 864 865 866 /** 867 * Sends an intermediate response message to the client with the provided 868 * information. 869 * 870 * @param messageID The message ID for the LDAP message to send to the 871 * client. It must match the message ID of the associated 872 * search request. 873 * @param protocolOp The intermediate response protocol op to include in the 874 * LDAP message to send to the client. 875 * @param controls The set of controls to include in the response message. 876 * It may be empty or {@code null} if no controls should 877 * be included. 878 * 879 * @throws LDAPException If a problem occurs while attempting to send the 880 * provided response message. If an exception is 881 * thrown, then the client connection will have been 882 * terminated. 883 */ 884 public void sendIntermediateResponse(final int messageID, 885 final IntermediateResponseProtocolOp protocolOp, 886 final Control... controls) 887 throws LDAPException 888 { 889 if (intermediateResponseTransformers.isEmpty()) 890 { 891 sendMessage(new LDAPMessage(messageID, protocolOp, controls)); 892 } 893 else 894 { 895 Control[] c; 896 IntermediateResponseProtocolOp op = protocolOp; 897 if (controls == null) 898 { 899 c = EMPTY_CONTROL_ARRAY; 900 } 901 else 902 { 903 c = controls; 904 } 905 906 for (final IntermediateResponseTransformer t : 907 intermediateResponseTransformers) 908 { 909 try 910 { 911 final ObjectPair<IntermediateResponseProtocolOp,Control[]> p = 912 t.transformIntermediateResponse(messageID, op, c); 913 if (p == null) 914 { 915 return; 916 } 917 918 op = p.getFirst(); 919 c = p.getSecond(); 920 } 921 catch (final Exception e) 922 { 923 Debug.debugException(e); 924 sendMessage(new LDAPMessage(messageID, protocolOp, c)); 925 throw new LDAPException(ResultCode.LOCAL_ERROR, 926 ERR_CONN_INTERMEDIATE_RESPONSE_TRANSFORMER_EXCEPTION.get( 927 t.getClass().getName(), String.valueOf(op), 928 StaticUtils.getExceptionMessage(e)), 929 e); 930 } 931 } 932 933 sendMessage(new LDAPMessage(messageID, op, c)); 934 } 935 } 936 937 938 939 /** 940 * Sends an unsolicited notification message to the client with the provided 941 * extended result. 942 * 943 * @param result The extended result to use for the unsolicited 944 * notification. 945 * 946 * @throws LDAPException If a problem occurs while attempting to send the 947 * unsolicited notification. If an exception is 948 * thrown, then the client connection will have been 949 * terminated. 950 */ 951 public void sendUnsolicitedNotification(final ExtendedResult result) 952 throws LDAPException 953 { 954 sendUnsolicitedNotification( 955 new ExtendedResponseProtocolOp(result.getResultCode().intValue(), 956 result.getMatchedDN(), result.getDiagnosticMessage(), 957 StaticUtils.toList(result.getReferralURLs()), result.getOID(), 958 result.getValue()), 959 result.getResponseControls() 960 ); 961 } 962 963 964 965 /** 966 * Sends an unsolicited notification message to the client with the provided 967 * information. 968 * 969 * @param extendedResponse The extended response to use for the unsolicited 970 * notification. 971 * @param controls The set of controls to include with the 972 * unsolicited notification. It may be empty or 973 * {@code null} if no controls should be included. 974 * 975 * @throws LDAPException If a problem occurs while attempting to send the 976 * unsolicited notification. If an exception is 977 * thrown, then the client connection will have been 978 * terminated. 979 */ 980 public void sendUnsolicitedNotification( 981 final ExtendedResponseProtocolOp extendedResponse, 982 final Control... controls) 983 throws LDAPException 984 { 985 sendMessage(new LDAPMessage(0, extendedResponse, controls)); 986 } 987 988 989 990 /** 991 * Retrieves the socket used to communicate with the client. 992 * 993 * @return The socket used to communicate with the client. 994 */ 995 public synchronized Socket getSocket() 996 { 997 return socket; 998 } 999 1000 1001 1002 /** 1003 * Attempts to convert this unencrypted connection to one that uses TLS 1004 * encryption, as would be used during the course of invoking the StartTLS 1005 * extended operation. If this is called, then the response that would have 1006 * been returned from the associated request will be suppressed, so the 1007 * returned output stream must be used to send the appropriate response to 1008 * the client. 1009 * 1010 * @param f The SSL socket factory that will be used to convert the existing 1011 * {@code Socket} to an {@code SSLSocket}. 1012 * 1013 * @return An output stream that can be used to send a clear-text message to 1014 * the client (e.g., the StartTLS response message). 1015 * 1016 * @throws LDAPException If a problem is encountered while trying to convert 1017 * the existing socket to an SSL socket. If this is 1018 * thrown, then the connection will have been closed. 1019 */ 1020 public synchronized OutputStream convertToTLS(final SSLSocketFactory f) 1021 throws LDAPException 1022 { 1023 final OutputStream clearOutputStream = outputStream; 1024 1025 final Socket origSocket = socket; 1026 final String hostname = origSocket.getInetAddress().getHostName(); 1027 final int port = origSocket.getPort(); 1028 1029 try 1030 { 1031 synchronized (f) 1032 { 1033 socket = f.createSocket(socket, hostname, port, true); 1034 SSLUtil.applyEnabledSSLProtocols(socket); 1035 } 1036 ((SSLSocket) socket).setUseClientMode(false); 1037 outputStream = socket.getOutputStream(); 1038 asn1Reader = new ASN1StreamReader(socket.getInputStream()); 1039 suppressNextResponse.set(true); 1040 return clearOutputStream; 1041 } 1042 catch (final Exception e) 1043 { 1044 Debug.debugException(e); 1045 1046 final LDAPException le = new LDAPException(ResultCode.LOCAL_ERROR, 1047 ERR_CONN_CONVERT_TO_TLS_FAILURE.get( 1048 StaticUtils.getExceptionMessage(e)), 1049 e); 1050 1051 close(le); 1052 1053 throw le; 1054 } 1055 } 1056 1057 1058 1059 /** 1060 * Retrieves the connection ID that has been assigned to this connection by 1061 * the associated listener. 1062 * 1063 * @return The connection ID that has been assigned to this connection by 1064 * the associated listener, or -1 if it is not associated with a 1065 * listener. 1066 */ 1067 public long getConnectionID() 1068 { 1069 return connectionID; 1070 } 1071 1072 1073 1074 /** 1075 * Adds the provided search entry transformer to this client connection. 1076 * 1077 * @param t A search entry transformer to be used to intercept and/or alter 1078 * search result entries before they are returned to the client. 1079 */ 1080 public void addSearchEntryTransformer(final SearchEntryTransformer t) 1081 { 1082 searchEntryTransformers.add(t); 1083 } 1084 1085 1086 1087 /** 1088 * Removes the provided search entry transformer from this client connection. 1089 * 1090 * @param t The search entry transformer to be removed. 1091 */ 1092 public void removeSearchEntryTransformer(final SearchEntryTransformer t) 1093 { 1094 searchEntryTransformers.remove(t); 1095 } 1096 1097 1098 1099 /** 1100 * Adds the provided search reference transformer to this client connection. 1101 * 1102 * @param t A search reference transformer to be used to intercept and/or 1103 * alter search result references before they are returned to the 1104 * client. 1105 */ 1106 public void addSearchReferenceTransformer(final SearchReferenceTransformer t) 1107 { 1108 searchReferenceTransformers.add(t); 1109 } 1110 1111 1112 1113 /** 1114 * Removes the provided search reference transformer from this client 1115 * connection. 1116 * 1117 * @param t The search reference transformer to be removed. 1118 */ 1119 public void removeSearchReferenceTransformer( 1120 final SearchReferenceTransformer t) 1121 { 1122 searchReferenceTransformers.remove(t); 1123 } 1124 1125 1126 1127 /** 1128 * Adds the provided intermediate response transformer to this client 1129 * connection. 1130 * 1131 * @param t An intermediate response transformer to be used to intercept 1132 * and/or alter intermediate responses before they are returned to 1133 * the client. 1134 */ 1135 public void addIntermediateResponseTransformer( 1136 final IntermediateResponseTransformer t) 1137 { 1138 intermediateResponseTransformers.add(t); 1139 } 1140 1141 1142 1143 /** 1144 * Removes the provided intermediate response transformer from this client 1145 * connection. 1146 * 1147 * @param t The intermediate response transformer to be removed. 1148 */ 1149 public void removeIntermediateResponseTransformer( 1150 final IntermediateResponseTransformer t) 1151 { 1152 intermediateResponseTransformers.remove(t); 1153 } 1154}