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 import com.unboundid.util.ssl.SSLUtil;
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 close();
377 }
378 catch (final Exception e)
379 {
380 Debug.debugException(e);
381 }
382 }
383
384
385
386 /**
387 * Operates in a loop, waiting for a request to arrive from the client and
388 * handing it off to the request handler for processing. This method is for
389 * internal use only and must not be invoked by external callers.
390 */
391 @InternalUseOnly()
392 @Override()
393 public void run()
394 {
395 try
396 {
397 while (true)
398 {
399 final LDAPMessage requestMessage;
400 try
401 {
402 requestMessage = LDAPMessage.readFrom(asn1Reader, false);
403 if (requestMessage == null)
404 {
405 // This indicates that the client has closed the connection without
406 // an unbind request. It's not all that nice, but it isn't an error
407 // so we won't notify the exception handler.
408 try
409 {
410 close();
411 }
412 catch (final IOException ioe)
413 {
414 Debug.debugException(ioe);
415 }
416
417 return;
418 }
419 }
420 catch (final LDAPException le)
421 {
422 Debug.debugException(le);
423 close(le);
424 return;
425 }
426
427 try
428 {
429 final int messageID = requestMessage.getMessageID();
430 final List<Control> controls = requestMessage.getControls();
431
432 LDAPMessage responseMessage;
433 switch (requestMessage.getProtocolOpType())
434 {
435 case LDAPMessage.PROTOCOL_OP_TYPE_ABANDON_REQUEST:
436 requestHandler.processAbandonRequest(messageID,
437 requestMessage.getAbandonRequestProtocolOp(), controls);
438 responseMessage = null;
439 break;
440
441 case LDAPMessage.PROTOCOL_OP_TYPE_ADD_REQUEST:
442 try
443 {
444 responseMessage = requestHandler.processAddRequest(messageID,
445 requestMessage.getAddRequestProtocolOp(), controls);
446 }
447 catch (final Exception e)
448 {
449 Debug.debugException(e);
450 responseMessage = new LDAPMessage(messageID,
451 new AddResponseProtocolOp(
452 ResultCode.OTHER_INT_VALUE, null,
453 ERR_CONN_REQUEST_HANDLER_FAILURE.get(
454 StaticUtils.getExceptionMessage(e)),
455 null));
456 }
457 break;
458
459 case LDAPMessage.PROTOCOL_OP_TYPE_BIND_REQUEST:
460 try
461 {
462 responseMessage = requestHandler.processBindRequest(messageID,
463 requestMessage.getBindRequestProtocolOp(), controls);
464 }
465 catch (final Exception e)
466 {
467 Debug.debugException(e);
468 responseMessage = new LDAPMessage(messageID,
469 new BindResponseProtocolOp(
470 ResultCode.OTHER_INT_VALUE, null,
471 ERR_CONN_REQUEST_HANDLER_FAILURE.get(
472 StaticUtils.getExceptionMessage(e)),
473 null, null));
474 }
475 break;
476
477 case LDAPMessage.PROTOCOL_OP_TYPE_COMPARE_REQUEST:
478 try
479 {
480 responseMessage = requestHandler.processCompareRequest(
481 messageID, requestMessage.getCompareRequestProtocolOp(),
482 controls);
483 }
484 catch (final Exception e)
485 {
486 Debug.debugException(e);
487 responseMessage = new LDAPMessage(messageID,
488 new CompareResponseProtocolOp(
489 ResultCode.OTHER_INT_VALUE, null,
490 ERR_CONN_REQUEST_HANDLER_FAILURE.get(
491 StaticUtils.getExceptionMessage(e)),
492 null));
493 }
494 break;
495
496 case LDAPMessage.PROTOCOL_OP_TYPE_DELETE_REQUEST:
497 try
498 {
499 responseMessage = requestHandler.processDeleteRequest(messageID,
500 requestMessage.getDeleteRequestProtocolOp(), controls);
501 }
502 catch (final Exception e)
503 {
504 Debug.debugException(e);
505 responseMessage = new LDAPMessage(messageID,
506 new DeleteResponseProtocolOp(
507 ResultCode.OTHER_INT_VALUE, null,
508 ERR_CONN_REQUEST_HANDLER_FAILURE.get(
509 StaticUtils.getExceptionMessage(e)),
510 null));
511 }
512 break;
513
514 case LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_REQUEST:
515 try
516 {
517 responseMessage = requestHandler.processExtendedRequest(
518 messageID, requestMessage.getExtendedRequestProtocolOp(),
519 controls);
520 }
521 catch (final Exception e)
522 {
523 Debug.debugException(e);
524 responseMessage = new LDAPMessage(messageID,
525 new ExtendedResponseProtocolOp(
526 ResultCode.OTHER_INT_VALUE, null,
527 ERR_CONN_REQUEST_HANDLER_FAILURE.get(
528 StaticUtils.getExceptionMessage(e)),
529 null, null, null));
530 }
531 break;
532
533 case LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_REQUEST:
534 try
535 {
536 responseMessage = requestHandler.processModifyRequest(messageID,
537 requestMessage.getModifyRequestProtocolOp(), controls);
538 }
539 catch (final Exception e)
540 {
541 Debug.debugException(e);
542 responseMessage = new LDAPMessage(messageID,
543 new ModifyResponseProtocolOp(
544 ResultCode.OTHER_INT_VALUE, null,
545 ERR_CONN_REQUEST_HANDLER_FAILURE.get(
546 StaticUtils.getExceptionMessage(e)),
547 null));
548 }
549 break;
550
551 case LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_DN_REQUEST:
552 try
553 {
554 responseMessage = requestHandler.processModifyDNRequest(
555 messageID, requestMessage.getModifyDNRequestProtocolOp(),
556 controls);
557 }
558 catch (final Exception e)
559 {
560 Debug.debugException(e);
561 responseMessage = new LDAPMessage(messageID,
562 new ModifyDNResponseProtocolOp(
563 ResultCode.OTHER_INT_VALUE, null,
564 ERR_CONN_REQUEST_HANDLER_FAILURE.get(
565 StaticUtils.getExceptionMessage(e)),
566 null));
567 }
568 break;
569
570 case LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_REQUEST:
571 try
572 {
573 responseMessage = requestHandler.processSearchRequest(messageID,
574 requestMessage.getSearchRequestProtocolOp(), controls);
575 }
576 catch (final Exception e)
577 {
578 Debug.debugException(e);
579 responseMessage = new LDAPMessage(messageID,
580 new SearchResultDoneProtocolOp(
581 ResultCode.OTHER_INT_VALUE, null,
582 ERR_CONN_REQUEST_HANDLER_FAILURE.get(
583 StaticUtils.getExceptionMessage(e)),
584 null));
585 }
586 break;
587
588 case LDAPMessage.PROTOCOL_OP_TYPE_UNBIND_REQUEST:
589 requestHandler.processUnbindRequest(messageID,
590 requestMessage.getUnbindRequestProtocolOp(), controls);
591 close();
592 return;
593
594 default:
595 close(new LDAPException(ResultCode.PROTOCOL_ERROR,
596 ERR_CONN_INVALID_PROTOCOL_OP_TYPE.get(StaticUtils.toHex(
597 requestMessage.getProtocolOpType()))));
598 return;
599 }
600
601 if (responseMessage != null)
602 {
603 try
604 {
605 sendMessage(responseMessage);
606 }
607 catch (final LDAPException le)
608 {
609 Debug.debugException(le);
610 close(le);
611 return;
612 }
613 }
614 }
615 catch (final Exception e)
616 {
617 close(new LDAPException(ResultCode.LOCAL_ERROR,
618 ERR_CONN_EXCEPTION_IN_REQUEST_HANDLER.get(
619 String.valueOf(requestMessage),
620 StaticUtils.getExceptionMessage(e))));
621 return;
622 }
623 }
624 }
625 finally
626 {
627 if (listener != null)
628 {
629 listener.connectionClosed(this);
630 }
631 }
632 }
633
634
635
636 /**
637 * Sends the provided message to the client.
638 *
639 * @param message The message to be written to the client.
640 *
641 * @throws LDAPException If a problem occurs while attempting to send the
642 * response to the client.
643 */
644 private synchronized void sendMessage(final LDAPMessage message)
645 throws LDAPException
646 {
647 // If we should suppress this response (which will only be because the
648 // response has already been sent through some other means, for example as
649 // part of StartTLS processing), then do so.
650 if (suppressNextResponse.compareAndSet(true, false))
651 {
652 return;
653 }
654
655 asn1Buffer.clear();
656
657 try
658 {
659 message.writeTo(asn1Buffer);
660 }
661 catch (final LDAPRuntimeException lre)
662 {
663 Debug.debugException(lre);
664 lre.throwLDAPException();
665 }
666
667 try
668 {
669 asn1Buffer.writeTo(outputStream);
670 }
671 catch (final IOException ioe)
672 {
673 Debug.debugException(ioe);
674
675 throw new LDAPException(ResultCode.LOCAL_ERROR,
676 ERR_CONN_SEND_MESSAGE_EXCEPTION.get(
677 StaticUtils.getExceptionMessage(ioe)),
678 ioe);
679 }
680 finally
681 {
682 if (asn1Buffer.zeroBufferOnClear())
683 {
684 asn1Buffer.clear();
685 }
686 }
687 }
688
689
690
691 /**
692 * Sends a search result entry message to the client with the provided
693 * information.
694 *
695 * @param messageID The message ID for the LDAP message to send to the
696 * client. It must match the message ID of the associated
697 * search request.
698 * @param protocolOp The search result entry protocol op to include in the
699 * LDAP message to send to the client. It must not be
700 * {@code null}.
701 * @param controls The set of controls to include in the response message.
702 * It may be empty or {@code null} if no controls should
703 * be included.
704 *
705 * @throws LDAPException If a problem occurs while attempting to send the
706 * provided response message. If an exception is
707 * thrown, then the client connection will have been
708 * terminated.
709 */
710 public void sendSearchResultEntry(final int messageID,
711 final SearchResultEntryProtocolOp protocolOp,
712 final Control... controls)
713 throws LDAPException
714 {
715 if (searchEntryTransformers.isEmpty())
716 {
717 sendMessage(new LDAPMessage(messageID, protocolOp, controls));
718 }
719 else
720 {
721 Control[] c;
722 SearchResultEntryProtocolOp op = protocolOp;
723 if (controls == null)
724 {
725 c = EMPTY_CONTROL_ARRAY;
726 }
727 else
728 {
729 c = controls;
730 }
731
732 for (final SearchEntryTransformer t : searchEntryTransformers)
733 {
734 try
735 {
736 final ObjectPair<SearchResultEntryProtocolOp,Control[]> p =
737 t.transformEntry(messageID, op, c);
738 if (p == null)
739 {
740 return;
741 }
742
743 op = p.getFirst();
744 c = p.getSecond();
745 }
746 catch (final Exception e)
747 {
748 Debug.debugException(e);
749 sendMessage(new LDAPMessage(messageID, protocolOp, c));
750 throw new LDAPException(ResultCode.LOCAL_ERROR,
751 ERR_CONN_SEARCH_ENTRY_TRANSFORMER_EXCEPTION.get(
752 t.getClass().getName(), String.valueOf(op),
753 StaticUtils.getExceptionMessage(e)),
754 e);
755 }
756 }
757
758 sendMessage(new LDAPMessage(messageID, op, c));
759 }
760 }
761
762
763
764 /**
765 * Sends a search result entry message to the client with the provided
766 * information.
767 *
768 * @param messageID The message ID for the LDAP message to send to the
769 * client. It must match the message ID of the associated
770 * search request.
771 * @param entry The entry to return to the client. It must not be
772 * {@code null}.
773 * @param controls The set of controls to include in the response message.
774 * It may be empty or {@code null} if no controls should be
775 * included.
776 *
777 * @throws LDAPException If a problem occurs while attempting to send the
778 * provided response message. If an exception is
779 * thrown, then the client connection will have been
780 * terminated.
781 */
782 public void sendSearchResultEntry(final int messageID, final Entry entry,
783 final Control... controls)
784 throws LDAPException
785 {
786 sendSearchResultEntry(messageID,
787 new SearchResultEntryProtocolOp(entry.getDN(),
788 new ArrayList<Attribute>(entry.getAttributes())),
789 controls);
790 }
791
792
793
794 /**
795 * Sends a search result reference message to the client with the provided
796 * information.
797 *
798 * @param messageID The message ID for the LDAP message to send to the
799 * client. It must match the message ID of the associated
800 * search request.
801 * @param protocolOp The search result reference protocol op to include in
802 * the LDAP message to send to the client.
803 * @param controls The set of controls to include in the response message.
804 * It may be empty or {@code null} if no controls should
805 * be included.
806 *
807 * @throws LDAPException If a problem occurs while attempting to send the
808 * provided response message. If an exception is
809 * thrown, then the client connection will have been
810 * terminated.
811 */
812 public void sendSearchResultReference(final int messageID,
813 final SearchResultReferenceProtocolOp protocolOp,
814 final Control... controls)
815 throws LDAPException
816 {
817 if (searchReferenceTransformers.isEmpty())
818 {
819 sendMessage(new LDAPMessage(messageID, protocolOp, controls));
820 }
821 else
822 {
823 Control[] c;
824 SearchResultReferenceProtocolOp op = protocolOp;
825 if (controls == null)
826 {
827 c = EMPTY_CONTROL_ARRAY;
828 }
829 else
830 {
831 c = controls;
832 }
833
834 for (final SearchReferenceTransformer t : searchReferenceTransformers)
835 {
836 try
837 {
838 final ObjectPair<SearchResultReferenceProtocolOp,Control[]> p =
839 t.transformReference(messageID, op, c);
840 if (p == null)
841 {
842 return;
843 }
844
845 op = p.getFirst();
846 c = p.getSecond();
847 }
848 catch (final Exception e)
849 {
850 Debug.debugException(e);
851 sendMessage(new LDAPMessage(messageID, protocolOp, c));
852 throw new LDAPException(ResultCode.LOCAL_ERROR,
853 ERR_CONN_SEARCH_REFERENCE_TRANSFORMER_EXCEPTION.get(
854 t.getClass().getName(), String.valueOf(op),
855 StaticUtils.getExceptionMessage(e)),
856 e);
857 }
858 }
859
860 sendMessage(new LDAPMessage(messageID, op, c));
861 }
862 }
863
864
865
866 /**
867 * Sends an intermediate response message to the client with the provided
868 * information.
869 *
870 * @param messageID The message ID for the LDAP message to send to the
871 * client. It must match the message ID of the associated
872 * search request.
873 * @param protocolOp The intermediate response protocol op to include in the
874 * LDAP message to send to the client.
875 * @param controls The set of controls to include in the response message.
876 * It may be empty or {@code null} if no controls should
877 * be included.
878 *
879 * @throws LDAPException If a problem occurs while attempting to send the
880 * provided response message. If an exception is
881 * thrown, then the client connection will have been
882 * terminated.
883 */
884 public void sendIntermediateResponse(final int messageID,
885 final IntermediateResponseProtocolOp protocolOp,
886 final Control... controls)
887 throws LDAPException
888 {
889 if (intermediateResponseTransformers.isEmpty())
890 {
891 sendMessage(new LDAPMessage(messageID, protocolOp, controls));
892 }
893 else
894 {
895 Control[] c;
896 IntermediateResponseProtocolOp op = protocolOp;
897 if (controls == null)
898 {
899 c = EMPTY_CONTROL_ARRAY;
900 }
901 else
902 {
903 c = controls;
904 }
905
906 for (final IntermediateResponseTransformer t :
907 intermediateResponseTransformers)
908 {
909 try
910 {
911 final ObjectPair<IntermediateResponseProtocolOp,Control[]> p =
912 t.transformIntermediateResponse(messageID, op, c);
913 if (p == null)
914 {
915 return;
916 }
917
918 op = p.getFirst();
919 c = p.getSecond();
920 }
921 catch (final Exception e)
922 {
923 Debug.debugException(e);
924 sendMessage(new LDAPMessage(messageID, protocolOp, c));
925 throw new LDAPException(ResultCode.LOCAL_ERROR,
926 ERR_CONN_INTERMEDIATE_RESPONSE_TRANSFORMER_EXCEPTION.get(
927 t.getClass().getName(), String.valueOf(op),
928 StaticUtils.getExceptionMessage(e)),
929 e);
930 }
931 }
932
933 sendMessage(new LDAPMessage(messageID, op, c));
934 }
935 }
936
937
938
939 /**
940 * Sends an unsolicited notification message to the client with the provided
941 * extended result.
942 *
943 * @param result The extended result to use for the unsolicited
944 * notification.
945 *
946 * @throws LDAPException If a problem occurs while attempting to send the
947 * unsolicited notification. If an exception is
948 * thrown, then the client connection will have been
949 * terminated.
950 */
951 public void sendUnsolicitedNotification(final ExtendedResult result)
952 throws LDAPException
953 {
954 sendUnsolicitedNotification(
955 new ExtendedResponseProtocolOp(result.getResultCode().intValue(),
956 result.getMatchedDN(), result.getDiagnosticMessage(),
957 StaticUtils.toList(result.getReferralURLs()), result.getOID(),
958 result.getValue()),
959 result.getResponseControls()
960 );
961 }
962
963
964
965 /**
966 * Sends an unsolicited notification message to the client with the provided
967 * information.
968 *
969 * @param extendedResponse The extended response to use for the unsolicited
970 * notification.
971 * @param controls The set of controls to include with the
972 * unsolicited notification. It may be empty or
973 * {@code null} if no controls should be included.
974 *
975 * @throws LDAPException If a problem occurs while attempting to send the
976 * unsolicited notification. If an exception is
977 * thrown, then the client connection will have been
978 * terminated.
979 */
980 public void sendUnsolicitedNotification(
981 final ExtendedResponseProtocolOp extendedResponse,
982 final Control... controls)
983 throws LDAPException
984 {
985 sendMessage(new LDAPMessage(0, extendedResponse, controls));
986 }
987
988
989
990 /**
991 * Retrieves the socket used to communicate with the client.
992 *
993 * @return The socket used to communicate with the client.
994 */
995 public synchronized Socket getSocket()
996 {
997 return socket;
998 }
999
1000
1001
1002 /**
1003 * Attempts to convert this unencrypted connection to one that uses TLS
1004 * encryption, as would be used during the course of invoking the StartTLS
1005 * extended operation. If this is called, then the response that would have
1006 * been returned from the associated request will be suppressed, so the
1007 * returned output stream must be used to send the appropriate response to
1008 * the client.
1009 *
1010 * @param f The SSL socket factory that will be used to convert the existing
1011 * {@code Socket} to an {@code SSLSocket}.
1012 *
1013 * @return An output stream that can be used to send a clear-text message to
1014 * the client (e.g., the StartTLS response message).
1015 *
1016 * @throws LDAPException If a problem is encountered while trying to convert
1017 * the existing socket to an SSL socket. If this is
1018 * thrown, then the connection will have been closed.
1019 */
1020 public synchronized OutputStream convertToTLS(final SSLSocketFactory f)
1021 throws LDAPException
1022 {
1023 final OutputStream clearOutputStream = outputStream;
1024
1025 final Socket origSocket = socket;
1026 final String hostname = origSocket.getInetAddress().getHostName();
1027 final int port = origSocket.getPort();
1028
1029 try
1030 {
1031 synchronized (f)
1032 {
1033 socket = f.createSocket(socket, hostname, port, true);
1034 SSLUtil.applyEnabledSSLProtocols(socket);
1035 }
1036 ((SSLSocket) socket).setUseClientMode(false);
1037 outputStream = socket.getOutputStream();
1038 asn1Reader = new ASN1StreamReader(socket.getInputStream());
1039 suppressNextResponse.set(true);
1040 return clearOutputStream;
1041 }
1042 catch (final Exception e)
1043 {
1044 Debug.debugException(e);
1045
1046 final LDAPException le = new LDAPException(ResultCode.LOCAL_ERROR,
1047 ERR_CONN_CONVERT_TO_TLS_FAILURE.get(
1048 StaticUtils.getExceptionMessage(e)),
1049 e);
1050
1051 close(le);
1052
1053 throw le;
1054 }
1055 }
1056
1057
1058
1059 /**
1060 * Retrieves the connection ID that has been assigned to this connection by
1061 * the associated listener.
1062 *
1063 * @return The connection ID that has been assigned to this connection by
1064 * the associated listener, or -1 if it is not associated with a
1065 * listener.
1066 */
1067 public long getConnectionID()
1068 {
1069 return connectionID;
1070 }
1071
1072
1073
1074 /**
1075 * Adds the provided search entry transformer to this client connection.
1076 *
1077 * @param t A search entry transformer to be used to intercept and/or alter
1078 * search result entries before they are returned to the client.
1079 */
1080 public void addSearchEntryTransformer(final SearchEntryTransformer t)
1081 {
1082 searchEntryTransformers.add(t);
1083 }
1084
1085
1086
1087 /**
1088 * Removes the provided search entry transformer from this client connection.
1089 *
1090 * @param t The search entry transformer to be removed.
1091 */
1092 public void removeSearchEntryTransformer(final SearchEntryTransformer t)
1093 {
1094 searchEntryTransformers.remove(t);
1095 }
1096
1097
1098
1099 /**
1100 * Adds the provided search reference transformer to this client connection.
1101 *
1102 * @param t A search reference transformer to be used to intercept and/or
1103 * alter search result references before they are returned to the
1104 * client.
1105 */
1106 public void addSearchReferenceTransformer(final SearchReferenceTransformer t)
1107 {
1108 searchReferenceTransformers.add(t);
1109 }
1110
1111
1112
1113 /**
1114 * Removes the provided search reference transformer from this client
1115 * connection.
1116 *
1117 * @param t The search reference transformer to be removed.
1118 */
1119 public void removeSearchReferenceTransformer(
1120 final SearchReferenceTransformer t)
1121 {
1122 searchReferenceTransformers.remove(t);
1123 }
1124
1125
1126
1127 /**
1128 * Adds the provided intermediate response transformer to this client
1129 * connection.
1130 *
1131 * @param t An intermediate response transformer to be used to intercept
1132 * and/or alter intermediate responses before they are returned to
1133 * the client.
1134 */
1135 public void addIntermediateResponseTransformer(
1136 final IntermediateResponseTransformer t)
1137 {
1138 intermediateResponseTransformers.add(t);
1139 }
1140
1141
1142
1143 /**
1144 * Removes the provided intermediate response transformer from this client
1145 * connection.
1146 *
1147 * @param t The intermediate response transformer to be removed.
1148 */
1149 public void removeIntermediateResponseTransformer(
1150 final IntermediateResponseTransformer t)
1151 {
1152 intermediateResponseTransformers.remove(t);
1153 }
1154 }