001 /*
002 * Copyright 2011-2013 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2011-2013 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.util.ArrayList;
026 import java.util.Arrays;
027 import java.util.Collection;
028 import java.util.Collections;
029 import java.util.Date;
030 import java.util.HashMap;
031 import java.util.Iterator;
032 import java.util.LinkedHashMap;
033 import java.util.LinkedHashSet;
034 import java.util.List;
035 import java.util.Map;
036 import java.util.Set;
037 import java.util.SortedSet;
038 import java.util.TreeMap;
039 import java.util.TreeSet;
040 import java.util.UUID;
041 import java.util.concurrent.atomic.AtomicBoolean;
042 import java.util.concurrent.atomic.AtomicLong;
043 import java.util.concurrent.atomic.AtomicReference;
044
045 import com.unboundid.asn1.ASN1Integer;
046 import com.unboundid.asn1.ASN1OctetString;
047 import com.unboundid.ldap.protocol.AddRequestProtocolOp;
048 import com.unboundid.ldap.protocol.AddResponseProtocolOp;
049 import com.unboundid.ldap.protocol.BindRequestProtocolOp;
050 import com.unboundid.ldap.protocol.BindResponseProtocolOp;
051 import com.unboundid.ldap.protocol.CompareRequestProtocolOp;
052 import com.unboundid.ldap.protocol.CompareResponseProtocolOp;
053 import com.unboundid.ldap.protocol.DeleteRequestProtocolOp;
054 import com.unboundid.ldap.protocol.DeleteResponseProtocolOp;
055 import com.unboundid.ldap.protocol.ExtendedRequestProtocolOp;
056 import com.unboundid.ldap.protocol.ExtendedResponseProtocolOp;
057 import com.unboundid.ldap.protocol.LDAPMessage;
058 import com.unboundid.ldap.protocol.ModifyRequestProtocolOp;
059 import com.unboundid.ldap.protocol.ModifyResponseProtocolOp;
060 import com.unboundid.ldap.protocol.ModifyDNRequestProtocolOp;
061 import com.unboundid.ldap.protocol.ModifyDNResponseProtocolOp;
062 import com.unboundid.ldap.protocol.ProtocolOp;
063 import com.unboundid.ldap.protocol.SearchRequestProtocolOp;
064 import com.unboundid.ldap.protocol.SearchResultDoneProtocolOp;
065 import com.unboundid.ldap.matchingrules.DistinguishedNameMatchingRule;
066 import com.unboundid.ldap.matchingrules.GeneralizedTimeMatchingRule;
067 import com.unboundid.ldap.matchingrules.IntegerMatchingRule;
068 import com.unboundid.ldap.matchingrules.MatchingRule;
069 import com.unboundid.ldap.matchingrules.OctetStringMatchingRule;
070 import com.unboundid.ldap.protocol.SearchResultReferenceProtocolOp;
071 import com.unboundid.ldap.sdk.Attribute;
072 import com.unboundid.ldap.sdk.BindResult;
073 import com.unboundid.ldap.sdk.ChangeLogEntry;
074 import com.unboundid.ldap.sdk.Control;
075 import com.unboundid.ldap.sdk.DN;
076 import com.unboundid.ldap.sdk.Entry;
077 import com.unboundid.ldap.sdk.EntrySorter;
078 import com.unboundid.ldap.sdk.ExtendedRequest;
079 import com.unboundid.ldap.sdk.ExtendedResult;
080 import com.unboundid.ldap.sdk.Filter;
081 import com.unboundid.ldap.sdk.LDAPException;
082 import com.unboundid.ldap.sdk.LDAPURL;
083 import com.unboundid.ldap.sdk.Modification;
084 import com.unboundid.ldap.sdk.ModificationType;
085 import com.unboundid.ldap.sdk.OperationType;
086 import com.unboundid.ldap.sdk.RDN;
087 import com.unboundid.ldap.sdk.ReadOnlyEntry;
088 import com.unboundid.ldap.sdk.ResultCode;
089 import com.unboundid.ldap.sdk.SearchResultEntry;
090 import com.unboundid.ldap.sdk.SearchResultReference;
091 import com.unboundid.ldap.sdk.SearchScope;
092 import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
093 import com.unboundid.ldap.sdk.schema.DITContentRuleDefinition;
094 import com.unboundid.ldap.sdk.schema.DITStructureRuleDefinition;
095 import com.unboundid.ldap.sdk.schema.EntryValidator;
096 import com.unboundid.ldap.sdk.schema.MatchingRuleUseDefinition;
097 import com.unboundid.ldap.sdk.schema.NameFormDefinition;
098 import com.unboundid.ldap.sdk.schema.ObjectClassDefinition;
099 import com.unboundid.ldap.sdk.schema.Schema;
100 import com.unboundid.ldap.sdk.controls.AssertionRequestControl;
101 import com.unboundid.ldap.sdk.controls.AuthorizationIdentityRequestControl;
102 import com.unboundid.ldap.sdk.controls.AuthorizationIdentityResponseControl;
103 import com.unboundid.ldap.sdk.controls.DontUseCopyRequestControl;
104 import com.unboundid.ldap.sdk.controls.ManageDsaITRequestControl;
105 import com.unboundid.ldap.sdk.controls.PermissiveModifyRequestControl;
106 import com.unboundid.ldap.sdk.controls.PostReadRequestControl;
107 import com.unboundid.ldap.sdk.controls.PostReadResponseControl;
108 import com.unboundid.ldap.sdk.controls.PreReadRequestControl;
109 import com.unboundid.ldap.sdk.controls.PreReadResponseControl;
110 import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV1RequestControl;
111 import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV2RequestControl;
112 import com.unboundid.ldap.sdk.controls.ServerSideSortRequestControl;
113 import com.unboundid.ldap.sdk.controls.ServerSideSortResponseControl;
114 import com.unboundid.ldap.sdk.controls.SimplePagedResultsControl;
115 import com.unboundid.ldap.sdk.controls.SortKey;
116 import com.unboundid.ldap.sdk.controls.SubentriesRequestControl;
117 import com.unboundid.ldap.sdk.controls.SubtreeDeleteRequestControl;
118 import com.unboundid.ldap.sdk.controls.TransactionSpecificationRequestControl;
119 import com.unboundid.ldap.sdk.controls.VirtualListViewRequestControl;
120 import com.unboundid.ldap.sdk.controls.VirtualListViewResponseControl;
121 import com.unboundid.ldap.sdk.extensions.AbortedTransactionExtendedResult;
122 import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest;
123 import com.unboundid.ldif.LDIFAddChangeRecord;
124 import com.unboundid.ldif.LDIFDeleteChangeRecord;
125 import com.unboundid.ldif.LDIFException;
126 import com.unboundid.ldif.LDIFModifyChangeRecord;
127 import com.unboundid.ldif.LDIFModifyDNChangeRecord;
128 import com.unboundid.ldif.LDIFReader;
129 import com.unboundid.ldif.LDIFWriter;
130 import com.unboundid.util.Debug;
131 import com.unboundid.util.Mutable;
132 import com.unboundid.util.ObjectPair;
133 import com.unboundid.util.StaticUtils;
134 import com.unboundid.util.ThreadSafety;
135 import com.unboundid.util.ThreadSafetyLevel;
136
137 import static com.unboundid.ldap.listener.ListenerMessages.*;
138
139
140
141 /**
142 * This class provides an implementation of an LDAP request handler that can be
143 * used to store entries in memory and process operations on those entries.
144 * It is primarily intended for use in creating a simple embeddable directory
145 * server that can be used for testing purposes. It performs only very basic
146 * validation, and is not intended to be a fully standards-compliant server.
147 */
148 @Mutable()
149 @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
150 public final class InMemoryRequestHandler
151 extends LDAPListenerRequestHandler
152 {
153 /**
154 * A pre-allocated array containing no controls.
155 */
156 private static final Control[] NO_CONTROLS = new Control[0];
157
158
159
160 /**
161 * The OID for a proprietary control that can be used to indicate that the
162 * associated operation should be considered an internal operation that was
163 * requested by a method call in the in-memory directory server class rather
164 * than from an LDAP client. It may be used to bypass certain restrictions
165 * that might otherwise be enforced (e.g., allowed operation types, write
166 * access to NO-USER-MODIFICATION attributes, etc.).
167 */
168 static final String OID_INTERNAL_OPERATION_REQUEST_CONTROL =
169 "1.3.6.1.4.1.30221.2.5.18";
170
171
172
173 // The change number for the first changelog entry in the server.
174 private final AtomicLong firstChangeNumber;
175
176 // The change number for the last changelog entry in the server.
177 private final AtomicLong lastChangeNumber;
178
179 // A delay (in milliseconds) to insert before processing operations.
180 private final AtomicLong processingDelayMillis;
181
182 // The reference to the entry validator that will be used for schema checking,
183 // if appropriate.
184 private final AtomicReference<EntryValidator> entryValidatorRef;
185
186 // The entry to use as the subschema subentry.
187 private final AtomicReference<ReadOnlyEntry> subschemaSubentryRef;
188
189 // The reference to the schema that will be used for this request handler.
190 private final AtomicReference<Schema> schemaRef;
191
192 // Indicates whether to generate operational attributes for writes.
193 private final boolean generateOperationalAttributes;
194
195 // The DN of the currently-authenticated user for the associated connection.
196 private DN authenticatedDN;
197
198 // The base DN for the server changelog.
199 private final DN changeLogBaseDN;
200
201 // The DN of the subschema subentry.
202 private final DN subschemaSubentryDN;
203
204 // The configuration used to create this request handler.
205 private final InMemoryDirectoryServerConfig config;
206
207 // A snapshot containing the server content as it initially appeared. It
208 // will not contain any user data, but may contain a changelog base entry.
209 private final InMemoryDirectoryServerSnapshot initialSnapshot;
210
211 // The maximum number of changelog entries to maintain.
212 private final int maxChangelogEntries;
213
214 // The client connection for this request handler instance.
215 private final LDAPListenerClientConnection connection;
216
217 // The set of equality indexes defined for the server.
218 private final Map<AttributeTypeDefinition,
219 InMemoryDirectoryServerEqualityAttributeIndex> equalityIndexes;
220
221 // An additional set of credentials that may be used for bind operations.
222 private final Map<DN,byte[]> additionalBindCredentials;
223
224 // A map of the available extended operation handlers by request OID.
225 private final Map<String,InMemoryExtendedOperationHandler>
226 extendedRequestHandlers;
227
228 // A map of the available SASL bind handlers by mechanism name.
229 private final Map<String,InMemorySASLBindHandler> saslBindHandlers;
230
231 // A map of state information specific to the associated connection.
232 private final Map<String,Object> connectionState;
233
234 // The set of base DNs for the server.
235 private final Set<DN> baseDNs;
236
237 // The set of referential integrity attributes for the server.
238 private final Set<String> referentialIntegrityAttributes;
239
240 // The map of entries currently held in the server.
241 private final TreeMap<DN,ReadOnlyEntry> entryMap;
242
243
244
245 /**
246 * Creates a new instance of this request handler with an initially-empty
247 * data set.
248 *
249 * @param config The configuration that should be used for the in-memory
250 * directory server.
251 *
252 * @throws LDAPException If there is a problem with the provided
253 * configuration.
254 */
255 public InMemoryRequestHandler(final InMemoryDirectoryServerConfig config)
256 throws LDAPException
257 {
258 this.config = config;
259
260 schemaRef = new AtomicReference<Schema>();
261 entryValidatorRef = new AtomicReference<EntryValidator>();
262 subschemaSubentryRef = new AtomicReference<ReadOnlyEntry>();
263
264 final Schema schema = config.getSchema();
265 schemaRef.set(schema);
266 if (schema != null)
267 {
268 final EntryValidator entryValidator = new EntryValidator(schema);
269 entryValidatorRef.set(entryValidator);
270 entryValidator.setCheckAttributeSyntax(
271 config.enforceAttributeSyntaxCompliance());
272 entryValidator.setCheckStructuralObjectClasses(
273 config.enforceSingleStructuralObjectClass());
274 }
275
276 final DN[] baseDNArray = config.getBaseDNs();
277 if ((baseDNArray == null) || (baseDNArray.length == 0))
278 {
279 throw new LDAPException(ResultCode.PARAM_ERROR,
280 ERR_MEM_HANDLER_NO_BASE_DNS.get());
281 }
282
283 entryMap = new TreeMap<DN,ReadOnlyEntry>();
284
285 final LinkedHashSet<DN> baseDNSet =
286 new LinkedHashSet<DN>(Arrays.asList(baseDNArray));
287 if (baseDNSet.contains(DN.NULL_DN))
288 {
289 throw new LDAPException(ResultCode.PARAM_ERROR,
290 ERR_MEM_HANDLER_NULL_BASE_DN.get());
291 }
292
293 changeLogBaseDN = new DN("cn=changelog", schema);
294 if (baseDNSet.contains(changeLogBaseDN))
295 {
296 throw new LDAPException(ResultCode.PARAM_ERROR,
297 ERR_MEM_HANDLER_CHANGELOG_BASE_DN.get());
298 }
299
300 maxChangelogEntries = config.getMaxChangeLogEntries();
301
302 final TreeMap<String,InMemoryExtendedOperationHandler> extOpHandlers =
303 new TreeMap<String,InMemoryExtendedOperationHandler>();
304 for (final InMemoryExtendedOperationHandler h :
305 config.getExtendedOperationHandlers())
306 {
307 for (final String oid : h.getSupportedExtendedRequestOIDs())
308 {
309 if (extOpHandlers.containsKey(oid))
310 {
311 throw new LDAPException(ResultCode.PARAM_ERROR,
312 ERR_MEM_HANDLER_EXTENDED_REQUEST_HANDLER_CONFLICT.get(oid));
313 }
314 else
315 {
316 extOpHandlers.put(oid, h);
317 }
318 }
319 }
320 extendedRequestHandlers = Collections.unmodifiableMap(extOpHandlers);
321
322 final TreeMap<String,InMemorySASLBindHandler> saslHandlers =
323 new TreeMap<String,InMemorySASLBindHandler>();
324 for (final InMemorySASLBindHandler h : config.getSASLBindHandlers())
325 {
326 final String mech = h.getSASLMechanismName();
327 if (saslHandlers.containsKey(mech))
328 {
329 throw new LDAPException(ResultCode.PARAM_ERROR,
330 ERR_MEM_HANDLER_SASL_BIND_HANDLER_CONFLICT.get(mech));
331 }
332 else
333 {
334 saslHandlers.put(mech, h);
335 }
336 }
337 saslBindHandlers = Collections.unmodifiableMap(saslHandlers);
338
339 additionalBindCredentials = Collections.unmodifiableMap(
340 config.getAdditionalBindCredentials());
341
342 final List<String> eqIndexAttrs = config.getEqualityIndexAttributes();
343 equalityIndexes = new HashMap<AttributeTypeDefinition,
344 InMemoryDirectoryServerEqualityAttributeIndex>(eqIndexAttrs.size());
345 for (final String s : eqIndexAttrs)
346 {
347 final InMemoryDirectoryServerEqualityAttributeIndex i =
348 new InMemoryDirectoryServerEqualityAttributeIndex(s, schema);
349 equalityIndexes.put(i.getAttributeType(), i);
350 }
351
352 referentialIntegrityAttributes = Collections.unmodifiableSet(
353 config.getReferentialIntegrityAttributes());
354
355 baseDNs = Collections.unmodifiableSet(baseDNSet);
356 generateOperationalAttributes = config.generateOperationalAttributes();
357 authenticatedDN = new DN("cn=Internal Root User", schema);
358 connection = null;
359 connectionState = Collections.emptyMap();
360 firstChangeNumber = new AtomicLong(0L);
361 lastChangeNumber = new AtomicLong(0L);
362 processingDelayMillis = new AtomicLong(0L);
363
364 final ReadOnlyEntry subschemaSubentry = generateSubschemaSubentry(schema);
365 subschemaSubentryRef.set(subschemaSubentry);
366 subschemaSubentryDN = subschemaSubentry.getParsedDN();
367
368 if (baseDNs.contains(subschemaSubentryDN))
369 {
370 throw new LDAPException(ResultCode.PARAM_ERROR,
371 ERR_MEM_HANDLER_SCHEMA_BASE_DN.get());
372 }
373
374 if (maxChangelogEntries > 0)
375 {
376 baseDNSet.add(changeLogBaseDN);
377
378 final ReadOnlyEntry changeLogBaseEntry = new ReadOnlyEntry(
379 changeLogBaseDN, schema,
380 new Attribute("objectClass", "top", "namedObject"),
381 new Attribute("cn", "changelog"),
382 new Attribute("entryDN",
383 DistinguishedNameMatchingRule.getInstance(),
384 "cn=changelog"),
385 new Attribute("entryUUID", UUID.randomUUID().toString()),
386 new Attribute("creatorsName",
387 DistinguishedNameMatchingRule.getInstance(),
388 DN.NULL_DN.toString()),
389 new Attribute("createTimestamp",
390 GeneralizedTimeMatchingRule.getInstance(),
391 StaticUtils.encodeGeneralizedTime(new Date())),
392 new Attribute("modifiersName",
393 DistinguishedNameMatchingRule.getInstance(),
394 DN.NULL_DN.toString()),
395 new Attribute("modifyTimestamp",
396 GeneralizedTimeMatchingRule.getInstance(),
397 StaticUtils.encodeGeneralizedTime(new Date())),
398 new Attribute("subschemaSubentry",
399 DistinguishedNameMatchingRule.getInstance(),
400 subschemaSubentryDN.toString()));
401 entryMap.put(changeLogBaseDN, changeLogBaseEntry);
402 indexAdd(changeLogBaseEntry);
403 }
404
405 initialSnapshot = createSnapshot();
406 }
407
408
409
410 /**
411 * Creates a new instance of this request handler that will use the provided
412 * entry map object.
413 *
414 * @param parent The parent request handler instance.
415 * @param connection The client connection for this instance.
416 */
417 private InMemoryRequestHandler(final InMemoryRequestHandler parent,
418 final LDAPListenerClientConnection connection)
419 {
420 this.connection = connection;
421
422 authenticatedDN = DN.NULL_DN;
423 connectionState = new LinkedHashMap<String,Object>(0);
424
425 config = parent.config;
426 generateOperationalAttributes = parent.generateOperationalAttributes;
427 additionalBindCredentials = parent.additionalBindCredentials;
428 baseDNs = parent.baseDNs;
429 changeLogBaseDN = parent.changeLogBaseDN;
430 firstChangeNumber = parent.firstChangeNumber;
431 lastChangeNumber = parent.lastChangeNumber;
432 processingDelayMillis = parent.processingDelayMillis;
433 maxChangelogEntries = parent.maxChangelogEntries;
434 equalityIndexes = parent.equalityIndexes;
435 referentialIntegrityAttributes = parent.referentialIntegrityAttributes;
436 entryMap = parent.entryMap;
437 entryValidatorRef = parent.entryValidatorRef;
438 extendedRequestHandlers = parent.extendedRequestHandlers;
439 saslBindHandlers = parent.saslBindHandlers;
440 schemaRef = parent.schemaRef;
441 subschemaSubentryRef = parent.subschemaSubentryRef;
442 subschemaSubentryDN = parent.subschemaSubentryDN;
443 initialSnapshot = parent.initialSnapshot;
444 }
445
446
447
448 /**
449 * Creates a new instance of this request handler that will be used to process
450 * requests read by the provided connection.
451 *
452 * @param connection The connection with which this request handler instance
453 * will be associated.
454 *
455 * @return The request handler instance that will be used for the provided
456 * connection.
457 *
458 * @throws LDAPException If the connection should not be accepted.
459 */
460 @Override()
461 public InMemoryRequestHandler newInstance(
462 final LDAPListenerClientConnection connection)
463 throws LDAPException
464 {
465 return new InMemoryRequestHandler(this, connection);
466 }
467
468
469
470 /**
471 * Creates a point-in-time snapshot of the information contained in this
472 * in-memory request handler. If desired, it may be restored using the
473 * {@link #restoreSnapshot} method.
474 *
475 * @return The snapshot created based on the current content of this
476 * in-memory request handler.
477 */
478 public synchronized InMemoryDirectoryServerSnapshot createSnapshot()
479 {
480 return new InMemoryDirectoryServerSnapshot(entryMap,
481 firstChangeNumber.get(), lastChangeNumber.get());
482 }
483
484
485
486 /**
487 * Updates the content of this in-memory request handler to match what it was
488 * at the time the snapshot was created.
489 *
490 * @param snapshot The snapshot to be restored. It must not be
491 * {@code null}.
492 */
493 public synchronized void restoreSnapshot(
494 final InMemoryDirectoryServerSnapshot snapshot)
495 {
496 entryMap.clear();
497 entryMap.putAll(snapshot.getEntryMap());
498
499 for (final InMemoryDirectoryServerEqualityAttributeIndex i :
500 equalityIndexes.values())
501 {
502 i.clear();
503 for (final Entry e : entryMap.values())
504 {
505 try
506 {
507 i.processAdd(e);
508 }
509 catch (final Exception ex)
510 {
511 Debug.debugException(ex);
512 }
513 }
514 }
515
516 firstChangeNumber.set(snapshot.getFirstChangeNumber());
517 lastChangeNumber.set(snapshot.getLastChangeNumber());
518 }
519
520
521
522 /**
523 * Retrieves the schema that will be used by the server, if any.
524 *
525 * @return The schema that will be used by the server, or {@code null} if
526 * none has been configured.
527 */
528 public Schema getSchema()
529 {
530 return schemaRef.get();
531 }
532
533
534
535 /**
536 * Retrieves a list of the base DNs configured for use by the server.
537 *
538 * @return A list of the base DNs configured for use by the server.
539 */
540 public List<DN> getBaseDNs()
541 {
542 return Collections.unmodifiableList(new ArrayList<DN>(baseDNs));
543 }
544
545
546
547 /**
548 * Retrieves the client connection associated with this request handler
549 * instance.
550 *
551 * @return The client connection associated with this request handler
552 * instance, or {@code null} if this instance is not associated with
553 * any client connection.
554 */
555 public synchronized LDAPListenerClientConnection getClientConnection()
556 {
557 return connection;
558 }
559
560
561
562 /**
563 * Retrieves the DN of the user currently authenticated on the connection
564 * associated with this request handler instance.
565 *
566 * @return The DN of the user currently authenticated on the connection
567 * associated with this request handler instance, or
568 * {@code DN#NULL_DN} if the connection is unauthenticated or is
569 * authenticated as the anonymous user.
570 */
571 public synchronized DN getAuthenticatedDN()
572 {
573 return authenticatedDN;
574 }
575
576
577
578 /**
579 * Sets the DN of the user currently authenticated on the connection
580 * associated with this request handler instance.
581 *
582 * @param authenticatedDN The DN of the user currently authenticated on the
583 * connection associated with this request handler.
584 * It may be {@code null} or {@link DN#NULL_DN} to
585 * indicate that the connection is unauthenticated.
586 */
587 public synchronized void setAuthenticatedDN(final DN authenticatedDN)
588 {
589 if (authenticatedDN == null)
590 {
591 this.authenticatedDN = DN.NULL_DN;
592 }
593 else
594 {
595 this.authenticatedDN = authenticatedDN;
596 }
597 }
598
599
600
601 /**
602 * Retrieves an unmodifiable map containing the defined set of additional bind
603 * credentials, mapped from bind DN to password bytes.
604 *
605 * @return An unmodifiable map containing the defined set of additional bind
606 * credentials, or an empty map if no additional credentials have
607 * been defined.
608 */
609 public Map<DN,byte[]> getAdditionalBindCredentials()
610 {
611 return additionalBindCredentials;
612 }
613
614
615
616 /**
617 * Retrieves the password for the given DN from the set of additional bind
618 * credentials.
619 *
620 * @param dn The DN for which to retrieve the corresponding password.
621 *
622 * @return The password bytes for the given DN, or {@code null} if the
623 * additional bind credentials does not include information for the
624 * provided DN.
625 */
626 public byte[] getAdditionalBindCredentials(final DN dn)
627 {
628 return additionalBindCredentials.get(dn);
629 }
630
631
632
633 /**
634 * Retrieves a map that may be used to hold state information specific to the
635 * connection associated with this request handler instance. It may be
636 * queried and updated if necessary to store state information that may be
637 * needed at multiple different times in the life of a connection (e.g., when
638 * processing a multi-stage SASL bind).
639 *
640 * @return An updatable map that may be used to hold state information
641 * specific to the connection associated with this request handler
642 * instance.
643 */
644 public synchronized Map<String,Object> getConnectionState()
645 {
646 return connectionState;
647 }
648
649
650
651 /**
652 * Retrieves the delay in milliseconds that the server should impose before
653 * beginning processing for operations.
654 *
655 * @return The delay in milliseconds that the server should impose before
656 * beginning processing for operations, or 0 if there should be no
657 * delay inserted when processing operations.
658 */
659 public long getProcessingDelayMillis()
660 {
661 return processingDelayMillis.get();
662 }
663
664
665
666 /**
667 * Specifies the delay in milliseconds that the server should impose before
668 * beginning processing for operations.
669 *
670 * @param processingDelayMillis The delay in milliseconds that the server
671 * should impose before beginning processing
672 * for operations. A value less than or equal
673 * to zero may be used to indicate that there
674 * should be no delay.
675 */
676 public void setProcessingDelayMillis(final long processingDelayMillis)
677 {
678 if (processingDelayMillis > 0)
679 {
680 this.processingDelayMillis.set(processingDelayMillis);
681 }
682 else
683 {
684 this.processingDelayMillis.set(0L);
685 }
686 }
687
688
689
690 /**
691 * Attempts to add an entry to the in-memory data set. The attempt will fail
692 * if any of the following conditions is true:
693 * <UL>
694 * <LI>There is a problem with any of the request controls.</LI>
695 * <LI>The provided entry has a malformed DN.</LI>
696 * <LI>The provided entry has the null DN.</LI>
697 * <LI>The provided entry has a DN that is the same as or subordinate to the
698 * subschema subentry.</LI>
699 * <LI>The provided entry has a DN that is the same as or subordinate to the
700 * changelog base entry.</LI>
701 * <LI>An entry already exists with the same DN as the entry in the provided
702 * request.</LI>
703 * <LI>The entry is outside the set of base DNs for the server.</LI>
704 * <LI>The entry is below one of the defined base DNs but the immediate
705 * parent entry does not exist.</LI>
706 * <LI>If a schema was provided, and the entry is not valid according to the
707 * constraints of that schema.</LI>
708 * </UL>
709 *
710 * @param messageID The message ID of the LDAP message containing the add
711 * request.
712 * @param request The add request that was included in the LDAP message
713 * that was received.
714 * @param controls The set of controls included in the LDAP message. It
715 * may be empty if there were no controls, but will not be
716 * {@code null}.
717 *
718 * @return The {@link LDAPMessage} containing the response to send to the
719 * client. The protocol op in the {@code LDAPMessage} must be an
720 * {@code AddResponseProtocolOp}.
721 */
722 @Override()
723 public synchronized LDAPMessage processAddRequest(final int messageID,
724 final AddRequestProtocolOp request,
725 final List<Control> controls)
726 {
727 // Sleep before processing, if appropriate.
728 sleepBeforeProcessing();
729
730 // Process the provided request controls.
731 final Map<String,Control> controlMap;
732 try
733 {
734 controlMap = RequestControlPreProcessor.processControls(
735 LDAPMessage.PROTOCOL_OP_TYPE_ADD_REQUEST, controls);
736 }
737 catch (final LDAPException le)
738 {
739 Debug.debugException(le);
740 return new LDAPMessage(messageID, new AddResponseProtocolOp(
741 le.getResultCode().intValue(), null, le.getMessage(), null));
742 }
743 final ArrayList<Control> responseControls = new ArrayList<Control>(1);
744
745
746 // If this operation type is not allowed, then reject it.
747 final boolean isInternalOp =
748 controlMap.containsKey(OID_INTERNAL_OPERATION_REQUEST_CONTROL);
749 if ((! isInternalOp) &&
750 (! config.getAllowedOperationTypes().contains(OperationType.ADD)))
751 {
752 return new LDAPMessage(messageID, new AddResponseProtocolOp(
753 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
754 ERR_MEM_HANDLER_ADD_NOT_ALLOWED.get(), null));
755 }
756
757
758 // If this operation type requires authentication, then ensure that the
759 // client is authenticated.
760 if ((authenticatedDN.isNullDN() &&
761 config.getAuthenticationRequiredOperationTypes().contains(
762 OperationType.ADD)))
763 {
764 return new LDAPMessage(messageID, new AddResponseProtocolOp(
765 ResultCode.INSUFFICIENT_ACCESS_RIGHTS_INT_VALUE, null,
766 ERR_MEM_HANDLER_ADD_REQUIRES_AUTH.get(), null));
767 }
768
769
770 // See if this add request is part of a transaction. If so, then perform
771 // appropriate processing for it and return success immediately without
772 // actually doing any further processing.
773 try
774 {
775 final ASN1OctetString txnID =
776 processTransactionRequest(messageID, request, controlMap);
777 if (txnID != null)
778 {
779 return new LDAPMessage(messageID, new AddResponseProtocolOp(
780 ResultCode.SUCCESS_INT_VALUE, null,
781 INFO_MEM_HANDLER_OP_IN_TXN.get(txnID.stringValue()), null));
782 }
783 }
784 catch (final LDAPException le)
785 {
786 Debug.debugException(le);
787 return new LDAPMessage(messageID,
788 new AddResponseProtocolOp(le.getResultCode().intValue(),
789 le.getMatchedDN(), le.getDiagnosticMessage(),
790 StaticUtils.toList(le.getReferralURLs())),
791 le.getResponseControls());
792 }
793
794
795 // Get the entry to be added. If a schema was provided, then make sure the
796 // attributes are created with the appropriate matching rules.
797 final Entry entry;
798 final Schema schema = schemaRef.get();
799 if (schema == null)
800 {
801 entry = new Entry(request.getDN(), request.getAttributes());
802 }
803 else
804 {
805 final List<Attribute> providedAttrs = request.getAttributes();
806 final List<Attribute> newAttrs =
807 new ArrayList<Attribute>(providedAttrs.size());
808 for (final Attribute a : providedAttrs)
809 {
810 final String baseName = a.getBaseName();
811 final MatchingRule matchingRule =
812 MatchingRule.selectEqualityMatchingRule(baseName, schema);
813 newAttrs.add(new Attribute(a.getName(), matchingRule,
814 a.getRawValues()));
815 }
816
817 entry = new Entry(request.getDN(), schema, newAttrs);
818 }
819
820 // Make sure that the DN is valid.
821 final DN dn;
822 try
823 {
824 dn = entry.getParsedDN();
825 }
826 catch (final LDAPException le)
827 {
828 Debug.debugException(le);
829 return new LDAPMessage(messageID, new AddResponseProtocolOp(
830 ResultCode.INVALID_DN_SYNTAX_INT_VALUE, null,
831 ERR_MEM_HANDLER_ADD_MALFORMED_DN.get(request.getDN(),
832 le.getMessage()),
833 null));
834 }
835
836 // See if the DN is the null DN, the schema entry DN, or a changelog entry.
837 if (dn.isNullDN())
838 {
839 return new LDAPMessage(messageID, new AddResponseProtocolOp(
840 ResultCode.ENTRY_ALREADY_EXISTS_INT_VALUE, null,
841 ERR_MEM_HANDLER_ADD_ROOT_DSE.get(), null));
842 }
843 else if (dn.isDescendantOf(subschemaSubentryDN, true))
844 {
845 return new LDAPMessage(messageID, new AddResponseProtocolOp(
846 ResultCode.ENTRY_ALREADY_EXISTS_INT_VALUE, null,
847 ERR_MEM_HANDLER_ADD_SCHEMA.get(subschemaSubentryDN.toString()),
848 null));
849 }
850 else if (dn.isDescendantOf(changeLogBaseDN, true))
851 {
852 return new LDAPMessage(messageID, new AddResponseProtocolOp(
853 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
854 ERR_MEM_HANDLER_ADD_CHANGELOG.get(changeLogBaseDN.toString()),
855 null));
856 }
857
858 // See if there is a referral at or above the target entry.
859 if (! controlMap.containsKey(
860 ManageDsaITRequestControl.MANAGE_DSA_IT_REQUEST_OID))
861 {
862 final Entry referralEntry = findNearestReferral(dn);
863 if (referralEntry != null)
864 {
865 return new LDAPMessage(messageID, new AddResponseProtocolOp(
866 ResultCode.REFERRAL_INT_VALUE, referralEntry.getDN(),
867 INFO_MEM_HANDLER_REFERRAL_ENCOUNTERED.get(),
868 getReferralURLs(dn, referralEntry)));
869 }
870 }
871
872 // See if another entry exists with the same DN.
873 if (entryMap.containsKey(dn))
874 {
875 return new LDAPMessage(messageID, new AddResponseProtocolOp(
876 ResultCode.ENTRY_ALREADY_EXISTS_INT_VALUE, null,
877 ERR_MEM_HANDLER_ADD_ALREADY_EXISTS.get(request.getDN()), null));
878 }
879
880 // Make sure that all RDN attribute values are present in the entry.
881 final RDN rdn = dn.getRDN();
882 final String[] rdnAttrNames = rdn.getAttributeNames();
883 final byte[][] rdnAttrValues = rdn.getByteArrayAttributeValues();
884 for (int i=0; i < rdnAttrNames.length; i++)
885 {
886 final MatchingRule matchingRule =
887 MatchingRule.selectEqualityMatchingRule(rdnAttrNames[i], schema);
888 entry.addAttribute(new Attribute(rdnAttrNames[i], matchingRule,
889 rdnAttrValues[i]));
890 }
891
892 // Make sure that all superior object classes are present in the entry.
893 if (schema != null)
894 {
895 final String[] objectClasses = entry.getObjectClassValues();
896 if (objectClasses != null)
897 {
898 final LinkedHashMap<String,String> ocMap =
899 new LinkedHashMap<String,String>(objectClasses.length);
900 for (final String ocName : objectClasses)
901 {
902 final ObjectClassDefinition oc = schema.getObjectClass(ocName);
903 if (oc == null)
904 {
905 ocMap.put(StaticUtils.toLowerCase(ocName), ocName);
906 }
907 else
908 {
909 ocMap.put(StaticUtils.toLowerCase(oc.getNameOrOID()), ocName);
910 for (final ObjectClassDefinition supClass :
911 oc.getSuperiorClasses(schema, true))
912 {
913 ocMap.put(StaticUtils.toLowerCase(supClass.getNameOrOID()),
914 supClass.getNameOrOID());
915 }
916 }
917 }
918
919 final String[] newObjectClasses = new String[ocMap.size()];
920 ocMap.values().toArray(newObjectClasses);
921 entry.setAttribute("objectClass", newObjectClasses);
922 }
923 }
924
925 // If a schema was provided, then make sure the entry complies with it.
926 // Also make sure that there are no attributes marked with
927 // NO-USER-MODIFICATION.
928 final EntryValidator entryValidator = entryValidatorRef.get();
929 if (entryValidator != null)
930 {
931 final ArrayList<String> invalidReasons =
932 new ArrayList<String>(1);
933 if (! entryValidator.entryIsValid(entry, invalidReasons))
934 {
935 return new LDAPMessage(messageID, new AddResponseProtocolOp(
936 ResultCode.OBJECT_CLASS_VIOLATION_INT_VALUE, null,
937 ERR_MEM_HANDLER_ADD_VIOLATES_SCHEMA.get(request.getDN(),
938 StaticUtils.concatenateStrings(invalidReasons)), null));
939 }
940
941 if (! isInternalOp)
942 {
943 for (final Attribute a : entry.getAttributes())
944 {
945 final AttributeTypeDefinition at =
946 schema.getAttributeType(a.getBaseName());
947 if ((at != null) && at.isNoUserModification())
948 {
949 return new LDAPMessage(messageID, new AddResponseProtocolOp(
950 ResultCode.CONSTRAINT_VIOLATION_INT_VALUE, null,
951 ERR_MEM_HANDLER_ADD_CONTAINS_NO_USER_MOD.get(request.getDN(),
952 a.getName()), null));
953 }
954 }
955 }
956 }
957
958 // If the entry contains a proxied authorization control, then process it.
959 final DN authzDN;
960 try
961 {
962 authzDN = handleProxiedAuthControl(controlMap);
963 }
964 catch (final LDAPException le)
965 {
966 Debug.debugException(le);
967 return new LDAPMessage(messageID, new AddResponseProtocolOp(
968 le.getResultCode().intValue(), null, le.getMessage(), null));
969 }
970
971 // Add a number of operational attributes to the entry.
972 if (generateOperationalAttributes)
973 {
974 final Date d = new Date();
975 if (! entry.hasAttribute("entryDN"))
976 {
977 entry.addAttribute(new Attribute("entryDN",
978 DistinguishedNameMatchingRule.getInstance(),
979 dn.toNormalizedString()));
980 }
981 if (! entry.hasAttribute("entryUUID"))
982 {
983 entry.addAttribute(new Attribute("entryUUID",
984 UUID.randomUUID().toString()));
985 }
986 if (! entry.hasAttribute("subschemaSubentry"))
987 {
988 entry.addAttribute(new Attribute("subschemaSubentry",
989 DistinguishedNameMatchingRule.getInstance(),
990 subschemaSubentryDN.toString()));
991 }
992 if (! entry.hasAttribute("creatorsName"))
993 {
994 entry.addAttribute(new Attribute("creatorsName",
995 DistinguishedNameMatchingRule.getInstance(),
996 authzDN.toString()));
997 }
998 if (! entry.hasAttribute("createTimestamp"))
999 {
1000 entry.addAttribute(new Attribute("createTimestamp",
1001 GeneralizedTimeMatchingRule.getInstance(),
1002 StaticUtils.encodeGeneralizedTime(d)));
1003 }
1004 if (! entry.hasAttribute("modifiersName"))
1005 {
1006 entry.addAttribute(new Attribute("modifiersName",
1007 DistinguishedNameMatchingRule.getInstance(),
1008 authzDN.toString()));
1009 }
1010 if (! entry.hasAttribute("modifyTimestamp"))
1011 {
1012 entry.addAttribute(new Attribute("modifyTimestamp",
1013 GeneralizedTimeMatchingRule.getInstance(),
1014 StaticUtils.encodeGeneralizedTime(d)));
1015 }
1016 }
1017
1018 // If the request includes the assertion request control, then check it now.
1019 try
1020 {
1021 handleAssertionRequestControl(controlMap, entry);
1022 }
1023 catch (final LDAPException le)
1024 {
1025 Debug.debugException(le);
1026 return new LDAPMessage(messageID, new AddResponseProtocolOp(
1027 le.getResultCode().intValue(), null, le.getMessage(), null));
1028 }
1029
1030 // If the request includes the post-read request control, then create the
1031 // appropriate response control.
1032 final PostReadResponseControl postReadResponse =
1033 handlePostReadControl(controlMap, entry);
1034 if (postReadResponse != null)
1035 {
1036 responseControls.add(postReadResponse);
1037 }
1038
1039 // See if the entry DN is one of the defined base DNs. If so, then we can
1040 // add the entry.
1041 if (baseDNs.contains(dn))
1042 {
1043 entryMap.put(dn, new ReadOnlyEntry(entry));
1044 indexAdd(entry);
1045 addChangeLogEntry(request, authzDN);
1046 return new LDAPMessage(messageID,
1047 new AddResponseProtocolOp(ResultCode.SUCCESS_INT_VALUE, null, null,
1048 null),
1049 responseControls);
1050 }
1051
1052 // See if the parent entry exists. If so, then we can add the entry.
1053 final DN parentDN = dn.getParent();
1054 if ((parentDN != null) && entryMap.containsKey(parentDN))
1055 {
1056 entryMap.put(dn, new ReadOnlyEntry(entry));
1057 indexAdd(entry);
1058 addChangeLogEntry(request, authzDN);
1059 return new LDAPMessage(messageID,
1060 new AddResponseProtocolOp(ResultCode.SUCCESS_INT_VALUE, null, null,
1061 null),
1062 responseControls);
1063 }
1064
1065 // The add attempt must fail.
1066 return new LDAPMessage(messageID, new AddResponseProtocolOp(
1067 ResultCode.NO_SUCH_OBJECT_INT_VALUE, getMatchedDNString(dn),
1068 ERR_MEM_HANDLER_ADD_MISSING_PARENT.get(request.getDN(),
1069 dn.getParentString()),
1070 null));
1071 }
1072
1073
1074
1075 /**
1076 * Attempts to process the provided bind request. The attempt will fail if
1077 * any of the following conditions is true:
1078 * <UL>
1079 * <LI>There is a problem with any of the request controls.</LI>
1080 * <LI>The bind request is not a simple bind request.</LI>
1081 * <LI>The bind request contains a malformed bind DN.</LI>
1082 * <LI>The bind DN is not the null DN and is not the DN of any entry in the
1083 * data set.</LI>
1084 * <LI>The bind password is empty and the bind DN is not the null DN.</LI>
1085 * <LI>The target user does not have a userPassword value that matches the
1086 * provided bind password.</LI>
1087 * </UL>
1088 *
1089 * @param messageID The message ID of the LDAP message containing the bind
1090 * request.
1091 * @param request The bind request that was included in the LDAP message
1092 * that was received.
1093 * @param controls The set of controls included in the LDAP message. It
1094 * may be empty if there were no controls, but will not be
1095 * {@code null}.
1096 *
1097 * @return The {@link LDAPMessage} containing the response to send to the
1098 * client. The protocol op in the {@code LDAPMessage} must be a
1099 * {@code BindResponseProtocolOp}.
1100 */
1101 @Override()
1102 public synchronized LDAPMessage processBindRequest(final int messageID,
1103 final BindRequestProtocolOp request,
1104 final List<Control> controls)
1105 {
1106 // Sleep before processing, if appropriate.
1107 sleepBeforeProcessing();
1108
1109 // If this operation type is not allowed, then reject it.
1110 if (! config.getAllowedOperationTypes().contains(OperationType.BIND))
1111 {
1112 return new LDAPMessage(messageID, new BindResponseProtocolOp(
1113 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
1114 ERR_MEM_HANDLER_BIND_NOT_ALLOWED.get(), null, null));
1115 }
1116
1117
1118 authenticatedDN = DN.NULL_DN;
1119
1120 // Get the parsed bind DN.
1121 final DN bindDN;
1122 try
1123 {
1124 bindDN = new DN(request.getBindDN(), schemaRef.get());
1125 }
1126 catch (final LDAPException le)
1127 {
1128 Debug.debugException(le);
1129 return new LDAPMessage(messageID, new BindResponseProtocolOp(
1130 ResultCode.INVALID_DN_SYNTAX_INT_VALUE, null,
1131 ERR_MEM_HANDLER_BIND_MALFORMED_DN.get(request.getBindDN(),
1132 le.getMessage()),
1133 null, null));
1134 }
1135
1136 // If the bind request is for a SASL bind, then see if there is a SASL
1137 // mechanism handler that can be used to process it.
1138 if (request.getCredentialsType() == BindRequestProtocolOp.CRED_TYPE_SASL)
1139 {
1140 final String mechanism = request.getSASLMechanism();
1141 final InMemorySASLBindHandler handler = saslBindHandlers.get(mechanism);
1142 if (handler == null)
1143 {
1144 return new LDAPMessage(messageID, new BindResponseProtocolOp(
1145 ResultCode.AUTH_METHOD_NOT_SUPPORTED_INT_VALUE, null,
1146 ERR_MEM_HANDLER_SASL_MECH_NOT_SUPPORTED.get(mechanism), null,
1147 null));
1148 }
1149
1150 try
1151 {
1152 final BindResult bindResult = handler.processSASLBind(this, messageID,
1153 bindDN, request.getSASLCredentials(), controls);
1154 return new LDAPMessage(messageID, new BindResponseProtocolOp(
1155 bindResult.getResultCode().intValue(),
1156 bindResult.getMatchedDN(), bindResult.getDiagnosticMessage(),
1157 Arrays.asList(bindResult.getReferralURLs()),
1158 bindResult.getServerSASLCredentials()),
1159 Arrays.asList(bindResult.getResponseControls()));
1160 }
1161 catch (final Exception e)
1162 {
1163 Debug.debugException(e);
1164 return new LDAPMessage(messageID, new BindResponseProtocolOp(
1165 ResultCode.OTHER_INT_VALUE, null,
1166 ERR_MEM_HANDLER_SASL_BIND_FAILURE.get(
1167 StaticUtils.getExceptionMessage(e)),
1168 null, null));
1169 }
1170 }
1171
1172 // If we've gotten here, then the bind must use simple authentication.
1173 // Process the provided request controls.
1174 final Map<String,Control> controlMap;
1175 try
1176 {
1177 controlMap = RequestControlPreProcessor.processControls(
1178 LDAPMessage.PROTOCOL_OP_TYPE_BIND_REQUEST, controls);
1179 }
1180 catch (final LDAPException le)
1181 {
1182 Debug.debugException(le);
1183 return new LDAPMessage(messageID, new BindResponseProtocolOp(
1184 le.getResultCode().intValue(), null, le.getMessage(), null, null));
1185 }
1186 final ArrayList<Control> responseControls = new ArrayList<Control>(1);
1187
1188 // If the bind DN is the null DN, then the bind will be considered
1189 // successful as long as the password is also empty.
1190 final ASN1OctetString bindPassword = request.getSimplePassword();
1191 if (bindDN.isNullDN())
1192 {
1193 if (bindPassword.getValueLength() == 0)
1194 {
1195 if (controlMap.containsKey(AuthorizationIdentityRequestControl.
1196 AUTHORIZATION_IDENTITY_REQUEST_OID))
1197 {
1198 responseControls.add(new AuthorizationIdentityResponseControl(""));
1199 }
1200 return new LDAPMessage(messageID,
1201 new BindResponseProtocolOp(ResultCode.SUCCESS_INT_VALUE, null,
1202 null, null, null),
1203 responseControls);
1204 }
1205 else
1206 {
1207 return new LDAPMessage(messageID, new BindResponseProtocolOp(
1208 ResultCode.INVALID_CREDENTIALS_INT_VALUE,
1209 getMatchedDNString(bindDN),
1210 ERR_MEM_HANDLER_BIND_WRONG_PASSWORD.get(request.getBindDN()), null,
1211 null));
1212 }
1213 }
1214
1215 // If the bind DN is not null and the password is empty, then reject the
1216 // request.
1217 if ((! bindDN.isNullDN()) && (bindPassword.getValueLength() == 0))
1218 {
1219 return new LDAPMessage(messageID, new BindResponseProtocolOp(
1220 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
1221 ERR_MEM_HANDLER_BIND_SIMPLE_DN_WITHOUT_PASSWORD.get(), null, null));
1222 }
1223
1224 // See if the bind DN is in the set of additional bind credentials. If so,
1225 // then use the password there.
1226 final byte[] additionalCreds = additionalBindCredentials.get(bindDN);
1227 if (additionalCreds != null)
1228 {
1229 if (Arrays.equals(additionalCreds, bindPassword.getValue()))
1230 {
1231 authenticatedDN = bindDN;
1232 if (controlMap.containsKey(AuthorizationIdentityRequestControl.
1233 AUTHORIZATION_IDENTITY_REQUEST_OID))
1234 {
1235 responseControls.add(new AuthorizationIdentityResponseControl(
1236 "dn:" + bindDN.toString()));
1237 }
1238 return new LDAPMessage(messageID,
1239 new BindResponseProtocolOp(ResultCode.SUCCESS_INT_VALUE, null,
1240 null, null, null),
1241 responseControls);
1242 }
1243 else
1244 {
1245 return new LDAPMessage(messageID, new BindResponseProtocolOp(
1246 ResultCode.INVALID_CREDENTIALS_INT_VALUE,
1247 getMatchedDNString(bindDN),
1248 ERR_MEM_HANDLER_BIND_WRONG_PASSWORD.get(request.getBindDN()), null,
1249 null));
1250 }
1251 }
1252
1253 // If the target user doesn't exist, then reject the request.
1254 final Entry userEntry = entryMap.get(bindDN);
1255 if (userEntry == null)
1256 {
1257 return new LDAPMessage(messageID, new BindResponseProtocolOp(
1258 ResultCode.INVALID_CREDENTIALS_INT_VALUE, getMatchedDNString(bindDN),
1259 ERR_MEM_HANDLER_BIND_NO_SUCH_USER.get(request.getBindDN()), null,
1260 null));
1261 }
1262
1263 // If the user entry has a userPassword value that matches the provided
1264 // password, then the bind will be successful. Otherwise, it will fail.
1265 if (userEntry.hasAttributeValue("userPassword", bindPassword.getValue(),
1266 OctetStringMatchingRule.getInstance()))
1267 {
1268 authenticatedDN = bindDN;
1269 if (controlMap.containsKey(AuthorizationIdentityRequestControl.
1270 AUTHORIZATION_IDENTITY_REQUEST_OID))
1271 {
1272 responseControls.add(new AuthorizationIdentityResponseControl(
1273 "dn:" + bindDN.toString()));
1274 }
1275 return new LDAPMessage(messageID,
1276 new BindResponseProtocolOp(ResultCode.SUCCESS_INT_VALUE, null, null,
1277 null, null),
1278 responseControls);
1279 }
1280 else
1281 {
1282 return new LDAPMessage(messageID, new BindResponseProtocolOp(
1283 ResultCode.INVALID_CREDENTIALS_INT_VALUE, getMatchedDNString(bindDN),
1284 ERR_MEM_HANDLER_BIND_WRONG_PASSWORD.get(request.getBindDN()), null,
1285 null));
1286 }
1287 }
1288
1289
1290
1291 /**
1292 * Attempts to process the provided compare request. The attempt will fail if
1293 * any of the following conditions is true:
1294 * <UL>
1295 * <LI>There is a problem with any of the request controls.</LI>
1296 * <LI>The compare request contains a malformed target DN.</LI>
1297 * <LI>The target entry does not exist.</LI>
1298 * </UL>
1299 *
1300 * @param messageID The message ID of the LDAP message containing the
1301 * compare request.
1302 * @param request The compare request that was included in the LDAP
1303 * message that was received.
1304 * @param controls The set of controls included in the LDAP message. It
1305 * may be empty if there were no controls, but will not be
1306 * {@code null}.
1307 *
1308 * @return The {@link LDAPMessage} containing the response to send to the
1309 * client. The protocol op in the {@code LDAPMessage} must be a
1310 * {@code CompareResponseProtocolOp}.
1311 */
1312 @Override()
1313 public synchronized LDAPMessage processCompareRequest(final int messageID,
1314 final CompareRequestProtocolOp request,
1315 final List<Control> controls)
1316 {
1317 // Sleep before processing, if appropriate.
1318 sleepBeforeProcessing();
1319
1320 // Process the provided request controls.
1321 final Map<String,Control> controlMap;
1322 try
1323 {
1324 controlMap = RequestControlPreProcessor.processControls(
1325 LDAPMessage.PROTOCOL_OP_TYPE_COMPARE_REQUEST, controls);
1326 }
1327 catch (final LDAPException le)
1328 {
1329 Debug.debugException(le);
1330 return new LDAPMessage(messageID, new CompareResponseProtocolOp(
1331 le.getResultCode().intValue(), null, le.getMessage(), null));
1332 }
1333 final ArrayList<Control> responseControls = new ArrayList<Control>(1);
1334
1335
1336 // If this operation type is not allowed, then reject it.
1337 final boolean isInternalOp =
1338 controlMap.containsKey(OID_INTERNAL_OPERATION_REQUEST_CONTROL);
1339 if ((! isInternalOp) &&
1340 (! config.getAllowedOperationTypes().contains(OperationType.COMPARE)))
1341 {
1342 return new LDAPMessage(messageID, new CompareResponseProtocolOp(
1343 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
1344 ERR_MEM_HANDLER_COMPARE_NOT_ALLOWED.get(), null));
1345 }
1346
1347
1348 // If this operation type requires authentication, then ensure that the
1349 // client is authenticated.
1350 if ((authenticatedDN.isNullDN() &&
1351 config.getAuthenticationRequiredOperationTypes().contains(
1352 OperationType.COMPARE)))
1353 {
1354 return new LDAPMessage(messageID, new CompareResponseProtocolOp(
1355 ResultCode.INSUFFICIENT_ACCESS_RIGHTS_INT_VALUE, null,
1356 ERR_MEM_HANDLER_COMPARE_REQUIRES_AUTH.get(), null));
1357 }
1358
1359
1360 // Get the parsed target DN.
1361 final DN dn;
1362 try
1363 {
1364 dn = new DN(request.getDN(), schemaRef.get());
1365 }
1366 catch (final LDAPException le)
1367 {
1368 Debug.debugException(le);
1369 return new LDAPMessage(messageID, new CompareResponseProtocolOp(
1370 ResultCode.INVALID_DN_SYNTAX_INT_VALUE, null,
1371 ERR_MEM_HANDLER_COMPARE_MALFORMED_DN.get(request.getDN(),
1372 le.getMessage()),
1373 null));
1374 }
1375
1376 // See if the target entry or one of its superiors is a smart referral.
1377 if (! controlMap.containsKey(
1378 ManageDsaITRequestControl.MANAGE_DSA_IT_REQUEST_OID))
1379 {
1380 final Entry referralEntry = findNearestReferral(dn);
1381 if (referralEntry != null)
1382 {
1383 return new LDAPMessage(messageID, new CompareResponseProtocolOp(
1384 ResultCode.REFERRAL_INT_VALUE, referralEntry.getDN(),
1385 INFO_MEM_HANDLER_REFERRAL_ENCOUNTERED.get(),
1386 getReferralURLs(dn, referralEntry)));
1387 }
1388 }
1389
1390 // Get the target entry (optionally checking for the root DSE or subschema
1391 // subentry). If it does not exist, then fail.
1392 final Entry entry;
1393 if (dn.isNullDN())
1394 {
1395 entry = generateRootDSE();
1396 }
1397 else if (dn.equals(subschemaSubentryDN))
1398 {
1399 entry = subschemaSubentryRef.get();
1400 }
1401 else
1402 {
1403 entry = entryMap.get(dn);
1404 }
1405 if (entry == null)
1406 {
1407 return new LDAPMessage(messageID, new CompareResponseProtocolOp(
1408 ResultCode.NO_SUCH_OBJECT_INT_VALUE, getMatchedDNString(dn),
1409 ERR_MEM_HANDLER_COMPARE_NO_SUCH_ENTRY.get(request.getDN()), null));
1410 }
1411
1412 // If the request includes an assertion or proxied authorization control,
1413 // then perform the appropriate processing.
1414 try
1415 {
1416 handleAssertionRequestControl(controlMap, entry);
1417 handleProxiedAuthControl(controlMap);
1418 }
1419 catch (final LDAPException le)
1420 {
1421 Debug.debugException(le);
1422 return new LDAPMessage(messageID, new CompareResponseProtocolOp(
1423 le.getResultCode().intValue(), null, le.getMessage(), null));
1424 }
1425
1426 // See if the entry contains the assertion value.
1427 final int resultCode;
1428 if (entry.hasAttributeValue(request.getAttributeName(),
1429 request.getAssertionValue().getValue()))
1430 {
1431 resultCode = ResultCode.COMPARE_TRUE_INT_VALUE;
1432 }
1433 else
1434 {
1435 resultCode = ResultCode.COMPARE_FALSE_INT_VALUE;
1436 }
1437 return new LDAPMessage(messageID,
1438 new CompareResponseProtocolOp(resultCode, null, null, null),
1439 responseControls);
1440 }
1441
1442
1443
1444 /**
1445 * Attempts to process the provided delete request. The attempt will fail if
1446 * any of the following conditions is true:
1447 * <UL>
1448 * <LI>There is a problem with any of the request controls.</LI>
1449 * <LI>The delete request contains a malformed target DN.</LI>
1450 * <LI>The target entry is the root DSE.</LI>
1451 * <LI>The target entry is the subschema subentry.</LI>
1452 * <LI>The target entry is at or below the changelog base entry.</LI>
1453 * <LI>The target entry does not exist.</LI>
1454 * <LI>The target entry has one or more subordinate entries.</LI>
1455 * </UL>
1456 *
1457 * @param messageID The message ID of the LDAP message containing the delete
1458 * request.
1459 * @param request The delete request that was included in the LDAP message
1460 * that was received.
1461 * @param controls The set of controls included in the LDAP message. It
1462 * may be empty if there were no controls, but will not be
1463 * {@code null}.
1464 *
1465 * @return The {@link LDAPMessage} containing the response to send to the
1466 * client. The protocol op in the {@code LDAPMessage} must be a
1467 * {@code DeleteResponseProtocolOp}.
1468 */
1469 @Override()
1470 public synchronized LDAPMessage processDeleteRequest(final int messageID,
1471 final DeleteRequestProtocolOp request,
1472 final List<Control> controls)
1473 {
1474 // Sleep before processing, if appropriate.
1475 sleepBeforeProcessing();
1476
1477 // Process the provided request controls.
1478 final Map<String,Control> controlMap;
1479 try
1480 {
1481 controlMap = RequestControlPreProcessor.processControls(
1482 LDAPMessage.PROTOCOL_OP_TYPE_DELETE_REQUEST, controls);
1483 }
1484 catch (final LDAPException le)
1485 {
1486 Debug.debugException(le);
1487 return new LDAPMessage(messageID, new DeleteResponseProtocolOp(
1488 le.getResultCode().intValue(), null, le.getMessage(), null));
1489 }
1490 final ArrayList<Control> responseControls = new ArrayList<Control>(1);
1491
1492
1493 // If this operation type is not allowed, then reject it.
1494 final boolean isInternalOp =
1495 controlMap.containsKey(OID_INTERNAL_OPERATION_REQUEST_CONTROL);
1496 if ((! isInternalOp) &&
1497 (! config.getAllowedOperationTypes().contains(OperationType.DELETE)))
1498 {
1499 return new LDAPMessage(messageID, new DeleteResponseProtocolOp(
1500 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
1501 ERR_MEM_HANDLER_DELETE_NOT_ALLOWED.get(), null));
1502 }
1503
1504
1505 // If this operation type requires authentication, then ensure that the
1506 // client is authenticated.
1507 if ((authenticatedDN.isNullDN() &&
1508 config.getAuthenticationRequiredOperationTypes().contains(
1509 OperationType.DELETE)))
1510 {
1511 return new LDAPMessage(messageID, new DeleteResponseProtocolOp(
1512 ResultCode.INSUFFICIENT_ACCESS_RIGHTS_INT_VALUE, null,
1513 ERR_MEM_HANDLER_DELETE_REQUIRES_AUTH.get(), null));
1514 }
1515
1516
1517 // See if this delete request is part of a transaction. If so, then perform
1518 // appropriate processing for it and return success immediately without
1519 // actually doing any further processing.
1520 try
1521 {
1522 final ASN1OctetString txnID =
1523 processTransactionRequest(messageID, request, controlMap);
1524 if (txnID != null)
1525 {
1526 return new LDAPMessage(messageID, new DeleteResponseProtocolOp(
1527 ResultCode.SUCCESS_INT_VALUE, null,
1528 INFO_MEM_HANDLER_OP_IN_TXN.get(txnID.stringValue()), null));
1529 }
1530 }
1531 catch (final LDAPException le)
1532 {
1533 Debug.debugException(le);
1534 return new LDAPMessage(messageID,
1535 new DeleteResponseProtocolOp(le.getResultCode().intValue(),
1536 le.getMatchedDN(), le.getDiagnosticMessage(),
1537 StaticUtils.toList(le.getReferralURLs())),
1538 le.getResponseControls());
1539 }
1540
1541
1542 // Get the parsed target DN.
1543 final DN dn;
1544 try
1545 {
1546 dn = new DN(request.getDN(), schemaRef.get());
1547 }
1548 catch (final LDAPException le)
1549 {
1550 Debug.debugException(le);
1551 return new LDAPMessage(messageID, new DeleteResponseProtocolOp(
1552 ResultCode.INVALID_DN_SYNTAX_INT_VALUE, null,
1553 ERR_MEM_HANDLER_DELETE_MALFORMED_DN.get(request.getDN(),
1554 le.getMessage()),
1555 null));
1556 }
1557
1558 // See if the target entry or one of its superiors is a smart referral.
1559 if (! controlMap.containsKey(
1560 ManageDsaITRequestControl.MANAGE_DSA_IT_REQUEST_OID))
1561 {
1562 final Entry referralEntry = findNearestReferral(dn);
1563 if (referralEntry != null)
1564 {
1565 return new LDAPMessage(messageID, new DeleteResponseProtocolOp(
1566 ResultCode.REFERRAL_INT_VALUE, referralEntry.getDN(),
1567 INFO_MEM_HANDLER_REFERRAL_ENCOUNTERED.get(),
1568 getReferralURLs(dn, referralEntry)));
1569 }
1570 }
1571
1572 // Make sure the target entry isn't the root DSE or schema, or a changelog
1573 // entry.
1574 if (dn.isNullDN())
1575 {
1576 return new LDAPMessage(messageID, new DeleteResponseProtocolOp(
1577 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
1578 ERR_MEM_HANDLER_DELETE_ROOT_DSE.get(), null));
1579 }
1580 else if (dn.equals(subschemaSubentryDN))
1581 {
1582 return new LDAPMessage(messageID, new DeleteResponseProtocolOp(
1583 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
1584 ERR_MEM_HANDLER_DELETE_SCHEMA.get(subschemaSubentryDN.toString()),
1585 null));
1586 }
1587 else if (dn.isDescendantOf(changeLogBaseDN, true))
1588 {
1589 return new LDAPMessage(messageID, new DeleteResponseProtocolOp(
1590 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
1591 ERR_MEM_HANDLER_DELETE_CHANGELOG.get(request.getDN()), null));
1592 }
1593
1594 // Get the target entry. If it does not exist, then fail.
1595 final Entry entry = entryMap.get(dn);
1596 if (entry == null)
1597 {
1598 return new LDAPMessage(messageID, new DeleteResponseProtocolOp(
1599 ResultCode.NO_SUCH_OBJECT_INT_VALUE, getMatchedDNString(dn),
1600 ERR_MEM_HANDLER_DELETE_NO_SUCH_ENTRY.get(request.getDN()), null));
1601 }
1602
1603 // Create a list with the DN of the target entry, and all the DNs of its
1604 // subordinates. If the entry has subordinates and the subtree delete
1605 // control was not provided, then fail.
1606 final ArrayList<DN> subordinateDNs = new ArrayList<DN>(entryMap.size());
1607 for (final DN mapEntryDN : entryMap.keySet())
1608 {
1609 if (mapEntryDN.isDescendantOf(dn, false))
1610 {
1611 subordinateDNs.add(mapEntryDN);
1612 }
1613 }
1614
1615 if ((! subordinateDNs.isEmpty()) &&
1616 (! controlMap.containsKey(
1617 SubtreeDeleteRequestControl.SUBTREE_DELETE_REQUEST_OID)))
1618 {
1619 return new LDAPMessage(messageID, new DeleteResponseProtocolOp(
1620 ResultCode.NOT_ALLOWED_ON_NONLEAF_INT_VALUE, null,
1621 ERR_MEM_HANDLER_DELETE_HAS_SUBORDINATES.get(request.getDN()),
1622 null));
1623 }
1624
1625 // Handle the necessary processing for the assertion, pre-read, and proxied
1626 // auth controls.
1627 final DN authzDN;
1628 try
1629 {
1630 handleAssertionRequestControl(controlMap, entry);
1631
1632 final PreReadResponseControl preReadResponse =
1633 handlePreReadControl(controlMap, entry);
1634 if (preReadResponse != null)
1635 {
1636 responseControls.add(preReadResponse);
1637 }
1638
1639 authzDN = handleProxiedAuthControl(controlMap);
1640 }
1641 catch (final LDAPException le)
1642 {
1643 Debug.debugException(le);
1644 return new LDAPMessage(messageID, new DeleteResponseProtocolOp(
1645 le.getResultCode().intValue(), null, le.getMessage(), null));
1646 }
1647
1648 // At this point, the entry will be removed. However, if this will be a
1649 // subtree delete, then we want to delete all of its subordinates first so
1650 // that the changelog will show the deletes in the appropriate order.
1651 for (int i=(subordinateDNs.size() - 1); i >= 0; i--)
1652 {
1653 final DN subordinateDN = subordinateDNs.get(i);
1654 final Entry subEntry = entryMap.remove(subordinateDN);
1655 indexDelete(subEntry);
1656 addDeleteChangeLogEntry(subEntry, authzDN);
1657 handleReferentialIntegrityDelete(subordinateDN);
1658 }
1659
1660 // Finally, remove the target entry and create a changelog entry for it.
1661 entryMap.remove(dn);
1662 indexDelete(entry);
1663 addDeleteChangeLogEntry(entry, authzDN);
1664 handleReferentialIntegrityDelete(dn);
1665
1666 return new LDAPMessage(messageID,
1667 new DeleteResponseProtocolOp(ResultCode.SUCCESS_INT_VALUE, null, null,
1668 null),
1669 responseControls);
1670 }
1671
1672
1673
1674 /**
1675 * Handles any appropriate referential integrity processing for a delete
1676 * operation.
1677 *
1678 * @param dn The DN of the entry that has been deleted.
1679 */
1680 private void handleReferentialIntegrityDelete(final DN dn)
1681 {
1682 if (referentialIntegrityAttributes.isEmpty())
1683 {
1684 return;
1685 }
1686
1687 final ArrayList<DN> entryDNs = new ArrayList<DN>(entryMap.keySet());
1688 for (final DN mapDN : entryDNs)
1689 {
1690 final ReadOnlyEntry e = entryMap.get(mapDN);
1691
1692 boolean referenceFound = false;
1693 final Schema schema = schemaRef.get();
1694 for (final String attrName : referentialIntegrityAttributes)
1695 {
1696 final Attribute a = e.getAttribute(attrName, schema);
1697 if ((a != null) &&
1698 a.hasValue(dn.toNormalizedString(),
1699 DistinguishedNameMatchingRule.getInstance()))
1700 {
1701 referenceFound = true;
1702 break;
1703 }
1704 }
1705
1706 if (referenceFound)
1707 {
1708 final Entry copy = e.duplicate();
1709 for (final String attrName : referentialIntegrityAttributes)
1710 {
1711 copy.removeAttributeValue(attrName, dn.toNormalizedString(),
1712 DistinguishedNameMatchingRule.getInstance());
1713 }
1714 entryMap.put(mapDN, new ReadOnlyEntry(copy));
1715 indexDelete(e);
1716 indexAdd(copy);
1717 }
1718 }
1719 }
1720
1721
1722
1723 /**
1724 * Attempts to process the provided extended request, if an extended operation
1725 * handler is defined for the given request OID.
1726 *
1727 * @param messageID The message ID of the LDAP message containing the
1728 * extended request.
1729 * @param request The extended request that was included in the LDAP
1730 * message that was received.
1731 * @param controls The set of controls included in the LDAP message. It
1732 * may be empty if there were no controls, but will not be
1733 * {@code null}.
1734 *
1735 * @return The {@link LDAPMessage} containing the response to send to the
1736 * client. The protocol op in the {@code LDAPMessage} must be an
1737 * {@code ExtendedResponseProtocolOp}.
1738 */
1739 @Override()
1740 public synchronized LDAPMessage processExtendedRequest(final int messageID,
1741 final ExtendedRequestProtocolOp request,
1742 final List<Control> controls)
1743 {
1744 // Sleep before processing, if appropriate.
1745 sleepBeforeProcessing();
1746
1747 boolean isInternalOp = false;
1748 for (final Control c : controls)
1749 {
1750 if (c.getOID().equals(OID_INTERNAL_OPERATION_REQUEST_CONTROL))
1751 {
1752 isInternalOp = true;
1753 break;
1754 }
1755 }
1756
1757
1758 // If this operation type is not allowed, then reject it.
1759 if ((! isInternalOp) &&
1760 (! config.getAllowedOperationTypes().contains(OperationType.EXTENDED)))
1761 {
1762 return new LDAPMessage(messageID, new ExtendedResponseProtocolOp(
1763 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
1764 ERR_MEM_HANDLER_EXTENDED_NOT_ALLOWED.get(), null, null, null));
1765 }
1766
1767
1768 // If this operation type requires authentication, then ensure that the
1769 // client is authenticated.
1770 if ((authenticatedDN.isNullDN() &&
1771 config.getAuthenticationRequiredOperationTypes().contains(
1772 OperationType.EXTENDED)))
1773 {
1774 return new LDAPMessage(messageID, new ExtendedResponseProtocolOp(
1775 ResultCode.INSUFFICIENT_ACCESS_RIGHTS_INT_VALUE, null,
1776 ERR_MEM_HANDLER_EXTENDED_REQUIRES_AUTH.get(), null, null, null));
1777 }
1778
1779
1780 final String oid = request.getOID();
1781 final InMemoryExtendedOperationHandler handler =
1782 extendedRequestHandlers.get(oid);
1783 if (handler == null)
1784 {
1785 return new LDAPMessage(messageID, new ExtendedResponseProtocolOp(
1786 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
1787 ERR_MEM_HANDLER_EXTENDED_OP_NOT_SUPPORTED.get(oid), null, null,
1788 null));
1789 }
1790
1791 try
1792 {
1793 final Control[] controlArray = new Control[controls.size()];
1794 controls.toArray(controlArray);
1795
1796 final ExtendedRequest extendedRequest = new ExtendedRequest(oid,
1797 request.getValue(), controlArray);
1798
1799 final ExtendedResult extendedResult =
1800 handler.processExtendedOperation(this, messageID, extendedRequest);
1801
1802 return new LDAPMessage(messageID,
1803 new ExtendedResponseProtocolOp(
1804 extendedResult.getResultCode().intValue(),
1805 extendedResult.getMatchedDN(),
1806 extendedResult.getDiagnosticMessage(),
1807 Arrays.asList(extendedResult.getReferralURLs()),
1808 extendedResult.getOID(), extendedResult.getValue()),
1809 extendedResult.getResponseControls());
1810 }
1811 catch (final Exception e)
1812 {
1813 Debug.debugException(e);
1814
1815 return new LDAPMessage(messageID, new ExtendedResponseProtocolOp(
1816 ResultCode.OTHER_INT_VALUE, null,
1817 ERR_MEM_HANDLER_EXTENDED_OP_FAILURE.get(
1818 StaticUtils.getExceptionMessage(e)),
1819 null, null, null));
1820 }
1821 }
1822
1823
1824
1825 /**
1826 * Attempts to process the provided modify request. The attempt will fail if
1827 * any of the following conditions is true:
1828 * <UL>
1829 * <LI>There is a problem with any of the request controls.</LI>
1830 * <LI>The modify request contains a malformed target DN.</LI>
1831 * <LI>The target entry is the root DSE.</LI>
1832 * <LI>The target entry is the subschema subentry.</LI>
1833 * <LI>The target entry does not exist.</LI>
1834 * <LI>Any of the modifications cannot be applied to the entry.</LI>
1835 * <LI>If a schema was provided, and the entry violates any of the
1836 * constraints of that schema.</LI>
1837 * </UL>
1838 *
1839 * @param messageID The message ID of the LDAP message containing the modify
1840 * request.
1841 * @param request The modify request that was included in the LDAP message
1842 * that was received.
1843 * @param controls The set of controls included in the LDAP message. It
1844 * may be empty if there were no controls, but will not be
1845 * {@code null}.
1846 *
1847 * @return The {@link LDAPMessage} containing the response to send to the
1848 * client. The protocol op in the {@code LDAPMessage} must be an
1849 * {@code ModifyResponseProtocolOp}.
1850 */
1851 @Override()
1852 public synchronized LDAPMessage processModifyRequest(final int messageID,
1853 final ModifyRequestProtocolOp request,
1854 final List<Control> controls)
1855 {
1856 // Sleep before processing, if appropriate.
1857 sleepBeforeProcessing();
1858
1859 // Process the provided request controls.
1860 final Map<String,Control> controlMap;
1861 try
1862 {
1863 controlMap = RequestControlPreProcessor.processControls(
1864 LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_REQUEST, controls);
1865 }
1866 catch (final LDAPException le)
1867 {
1868 Debug.debugException(le);
1869 return new LDAPMessage(messageID, new ModifyResponseProtocolOp(
1870 le.getResultCode().intValue(), null, le.getMessage(), null));
1871 }
1872 final ArrayList<Control> responseControls = new ArrayList<Control>(1);
1873
1874
1875 // If this operation type is not allowed, then reject it.
1876 final boolean isInternalOp =
1877 controlMap.containsKey(OID_INTERNAL_OPERATION_REQUEST_CONTROL);
1878 if ((! isInternalOp) &&
1879 (! config.getAllowedOperationTypes().contains(OperationType.MODIFY)))
1880 {
1881 return new LDAPMessage(messageID, new ModifyResponseProtocolOp(
1882 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
1883 ERR_MEM_HANDLER_MODIFY_NOT_ALLOWED.get(), null));
1884 }
1885
1886
1887 // If this operation type requires authentication, then ensure that the
1888 // client is authenticated.
1889 if ((authenticatedDN.isNullDN() &&
1890 config.getAuthenticationRequiredOperationTypes().contains(
1891 OperationType.MODIFY)))
1892 {
1893 return new LDAPMessage(messageID, new ModifyResponseProtocolOp(
1894 ResultCode.INSUFFICIENT_ACCESS_RIGHTS_INT_VALUE, null,
1895 ERR_MEM_HANDLER_MODIFY_REQUIRES_AUTH.get(), null));
1896 }
1897
1898
1899 // See if this modify request is part of a transaction. If so, then perform
1900 // appropriate processing for it and return success immediately without
1901 // actually doing any further processing.
1902 try
1903 {
1904 final ASN1OctetString txnID =
1905 processTransactionRequest(messageID, request, controlMap);
1906 if (txnID != null)
1907 {
1908 return new LDAPMessage(messageID, new ModifyResponseProtocolOp(
1909 ResultCode.SUCCESS_INT_VALUE, null,
1910 INFO_MEM_HANDLER_OP_IN_TXN.get(txnID.stringValue()), null));
1911 }
1912 }
1913 catch (final LDAPException le)
1914 {
1915 Debug.debugException(le);
1916 return new LDAPMessage(messageID,
1917 new ModifyResponseProtocolOp(le.getResultCode().intValue(),
1918 le.getMatchedDN(), le.getDiagnosticMessage(),
1919 StaticUtils.toList(le.getReferralURLs())),
1920 le.getResponseControls());
1921 }
1922
1923
1924 // Get the parsed target DN.
1925 final DN dn;
1926 final Schema schema = schemaRef.get();
1927 try
1928 {
1929 dn = new DN(request.getDN(), schema);
1930 }
1931 catch (final LDAPException le)
1932 {
1933 Debug.debugException(le);
1934 return new LDAPMessage(messageID, new ModifyResponseProtocolOp(
1935 ResultCode.INVALID_DN_SYNTAX_INT_VALUE, null,
1936 ERR_MEM_HANDLER_MOD_MALFORMED_DN.get(request.getDN(),
1937 le.getMessage()),
1938 null));
1939 }
1940
1941 // See if the target entry or one of its superiors is a smart referral.
1942 if (! controlMap.containsKey(
1943 ManageDsaITRequestControl.MANAGE_DSA_IT_REQUEST_OID))
1944 {
1945 final Entry referralEntry = findNearestReferral(dn);
1946 if (referralEntry != null)
1947 {
1948 return new LDAPMessage(messageID, new ModifyResponseProtocolOp(
1949 ResultCode.REFERRAL_INT_VALUE, referralEntry.getDN(),
1950 INFO_MEM_HANDLER_REFERRAL_ENCOUNTERED.get(),
1951 getReferralURLs(dn, referralEntry)));
1952 }
1953 }
1954
1955 // See if the target entry is the root DSE, the subschema subentry, or a
1956 // changelog entry.
1957 if (dn.isNullDN())
1958 {
1959 return new LDAPMessage(messageID, new ModifyResponseProtocolOp(
1960 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
1961 ERR_MEM_HANDLER_MOD_ROOT_DSE.get(), null));
1962 }
1963 else if (dn.equals(subschemaSubentryDN))
1964 {
1965 try
1966 {
1967 validateSchemaMods(request);
1968 }
1969 catch (final LDAPException le)
1970 {
1971 return new LDAPMessage(messageID, new ModifyResponseProtocolOp(
1972 le.getResultCode().intValue(), le.getMatchedDN(), le.getMessage(),
1973 null));
1974 }
1975 }
1976 else if (dn.isDescendantOf(changeLogBaseDN, true))
1977 {
1978 return new LDAPMessage(messageID, new ModifyResponseProtocolOp(
1979 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
1980 ERR_MEM_HANDLER_MOD_CHANGELOG.get(request.getDN()), null));
1981 }
1982
1983 // Get the target entry. If it does not exist, then fail.
1984 Entry entry = entryMap.get(dn);
1985 if (entry == null)
1986 {
1987 if (dn.equals(subschemaSubentryDN))
1988 {
1989 entry = subschemaSubentryRef.get().duplicate();
1990 }
1991 else
1992 {
1993 return new LDAPMessage(messageID, new ModifyResponseProtocolOp(
1994 ResultCode.NO_SUCH_OBJECT_INT_VALUE, getMatchedDNString(dn),
1995 ERR_MEM_HANDLER_MOD_NO_SUCH_ENTRY.get(request.getDN()), null));
1996 }
1997 }
1998
1999
2000 // Attempt to apply the modifications to the entry. If successful, then a
2001 // copy of the entry will be returned with the modifications applied.
2002 final Entry modifiedEntry;
2003 try
2004 {
2005 modifiedEntry = Entry.applyModifications(entry,
2006 controlMap.containsKey(PermissiveModifyRequestControl.
2007 PERMISSIVE_MODIFY_REQUEST_OID),
2008 request.getModifications());
2009 }
2010 catch (final LDAPException le)
2011 {
2012 Debug.debugException(le);
2013 return new LDAPMessage(messageID, new ModifyResponseProtocolOp(
2014 le.getResultCode().intValue(), null,
2015 ERR_MEM_HANDLER_MOD_FAILED.get(request.getDN(), le.getMessage()),
2016 null));
2017 }
2018
2019 // If a schema was provided, use it to validate the resulting entry. Also,
2020 // ensure that no NO-USER-MODIFICATION attributes were targeted.
2021 final EntryValidator entryValidator = entryValidatorRef.get();
2022 if (entryValidator != null)
2023 {
2024 final ArrayList<String> invalidReasons = new ArrayList<String>(1);
2025 if (! entryValidator.entryIsValid(modifiedEntry, invalidReasons))
2026 {
2027 return new LDAPMessage(messageID, new ModifyResponseProtocolOp(
2028 ResultCode.OBJECT_CLASS_VIOLATION_INT_VALUE, null,
2029 ERR_MEM_HANDLER_MOD_VIOLATES_SCHEMA.get(request.getDN(),
2030 StaticUtils.concatenateStrings(invalidReasons)),
2031 null));
2032 }
2033
2034 for (final Modification m : request.getModifications())
2035 {
2036 final Attribute a = m.getAttribute();
2037 final String baseName = a.getBaseName();
2038 final AttributeTypeDefinition at = schema.getAttributeType(baseName);
2039 if ((! isInternalOp) && (at != null) && at.isNoUserModification())
2040 {
2041 return new LDAPMessage(messageID, new ModifyResponseProtocolOp(
2042 ResultCode.CONSTRAINT_VIOLATION_INT_VALUE, null,
2043 ERR_MEM_HANDLER_MOD_NO_USER_MOD.get(request.getDN(),
2044 a.getName()), null));
2045 }
2046 }
2047 }
2048
2049
2050 // Perform the appropriate processing for the assertion, pre-read,
2051 // post-read, and proxied authorization controls.
2052 final DN authzDN;
2053 try
2054 {
2055 handleAssertionRequestControl(controlMap, entry);
2056
2057 final PreReadResponseControl preReadResponse =
2058 handlePreReadControl(controlMap, entry);
2059 if (preReadResponse != null)
2060 {
2061 responseControls.add(preReadResponse);
2062 }
2063
2064 final PostReadResponseControl postReadResponse =
2065 handlePostReadControl(controlMap, modifiedEntry);
2066 if (postReadResponse != null)
2067 {
2068 responseControls.add(postReadResponse);
2069 }
2070
2071 authzDN = handleProxiedAuthControl(controlMap);
2072 }
2073 catch (final LDAPException le)
2074 {
2075 Debug.debugException(le);
2076 return new LDAPMessage(messageID, new ModifyResponseProtocolOp(
2077 le.getResultCode().intValue(), null, le.getMessage(), null));
2078 }
2079
2080 // Update modifiersName and modifyTimestamp.
2081 if (generateOperationalAttributes)
2082 {
2083 modifiedEntry.setAttribute(new Attribute("modifiersName",
2084 DistinguishedNameMatchingRule.getInstance(),
2085 authzDN.toString()));
2086 modifiedEntry.setAttribute(new Attribute("modifyTimestamp",
2087 GeneralizedTimeMatchingRule.getInstance(),
2088 StaticUtils.encodeGeneralizedTime(new Date())));
2089 }
2090
2091
2092 // Replace the entry in the map and return a success result.
2093 if (dn.equals(subschemaSubentryDN))
2094 {
2095 final Schema newSchema = new Schema(modifiedEntry);
2096 subschemaSubentryRef.set(new ReadOnlyEntry(modifiedEntry));
2097 schemaRef.set(newSchema);
2098 entryValidatorRef.set(new EntryValidator(newSchema));
2099 }
2100 else
2101 {
2102 entryMap.put(dn, new ReadOnlyEntry(modifiedEntry));
2103 indexDelete(entry);
2104 indexAdd(modifiedEntry);
2105 }
2106 addChangeLogEntry(request, authzDN);
2107 return new LDAPMessage(messageID,
2108 new ModifyResponseProtocolOp(ResultCode.SUCCESS_INT_VALUE, null, null,
2109 null),
2110 responseControls);
2111 }
2112
2113
2114
2115 /**
2116 * Validates a modify request targeting the server schema. Modifications to
2117 * attribute syntaxes and matching rules will not be allowed. Modifications
2118 * to other schema elements will only be allowed for add and delete
2119 * modification types, and adds will only be allowed with a valid syntax.
2120 *
2121 * @param request The modify request to validate.
2122 *
2123 * @throws LDAPException If a problem is encountered.
2124 */
2125 private void validateSchemaMods(final ModifyRequestProtocolOp request)
2126 throws LDAPException
2127 {
2128 // If there is no schema, then we won't allow modifications at all.
2129 if (schemaRef.get() == null)
2130 {
2131 throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM,
2132 ERR_MEM_HANDLER_MOD_SCHEMA.get(subschemaSubentryDN.toString()));
2133 }
2134
2135
2136 for (final Modification m : request.getModifications())
2137 {
2138 // If the modification targets attribute syntaxes or matching rules, then
2139 // reject it.
2140 final String attrName = m.getAttributeName();
2141 if (attrName.equalsIgnoreCase(Schema.ATTR_ATTRIBUTE_SYNTAX) ||
2142 attrName.equalsIgnoreCase(Schema.ATTR_MATCHING_RULE))
2143 {
2144 throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM,
2145 ERR_MEM_HANDLER_MOD_SCHEMA_DISALLOWED_ATTR.get(attrName));
2146 }
2147 else if (attrName.equalsIgnoreCase(Schema.ATTR_ATTRIBUTE_TYPE))
2148 {
2149 if (m.getModificationType() == ModificationType.ADD)
2150 {
2151 for (final String value : m.getValues())
2152 {
2153 new AttributeTypeDefinition(value);
2154 }
2155 }
2156 else if (m.getModificationType() != ModificationType.DELETE)
2157 {
2158 throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM,
2159 ERR_MEM_HANDLER_MOD_SCHEMA_DISALLOWED_MOD_TYPE.get(
2160 m.getModificationType().getName(), attrName));
2161 }
2162 }
2163 else if (attrName.equalsIgnoreCase(Schema.ATTR_OBJECT_CLASS))
2164 {
2165 if (m.getModificationType() == ModificationType.ADD)
2166 {
2167 for (final String value : m.getValues())
2168 {
2169 new ObjectClassDefinition(value);
2170 }
2171 }
2172 else if (m.getModificationType() != ModificationType.DELETE)
2173 {
2174 throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM,
2175 ERR_MEM_HANDLER_MOD_SCHEMA_DISALLOWED_MOD_TYPE.get(
2176 m.getModificationType().getName(), attrName));
2177 }
2178 }
2179 else if (attrName.equalsIgnoreCase(Schema.ATTR_NAME_FORM))
2180 {
2181 if (m.getModificationType() == ModificationType.ADD)
2182 {
2183 for (final String value : m.getValues())
2184 {
2185 new NameFormDefinition(value);
2186 }
2187 }
2188 else if (m.getModificationType() != ModificationType.DELETE)
2189 {
2190 throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM,
2191 ERR_MEM_HANDLER_MOD_SCHEMA_DISALLOWED_MOD_TYPE.get(
2192 m.getModificationType().getName(), attrName));
2193 }
2194 }
2195 else if (attrName.equalsIgnoreCase(Schema.ATTR_DIT_CONTENT_RULE))
2196 {
2197 if (m.getModificationType() == ModificationType.ADD)
2198 {
2199 for (final String value : m.getValues())
2200 {
2201 new DITContentRuleDefinition(value);
2202 }
2203 }
2204 else if (m.getModificationType() != ModificationType.DELETE)
2205 {
2206 throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM,
2207 ERR_MEM_HANDLER_MOD_SCHEMA_DISALLOWED_MOD_TYPE.get(
2208 m.getModificationType().getName(), attrName));
2209 }
2210 }
2211 else if (attrName.equalsIgnoreCase(Schema.ATTR_DIT_STRUCTURE_RULE))
2212 {
2213 if (m.getModificationType() == ModificationType.ADD)
2214 {
2215 for (final String value : m.getValues())
2216 {
2217 new DITStructureRuleDefinition(value);
2218 }
2219 }
2220 else if (m.getModificationType() != ModificationType.DELETE)
2221 {
2222 throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM,
2223 ERR_MEM_HANDLER_MOD_SCHEMA_DISALLOWED_MOD_TYPE.get(
2224 m.getModificationType().getName(), attrName));
2225 }
2226 }
2227 else if (attrName.equalsIgnoreCase(Schema.ATTR_MATCHING_RULE_USE))
2228 {
2229 if (m.getModificationType() == ModificationType.ADD)
2230 {
2231 for (final String value : m.getValues())
2232 {
2233 new MatchingRuleUseDefinition(value);
2234 }
2235 }
2236 else if (m.getModificationType() != ModificationType.DELETE)
2237 {
2238 throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM,
2239 ERR_MEM_HANDLER_MOD_SCHEMA_DISALLOWED_MOD_TYPE.get(
2240 m.getModificationType().getName(), attrName));
2241 }
2242 }
2243 }
2244 }
2245
2246
2247
2248 /**
2249 * Attempts to process the provided modify DN request. The attempt will fail
2250 * if any of the following conditions is true:
2251 * <UL>
2252 * <LI>There is a problem with any of the request controls.</LI>
2253 * <LI>The modify DN request contains a malformed target DN, new RDN, or
2254 * new superior DN.</LI>
2255 * <LI>The original or new DN is that of the root DSE.</LI>
2256 * <LI>The original or new DN is that of the subschema subentry.</LI>
2257 * <LI>The new DN of the entry would conflict with the DN of an existing
2258 * entry.</LI>
2259 * <LI>The new DN of the entry would exist outside the set of defined
2260 * base DNs.</LI>
2261 * <LI>The new DN of the entry is not a defined base DN and does not exist
2262 * immediately below an existing entry.</LI>
2263 * </UL>
2264 *
2265 * @param messageID The message ID of the LDAP message containing the modify
2266 * DN request.
2267 * @param request The modify DN request that was included in the LDAP
2268 * message that was received.
2269 * @param controls The set of controls included in the LDAP message. It
2270 * may be empty if there were no controls, but will not be
2271 * {@code null}.
2272 *
2273 * @return The {@link LDAPMessage} containing the response to send to the
2274 * client. The protocol op in the {@code LDAPMessage} must be an
2275 * {@code ModifyDNResponseProtocolOp}.
2276 */
2277 @Override()
2278 public synchronized LDAPMessage processModifyDNRequest(final int messageID,
2279 final ModifyDNRequestProtocolOp request,
2280 final List<Control> controls)
2281 {
2282 // Sleep before processing, if appropriate.
2283 sleepBeforeProcessing();
2284
2285 // Process the provided request controls.
2286 final Map<String,Control> controlMap;
2287 try
2288 {
2289 controlMap = RequestControlPreProcessor.processControls(
2290 LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_DN_REQUEST, controls);
2291 }
2292 catch (final LDAPException le)
2293 {
2294 Debug.debugException(le);
2295 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2296 le.getResultCode().intValue(), null, le.getMessage(), null));
2297 }
2298 final ArrayList<Control> responseControls = new ArrayList<Control>(1);
2299
2300
2301 // If this operation type is not allowed, then reject it.
2302 final boolean isInternalOp =
2303 controlMap.containsKey(OID_INTERNAL_OPERATION_REQUEST_CONTROL);
2304 if ((! isInternalOp) &&
2305 (! config.getAllowedOperationTypes().contains(OperationType.MODIFY_DN)))
2306 {
2307 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2308 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
2309 ERR_MEM_HANDLER_MODIFY_DN_NOT_ALLOWED.get(), null));
2310 }
2311
2312
2313 // If this operation type requires authentication, then ensure that the
2314 // client is authenticated.
2315 if ((authenticatedDN.isNullDN() &&
2316 config.getAuthenticationRequiredOperationTypes().contains(
2317 OperationType.MODIFY_DN)))
2318 {
2319 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2320 ResultCode.INSUFFICIENT_ACCESS_RIGHTS_INT_VALUE, null,
2321 ERR_MEM_HANDLER_MODIFY_DN_REQUIRES_AUTH.get(), null));
2322 }
2323
2324
2325 // See if this modify DN request is part of a transaction. If so, then
2326 // perform appropriate processing for it and return success immediately
2327 // without actually doing any further processing.
2328 try
2329 {
2330 final ASN1OctetString txnID =
2331 processTransactionRequest(messageID, request, controlMap);
2332 if (txnID != null)
2333 {
2334 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2335 ResultCode.SUCCESS_INT_VALUE, null,
2336 INFO_MEM_HANDLER_OP_IN_TXN.get(txnID.stringValue()), null));
2337 }
2338 }
2339 catch (final LDAPException le)
2340 {
2341 Debug.debugException(le);
2342 return new LDAPMessage(messageID,
2343 new ModifyDNResponseProtocolOp(le.getResultCode().intValue(),
2344 le.getMatchedDN(), le.getDiagnosticMessage(),
2345 StaticUtils.toList(le.getReferralURLs())),
2346 le.getResponseControls());
2347 }
2348
2349
2350 // Get the parsed target DN, new RDN, and new superior DN values.
2351 final DN dn;
2352 final Schema schema = schemaRef.get();
2353 try
2354 {
2355 dn = new DN(request.getDN(), schema);
2356 }
2357 catch (final LDAPException le)
2358 {
2359 Debug.debugException(le);
2360 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2361 ResultCode.INVALID_DN_SYNTAX_INT_VALUE, null,
2362 ERR_MEM_HANDLER_MOD_DN_MALFORMED_DN.get(request.getDN(),
2363 le.getMessage()),
2364 null));
2365 }
2366
2367 final RDN newRDN;
2368 try
2369 {
2370 newRDN = new RDN(request.getNewRDN(), schema);
2371 }
2372 catch (final LDAPException le)
2373 {
2374 Debug.debugException(le);
2375 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2376 ResultCode.INVALID_DN_SYNTAX_INT_VALUE, null,
2377 ERR_MEM_HANDLER_MOD_DN_MALFORMED_NEW_RDN.get(request.getDN(),
2378 request.getNewRDN(), le.getMessage()),
2379 null));
2380 }
2381
2382 final DN newSuperiorDN;
2383 final String newSuperiorString = request.getNewSuperiorDN();
2384 if (newSuperiorString == null)
2385 {
2386 newSuperiorDN = null;
2387 }
2388 else
2389 {
2390 try
2391 {
2392 newSuperiorDN = new DN(newSuperiorString, schema);
2393 }
2394 catch (final LDAPException le)
2395 {
2396 Debug.debugException(le);
2397 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2398 ResultCode.INVALID_DN_SYNTAX_INT_VALUE, null,
2399 ERR_MEM_HANDLER_MOD_DN_MALFORMED_NEW_SUPERIOR.get(request.getDN(),
2400 request.getNewSuperiorDN(), le.getMessage()),
2401 null));
2402 }
2403 }
2404
2405 // See if the target entry or one of its superiors is a smart referral.
2406 if (! controlMap.containsKey(
2407 ManageDsaITRequestControl.MANAGE_DSA_IT_REQUEST_OID))
2408 {
2409 final Entry referralEntry = findNearestReferral(dn);
2410 if (referralEntry != null)
2411 {
2412 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2413 ResultCode.REFERRAL_INT_VALUE, referralEntry.getDN(),
2414 INFO_MEM_HANDLER_REFERRAL_ENCOUNTERED.get(),
2415 getReferralURLs(dn, referralEntry)));
2416 }
2417 }
2418
2419 // See if the target is the root DSE, the subschema subentry, or a changelog
2420 // entry.
2421 if (dn.isNullDN())
2422 {
2423 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2424 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
2425 ERR_MEM_HANDLER_MOD_DN_ROOT_DSE.get(), null));
2426 }
2427 else if (dn.equals(subschemaSubentryDN))
2428 {
2429 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2430 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
2431 ERR_MEM_HANDLER_MOD_DN_SOURCE_IS_SCHEMA.get(), null));
2432 }
2433 else if (dn.isDescendantOf(changeLogBaseDN, true))
2434 {
2435 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2436 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
2437 ERR_MEM_HANDLER_MOD_DN_SOURCE_IS_CHANGELOG.get(), null));
2438 }
2439
2440 // Construct the new DN.
2441 final DN newDN;
2442 if (newSuperiorDN == null)
2443 {
2444 final DN originalParent = dn.getParent();
2445 if (originalParent == null)
2446 {
2447 newDN = new DN(newRDN);
2448 }
2449 else
2450 {
2451 newDN = new DN(newRDN, originalParent);
2452 }
2453 }
2454 else
2455 {
2456 newDN = new DN(newRDN, newSuperiorDN);
2457 }
2458
2459 // If the new DN matches the old DN, then fail.
2460 if (newDN.equals(dn))
2461 {
2462 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2463 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
2464 ERR_MEM_HANDLER_MOD_DN_NEW_DN_SAME_AS_OLD.get(request.getDN()),
2465 null));
2466 }
2467
2468 // If the new DN is below a smart referral, then fail.
2469 if (! controlMap.containsKey(
2470 ManageDsaITRequestControl.MANAGE_DSA_IT_REQUEST_OID))
2471 {
2472 final Entry referralEntry = findNearestReferral(newDN);
2473 if (referralEntry != null)
2474 {
2475 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2476 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, referralEntry.getDN(),
2477 ERR_MEM_HANDLER_MOD_DN_NEW_DN_BELOW_REFERRAL.get(request.getDN(),
2478 referralEntry.getDN().toString(), newDN.toString()),
2479 null));
2480 }
2481 }
2482
2483 // If the target entry doesn't exist, then fail.
2484 final Entry originalEntry = entryMap.get(dn);
2485 if (originalEntry == null)
2486 {
2487 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2488 ResultCode.NO_SUCH_OBJECT_INT_VALUE, getMatchedDNString(dn),
2489 ERR_MEM_HANDLER_MOD_DN_NO_SUCH_ENTRY.get(request.getDN()), null));
2490 }
2491
2492 // If the new DN matches the subschema subentry DN, then fail.
2493 if (newDN.equals(subschemaSubentryDN))
2494 {
2495 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2496 ResultCode.ENTRY_ALREADY_EXISTS_INT_VALUE, null,
2497 ERR_MEM_HANDLER_MOD_DN_TARGET_IS_SCHEMA.get(request.getDN(),
2498 newDN.toString()),
2499 null));
2500 }
2501
2502 // If the new DN is at or below the changelog base DN, then fail.
2503 if (newDN.isDescendantOf(changeLogBaseDN, true))
2504 {
2505 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2506 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
2507 ERR_MEM_HANDLER_MOD_DN_TARGET_IS_CHANGELOG.get(request.getDN(),
2508 newDN.toString()),
2509 null));
2510 }
2511
2512 // If the new DN already exists, then fail.
2513 if (entryMap.containsKey(newDN))
2514 {
2515 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2516 ResultCode.ENTRY_ALREADY_EXISTS_INT_VALUE, null,
2517 ERR_MEM_HANDLER_MOD_DN_TARGET_ALREADY_EXISTS.get(request.getDN(),
2518 newDN.toString()),
2519 null));
2520 }
2521
2522 // If the new DN is not a base DN and its parent does not exist, then fail.
2523 if (baseDNs.contains(newDN))
2524 {
2525 // The modify DN can be processed.
2526 }
2527 else
2528 {
2529 final DN newParent = newDN.getParent();
2530 if ((newParent != null) && entryMap.containsKey(newParent))
2531 {
2532 // The modify DN can be processed.
2533 }
2534 else
2535 {
2536 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2537 ResultCode.NO_SUCH_OBJECT_INT_VALUE, getMatchedDNString(newDN),
2538 ERR_MEM_HANDLER_MOD_DN_PARENT_DOESNT_EXIST.get(request.getDN(),
2539 newDN.toString()),
2540 null));
2541 }
2542 }
2543
2544 // Create a copy of the entry and update it to reflect the new DN (with
2545 // attribute value changes).
2546 final RDN originalRDN = dn.getRDN();
2547 final Entry updatedEntry = originalEntry.duplicate();
2548 updatedEntry.setDN(newDN);
2549 if (request.deleteOldRDN() && (! newRDN.equals(originalRDN)))
2550 {
2551 final String[] oldRDNNames = originalRDN.getAttributeNames();
2552 final byte[][] oldRDNValues = originalRDN.getByteArrayAttributeValues();
2553 for (int i=0; i < oldRDNNames.length; i++)
2554 {
2555 updatedEntry.removeAttributeValue(oldRDNNames[i], oldRDNValues[i]);
2556 }
2557
2558 final String[] newRDNNames = newRDN.getAttributeNames();
2559 final byte[][] newRDNValues = newRDN.getByteArrayAttributeValues();
2560 for (int i=0; i < newRDNNames.length; i++)
2561 {
2562 final MatchingRule matchingRule =
2563 MatchingRule.selectEqualityMatchingRule(newRDNNames[i], schema);
2564 updatedEntry.addAttribute(new Attribute(newRDNNames[i], matchingRule,
2565 newRDNValues[i]));
2566 }
2567 }
2568
2569 // If a schema was provided, then make sure the updated entry conforms to
2570 // the schema. Also, reject the attempt if any of the new RDN attributes
2571 // is marked with NO-USER-MODIFICATION.
2572 final EntryValidator entryValidator = entryValidatorRef.get();
2573 if (entryValidator != null)
2574 {
2575 final ArrayList<String> invalidReasons = new ArrayList<String>(1);
2576 if (! entryValidator.entryIsValid(updatedEntry, invalidReasons))
2577 {
2578 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2579 ResultCode.OBJECT_CLASS_VIOLATION_INT_VALUE, null,
2580 ERR_MEM_HANDLER_MOD_DN_VIOLATES_SCHEMA.get(request.getDN(),
2581 StaticUtils.concatenateStrings(invalidReasons)),
2582 null));
2583 }
2584
2585 final String[] oldRDNNames = originalRDN.getAttributeNames();
2586 for (int i=0; i < oldRDNNames.length; i++)
2587 {
2588 final String name = oldRDNNames[i];
2589 final AttributeTypeDefinition at = schema.getAttributeType(name);
2590 if ((! isInternalOp) && (at != null) && at.isNoUserModification())
2591 {
2592 final byte[] value = originalRDN.getByteArrayAttributeValues()[i];
2593 if (! updatedEntry.hasAttributeValue(name, value))
2594 {
2595 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2596 ResultCode.CONSTRAINT_VIOLATION_INT_VALUE, null,
2597 ERR_MEM_HANDLER_MOD_DN_NO_USER_MOD.get(request.getDN(),
2598 name), null));
2599 }
2600 }
2601 }
2602
2603 final String[] newRDNNames = newRDN.getAttributeNames();
2604 for (int i=0; i < newRDNNames.length; i++)
2605 {
2606 final String name = newRDNNames[i];
2607 final AttributeTypeDefinition at = schema.getAttributeType(name);
2608 if ((! isInternalOp) && (at != null) && at.isNoUserModification())
2609 {
2610 final byte[] value = newRDN.getByteArrayAttributeValues()[i];
2611 if (! originalEntry.hasAttributeValue(name, value))
2612 {
2613 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2614 ResultCode.CONSTRAINT_VIOLATION_INT_VALUE, null,
2615 ERR_MEM_HANDLER_MOD_DN_NO_USER_MOD.get(request.getDN(),
2616 name), null));
2617 }
2618 }
2619 }
2620 }
2621
2622 // Perform the appropriate processing for the assertion, pre-read,
2623 // post-read, and proxied authorization controls
2624 // Perform the appropriate processing for the assertion, pre-read,
2625 // post-read, and proxied authorization controls.
2626 final DN authzDN;
2627 try
2628 {
2629 handleAssertionRequestControl(controlMap, originalEntry);
2630
2631 final PreReadResponseControl preReadResponse =
2632 handlePreReadControl(controlMap, originalEntry);
2633 if (preReadResponse != null)
2634 {
2635 responseControls.add(preReadResponse);
2636 }
2637
2638 final PostReadResponseControl postReadResponse =
2639 handlePostReadControl(controlMap, updatedEntry);
2640 if (postReadResponse != null)
2641 {
2642 responseControls.add(postReadResponse);
2643 }
2644
2645 authzDN = handleProxiedAuthControl(controlMap);
2646 }
2647 catch (final LDAPException le)
2648 {
2649 Debug.debugException(le);
2650 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp(
2651 le.getResultCode().intValue(), null, le.getMessage(), null));
2652 }
2653
2654 // Update the modifiersName, modifyTimestamp, and entryDN operational
2655 // attributes.
2656 if (generateOperationalAttributes)
2657 {
2658 updatedEntry.setAttribute(new Attribute("modifiersName",
2659 DistinguishedNameMatchingRule.getInstance(),
2660 authzDN.toString()));
2661 updatedEntry.setAttribute(new Attribute("modifyTimestamp",
2662 GeneralizedTimeMatchingRule.getInstance(),
2663 StaticUtils.encodeGeneralizedTime(new Date())));
2664 updatedEntry.setAttribute(new Attribute("entryDN",
2665 DistinguishedNameMatchingRule.getInstance(),
2666 newDN.toNormalizedString()));
2667 }
2668
2669 // Remove the old entry and add the new one.
2670 entryMap.remove(dn);
2671 entryMap.put(newDN, new ReadOnlyEntry(updatedEntry));
2672 indexDelete(originalEntry);
2673 indexAdd(updatedEntry);
2674
2675 // If the target entry had any subordinates, then rename them as well.
2676 final RDN[] oldDNComps = dn.getRDNs();
2677 final RDN[] newDNComps = newDN.getRDNs();
2678 final Set<DN> dnSet = new LinkedHashSet<DN>(entryMap.keySet());
2679 for (final DN mapEntryDN : dnSet)
2680 {
2681 if (mapEntryDN.isDescendantOf(dn, false))
2682 {
2683 final Entry o = entryMap.remove(mapEntryDN);
2684 final Entry e = o.duplicate();
2685
2686 final RDN[] oldMapEntryComps = mapEntryDN.getRDNs();
2687 final int compsToSave = oldMapEntryComps.length - oldDNComps.length ;
2688
2689 final RDN[] newMapEntryComps = new RDN[compsToSave + newDNComps.length];
2690 System.arraycopy(oldMapEntryComps, 0, newMapEntryComps, 0,
2691 compsToSave);
2692 System.arraycopy(newDNComps, 0, newMapEntryComps, compsToSave,
2693 newDNComps.length);
2694
2695 final DN newMapEntryDN = new DN(newMapEntryComps);
2696 e.setDN(newMapEntryDN);
2697 if (generateOperationalAttributes)
2698 {
2699 e.setAttribute(new Attribute("entryDN",
2700 DistinguishedNameMatchingRule.getInstance(),
2701 newMapEntryDN.toNormalizedString()));
2702 }
2703 entryMap.put(newMapEntryDN, new ReadOnlyEntry(e));
2704 indexDelete(o);
2705 indexAdd(e);
2706 handleReferentialIntegrityModifyDN(mapEntryDN, newMapEntryDN);
2707 }
2708 }
2709
2710 addChangeLogEntry(request, authzDN);
2711 handleReferentialIntegrityModifyDN(dn, newDN);
2712 return new LDAPMessage(messageID,
2713 new ModifyDNResponseProtocolOp(ResultCode.SUCCESS_INT_VALUE, null,
2714 null, null),
2715 responseControls);
2716 }
2717
2718
2719
2720 /**
2721 * Handles any appropriate referential integrity processing for a modify DN
2722 * operation.
2723 *
2724 * @param oldDN The old DN for the entry.
2725 * @param newDN The new DN for the entry.
2726 */
2727 private void handleReferentialIntegrityModifyDN(final DN oldDN,
2728 final DN newDN)
2729 {
2730 if (referentialIntegrityAttributes.isEmpty())
2731 {
2732 return;
2733 }
2734
2735 final ArrayList<DN> entryDNs = new ArrayList<DN>(entryMap.keySet());
2736 for (final DN mapDN : entryDNs)
2737 {
2738 final ReadOnlyEntry e = entryMap.get(mapDN);
2739
2740 boolean referenceFound = false;
2741 final Schema schema = schemaRef.get();
2742 for (final String attrName : referentialIntegrityAttributes)
2743 {
2744 final Attribute a = e.getAttribute(attrName, schema);
2745 if ((a != null) &&
2746 a.hasValue(oldDN.toNormalizedString(),
2747 DistinguishedNameMatchingRule.getInstance()))
2748 {
2749 referenceFound = true;
2750 break;
2751 }
2752 }
2753
2754 if (referenceFound)
2755 {
2756 final Entry copy = e.duplicate();
2757 for (final String attrName : referentialIntegrityAttributes)
2758 {
2759 if (copy.removeAttributeValue(attrName, oldDN.toNormalizedString(),
2760 DistinguishedNameMatchingRule.getInstance()))
2761 {
2762 copy.addAttribute(attrName, newDN.toString());
2763 }
2764 }
2765 entryMap.put(mapDN, new ReadOnlyEntry(copy));
2766 indexDelete(e);
2767 indexAdd(copy);
2768 }
2769 }
2770 }
2771
2772
2773
2774 /**
2775 * Attempts to process the provided search request. The attempt will fail
2776 * if any of the following conditions is true:
2777 * <UL>
2778 * <LI>There is a problem with any of the request controls.</LI>
2779 * <LI>The modify DN request contains a malformed target DN, new RDN, or
2780 * new superior DN.</LI>
2781 * <LI>The new DN of the entry would conflict with the DN of an existing
2782 * entry.</LI>
2783 * <LI>The new DN of the entry would exist outside the set of defined
2784 * base DNs.</LI>
2785 * <LI>The new DN of the entry is not a defined base DN and does not exist
2786 * immediately below an existing entry.</LI>
2787 * </UL>
2788 *
2789 * @param messageID The message ID of the LDAP message containing the search
2790 * request.
2791 * @param request The search request that was included in the LDAP message
2792 * that was received.
2793 * @param controls The set of controls included in the LDAP message. It
2794 * may be empty if there were no controls, but will not be
2795 * {@code null}.
2796 *
2797 * @return The {@link LDAPMessage} containing the response to send to the
2798 * client. The protocol op in the {@code LDAPMessage} must be an
2799 * {@code SearchResultDoneProtocolOp}.
2800 */
2801 @Override()
2802 public synchronized LDAPMessage processSearchRequest(final int messageID,
2803 final SearchRequestProtocolOp request,
2804 final List<Control> controls)
2805 {
2806 final List<SearchResultEntry> entryList =
2807 new ArrayList<SearchResultEntry>(entryMap.size());
2808 final List<SearchResultReference> referenceList =
2809 new ArrayList<SearchResultReference>(entryMap.size());
2810
2811 final LDAPMessage returnMessage = processSearchRequest(messageID, request,
2812 controls, entryList, referenceList);
2813
2814 for (final SearchResultEntry e : entryList)
2815 {
2816 try
2817 {
2818 connection.sendSearchResultEntry(messageID, e, e.getControls());
2819 }
2820 catch (final LDAPException le)
2821 {
2822 Debug.debugException(le);
2823 return new LDAPMessage(messageID,
2824 new SearchResultDoneProtocolOp(le.getResultCode().intValue(),
2825 le.getMatchedDN(), le.getDiagnosticMessage(),
2826 StaticUtils.toList(le.getReferralURLs())),
2827 le.getResponseControls());
2828 }
2829 }
2830
2831 for (final SearchResultReference r : referenceList)
2832 {
2833 try
2834 {
2835 connection.sendSearchResultReference(messageID,
2836 new SearchResultReferenceProtocolOp(
2837 StaticUtils.toList(r.getReferralURLs())),
2838 r.getControls());
2839 }
2840 catch (final LDAPException le)
2841 {
2842 Debug.debugException(le);
2843 return new LDAPMessage(messageID,
2844 new SearchResultDoneProtocolOp(le.getResultCode().intValue(),
2845 le.getMatchedDN(), le.getDiagnosticMessage(),
2846 StaticUtils.toList(le.getReferralURLs())),
2847 le.getResponseControls());
2848 }
2849 }
2850
2851 return returnMessage;
2852 }
2853
2854
2855
2856 /**
2857 * Attempts to process the provided search request. The attempt will fail
2858 * if any of the following conditions is true:
2859 * <UL>
2860 * <LI>There is a problem with any of the request controls.</LI>
2861 * <LI>The modify DN request contains a malformed target DN, new RDN, or
2862 * new superior DN.</LI>
2863 * <LI>The new DN of the entry would conflict with the DN of an existing
2864 * entry.</LI>
2865 * <LI>The new DN of the entry would exist outside the set of defined
2866 * base DNs.</LI>
2867 * <LI>The new DN of the entry is not a defined base DN and does not exist
2868 * immediately below an existing entry.</LI>
2869 * </UL>
2870 *
2871 * @param messageID The message ID of the LDAP message containing the
2872 * search request.
2873 * @param request The search request that was included in the LDAP
2874 * message that was received.
2875 * @param controls The set of controls included in the LDAP message.
2876 * It may be empty if there were no controls, but will
2877 * not be {@code null}.
2878 * @param entryList A list to which to add search result entries
2879 * intended for return to the client. It must not be
2880 * {@code null}.
2881 * @param referenceList A list to which to add search result references
2882 * intended for return to the client. It must not be
2883 * {@code null}.
2884 *
2885 * @return The {@link LDAPMessage} containing the response to send to the
2886 * client. The protocol op in the {@code LDAPMessage} must be an
2887 * {@code SearchResultDoneProtocolOp}.
2888 */
2889 synchronized LDAPMessage processSearchRequest(final int messageID,
2890 final SearchRequestProtocolOp request,
2891 final List<Control> controls,
2892 final List<SearchResultEntry> entryList,
2893 final List<SearchResultReference> referenceList)
2894 {
2895 // Sleep before processing, if appropriate.
2896 sleepBeforeProcessing();
2897
2898 // Process the provided request controls.
2899 final Map<String,Control> controlMap;
2900 try
2901 {
2902 controlMap = RequestControlPreProcessor.processControls(
2903 LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_REQUEST, controls);
2904 }
2905 catch (final LDAPException le)
2906 {
2907 Debug.debugException(le);
2908 return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(
2909 le.getResultCode().intValue(), null, le.getMessage(), null));
2910 }
2911 final ArrayList<Control> responseControls = new ArrayList<Control>(1);
2912
2913
2914 // If this operation type is not allowed, then reject it.
2915 final boolean isInternalOp =
2916 controlMap.containsKey(OID_INTERNAL_OPERATION_REQUEST_CONTROL);
2917 if ((! isInternalOp) &&
2918 (! config.getAllowedOperationTypes().contains(OperationType.SEARCH)))
2919 {
2920 return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(
2921 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null,
2922 ERR_MEM_HANDLER_SEARCH_NOT_ALLOWED.get(), null));
2923 }
2924
2925
2926 // If this operation type requires authentication, then ensure that the
2927 // client is authenticated.
2928 if ((authenticatedDN.isNullDN() &&
2929 config.getAuthenticationRequiredOperationTypes().contains(
2930 OperationType.SEARCH)))
2931 {
2932 return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(
2933 ResultCode.INSUFFICIENT_ACCESS_RIGHTS_INT_VALUE, null,
2934 ERR_MEM_HANDLER_SEARCH_REQUIRES_AUTH.get(), null));
2935 }
2936
2937
2938 // Get the parsed base DN.
2939 final DN baseDN;
2940 final Schema schema = schemaRef.get();
2941 try
2942 {
2943 baseDN = new DN(request.getBaseDN(), schema);
2944 }
2945 catch (final LDAPException le)
2946 {
2947 Debug.debugException(le);
2948 return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(
2949 ResultCode.INVALID_DN_SYNTAX_INT_VALUE, null,
2950 ERR_MEM_HANDLER_SEARCH_MALFORMED_BASE.get(request.getBaseDN(),
2951 le.getMessage()),
2952 null));
2953 }
2954
2955 // See if the search base or one of its superiors is a smart referral.
2956 final boolean hasManageDsaIT = controlMap.containsKey(
2957 ManageDsaITRequestControl.MANAGE_DSA_IT_REQUEST_OID);
2958 if (! hasManageDsaIT)
2959 {
2960 final Entry referralEntry = findNearestReferral(baseDN);
2961 if (referralEntry != null)
2962 {
2963 return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(
2964 ResultCode.REFERRAL_INT_VALUE, referralEntry.getDN(),
2965 INFO_MEM_HANDLER_REFERRAL_ENCOUNTERED.get(),
2966 getReferralURLs(baseDN, referralEntry)));
2967 }
2968 }
2969
2970 // Make sure that the base entry exists. It may be the root DSE or
2971 // subschema subentry.
2972 final Entry baseEntry;
2973 boolean includeChangeLog = true;
2974 if (baseDN.isNullDN())
2975 {
2976 baseEntry = generateRootDSE();
2977 includeChangeLog = false;
2978 }
2979 else if (baseDN.equals(subschemaSubentryDN))
2980 {
2981 baseEntry = subschemaSubentryRef.get();
2982 }
2983 else
2984 {
2985 baseEntry = entryMap.get(baseDN);
2986 }
2987
2988 if (baseEntry == null)
2989 {
2990 return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(
2991 ResultCode.NO_SUCH_OBJECT_INT_VALUE, getMatchedDNString(baseDN),
2992 ERR_MEM_HANDLER_SEARCH_BASE_DOES_NOT_EXIST.get(request.getBaseDN()),
2993 null));
2994 }
2995
2996 // Perform any necessary processing for the assertion and proxied auth
2997 // controls.
2998 try
2999 {
3000 handleAssertionRequestControl(controlMap, baseEntry);
3001 handleProxiedAuthControl(controlMap);
3002 }
3003 catch (final LDAPException le)
3004 {
3005 Debug.debugException(le);
3006 return new LDAPMessage(messageID, new SearchResultDoneProtocolOp(
3007 le.getResultCode().intValue(), null, le.getMessage(), null));
3008 }
3009
3010 // Create a temporary list to hold all of the entries to be returned. These
3011 // entries will not have been pared down based on the requested attributes.
3012 final List<Entry> fullEntryList = new ArrayList<Entry>(entryMap.size());
3013
3014 findEntriesAndRefs:
3015 {
3016 // Check the scope. If it is a base-level search, then we only need to
3017 // examine the base entry. Otherwise, we'll have to scan the entire entry
3018 // map.
3019 final Filter filter = request.getFilter();
3020 final SearchScope scope = request.getScope();
3021 final boolean includeSubEntries = ((scope == SearchScope.BASE) ||
3022 controlMap.containsKey(
3023 SubentriesRequestControl.SUBENTRIES_REQUEST_OID));
3024 if (scope == SearchScope.BASE)
3025 {
3026 try
3027 {
3028 if (filter.matchesEntry(baseEntry, schema))
3029 {
3030 processSearchEntry(baseEntry, includeSubEntries, includeChangeLog,
3031 hasManageDsaIT, fullEntryList, referenceList);
3032 }
3033 }
3034 catch (final Exception e)
3035 {
3036 Debug.debugException(e);
3037 }
3038
3039 break findEntriesAndRefs;
3040 }
3041
3042 // If the search uses a single-level scope and the base DN is the root
3043 // DSE, then we will only examine the defined base entries for the data
3044 // set.
3045 if ((scope == SearchScope.ONE) && baseDN.isNullDN())
3046 {
3047 for (final DN dn : baseDNs)
3048 {
3049 final Entry e = entryMap.get(dn);
3050 if (e != null)
3051 {
3052 try
3053 {
3054 if (filter.matchesEntry(e, schema))
3055 {
3056 processSearchEntry(e, includeSubEntries, includeChangeLog,
3057 hasManageDsaIT, fullEntryList, referenceList);
3058 }
3059 }
3060 catch (final Exception ex)
3061 {
3062 Debug.debugException(ex);
3063 }
3064 }
3065 }
3066
3067 break findEntriesAndRefs;
3068 }
3069
3070
3071 // Try to use indexes to process the request. If we can't use any
3072 // indexes to get a candidate list, then just iterate over all the
3073 // entries. It's not necessary to consider the root DSE for non-base
3074 // scopes.
3075 final Set<DN> candidateDNs = indexSearch(filter);
3076 if (candidateDNs == null)
3077 {
3078 for (final Map.Entry<DN,ReadOnlyEntry> me : entryMap.entrySet())
3079 {
3080 final DN dn = me.getKey();
3081 final Entry entry = me.getValue();
3082 try
3083 {
3084 if (dn.matchesBaseAndScope(baseDN, scope) &&
3085 filter.matchesEntry(entry, schema))
3086 {
3087 processSearchEntry(entry, includeSubEntries, includeChangeLog,
3088 hasManageDsaIT, fullEntryList, referenceList);
3089 }
3090 }
3091 catch (final Exception e)
3092 {
3093 Debug.debugException(e);
3094 }
3095 }
3096 }
3097 else
3098 {
3099 for (final DN dn : candidateDNs)
3100 {
3101 try
3102 {
3103 if (! dn.matchesBaseAndScope(baseDN, scope))
3104 {
3105 continue;
3106 }
3107
3108 final Entry entry = entryMap.get(dn);
3109 if (filter.matchesEntry(entry, schema))
3110 {
3111 processSearchEntry(entry, includeSubEntries, includeChangeLog,
3112 hasManageDsaIT, fullEntryList, referenceList);
3113 }
3114 }
3115 catch (final Exception e)
3116 {
3117 Debug.debugException(e);
3118 }
3119 }
3120 }
3121 }
3122
3123
3124 // If the request included the server-side sort request control, then sort
3125 // the matching entries appropriately.
3126 final ServerSideSortRequestControl sortRequestControl =
3127 (ServerSideSortRequestControl) controlMap.get(
3128 ServerSideSortRequestControl.SERVER_SIDE_SORT_REQUEST_OID);
3129 if (sortRequestControl != null)
3130 {
3131 final EntrySorter entrySorter = new EntrySorter(false, schema,
3132 sortRequestControl.getSortKeys());
3133 final SortedSet<Entry> sortedEntrySet = entrySorter.sort(fullEntryList);
3134 fullEntryList.clear();
3135 fullEntryList.addAll(sortedEntrySet);
3136
3137 responseControls.add(new ServerSideSortResponseControl(ResultCode.SUCCESS,
3138 null, false));
3139 }
3140
3141
3142 // If the request included the simple paged results control, then handle it.
3143 final SimplePagedResultsControl pagedResultsControl =
3144 (SimplePagedResultsControl)
3145 controlMap.get(SimplePagedResultsControl.PAGED_RESULTS_OID);
3146 if (pagedResultsControl != null)
3147 {
3148 final int totalSize = fullEntryList.size();
3149 final int pageSize = pagedResultsControl.getSize();
3150 final ASN1OctetString cookie = pagedResultsControl.getCookie();
3151
3152 final int offset;
3153 if ((cookie == null) || (cookie.getValueLength() == 0))
3154 {
3155 // This is the first request in the series, so start at the beginning of
3156 // the list.
3157 offset = 0;
3158 }
3159 else
3160 {
3161 // The cookie value will simply be an integer representation of the
3162 // offset within the result list at which to start the next batch.
3163 try
3164 {
3165 final ASN1Integer offsetInteger =
3166 ASN1Integer.decodeAsInteger(cookie.getValue());
3167 offset = offsetInteger.intValue();
3168 }
3169 catch (final Exception e)
3170 {
3171 Debug.debugException(e);
3172 return new LDAPMessage(messageID,
3173 new SearchResultDoneProtocolOp(
3174 ResultCode.PROTOCOL_ERROR_INT_VALUE, null,
3175 ERR_MEM_HANDLER_MALFORMED_PAGED_RESULTS_COOKIE.get(), null),
3176 responseControls);
3177 }
3178 }
3179
3180 // Create an iterator that will be used to remove entries from the result
3181 // set that are outside of the requested page of results.
3182 int pos = 0;
3183 final Iterator<Entry> iterator = fullEntryList.iterator();
3184
3185 // First, remove entries at the beginning of the list until we hit the
3186 // offset.
3187 while (iterator.hasNext() && (pos < offset))
3188 {
3189 iterator.next();
3190 iterator.remove();
3191 pos++;
3192 }
3193
3194 // Next, skip over the entries that should be returned.
3195 int keptEntries = 0;
3196 while (iterator.hasNext() && (keptEntries < pageSize))
3197 {
3198 iterator.next();
3199 pos++;
3200 keptEntries++;
3201 }
3202
3203 // If there are still entries left, then remove them and create a cookie
3204 // to include in the response. Otherwise, use an empty cookie.
3205 if (iterator.hasNext())
3206 {
3207 responseControls.add(new SimplePagedResultsControl(totalSize,
3208 new ASN1OctetString(new ASN1Integer(pos).encode()), false));
3209 while (iterator.hasNext())
3210 {
3211 iterator.next();
3212 iterator.remove();
3213 }
3214 }
3215 else
3216 {
3217 responseControls.add(new SimplePagedResultsControl(totalSize,
3218 new ASN1OctetString(), false));
3219 }
3220 }
3221
3222
3223 // If the request includes the virtual list view request control, then
3224 // handle it.
3225 final VirtualListViewRequestControl vlvRequest =
3226 (VirtualListViewRequestControl) controlMap.get(
3227 VirtualListViewRequestControl.VIRTUAL_LIST_VIEW_REQUEST_OID);
3228 if (vlvRequest != null)
3229 {
3230 final int totalEntries = fullEntryList.size();
3231 final ASN1OctetString assertionValue = vlvRequest.getAssertionValue();
3232
3233 // Figure out the position of the target entry in the list.
3234 int offset = vlvRequest.getTargetOffset();
3235 if (assertionValue == null)
3236 {
3237 // The offset is one-based, so we need to adjust it for the list's
3238 // zero-based offset. Also, make sure to put it within the bounds of
3239 // the list.
3240 offset--;
3241 offset = Math.max(0, offset);
3242 offset = Math.min(fullEntryList.size(), offset);
3243 }
3244 else
3245 {
3246 final SortKey primarySortKey = sortRequestControl.getSortKeys()[0];
3247
3248 final Entry testEntry = new Entry("cn=test", schema,
3249 new Attribute(primarySortKey.getAttributeName(), assertionValue));
3250
3251 final EntrySorter entrySorter =
3252 new EntrySorter(false, schema, primarySortKey);
3253
3254 offset = fullEntryList.size();
3255 for (int i=0; i < fullEntryList.size(); i++)
3256 {
3257 if (entrySorter.compare(fullEntryList.get(i), testEntry) >= 0)
3258 {
3259 offset = i;
3260 break;
3261 }
3262 }
3263 }
3264
3265 // Get the start and end positions based on the before and after counts.
3266 final int beforeCount = Math.max(0, vlvRequest.getBeforeCount());
3267 final int afterCount = Math.max(0, vlvRequest.getAfterCount());
3268
3269 final int start = Math.max(0, (offset - beforeCount));
3270 final int end = Math.min(fullEntryList.size(), (offset + afterCount + 1));
3271
3272 // Create an iterator to use to alter the list so that it only contains
3273 // the appropriate set of entries.
3274 int pos = 0;
3275 final Iterator<Entry> iterator = fullEntryList.iterator();
3276 while (iterator.hasNext())
3277 {
3278 iterator.next();
3279 if ((pos < start) || (pos >= end))
3280 {
3281 iterator.remove();
3282 }
3283 pos++;
3284 }
3285
3286 // Create the appropriate response control.
3287 responseControls.add(new VirtualListViewResponseControl((offset+1),
3288 totalEntries, ResultCode.SUCCESS, null));
3289 }
3290
3291
3292 // Process the set of requested attributes so that we can pare down the
3293 // entries.
3294 final AtomicBoolean allUserAttrs = new AtomicBoolean(false);
3295 final AtomicBoolean allOpAttrs = new AtomicBoolean(false);
3296 final Map<String,List<List<String>>> returnAttrs =
3297 processRequestedAttributes(request.getAttributes(), allUserAttrs,
3298 allOpAttrs);
3299
3300 final int sizeLimit;
3301 if (request.getSizeLimit() > 0)
3302 {
3303 sizeLimit = request.getSizeLimit();
3304 }
3305 else
3306 {
3307 sizeLimit = Integer.MAX_VALUE;
3308 }
3309
3310 int entryCount = 0;
3311 for (final Entry e : fullEntryList)
3312 {
3313 entryCount++;
3314 if (entryCount > sizeLimit)
3315 {
3316 return new LDAPMessage(messageID,
3317 new SearchResultDoneProtocolOp(
3318 ResultCode.SIZE_LIMIT_EXCEEDED_INT_VALUE, null,
3319 ERR_MEM_HANDLER_SEARCH_SIZE_LIMIT_EXCEEDED.get(), null),
3320 responseControls);
3321 }
3322
3323 final Entry trimmedEntry = trimForRequestedAttributes(e,
3324 allUserAttrs.get(), allOpAttrs.get(), returnAttrs);
3325 if (request.typesOnly())
3326 {
3327 final Entry typesOnlyEntry = new Entry(trimmedEntry.getDN(), schema);
3328 for (final Attribute a : trimmedEntry.getAttributes())
3329 {
3330 typesOnlyEntry.addAttribute(new Attribute(a.getName()));
3331 }
3332 entryList.add(new SearchResultEntry(typesOnlyEntry));
3333 }
3334 else
3335 {
3336 entryList.add(new SearchResultEntry(trimmedEntry));
3337 }
3338 }
3339
3340 return new LDAPMessage(messageID,
3341 new SearchResultDoneProtocolOp(ResultCode.SUCCESS_INT_VALUE, null,
3342 null, null),
3343 responseControls);
3344 }
3345
3346
3347
3348 /**
3349 * Performs any necessary index processing to add the provided entry.
3350 *
3351 * @param entry The entry that has been added.
3352 */
3353 private void indexAdd(final Entry entry)
3354 {
3355 for (final InMemoryDirectoryServerEqualityAttributeIndex i :
3356 equalityIndexes.values())
3357 {
3358 try
3359 {
3360 i.processAdd(entry);
3361 }
3362 catch (final LDAPException le)
3363 {
3364 Debug.debugException(le);
3365 }
3366 }
3367 }
3368
3369
3370
3371 /**
3372 * Performs any necessary index processing to delete the provided entry.
3373 *
3374 * @param entry The entry that has been deleted.
3375 */
3376 private void indexDelete(final Entry entry)
3377 {
3378 for (final InMemoryDirectoryServerEqualityAttributeIndex i :
3379 equalityIndexes.values())
3380 {
3381 try
3382 {
3383 i.processDelete(entry);
3384 }
3385 catch (final LDAPException le)
3386 {
3387 Debug.debugException(le);
3388 }
3389 }
3390 }
3391
3392
3393
3394 /**
3395 * Attempts to use indexes to obtain a candidate list for the provided filter.
3396 *
3397 * @param filter The filter to be processed.
3398 *
3399 * @return The DNs of entries which may match the given filter, or
3400 * {@code null} if the filter is not indexed.
3401 */
3402 private Set<DN> indexSearch(final Filter filter)
3403 {
3404 switch (filter.getFilterType())
3405 {
3406 case Filter.FILTER_TYPE_AND:
3407 Filter[] comps = filter.getComponents();
3408 if (comps.length == 0)
3409 {
3410 return null;
3411 }
3412 else if (comps.length == 1)
3413 {
3414 return indexSearch(comps[0]);
3415 }
3416 else
3417 {
3418 Set<DN> candidateSet = null;
3419 for (final Filter f : comps)
3420 {
3421 final Set<DN> dnSet = indexSearch(f);
3422 if (dnSet != null)
3423 {
3424 if (candidateSet == null)
3425 {
3426 candidateSet = new TreeSet<DN>(dnSet);
3427 }
3428 else
3429 {
3430 candidateSet.retainAll(dnSet);
3431 }
3432 }
3433 }
3434 return candidateSet;
3435 }
3436
3437 case Filter.FILTER_TYPE_OR:
3438 comps = filter.getComponents();
3439 if (comps.length == 0)
3440 {
3441 return Collections.emptySet();
3442 }
3443 else if (comps.length == 1)
3444 {
3445 return indexSearch(comps[0]);
3446 }
3447 else
3448 {
3449 Set<DN> candidateSet = null;
3450 for (final Filter f : comps)
3451 {
3452 final Set<DN> dnSet = indexSearch(f);
3453 if (dnSet == null)
3454 {
3455 return null;
3456 }
3457
3458 if (candidateSet == null)
3459 {
3460 candidateSet = new TreeSet<DN>(dnSet);
3461 }
3462 else
3463 {
3464 candidateSet.addAll(dnSet);
3465 }
3466 }
3467 return candidateSet;
3468 }
3469
3470 case Filter.FILTER_TYPE_EQUALITY:
3471 final Schema schema = schemaRef.get();
3472 if (schema == null)
3473 {
3474 return null;
3475 }
3476 final AttributeTypeDefinition at =
3477 schema.getAttributeType(filter.getAttributeName());
3478 if (at == null)
3479 {
3480 return null;
3481 }
3482 final InMemoryDirectoryServerEqualityAttributeIndex i =
3483 equalityIndexes.get(at);
3484 if (i == null)
3485 {
3486 return null;
3487 }
3488 try
3489 {
3490 return i.getMatchingEntries(filter.getRawAssertionValue());
3491 }
3492 catch (final Exception e)
3493 {
3494 Debug.debugException(e);
3495 return null;
3496 }
3497
3498 default:
3499 return null;
3500 }
3501 }
3502
3503
3504
3505 /**
3506 * Determines whether the provided set of controls includes a transaction
3507 * specification request control. If so, then it will verify that it
3508 * references a valid transaction for the client. If the request is part of a
3509 * valid transaction, then the transaction specification request control will
3510 * be removed and the request will be stashed in the client connection state
3511 * so that it can be retrieved and processed when the transaction is
3512 * committed.
3513 *
3514 * @param messageID The message ID for the request to be processed.
3515 * @param request The protocol op for the request to be processed.
3516 * @param controls The set of controls for the request to be processed.
3517 *
3518 * @return The transaction ID for the associated transaction, or {@code null}
3519 * if the request is not part of any transaction.
3520 *
3521 * @throws LDAPException If the transaction specification request control is
3522 * present but does not refer to a valid transaction
3523 * for the associated client connection.
3524 */
3525 @SuppressWarnings("unchecked")
3526 private ASN1OctetString processTransactionRequest(final int messageID,
3527 final ProtocolOp request,
3528 final Map<String,Control> controls)
3529 throws LDAPException
3530 {
3531 final TransactionSpecificationRequestControl txnControl =
3532 (TransactionSpecificationRequestControl)
3533 controls.remove(TransactionSpecificationRequestControl.
3534 TRANSACTION_SPECIFICATION_REQUEST_OID);
3535 if (txnControl == null)
3536 {
3537 return null;
3538 }
3539
3540 // See if the client has an active transaction. If not, then fail.
3541 final ASN1OctetString txnID = txnControl.getTransactionID();
3542 final ObjectPair<ASN1OctetString,List<LDAPMessage>> txnInfo =
3543 (ObjectPair<ASN1OctetString,List<LDAPMessage>>) connectionState.get(
3544 TransactionExtendedOperationHandler.STATE_VARIABLE_TXN_INFO);
3545 if (txnInfo == null)
3546 {
3547 throw new LDAPException(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
3548 ERR_MEM_HANDLER_TXN_CONTROL_WITHOUT_TXN.get(txnID.stringValue()));
3549 }
3550
3551
3552 // Make sure that the active transaction has a transaction ID that matches
3553 // the transaction ID from the control. If not, then abort the existing
3554 // transaction and fail.
3555 final ASN1OctetString existingTxnID = txnInfo.getFirst();
3556 if (! txnID.stringValue().equals(existingTxnID.stringValue()))
3557 {
3558 connectionState.remove(
3559 TransactionExtendedOperationHandler.STATE_VARIABLE_TXN_INFO);
3560 connection.sendUnsolicitedNotification(
3561 new AbortedTransactionExtendedResult(existingTxnID,
3562 ResultCode.CONSTRAINT_VIOLATION,
3563 ERR_MEM_HANDLER_TXN_ABORTED_BY_CONTROL_TXN_ID_MISMATCH.get(
3564 existingTxnID.stringValue(), txnID.stringValue()),
3565 null, null, null));
3566 throw new LDAPException(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
3567 ERR_MEM_HANDLER_TXN_CONTROL_ID_MISMATCH.get(txnID.stringValue(),
3568 existingTxnID.stringValue()));
3569 }
3570
3571
3572 // Stash the request in the transaction state information so that it will
3573 // be processed when the transaction is committed.
3574 txnInfo.getSecond().add(new LDAPMessage(messageID, request,
3575 new ArrayList<Control>(controls.values())));
3576
3577 return txnID;
3578 }
3579
3580
3581
3582 /**
3583 * Sleeps for a period of time (if appropriate) before beginning processing
3584 * for an operation.
3585 */
3586 private void sleepBeforeProcessing()
3587 {
3588 final long delay = processingDelayMillis.get();
3589 if (delay > 0)
3590 {
3591 try
3592 {
3593 Thread.sleep(delay);
3594 }
3595 catch (final Exception e)
3596 {
3597 Debug.debugException(e);
3598 }
3599 }
3600 }
3601
3602
3603
3604 /**
3605 * Retrieves the number of entries currently held in the server.
3606 *
3607 * @param includeChangeLog Indicates whether to include entries that are
3608 * part of the changelog in the count.
3609 *
3610 * @return The number of entries currently held in the server.
3611 */
3612 public synchronized int countEntries(final boolean includeChangeLog)
3613 {
3614 if (includeChangeLog || (maxChangelogEntries == 0))
3615 {
3616 return entryMap.size();
3617 }
3618 else
3619 {
3620 int count = 0;
3621
3622 for (final DN dn : entryMap.keySet())
3623 {
3624 if (! dn.isDescendantOf(changeLogBaseDN, true))
3625 {
3626 count++;
3627 }
3628 }
3629
3630 return count;
3631 }
3632 }
3633
3634
3635
3636 /**
3637 * Retrieves the number of entries currently held in the server whose DN
3638 * matches or is subordinate to the provided base DN.
3639 *
3640 * @param baseDN The base DN to use for the determination.
3641 *
3642 * @return The number of entries currently held in the server whose DN
3643 * matches or is subordinate to the provided base DN.
3644 *
3645 * @throws LDAPException If the provided string cannot be parsed as a valid
3646 * DN.
3647 */
3648 public synchronized int countEntriesBelow(final String baseDN)
3649 throws LDAPException
3650 {
3651 final DN parsedBaseDN = new DN(baseDN, schemaRef.get());
3652
3653 int count = 0;
3654 for (final DN dn : entryMap.keySet())
3655 {
3656 if (dn.isDescendantOf(parsedBaseDN, true))
3657 {
3658 count++;
3659 }
3660 }
3661
3662 return count;
3663 }
3664
3665
3666
3667 /**
3668 * Removes all entries currently held in the server. If a changelog is
3669 * enabled, then all changelog entries will also be cleared but the base
3670 * "cn=changelog" entry will be retained.
3671 */
3672 public synchronized void clear()
3673 {
3674 restoreSnapshot(initialSnapshot);
3675 }
3676
3677
3678
3679 /**
3680 * Reads entries from the provided LDIF reader and adds them to the server,
3681 * optionally clearing any existing entries before beginning to add the new
3682 * entries. If an error is encountered while adding entries from LDIF then
3683 * the server will remain populated with the data it held before the import
3684 * attempt (even if the {@code clear} is given with a value of {@code true}).
3685 *
3686 * @param clear Indicates whether to remove all existing entries prior
3687 * to adding entries read from LDIF.
3688 * @param ldifReader The LDIF reader to use to obtain the entries to be
3689 * imported.
3690 *
3691 * @return The number of entries read from LDIF and added to the server.
3692 *
3693 * @throws LDAPException If a problem occurs while reading entries or adding
3694 * them to the server.
3695 */
3696 public synchronized int importFromLDIF(final boolean clear,
3697 final LDIFReader ldifReader)
3698 throws LDAPException
3699 {
3700 final InMemoryDirectoryServerSnapshot snapshot = createSnapshot();
3701 boolean restoreSnapshot = true;
3702
3703 try
3704 {
3705 if (clear)
3706 {
3707 restoreSnapshot(initialSnapshot);
3708 }
3709
3710 int entriesAdded = 0;
3711 while (true)
3712 {
3713 final Entry entry;
3714 try
3715 {
3716 entry = ldifReader.readEntry();
3717 if (entry == null)
3718 {
3719 restoreSnapshot = false;
3720 return entriesAdded;
3721 }
3722 }
3723 catch (final LDIFException le)
3724 {
3725 Debug.debugException(le);
3726 throw new LDAPException(ResultCode.LOCAL_ERROR,
3727 ERR_MEM_HANDLER_INIT_FROM_LDIF_READ_ERROR.get(le.getMessage()),
3728 le);
3729 }
3730 catch (final Exception e)
3731 {
3732 Debug.debugException(e);
3733 throw new LDAPException(ResultCode.LOCAL_ERROR,
3734 ERR_MEM_HANDLER_INIT_FROM_LDIF_READ_ERROR.get(
3735 StaticUtils.getExceptionMessage(e)),
3736 e);
3737 }
3738
3739 addEntry(entry, true);
3740 entriesAdded++;
3741 }
3742 }
3743 finally
3744 {
3745 try
3746 {
3747 ldifReader.close();
3748 }
3749 catch (final Exception e)
3750 {
3751 Debug.debugException(e);
3752 }
3753
3754 if (restoreSnapshot)
3755 {
3756 restoreSnapshot(snapshot);
3757 }
3758 }
3759 }
3760
3761
3762
3763 /**
3764 * Writes all entries contained in the server to LDIF using the provided
3765 * writer.
3766 *
3767 * @param ldifWriter The LDIF writer to use when writing the
3768 * entries. It must not be {@code null}.
3769 * @param excludeGeneratedAttrs Indicates whether to exclude automatically
3770 * generated operational attributes like
3771 * entryUUID, entryDN, creatorsName, etc.
3772 * @param excludeChangeLog Indicates whether to exclude entries
3773 * contained in the changelog.
3774 * @param closeWriter Indicates whether the LDIF writer should be
3775 * closed after all entries have been written.
3776 *
3777 * @return The number of entries written to LDIF.
3778 *
3779 * @throws LDAPException If a problem is encountered while attempting to
3780 * write an entry to LDIF.
3781 */
3782 public synchronized int exportToLDIF(final LDIFWriter ldifWriter,
3783 final boolean excludeGeneratedAttrs,
3784 final boolean excludeChangeLog,
3785 final boolean closeWriter)
3786 throws LDAPException
3787 {
3788 boolean exceptionThrown = false;
3789
3790 try
3791 {
3792 int entriesWritten = 0;
3793
3794 for (final Map.Entry<DN,ReadOnlyEntry> me : entryMap.entrySet())
3795 {
3796 final DN dn = me.getKey();
3797 if (excludeChangeLog && dn.isDescendantOf(changeLogBaseDN, true))
3798 {
3799 continue;
3800 }
3801
3802 final Entry entry;
3803 if (excludeGeneratedAttrs)
3804 {
3805 entry = me.getValue().duplicate();
3806 entry.removeAttribute("entryDN");
3807 entry.removeAttribute("entryUUID");
3808 entry.removeAttribute("subschemaSubentry");
3809 entry.removeAttribute("creatorsName");
3810 entry.removeAttribute("createTimestamp");
3811 entry.removeAttribute("modifiersName");
3812 entry.removeAttribute("modifyTimestamp");
3813 }
3814 else
3815 {
3816 entry = me.getValue();
3817 }
3818
3819 try
3820 {
3821 ldifWriter.writeEntry(entry);
3822 entriesWritten++;
3823 }
3824 catch (final Exception e)
3825 {
3826 Debug.debugException(e);
3827 exceptionThrown = true;
3828 throw new LDAPException(ResultCode.LOCAL_ERROR,
3829 ERR_MEM_HANDLER_LDIF_WRITE_ERROR.get(entry.getDN(),
3830 StaticUtils.getExceptionMessage(e)),
3831 e);
3832 }
3833 }
3834
3835 return entriesWritten;
3836 }
3837 finally
3838 {
3839 if (closeWriter)
3840 {
3841 try
3842 {
3843 ldifWriter.close();
3844 }
3845 catch (final Exception e)
3846 {
3847 Debug.debugException(e);
3848 if (! exceptionThrown)
3849 {
3850 throw new LDAPException(ResultCode.LOCAL_ERROR,
3851 ERR_MEM_HANDLER_LDIF_WRITE_CLOSE_ERROR.get(
3852 StaticUtils.getExceptionMessage(e)),
3853 e);
3854 }
3855 }
3856 }
3857 }
3858 }
3859
3860
3861
3862 /**
3863 * Attempts to add the provided entry to the in-memory data set. The attempt
3864 * will fail if any of the following conditions is true:
3865 * <UL>
3866 * <LI>The provided entry has a malformed DN.</LI>
3867 * <LI>The provided entry has the null DN.</LI>
3868 * <LI>The provided entry has a DN that is the same as or subordinate to the
3869 * subschema subentry.</LI>
3870 * <LI>An entry already exists with the same DN as the entry in the provided
3871 * request.</LI>
3872 * <LI>The entry is outside the set of base DNs for the server.</LI>
3873 * <LI>The entry is below one of the defined base DNs but the immediate
3874 * parent entry does not exist.</LI>
3875 * <LI>If a schema was provided, and the entry is not valid according to the
3876 * constraints of that schema.</LI>
3877 * </UL>
3878 *
3879 * @param entry The entry to be added. It must not be
3880 * {@code null}.
3881 * @param ignoreNoUserModification Indicates whether to ignore constraints
3882 * normally imposed by the
3883 * NO-USER-MODIFICATION element in attribute
3884 * type definitions.
3885 *
3886 * @throws LDAPException If a problem occurs while attempting to add the
3887 * provided entry.
3888 */
3889 public void addEntry(final Entry entry,
3890 final boolean ignoreNoUserModification)
3891 throws LDAPException
3892 {
3893 final List<Control> controls;
3894 if (ignoreNoUserModification)
3895 {
3896 controls = new ArrayList<Control>(1);
3897 controls.add(new Control(OID_INTERNAL_OPERATION_REQUEST_CONTROL, false));
3898 }
3899 else
3900 {
3901 controls = Collections.emptyList();
3902 }
3903
3904 final AddRequestProtocolOp addRequest = new AddRequestProtocolOp(
3905 entry.getDN(), new ArrayList<Attribute>(entry.getAttributes()));
3906
3907 final LDAPMessage resultMessage =
3908 processAddRequest(-1, addRequest, controls);
3909
3910 final AddResponseProtocolOp addResponse =
3911 resultMessage.getAddResponseProtocolOp();
3912 if (addResponse.getResultCode() != ResultCode.SUCCESS_INT_VALUE)
3913 {
3914 throw new LDAPException(ResultCode.valueOf(addResponse.getResultCode()),
3915 addResponse.getDiagnosticMessage(), addResponse.getMatchedDN(),
3916 stringListToArray(addResponse.getReferralURLs()));
3917 }
3918 }
3919
3920
3921
3922 /**
3923 * Attempts to add all of the provided entries to the server. If an error is
3924 * encountered during processing, then the contents of the server will be the
3925 * same as they were before this method was called.
3926 *
3927 * @param entries The collection of entries to be added.
3928 *
3929 * @throws LDAPException If a problem was encountered while attempting to
3930 * add any of the entries to the server.
3931 */
3932 public synchronized void addEntries(final List<? extends Entry> entries)
3933 throws LDAPException
3934 {
3935 final InMemoryDirectoryServerSnapshot snapshot = createSnapshot();
3936 boolean restoreSnapshot = true;
3937
3938 try
3939 {
3940 for (final Entry e : entries)
3941 {
3942 addEntry(e, false);
3943 }
3944 restoreSnapshot = false;
3945 }
3946 finally
3947 {
3948 if (restoreSnapshot)
3949 {
3950 restoreSnapshot(snapshot);
3951 }
3952 }
3953 }
3954
3955
3956
3957 /**
3958 * Removes the entry with the specified DN and any subordinate entries it may
3959 * have.
3960 *
3961 * @param baseDN The DN of the entry to be deleted. It must not be
3962 * {@code null} or represent the null DN.
3963 *
3964 * @return The number of entries actually removed, or zero if the specified
3965 * base DN does not represent an entry in the server.
3966 *
3967 * @throws LDAPException If the provided base DN is not a valid DN, or is
3968 * the DN of an entry that cannot be deleted (e.g.,
3969 * the null DN).
3970 */
3971 public synchronized int deleteSubtree(final String baseDN)
3972 throws LDAPException
3973 {
3974 final DN dn = new DN(baseDN, schemaRef.get());
3975 if (dn.isNullDN())
3976 {
3977 throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM,
3978 ERR_MEM_HANDLER_DELETE_ROOT_DSE.get());
3979 }
3980
3981 int numDeleted = 0;
3982
3983 final Iterator<Map.Entry<DN,ReadOnlyEntry>> iterator =
3984 entryMap.entrySet().iterator();
3985 while (iterator.hasNext())
3986 {
3987 final Map.Entry<DN,ReadOnlyEntry> e = iterator.next();
3988 if (e.getKey().isDescendantOf(dn, true))
3989 {
3990 iterator.remove();
3991 numDeleted++;
3992 }
3993 }
3994
3995 return numDeleted;
3996 }
3997
3998
3999
4000 /**
4001 * Attempts to apply the provided set of modifications to the specified entry.
4002 * The attempt will fail if any of the following conditions is true:
4003 * <UL>
4004 * <LI>The target DN is malformed.</LI>
4005 * <LI>The target entry is the root DSE.</LI>
4006 * <LI>The target entry is the subschema subentry.</LI>
4007 * <LI>The target entry does not exist.</LI>
4008 * <LI>Any of the modifications cannot be applied to the entry.</LI>
4009 * <LI>If a schema was provided, and the entry violates any of the
4010 * constraints of that schema.</LI>
4011 * </UL>
4012 *
4013 * @param dn The DN of the entry to be modified.
4014 * @param mods The set of modifications to be applied to the entry.
4015 *
4016 * @throws LDAPException If a problem is encountered while attempting to
4017 * update the specified entry.
4018 */
4019 public void modifyEntry(final String dn, final List<Modification> mods)
4020 throws LDAPException
4021 {
4022 final ModifyRequestProtocolOp modifyRequest =
4023 new ModifyRequestProtocolOp(dn, mods);
4024
4025 final LDAPMessage resultMessage = processModifyRequest(-1, modifyRequest,
4026 Collections.<Control>emptyList());
4027
4028 final ModifyResponseProtocolOp modifyResponse =
4029 resultMessage.getModifyResponseProtocolOp();
4030 if (modifyResponse.getResultCode() != ResultCode.SUCCESS_INT_VALUE)
4031 {
4032 throw new LDAPException(
4033 ResultCode.valueOf(modifyResponse.getResultCode()),
4034 modifyResponse.getDiagnosticMessage(), modifyResponse.getMatchedDN(),
4035 stringListToArray(modifyResponse.getReferralURLs()));
4036 }
4037 }
4038
4039
4040
4041 /**
4042 * Retrieves a read-only representation the entry with the specified DN, if
4043 * it exists.
4044 *
4045 * @param dn The DN of the entry to retrieve.
4046 *
4047 * @return The requested entry, or {@code null} if no entry exists with the
4048 * given DN.
4049 *
4050 * @throws LDAPException If the provided DN is malformed.
4051 */
4052 public synchronized ReadOnlyEntry getEntry(final String dn)
4053 throws LDAPException
4054 {
4055 return getEntry(new DN(dn, schemaRef.get()));
4056 }
4057
4058
4059
4060 /**
4061 * Retrieves a read-only representation the entry with the specified DN, if
4062 * it exists.
4063 *
4064 * @param dn The DN of the entry to retrieve.
4065 *
4066 * @return The requested entry, or {@code null} if no entry exists with the
4067 * given DN.
4068 */
4069 public synchronized ReadOnlyEntry getEntry(final DN dn)
4070 {
4071 if (dn.isNullDN())
4072 {
4073 return generateRootDSE();
4074 }
4075 else if (dn.equals(subschemaSubentryDN))
4076 {
4077 return subschemaSubentryRef.get();
4078 }
4079 else
4080 {
4081 final Entry e = entryMap.get(dn);
4082 if (e == null)
4083 {
4084 return null;
4085 }
4086 else
4087 {
4088 return new ReadOnlyEntry(e);
4089 }
4090 }
4091 }
4092
4093
4094
4095 /**
4096 * Retrieves a list of all entries in the server which match the given
4097 * search criteria.
4098 *
4099 * @param baseDN The base DN to use for the search. It must not be
4100 * {@code null}.
4101 * @param scope The scope to use for the search. It must not be
4102 * {@code null}.
4103 * @param filter The filter to use for the search. It must not be
4104 * {@code null}.
4105 *
4106 * @return A list of the entries that matched the provided search criteria.
4107 *
4108 * @throws LDAPException If a problem is encountered while performing the
4109 * search.
4110 */
4111 public synchronized List<ReadOnlyEntry> search(final String baseDN,
4112 final SearchScope scope,
4113 final Filter filter)
4114 throws LDAPException
4115 {
4116 final DN parsedDN;
4117 final Schema schema = schemaRef.get();
4118 try
4119 {
4120 parsedDN = new DN(baseDN, schema);
4121 }
4122 catch (final LDAPException le)
4123 {
4124 Debug.debugException(le);
4125 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
4126 ERR_MEM_HANDLER_SEARCH_MALFORMED_BASE.get(baseDN, le.getMessage()),
4127 le);
4128 }
4129
4130 final ReadOnlyEntry baseEntry;
4131 if (parsedDN.isNullDN())
4132 {
4133 baseEntry = generateRootDSE();
4134 }
4135 else if (parsedDN.equals(subschemaSubentryDN))
4136 {
4137 baseEntry = subschemaSubentryRef.get();
4138 }
4139 else
4140 {
4141 final Entry e = entryMap.get(parsedDN);
4142 if (e == null)
4143 {
4144 throw new LDAPException(ResultCode.NO_SUCH_OBJECT,
4145 ERR_MEM_HANDLER_SEARCH_BASE_DOES_NOT_EXIST.get(baseDN),
4146 getMatchedDNString(parsedDN), null);
4147 }
4148
4149 baseEntry = new ReadOnlyEntry(e);
4150 }
4151
4152 if (scope == SearchScope.BASE)
4153 {
4154 final List<ReadOnlyEntry> entryList = new ArrayList<ReadOnlyEntry>(1);
4155
4156 try
4157 {
4158 if (filter.matchesEntry(baseEntry, schema))
4159 {
4160 entryList.add(baseEntry);
4161 }
4162 }
4163 catch (final LDAPException le)
4164 {
4165 Debug.debugException(le);
4166 }
4167
4168 return Collections.unmodifiableList(entryList);
4169 }
4170
4171 if ((scope == SearchScope.ONE) && parsedDN.isNullDN())
4172 {
4173 final List<ReadOnlyEntry> entryList =
4174 new ArrayList<ReadOnlyEntry>(baseDNs.size());
4175
4176 try
4177 {
4178 for (final DN dn : baseDNs)
4179 {
4180 final Entry e = entryMap.get(dn);
4181 if ((e != null) && filter.matchesEntry(e, schema))
4182 {
4183 entryList.add(new ReadOnlyEntry(e));
4184 }
4185 }
4186 }
4187 catch (final LDAPException le)
4188 {
4189 Debug.debugException(le);
4190 }
4191
4192 return Collections.unmodifiableList(entryList);
4193 }
4194
4195 final List<ReadOnlyEntry> entryList = new ArrayList<ReadOnlyEntry>(10);
4196 for (final Map.Entry<DN,ReadOnlyEntry> me : entryMap.entrySet())
4197 {
4198 final DN dn = me.getKey();
4199 if (dn.matchesBaseAndScope(parsedDN, scope))
4200 {
4201 // We don't want to return changelog entries searches based at the
4202 // root DSE.
4203 if (parsedDN.isNullDN() && dn.isDescendantOf(changeLogBaseDN, true))
4204 {
4205 continue;
4206 }
4207
4208 try
4209 {
4210 final Entry entry = me.getValue();
4211 if (filter.matchesEntry(entry, schema))
4212 {
4213 entryList.add(new ReadOnlyEntry(entry));
4214 }
4215 }
4216 catch (final LDAPException le)
4217 {
4218 Debug.debugException(le);
4219 }
4220 }
4221 }
4222
4223 return Collections.unmodifiableList(entryList);
4224 }
4225
4226
4227
4228 /**
4229 * Generates an entry to use as the server root DSE.
4230 *
4231 * @return The generated root DSE entry.
4232 */
4233 private ReadOnlyEntry generateRootDSE()
4234 {
4235 final Entry rootDSEEntry = new Entry(DN.NULL_DN, schemaRef.get());
4236 rootDSEEntry.addAttribute("objectClass", "top", "ds-root-dse");
4237 rootDSEEntry.addAttribute(new Attribute("supportedLDAPVersion",
4238 IntegerMatchingRule.getInstance(), "3"));
4239
4240 final String vendorName = config.getVendorName();
4241 if (vendorName != null)
4242 {
4243 rootDSEEntry.addAttribute("vendorName", vendorName);
4244 }
4245
4246 final String vendorVersion = config.getVendorVersion();
4247 if (vendorVersion != null)
4248 {
4249 rootDSEEntry.addAttribute("vendorVersion", vendorVersion);
4250 }
4251
4252 rootDSEEntry.addAttribute(new Attribute("subschemaSubentry",
4253 DistinguishedNameMatchingRule.getInstance(),
4254 subschemaSubentryDN.toString()));
4255 rootDSEEntry.addAttribute(new Attribute("entryDN",
4256 DistinguishedNameMatchingRule.getInstance(), ""));
4257 rootDSEEntry.addAttribute("entryUUID", UUID.randomUUID().toString());
4258
4259 rootDSEEntry.addAttribute("supportedFeatures",
4260 "1.3.6.1.4.1.4203.1.5.1", // All operational attributes
4261 "1.3.6.1.4.1.4203.1.5.2", // Request attributes by object class
4262 "1.3.6.1.4.1.4203.1.5.3", // LDAP absolute true and false filters
4263 "1.3.6.1.1.14"); // Increment modification type
4264
4265 final TreeSet<String> ctlSet = new TreeSet<String>();
4266
4267 ctlSet.add(AssertionRequestControl.ASSERTION_REQUEST_OID);
4268 ctlSet.add(AuthorizationIdentityRequestControl.
4269 AUTHORIZATION_IDENTITY_REQUEST_OID);
4270 ctlSet.add(DontUseCopyRequestControl.DONT_USE_COPY_REQUEST_OID);
4271 ctlSet.add(ManageDsaITRequestControl.MANAGE_DSA_IT_REQUEST_OID);
4272 ctlSet.add(PermissiveModifyRequestControl.PERMISSIVE_MODIFY_REQUEST_OID);
4273 ctlSet.add(PostReadRequestControl.POST_READ_REQUEST_OID);
4274 ctlSet.add(PreReadRequestControl.PRE_READ_REQUEST_OID);
4275 ctlSet.add(ProxiedAuthorizationV1RequestControl.
4276 PROXIED_AUTHORIZATION_V1_REQUEST_OID);
4277 ctlSet.add(ProxiedAuthorizationV2RequestControl.
4278 PROXIED_AUTHORIZATION_V2_REQUEST_OID);
4279 ctlSet.add(ServerSideSortRequestControl.SERVER_SIDE_SORT_REQUEST_OID);
4280 ctlSet.add(SimplePagedResultsControl.PAGED_RESULTS_OID);
4281 ctlSet.add(SubentriesRequestControl.SUBENTRIES_REQUEST_OID);
4282 ctlSet.add(SubtreeDeleteRequestControl.SUBTREE_DELETE_REQUEST_OID);
4283 ctlSet.add(TransactionSpecificationRequestControl.
4284 TRANSACTION_SPECIFICATION_REQUEST_OID);
4285 ctlSet.add(VirtualListViewRequestControl.VIRTUAL_LIST_VIEW_REQUEST_OID);
4286
4287 final String[] controlOIDs = new String[ctlSet.size()];
4288 rootDSEEntry.addAttribute("supportedControl", ctlSet.toArray(controlOIDs));
4289
4290
4291 if (! extendedRequestHandlers.isEmpty())
4292 {
4293 final String[] oidArray = new String[extendedRequestHandlers.size()];
4294 rootDSEEntry.addAttribute("supportedExtension",
4295 extendedRequestHandlers.keySet().toArray(oidArray));
4296
4297 for (final InMemoryListenerConfig c : config.getListenerConfigs())
4298 {
4299 if (c.getStartTLSSocketFactory() != null)
4300 {
4301 rootDSEEntry.addAttribute("supportedExtension",
4302 StartTLSExtendedRequest.STARTTLS_REQUEST_OID);
4303 break;
4304 }
4305 }
4306 }
4307
4308 if (! saslBindHandlers.isEmpty())
4309 {
4310 final String[] mechanismArray = new String[saslBindHandlers.size()];
4311 rootDSEEntry.addAttribute("supportedSASLMechanisms",
4312 saslBindHandlers.keySet().toArray(mechanismArray));
4313 }
4314
4315 int pos = 0;
4316 final String[] baseDNStrings = new String[baseDNs.size()];
4317 for (final DN baseDN : baseDNs)
4318 {
4319 baseDNStrings[pos++] = baseDN.toString();
4320 }
4321 rootDSEEntry.addAttribute(new Attribute("namingContexts",
4322 DistinguishedNameMatchingRule.getInstance(), baseDNStrings));
4323
4324 if (maxChangelogEntries > 0)
4325 {
4326 rootDSEEntry.addAttribute(new Attribute("changeLog",
4327 DistinguishedNameMatchingRule.getInstance(),
4328 changeLogBaseDN.toString()));
4329 rootDSEEntry.addAttribute(new Attribute("firstChangeNumber",
4330 IntegerMatchingRule.getInstance(), firstChangeNumber.toString()));
4331 rootDSEEntry.addAttribute(new Attribute("lastChangeNumber",
4332 IntegerMatchingRule.getInstance(), lastChangeNumber.toString()));
4333 }
4334
4335 return new ReadOnlyEntry(rootDSEEntry);
4336 }
4337
4338
4339
4340 /**
4341 * Generates a subschema subentry from the provided schema object.
4342 *
4343 * @param schema The schema to use to generate the subschema subentry. It
4344 * may be {@code null} if a minimal default entry should be
4345 * generated.
4346 *
4347 * @return The generated subschema subentry.
4348 */
4349 private static ReadOnlyEntry generateSubschemaSubentry(final Schema schema)
4350 {
4351 final Entry e;
4352
4353 if (schema == null)
4354 {
4355 e = new Entry("cn=schema", schema);
4356
4357 e.addAttribute("objectClass", "namedObject", "ldapSubEntry",
4358 "subschema");
4359 e.addAttribute("cn", "schema");
4360 }
4361 else
4362 {
4363 e = schema.getSchemaEntry().duplicate();
4364 }
4365
4366 try
4367 {
4368 e.addAttribute("entryDN", DN.normalize(e.getDN(), schema));
4369 }
4370 catch (final LDAPException le)
4371 {
4372 // This should never happen.
4373 Debug.debugException(le);
4374 e.setAttribute("entryDN", StaticUtils.toLowerCase(e.getDN()));
4375 }
4376
4377
4378 e.addAttribute("entryUUID", UUID.randomUUID().toString());
4379 return new ReadOnlyEntry(e);
4380 }
4381
4382
4383
4384 /**
4385 * Processes the set of requested attributes from the given search request.
4386 *
4387 * @param attrList The list of requested attributes to examine.
4388 * @param allUserAttrs Indicates whether to return all user attributes. It
4389 * should have an initial value of {@code false}.
4390 * @param allOpAttrs Indicates whether to return all operational
4391 * attributes. It should have an initial value of
4392 * {@code false}.
4393 *
4394 * @return A map of specific attribute types to be returned. The keys of the
4395 * map will be the lowercase OID and names of the attribute types,
4396 * and the values will be a list of option sets for the associated
4397 * attribute type.
4398 */
4399 private Map<String,List<List<String>>> processRequestedAttributes(
4400 final List<String> attrList, final AtomicBoolean allUserAttrs,
4401 final AtomicBoolean allOpAttrs)
4402 {
4403 if (attrList.isEmpty())
4404 {
4405 allUserAttrs.set(true);
4406 return Collections.emptyMap();
4407 }
4408
4409 final Schema schema = schemaRef.get();
4410 final HashMap<String,List<List<String>>> m =
4411 new HashMap<String,List<List<String>>>(attrList.size() * 2);
4412 for (final String s : attrList)
4413 {
4414 if (s.equals("*"))
4415 {
4416 // All user attributes.
4417 allUserAttrs.set(true);
4418 }
4419 else if (s.equals("+"))
4420 {
4421 // All operational attributes.
4422 allOpAttrs.set(true);
4423 }
4424 else if (s.startsWith("@"))
4425 {
4426 // Return attributes by object class. This can only be supported if a
4427 // schema has been defined.
4428 if (schema != null)
4429 {
4430 final String ocName = s.substring(1);
4431 final ObjectClassDefinition oc = schema.getObjectClass(ocName);
4432 if (oc != null)
4433 {
4434 for (final AttributeTypeDefinition at :
4435 oc.getRequiredAttributes(schema, true))
4436 {
4437 addAttributeOIDAndNames(at, m, Collections.<String>emptyList());
4438 }
4439 for (final AttributeTypeDefinition at :
4440 oc.getOptionalAttributes(schema, true))
4441 {
4442 addAttributeOIDAndNames(at, m, Collections.<String>emptyList());
4443 }
4444 }
4445 }
4446 }
4447 else
4448 {
4449 final ObjectPair<String,List<String>> nameWithOptions =
4450 getNameWithOptions(s);
4451 if (nameWithOptions == null)
4452 {
4453 continue;
4454 }
4455
4456 final String name = nameWithOptions.getFirst();
4457 final List<String> options = nameWithOptions.getSecond();
4458
4459 if (schema == null)
4460 {
4461 // Just use the name as provided.
4462 List<List<String>> optionLists = m.get(name);
4463 if (optionLists == null)
4464 {
4465 optionLists = new ArrayList<List<String>>(1);
4466 m.put(name, optionLists);
4467 }
4468 optionLists.add(options);
4469 }
4470 else
4471 {
4472 // If the attribute type is defined in the schema, then use it to get
4473 // all names and the OID. Otherwise, just use the name as provided.
4474 final AttributeTypeDefinition at = schema.getAttributeType(name);
4475 if (at == null)
4476 {
4477 List<List<String>> optionLists = m.get(name);
4478 if (optionLists == null)
4479 {
4480 optionLists = new ArrayList<List<String>>(1);
4481 m.put(name, optionLists);
4482 }
4483 optionLists.add(options);
4484 }
4485 else
4486 {
4487 addAttributeOIDAndNames(at, m, options);
4488 }
4489 }
4490 }
4491 }
4492
4493 return m;
4494 }
4495
4496
4497
4498 /**
4499 * Parses the provided string into an attribute type and set of options.
4500 *
4501 * @param s The string to be parsed.
4502 *
4503 * @return An {@code ObjectPair} in which the first element is the attribute
4504 * type name and the second is the list of options (or an empty
4505 * list if there are no options). Alternately, a value of
4506 * {@code null} may be returned if the provided string does not
4507 * represent a valid attribute type description.
4508 */
4509 private static ObjectPair<String,List<String>> getNameWithOptions(
4510 final String s)
4511 {
4512 if (! Attribute.nameIsValid(s, true))
4513 {
4514 return null;
4515 }
4516
4517 final String l = StaticUtils.toLowerCase(s);
4518
4519 int semicolonPos = l.indexOf(';');
4520 if (semicolonPos < 0)
4521 {
4522 return new ObjectPair<String,List<String>>(l,
4523 Collections.<String>emptyList());
4524 }
4525
4526 final String name = l.substring(0, semicolonPos);
4527 final ArrayList<String> optionList = new ArrayList<String>(1);
4528 while (true)
4529 {
4530 final int nextSemicolonPos = l.indexOf(';', semicolonPos+1);
4531 if (nextSemicolonPos < 0)
4532 {
4533 optionList.add(l.substring(semicolonPos+1));
4534 break;
4535 }
4536 else
4537 {
4538 optionList.add(l.substring(semicolonPos+1, nextSemicolonPos));
4539 semicolonPos = nextSemicolonPos;
4540 }
4541 }
4542
4543 return new ObjectPair<String,List<String>>(name, optionList);
4544 }
4545
4546
4547
4548 /**
4549 * Adds all-lowercase versions of the OID and all names for the provided
4550 * attribute type definition to the given map with the given options.
4551 *
4552 * @param d The attribute type definition to process.
4553 * @param m The map to which the OID and names should be added.
4554 * @param o The array of attribute options to use in the map. It should be
4555 * empty if no options are needed, and must not be {@code null}.
4556 */
4557 private void addAttributeOIDAndNames(final AttributeTypeDefinition d,
4558 final Map<String,List<List<String>>> m,
4559 final List<String> o)
4560 {
4561 if (d == null)
4562 {
4563 return;
4564 }
4565
4566 final String lowerOID = StaticUtils.toLowerCase(d.getOID());
4567 if (lowerOID != null)
4568 {
4569 List<List<String>> l = m.get(lowerOID);
4570 if (l == null)
4571 {
4572 l = new ArrayList<List<String>>(1);
4573 m.put(lowerOID, l);
4574 }
4575
4576 l.add(o);
4577 }
4578
4579 for (final String name : d.getNames())
4580 {
4581 final String lowerName = StaticUtils.toLowerCase(name);
4582 List<List<String>> l = m.get(lowerName);
4583 if (l == null)
4584 {
4585 l = new ArrayList<List<String>>(1);
4586 m.put(lowerName, l);
4587 }
4588
4589 l.add(o);
4590 }
4591
4592 // If a schema is available, then see if the attribute type has any
4593 // subordinate types. If so, then add them.
4594 final Schema schema = schemaRef.get();
4595 if (schema != null)
4596 {
4597 for (final AttributeTypeDefinition subordinateType :
4598 schema.getSubordinateAttributeTypes(d))
4599 {
4600 addAttributeOIDAndNames(subordinateType, m, o);
4601 }
4602 }
4603 }
4604
4605
4606
4607 /**
4608 * Performs the necessary processing to determine whether the given entry
4609 * should be returned as a search result entry or reference, or if it should
4610 * not be returned at all.
4611 *
4612 * @param entry The entry to be processed.
4613 * @param includeSubEntries Indicates whether LDAP subentries should be
4614 * returned to the client.
4615 * @param includeChangeLog Indicates whether entries within the changelog
4616 * should be returned to the client.
4617 * @param hasManageDsaIT Indicates whether the request includes the
4618 * ManageDsaIT control, which can change how smart
4619 * referrals should be handled.
4620 * @param entryList The list to which the entry should be added if
4621 * it should be returned to the client as a search
4622 * result entry.
4623 * @param referenceList The list that should be updated if the provided
4624 * entry represents a smart referral that should be
4625 * returned as a search result reference.
4626 */
4627 private void processSearchEntry(final Entry entry,
4628 final boolean includeSubEntries,
4629 final boolean includeChangeLog,
4630 final boolean hasManageDsaIT,
4631 final List<Entry> entryList,
4632 final List<SearchResultReference> referenceList)
4633 {
4634 // See if the entry should be suppressed as an LDAP subentry.
4635 if ((! includeSubEntries) &&
4636 (entry.hasObjectClass("ldapSubEntry") ||
4637 entry.hasObjectClass("inheritableLDAPSubEntry")))
4638 {
4639 return;
4640 }
4641
4642 // See if the entry should be suppressed as a changelog entry.
4643 try
4644 {
4645 if ((! includeChangeLog) &&
4646 (entry.getParsedDN().isDescendantOf(changeLogBaseDN, true)))
4647 {
4648 return;
4649 }
4650 }
4651 catch (final Exception e)
4652 {
4653 // This should never happen.
4654 Debug.debugException(e);
4655 }
4656
4657 // See if the entry is a referral and should result in a reference rather
4658 // than an entry.
4659 if ((! hasManageDsaIT) && entry.hasObjectClass("referral") &&
4660 entry.hasAttribute("ref"))
4661 {
4662 referenceList.add(new SearchResultReference(
4663 entry.getAttributeValues("ref"), NO_CONTROLS));
4664 return;
4665 }
4666
4667 entryList.add(entry);
4668 }
4669
4670
4671
4672 /**
4673 * Retrieves a copy of the provided entry that includes only the appropriate
4674 * set of requested attributes.
4675 *
4676 * @param entry The entry to be returned.
4677 * @param allUserAttrs Indicates whether to return all user attributes.
4678 * @param allOpAttrs Indicates whether to return all operational
4679 * attributes.
4680 * @param returnAttrs A map with information about the specific attribute
4681 * types to return.
4682 *
4683 * @return A copy of the provided entry that includes only the appropriate
4684 * set of requested attributes.
4685 */
4686 private Entry trimForRequestedAttributes(final Entry entry,
4687 final boolean allUserAttrs, final boolean allOpAttrs,
4688 final Map<String,List<List<String>>> returnAttrs)
4689 {
4690 // See if we can return the entry without paring it down.
4691 final Schema schema = schemaRef.get();
4692 if (allUserAttrs)
4693 {
4694 if (allOpAttrs || (schema == null))
4695 {
4696 return entry;
4697 }
4698 }
4699
4700
4701 // If we've gotten here, then we may only need to return a partial entry.
4702 final Entry copy = new Entry(entry.getDN(), schema);
4703
4704 for (final Attribute a : entry.getAttributes())
4705 {
4706 final ObjectPair<String,List<String>> nameWithOptions =
4707 getNameWithOptions(a.getName());
4708 final String name = nameWithOptions.getFirst();
4709 final List<String> options = nameWithOptions.getSecond();
4710
4711 // If there is a schema, then see if it is an operational attribute, since
4712 // that needs to be handled in a manner different from user attributes
4713 if (schema != null)
4714 {
4715 final AttributeTypeDefinition at = schema.getAttributeType(name);
4716 if ((at != null) && at.isOperational())
4717 {
4718 if (allOpAttrs)
4719 {
4720 copy.addAttribute(a);
4721 continue;
4722 }
4723
4724 final List<List<String>> optionLists = returnAttrs.get(name);
4725 if (optionLists == null)
4726 {
4727 continue;
4728 }
4729
4730 for (final List<String> optionList : optionLists)
4731 {
4732 boolean matchAll = true;
4733 for (final String option : optionList)
4734 {
4735 if (! options.contains(option))
4736 {
4737 matchAll = false;
4738 break;
4739 }
4740 }
4741
4742 if (matchAll)
4743 {
4744 copy.addAttribute(a);
4745 break;
4746 }
4747 }
4748 continue;
4749 }
4750 }
4751
4752 // We'll assume that it's a user attribute, and we'll look for an exact
4753 // match on the base name.
4754 if (allUserAttrs)
4755 {
4756 copy.addAttribute(a);
4757 continue;
4758 }
4759
4760 final List<List<String>> optionLists = returnAttrs.get(name);
4761 if (optionLists == null)
4762 {
4763 continue;
4764 }
4765
4766 for (final List<String> optionList : optionLists)
4767 {
4768 boolean matchAll = true;
4769 for (final String option : optionList)
4770 {
4771 if (! options.contains(option))
4772 {
4773 matchAll = false;
4774 break;
4775 }
4776 }
4777
4778 if (matchAll)
4779 {
4780 copy.addAttribute(a);
4781 break;
4782 }
4783 }
4784 }
4785
4786 return copy;
4787 }
4788
4789
4790
4791 /**
4792 * Retrieves the DN of the existing entry which is the closest hierarchical
4793 * match to the provided DN.
4794 *
4795 * @param dn The DN for which to retrieve the appropriate matched DN.
4796 *
4797 * @return The appropriate matched DN value, or {@code null} if there is
4798 * none.
4799 */
4800 private String getMatchedDNString(final DN dn)
4801 {
4802 DN parentDN = dn.getParent();
4803 while (parentDN != null)
4804 {
4805 if (entryMap.containsKey(parentDN))
4806 {
4807 return parentDN.toString();
4808 }
4809
4810 parentDN = parentDN.getParent();
4811 }
4812
4813 return null;
4814 }
4815
4816
4817
4818 /**
4819 * Converts the provided string list to an array.
4820 *
4821 * @param l The possibly null list to be converted.
4822 *
4823 * @return The string array with the same elements as the given list in the
4824 * same order, or {@code null} if the given list was null.
4825 */
4826 private static String[] stringListToArray(final List<String> l)
4827 {
4828 if (l == null)
4829 {
4830 return null;
4831 }
4832 else
4833 {
4834 final String[] a = new String[l.size()];
4835 return l.toArray(a);
4836 }
4837 }
4838
4839
4840
4841 /**
4842 * Creates a changelog entry from the information in the provided add request
4843 * and adds it to the server changelog.
4844 *
4845 * @param addRequest The add request to use to construct the changelog
4846 * entry.
4847 * @param authzDN The authorization DN for the change.
4848 */
4849 private void addChangeLogEntry(final AddRequestProtocolOp addRequest,
4850 final DN authzDN)
4851 {
4852 // If the changelog is disabled, then don't do anything.
4853 if (maxChangelogEntries <= 0)
4854 {
4855 return;
4856 }
4857
4858 final long changeNumber = lastChangeNumber.incrementAndGet();
4859 final LDIFAddChangeRecord changeRecord = new LDIFAddChangeRecord(
4860 addRequest.getDN(), addRequest.getAttributes());
4861 try
4862 {
4863 addChangeLogEntry(
4864 ChangeLogEntry.constructChangeLogEntry(changeNumber, changeRecord),
4865 authzDN);
4866 }
4867 catch (final LDAPException le)
4868 {
4869 // This should not happen.
4870 Debug.debugException(le);
4871 }
4872 }
4873
4874
4875
4876 /**
4877 * Creates a changelog entry from the information in the provided delete
4878 * request and adds it to the server changelog.
4879 *
4880 * @param e The entry to be deleted.
4881 * @param authzDN The authorization DN for the change.
4882 */
4883 private void addDeleteChangeLogEntry(final Entry e, final DN authzDN)
4884 {
4885 // If the changelog is disabled, then don't do anything.
4886 if (maxChangelogEntries <= 0)
4887 {
4888 return;
4889 }
4890
4891 final long changeNumber = lastChangeNumber.incrementAndGet();
4892 final LDIFDeleteChangeRecord changeRecord =
4893 new LDIFDeleteChangeRecord(e.getDN());
4894
4895 // Create the changelog entry.
4896 try
4897 {
4898 final ChangeLogEntry cle = ChangeLogEntry.constructChangeLogEntry(
4899 changeNumber, changeRecord);
4900
4901 // Add a set of deleted entry attributes, which is simply an LDIF-encoded
4902 // representation of the entry, excluding the first line since it contains
4903 // the DN.
4904 final StringBuilder deletedEntryAttrsBuffer = new StringBuilder();
4905 final String[] ldifLines = e.toLDIF(0);
4906 for (int i=1; i < ldifLines.length; i++)
4907 {
4908 deletedEntryAttrsBuffer.append(ldifLines[i]);
4909 deletedEntryAttrsBuffer.append(StaticUtils.EOL);
4910 }
4911
4912 final Entry copy = cle.duplicate();
4913 copy.addAttribute(ChangeLogEntry.ATTR_DELETED_ENTRY_ATTRS,
4914 deletedEntryAttrsBuffer.toString());
4915 addChangeLogEntry(new ChangeLogEntry(copy), authzDN);
4916 }
4917 catch (final LDAPException le)
4918 {
4919 // This should never happen.
4920 Debug.debugException(le);
4921 }
4922 }
4923
4924
4925
4926 /**
4927 * Creates a changelog entry from the information in the provided modify
4928 * request and adds it to the server changelog.
4929 *
4930 * @param modifyRequest The modify request to use to construct the changelog
4931 * entry.
4932 * @param authzDN The authorization DN for the change.
4933 */
4934 private void addChangeLogEntry(final ModifyRequestProtocolOp modifyRequest,
4935 final DN authzDN)
4936 {
4937 // If the changelog is disabled, then don't do anything.
4938 if (maxChangelogEntries <= 0)
4939 {
4940 return;
4941 }
4942
4943 final long changeNumber = lastChangeNumber.incrementAndGet();
4944 final LDIFModifyChangeRecord changeRecord =
4945 new LDIFModifyChangeRecord(modifyRequest.getDN(),
4946 modifyRequest.getModifications());
4947 try
4948 {
4949 addChangeLogEntry(
4950 ChangeLogEntry.constructChangeLogEntry(changeNumber, changeRecord),
4951 authzDN);
4952 }
4953 catch (final LDAPException le)
4954 {
4955 // This should not happen.
4956 Debug.debugException(le);
4957 }
4958 }
4959
4960
4961
4962 /**
4963 * Creates a changelog entry from the information in the provided modify DN
4964 * request and adds it to the server changelog.
4965 *
4966 * @param modifyDNRequest The modify DN request to use to construct the
4967 * changelog entry.
4968 * @param authzDN The authorization DN for the change.
4969 */
4970 private void addChangeLogEntry(
4971 final ModifyDNRequestProtocolOp modifyDNRequest,
4972 final DN authzDN)
4973 {
4974 // If the changelog is disabled, then don't do anything.
4975 if (maxChangelogEntries <= 0)
4976 {
4977 return;
4978 }
4979
4980 final long changeNumber = lastChangeNumber.incrementAndGet();
4981 final LDIFModifyDNChangeRecord changeRecord =
4982 new LDIFModifyDNChangeRecord(modifyDNRequest.getDN(),
4983 modifyDNRequest.getNewRDN(), modifyDNRequest.deleteOldRDN(),
4984 modifyDNRequest.getNewSuperiorDN());
4985 try
4986 {
4987 addChangeLogEntry(
4988 ChangeLogEntry.constructChangeLogEntry(changeNumber, changeRecord),
4989 authzDN);
4990 }
4991 catch (final LDAPException le)
4992 {
4993 // This should not happen.
4994 Debug.debugException(le);
4995 }
4996 }
4997
4998
4999
5000 /**
5001 * Adds the provided changelog entry to the data set, removing an old entry if
5002 * necessary to remain within the maximum allowed number of changes. This
5003 * must only be called from a synchronized method, and the change number for
5004 * the changelog entry must have been obtained by calling
5005 * {@code lastChangeNumber.incrementAndGet()}.
5006 *
5007 * @param e The changelog entry to add to the data set.
5008 * @param authzDN The authorization DN for the change.
5009 */
5010 private void addChangeLogEntry(final ChangeLogEntry e, final DN authzDN)
5011 {
5012 // Construct the DN object to use for the entry and put it in the map.
5013 final long changeNumber = e.getChangeNumber();
5014 final Schema schema = schemaRef.get();
5015 final DN dn = new DN(
5016 new RDN("changeNumber", String.valueOf(changeNumber), schema),
5017 changeLogBaseDN);
5018
5019 final Entry entry = e.duplicate();
5020 if (generateOperationalAttributes)
5021 {
5022 final Date d = new Date();
5023 entry.addAttribute(new Attribute("entryDN",
5024 DistinguishedNameMatchingRule.getInstance(),
5025 dn.toNormalizedString()));
5026 entry.addAttribute(new Attribute("entryUUID",
5027 UUID.randomUUID().toString()));
5028 entry.addAttribute(new Attribute("subschemaSubentry",
5029 DistinguishedNameMatchingRule.getInstance(),
5030 subschemaSubentryDN.toString()));
5031 entry.addAttribute(new Attribute("creatorsName",
5032 DistinguishedNameMatchingRule.getInstance(),
5033 authzDN.toString()));
5034 entry.addAttribute(new Attribute("createTimestamp",
5035 GeneralizedTimeMatchingRule.getInstance(),
5036 StaticUtils.encodeGeneralizedTime(d)));
5037 entry.addAttribute(new Attribute("modifiersName",
5038 DistinguishedNameMatchingRule.getInstance(),
5039 authzDN.toString()));
5040 entry.addAttribute(new Attribute("modifyTimestamp",
5041 GeneralizedTimeMatchingRule.getInstance(),
5042 StaticUtils.encodeGeneralizedTime(d)));
5043 }
5044
5045 entryMap.put(dn, new ReadOnlyEntry(entry));
5046 indexAdd(entry);
5047
5048 // Update the first change number and/or trim the changelog if necessary.
5049 final long firstNumber = firstChangeNumber.get();
5050 if (changeNumber == 1L)
5051 {
5052 // It's the first change, so we need to set the first change number.
5053 firstChangeNumber.set(1);
5054 }
5055 else
5056 {
5057 // See if we need to trim an entry.
5058 final long numChangeLogEntries = changeNumber - firstNumber + 1;
5059 if (numChangeLogEntries > maxChangelogEntries)
5060 {
5061 // We need to delete the first changelog entry and increment the
5062 // first change number.
5063 firstChangeNumber.incrementAndGet();
5064 final Entry deletedEntry = entryMap.remove(new DN(
5065 new RDN("changeNumber", String.valueOf(firstNumber), schema),
5066 changeLogBaseDN));
5067 indexDelete(deletedEntry);
5068 }
5069 }
5070 }
5071
5072
5073
5074 /**
5075 * Checks to see if the provided control map includes a proxied authorization
5076 * control (v1 or v2) and if so then attempts to determine the appropriate
5077 * authorization identity to use for the operation.
5078 *
5079 * @param m The map of request controls, indexed by OID.
5080 *
5081 * @return The DN of the authorized user, or the current authentication DN
5082 * if the control map does not include a proxied authorization
5083 * request control.
5084 *
5085 * @throws LDAPException If a problem is encountered while attempting to
5086 * determine the authorization DN.
5087 */
5088 private DN handleProxiedAuthControl(final Map<String,Control> m)
5089 throws LDAPException
5090 {
5091 final ProxiedAuthorizationV1RequestControl p1 =
5092 (ProxiedAuthorizationV1RequestControl) m.get(
5093 ProxiedAuthorizationV1RequestControl.
5094 PROXIED_AUTHORIZATION_V1_REQUEST_OID);
5095 if (p1 != null)
5096 {
5097 final DN authzDN = new DN(p1.getProxyDN(), schemaRef.get());
5098 if (authzDN.isNullDN() ||
5099 entryMap.containsKey(authzDN) ||
5100 additionalBindCredentials.containsKey(authzDN))
5101 {
5102 return authzDN;
5103 }
5104 else
5105 {
5106 throw new LDAPException(ResultCode.AUTHORIZATION_DENIED,
5107 ERR_MEM_HANDLER_NO_SUCH_IDENTITY.get("dn:" + authzDN.toString()));
5108 }
5109 }
5110
5111 final ProxiedAuthorizationV2RequestControl p2 =
5112 (ProxiedAuthorizationV2RequestControl) m.get(
5113 ProxiedAuthorizationV2RequestControl.
5114 PROXIED_AUTHORIZATION_V2_REQUEST_OID);
5115 if (p2 != null)
5116 {
5117 return getDNForAuthzID(p2.getAuthorizationID());
5118 }
5119
5120 return authenticatedDN;
5121 }
5122
5123
5124
5125 /**
5126 * Attempts to identify the DN of the user referenced by the provided
5127 * authorization ID string. It may be "dn:" followed by the target DN, or
5128 * "u:" followed by the value of the uid attribute in the entry. If it uses
5129 * the "dn:" form, then it may reference the DN of a regular entry or a DN
5130 * in the configured set of additional bind credentials.
5131 *
5132 * @param authzID The authorization ID to resolve to a user DN.
5133 *
5134 * @return The DN identified for the provided authorization ID.
5135 *
5136 * @throws LDAPException If a problem prevents resolving the authorization
5137 * ID to a user DN.
5138 */
5139 public synchronized DN getDNForAuthzID(final String authzID)
5140 throws LDAPException
5141 {
5142 final String lowerAuthzID = StaticUtils.toLowerCase(authzID);
5143 if (lowerAuthzID.startsWith("dn:"))
5144 {
5145 if (lowerAuthzID.equals("dn:"))
5146 {
5147 return DN.NULL_DN;
5148 }
5149 else
5150 {
5151 final DN dn = new DN(authzID.substring(3), schemaRef.get());
5152 if (entryMap.containsKey(dn) ||
5153 additionalBindCredentials.containsKey(dn))
5154 {
5155 return dn;
5156 }
5157 else
5158 {
5159 throw new LDAPException(ResultCode.AUTHORIZATION_DENIED,
5160 ERR_MEM_HANDLER_NO_SUCH_IDENTITY.get(authzID));
5161 }
5162 }
5163 }
5164 else if (lowerAuthzID.startsWith("u:"))
5165 {
5166 final Filter f =
5167 Filter.createEqualityFilter("uid", authzID.substring(2));
5168 final List<ReadOnlyEntry> entryList = search("", SearchScope.SUB, f);
5169 if (entryList.size() == 1)
5170 {
5171 return entryList.get(0).getParsedDN();
5172 }
5173 else
5174 {
5175 throw new LDAPException(ResultCode.AUTHORIZATION_DENIED,
5176 ERR_MEM_HANDLER_NO_SUCH_IDENTITY.get(authzID));
5177 }
5178 }
5179 else
5180 {
5181 throw new LDAPException(ResultCode.AUTHORIZATION_DENIED,
5182 ERR_MEM_HANDLER_NO_SUCH_IDENTITY.get(authzID));
5183 }
5184 }
5185
5186
5187
5188 /**
5189 * Checks to see if the provided control map includes an assertion request
5190 * control, and if so then checks to see whether the provided entry satisfies
5191 * the filter in that control.
5192 *
5193 * @param m The map of request controls, indexed by OID.
5194 * @param e The entry to examine against the assertion filter.
5195 *
5196 * @throws LDAPException If the control map includes an assertion request
5197 * control and the provided entry does not match the
5198 * filter contained in that control.
5199 */
5200 private static void handleAssertionRequestControl(final Map<String,Control> m,
5201 final Entry e)
5202 throws LDAPException
5203 {
5204 final AssertionRequestControl c = (AssertionRequestControl)
5205 m.get(AssertionRequestControl.ASSERTION_REQUEST_OID);
5206 if (c == null)
5207 {
5208 return;
5209 }
5210
5211 try
5212 {
5213 if (c.getFilter().matchesEntry(e))
5214 {
5215 return;
5216 }
5217 }
5218 catch (final LDAPException le)
5219 {
5220 Debug.debugException(le);
5221 }
5222
5223 // If we've gotten here, then the filter doesn't match.
5224 throw new LDAPException(ResultCode.ASSERTION_FAILED,
5225 ERR_MEM_HANDLER_ASSERTION_CONTROL_NOT_SATISFIED.get());
5226 }
5227
5228
5229
5230 /**
5231 * Checks to see if the provided control map includes a pre-read request
5232 * control, and if so then generates the appropriate response control that
5233 * should be returned to the client.
5234 *
5235 * @param m The map of request controls, indexed by OID.
5236 * @param e The entry as it appeared before the operation.
5237 *
5238 * @return The pre-read response control that should be returned to the
5239 * client, or {@code null} if there is none.
5240 */
5241 private PreReadResponseControl handlePreReadControl(
5242 final Map<String,Control> m, final Entry e)
5243 {
5244 final PreReadRequestControl c = (PreReadRequestControl)
5245 m.get(PreReadRequestControl.PRE_READ_REQUEST_OID);
5246 if (c == null)
5247 {
5248 return null;
5249 }
5250
5251 final AtomicBoolean allUserAttrs = new AtomicBoolean(false);
5252 final AtomicBoolean allOpAttrs = new AtomicBoolean(false);
5253 final Map<String,List<List<String>>> returnAttrs =
5254 processRequestedAttributes(Arrays.asList(c.getAttributes()),
5255 allUserAttrs, allOpAttrs);
5256
5257 final Entry trimmedEntry = trimForRequestedAttributes(e, allUserAttrs.get(),
5258 allOpAttrs.get(), returnAttrs);
5259 return new PreReadResponseControl(new ReadOnlyEntry(trimmedEntry));
5260 }
5261
5262
5263
5264 /**
5265 * Checks to see if the provided control map includes a post-read request
5266 * control, and if so then generates the appropriate response control that
5267 * should be returned to the client.
5268 *
5269 * @param m The map of request controls, indexed by OID.
5270 * @param e The entry as it appeared before the operation.
5271 *
5272 * @return The post-read response control that should be returned to the
5273 * client, or {@code null} if there is none.
5274 */
5275 private PostReadResponseControl handlePostReadControl(
5276 final Map<String,Control> m, final Entry e)
5277 {
5278 final PostReadRequestControl c = (PostReadRequestControl)
5279 m.get(PostReadRequestControl.POST_READ_REQUEST_OID);
5280 if (c == null)
5281 {
5282 return null;
5283 }
5284
5285 final AtomicBoolean allUserAttrs = new AtomicBoolean(false);
5286 final AtomicBoolean allOpAttrs = new AtomicBoolean(false);
5287 final Map<String,List<List<String>>> returnAttrs =
5288 processRequestedAttributes(Arrays.asList(c.getAttributes()),
5289 allUserAttrs, allOpAttrs);
5290
5291 final Entry trimmedEntry = trimForRequestedAttributes(e, allUserAttrs.get(),
5292 allOpAttrs.get(), returnAttrs);
5293 return new PostReadResponseControl(new ReadOnlyEntry(trimmedEntry));
5294 }
5295
5296
5297
5298 /**
5299 * Finds the smart referral entry which is hierarchically nearest the entry
5300 * with the given DN.
5301 *
5302 * @param dn The DN for which to find the hierarchically nearest smart
5303 * referral entry.
5304 *
5305 * @return The hierarchically nearest smart referral entry for the provided
5306 * DN, or {@code null} if there are no smart referral entries with
5307 * the provided DN or any of its ancestors.
5308 */
5309 private Entry findNearestReferral(final DN dn)
5310 {
5311 DN d = dn;
5312 while (true)
5313 {
5314 final Entry e = entryMap.get(d);
5315 if (e == null)
5316 {
5317 d = d.getParent();
5318 if (d == null)
5319 {
5320 return null;
5321 }
5322 }
5323 else if (e.hasObjectClass("referral"))
5324 {
5325 return e;
5326 }
5327 else
5328 {
5329 return null;
5330 }
5331 }
5332 }
5333
5334
5335
5336 /**
5337 * Retrieves the referral URLs that should be used for the provided target DN
5338 * based on the given referral entry.
5339 *
5340 * @param targetDN The target DN from the associated operation.
5341 * @param referralEntry The entry containing the smart referral.
5342 *
5343 * @return The referral URLs that should be returned.
5344 */
5345 private static List<String> getReferralURLs(final DN targetDN,
5346 final Entry referralEntry)
5347 {
5348 final String[] refs = referralEntry.getAttributeValues("ref");
5349 if (refs == null)
5350 {
5351 return null;
5352 }
5353
5354 final RDN[] retainRDNs;
5355 try
5356 {
5357 // If the target DN equals the referral entry DN, or if it's not
5358 // subordinate to the referral entry, then the URLs should be returned
5359 // as-is.
5360 final DN parsedEntryDN = referralEntry.getParsedDN();
5361 if (targetDN.equals(parsedEntryDN) ||
5362 (! targetDN.isDescendantOf(parsedEntryDN, true)))
5363 {
5364 return Arrays.asList(refs);
5365 }
5366
5367 final RDN[] targetRDNs = targetDN.getRDNs();
5368 final RDN[] refEntryRDNs = referralEntry.getParsedDN().getRDNs();
5369 retainRDNs = new RDN[targetRDNs.length - refEntryRDNs.length];
5370 System.arraycopy(targetRDNs, 0, retainRDNs, 0, retainRDNs.length);
5371 }
5372 catch (final LDAPException le)
5373 {
5374 Debug.debugException(le);
5375 return Arrays.asList(refs);
5376 }
5377
5378 final List<String> refList = new ArrayList<String>(refs.length);
5379 for (final String ref : refs)
5380 {
5381 try
5382 {
5383 final LDAPURL url = new LDAPURL(ref);
5384 final RDN[] refRDNs = url.getBaseDN().getRDNs();
5385 final RDN[] newRefRDNs = new RDN[retainRDNs.length + refRDNs.length];
5386 System.arraycopy(retainRDNs, 0, newRefRDNs, 0, retainRDNs.length);
5387 System.arraycopy(refRDNs, 0, newRefRDNs, retainRDNs.length,
5388 refRDNs.length);
5389 final DN newBaseDN = new DN(newRefRDNs);
5390
5391 final LDAPURL newURL = new LDAPURL(url.getScheme(), url.getHost(),
5392 url.getPort(), newBaseDN, null, null, null);
5393 refList.add(newURL.toString());
5394 }
5395 catch (final LDAPException le)
5396 {
5397 Debug.debugException(le);
5398 refList.add(ref);
5399 }
5400 }
5401
5402 return refList;
5403 }
5404
5405
5406
5407 /**
5408 * Indicates whether the specified entry exists in the server.
5409 *
5410 * @param dn The DN of the entry for which to make the determination.
5411 *
5412 * @return {@code true} if the entry exists, or {@code false} if not.
5413 *
5414 * @throws LDAPException If a problem is encountered while trying to
5415 * communicate with the directory server.
5416 */
5417 public synchronized boolean entryExists(final String dn)
5418 throws LDAPException
5419 {
5420 return (getEntry(dn) != null);
5421 }
5422
5423
5424
5425 /**
5426 * Indicates whether the specified entry exists in the server and matches the
5427 * given filter.
5428 *
5429 * @param dn The DN of the entry for which to make the determination.
5430 * @param filter The filter the entry is expected to match.
5431 *
5432 * @return {@code true} if the entry exists and matches the specified filter,
5433 * or {@code false} if not.
5434 *
5435 * @throws LDAPException If a problem is encountered while trying to
5436 * communicate with the directory server.
5437 */
5438 public synchronized boolean entryExists(final String dn, final String filter)
5439 throws LDAPException
5440 {
5441 final Entry e = getEntry(dn);
5442 if (e == null)
5443 {
5444 return false;
5445 }
5446
5447 final Filter f = Filter.create(filter);
5448 try
5449 {
5450 return f.matchesEntry(e, schemaRef.get());
5451 }
5452 catch (final LDAPException le)
5453 {
5454 Debug.debugException(le);
5455 return false;
5456 }
5457 }
5458
5459
5460
5461 /**
5462 * Indicates whether the specified entry exists in the server. This will
5463 * return {@code true} only if the target entry exists and contains all values
5464 * for all attributes of the provided entry. The entry will be allowed to
5465 * have attribute values not included in the provided entry.
5466 *
5467 * @param entry The entry to compare against the directory server.
5468 *
5469 * @return {@code true} if the entry exists in the server and is a superset
5470 * of the provided entry, or {@code false} if not.
5471 *
5472 * @throws LDAPException If a problem is encountered while trying to
5473 * communicate with the directory server.
5474 */
5475 public synchronized boolean entryExists(final Entry entry)
5476 throws LDAPException
5477 {
5478 final Entry e = getEntry(entry.getDN());
5479 if (e == null)
5480 {
5481 return false;
5482 }
5483
5484 for (final Attribute a : entry.getAttributes())
5485 {
5486 for (final byte[] value : a.getValueByteArrays())
5487 {
5488 if (! e.hasAttributeValue(a.getName(), value))
5489 {
5490 return false;
5491 }
5492 }
5493 }
5494
5495 return true;
5496 }
5497
5498
5499
5500 /**
5501 * Ensures that an entry with the provided DN exists in the directory.
5502 *
5503 * @param dn The DN of the entry for which to make the determination.
5504 *
5505 * @throws LDAPException If a problem is encountered while trying to
5506 * communicate with the directory server.
5507 *
5508 * @throws AssertionError If the target entry does not exist.
5509 */
5510 public synchronized void assertEntryExists(final String dn)
5511 throws LDAPException, AssertionError
5512 {
5513 final Entry e = getEntry(dn);
5514 if (e == null)
5515 {
5516 throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn));
5517 }
5518 }
5519
5520
5521
5522 /**
5523 * Ensures that an entry with the provided DN exists in the directory.
5524 *
5525 * @param dn The DN of the entry for which to make the determination.
5526 * @param filter A filter that the target entry must match.
5527 *
5528 * @throws LDAPException If a problem is encountered while trying to
5529 * communicate with the directory server.
5530 *
5531 * @throws AssertionError If the target entry does not exist or does not
5532 * match the provided filter.
5533 */
5534 public synchronized void assertEntryExists(final String dn,
5535 final String filter)
5536 throws LDAPException, AssertionError
5537 {
5538 final Entry e = getEntry(dn);
5539 if (e == null)
5540 {
5541 throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn));
5542 }
5543
5544 final Filter f = Filter.create(filter);
5545 try
5546 {
5547 if (! f.matchesEntry(e, schemaRef.get()))
5548 {
5549 throw new AssertionError(
5550 ERR_MEM_HANDLER_TEST_ENTRY_DOES_NOT_MATCH_FILTER.get(dn, filter));
5551 }
5552 }
5553 catch (final LDAPException le)
5554 {
5555 Debug.debugException(le);
5556 throw new AssertionError(
5557 ERR_MEM_HANDLER_TEST_ENTRY_DOES_NOT_MATCH_FILTER.get(dn, filter));
5558 }
5559 }
5560
5561
5562
5563 /**
5564 * Ensures that an entry exists in the directory with the same DN and all
5565 * attribute values contained in the provided entry. The server entry may
5566 * contain additional attributes and/or attribute values not included in the
5567 * provided entry.
5568 *
5569 * @param entry The entry expected to be present in the directory server.
5570 *
5571 * @throws LDAPException If a problem is encountered while trying to
5572 * communicate with the directory server.
5573 *
5574 * @throws AssertionError If the target entry does not exist or does not
5575 * match the provided filter.
5576 */
5577 public synchronized void assertEntryExists(final Entry entry)
5578 throws LDAPException, AssertionError
5579 {
5580 final Entry e = getEntry(entry.getDN());
5581 if (e == null)
5582 {
5583 throw new AssertionError(
5584 ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(entry.getDN()));
5585 }
5586
5587
5588 final Collection<Attribute> attrs = entry.getAttributes();
5589 final List<String> messages = new ArrayList<String>(attrs.size());
5590
5591 final Schema schema = schemaRef.get();
5592 for (final Attribute a : entry.getAttributes())
5593 {
5594 final Filter presFilter = Filter.createPresenceFilter(a.getName());
5595 if (! presFilter.matchesEntry(e, schema))
5596 {
5597 messages.add(ERR_MEM_HANDLER_TEST_ATTR_MISSING.get(entry.getDN(),
5598 a.getName()));
5599 continue;
5600 }
5601
5602 for (final byte[] value : a.getValueByteArrays())
5603 {
5604 final Filter eqFilter = Filter.createEqualityFilter(a.getName(), value);
5605 if (! eqFilter.matchesEntry(e, schema))
5606 {
5607 messages.add(ERR_MEM_HANDLER_TEST_VALUE_MISSING.get(entry.getDN(),
5608 a.getName(), StaticUtils.toUTF8String(value)));
5609 }
5610 }
5611 }
5612
5613 if (! messages.isEmpty())
5614 {
5615 throw new AssertionError(StaticUtils.concatenateStrings(messages));
5616 }
5617 }
5618
5619
5620
5621 /**
5622 * Retrieves a list containing the DNs of the entries which are missing from
5623 * the directory server.
5624 *
5625 * @param dns The DNs of the entries to try to find in the server.
5626 *
5627 * @return A list containing all of the provided DNs that were not found in
5628 * the server, or an empty list if all entries were found.
5629 *
5630 * @throws LDAPException If a problem is encountered while trying to
5631 * communicate with the directory server.
5632 */
5633 public synchronized List<String> getMissingEntryDNs(
5634 final Collection<String> dns)
5635 throws LDAPException
5636 {
5637 final List<String> missingDNs = new ArrayList<String>(dns.size());
5638 for (final String dn : dns)
5639 {
5640 final Entry e = getEntry(dn);
5641 if (e == null)
5642 {
5643 missingDNs.add(dn);
5644 }
5645 }
5646
5647 return missingDNs;
5648 }
5649
5650
5651
5652 /**
5653 * Ensures that all of the entries with the provided DNs exist in the
5654 * directory.
5655 *
5656 * @param dns The DNs of the entries for which to make the determination.
5657 *
5658 * @throws LDAPException If a problem is encountered while trying to
5659 * communicate with the directory server.
5660 *
5661 * @throws AssertionError If any of the target entries does not exist.
5662 */
5663 public synchronized void assertEntriesExist(final Collection<String> dns)
5664 throws LDAPException, AssertionError
5665 {
5666 final List<String> missingDNs = getMissingEntryDNs(dns);
5667 if (missingDNs.isEmpty())
5668 {
5669 return;
5670 }
5671
5672 final List<String> messages = new ArrayList<String>(missingDNs.size());
5673 for (final String dn : missingDNs)
5674 {
5675 messages.add(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn));
5676 }
5677
5678 throw new AssertionError(StaticUtils.concatenateStrings(messages));
5679 }
5680
5681
5682
5683 /**
5684 * Retrieves a list containing all of the named attributes which do not exist
5685 * in the target entry.
5686 *
5687 * @param dn The DN of the entry to examine.
5688 * @param attributeNames The names of the attributes expected to be present
5689 * in the target entry.
5690 *
5691 * @return A list containing the names of the attributes which were not
5692 * present in the target entry, an empty list if all specified
5693 * attributes were found in the entry, or {@code null} if the target
5694 * entry does not exist.
5695 *
5696 * @throws LDAPException If a problem is encountered while trying to
5697 * communicate with the directory server.
5698 */
5699 public synchronized List<String> getMissingAttributeNames(final String dn,
5700 final Collection<String> attributeNames)
5701 throws LDAPException
5702 {
5703 final Entry e = getEntry(dn);
5704 if (e == null)
5705 {
5706 return null;
5707 }
5708
5709 final Schema schema = schemaRef.get();
5710 final List<String> missingAttrs =
5711 new ArrayList<String>(attributeNames.size());
5712 for (final String attr : attributeNames)
5713 {
5714 final Filter f = Filter.createPresenceFilter(attr);
5715 if (! f.matchesEntry(e, schema))
5716 {
5717 missingAttrs.add(attr);
5718 }
5719 }
5720
5721 return missingAttrs;
5722 }
5723
5724
5725
5726 /**
5727 * Ensures that the specified entry exists in the directory with all of the
5728 * specified attributes.
5729 *
5730 * @param dn The DN of the entry to examine.
5731 * @param attributeNames The names of the attributes that are expected to be
5732 * present in the provided entry.
5733 *
5734 * @throws LDAPException If a problem is encountered while trying to
5735 * communicate with the directory server.
5736 *
5737 * @throws AssertionError If the target entry does not exist or does not
5738 * contain all of the specified attributes.
5739 */
5740 public synchronized void assertAttributeExists(final String dn,
5741 final Collection<String> attributeNames)
5742 throws LDAPException, AssertionError
5743 {
5744 final List<String> missingAttrs =
5745 getMissingAttributeNames(dn, attributeNames);
5746 if (missingAttrs == null)
5747 {
5748 throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn));
5749 }
5750 else if (missingAttrs.isEmpty())
5751 {
5752 return;
5753 }
5754
5755 final List<String> messages = new ArrayList<String>(missingAttrs.size());
5756 for (final String attr : missingAttrs)
5757 {
5758 messages.add(ERR_MEM_HANDLER_TEST_ATTR_MISSING.get(dn, attr));
5759 }
5760
5761 throw new AssertionError(StaticUtils.concatenateStrings(messages));
5762 }
5763
5764
5765
5766 /**
5767 * Retrieves a list of all provided attribute values which are missing from
5768 * the specified entry. The target attribute may or may not contain
5769 * additional values.
5770 *
5771 * @param dn The DN of the entry to examine.
5772 * @param attributeName The attribute expected to be present in the target
5773 * entry with the given values.
5774 * @param attributeValues The values expected to be present in the target
5775 * entry.
5776 *
5777 * @return A list containing all of the provided values which were not found
5778 * in the entry, an empty list if all provided attribute values were
5779 * found, or {@code null} if the target entry does not exist.
5780 *
5781 * @throws LDAPException If a problem is encountered while trying to
5782 * communicate with the directory server.
5783 */
5784 public synchronized List<String> getMissingAttributeValues(final String dn,
5785 final String attributeName,
5786 final Collection<String> attributeValues)
5787 throws LDAPException
5788 {
5789 final Entry e = getEntry(dn);
5790 if (e == null)
5791 {
5792 return null;
5793 }
5794
5795 final Schema schema = schemaRef.get();
5796 final List<String> missingValues =
5797 new ArrayList<String>(attributeValues.size());
5798 for (final String value : attributeValues)
5799 {
5800 final Filter f = Filter.createEqualityFilter(attributeName, value);
5801 if (! f.matchesEntry(e, schema))
5802 {
5803 missingValues.add(value);
5804 }
5805 }
5806
5807 return missingValues;
5808 }
5809
5810
5811
5812 /**
5813 * Ensures that the specified entry exists in the directory with all of the
5814 * specified values for the given attribute. The attribute may or may not
5815 * contain additional values.
5816 *
5817 * @param dn The DN of the entry to examine.
5818 * @param attributeName The name of the attribute to examine.
5819 * @param attributeValues The set of values which must exist for the given
5820 * attribute.
5821 *
5822 * @throws LDAPException If a problem is encountered while trying to
5823 * communicate with the directory server.
5824 *
5825 * @throws AssertionError If the target entry does not exist, does not
5826 * contain the specified attribute, or that attribute
5827 * does not have all of the specified values.
5828 */
5829 public synchronized void assertValueExists(final String dn,
5830 final String attributeName,
5831 final Collection<String> attributeValues)
5832 throws LDAPException, AssertionError
5833 {
5834 final List<String> missingValues =
5835 getMissingAttributeValues(dn, attributeName, attributeValues);
5836 if (missingValues == null)
5837 {
5838 throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn));
5839 }
5840 else if (missingValues.isEmpty())
5841 {
5842 return;
5843 }
5844
5845 // See if the attribute exists at all in the entry.
5846 final Entry e = getEntry(dn);
5847 final Filter f = Filter.createPresenceFilter(attributeName);
5848 if (! f.matchesEntry(e, schemaRef.get()))
5849 {
5850 throw new AssertionError(
5851 ERR_MEM_HANDLER_TEST_ATTR_MISSING.get(dn, attributeName));
5852 }
5853
5854 final List<String> messages = new ArrayList<String>(missingValues.size());
5855 for (final String value : missingValues)
5856 {
5857 messages.add(ERR_MEM_HANDLER_TEST_VALUE_MISSING.get(dn, attributeName,
5858 value));
5859 }
5860
5861 throw new AssertionError(StaticUtils.concatenateStrings(messages));
5862 }
5863
5864
5865
5866 /**
5867 * Ensures that the specified entry does not exist in the directory.
5868 *
5869 * @param dn The DN of the entry expected to be missing.
5870 *
5871 * @throws LDAPException If a problem is encountered while trying to
5872 * communicate with the directory server.
5873 *
5874 * @throws AssertionError If the target entry is found in the server.
5875 */
5876 public synchronized void assertEntryMissing(final String dn)
5877 throws LDAPException, AssertionError
5878 {
5879 final Entry e = getEntry(dn);
5880 if (e != null)
5881 {
5882 throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_EXISTS.get(dn));
5883 }
5884 }
5885
5886
5887
5888 /**
5889 * Ensures that the specified entry exists in the directory but does not
5890 * contain any of the specified attributes.
5891 *
5892 * @param dn The DN of the entry expected to be present.
5893 * @param attributeNames The names of the attributes expected to be missing
5894 * from the entry.
5895 *
5896 * @throws LDAPException If a problem is encountered while trying to
5897 * communicate with the directory server.
5898 *
5899 * @throws AssertionError If the target entry is missing from the server, or
5900 * if it contains any of the target attributes.
5901 */
5902 public synchronized void assertAttributeMissing(final String dn,
5903 final Collection<String> attributeNames)
5904 throws LDAPException, AssertionError
5905 {
5906 final Entry e = getEntry(dn);
5907 if (e == null)
5908 {
5909 throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn));
5910 }
5911
5912 final Schema schema = schemaRef.get();
5913 final List<String> messages = new ArrayList<String>(attributeNames.size());
5914 for (final String name : attributeNames)
5915 {
5916 final Filter f = Filter.createPresenceFilter(name);
5917 if (f.matchesEntry(e, schema))
5918 {
5919 messages.add(ERR_MEM_HANDLER_TEST_ATTR_EXISTS.get(dn, name));
5920 }
5921 }
5922
5923 if (! messages.isEmpty())
5924 {
5925 throw new AssertionError(StaticUtils.concatenateStrings(messages));
5926 }
5927 }
5928
5929
5930
5931 /**
5932 * Ensures that the specified entry exists in the directory but does not
5933 * contain any of the specified attribute values.
5934 *
5935 * @param dn The DN of the entry expected to be present.
5936 * @param attributeName The name of the attribute to examine.
5937 * @param attributeValues The values expected to be missing from the target
5938 * entry.
5939 *
5940 * @throws LDAPException If a problem is encountered while trying to
5941 * communicate with the directory server.
5942 *
5943 * @throws AssertionError If the target entry is missing from the server, or
5944 * if it contains any of the target attribute values.
5945 */
5946 public synchronized void assertValueMissing(final String dn,
5947 final String attributeName,
5948 final Collection<String> attributeValues)
5949 throws LDAPException, AssertionError
5950 {
5951 final Entry e = getEntry(dn);
5952 if (e == null)
5953 {
5954 throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn));
5955 }
5956
5957 final Schema schema = schemaRef.get();
5958 final List<String> messages = new ArrayList<String>(attributeValues.size());
5959 for (final String value : attributeValues)
5960 {
5961 final Filter f = Filter.createEqualityFilter(attributeName, value);
5962 if (f.matchesEntry(e, schema))
5963 {
5964 messages.add(ERR_MEM_HANDLER_TEST_VALUE_EXISTS.get(dn, attributeName,
5965 value));
5966 }
5967 }
5968
5969 if (! messages.isEmpty())
5970 {
5971 throw new AssertionError(StaticUtils.concatenateStrings(messages));
5972 }
5973 }
5974 }