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