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