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