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 }