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