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