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