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