001 /*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License"). You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at
010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012 * See the License for the specific language governing permissions
013 * and limitations under the License.
014 *
015 * When distributing Covered Code, include this CDDL HEADER in each
016 * file and include the License file at
017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
018 * add the following below this CDDL HEADER, with the fields enclosed
019 * by brackets "[]" replaced with your own identifying information:
020 * Portions Copyright [yyyy] [name of copyright owner]
021 *
022 * CDDL HEADER END
023 *
024 *
025 * Copyright 2006-2008 Sun Microsystems, Inc.
026 */
027 package org.opends.server.protocols.ldap;
028
029
030
031 import java.net.InetAddress;
032 import java.nio.ByteBuffer;
033 import java.nio.channels.Selector;
034 import java.nio.channels.SocketChannel;
035 import java.util.ArrayList;
036 import java.util.Collection;
037 import java.util.Iterator;
038 import java.util.List;
039 import java.util.concurrent.ConcurrentHashMap;
040 import java.util.concurrent.atomic.AtomicLong;
041 import java.util.concurrent.atomic.AtomicReference;
042
043 import org.opends.messages.Message;
044 import org.opends.messages.MessageBuilder;
045 import org.opends.server.api.ClientConnection;
046 import org.opends.server.api.ConnectionHandler;
047 import org.opends.server.api.ConnectionSecurityProvider;
048 import org.opends.server.core.AbandonOperationBasis;
049 import org.opends.server.core.AddOperationBasis;
050 import org.opends.server.core.BindOperationBasis;
051 import org.opends.server.core.CompareOperationBasis;
052 import org.opends.server.core.DeleteOperationBasis;
053 import org.opends.server.core.DirectoryServer;
054 import org.opends.server.core.ExtendedOperationBasis;
055 import org.opends.server.core.ModifyDNOperationBasis;
056 import org.opends.server.core.ModifyOperationBasis;
057 import org.opends.server.core.PersistentSearch;
058 import org.opends.server.core.PluginConfigManager;
059 import org.opends.server.core.SearchOperation;
060 import org.opends.server.core.SearchOperationBasis;
061 import org.opends.server.core.UnbindOperationBasis;
062 import org.opends.server.extensions.NullConnectionSecurityProvider;
063 import org.opends.server.extensions.TLSCapableConnection;
064 import org.opends.server.extensions.TLSConnectionSecurityProvider;
065 import org.opends.server.loggers.debug.DebugTracer;
066 import org.opends.server.protocols.asn1.ASN1Element;
067 import org.opends.server.protocols.asn1.ASN1OctetString;
068 import org.opends.server.protocols.asn1.ASN1Sequence;
069 import org.opends.server.types.AbstractOperation;
070 import org.opends.server.types.CancelRequest;
071 import org.opends.server.types.CancelResult;
072 import org.opends.server.types.Control;
073 import org.opends.server.types.DN;
074 import org.opends.server.types.DebugLogLevel;
075 import org.opends.server.types.DirectoryException;
076 import org.opends.server.types.DisconnectReason;
077 import org.opends.server.types.IntermediateResponse;
078 import org.opends.server.types.Operation;
079 import org.opends.server.types.ResultCode;
080 import org.opends.server.types.SearchResultEntry;
081 import org.opends.server.types.SearchResultReference;
082 import org.opends.server.util.TimeThread;
083
084 import static org.opends.messages.ProtocolMessages.*;
085 import static org.opends.server.loggers.AccessLogger.logDisconnect;
086 import static org.opends.server.loggers.ErrorLogger.logError;
087 import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
088 import static org.opends.server.loggers.debug.DebugLogger.getTracer;
089 import static org.opends.server.protocols.ldap.LDAPConstants.*;
090 import static org.opends.server.util.StaticUtils.getExceptionMessage;
091 import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
092
093
094
095 /**
096 * This class defines an LDAP client connection, which is a type of client
097 * connection that will be accepted by an instance of the LDAP connection
098 * handler and have its requests decoded by an LDAP request handler.
099 */
100 public class LDAPClientConnection
101 extends ClientConnection
102 implements TLSCapableConnection
103 {
104 /**
105 * The tracer object for the debug logger.
106 */
107 private static final DebugTracer TRACER = getTracer();
108
109
110
111 // The time that the last operation was completed.
112 private AtomicLong lastCompletionTime;
113
114 // The next operation ID that should be used for this connection.
115 private AtomicLong nextOperationID;
116
117 // The selector that may be used for write operations.
118 private AtomicReference<Selector> writeSelector;
119
120 // Indicates whether the Directory Server believes this connection to be
121 // valid and available for communication.
122 private boolean connectionValid;
123
124 // Indicates whether this connection is about to be closed. This will be used
125 // to prevent accepting new requests while a disconnect is in progress.
126 private boolean disconnectRequested;
127
128 // Indicates whether the connection should keep statistics regarding the
129 // operations that it is performing.
130 private boolean keepStats;
131
132 // The BER type for the ASN.1 element that is in the process of being read.
133 private byte elementType;
134
135 // The encoded value for the ASN.1 element that is in the process of being
136 // read.
137 private byte[] elementValue;
138
139 // The set of all operations currently in progress on this connection.
140 private ConcurrentHashMap<Integer,AbstractOperation> operationsInProgress;
141
142 // The connection security provider that was in use for the client connection
143 // before switching to a TLS-based provider.
144 private ConnectionSecurityProvider clearSecurityProvider;
145
146 // The connection security provider for this client connection.
147 private ConnectionSecurityProvider securityProvider;
148
149 // The port on the client from which this connection originated.
150 private int clientPort;
151
152 // The number of bytes contained in the value for the ASN.1 element that is in
153 // the process of being read.
154 private int elementLength;
155
156 // The number of bytes in the multi-byte length that are still needed to fully
157 // decode the length of the ASN.1 element in process.
158 private int elementLengthBytesNeeded;
159
160 // The current state for the data read for the ASN.1 element in progress.
161 private int elementReadState;
162
163 // The number of bytes that have already been read for the ASN.1 element
164 // value in progress.
165 private int elementValueBytesRead;
166
167 // The number of bytes that are still needed to fully decode the value of the
168 // ASN.1 element in progress.
169 private int elementValueBytesNeeded;
170
171 // The LDAP version that the client is using to communicate with the server.
172 private int ldapVersion;
173
174 // The port on the server to which this client has connected.
175 private int serverPort;
176
177 // The reference to the connection handler that accepted this connection.
178 private LDAPConnectionHandler connectionHandler;
179
180 // The reference to the request handler with which this connection is
181 // associated.
182 private LDAPRequestHandler requestHandler;
183
184 // The statistics tracker associated with this client connection.
185 private LDAPStatistics statTracker;
186
187 // The connection ID assigned to this connection.
188 private long connectionID;
189
190 // The lock used to provide threadsafe access to the set of operations in
191 // progress.
192 private Object opsInProgressLock;
193
194 // The lock used to provide threadsafe access when sending data to the client.
195 private Object transmitLock;
196
197 // The socket channel with which this client connection is associated.
198 private SocketChannel clientChannel;
199
200 // The string representation of the address of the client.
201 private String clientAddress;
202
203 // The name of the protocol that the client is using to communicate with the
204 // server.
205 private String protocol;
206
207 // The string representation of the address of the server to which the client
208 // has connected.
209 private String serverAddress;
210
211 // The TLS connection security provider that may be used for this connection
212 // if StartTLS is requested.
213 private TLSConnectionSecurityProvider tlsSecurityProvider;
214
215
216
217 /**
218 * Creates a new LDAP client connection with the provided information.
219 *
220 * @param connectionHandler The connection handler that accepted this
221 * connection.
222 * @param clientChannel The socket channel that may be used to
223 * communicate with the client.
224 */
225 public LDAPClientConnection(LDAPConnectionHandler connectionHandler,
226 SocketChannel clientChannel)
227 {
228 super();
229
230
231 this.connectionHandler = connectionHandler;
232 this.clientChannel = clientChannel;
233 this.securityProvider = null;
234 this.clearSecurityProvider = null;
235
236 opsInProgressLock = new Object();
237 transmitLock = new Object();
238
239 elementReadState = ELEMENT_READ_STATE_NEED_TYPE;
240 elementType = 0x00;
241 elementLength = 0;
242 elementLengthBytesNeeded = 0;
243 elementValue = null;
244 elementValueBytesRead = 0;
245 elementValueBytesNeeded = 0;
246
247 ldapVersion = 3;
248 requestHandler = null;
249 lastCompletionTime = new AtomicLong(TimeThread.getTime());
250 nextOperationID = new AtomicLong(0);
251 connectionValid = true;
252 disconnectRequested = false;
253 operationsInProgress = new ConcurrentHashMap<Integer,AbstractOperation>();
254 keepStats = connectionHandler.keepStats();
255 protocol = "LDAP";
256 writeSelector = new AtomicReference<Selector>();
257
258 clientAddress = clientChannel.socket().getInetAddress().getHostAddress();
259 clientPort = clientChannel.socket().getPort();
260 serverAddress = clientChannel.socket().getLocalAddress().getHostAddress();
261 serverPort = clientChannel.socket().getLocalPort();
262
263 LDAPStatistics parentTracker = connectionHandler.getStatTracker();
264 String instanceName = parentTracker.getMonitorInstanceName() +
265 " for " + toString();
266 statTracker = new LDAPStatistics(instanceName, parentTracker);
267
268 if (keepStats)
269 {
270 statTracker.updateConnect();
271 }
272
273 connectionID = DirectoryServer.newConnectionAccepted(this);
274 if (connectionID < 0)
275 {
276 disconnect(DisconnectReason.ADMIN_LIMIT_EXCEEDED, true,
277 ERR_LDAP_CONNHANDLER_REJECTED_BY_SERVER.get());
278 }
279 }
280
281
282
283 /**
284 * Retrieves the connection ID assigned to this connection.
285 *
286 * @return The connection ID assigned to this connection.
287 */
288 public long getConnectionID()
289 {
290 return connectionID;
291 }
292
293
294
295 /**
296 * Retrieves the connection handler that accepted this client connection.
297 *
298 * @return The connection handler that accepted this client connection.
299 */
300 public ConnectionHandler getConnectionHandler()
301 {
302 return connectionHandler;
303 }
304
305
306
307 /**
308 * Retrieves the request handler that will read requests for this client
309 * connection.
310 *
311 * @return The request handler that will read requests for this client
312 * connection, or <CODE>null</CODE> if none has been assigned yet.
313 */
314 public LDAPRequestHandler getRequestHandler()
315 {
316 return requestHandler;
317 }
318
319
320
321 /**
322 * Specifies the request handler that will read requests for this client
323 * connection.
324 *
325 * @param requestHandler The request handler that will read requests for
326 * this client connection.
327 */
328 public void setRequestHandler(LDAPRequestHandler requestHandler)
329 {
330 this.requestHandler = requestHandler;
331 }
332
333
334
335 /**
336 * Retrieves the socket channel that can be used to communicate with the
337 * client.
338 *
339 * @return The socket channel that can be used to communicate with the
340 * client.
341 */
342 public SocketChannel getSocketChannel()
343 {
344 return clientChannel;
345 }
346
347
348
349 /**
350 * Retrieves the protocol that the client is using to communicate with the
351 * Directory Server.
352 *
353 * @return The protocol that the client is using to communicate with the
354 * Directory Server.
355 */
356 public String getProtocol()
357 {
358 return protocol;
359 }
360
361
362
363 /**
364 * Retrieves a string representation of the address of the client.
365 *
366 * @return A string representation of the address of the client.
367 */
368 public String getClientAddress()
369 {
370 return clientAddress;
371 }
372
373
374
375 /**
376 * Retrieves the port number for this connection on the client system.
377 *
378 * @return The port number for this connection on the client system.
379 */
380 public int getClientPort()
381 {
382 return clientPort;
383 }
384
385
386
387 /**
388 * Retrieves the address and port of the client system, separated by a colon.
389 *
390 * @return The address and port of the client system, separated by a colon.
391 */
392 public String getClientHostPort()
393 {
394 return clientAddress + ":" + clientPort;
395 }
396
397
398
399 /**
400 * Retrieves a string representation of the address on the server to which the
401 * client connected.
402 *
403 * @return A string representation of the address on the server to which the
404 * client connected.
405 */
406 public String getServerAddress()
407 {
408 return serverAddress;
409 }
410
411
412
413 /**
414 * Retrieves the port number for this connection on the server system.
415 *
416 * @return The port number for this connection on the server system.
417 */
418 public int getServerPort()
419 {
420 return serverPort;
421 }
422
423
424
425 /**
426 * Retrieves the address and port of the server system, separated by a colon.
427 *
428 * @return The address and port of the server system, separated by a colon.
429 */
430 public String getServerHostPort()
431 {
432 return serverAddress + ":" + serverPort;
433 }
434
435
436
437 /**
438 * Retrieves the <CODE>java.net.InetAddress</CODE> associated with the remote
439 * client system.
440 *
441 * @return The <CODE>java.net.InetAddress</CODE> associated with the remote
442 * client system. It may be <CODE>null</CODE> if the client is not
443 * connected over an IP-based connection.
444 */
445 public InetAddress getRemoteAddress()
446 {
447 return clientChannel.socket().getInetAddress();
448 }
449
450
451
452 /**
453 * Retrieves the <CODE>java.net.InetAddress</CODE> for the Directory Server
454 * system to which the client has established the connection.
455 *
456 * @return The <CODE>java.net.InetAddress</CODE> for the Directory Server
457 * system to which the client has established the connection. It may
458 * be <CODE>null</CODE> if the client is not connected over an
459 * IP-based connection.
460 */
461 public InetAddress getLocalAddress()
462 {
463 return clientChannel.socket().getLocalAddress();
464 }
465
466
467
468 /**
469 * Indicates whether this client connection is currently using a secure
470 * mechanism to communicate with the server. Note that this may change over
471 * time based on operations performed by the client or server (e.g., it may go
472 * from <CODE>false</CODE> to <CODE>true</CODE> if the client uses the
473 * StartTLS extended operation).
474 *
475 * @return <CODE>true</CODE> if the client connection is currently using a
476 * secure mechanism to communicate with the server, or
477 * <CODE>false</CODE> if not.
478 */
479 public boolean isSecure()
480 {
481 return securityProvider.isSecure();
482 }
483
484
485
486 /**
487 * Retrieves the connection security provider for this client connection.
488 *
489 * @return The connection security provider for this client connection.
490 */
491 public ConnectionSecurityProvider getConnectionSecurityProvider()
492 {
493 return securityProvider;
494 }
495
496
497
498 /**
499 * Specifies the connection security provider for this client connection.
500 *
501 * @param securityProvider The connection security provider to use for
502 * communication on this client connection.
503 */
504 public void setConnectionSecurityProvider(ConnectionSecurityProvider
505 securityProvider)
506 {
507 this.securityProvider = securityProvider;
508
509 if (securityProvider.isSecure())
510 {
511 protocol = "LDAP+" + securityProvider.getSecurityMechanismName();
512 }
513 else
514 {
515 protocol = "LDAP";
516 }
517 }
518
519
520
521 /**
522 * Retrieves the human-readable name of the security mechanism that is used to
523 * protect communication with this client.
524 *
525 * @return The human-readable name of the security mechanism that is used to
526 * protect communication with this client, or <CODE>null</CODE> if no
527 * security is in place.
528 */
529 public String getSecurityMechanism()
530 {
531 return securityProvider.getSecurityMechanismName();
532 }
533
534
535
536 /**
537 * Retrieves the next operation ID that should be used for this connection.
538 *
539 * @return The next operation ID that should be used for this connection.
540 */
541 public long nextOperationID()
542 {
543 return nextOperationID.getAndIncrement();
544 }
545
546
547
548 /**
549 * Sends a response to the client based on the information in the provided
550 * operation.
551 *
552 * @param operation The operation for which to send the response.
553 */
554 public void sendResponse(Operation operation)
555 {
556 // Since this is the final response for this operation, we can go ahead and
557 // remove it from the "operations in progress" list. It can't be canceled
558 // after this point, and this will avoid potential race conditions in which
559 // the client immediately sends another request with the same message ID as
560 // was used for this operation.
561 removeOperationInProgress(operation.getMessageID());
562
563 LDAPMessage message = operationToResponseLDAPMessage(operation);
564 if (message != null)
565 {
566 sendLDAPMessage(securityProvider, message);
567 }
568 }
569
570
571
572 /**
573 * Retrieves an LDAPMessage containing a response generated from the provided
574 * operation.
575 *
576 * @param operation The operation to use to generate the response
577 * LDAPMessage.
578 *
579 * @return An LDAPMessage containing a response generated from the provided
580 * operation.
581 */
582 private LDAPMessage operationToResponseLDAPMessage(Operation operation)
583 {
584 ResultCode resultCode = operation.getResultCode();
585 if (resultCode == null)
586 {
587 // This must mean that the operation has either not yet completed or that
588 // it completed without a result for some reason. In any case, log a
589 // message and set the response to "operations error".
590 logError(ERR_LDAP_CLIENT_SEND_RESPONSE_NO_RESULT_CODE.
591 get(operation.getOperationType().toString(),
592 operation.getConnectionID(), operation.getOperationID()));
593 resultCode = DirectoryServer.getServerErrorResultCode();
594 }
595
596
597 MessageBuilder errorMessage = operation.getErrorMessage();
598 DN matchedDN = operation.getMatchedDN();
599
600
601 // Referrals are not allowed for LDAPv2 clients.
602 List<String> referralURLs;
603 if (ldapVersion == 2)
604 {
605 referralURLs = null;
606
607 if (resultCode == ResultCode.REFERRAL)
608 {
609 resultCode = ResultCode.CONSTRAINT_VIOLATION;
610 errorMessage.append(ERR_LDAPV2_REFERRAL_RESULT_CHANGED.get());
611 }
612
613 List<String> opReferrals = operation.getReferralURLs();
614 if ((opReferrals != null) && (! opReferrals.isEmpty()))
615 {
616 StringBuilder referralsStr = new StringBuilder();
617 Iterator<String> iterator = opReferrals.iterator();
618 referralsStr.append(iterator.next());
619
620 while (iterator.hasNext())
621 {
622 referralsStr.append(", ");
623 referralsStr.append(iterator.next());
624 }
625
626 errorMessage.append(ERR_LDAPV2_REFERRALS_OMITTED.get(
627 String.valueOf(referralsStr)));
628 }
629 }
630 else
631 {
632 referralURLs = operation.getReferralURLs();
633 }
634
635 ProtocolOp protocolOp;
636 switch (operation.getOperationType())
637 {
638 case ADD:
639 protocolOp = new AddResponseProtocolOp(resultCode.getIntValue(),
640 errorMessage.toMessage(),
641 matchedDN, referralURLs);
642 break;
643 case BIND:
644 ASN1OctetString serverSASLCredentials =
645 ((BindOperationBasis) operation).getServerSASLCredentials();
646 protocolOp = new BindResponseProtocolOp(resultCode.getIntValue(),
647 errorMessage.toMessage(), matchedDN,
648 referralURLs, serverSASLCredentials);
649 break;
650 case COMPARE:
651 protocolOp = new CompareResponseProtocolOp(resultCode.getIntValue(),
652 errorMessage.toMessage(),
653 matchedDN, referralURLs);
654 break;
655 case DELETE:
656 protocolOp = new DeleteResponseProtocolOp(resultCode.getIntValue(),
657 errorMessage.toMessage(),
658 matchedDN, referralURLs);
659 break;
660 case EXTENDED:
661 // If this an LDAPv2 client, then we can't send this.
662 if (ldapVersion == 2)
663 {
664 logError(ERR_LDAPV2_SKIPPING_EXTENDED_RESPONSE.get(
665 getConnectionID(), operation.getOperationID(),
666 String.valueOf(operation)));
667 return null;
668 }
669
670 ExtendedOperationBasis extOp = (ExtendedOperationBasis) operation;
671 protocolOp = new ExtendedResponseProtocolOp(resultCode.getIntValue(),
672 errorMessage.toMessage(), matchedDN, referralURLs,
673 extOp.getResponseOID(), extOp.getResponseValue());
674 break;
675 case MODIFY:
676 protocolOp = new ModifyResponseProtocolOp(resultCode.getIntValue(),
677 errorMessage.toMessage(),
678 matchedDN, referralURLs);
679 break;
680 case MODIFY_DN:
681 protocolOp = new ModifyDNResponseProtocolOp(resultCode.getIntValue(),
682 errorMessage.toMessage(),
683 matchedDN, referralURLs);
684 break;
685 case SEARCH:
686 protocolOp = new SearchResultDoneProtocolOp(resultCode.getIntValue(),
687 errorMessage.toMessage(),
688 matchedDN, referralURLs);
689 break;
690 default:
691 // This must be a type of operation that doesn't have a response. This
692 // shouldn't happen, so log a message and return.
693 logError(ERR_LDAP_CLIENT_SEND_RESPONSE_INVALID_OP.get(
694 String.valueOf(operation.getOperationType()),
695 getConnectionID(),
696 operation.getOperationID(),
697 String.valueOf(operation)));
698 return null;
699 }
700
701
702 // Controls are not allowed for LDAPv2 clients.
703 ArrayList<LDAPControl> controls;
704 if (ldapVersion == 2)
705 {
706 controls = null;
707 }
708 else
709 {
710 List<Control> responseControls = operation.getResponseControls();
711 if ((responseControls == null) || responseControls.isEmpty())
712 {
713 controls = null;
714 }
715 else
716 {
717 controls = new ArrayList<LDAPControl>(responseControls.size());
718 for (Control c : responseControls)
719 {
720 controls.add(new LDAPControl(c));
721 }
722 }
723 }
724
725 return new LDAPMessage(operation.getMessageID(), protocolOp, controls);
726 }
727
728
729
730 /**
731 * Sends the provided search result entry to the client.
732 *
733 * @param searchOperation The search operation with which the entry is
734 * associated.
735 * @param searchEntry The search result entry to be sent to the client.
736 */
737 public void sendSearchEntry(SearchOperation searchOperation,
738 SearchResultEntry searchEntry)
739 {
740 SearchResultEntryProtocolOp protocolOp =
741 new SearchResultEntryProtocolOp(searchEntry);
742
743 List<Control> entryControls = searchEntry.getControls();
744 ArrayList<LDAPControl> controls;
745 if ((entryControls == null) || entryControls.isEmpty())
746 {
747 controls = null;
748 }
749 else
750 {
751 controls = new ArrayList<LDAPControl>(entryControls.size());
752 for (Control c : entryControls)
753 {
754 controls.add(new LDAPControl(c));
755 }
756 }
757
758 sendLDAPMessage(securityProvider,
759 new LDAPMessage(searchOperation.getMessageID(), protocolOp,
760 controls));
761 }
762
763
764
765 /**
766 * Sends the provided search result reference to the client.
767 *
768 * @param searchOperation The search operation with which the reference is
769 * associated.
770 * @param searchReference The search result reference to be sent to the
771 * client.
772 *
773 * @return <CODE>true</CODE> if the client is able to accept referrals, or
774 * <CODE>false</CODE> if the client cannot handle referrals and no
775 * more attempts should be made to send them for the associated
776 * search operation.
777 */
778 public boolean sendSearchReference(SearchOperation searchOperation,
779 SearchResultReference searchReference)
780 {
781 // Make sure this is not an LDAPv2 client. If it is, then they can't see
782 // referrals so we'll not send anything. Also, throw an exception so that
783 // the core server will know not to try sending any more referrals to this
784 // client for the rest of the operation.
785 if (ldapVersion == 2)
786 {
787 Message message = ERR_LDAPV2_SKIPPING_SEARCH_REFERENCE.
788 get(getConnectionID(), searchOperation.getOperationID(),
789 String.valueOf(searchReference));
790 logError(message);
791 return false;
792 }
793
794 SearchResultReferenceProtocolOp protocolOp =
795 new SearchResultReferenceProtocolOp(searchReference);
796
797 List<Control> referenceControls = searchReference.getControls();
798 ArrayList<LDAPControl> controls;
799 if ((referenceControls == null) || referenceControls.isEmpty())
800 {
801 controls = null;
802 }
803 else
804 {
805 controls = new ArrayList<LDAPControl>(referenceControls.size());
806 for (Control c : referenceControls)
807 {
808 controls.add(new LDAPControl(c));
809 }
810 }
811
812 sendLDAPMessage(securityProvider,
813 new LDAPMessage(searchOperation.getMessageID(), protocolOp,
814 controls));
815 return true;
816 }
817
818
819
820
821 /**
822 * Sends the provided intermediate response message to the client.
823 *
824 * @param intermediateResponse The intermediate response message to be sent.
825 *
826 * @return <CODE>true</CODE> if processing on the associated operation should
827 * continue, or <CODE>false</CODE> if not.
828 */
829 protected boolean sendIntermediateResponseMessage(
830 IntermediateResponse intermediateResponse)
831 {
832 IntermediateResponseProtocolOp protocolOp =
833 new IntermediateResponseProtocolOp(intermediateResponse.getOID(),
834 intermediateResponse.getValue());
835
836 Operation operation = intermediateResponse.getOperation();
837
838 List<Control> controls = intermediateResponse.getControls();
839 ArrayList<LDAPControl> ldapControls =
840 new ArrayList<LDAPControl>(controls.size());
841 for (Control c : controls)
842 {
843 ldapControls.add(new LDAPControl(c));
844 }
845
846
847 LDAPMessage message = new LDAPMessage(operation.getMessageID(), protocolOp,
848 ldapControls);
849 sendLDAPMessage(securityProvider, message);
850
851
852 // The only reason we shouldn't continue processing is if the connection is
853 // closed.
854 return connectionValid;
855 }
856
857
858
859 /**
860 * Sends the provided LDAP message to the client.
861 *
862 * @param secProvider The connection security provider to use to handle any
863 * necessary security translation.
864 * @param message The LDAP message to send to the client.
865 */
866 public void sendLDAPMessage(ConnectionSecurityProvider secProvider,
867 LDAPMessage message)
868 {
869 ASN1Element messageElement = message.encode();
870
871 ByteBuffer messageBuffer = ByteBuffer.wrap(messageElement.encode());
872
873
874 // Make sure that we can only send one message at a time. This locking will
875 // not have any impact on the ability to read requests from the client.
876 synchronized (transmitLock)
877 {
878 try
879 {
880 try
881 {
882 int bytesWritten = messageBuffer.limit() - messageBuffer.position();
883 if (! secProvider.writeData(messageBuffer))
884 {
885 return;
886 }
887
888 TRACER.debugProtocolElement(DebugLogLevel.VERBOSE, message);
889 TRACER.debugProtocolElement(DebugLogLevel.VERBOSE, messageElement);
890
891 messageBuffer.rewind();
892 if (debugEnabled())
893 {
894 TRACER.debugData(DebugLogLevel.VERBOSE, messageBuffer);
895 }
896
897 if (keepStats)
898 {
899 statTracker.updateMessageWritten(message, bytesWritten);
900 }
901 }
902 catch (@Deprecated Exception e)
903 {
904 if (debugEnabled())
905 {
906 TRACER.debugCaught(DebugLogLevel.ERROR, e);
907 }
908
909 // We were unable to send the message due to some other internal
910 // problem. Disconnect from the client and return.
911 disconnect(DisconnectReason.SERVER_ERROR, true, null);
912 return;
913 }
914 }
915 catch (Exception e)
916 {
917 if (debugEnabled())
918 {
919 TRACER.debugCaught(DebugLogLevel.ERROR, e);
920 }
921
922 // FIXME -- Log a message or something
923 disconnect(DisconnectReason.SERVER_ERROR, true, null);
924 return;
925 }
926 }
927 }
928
929
930
931 /**
932 * Closes the connection to the client, optionally sending it a message
933 * indicating the reason for the closure. Note that the ability to send a
934 * notice of disconnection may not be available for all protocols or under all
935 * circumstances.
936 *
937 * @param disconnectReason The disconnect reason that provides the generic
938 * cause for the disconnect.
939 * @param sendNotification Indicates whether to try to provide notification
940 * to the client that the connection will be closed.
941 * @param message The message to include in the disconnect
942 * notification response. It may be
943 * <CODE>null</CODE> if no message is to be sent.
944 */
945 public void disconnect(DisconnectReason disconnectReason,
946 boolean sendNotification,
947 Message message)
948 {
949 // Set a flag indicating that the connection is being terminated so that no
950 // new requests will be accepted. Also cancel all operations in progress.
951 synchronized (opsInProgressLock)
952 {
953 // If we are already in the middle of a disconnect, then don't
954 // do anything.
955 if (disconnectRequested)
956 {
957 return;
958 }
959
960 disconnectRequested = true;
961 }
962
963
964 if (keepStats)
965 {
966 statTracker.updateDisconnect();
967 }
968
969 if (connectionID >= 0)
970 {
971 DirectoryServer.connectionClosed(this);
972 }
973
974
975 // Indicate that this connection is no longer valid.
976 connectionValid = false;
977
978 MessageBuilder msgBuilder = new MessageBuilder();
979 msgBuilder.append(disconnectReason.getClosureMessage());
980 msgBuilder.append(": ");
981 msgBuilder.append(message);
982 cancelAllOperations(new CancelRequest(true, msgBuilder.toMessage()));
983 finalizeConnectionInternal();
984
985
986 // If there is a write selector for this connection, then close it.
987 Selector selector = writeSelector.get();
988 if (selector != null)
989 {
990 try
991 {
992 selector.close();
993 } catch (Exception e) {}
994 }
995
996
997 // See if we should send a notification to the client. If so, then
998 // construct and send a notice of disconnection unsolicited response.
999 // Note that we cannot send this notification to an LDAPv2 client.
1000 if (sendNotification && (ldapVersion != 2))
1001 {
1002 try
1003 {
1004 int resultCode;
1005 switch (disconnectReason)
1006 {
1007 case PROTOCOL_ERROR:
1008 resultCode = LDAPResultCode.PROTOCOL_ERROR;
1009 break;
1010 case SERVER_SHUTDOWN:
1011 resultCode = LDAPResultCode.UNAVAILABLE;
1012 break;
1013 case SERVER_ERROR:
1014 resultCode =
1015 DirectoryServer.getServerErrorResultCode().getIntValue();
1016 break;
1017 case ADMIN_LIMIT_EXCEEDED:
1018 case IDLE_TIME_LIMIT_EXCEEDED:
1019 case MAX_REQUEST_SIZE_EXCEEDED:
1020 case IO_TIMEOUT:
1021 resultCode = LDAPResultCode.ADMIN_LIMIT_EXCEEDED;
1022 break;
1023 case CONNECTION_REJECTED:
1024 resultCode = LDAPResultCode.CONSTRAINT_VIOLATION;
1025 break;
1026 default:
1027 resultCode = LDAPResultCode.OTHER;
1028 break;
1029 }
1030
1031
1032 Message errMsg;
1033 if (message == null)
1034 {
1035 errMsg = INFO_LDAP_CLIENT_GENERIC_NOTICE_OF_DISCONNECTION.get();
1036 }
1037 else
1038 {
1039 errMsg = message;
1040 }
1041
1042
1043 ExtendedResponseProtocolOp notificationOp =
1044 new ExtendedResponseProtocolOp(resultCode, errMsg, null, null,
1045 OID_NOTICE_OF_DISCONNECTION, null);
1046 byte[] messageBytes =
1047 new LDAPMessage(0, notificationOp, null).encode().encode();
1048 ByteBuffer buffer = ByteBuffer.wrap(messageBytes);
1049 try
1050 {
1051 securityProvider.writeData(buffer);
1052 } catch (Exception e) {}
1053 }
1054 catch (Exception e)
1055 {
1056 // NYI -- Log a message indicating that we couldn't send the notice of
1057 // disconnection.
1058 }
1059 }
1060
1061
1062 // Close the connection to the client.
1063 try
1064 {
1065 securityProvider.disconnect(sendNotification);
1066 }
1067 catch (Exception e)
1068 {
1069 // In general, we don't care about any exception that might be thrown
1070 // here.
1071 if (debugEnabled())
1072 {
1073 TRACER.debugCaught(DebugLogLevel.ERROR, e);
1074 }
1075 }
1076
1077 try
1078 {
1079 clientChannel.close();
1080 }
1081 catch (Exception e)
1082 {
1083 // In general, we don't care about any exception that might be thrown
1084 // here.
1085 if (debugEnabled())
1086 {
1087 TRACER.debugCaught(DebugLogLevel.ERROR, e);
1088 }
1089 }
1090
1091
1092 // NYI -- Deregister the client connection from any server components that
1093 // might know about it.
1094
1095
1096 // Log a disconnect message.
1097 logDisconnect(this, disconnectReason, message);
1098
1099
1100 try
1101 {
1102 PluginConfigManager pluginManager =
1103 DirectoryServer.getPluginConfigManager();
1104 pluginManager.invokePostDisconnectPlugins(this, disconnectReason,
1105 message);
1106 }
1107 catch (Exception e)
1108 {
1109 if (debugEnabled())
1110 {
1111 TRACER.debugCaught(DebugLogLevel.ERROR, e);
1112 }
1113 }
1114 }
1115
1116
1117
1118 /**
1119 * Retrieves the set of operations in progress for this client connection.
1120 * This list must not be altered by any caller.
1121 *
1122 * @return The set of operations in progress for this client connection.
1123 */
1124 public Collection<AbstractOperation> getOperationsInProgress()
1125 {
1126 return operationsInProgress.values();
1127 }
1128
1129
1130
1131 /**
1132 * Retrieves the operation in progress with the specified message ID.
1133 *
1134 * @param messageID The message ID for the operation to retrieve.
1135 *
1136 * @return The operation in progress with the specified message ID, or
1137 * <CODE>null</CODE> if no such operation could be found.
1138 */
1139 public AbstractOperation getOperationInProgress(int messageID)
1140 {
1141 return operationsInProgress.get(messageID);
1142 }
1143
1144
1145
1146 /**
1147 * Adds the provided operation to the set of operations in progress for this
1148 * client connection.
1149 *
1150 * @param operation The operation to add to the set of operations in
1151 * progress for this client connection.
1152 *
1153 * @throws DirectoryException If the operation is not added for some reason
1154 * (e.g., the client already has reached the
1155 * maximum allowed concurrent requests).
1156 */
1157 public void addOperationInProgress(AbstractOperation operation)
1158 throws DirectoryException
1159 {
1160 int messageID = operation.getMessageID();
1161
1162 // We need to grab a lock to ensure that no one else can add operations to
1163 // the queue while we are performing some preliminary checks.
1164 synchronized (opsInProgressLock)
1165 {
1166 try
1167 {
1168 // If we're already in the process of disconnecting the client, then
1169 // reject the operation.
1170 if (disconnectRequested)
1171 {
1172 Message message = WARN_LDAP_CLIENT_DISCONNECT_IN_PROGRESS.get();
1173 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
1174 message);
1175 }
1176
1177
1178 // See if there is already an operation in progress with the same
1179 // message ID. If so, then we can't allow it.
1180 AbstractOperation op = operationsInProgress.get(messageID);
1181 if (op != null)
1182 {
1183 Message message =
1184 WARN_LDAP_CLIENT_DUPLICATE_MESSAGE_ID.get(messageID);
1185 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message);
1186 }
1187
1188
1189 // Add the operation to the list of operations in progress for this
1190 // connection.
1191 operationsInProgress.put(messageID, operation);
1192
1193
1194 // Try to add the operation to the work queue.
1195 DirectoryServer.enqueueRequest(operation);
1196 }
1197 catch (DirectoryException de)
1198 {
1199 if (debugEnabled())
1200 {
1201 TRACER.debugCaught(DebugLogLevel.ERROR, de);
1202 }
1203
1204 operationsInProgress.remove(messageID);
1205 lastCompletionTime.set(TimeThread.getTime());
1206
1207 throw de;
1208 }
1209 catch (Exception e)
1210 {
1211 if (debugEnabled())
1212 {
1213 TRACER.debugCaught(DebugLogLevel.ERROR, e);
1214 }
1215
1216 Message message =
1217 WARN_LDAP_CLIENT_CANNOT_ENQUEUE.get(getExceptionMessage(e));
1218 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
1219 message, e);
1220 }
1221 }
1222 }
1223
1224
1225
1226 /**
1227 * Removes the provided operation from the set of operations in progress for
1228 * this client connection. Note that this does not make any attempt to
1229 * cancel any processing that may already be in progress for the operation.
1230 *
1231 * @param messageID The message ID of the operation to remove from the set
1232 * of operations in progress.
1233 *
1234 * @return <CODE>true</CODE> if the operation was found and removed from the
1235 * set of operations in progress, or <CODE>false</CODE> if not.
1236 */
1237 public boolean removeOperationInProgress(int messageID)
1238 {
1239 AbstractOperation operation = operationsInProgress.remove(messageID);
1240 if (operation == null)
1241 {
1242 return false;
1243 }
1244 else
1245 {
1246 lastCompletionTime.set(TimeThread.getTime());
1247 return true;
1248 }
1249 }
1250
1251
1252
1253 /**
1254 * Attempts to cancel the specified operation.
1255 *
1256 * @param messageID The message ID of the operation to cancel.
1257 * @param cancelRequest An object providing additional information about how
1258 * the cancel should be processed.
1259 *
1260 * @return A cancel result that either indicates that the cancel was
1261 * successful or provides a reason that it was not.
1262 */
1263 public CancelResult cancelOperation(int messageID,
1264 CancelRequest cancelRequest)
1265 {
1266 AbstractOperation op = operationsInProgress.get(messageID);
1267 if (op == null)
1268 {
1269 // See if the operation is in the list of persistent searches.
1270 for (PersistentSearch ps : getPersistentSearches())
1271 {
1272 if (ps.getSearchOperation().getMessageID() == messageID)
1273 {
1274 CancelResult cancelResult =
1275 ps.getSearchOperation().cancel(cancelRequest);
1276
1277 if (keepStats && (cancelResult.getResultCode() ==
1278 ResultCode.CANCELED))
1279 {
1280 statTracker.updateAbandonedOperation();
1281 }
1282
1283 return cancelResult;
1284 }
1285 }
1286
1287 return new CancelResult(ResultCode.NO_SUCH_OPERATION, null);
1288 }
1289 else
1290 {
1291 CancelResult cancelResult = op.cancel(cancelRequest);
1292 if (keepStats && (cancelResult.getResultCode() == ResultCode.CANCELED))
1293 {
1294 statTracker.updateAbandonedOperation();
1295 }
1296
1297 return op.cancel(cancelRequest);
1298 }
1299 }
1300
1301
1302
1303 /**
1304 * Attempts to cancel all operations in progress on this connection.
1305 *
1306 * @param cancelRequest An object providing additional information about how
1307 * the cancel should be processed.
1308 */
1309 public void cancelAllOperations(CancelRequest cancelRequest)
1310 {
1311 // Make sure that no one can add any new operations.
1312 synchronized (opsInProgressLock)
1313 {
1314 try
1315 {
1316 for (AbstractOperation o : operationsInProgress.values())
1317 {
1318 try
1319 {
1320 o.abort(cancelRequest);
1321
1322 // TODO: Assume its cancelled?
1323 if (keepStats)
1324 {
1325 statTracker.updateAbandonedOperation();
1326 }
1327 }
1328 catch (Exception e)
1329 {
1330 if (debugEnabled())
1331 {
1332 TRACER.debugCaught(DebugLogLevel.ERROR, e);
1333 }
1334 }
1335 }
1336
1337 if (! (operationsInProgress.isEmpty() &&
1338 getPersistentSearches().isEmpty()))
1339 {
1340 lastCompletionTime.set(TimeThread.getTime());
1341 }
1342
1343 operationsInProgress.clear();
1344
1345
1346 for (PersistentSearch persistentSearch : getPersistentSearches())
1347 {
1348 DirectoryServer.deregisterPersistentSearch(persistentSearch);
1349 }
1350 }
1351 catch (Exception e)
1352 {
1353 if (debugEnabled())
1354 {
1355 TRACER.debugCaught(DebugLogLevel.ERROR, e);
1356 }
1357 }
1358 }
1359 }
1360
1361
1362
1363 /**
1364 * Attempts to cancel all operations in progress on this connection except the
1365 * operation with the specified message ID.
1366 *
1367 * @param cancelRequest An object providing additional information about how
1368 * the cancel should be processed.
1369 * @param messageID The message ID of the operation that should not be
1370 * canceled.
1371 */
1372 public void cancelAllOperationsExcept(CancelRequest cancelRequest,
1373 int messageID)
1374 {
1375 // Make sure that no one can add any new operations.
1376 synchronized (opsInProgressLock)
1377 {
1378 try
1379 {
1380 for (int msgID : operationsInProgress.keySet())
1381 {
1382 if (msgID == messageID)
1383 {
1384 continue;
1385 }
1386
1387 AbstractOperation o = operationsInProgress.get(msgID);
1388 if (o != null)
1389 {
1390 try
1391 {
1392 o.abort(cancelRequest);
1393
1394 // TODO: Assume its cancelled?
1395 if (keepStats)
1396 {
1397 statTracker.updateAbandonedOperation();
1398 }
1399 }
1400 catch (Exception e)
1401 {
1402 if (debugEnabled())
1403 {
1404 TRACER.debugCaught(DebugLogLevel.ERROR, e);
1405 }
1406 }
1407 }
1408
1409 operationsInProgress.remove(msgID);
1410 lastCompletionTime.set(TimeThread.getTime());
1411 }
1412
1413
1414 for (PersistentSearch persistentSearch : getPersistentSearches())
1415 {
1416 DirectoryServer.deregisterPersistentSearch(persistentSearch);
1417 lastCompletionTime.set(TimeThread.getTime());
1418 }
1419 }
1420 catch (Exception e)
1421 {
1422 if (debugEnabled())
1423 {
1424 TRACER.debugCaught(DebugLogLevel.ERROR, e);
1425 }
1426 }
1427 }
1428 }
1429
1430
1431
1432 /**
1433 * {@inheritDoc}
1434 */
1435 @Override()
1436 public Selector getWriteSelector()
1437 {
1438 Selector selector = writeSelector.get();
1439 if (selector == null)
1440 {
1441 try
1442 {
1443 selector = Selector.open();
1444 if (! writeSelector.compareAndSet(null, selector))
1445 {
1446 selector.close();
1447 selector = writeSelector.get();
1448 }
1449 }
1450 catch (Exception e)
1451 {
1452 if (debugEnabled())
1453 {
1454 TRACER.debugCaught(DebugLogLevel.ERROR, e);
1455 }
1456 }
1457 }
1458
1459 return selector;
1460 }
1461
1462
1463
1464 /**
1465 * {@inheritDoc}
1466 */
1467 @Override()
1468 public long getMaxBlockedWriteTimeLimit()
1469 {
1470 return connectionHandler.getMaxBlockedWriteTimeLimit();
1471 }
1472
1473
1474
1475 /**
1476 * Process the information contained in the provided byte buffer as an ASN.1
1477 * element. It may take several calls to this method in order to get all the
1478 * information necessary to decode a single ASN.1 element, but it may also be
1479 * possible that there are multiple elements (or at least fragments of
1480 * multiple elements) in a single buffer. This will fully process whatever
1481 * the client provided and set up the appropriate state information to make it
1482 * possible to pick up in the right place the next time around.
1483 *
1484 * @param buffer The buffer containing the data to be processed. It must be
1485 * ready for reading (i.e., it should have been flipped by the
1486 * caller), and the data provided must be unencrypted (e.g.,
1487 * if the client is communicating over SSL, then the
1488 * decryption should happen before calling this method).
1489 *
1490 * @return <CODE>true</CODE> if all the data in the provided buffer was
1491 * processed and the client connection can remain established, or
1492 * <CODE>false</CODE> if a decoding error occurred and requests from
1493 * this client should no longer be processed. Note that if this
1494 * method does return <CODE>false</CODE>, then it must have already
1495 * disconnected the client, and upon returning the request handler
1496 * should remove it from the associated selector.
1497 */
1498 public boolean processDataRead(ByteBuffer buffer)
1499 {
1500 if (debugEnabled())
1501 {
1502 TRACER.debugData(DebugLogLevel.VERBOSE, buffer);
1503 }
1504
1505
1506 int bytesAvailable = buffer.limit() - buffer.position();
1507
1508 if (keepStats)
1509 {
1510 statTracker.updateBytesRead(bytesAvailable);
1511 }
1512
1513 while (bytesAvailable > 0)
1514 {
1515 switch (elementReadState)
1516 {
1517 case ELEMENT_READ_STATE_NEED_TYPE:
1518 // Read just the type and then loop again to see if there is more.
1519 elementType = buffer.get();
1520 bytesAvailable--;
1521 elementReadState = ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE;
1522 continue;
1523
1524
1525 case ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE:
1526 // Get the first length byte and see if it is a single-byte or
1527 // multi-byte length.
1528 byte firstLengthByte = buffer.get();
1529 bytesAvailable--;
1530 elementLengthBytesNeeded = (firstLengthByte & 0x7F);
1531 if (elementLengthBytesNeeded == firstLengthByte)
1532 {
1533 elementLength = firstLengthByte;
1534
1535 // If the length is zero, then it cannot be a valid LDAP message.
1536 if (elementLength == 0)
1537 {
1538 disconnect(DisconnectReason.PROTOCOL_ERROR, true,
1539 ERR_LDAP_CLIENT_DECODE_ZERO_BYTE_VALUE.get());
1540 return false;
1541 }
1542
1543 // Make sure that the element is not larger than the maximum allowed
1544 // message size.
1545 if ((connectionHandler.getMaxRequestSize() > 0) &&
1546 (elementLength > connectionHandler.getMaxRequestSize()))
1547 {
1548 Message m = ERR_LDAP_CLIENT_DECODE_MAX_REQUEST_SIZE_EXCEEDED.get(
1549 elementLength, connectionHandler.getMaxRequestSize());
1550 disconnect(DisconnectReason.MAX_REQUEST_SIZE_EXCEEDED, true, m);
1551 return false;
1552 }
1553
1554 elementValue = new byte[elementLength];
1555 elementValueBytesRead = 0;
1556 elementValueBytesNeeded = elementLength;
1557 elementReadState = ELEMENT_READ_STATE_NEED_VALUE_BYTES;
1558 continue;
1559 }
1560 else
1561 {
1562 if (elementLengthBytesNeeded > 4)
1563 {
1564 // We cannot handle multi-byte lengths in which more than four
1565 // bytes are used to encode the length.
1566 Message m = ERR_LDAP_CLIENT_DECODE_INVALID_MULTIBYTE_LENGTH.get(
1567 elementLengthBytesNeeded);
1568 disconnect(DisconnectReason.PROTOCOL_ERROR, true, m);
1569 return false;
1570 }
1571
1572 elementLength = 0x00;
1573 if (elementLengthBytesNeeded <= bytesAvailable)
1574 {
1575 // We can read the entire length, so do it.
1576 while (elementLengthBytesNeeded > 0)
1577 {
1578 elementLength = (elementLength << 8) | (buffer.get() & 0xFF);
1579 bytesAvailable--;
1580 elementLengthBytesNeeded--;
1581 }
1582
1583 // If the length is zero, then it cannot be a valid LDAP message.
1584 if (elementLength == 0)
1585 {
1586 disconnect(DisconnectReason.PROTOCOL_ERROR, true,
1587 ERR_LDAP_CLIENT_DECODE_ZERO_BYTE_VALUE.get());
1588 return false;
1589 }
1590
1591 // Make sure that the element is not larger than the maximum
1592 // allowed message size.
1593 if ((connectionHandler.getMaxRequestSize() > 0) &&
1594 (elementLength > connectionHandler.getMaxRequestSize()))
1595 {
1596 disconnect(DisconnectReason.MAX_REQUEST_SIZE_EXCEEDED, true,
1597 ERR_LDAP_CLIENT_DECODE_MAX_REQUEST_SIZE_EXCEEDED.get(
1598 elementLength,
1599 connectionHandler.getMaxRequestSize()));
1600 return false;
1601 }
1602
1603 elementValue = new byte[elementLength];
1604 elementValueBytesRead = 0;
1605 elementValueBytesNeeded = elementLength;
1606 elementReadState = ELEMENT_READ_STATE_NEED_VALUE_BYTES;
1607 continue;
1608 }
1609 else
1610 {
1611 // We can't read the entire length, so just read what is
1612 // available.
1613 while (bytesAvailable > 0)
1614 {
1615 elementLength = (elementLength << 8) | (buffer.get() & 0xFF);
1616 bytesAvailable--;
1617 elementLengthBytesNeeded--;
1618 }
1619
1620 return true;
1621 }
1622 }
1623
1624
1625 case ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES:
1626 if (bytesAvailable >= elementLengthBytesNeeded)
1627 {
1628 // We have enough data available to be able to read the entire
1629 // length. Do so.
1630 while (elementLengthBytesNeeded > 0)
1631 {
1632 elementLength = (elementLength << 8) | (buffer.get() & 0xFF);
1633 bytesAvailable--;
1634 elementLengthBytesNeeded--;
1635 }
1636
1637 // If the length is zero, then it cannot be a valid LDAP message.
1638 if (elementLength == 0)
1639 {
1640 disconnect(DisconnectReason.PROTOCOL_ERROR, true,
1641 ERR_LDAP_CLIENT_DECODE_ZERO_BYTE_VALUE.get());
1642 return false;
1643 }
1644
1645 // Make sure that the element is not larger than the maximum allowed
1646 // message size.
1647 if ((connectionHandler.getMaxRequestSize() > 0) &&
1648 (elementLength > connectionHandler.getMaxRequestSize()))
1649 {
1650 disconnect(DisconnectReason.MAX_REQUEST_SIZE_EXCEEDED, true,
1651 ERR_LDAP_CLIENT_DECODE_MAX_REQUEST_SIZE_EXCEEDED.get(
1652 elementLength,
1653 connectionHandler.getMaxRequestSize()));
1654 return false;
1655 }
1656
1657 elementValue = new byte[elementLength];
1658 elementValueBytesRead = 0;
1659 elementValueBytesNeeded = elementLength;
1660 elementReadState = ELEMENT_READ_STATE_NEED_VALUE_BYTES;
1661 continue;
1662 }
1663 else
1664 {
1665 // We still don't have enough data to complete the length, so just
1666 // read as much as possible.
1667 while (bytesAvailable > 0)
1668 {
1669 elementLength = (elementLength << 8) | (buffer.get() & 0xFF);
1670 bytesAvailable--;
1671 elementLengthBytesNeeded--;
1672 }
1673
1674 return true;
1675 }
1676
1677
1678 case ELEMENT_READ_STATE_NEED_VALUE_BYTES:
1679 if (bytesAvailable >= elementValueBytesNeeded)
1680 {
1681 // We have enough data available to fully read the value. Finish
1682 // reading the information and convert it to an ASN.1 element. Then
1683 // decode that as an LDAP message.
1684 buffer.get(elementValue, elementValueBytesRead,
1685 elementValueBytesNeeded);
1686 elementValueBytesRead += elementValueBytesNeeded;
1687 bytesAvailable -= elementValueBytesNeeded;
1688 elementReadState = ELEMENT_READ_STATE_NEED_TYPE;
1689
1690 ASN1Sequence requestSequence;
1691 try
1692 {
1693 requestSequence = ASN1Sequence.decodeAsSequence(elementType,
1694 elementValue);
1695 TRACER.debugProtocolElement(DebugLogLevel.VERBOSE,
1696 requestSequence);
1697 }
1698 catch (Exception e)
1699 {
1700 if (debugEnabled())
1701 {
1702 TRACER.debugCaught(DebugLogLevel.ERROR, e);
1703 }
1704 Message m = ERR_LDAP_CLIENT_DECODE_ASN1_FAILED.get(
1705 String.valueOf(e));
1706 disconnect(DisconnectReason.PROTOCOL_ERROR, true, m);
1707 return false;
1708 }
1709
1710 LDAPMessage requestMessage;
1711 try
1712 {
1713 requestMessage = LDAPMessage.decode(requestSequence);
1714 TRACER.debugProtocolElement(DebugLogLevel.VERBOSE,
1715 requestMessage);
1716 }
1717 catch (Exception e)
1718 {
1719 if (debugEnabled())
1720 {
1721 TRACER.debugCaught(DebugLogLevel.ERROR, e);
1722 }
1723 Message m = ERR_LDAP_CLIENT_DECODE_LDAP_MESSAGE_FAILED.get(
1724 String.valueOf(e));
1725 disconnect(DisconnectReason.PROTOCOL_ERROR, true, m);
1726 return false;
1727 }
1728
1729 if (processLDAPMessage(requestMessage))
1730 {
1731 continue;
1732 }
1733 else
1734 {
1735 return false;
1736 }
1737 }
1738 else
1739 {
1740 // We can't read all the value, so just read as much as we have
1741 // available and pick it up again the next time around.
1742 buffer.get(elementValue, elementValueBytesRead, bytesAvailable);
1743 elementValueBytesRead += bytesAvailable;
1744 elementValueBytesNeeded -= bytesAvailable;
1745 return true;
1746 }
1747
1748
1749 default:
1750 // This should never happen. There is an invalid internal read state.
1751 // The only recourse that we have is to log a message and disconnect
1752 // the client.
1753 Message message =
1754 ERR_LDAP_CLIENT_INVALID_DECODE_STATE.get(elementReadState);
1755 logError(message);
1756 disconnect(DisconnectReason.SERVER_ERROR, true, message);
1757 return false;
1758 }
1759 }
1760
1761
1762 // If we've gotten here, then all of the data must have been processed
1763 // properly so we can return true.
1764 return true;
1765 }
1766
1767
1768
1769 /**
1770 * Processes the provided LDAP message read from the client and takes
1771 * whatever action is appropriate. For most requests, this will include
1772 * placing the operation in the work queue. Certain requests (in particular,
1773 * abandons and unbinds) will be processed directly.
1774 *
1775 * @param message The LDAP message to process.
1776 *
1777 * @return <CODE>true</CODE> if the appropriate action was taken for the
1778 * request, or <CODE>false</CODE> if there was a fatal error and
1779 * the client has been disconnected as a result, or if the client
1780 * unbound from the server.
1781 */
1782 private boolean processLDAPMessage(LDAPMessage message)
1783 {
1784 if (keepStats)
1785 {
1786 statTracker.updateMessageRead(message);
1787 }
1788
1789 ArrayList<Control> opControls;
1790 ArrayList<LDAPControl> ldapControls = message.getControls();
1791 if ((ldapControls == null) || ldapControls.isEmpty())
1792 {
1793 opControls = null;
1794 }
1795 else
1796 {
1797 opControls = new ArrayList<Control>(ldapControls.size());
1798 for (LDAPControl c : ldapControls)
1799 {
1800 opControls.add(c.getControl());
1801 }
1802 }
1803
1804
1805 // FIXME -- See if there is a bind in progress. If so, then deny most
1806 // kinds of operations.
1807
1808
1809 // Figure out what type of operation we're dealing with based on the LDAP
1810 // message. Abandon and unbind requests will be processed here. All other
1811 // types of requests will be encapsulated into operations and put into the
1812 // work queue to be picked up by a worker thread. Any other kinds of
1813 // LDAP messages (e.g., response messages) are illegal and will result in
1814 // the connection being terminated.
1815 try
1816 {
1817 switch (message.getProtocolOpType())
1818 {
1819 case OP_TYPE_ABANDON_REQUEST:
1820 return processAbandonRequest(message, opControls);
1821 case OP_TYPE_ADD_REQUEST:
1822 return processAddRequest(message, opControls);
1823 case OP_TYPE_BIND_REQUEST:
1824 return processBindRequest(message, opControls);
1825 case OP_TYPE_COMPARE_REQUEST:
1826 return processCompareRequest(message, opControls);
1827 case OP_TYPE_DELETE_REQUEST:
1828 return processDeleteRequest(message, opControls);
1829 case OP_TYPE_EXTENDED_REQUEST:
1830 return processExtendedRequest(message, opControls);
1831 case OP_TYPE_MODIFY_REQUEST:
1832 return processModifyRequest(message, opControls);
1833 case OP_TYPE_MODIFY_DN_REQUEST:
1834 return processModifyDNRequest(message, opControls);
1835 case OP_TYPE_SEARCH_REQUEST:
1836 return processSearchRequest(message, opControls);
1837 case OP_TYPE_UNBIND_REQUEST:
1838 return processUnbindRequest(message, opControls);
1839 default:
1840 Message msg = ERR_LDAP_DISCONNECT_DUE_TO_INVALID_REQUEST_TYPE.get(
1841 message.getProtocolOpName(), message.getMessageID());
1842 disconnect(DisconnectReason.PROTOCOL_ERROR, true, msg);
1843 return false;
1844 }
1845 }
1846 catch (Exception e)
1847 {
1848 if (debugEnabled())
1849 {
1850 TRACER.debugCaught(DebugLogLevel.ERROR, e);
1851 }
1852
1853 Message msg = ERR_LDAP_DISCONNECT_DUE_TO_PROCESSING_FAILURE.get(
1854 message.getProtocolOpName(),
1855 message.getMessageID(), String.valueOf(e));
1856 disconnect(DisconnectReason.SERVER_ERROR, true, msg);
1857 return false;
1858 }
1859 }
1860
1861
1862
1863 /**
1864 * Processes the provided LDAP message as an abandon request.
1865 *
1866 * @param message The LDAP message containing the abandon request to
1867 * process.
1868 * @param controls The set of pre-decoded request controls contained in the
1869 * message.
1870 *
1871 * @return <CODE>true</CODE> if the request was processed successfully, or
1872 * <CODE>false</CODE> if not and the connection has been closed as a
1873 * result (it is the responsibility of this method to close the
1874 * connection).
1875 */
1876 private boolean processAbandonRequest(LDAPMessage message,
1877 ArrayList<Control> controls)
1878 {
1879 AbandonRequestProtocolOp protocolOp = message.getAbandonRequestProtocolOp();
1880 AbandonOperationBasis abandonOp =
1881 new AbandonOperationBasis(this, nextOperationID.getAndIncrement(),
1882 message.getMessageID(), controls,
1883 protocolOp.getIDToAbandon());
1884
1885 abandonOp.run();
1886 if (keepStats && (abandonOp.getResultCode() == ResultCode.CANCELED))
1887 {
1888 statTracker.updateAbandonedOperation();
1889 }
1890
1891 return connectionValid;
1892 }
1893
1894
1895
1896 /**
1897 * Processes the provided LDAP message as an add request.
1898 *
1899 * @param message The LDAP message containing the add request to process.
1900 * @param controls The set of pre-decoded request controls contained in the
1901 * message.
1902 *
1903 * @return <CODE>true</CODE> if the request was processed successfully, or
1904 * <CODE>false</CODE> if not and the connection has been closed as a
1905 * result (it is the responsibility of this method to close the
1906 * connection).
1907 */
1908 private boolean processAddRequest(LDAPMessage message,
1909 ArrayList<Control> controls)
1910 {
1911 if ((ldapVersion == 2) && (controls != null) && (! controls.isEmpty()))
1912 {
1913 // LDAPv2 clients aren't allowed to send controls.
1914 AddResponseProtocolOp responseOp =
1915 new AddResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
1916 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
1917 sendLDAPMessage(securityProvider,
1918 new LDAPMessage(message.getMessageID(), responseOp));
1919 disconnect(DisconnectReason.PROTOCOL_ERROR, false,
1920 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
1921 return false;
1922 }
1923
1924 // Create the add operation and add it into the work queue.
1925 AddRequestProtocolOp protocolOp = message.getAddRequestProtocolOp();
1926 AddOperationBasis addOp =
1927 new AddOperationBasis(this, nextOperationID.getAndIncrement(),
1928 message.getMessageID(), controls, protocolOp.getDN(),
1929 protocolOp.getAttributes());
1930
1931 try
1932 {
1933 addOperationInProgress(addOp);
1934 }
1935 catch (DirectoryException de)
1936 {
1937 if (debugEnabled())
1938 {
1939 TRACER.debugCaught(DebugLogLevel.ERROR, de);
1940 }
1941
1942 AddResponseProtocolOp responseOp =
1943 new AddResponseProtocolOp(de.getResultCode().getIntValue(),
1944 de.getMessageObject(), de.getMatchedDN(),
1945 de.getReferralURLs());
1946
1947 List<Control> responseControls = addOp.getResponseControls();
1948 ArrayList<LDAPControl> responseLDAPControls =
1949 new ArrayList<LDAPControl>(responseControls.size());
1950 for (Control c : responseControls)
1951 {
1952 responseLDAPControls.add(new LDAPControl(c));
1953 }
1954
1955 sendLDAPMessage(securityProvider,
1956 new LDAPMessage(message.getMessageID(), responseOp,
1957 responseLDAPControls));
1958 }
1959
1960
1961 return connectionValid;
1962 }
1963
1964
1965
1966 /**
1967 * Processes the provided LDAP message as a bind request.
1968 *
1969 * @param message The LDAP message containing the bind request to process.
1970 * @param controls The set of pre-decoded request controls contained in the
1971 * message.
1972 *
1973 * @return <CODE>true</CODE> if the request was processed successfully, or
1974 * <CODE>false</CODE> if not and the connection has been closed as a
1975 * result (it is the responsibility of this method to close the
1976 * connection).
1977 */
1978 private boolean processBindRequest(LDAPMessage message,
1979 ArrayList<Control> controls)
1980 {
1981 BindRequestProtocolOp protocolOp = message.getBindRequestProtocolOp();
1982
1983 // See if this is an LDAPv2 bind request, and if so whether that should be
1984 // allowed.
1985 String versionString;
1986 switch (ldapVersion = protocolOp.getProtocolVersion())
1987 {
1988 case 2:
1989 versionString = "2";
1990
1991 if (! connectionHandler.allowLDAPv2())
1992 {
1993 BindResponseProtocolOp responseOp =
1994 new BindResponseProtocolOp(
1995 LDAPResultCode.INAPPROPRIATE_AUTHENTICATION,
1996 ERR_LDAPV2_CLIENTS_NOT_ALLOWED.get());
1997 sendLDAPMessage(securityProvider,
1998 new LDAPMessage(message.getMessageID(), responseOp));
1999 disconnect(DisconnectReason.PROTOCOL_ERROR, false,
2000 ERR_LDAPV2_CLIENTS_NOT_ALLOWED.get());
2001 return false;
2002 }
2003
2004 if ((controls != null) && (! controls.isEmpty()))
2005 {
2006 // LDAPv2 clients aren't allowed to send controls.
2007 BindResponseProtocolOp responseOp =
2008 new BindResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
2009 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
2010 sendLDAPMessage(securityProvider,
2011 new LDAPMessage(message.getMessageID(), responseOp));
2012 disconnect(DisconnectReason.PROTOCOL_ERROR, false,
2013 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
2014 return false;
2015 }
2016
2017 break;
2018 case 3:
2019 versionString = "3";
2020 break;
2021 default:
2022 versionString = String.valueOf(ldapVersion);
2023 break;
2024 }
2025
2026
2027 ASN1OctetString bindDN = protocolOp.getDN();
2028
2029 BindOperationBasis bindOp;
2030 switch (protocolOp.getAuthenticationType())
2031 {
2032 case SIMPLE:
2033 bindOp = new BindOperationBasis(this, nextOperationID.getAndIncrement(),
2034 message.getMessageID(), controls,
2035 versionString, bindDN,
2036 protocolOp.getSimplePassword());
2037 break;
2038 case SASL:
2039 bindOp = new BindOperationBasis(this, nextOperationID.getAndIncrement(),
2040 message.getMessageID(), controls,
2041 versionString, bindDN,
2042 protocolOp.getSASLMechanism(),
2043 protocolOp.getSASLCredentials());
2044 break;
2045 default:
2046 // This is an invalid authentication type, and therefore a protocol
2047 // error. As per RFC 2251, a protocol error in a bind request must
2048 // result in terminating the connection.
2049 Message msg =
2050 ERR_LDAP_INVALID_BIND_AUTH_TYPE.get(message.getMessageID(),
2051 String.valueOf(protocolOp.getAuthenticationType()));
2052 disconnect(DisconnectReason.PROTOCOL_ERROR, true, msg);
2053 return false;
2054 }
2055
2056 // Add the operation into the work queue.
2057 try
2058 {
2059 addOperationInProgress(bindOp);
2060 }
2061 catch (DirectoryException de)
2062 {
2063 if (debugEnabled())
2064 {
2065 TRACER.debugCaught(DebugLogLevel.ERROR, de);
2066 }
2067
2068 BindResponseProtocolOp responseOp =
2069 new BindResponseProtocolOp(de.getResultCode().getIntValue(),
2070 de.getMessageObject(), de.getMatchedDN(),
2071 de.getReferralURLs());
2072
2073 List<Control> responseControls = bindOp.getResponseControls();
2074 ArrayList<LDAPControl> responseLDAPControls =
2075 new ArrayList<LDAPControl>(responseControls.size());
2076 for (Control c : responseControls)
2077 {
2078 responseLDAPControls.add(new LDAPControl(c));
2079 }
2080
2081 sendLDAPMessage(securityProvider,
2082 new LDAPMessage(message.getMessageID(), responseOp,
2083 responseLDAPControls));
2084
2085 // If it was a protocol error, then terminate the connection.
2086 if (de.getResultCode() == ResultCode.PROTOCOL_ERROR)
2087 {
2088 Message msg = ERR_LDAP_DISCONNECT_DUE_TO_BIND_PROTOCOL_ERROR.get(
2089 message.getMessageID(), de.getMessageObject());
2090 disconnect(DisconnectReason.PROTOCOL_ERROR, true, msg);
2091 }
2092 }
2093
2094
2095 return connectionValid;
2096 }
2097
2098
2099
2100 /**
2101 * Processes the provided LDAP message as a compare request.
2102 *
2103 * @param message The LDAP message containing the compare request to
2104 * process.
2105 * @param controls The set of pre-decoded request controls contained in the
2106 * message.
2107 *
2108 * @return <CODE>true</CODE> if the request was processed successfully, or
2109 * <CODE>false</CODE> if not and the connection has been closed as a
2110 * result (it is the responsibility of this method to close the
2111 * connection).
2112 */
2113 private boolean processCompareRequest(LDAPMessage message,
2114 ArrayList<Control> controls)
2115 {
2116 if ((ldapVersion == 2) && (controls != null) && (! controls.isEmpty()))
2117 {
2118 // LDAPv2 clients aren't allowed to send controls.
2119 CompareResponseProtocolOp responseOp =
2120 new CompareResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
2121 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
2122 sendLDAPMessage(securityProvider,
2123 new LDAPMessage(message.getMessageID(), responseOp));
2124 disconnect(DisconnectReason.PROTOCOL_ERROR, false,
2125 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
2126 return false;
2127 }
2128
2129 CompareRequestProtocolOp protocolOp = message.getCompareRequestProtocolOp();
2130 CompareOperationBasis compareOp =
2131 new CompareOperationBasis(this, nextOperationID.getAndIncrement(),
2132 message.getMessageID(), controls,
2133 protocolOp.getDN(), protocolOp.getAttributeType(),
2134 protocolOp.getAssertionValue());
2135
2136 // Add the operation into the work queue.
2137 try
2138 {
2139 addOperationInProgress(compareOp);
2140 }
2141 catch (DirectoryException de)
2142 {
2143 if (debugEnabled())
2144 {
2145 TRACER.debugCaught(DebugLogLevel.ERROR, de);
2146 }
2147
2148 CompareResponseProtocolOp responseOp =
2149 new CompareResponseProtocolOp(de.getResultCode().getIntValue(),
2150 de.getMessageObject(),
2151 de.getMatchedDN(),
2152 de.getReferralURLs());
2153
2154 List<Control> responseControls = compareOp.getResponseControls();
2155 ArrayList<LDAPControl> responseLDAPControls =
2156 new ArrayList<LDAPControl>(responseControls.size());
2157 for (Control c : responseControls)
2158 {
2159 responseLDAPControls.add(new LDAPControl(c));
2160 }
2161
2162 sendLDAPMessage(securityProvider,
2163 new LDAPMessage(message.getMessageID(), responseOp,
2164 responseLDAPControls));
2165 }
2166
2167
2168 return connectionValid;
2169 }
2170
2171
2172
2173 /**
2174 * Processes the provided LDAP message as a delete request.
2175 *
2176 * @param message The LDAP message containing the delete request to
2177 * process.
2178 * @param controls The set of pre-decoded request controls contained in the
2179 * message.
2180 *
2181 * @return <CODE>true</CODE> if the request was processed successfully, or
2182 * <CODE>false</CODE> if not and the connection has been closed as a
2183 * result (it is the responsibility of this method to close the
2184 * connection).
2185 */
2186 private boolean processDeleteRequest(LDAPMessage message,
2187 ArrayList<Control> controls)
2188 {
2189 if ((ldapVersion == 2) && (controls != null) && (! controls.isEmpty()))
2190 {
2191 // LDAPv2 clients aren't allowed to send controls.
2192 DeleteResponseProtocolOp responseOp =
2193 new DeleteResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
2194 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
2195 sendLDAPMessage(securityProvider,
2196 new LDAPMessage(message.getMessageID(), responseOp));
2197 disconnect(DisconnectReason.PROTOCOL_ERROR, false,
2198 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
2199 return false;
2200 }
2201
2202 DeleteRequestProtocolOp protocolOp = message.getDeleteRequestProtocolOp();
2203 DeleteOperationBasis deleteOp =
2204 new DeleteOperationBasis(this, nextOperationID.getAndIncrement(),
2205 message.getMessageID(), controls,
2206 protocolOp.getDN());
2207
2208 // Add the operation into the work queue.
2209 try
2210 {
2211 addOperationInProgress(deleteOp);
2212 }
2213 catch (DirectoryException de)
2214 {
2215 if (debugEnabled())
2216 {
2217 TRACER.debugCaught(DebugLogLevel.ERROR, de);
2218 }
2219
2220 DeleteResponseProtocolOp responseOp =
2221 new DeleteResponseProtocolOp(de.getResultCode().getIntValue(),
2222 de.getMessageObject(),
2223 de.getMatchedDN(),
2224 de.getReferralURLs());
2225
2226 List<Control> responseControls = deleteOp.getResponseControls();
2227 ArrayList<LDAPControl> responseLDAPControls =
2228 new ArrayList<LDAPControl>(responseControls.size());
2229 for (Control c : responseControls)
2230 {
2231 responseLDAPControls.add(new LDAPControl(c));
2232 }
2233
2234 sendLDAPMessage(securityProvider,
2235 new LDAPMessage(message.getMessageID(), responseOp,
2236 responseLDAPControls));
2237 }
2238
2239
2240 return connectionValid;
2241 }
2242
2243
2244
2245 /**
2246 * Processes the provided LDAP message as an extended request.
2247 *
2248 * @param message The LDAP message containing the extended request to
2249 * process.
2250 * @param controls The set of pre-decoded request controls contained in the
2251 * message.
2252 *
2253 * @return <CODE>true</CODE> if the request was processed successfully, or
2254 * <CODE>false</CODE> if not and the connection has been closed as a
2255 * result (it is the responsibility of this method to close the
2256 * connection).
2257 */
2258 private boolean processExtendedRequest(LDAPMessage message,
2259 ArrayList<Control> controls)
2260 {
2261 // See if this is an LDAPv2 client. If it is, then they should not be
2262 // issuing extended requests. We can't send a response that we can be sure
2263 // they can understand, so we have no choice but to close the connection.
2264 if (ldapVersion == 2)
2265 {
2266 Message msg = ERR_LDAPV2_EXTENDED_REQUEST_NOT_ALLOWED.get(
2267 getConnectionID(), message.getMessageID());
2268 logError(msg);
2269 disconnect(DisconnectReason.PROTOCOL_ERROR, false, msg);
2270 return false;
2271 }
2272
2273
2274 // FIXME -- Do we need to handle certain types of request here?
2275 // -- StartTLS requests
2276 // -- Cancel requests
2277
2278
2279 ExtendedRequestProtocolOp protocolOp =
2280 message.getExtendedRequestProtocolOp();
2281 ExtendedOperationBasis extendedOp =
2282 new ExtendedOperationBasis(this, nextOperationID.getAndIncrement(),
2283 message.getMessageID(), controls,
2284 protocolOp.getOID(), protocolOp.getValue());
2285
2286 // Add the operation into the work queue.
2287 try
2288 {
2289 addOperationInProgress(extendedOp);
2290 }
2291 catch (DirectoryException de)
2292 {
2293 if (debugEnabled())
2294 {
2295 TRACER.debugCaught(DebugLogLevel.ERROR, de);
2296 }
2297
2298 ExtendedResponseProtocolOp responseOp =
2299 new ExtendedResponseProtocolOp(de.getResultCode().getIntValue(),
2300 de.getMessageObject(),
2301 de.getMatchedDN(),
2302 de.getReferralURLs());
2303
2304 List<Control> responseControls = extendedOp.getResponseControls();
2305 ArrayList<LDAPControl> responseLDAPControls =
2306 new ArrayList<LDAPControl>(responseControls.size());
2307 for (Control c : responseControls)
2308 {
2309 responseLDAPControls.add(new LDAPControl(c));
2310 }
2311
2312 sendLDAPMessage(securityProvider,
2313 new LDAPMessage(message.getMessageID(), responseOp,
2314 responseLDAPControls));
2315 }
2316
2317
2318 return connectionValid;
2319 }
2320
2321
2322
2323 /**
2324 * Processes the provided LDAP message as a modify request.
2325 *
2326 * @param message The LDAP message containing the modify request to
2327 * process.
2328 * @param controls The set of pre-decoded request controls contained in the
2329 * message.
2330 *
2331 * @return <CODE>true</CODE> if the request was processed successfully, or
2332 * <CODE>false</CODE> if not and the connection has been closed as a
2333 * result (it is the responsibility of this method to close the
2334 * connection).
2335 */
2336 private boolean processModifyRequest(LDAPMessage message,
2337 ArrayList<Control> controls)
2338 {
2339 if ((ldapVersion == 2) && (controls != null) && (! controls.isEmpty()))
2340 {
2341 // LDAPv2 clients aren't allowed to send controls.
2342 ModifyResponseProtocolOp responseOp =
2343 new ModifyResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
2344 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
2345 sendLDAPMessage(securityProvider,
2346 new LDAPMessage(message.getMessageID(), responseOp));
2347 disconnect(DisconnectReason.PROTOCOL_ERROR, false,
2348 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
2349 return false;
2350 }
2351
2352 ModifyRequestProtocolOp protocolOp = message.getModifyRequestProtocolOp();
2353 ModifyOperationBasis modifyOp =
2354 new ModifyOperationBasis(this, nextOperationID.getAndIncrement(),
2355 message.getMessageID(), controls,
2356 protocolOp.getDN(), protocolOp.getModifications());
2357
2358 // Add the operation into the work queue.
2359 try
2360 {
2361 addOperationInProgress(modifyOp);
2362 }
2363 catch (DirectoryException de)
2364 {
2365 if (debugEnabled())
2366 {
2367 TRACER.debugCaught(DebugLogLevel.ERROR, de);
2368 }
2369
2370 ModifyResponseProtocolOp responseOp =
2371 new ModifyResponseProtocolOp(de.getResultCode().getIntValue(),
2372 de.getMessageObject(),
2373 de.getMatchedDN(),
2374 de.getReferralURLs());
2375
2376 List<Control> responseControls = modifyOp.getResponseControls();
2377 ArrayList<LDAPControl> responseLDAPControls =
2378 new ArrayList<LDAPControl>(responseControls.size());
2379 for (Control c : responseControls)
2380 {
2381 responseLDAPControls.add(new LDAPControl(c));
2382 }
2383
2384 sendLDAPMessage(securityProvider,
2385 new LDAPMessage(message.getMessageID(), responseOp,
2386 responseLDAPControls));
2387 }
2388
2389
2390 return connectionValid;
2391 }
2392
2393
2394
2395 /**
2396 * Processes the provided LDAP message as a modify DN request.
2397 *
2398 * @param message The LDAP message containing the modify DN request to
2399 * process.
2400 * @param controls The set of pre-decoded request controls contained in the
2401 * message.
2402 *
2403 * @return <CODE>true</CODE> if the request was processed successfully, or
2404 * <CODE>false</CODE> if not and the connection has been closed as a
2405 * result (it is the responsibility of this method to close the
2406 * connection).
2407 */
2408 private boolean processModifyDNRequest(LDAPMessage message,
2409 ArrayList<Control> controls)
2410 {
2411 if ((ldapVersion == 2) && (controls != null) && (! controls.isEmpty()))
2412 {
2413 // LDAPv2 clients aren't allowed to send controls.
2414 ModifyDNResponseProtocolOp responseOp =
2415 new ModifyDNResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
2416 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
2417 sendLDAPMessage(securityProvider,
2418 new LDAPMessage(message.getMessageID(), responseOp));
2419 disconnect(DisconnectReason.PROTOCOL_ERROR, false,
2420 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
2421 return false;
2422 }
2423
2424 ModifyDNRequestProtocolOp protocolOp =
2425 message.getModifyDNRequestProtocolOp();
2426 ModifyDNOperationBasis modifyDNOp =
2427 new ModifyDNOperationBasis(this, nextOperationID.getAndIncrement(),
2428 message.getMessageID(), controls,
2429 protocolOp.getEntryDN(), protocolOp.getNewRDN(),
2430 protocolOp.deleteOldRDN(),
2431 protocolOp.getNewSuperior());
2432
2433 // Add the operation into the work queue.
2434 try
2435 {
2436 addOperationInProgress(modifyDNOp);
2437 }
2438 catch (DirectoryException de)
2439 {
2440 if (debugEnabled())
2441 {
2442 TRACER.debugCaught(DebugLogLevel.ERROR, de);
2443 }
2444
2445 ModifyDNResponseProtocolOp responseOp =
2446 new ModifyDNResponseProtocolOp(de.getResultCode().getIntValue(),
2447 de.getMessageObject(),
2448 de.getMatchedDN(),
2449 de.getReferralURLs());
2450
2451 List<Control> responseControls = modifyDNOp.getResponseControls();
2452 ArrayList<LDAPControl> responseLDAPControls =
2453 new ArrayList<LDAPControl>(responseControls.size());
2454 for (Control c : responseControls)
2455 {
2456 responseLDAPControls.add(new LDAPControl(c));
2457 }
2458
2459 sendLDAPMessage(securityProvider,
2460 new LDAPMessage(message.getMessageID(), responseOp,
2461 responseLDAPControls));
2462 }
2463
2464
2465 return connectionValid;
2466 }
2467
2468
2469
2470 /**
2471 * Processes the provided LDAP message as a search request.
2472 *
2473 * @param message The LDAP message containing the search request to
2474 * process.
2475 * @param controls The set of pre-decoded request controls contained in the
2476 * message.
2477 *
2478 * @return <CODE>true</CODE> if the request was processed successfully, or
2479 * <CODE>false</CODE> if not and the connection has been closed as a
2480 * result (it is the responsibility of this method to close the
2481 * connection).
2482 */
2483 private boolean processSearchRequest(LDAPMessage message,
2484 ArrayList<Control> controls)
2485 {
2486 if ((ldapVersion == 2) && (controls != null) && (! controls.isEmpty()))
2487 {
2488 // LDAPv2 clients aren't allowed to send controls.
2489 SearchResultDoneProtocolOp responseOp =
2490 new SearchResultDoneProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
2491 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
2492 sendLDAPMessage(securityProvider,
2493 new LDAPMessage(message.getMessageID(), responseOp));
2494 disconnect(DisconnectReason.PROTOCOL_ERROR, false,
2495 ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
2496 return false;
2497 }
2498
2499 SearchRequestProtocolOp protocolOp = message.getSearchRequestProtocolOp();
2500 SearchOperationBasis searchOp =
2501 new SearchOperationBasis(this, nextOperationID.getAndIncrement(),
2502 message.getMessageID(), controls,
2503 protocolOp.getBaseDN(), protocolOp.getScope(),
2504 protocolOp.getDereferencePolicy(),
2505 protocolOp.getSizeLimit(),
2506 protocolOp.getTimeLimit(),
2507 protocolOp.getTypesOnly(), protocolOp.getFilter(),
2508 protocolOp.getAttributes());
2509
2510 // Add the operation into the work queue.
2511 try
2512 {
2513 addOperationInProgress(searchOp);
2514 }
2515 catch (DirectoryException de)
2516 {
2517 if (debugEnabled())
2518 {
2519 TRACER.debugCaught(DebugLogLevel.ERROR, de);
2520 }
2521
2522 SearchResultDoneProtocolOp responseOp =
2523 new SearchResultDoneProtocolOp(de.getResultCode().getIntValue(),
2524 de.getMessageObject(),
2525 de.getMatchedDN(),
2526 de.getReferralURLs());
2527
2528 List<Control> responseControls = searchOp.getResponseControls();
2529 ArrayList<LDAPControl> responseLDAPControls =
2530 new ArrayList<LDAPControl>(responseControls.size());
2531 for (Control c : responseControls)
2532 {
2533 responseLDAPControls.add(new LDAPControl(c));
2534 }
2535
2536 sendLDAPMessage(securityProvider,
2537 new LDAPMessage(message.getMessageID(), responseOp,
2538 responseLDAPControls));
2539 }
2540
2541
2542 return connectionValid;
2543 }
2544
2545
2546
2547 /**
2548 * Processes the provided LDAP message as an unbind request.
2549 *
2550 * @param message The LDAP message containing the unbind request to
2551 * process.
2552 * @param controls The set of pre-decoded request controls contained in the
2553 * message.
2554 *
2555 * @return <CODE>true</CODE> if the request was processed successfully, or
2556 * <CODE>false</CODE> if not and the connection has been closed as a
2557 * result (it is the responsibility of this method to close the
2558 * connection).
2559 */
2560 private boolean processUnbindRequest(LDAPMessage message,
2561 ArrayList<Control> controls)
2562 {
2563 UnbindOperationBasis unbindOp =
2564 new UnbindOperationBasis(this, nextOperationID.getAndIncrement(),
2565 message.getMessageID(), controls);
2566
2567 unbindOp.run();
2568
2569 // The client connection will never be valid after an unbind.
2570 return false;
2571 }
2572
2573
2574
2575 /**
2576 * {@inheritDoc}
2577 */
2578 public String getMonitorSummary()
2579 {
2580 StringBuilder buffer = new StringBuilder();
2581 buffer.append("connID=\"");
2582 buffer.append(connectionID);
2583 buffer.append("\" connectTime=\"");
2584 buffer.append(getConnectTimeString());
2585 buffer.append("\" source=\"");
2586 buffer.append(clientAddress);
2587 buffer.append(":");
2588 buffer.append(clientPort);
2589 buffer.append("\" destination=\"");
2590 buffer.append(serverAddress);
2591 buffer.append(":");
2592 buffer.append(connectionHandler.getListenPort());
2593 buffer.append("\" ldapVersion=\"");
2594 buffer.append(ldapVersion);
2595 buffer.append("\" authDN=\"");
2596
2597 DN authDN = getAuthenticationInfo().getAuthenticationDN();
2598 if (authDN != null)
2599 {
2600 authDN.toString(buffer);
2601 }
2602
2603 buffer.append("\" security=\"");
2604 if (securityProvider.isSecure())
2605 {
2606 buffer.append(securityProvider.getSecurityMechanismName());
2607 }
2608 else
2609 {
2610 buffer.append("none");
2611 }
2612
2613 buffer.append("\" opsInProgress=\"");
2614 buffer.append(operationsInProgress.size());
2615 buffer.append("\"");
2616
2617 return buffer.toString();
2618 }
2619
2620
2621
2622 /**
2623 * Appends a string representation of this client connection to the provided
2624 * buffer.
2625 *
2626 * @param buffer The buffer to which the information should be appended.
2627 */
2628 public void toString(StringBuilder buffer)
2629 {
2630 buffer.append("LDAP client connection from ");
2631 buffer.append(clientAddress);
2632 buffer.append(":");
2633 buffer.append(clientPort);
2634 buffer.append(" to ");
2635 buffer.append(serverAddress);
2636 buffer.append(":");
2637 buffer.append(serverPort);
2638 }
2639
2640
2641
2642 /**
2643 * Indicates whether TLS protection is actually available for the underlying
2644 * client connection. If there is any reason that TLS protection cannot be
2645 * enabled on this client connection, then it should be appended to the
2646 * provided buffer.
2647 *
2648 * @param unavailableReason The buffer used to hold the reason that TLS is
2649 * not available on the underlying client
2650 * connection.
2651 *
2652 * @return <CODE>true</CODE> if TLS is available on the underlying client
2653 * connection, or <CODE>false</CODE> if it is not.
2654 */
2655 public boolean tlsProtectionAvailable(MessageBuilder unavailableReason)
2656 {
2657 // Make sure that this client connection does not already have some other
2658 // security provider enabled.
2659 if (! (securityProvider instanceof NullConnectionSecurityProvider))
2660 {
2661
2662 unavailableReason.append(ERR_LDAP_TLS_EXISTING_SECURITY_PROVIDER.get(
2663 securityProvider.getSecurityMechanismName()));
2664 return false;
2665 }
2666
2667
2668 // Make sure that the connection handler allows the use of the StartTLS
2669 // operation.
2670 if (! connectionHandler.allowStartTLS())
2671 {
2672
2673 unavailableReason.append(ERR_LDAP_TLS_STARTTLS_NOT_ALLOWED.get());
2674 return false;
2675 }
2676
2677
2678 // Make sure that the TLS security provider is available.
2679 if (tlsSecurityProvider == null)
2680 {
2681 try
2682 {
2683 TLSConnectionSecurityProvider tlsProvider =
2684 new TLSConnectionSecurityProvider();
2685 tlsProvider.initializeConnectionSecurityProvider(null);
2686 tlsProvider.setSSLClientAuthPolicy(
2687 connectionHandler.getSSLClientAuthPolicy());
2688 tlsProvider.setEnabledProtocols(
2689 connectionHandler.getEnabledSSLProtocols());
2690 tlsProvider.setEnabledCipherSuites(
2691 connectionHandler.getEnabledSSLCipherSuites());
2692
2693 tlsSecurityProvider = (TLSConnectionSecurityProvider)
2694 tlsProvider.newInstance(this, clientChannel);
2695 }
2696 catch (Exception e)
2697 {
2698 if (debugEnabled())
2699 {
2700 TRACER.debugCaught(DebugLogLevel.ERROR, e);
2701 }
2702
2703 tlsSecurityProvider = null;
2704
2705
2706 unavailableReason.append(ERR_LDAP_TLS_CANNOT_CREATE_TLS_PROVIDER.get(
2707 stackTraceToSingleLineString(e)));
2708 return false;
2709 }
2710 }
2711
2712
2713 // If we've gotten here, then everything looks OK.
2714 return true;
2715 }
2716
2717
2718
2719 /**
2720 * Installs the TLS connection security provider on this client connection.
2721 * If an error occurs in the process, then the underlying client connection
2722 * must be terminated and an exception must be thrown to indicate the
2723 * underlying cause.
2724 *
2725 * @throws DirectoryException If the TLS connection security provider could
2726 * not be enabled and the underlying connection
2727 * has been closed.
2728 */
2729 public void enableTLSConnectionSecurityProvider()
2730 throws DirectoryException
2731 {
2732 if (tlsSecurityProvider == null)
2733 {
2734 Message message = ERR_LDAP_TLS_NO_PROVIDER.get();
2735
2736 disconnect(DisconnectReason.OTHER, false, message);
2737 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
2738 message);
2739 }
2740
2741 clearSecurityProvider = securityProvider;
2742 setConnectionSecurityProvider(tlsSecurityProvider);
2743 }
2744
2745
2746
2747 /**
2748 * Disables the TLS connection security provider on this client connection.
2749 * This must also eliminate any authentication that had been performed on the
2750 * client connection so that it is in an anonymous state. If a problem occurs
2751 * while attempting to revert the connection to a non-TLS-protected state,
2752 * then an exception must be thrown and the client connection must be
2753 * terminated.
2754 *
2755 * @throws DirectoryException If TLS protection cannot be reverted and the
2756 * underlying client connection has been closed.
2757 */
2758 public void disableTLSConnectionSecurityProvider()
2759 throws DirectoryException
2760 {
2761 Message message = ERR_LDAP_TLS_CLOSURE_NOT_ALLOWED.get();
2762
2763 disconnect(DisconnectReason.OTHER, false, message);
2764 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
2765 message);
2766 }
2767
2768
2769
2770 /**
2771 * Sends a response to the client in the clear rather than through the
2772 * encrypted channel. This should only be used when processing the StartTLS
2773 * extended operation to send the response in the clear after the TLS
2774 * negotiation has already been initiated.
2775 *
2776 * @param operation The operation for which to send the response in the
2777 * clear.
2778 *
2779 *
2780 * @throws DirectoryException If a problem occurs while sending the response
2781 * in the clear.
2782 */
2783 public void sendClearResponse(Operation operation)
2784 throws DirectoryException
2785 {
2786 if (clearSecurityProvider == null)
2787 {
2788 Message message = ERR_LDAP_NO_CLEAR_SECURITY_PROVIDER.get(toString());
2789 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
2790 message);
2791 }
2792
2793 sendLDAPMessage(clearSecurityProvider,
2794 operationToResponseLDAPMessage(operation));
2795 }
2796
2797
2798
2799 /**
2800 * {@inheritDoc}
2801 */
2802 public DN getKeyManagerProviderDN()
2803 {
2804 return connectionHandler.getKeyManagerProviderDN();
2805 }
2806
2807
2808
2809 /**
2810 * {@inheritDoc}
2811 */
2812 public DN getTrustManagerProviderDN()
2813 {
2814 return connectionHandler.getTrustManagerProviderDN();
2815 }
2816
2817
2818
2819 /**
2820 * Retrieves the alias of the server certificate that should be used
2821 * for operations requiring a server certificate. The default
2822 * implementation returns {@code null} to indicate that any alias is
2823 * acceptable.
2824 *
2825 * @return The alias of the server certificate that should be used
2826 * for operations requring a server certificate, or
2827 * {@code null} if any alias is acceptable.
2828 */
2829 public String getCertificateAlias()
2830 {
2831 return connectionHandler.getSSLServerCertNickname();
2832 }
2833
2834
2835
2836 /**
2837 * Retrieves the length of time in milliseconds that this client
2838 * connection has been idle.
2839 * <BR><BR>
2840 * Note that the default implementation will always return zero.
2841 * Subclasses associated with connection handlers should override
2842 * this method if they wish to provided idle time limit
2843 * functionality.
2844 *
2845 * @return The length of time in milliseconds that this client
2846 * connection has been idle.
2847 */
2848 public long getIdleTime()
2849 {
2850 if (operationsInProgress.isEmpty() && getPersistentSearches().isEmpty())
2851 {
2852 return (TimeThread.getTime() - lastCompletionTime.get());
2853 }
2854 else
2855 {
2856 // There's at least one operation in progress, so it's not idle.
2857 return 0L;
2858 }
2859 }
2860 }
2861