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