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 Debug.debugException(le); 435 close(le); 436 return; 437 } 438 439 try 440 { 441 final int messageID = requestMessage.getMessageID(); 442 final List<Control> controls = requestMessage.getControls(); 443 444 LDAPMessage responseMessage; 445 switch (requestMessage.getProtocolOpType()) 446 { 447 case LDAPMessage.PROTOCOL_OP_TYPE_ABANDON_REQUEST: 448 requestHandler.processAbandonRequest(messageID, 449 requestMessage.getAbandonRequestProtocolOp(), controls); 450 responseMessage = null; 451 break; 452 453 case LDAPMessage.PROTOCOL_OP_TYPE_ADD_REQUEST: 454 try 455 { 456 responseMessage = requestHandler.processAddRequest(messageID, 457 requestMessage.getAddRequestProtocolOp(), controls); 458 } 459 catch (final Exception e) 460 { 461 Debug.debugException(e); 462 responseMessage = new LDAPMessage(messageID, 463 new AddResponseProtocolOp( 464 ResultCode.OTHER_INT_VALUE, null, 465 ERR_CONN_REQUEST_HANDLER_FAILURE.get( 466 StaticUtils.getExceptionMessage(e)), 467 null)); 468 } 469 break; 470 471 case LDAPMessage.PROTOCOL_OP_TYPE_BIND_REQUEST: 472 try 473 { 474 responseMessage = requestHandler.processBindRequest(messageID, 475 requestMessage.getBindRequestProtocolOp(), controls); 476 } 477 catch (final Exception e) 478 { 479 Debug.debugException(e); 480 responseMessage = new LDAPMessage(messageID, 481 new BindResponseProtocolOp( 482 ResultCode.OTHER_INT_VALUE, null, 483 ERR_CONN_REQUEST_HANDLER_FAILURE.get( 484 StaticUtils.getExceptionMessage(e)), 485 null, null)); 486 } 487 break; 488 489 case LDAPMessage.PROTOCOL_OP_TYPE_COMPARE_REQUEST: 490 try 491 { 492 responseMessage = requestHandler.processCompareRequest( 493 messageID, requestMessage.getCompareRequestProtocolOp(), 494 controls); 495 } 496 catch (final Exception e) 497 { 498 Debug.debugException(e); 499 responseMessage = new LDAPMessage(messageID, 500 new CompareResponseProtocolOp( 501 ResultCode.OTHER_INT_VALUE, null, 502 ERR_CONN_REQUEST_HANDLER_FAILURE.get( 503 StaticUtils.getExceptionMessage(e)), 504 null)); 505 } 506 break; 507 508 case LDAPMessage.PROTOCOL_OP_TYPE_DELETE_REQUEST: 509 try 510 { 511 responseMessage = requestHandler.processDeleteRequest(messageID, 512 requestMessage.getDeleteRequestProtocolOp(), controls); 513 } 514 catch (final Exception e) 515 { 516 Debug.debugException(e); 517 responseMessage = new LDAPMessage(messageID, 518 new DeleteResponseProtocolOp( 519 ResultCode.OTHER_INT_VALUE, null, 520 ERR_CONN_REQUEST_HANDLER_FAILURE.get( 521 StaticUtils.getExceptionMessage(e)), 522 null)); 523 } 524 break; 525 526 case LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_REQUEST: 527 try 528 { 529 responseMessage = requestHandler.processExtendedRequest( 530 messageID, requestMessage.getExtendedRequestProtocolOp(), 531 controls); 532 } 533 catch (final Exception e) 534 { 535 Debug.debugException(e); 536 responseMessage = new LDAPMessage(messageID, 537 new ExtendedResponseProtocolOp( 538 ResultCode.OTHER_INT_VALUE, null, 539 ERR_CONN_REQUEST_HANDLER_FAILURE.get( 540 StaticUtils.getExceptionMessage(e)), 541 null, null, null)); 542 } 543 break; 544 545 case LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_REQUEST: 546 try 547 { 548 responseMessage = requestHandler.processModifyRequest(messageID, 549 requestMessage.getModifyRequestProtocolOp(), controls); 550 } 551 catch (final Exception e) 552 { 553 Debug.debugException(e); 554 responseMessage = new LDAPMessage(messageID, 555 new ModifyResponseProtocolOp( 556 ResultCode.OTHER_INT_VALUE, null, 557 ERR_CONN_REQUEST_HANDLER_FAILURE.get( 558 StaticUtils.getExceptionMessage(e)), 559 null)); 560 } 561 break; 562 563 case LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_DN_REQUEST: 564 try 565 { 566 responseMessage = requestHandler.processModifyDNRequest( 567 messageID, requestMessage.getModifyDNRequestProtocolOp(), 568 controls); 569 } 570 catch (final Exception e) 571 { 572 Debug.debugException(e); 573 responseMessage = new LDAPMessage(messageID, 574 new ModifyDNResponseProtocolOp( 575 ResultCode.OTHER_INT_VALUE, null, 576 ERR_CONN_REQUEST_HANDLER_FAILURE.get( 577 StaticUtils.getExceptionMessage(e)), 578 null)); 579 } 580 break; 581 582 case LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_REQUEST: 583 try 584 { 585 responseMessage = requestHandler.processSearchRequest(messageID, 586 requestMessage.getSearchRequestProtocolOp(), controls); 587 } 588 catch (final Exception e) 589 { 590 Debug.debugException(e); 591 responseMessage = new LDAPMessage(messageID, 592 new SearchResultDoneProtocolOp( 593 ResultCode.OTHER_INT_VALUE, null, 594 ERR_CONN_REQUEST_HANDLER_FAILURE.get( 595 StaticUtils.getExceptionMessage(e)), 596 null)); 597 } 598 break; 599 600 case LDAPMessage.PROTOCOL_OP_TYPE_UNBIND_REQUEST: 601 requestHandler.processUnbindRequest(messageID, 602 requestMessage.getUnbindRequestProtocolOp(), controls); 603 close(); 604 return; 605 606 default: 607 close(new LDAPException(ResultCode.PROTOCOL_ERROR, 608 ERR_CONN_INVALID_PROTOCOL_OP_TYPE.get(StaticUtils.toHex( 609 requestMessage.getProtocolOpType())))); 610 return; 611 } 612 613 if (responseMessage != null) 614 { 615 try 616 { 617 sendMessage(responseMessage); 618 } 619 catch (final LDAPException le) 620 { 621 Debug.debugException(le); 622 close(le); 623 return; 624 } 625 } 626 } 627 catch (final Exception e) 628 { 629 close(new LDAPException(ResultCode.LOCAL_ERROR, 630 ERR_CONN_EXCEPTION_IN_REQUEST_HANDLER.get( 631 String.valueOf(requestMessage), 632 StaticUtils.getExceptionMessage(e)))); 633 return; 634 } 635 } 636 } 637 finally 638 { 639 if (listener != null) 640 { 641 listener.connectionClosed(this); 642 } 643 } 644 } 645 646 647 648 /** 649 * Sends the provided message to the client. 650 * 651 * @param message The message to be written to the client. 652 * 653 * @throws LDAPException If a problem occurs while attempting to send the 654 * response to the client. 655 */ 656 private synchronized void sendMessage(final LDAPMessage message) 657 throws LDAPException 658 { 659 // If we should suppress this response (which will only be because the 660 // response has already been sent through some other means, for example as 661 // part of StartTLS processing), then do so. 662 if (suppressNextResponse.compareAndSet(true, false)) 663 { 664 return; 665 } 666 667 asn1Buffer.clear(); 668 669 try 670 { 671 message.writeTo(asn1Buffer); 672 } 673 catch (final LDAPRuntimeException lre) 674 { 675 Debug.debugException(lre); 676 lre.throwLDAPException(); 677 } 678 679 try 680 { 681 asn1Buffer.writeTo(outputStream); 682 } 683 catch (final IOException ioe) 684 { 685 Debug.debugException(ioe); 686 687 throw new LDAPException(ResultCode.LOCAL_ERROR, 688 ERR_CONN_SEND_MESSAGE_EXCEPTION.get( 689 StaticUtils.getExceptionMessage(ioe)), 690 ioe); 691 } 692 finally 693 { 694 if (asn1Buffer.zeroBufferOnClear()) 695 { 696 asn1Buffer.clear(); 697 } 698 } 699 } 700 701 702 703 /** 704 * Sends a search result entry message to the client with the provided 705 * information. 706 * 707 * @param messageID The message ID for the LDAP message to send to the 708 * client. It must match the message ID of the associated 709 * search request. 710 * @param protocolOp The search result entry protocol op to include in the 711 * LDAP message to send to the client. It must not be 712 * {@code null}. 713 * @param controls The set of controls to include in the response message. 714 * It may be empty or {@code null} if no controls should 715 * be included. 716 * 717 * @throws LDAPException If a problem occurs while attempting to send the 718 * provided response message. If an exception is 719 * thrown, then the client connection will have been 720 * terminated. 721 */ 722 public void sendSearchResultEntry(final int messageID, 723 final SearchResultEntryProtocolOp protocolOp, 724 final Control... controls) 725 throws LDAPException 726 { 727 if (searchEntryTransformers.isEmpty()) 728 { 729 sendMessage(new LDAPMessage(messageID, protocolOp, controls)); 730 } 731 else 732 { 733 Control[] c; 734 SearchResultEntryProtocolOp op = protocolOp; 735 if (controls == null) 736 { 737 c = EMPTY_CONTROL_ARRAY; 738 } 739 else 740 { 741 c = controls; 742 } 743 744 for (final SearchEntryTransformer t : searchEntryTransformers) 745 { 746 try 747 { 748 final ObjectPair<SearchResultEntryProtocolOp,Control[]> p = 749 t.transformEntry(messageID, op, c); 750 if (p == null) 751 { 752 return; 753 } 754 755 op = p.getFirst(); 756 c = p.getSecond(); 757 } 758 catch (final Exception e) 759 { 760 Debug.debugException(e); 761 sendMessage(new LDAPMessage(messageID, protocolOp, c)); 762 throw new LDAPException(ResultCode.LOCAL_ERROR, 763 ERR_CONN_SEARCH_ENTRY_TRANSFORMER_EXCEPTION.get( 764 t.getClass().getName(), String.valueOf(op), 765 StaticUtils.getExceptionMessage(e)), 766 e); 767 } 768 } 769 770 sendMessage(new LDAPMessage(messageID, op, c)); 771 } 772 } 773 774 775 776 /** 777 * Sends a search result entry message to the client with the provided 778 * information. 779 * 780 * @param messageID The message ID for the LDAP message to send to the 781 * client. It must match the message ID of the associated 782 * search request. 783 * @param entry The entry to return to the client. It must not be 784 * {@code null}. 785 * @param controls The set of controls to include in the response message. 786 * It may be empty or {@code null} if no controls should be 787 * included. 788 * 789 * @throws LDAPException If a problem occurs while attempting to send the 790 * provided response message. If an exception is 791 * thrown, then the client connection will have been 792 * terminated. 793 */ 794 public void sendSearchResultEntry(final int messageID, final Entry entry, 795 final Control... controls) 796 throws LDAPException 797 { 798 sendSearchResultEntry(messageID, 799 new SearchResultEntryProtocolOp(entry.getDN(), 800 new ArrayList<Attribute>(entry.getAttributes())), 801 controls); 802 } 803 804 805 806 /** 807 * Sends a search result reference message to the client with the provided 808 * information. 809 * 810 * @param messageID The message ID for the LDAP message to send to the 811 * client. It must match the message ID of the associated 812 * search request. 813 * @param protocolOp The search result reference protocol op to include in 814 * the LDAP message to send to the client. 815 * @param controls The set of controls to include in the response message. 816 * It may be empty or {@code null} if no controls should 817 * be included. 818 * 819 * @throws LDAPException If a problem occurs while attempting to send the 820 * provided response message. If an exception is 821 * thrown, then the client connection will have been 822 * terminated. 823 */ 824 public void sendSearchResultReference(final int messageID, 825 final SearchResultReferenceProtocolOp protocolOp, 826 final Control... controls) 827 throws LDAPException 828 { 829 if (searchReferenceTransformers.isEmpty()) 830 { 831 sendMessage(new LDAPMessage(messageID, protocolOp, controls)); 832 } 833 else 834 { 835 Control[] c; 836 SearchResultReferenceProtocolOp op = protocolOp; 837 if (controls == null) 838 { 839 c = EMPTY_CONTROL_ARRAY; 840 } 841 else 842 { 843 c = controls; 844 } 845 846 for (final SearchReferenceTransformer t : searchReferenceTransformers) 847 { 848 try 849 { 850 final ObjectPair<SearchResultReferenceProtocolOp,Control[]> p = 851 t.transformReference(messageID, op, c); 852 if (p == null) 853 { 854 return; 855 } 856 857 op = p.getFirst(); 858 c = p.getSecond(); 859 } 860 catch (final Exception e) 861 { 862 Debug.debugException(e); 863 sendMessage(new LDAPMessage(messageID, protocolOp, c)); 864 throw new LDAPException(ResultCode.LOCAL_ERROR, 865 ERR_CONN_SEARCH_REFERENCE_TRANSFORMER_EXCEPTION.get( 866 t.getClass().getName(), String.valueOf(op), 867 StaticUtils.getExceptionMessage(e)), 868 e); 869 } 870 } 871 872 sendMessage(new LDAPMessage(messageID, op, c)); 873 } 874 } 875 876 877 878 /** 879 * Sends an intermediate response message to the client with the provided 880 * information. 881 * 882 * @param messageID The message ID for the LDAP message to send to the 883 * client. It must match the message ID of the associated 884 * search request. 885 * @param protocolOp The intermediate response protocol op to include in the 886 * LDAP message to send to the client. 887 * @param controls The set of controls to include in the response message. 888 * It may be empty or {@code null} if no controls should 889 * be included. 890 * 891 * @throws LDAPException If a problem occurs while attempting to send the 892 * provided response message. If an exception is 893 * thrown, then the client connection will have been 894 * terminated. 895 */ 896 public void sendIntermediateResponse(final int messageID, 897 final IntermediateResponseProtocolOp protocolOp, 898 final Control... controls) 899 throws LDAPException 900 { 901 if (intermediateResponseTransformers.isEmpty()) 902 { 903 sendMessage(new LDAPMessage(messageID, protocolOp, controls)); 904 } 905 else 906 { 907 Control[] c; 908 IntermediateResponseProtocolOp op = protocolOp; 909 if (controls == null) 910 { 911 c = EMPTY_CONTROL_ARRAY; 912 } 913 else 914 { 915 c = controls; 916 } 917 918 for (final IntermediateResponseTransformer t : 919 intermediateResponseTransformers) 920 { 921 try 922 { 923 final ObjectPair<IntermediateResponseProtocolOp,Control[]> p = 924 t.transformIntermediateResponse(messageID, op, c); 925 if (p == null) 926 { 927 return; 928 } 929 930 op = p.getFirst(); 931 c = p.getSecond(); 932 } 933 catch (final Exception e) 934 { 935 Debug.debugException(e); 936 sendMessage(new LDAPMessage(messageID, protocolOp, c)); 937 throw new LDAPException(ResultCode.LOCAL_ERROR, 938 ERR_CONN_INTERMEDIATE_RESPONSE_TRANSFORMER_EXCEPTION.get( 939 t.getClass().getName(), String.valueOf(op), 940 StaticUtils.getExceptionMessage(e)), 941 e); 942 } 943 } 944 945 sendMessage(new LDAPMessage(messageID, op, c)); 946 } 947 } 948 949 950 951 /** 952 * Sends an unsolicited notification message to the client with the provided 953 * extended result. 954 * 955 * @param result The extended result to use for the unsolicited 956 * notification. 957 * 958 * @throws LDAPException If a problem occurs while attempting to send the 959 * unsolicited notification. If an exception is 960 * thrown, then the client connection will have been 961 * terminated. 962 */ 963 public void sendUnsolicitedNotification(final ExtendedResult result) 964 throws LDAPException 965 { 966 sendUnsolicitedNotification( 967 new ExtendedResponseProtocolOp(result.getResultCode().intValue(), 968 result.getMatchedDN(), result.getDiagnosticMessage(), 969 StaticUtils.toList(result.getReferralURLs()), result.getOID(), 970 result.getValue()), 971 result.getResponseControls() 972 ); 973 } 974 975 976 977 /** 978 * Sends an unsolicited notification message to the client with the provided 979 * information. 980 * 981 * @param extendedResponse The extended response to use for the unsolicited 982 * notification. 983 * @param controls The set of controls to include with the 984 * unsolicited notification. It may be empty or 985 * {@code null} if no controls should be included. 986 * 987 * @throws LDAPException If a problem occurs while attempting to send the 988 * unsolicited notification. If an exception is 989 * thrown, then the client connection will have been 990 * terminated. 991 */ 992 public void sendUnsolicitedNotification( 993 final ExtendedResponseProtocolOp extendedResponse, 994 final Control... controls) 995 throws LDAPException 996 { 997 sendMessage(new LDAPMessage(0, extendedResponse, controls)); 998 } 999 1000 1001 1002 /** 1003 * Retrieves the socket used to communicate with the client. 1004 * 1005 * @return The socket used to communicate with the client. 1006 */ 1007 public synchronized Socket getSocket() 1008 { 1009 return socket; 1010 } 1011 1012 1013 1014 /** 1015 * Attempts to convert this unencrypted connection to one that uses TLS 1016 * encryption, as would be used during the course of invoking the StartTLS 1017 * extended operation. If this is called, then the response that would have 1018 * been returned from the associated request will be suppressed, so the 1019 * returned output stream must be used to send the appropriate response to 1020 * the client. 1021 * 1022 * @param f The SSL socket factory that will be used to convert the existing 1023 * {@code Socket} to an {@code SSLSocket}. 1024 * 1025 * @return An output stream that can be used to send a clear-text message to 1026 * the client (e.g., the StartTLS response message). 1027 * 1028 * @throws LDAPException If a problem is encountered while trying to convert 1029 * the existing socket to an SSL socket. If this is 1030 * thrown, then the connection will have been closed. 1031 */ 1032 public synchronized OutputStream convertToTLS(final SSLSocketFactory f) 1033 throws LDAPException 1034 { 1035 final OutputStream clearOutputStream = outputStream; 1036 1037 final Socket origSocket = socket; 1038 final String hostname = origSocket.getInetAddress().getHostName(); 1039 final int port = origSocket.getPort(); 1040 1041 try 1042 { 1043 synchronized (f) 1044 { 1045 socket = f.createSocket(socket, hostname, port, true); 1046 } 1047 ((SSLSocket) socket).setUseClientMode(false); 1048 outputStream = socket.getOutputStream(); 1049 asn1Reader = new ASN1StreamReader(socket.getInputStream()); 1050 suppressNextResponse.set(true); 1051 return clearOutputStream; 1052 } 1053 catch (final Exception e) 1054 { 1055 Debug.debugException(e); 1056 1057 final LDAPException le = new LDAPException(ResultCode.LOCAL_ERROR, 1058 ERR_CONN_CONVERT_TO_TLS_FAILURE.get( 1059 StaticUtils.getExceptionMessage(e)), 1060 e); 1061 1062 close(le); 1063 1064 throw le; 1065 } 1066 } 1067 1068 1069 1070 /** 1071 * Retrieves the connection ID that has been assigned to this connection by 1072 * the associated listener. 1073 * 1074 * @return The connection ID that has been assigned to this connection by 1075 * the associated listener, or -1 if it is not associated with a 1076 * listener. 1077 */ 1078 public long getConnectionID() 1079 { 1080 return connectionID; 1081 } 1082 1083 1084 1085 /** 1086 * Adds the provided search entry transformer to this client connection. 1087 * 1088 * @param t A search entry transformer to be used to intercept and/or alter 1089 * search result entries before they are returned to the client. 1090 */ 1091 public void addSearchEntryTransformer(final SearchEntryTransformer t) 1092 { 1093 searchEntryTransformers.add(t); 1094 } 1095 1096 1097 1098 /** 1099 * Removes the provided search entry transformer from this client connection. 1100 * 1101 * @param t The search entry transformer to be removed. 1102 */ 1103 public void removeSearchEntryTransformer(final SearchEntryTransformer t) 1104 { 1105 searchEntryTransformers.remove(t); 1106 } 1107 1108 1109 1110 /** 1111 * Adds the provided search reference transformer to this client connection. 1112 * 1113 * @param t A search reference transformer to be used to intercept and/or 1114 * alter search result references before they are returned to the 1115 * client. 1116 */ 1117 public void addSearchReferenceTransformer(final SearchReferenceTransformer t) 1118 { 1119 searchReferenceTransformers.add(t); 1120 } 1121 1122 1123 1124 /** 1125 * Removes the provided search reference transformer from this client 1126 * connection. 1127 * 1128 * @param t The search reference transformer to be removed. 1129 */ 1130 public void removeSearchReferenceTransformer( 1131 final SearchReferenceTransformer t) 1132 { 1133 searchReferenceTransformers.remove(t); 1134 } 1135 1136 1137 1138 /** 1139 * Adds the provided intermediate response transformer to this client 1140 * connection. 1141 * 1142 * @param t An intermediate response transformer to be used to intercept 1143 * and/or alter intermediate responses before they are returned to 1144 * the client. 1145 */ 1146 public void addIntermediateResponseTransformer( 1147 final IntermediateResponseTransformer t) 1148 { 1149 intermediateResponseTransformers.add(t); 1150 } 1151 1152 1153 1154 /** 1155 * Removes the provided intermediate response transformer from this client 1156 * connection. 1157 * 1158 * @param t The intermediate response transformer to be removed. 1159 */ 1160 public void removeIntermediateResponseTransformer( 1161 final IntermediateResponseTransformer t) 1162 { 1163 intermediateResponseTransformers.remove(t); 1164 } 1165}