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