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}