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