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