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