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