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