001/* 002 * Copyright 2011-2020 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2011-2020 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-2020 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.UUID; 056import java.util.concurrent.atomic.AtomicLong; 057import java.util.concurrent.atomic.AtomicReference; 058 059import com.unboundid.asn1.ASN1Integer; 060import com.unboundid.asn1.ASN1OctetString; 061import com.unboundid.ldap.protocol.AddRequestProtocolOp; 062import com.unboundid.ldap.protocol.AddResponseProtocolOp; 063import com.unboundid.ldap.protocol.BindRequestProtocolOp; 064import com.unboundid.ldap.protocol.BindResponseProtocolOp; 065import com.unboundid.ldap.protocol.CompareRequestProtocolOp; 066import com.unboundid.ldap.protocol.CompareResponseProtocolOp; 067import com.unboundid.ldap.protocol.DeleteRequestProtocolOp; 068import com.unboundid.ldap.protocol.DeleteResponseProtocolOp; 069import com.unboundid.ldap.protocol.ExtendedRequestProtocolOp; 070import com.unboundid.ldap.protocol.ExtendedResponseProtocolOp; 071import com.unboundid.ldap.protocol.LDAPMessage; 072import com.unboundid.ldap.protocol.ModifyRequestProtocolOp; 073import com.unboundid.ldap.protocol.ModifyResponseProtocolOp; 074import com.unboundid.ldap.protocol.ModifyDNRequestProtocolOp; 075import com.unboundid.ldap.protocol.ModifyDNResponseProtocolOp; 076import com.unboundid.ldap.protocol.ProtocolOp; 077import com.unboundid.ldap.protocol.SearchRequestProtocolOp; 078import com.unboundid.ldap.protocol.SearchResultDoneProtocolOp; 079import com.unboundid.ldap.matchingrules.DistinguishedNameMatchingRule; 080import com.unboundid.ldap.matchingrules.GeneralizedTimeMatchingRule; 081import com.unboundid.ldap.matchingrules.IntegerMatchingRule; 082import com.unboundid.ldap.matchingrules.MatchingRule; 083import com.unboundid.ldap.protocol.SearchResultReferenceProtocolOp; 084import com.unboundid.ldap.sdk.AddRequest; 085import com.unboundid.ldap.sdk.Attribute; 086import com.unboundid.ldap.sdk.BindResult; 087import com.unboundid.ldap.sdk.ChangeLogEntry; 088import com.unboundid.ldap.sdk.Control; 089import com.unboundid.ldap.sdk.DN; 090import com.unboundid.ldap.sdk.DeleteRequest; 091import com.unboundid.ldap.sdk.Entry; 092import com.unboundid.ldap.sdk.EntrySorter; 093import com.unboundid.ldap.sdk.ExtendedRequest; 094import com.unboundid.ldap.sdk.ExtendedResult; 095import com.unboundid.ldap.sdk.Filter; 096import com.unboundid.ldap.sdk.LDAPException; 097import com.unboundid.ldap.sdk.LDAPResult; 098import com.unboundid.ldap.sdk.LDAPURL; 099import com.unboundid.ldap.sdk.Modification; 100import com.unboundid.ldap.sdk.ModificationType; 101import com.unboundid.ldap.sdk.ModifyDNRequest; 102import com.unboundid.ldap.sdk.ModifyRequest; 103import com.unboundid.ldap.sdk.OperationType; 104import com.unboundid.ldap.sdk.RDN; 105import com.unboundid.ldap.sdk.ReadOnlyEntry; 106import com.unboundid.ldap.sdk.ResultCode; 107import com.unboundid.ldap.sdk.SearchResultEntry; 108import com.unboundid.ldap.sdk.SearchResultReference; 109import com.unboundid.ldap.sdk.SearchScope; 110import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition; 111import com.unboundid.ldap.sdk.schema.DITContentRuleDefinition; 112import com.unboundid.ldap.sdk.schema.DITStructureRuleDefinition; 113import com.unboundid.ldap.sdk.schema.EntryValidator; 114import com.unboundid.ldap.sdk.schema.MatchingRuleUseDefinition; 115import com.unboundid.ldap.sdk.schema.NameFormDefinition; 116import com.unboundid.ldap.sdk.schema.ObjectClassDefinition; 117import com.unboundid.ldap.sdk.schema.Schema; 118import com.unboundid.ldap.sdk.controls.AssertionRequestControl; 119import com.unboundid.ldap.sdk.controls.AuthorizationIdentityRequestControl; 120import com.unboundid.ldap.sdk.controls.AuthorizationIdentityResponseControl; 121import com.unboundid.ldap.sdk.controls.DontUseCopyRequestControl; 122import com.unboundid.ldap.sdk.controls.DraftLDUPSubentriesRequestControl; 123import com.unboundid.ldap.sdk.controls.ManageDsaITRequestControl; 124import com.unboundid.ldap.sdk.controls.PermissiveModifyRequestControl; 125import com.unboundid.ldap.sdk.controls.PostReadRequestControl; 126import com.unboundid.ldap.sdk.controls.PostReadResponseControl; 127import com.unboundid.ldap.sdk.controls.PreReadRequestControl; 128import com.unboundid.ldap.sdk.controls.PreReadResponseControl; 129import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV1RequestControl; 130import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV2RequestControl; 131import com.unboundid.ldap.sdk.controls.RFC3672SubentriesRequestControl; 132import com.unboundid.ldap.sdk.controls.ServerSideSortRequestControl; 133import com.unboundid.ldap.sdk.controls.ServerSideSortResponseControl; 134import com.unboundid.ldap.sdk.controls.SimplePagedResultsControl; 135import com.unboundid.ldap.sdk.controls.SortKey; 136import com.unboundid.ldap.sdk.controls.SubtreeDeleteRequestControl; 137import com.unboundid.ldap.sdk.controls.TransactionSpecificationRequestControl; 138import com.unboundid.ldap.sdk.controls.VirtualListViewRequestControl; 139import com.unboundid.ldap.sdk.controls.VirtualListViewResponseControl; 140import com.unboundid.ldap.sdk.experimental. 141 DraftZeilengaLDAPNoOp12RequestControl; 142import com.unboundid.ldap.sdk.extensions.AbortedTransactionExtendedResult; 143import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest; 144import com.unboundid.ldap.sdk.unboundidds.controls. 145 IgnoreNoUserModificationRequestControl; 146import com.unboundid.ldif.LDIFAddChangeRecord; 147import com.unboundid.ldif.LDIFChangeRecord; 148import com.unboundid.ldif.LDIFDeleteChangeRecord; 149import com.unboundid.ldif.LDIFException; 150import com.unboundid.ldif.LDIFModifyChangeRecord; 151import com.unboundid.ldif.LDIFModifyDNChangeRecord; 152import com.unboundid.ldif.LDIFReader; 153import com.unboundid.ldif.LDIFWriter; 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", UUID.randomUUID().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 UUID.randomUUID().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, 1507 getMatchedDNString(bindDN), 1508 ERR_MEM_HANDLER_BIND_WRONG_PASSWORD.get(request.getBindDN()), 1509 null, null)); 1510 } 1511 } 1512 1513 // If the bind DN is not null and the password is empty, then reject the 1514 // request. 1515 if ((! bindDN.isNullDN()) && (bindPassword.getValueLength() == 0)) 1516 { 1517 return new LDAPMessage(messageID, new BindResponseProtocolOp( 1518 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null, 1519 ERR_MEM_HANDLER_BIND_SIMPLE_DN_WITHOUT_PASSWORD.get(), null, 1520 null)); 1521 } 1522 1523 // See if the bind DN is in the set of additional bind credentials. If 1524 // so, then use the password there. 1525 final byte[] additionalCreds = additionalBindCredentials.get(bindDN); 1526 if (additionalCreds != null) 1527 { 1528 if (Arrays.equals(additionalCreds, bindPassword.getValue())) 1529 { 1530 authenticatedDN = bindDN; 1531 if (controlMap.containsKey(AuthorizationIdentityRequestControl. 1532 AUTHORIZATION_IDENTITY_REQUEST_OID)) 1533 { 1534 responseControls.add(new AuthorizationIdentityResponseControl( 1535 "dn:" + bindDN.toString())); 1536 } 1537 return new LDAPMessage(messageID, 1538 new BindResponseProtocolOp(ResultCode.SUCCESS_INT_VALUE, null, 1539 null, null, null), 1540 responseControls); 1541 } 1542 else 1543 { 1544 return new LDAPMessage(messageID, new BindResponseProtocolOp( 1545 ResultCode.INVALID_CREDENTIALS_INT_VALUE, 1546 getMatchedDNString(bindDN), 1547 ERR_MEM_HANDLER_BIND_WRONG_PASSWORD.get(request.getBindDN()), 1548 null, null)); 1549 } 1550 } 1551 1552 // If the target user doesn't exist, then reject the request. 1553 final ReadOnlyEntry userEntry = entryMap.get(bindDN); 1554 if (userEntry == null) 1555 { 1556 return new LDAPMessage(messageID, new BindResponseProtocolOp( 1557 ResultCode.INVALID_CREDENTIALS_INT_VALUE, 1558 getMatchedDNString(bindDN), 1559 ERR_MEM_HANDLER_BIND_NO_SUCH_USER.get(request.getBindDN()), null, 1560 null)); 1561 } 1562 1563 1564 // Get a list of the user's passwords, restricted to those that match the 1565 // provided clear-text password. If the list is empty, then the 1566 // authentication failed. 1567 final List<InMemoryDirectoryServerPassword> matchingPasswords = 1568 getPasswordsInEntry(userEntry, bindPassword); 1569 if (matchingPasswords.isEmpty()) 1570 { 1571 return new LDAPMessage(messageID, new BindResponseProtocolOp( 1572 ResultCode.INVALID_CREDENTIALS_INT_VALUE, 1573 getMatchedDNString(bindDN), 1574 ERR_MEM_HANDLER_BIND_WRONG_PASSWORD.get(request.getBindDN()), null, 1575 null)); 1576 } 1577 1578 1579 // If we've gotten here, then authentication was successful. 1580 authenticatedDN = bindDN; 1581 if (controlMap.containsKey(AuthorizationIdentityRequestControl. 1582 AUTHORIZATION_IDENTITY_REQUEST_OID)) 1583 { 1584 responseControls.add(new AuthorizationIdentityResponseControl( 1585 "dn:" + bindDN.toString())); 1586 } 1587 return new LDAPMessage(messageID, 1588 new BindResponseProtocolOp(ResultCode.SUCCESS_INT_VALUE, null, 1589 null, null, null), 1590 responseControls); 1591 } 1592 } 1593 1594 1595 1596 /** 1597 * Attempts to process the provided compare request. The attempt will fail if 1598 * any of the following conditions is true: 1599 * <UL> 1600 * <LI>There is a problem with any of the request controls.</LI> 1601 * <LI>The compare request contains a malformed target DN.</LI> 1602 * <LI>The target entry does not exist.</LI> 1603 * </UL> 1604 * 1605 * @param messageID The message ID of the LDAP message containing the 1606 * compare request. 1607 * @param request The compare request that was included in the LDAP 1608 * message that was received. 1609 * @param controls The set of controls included in the LDAP message. It 1610 * may be empty if there were no controls, but will not be 1611 * {@code null}. 1612 * 1613 * @return The {@link LDAPMessage} containing the response to send to the 1614 * client. The protocol op in the {@code LDAPMessage} must be a 1615 * {@code CompareResponseProtocolOp}. 1616 */ 1617 @Override() 1618 @NotNull() 1619 public LDAPMessage processCompareRequest(final int messageID, 1620 @NotNull final CompareRequestProtocolOp request, 1621 @NotNull final List<Control> controls) 1622 { 1623 synchronized (entryMap) 1624 { 1625 // Sleep before processing, if appropriate. 1626 sleepBeforeProcessing(); 1627 1628 // Process the provided request controls. 1629 final Map<String,Control> controlMap; 1630 try 1631 { 1632 controlMap = RequestControlPreProcessor.processControls( 1633 LDAPMessage.PROTOCOL_OP_TYPE_COMPARE_REQUEST, controls); 1634 } 1635 catch (final LDAPException le) 1636 { 1637 Debug.debugException(le); 1638 return new LDAPMessage(messageID, new CompareResponseProtocolOp( 1639 le.getResultCode().intValue(), null, le.getMessage(), null)); 1640 } 1641 final ArrayList<Control> responseControls = new ArrayList<>(1); 1642 1643 1644 // If this operation type is not allowed, then reject it. 1645 final boolean isInternalOp = 1646 controlMap.containsKey(OID_INTERNAL_OPERATION_REQUEST_CONTROL); 1647 if ((! isInternalOp) && 1648 (! config.getAllowedOperationTypes().contains( 1649 OperationType.COMPARE))) 1650 { 1651 return new LDAPMessage(messageID, new CompareResponseProtocolOp( 1652 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null, 1653 ERR_MEM_HANDLER_COMPARE_NOT_ALLOWED.get(), null)); 1654 } 1655 1656 1657 // If this operation type requires authentication, then ensure that the 1658 // client is authenticated. 1659 if ((authenticatedDN.isNullDN() && 1660 config.getAuthenticationRequiredOperationTypes().contains( 1661 OperationType.COMPARE))) 1662 { 1663 return new LDAPMessage(messageID, new CompareResponseProtocolOp( 1664 ResultCode.INSUFFICIENT_ACCESS_RIGHTS_INT_VALUE, null, 1665 ERR_MEM_HANDLER_COMPARE_REQUIRES_AUTH.get(), null)); 1666 } 1667 1668 1669 // Get the parsed target DN. 1670 final DN dn; 1671 try 1672 { 1673 dn = new DN(request.getDN(), schemaRef.get()); 1674 } 1675 catch (final LDAPException le) 1676 { 1677 Debug.debugException(le); 1678 return new LDAPMessage(messageID, new CompareResponseProtocolOp( 1679 ResultCode.INVALID_DN_SYNTAX_INT_VALUE, null, 1680 ERR_MEM_HANDLER_COMPARE_MALFORMED_DN.get(request.getDN(), 1681 le.getMessage()), 1682 null)); 1683 } 1684 1685 // See if the target entry or one of its superiors is a smart referral. 1686 if (! controlMap.containsKey( 1687 ManageDsaITRequestControl.MANAGE_DSA_IT_REQUEST_OID)) 1688 { 1689 final Entry referralEntry = findNearestReferral(dn); 1690 if (referralEntry != null) 1691 { 1692 return new LDAPMessage(messageID, new CompareResponseProtocolOp( 1693 ResultCode.REFERRAL_INT_VALUE, referralEntry.getDN(), 1694 INFO_MEM_HANDLER_REFERRAL_ENCOUNTERED.get(), 1695 getReferralURLs(dn, referralEntry))); 1696 } 1697 } 1698 1699 // Get the target entry (optionally checking for the root DSE or subschema 1700 // subentry). If it does not exist, then fail. 1701 final Entry entry; 1702 if (dn.isNullDN()) 1703 { 1704 entry = generateRootDSE(); 1705 } 1706 else if (dn.equals(subschemaSubentryDN)) 1707 { 1708 entry = subschemaSubentryRef.get(); 1709 } 1710 else 1711 { 1712 entry = entryMap.get(dn); 1713 } 1714 if (entry == null) 1715 { 1716 return new LDAPMessage(messageID, new CompareResponseProtocolOp( 1717 ResultCode.NO_SUCH_OBJECT_INT_VALUE, getMatchedDNString(dn), 1718 ERR_MEM_HANDLER_COMPARE_NO_SUCH_ENTRY.get(request.getDN()), null)); 1719 } 1720 1721 // If the request includes an assertion or proxied authorization control, 1722 // then perform the appropriate processing. 1723 try 1724 { 1725 handleAssertionRequestControl(controlMap, entry); 1726 handleProxiedAuthControl(controlMap); 1727 } 1728 catch (final LDAPException le) 1729 { 1730 Debug.debugException(le); 1731 return new LDAPMessage(messageID, new CompareResponseProtocolOp( 1732 le.getResultCode().intValue(), null, le.getMessage(), null)); 1733 } 1734 1735 // See if the entry contains the assertion value. 1736 final int resultCode; 1737 if (entry.hasAttributeValue(request.getAttributeName(), 1738 request.getAssertionValue().getValue())) 1739 { 1740 resultCode = ResultCode.COMPARE_TRUE_INT_VALUE; 1741 } 1742 else 1743 { 1744 resultCode = ResultCode.COMPARE_FALSE_INT_VALUE; 1745 } 1746 return new LDAPMessage(messageID, 1747 new CompareResponseProtocolOp(resultCode, null, null, null), 1748 responseControls); 1749 } 1750 } 1751 1752 1753 1754 /** 1755 * Processes the provided delete request. 1756 * <BR><BR> 1757 * This method may be used regardless of whether the server is listening for 1758 * client connections, and regardless of whether delete operations are 1759 * allowed in the server. 1760 * 1761 * @param deleteRequest The delete request to be processed. It must not be 1762 * {@code null}. 1763 * 1764 * @return The result of processing the delete operation. 1765 * 1766 * @throws LDAPException If the server rejects the delete request, or if a 1767 * problem is encountered while sending the request or 1768 * reading the response. 1769 */ 1770 @NotNull() 1771 public LDAPResult delete(@NotNull final DeleteRequest deleteRequest) 1772 throws LDAPException 1773 { 1774 final ArrayList<Control> requestControlList = 1775 new ArrayList<>(deleteRequest.getControlList()); 1776 requestControlList.add(new Control(OID_INTERNAL_OPERATION_REQUEST_CONTROL, 1777 false)); 1778 1779 final LDAPMessage responseMessage = processDeleteRequest(1, 1780 new DeleteRequestProtocolOp(deleteRequest.getDN()), 1781 requestControlList); 1782 1783 final DeleteResponseProtocolOp deleteResponse = 1784 responseMessage.getDeleteResponseProtocolOp(); 1785 1786 final LDAPResult ldapResult = new LDAPResult(responseMessage.getMessageID(), 1787 ResultCode.valueOf(deleteResponse.getResultCode()), 1788 deleteResponse.getDiagnosticMessage(), deleteResponse.getMatchedDN(), 1789 deleteResponse.getReferralURLs(), responseMessage.getControls()); 1790 1791 switch (deleteResponse.getResultCode()) 1792 { 1793 case ResultCode.SUCCESS_INT_VALUE: 1794 case ResultCode.NO_OPERATION_INT_VALUE: 1795 return ldapResult; 1796 default: 1797 throw new LDAPException(ldapResult); 1798 } 1799 } 1800 1801 1802 1803 /** 1804 * Attempts to process the provided delete request. The attempt will fail if 1805 * any of the following conditions is true: 1806 * <UL> 1807 * <LI>There is a problem with any of the request controls.</LI> 1808 * <LI>The delete request contains a malformed target DN.</LI> 1809 * <LI>The target entry is the root DSE.</LI> 1810 * <LI>The target entry is the subschema subentry.</LI> 1811 * <LI>The target entry is at or below the changelog base entry.</LI> 1812 * <LI>The target entry does not exist.</LI> 1813 * <LI>The target entry has one or more subordinate entries.</LI> 1814 * </UL> 1815 * 1816 * @param messageID The message ID of the LDAP message containing the delete 1817 * request. 1818 * @param request The delete request that was included in the LDAP message 1819 * that was received. 1820 * @param controls The set of controls included in the LDAP message. It 1821 * may be empty if there were no controls, but will not be 1822 * {@code null}. 1823 * 1824 * @return The {@link LDAPMessage} containing the response to send to the 1825 * client. The protocol op in the {@code LDAPMessage} must be a 1826 * {@code DeleteResponseProtocolOp}. 1827 */ 1828 @Override() 1829 @NotNull() 1830 public LDAPMessage processDeleteRequest(final int messageID, 1831 @NotNull final DeleteRequestProtocolOp request, 1832 @NotNull final List<Control> controls) 1833 { 1834 synchronized (entryMap) 1835 { 1836 // Sleep before processing, if appropriate. 1837 sleepBeforeProcessing(); 1838 1839 // Process the provided request controls. 1840 final Map<String,Control> controlMap; 1841 try 1842 { 1843 controlMap = RequestControlPreProcessor.processControls( 1844 LDAPMessage.PROTOCOL_OP_TYPE_DELETE_REQUEST, controls); 1845 } 1846 catch (final LDAPException le) 1847 { 1848 Debug.debugException(le); 1849 return new LDAPMessage(messageID, new DeleteResponseProtocolOp( 1850 le.getResultCode().intValue(), null, le.getMessage(), null)); 1851 } 1852 final ArrayList<Control> responseControls = new ArrayList<>(1); 1853 1854 1855 // If this operation type is not allowed, then reject it. 1856 final boolean isInternalOp = 1857 controlMap.containsKey(OID_INTERNAL_OPERATION_REQUEST_CONTROL); 1858 if ((! isInternalOp) && 1859 (! config.getAllowedOperationTypes().contains(OperationType.DELETE))) 1860 { 1861 return new LDAPMessage(messageID, new DeleteResponseProtocolOp( 1862 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null, 1863 ERR_MEM_HANDLER_DELETE_NOT_ALLOWED.get(), null)); 1864 } 1865 1866 1867 // If this operation type requires authentication, then ensure that the 1868 // client is authenticated. 1869 if ((authenticatedDN.isNullDN() && 1870 config.getAuthenticationRequiredOperationTypes().contains( 1871 OperationType.DELETE))) 1872 { 1873 return new LDAPMessage(messageID, new DeleteResponseProtocolOp( 1874 ResultCode.INSUFFICIENT_ACCESS_RIGHTS_INT_VALUE, null, 1875 ERR_MEM_HANDLER_DELETE_REQUIRES_AUTH.get(), null)); 1876 } 1877 1878 1879 // See if this delete request is part of a transaction. If so, then 1880 // perform appropriate processing for it and return success immediately 1881 // without actually doing any further processing. 1882 try 1883 { 1884 final ASN1OctetString txnID = 1885 processTransactionRequest(messageID, request, controlMap); 1886 if (txnID != null) 1887 { 1888 return new LDAPMessage(messageID, new DeleteResponseProtocolOp( 1889 ResultCode.SUCCESS_INT_VALUE, null, 1890 INFO_MEM_HANDLER_OP_IN_TXN.get(txnID.stringValue()), null)); 1891 } 1892 } 1893 catch (final LDAPException le) 1894 { 1895 Debug.debugException(le); 1896 return new LDAPMessage(messageID, 1897 new DeleteResponseProtocolOp(le.getResultCode().intValue(), 1898 le.getMatchedDN(), le.getDiagnosticMessage(), 1899 StaticUtils.toList(le.getReferralURLs())), 1900 le.getResponseControls()); 1901 } 1902 1903 1904 // Get the parsed target DN. 1905 final DN dn; 1906 try 1907 { 1908 dn = new DN(request.getDN(), schemaRef.get()); 1909 } 1910 catch (final LDAPException le) 1911 { 1912 Debug.debugException(le); 1913 return new LDAPMessage(messageID, new DeleteResponseProtocolOp( 1914 ResultCode.INVALID_DN_SYNTAX_INT_VALUE, null, 1915 ERR_MEM_HANDLER_DELETE_MALFORMED_DN.get(request.getDN(), 1916 le.getMessage()), 1917 null)); 1918 } 1919 1920 // See if the target entry or one of its superiors is a smart referral. 1921 if (! controlMap.containsKey( 1922 ManageDsaITRequestControl.MANAGE_DSA_IT_REQUEST_OID)) 1923 { 1924 final Entry referralEntry = findNearestReferral(dn); 1925 if (referralEntry != null) 1926 { 1927 return new LDAPMessage(messageID, new DeleteResponseProtocolOp( 1928 ResultCode.REFERRAL_INT_VALUE, referralEntry.getDN(), 1929 INFO_MEM_HANDLER_REFERRAL_ENCOUNTERED.get(), 1930 getReferralURLs(dn, referralEntry))); 1931 } 1932 } 1933 1934 // Make sure the target entry isn't the root DSE or schema, or a changelog 1935 // entry. 1936 if (dn.isNullDN()) 1937 { 1938 return new LDAPMessage(messageID, new DeleteResponseProtocolOp( 1939 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null, 1940 ERR_MEM_HANDLER_DELETE_ROOT_DSE.get(), null)); 1941 } 1942 else if (dn.equals(subschemaSubentryDN)) 1943 { 1944 return new LDAPMessage(messageID, new DeleteResponseProtocolOp( 1945 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null, 1946 ERR_MEM_HANDLER_DELETE_SCHEMA.get(subschemaSubentryDN.toString()), 1947 null)); 1948 } 1949 else if (dn.isDescendantOf(changeLogBaseDN, true)) 1950 { 1951 return new LDAPMessage(messageID, new DeleteResponseProtocolOp( 1952 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null, 1953 ERR_MEM_HANDLER_DELETE_CHANGELOG.get(request.getDN()), null)); 1954 } 1955 1956 // Get the target entry. If it does not exist, then fail. 1957 final Entry entry = entryMap.get(dn); 1958 if (entry == null) 1959 { 1960 return new LDAPMessage(messageID, new DeleteResponseProtocolOp( 1961 ResultCode.NO_SUCH_OBJECT_INT_VALUE, getMatchedDNString(dn), 1962 ERR_MEM_HANDLER_DELETE_NO_SUCH_ENTRY.get(request.getDN()), null)); 1963 } 1964 1965 // Create a list with the DN of the target entry, and all the DNs of its 1966 // subordinates. If the entry has subordinates and the subtree delete 1967 // control was not provided, then fail. 1968 final ArrayList<DN> subordinateDNs = new ArrayList<>(entryMap.size()); 1969 for (final DN mapEntryDN : entryMap.keySet()) 1970 { 1971 if (mapEntryDN.isDescendantOf(dn, false)) 1972 { 1973 subordinateDNs.add(mapEntryDN); 1974 } 1975 } 1976 1977 if ((! subordinateDNs.isEmpty()) && 1978 (! controlMap.containsKey( 1979 SubtreeDeleteRequestControl.SUBTREE_DELETE_REQUEST_OID))) 1980 { 1981 return new LDAPMessage(messageID, new DeleteResponseProtocolOp( 1982 ResultCode.NOT_ALLOWED_ON_NONLEAF_INT_VALUE, null, 1983 ERR_MEM_HANDLER_DELETE_HAS_SUBORDINATES.get(request.getDN()), 1984 null)); 1985 } 1986 1987 // Handle the necessary processing for the assertion, pre-read, and 1988 // proxied auth controls. 1989 final DN authzDN; 1990 try 1991 { 1992 handleAssertionRequestControl(controlMap, entry); 1993 1994 final PreReadResponseControl preReadResponse = 1995 handlePreReadControl(controlMap, entry); 1996 if (preReadResponse != null) 1997 { 1998 responseControls.add(preReadResponse); 1999 } 2000 2001 authzDN = handleProxiedAuthControl(controlMap); 2002 } 2003 catch (final LDAPException le) 2004 { 2005 Debug.debugException(le); 2006 return new LDAPMessage(messageID, new DeleteResponseProtocolOp( 2007 le.getResultCode().intValue(), null, le.getMessage(), null)); 2008 } 2009 2010 // At this point, the entry will be removed. However, if this will be a 2011 // subtree delete, then we want to delete all of its subordinates first so 2012 // that the changelog will show the deletes in the appropriate order. 2013 for (int i=(subordinateDNs.size() - 1); i >= 0; i--) 2014 { 2015 final DN subordinateDN = subordinateDNs.get(i); 2016 final Entry subEntry = entryMap.remove(subordinateDN); 2017 indexDelete(subEntry); 2018 addDeleteChangeLogEntry(subEntry, authzDN); 2019 handleReferentialIntegrityDelete(subordinateDN); 2020 } 2021 2022 // Finally, remove the target entry and create a changelog entry for it. 2023 entryMap.remove(dn); 2024 indexDelete(entry); 2025 addDeleteChangeLogEntry(entry, authzDN); 2026 handleReferentialIntegrityDelete(dn); 2027 2028 return new LDAPMessage(messageID, 2029 new DeleteResponseProtocolOp(ResultCode.SUCCESS_INT_VALUE, null, 2030 null, null), 2031 responseControls); 2032 } 2033 } 2034 2035 2036 2037 /** 2038 * Handles any appropriate referential integrity processing for a delete 2039 * operation. 2040 * 2041 * @param dn The DN of the entry that has been deleted. 2042 */ 2043 private void handleReferentialIntegrityDelete(@NotNull final DN dn) 2044 { 2045 if (referentialIntegrityAttributes.isEmpty()) 2046 { 2047 return; 2048 } 2049 2050 final ArrayList<DN> entryDNs = new ArrayList<>(entryMap.keySet()); 2051 for (final DN mapDN : entryDNs) 2052 { 2053 final ReadOnlyEntry e = entryMap.get(mapDN); 2054 2055 boolean referenceFound = false; 2056 final Schema schema = schemaRef.get(); 2057 for (final String attrName : referentialIntegrityAttributes) 2058 { 2059 final Attribute a = e.getAttribute(attrName, schema); 2060 if ((a != null) && 2061 a.hasValue(dn.toNormalizedString(), 2062 DistinguishedNameMatchingRule.getInstance())) 2063 { 2064 referenceFound = true; 2065 break; 2066 } 2067 } 2068 2069 if (referenceFound) 2070 { 2071 final Entry copy = e.duplicate(); 2072 for (final String attrName : referentialIntegrityAttributes) 2073 { 2074 copy.removeAttributeValue(attrName, dn.toNormalizedString(), 2075 DistinguishedNameMatchingRule.getInstance()); 2076 } 2077 entryMap.put(mapDN, new ReadOnlyEntry(copy)); 2078 indexDelete(e); 2079 indexAdd(copy); 2080 } 2081 } 2082 } 2083 2084 2085 2086 /** 2087 * Attempts to process the provided extended request, if an extended operation 2088 * handler is defined for the given request OID. 2089 * 2090 * @param messageID The message ID of the LDAP message containing the 2091 * extended request. 2092 * @param request The extended request that was included in the LDAP 2093 * message that was received. 2094 * @param controls The set of controls included in the LDAP message. It 2095 * may be empty if there were no controls, but will not be 2096 * {@code null}. 2097 * 2098 * @return The {@link LDAPMessage} containing the response to send to the 2099 * client. The protocol op in the {@code LDAPMessage} must be an 2100 * {@code ExtendedResponseProtocolOp}. 2101 */ 2102 @Override() 2103 @NotNull() 2104 public LDAPMessage processExtendedRequest(final int messageID, 2105 @NotNull final ExtendedRequestProtocolOp request, 2106 @NotNull final List<Control> controls) 2107 { 2108 synchronized (entryMap) 2109 { 2110 // Sleep before processing, if appropriate. 2111 sleepBeforeProcessing(); 2112 2113 boolean isInternalOp = false; 2114 for (final Control c : controls) 2115 { 2116 if (c.getOID().equals(OID_INTERNAL_OPERATION_REQUEST_CONTROL)) 2117 { 2118 isInternalOp = true; 2119 break; 2120 } 2121 } 2122 2123 2124 // If this operation type is not allowed, then reject it. 2125 if ((! isInternalOp) && 2126 (! config.getAllowedOperationTypes().contains( 2127 OperationType.EXTENDED))) 2128 { 2129 return new LDAPMessage(messageID, new ExtendedResponseProtocolOp( 2130 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null, 2131 ERR_MEM_HANDLER_EXTENDED_NOT_ALLOWED.get(), null, null, null)); 2132 } 2133 2134 2135 // If this operation type requires authentication, then ensure that the 2136 // client is authenticated. 2137 if ((authenticatedDN.isNullDN() && 2138 config.getAuthenticationRequiredOperationTypes().contains( 2139 OperationType.EXTENDED))) 2140 { 2141 return new LDAPMessage(messageID, new ExtendedResponseProtocolOp( 2142 ResultCode.INSUFFICIENT_ACCESS_RIGHTS_INT_VALUE, null, 2143 ERR_MEM_HANDLER_EXTENDED_REQUIRES_AUTH.get(), null, null, null)); 2144 } 2145 2146 2147 final String oid = request.getOID(); 2148 final InMemoryExtendedOperationHandler handler = 2149 extendedRequestHandlers.get(oid); 2150 if (handler == null) 2151 { 2152 return new LDAPMessage(messageID, new ExtendedResponseProtocolOp( 2153 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null, 2154 ERR_MEM_HANDLER_EXTENDED_OP_NOT_SUPPORTED.get(oid), null, null, 2155 null)); 2156 } 2157 2158 try 2159 { 2160 final Control[] controlArray = new Control[controls.size()]; 2161 controls.toArray(controlArray); 2162 2163 final ExtendedRequest extendedRequest = new ExtendedRequest(oid, 2164 request.getValue(), controlArray); 2165 2166 final ExtendedResult extendedResult = 2167 handler.processExtendedOperation(this, messageID, extendedRequest); 2168 2169 return new LDAPMessage(messageID, 2170 new ExtendedResponseProtocolOp( 2171 extendedResult.getResultCode().intValue(), 2172 extendedResult.getMatchedDN(), 2173 extendedResult.getDiagnosticMessage(), 2174 Arrays.asList(extendedResult.getReferralURLs()), 2175 extendedResult.getOID(), extendedResult.getValue()), 2176 extendedResult.getResponseControls()); 2177 } 2178 catch (final Exception e) 2179 { 2180 Debug.debugException(e); 2181 2182 return new LDAPMessage(messageID, new ExtendedResponseProtocolOp( 2183 ResultCode.OTHER_INT_VALUE, null, 2184 ERR_MEM_HANDLER_EXTENDED_OP_FAILURE.get( 2185 StaticUtils.getExceptionMessage(e)), 2186 null, null, null)); 2187 } 2188 } 2189 } 2190 2191 2192 2193 /** 2194 * Processes the provided modify request. 2195 * <BR><BR> 2196 * This method may be used regardless of whether the server is listening for 2197 * client connections, and regardless of whether modify operations are allowed 2198 * in the server. 2199 * 2200 * @param modifyRequest The modify request to be processed. It must not be 2201 * {@code null}. 2202 * 2203 * @return The result of processing the modify operation. 2204 * 2205 * @throws LDAPException If the server rejects the modify request, or if a 2206 * problem is encountered while sending the request or 2207 * reading the response. 2208 */ 2209 @NotNull() 2210 public LDAPResult modify(@NotNull final ModifyRequest modifyRequest) 2211 throws LDAPException 2212 { 2213 final ArrayList<Control> requestControlList = 2214 new ArrayList<>(modifyRequest.getControlList()); 2215 requestControlList.add(new Control(OID_INTERNAL_OPERATION_REQUEST_CONTROL, 2216 false)); 2217 2218 final LDAPMessage responseMessage = processModifyRequest(1, 2219 new ModifyRequestProtocolOp(modifyRequest.getDN(), 2220 modifyRequest.getModifications()), 2221 requestControlList); 2222 2223 final ModifyResponseProtocolOp modifyResponse = 2224 responseMessage.getModifyResponseProtocolOp(); 2225 2226 final LDAPResult ldapResult = new LDAPResult(responseMessage.getMessageID(), 2227 ResultCode.valueOf(modifyResponse.getResultCode()), 2228 modifyResponse.getDiagnosticMessage(), modifyResponse.getMatchedDN(), 2229 modifyResponse.getReferralURLs(), responseMessage.getControls()); 2230 2231 switch (modifyResponse.getResultCode()) 2232 { 2233 case ResultCode.SUCCESS_INT_VALUE: 2234 case ResultCode.NO_OPERATION_INT_VALUE: 2235 return ldapResult; 2236 default: 2237 throw new LDAPException(ldapResult); 2238 } 2239 } 2240 2241 2242 2243 /** 2244 * Attempts to process the provided modify request. The attempt will fail if 2245 * any of the following conditions is true: 2246 * <UL> 2247 * <LI>There is a problem with any of the request controls.</LI> 2248 * <LI>The modify request contains a malformed target DN.</LI> 2249 * <LI>The target entry is the root DSE.</LI> 2250 * <LI>The target entry is the subschema subentry.</LI> 2251 * <LI>The target entry does not exist.</LI> 2252 * <LI>Any of the modifications cannot be applied to the entry.</LI> 2253 * <LI>If a schema was provided, and the entry violates any of the 2254 * constraints of that schema.</LI> 2255 * </UL> 2256 * 2257 * @param messageID The message ID of the LDAP message containing the modify 2258 * request. 2259 * @param request The modify request that was included in the LDAP message 2260 * that was received. 2261 * @param controls The set of controls included in the LDAP message. It 2262 * may be empty if there were no controls, but will not be 2263 * {@code null}. 2264 * 2265 * @return The {@link LDAPMessage} containing the response to send to the 2266 * client. The protocol op in the {@code LDAPMessage} must be an 2267 * {@code ModifyResponseProtocolOp}. 2268 */ 2269 @Override() 2270 @NotNull() 2271 public LDAPMessage processModifyRequest(final int messageID, 2272 @NotNull final ModifyRequestProtocolOp request, 2273 @NotNull final List<Control> controls) 2274 { 2275 synchronized (entryMap) 2276 { 2277 // Sleep before processing, if appropriate. 2278 sleepBeforeProcessing(); 2279 2280 // Process the provided request controls. 2281 final Map<String,Control> controlMap; 2282 try 2283 { 2284 controlMap = RequestControlPreProcessor.processControls( 2285 LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_REQUEST, controls); 2286 } 2287 catch (final LDAPException le) 2288 { 2289 Debug.debugException(le); 2290 return new LDAPMessage(messageID, new ModifyResponseProtocolOp( 2291 le.getResultCode().intValue(), null, le.getMessage(), null)); 2292 } 2293 final ArrayList<Control> responseControls = new ArrayList<>(1); 2294 2295 2296 // If this operation type is not allowed, then reject it. 2297 final boolean isInternalOp = 2298 controlMap.containsKey(OID_INTERNAL_OPERATION_REQUEST_CONTROL); 2299 if ((! isInternalOp) && 2300 (! config.getAllowedOperationTypes().contains(OperationType.MODIFY))) 2301 { 2302 return new LDAPMessage(messageID, new ModifyResponseProtocolOp( 2303 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null, 2304 ERR_MEM_HANDLER_MODIFY_NOT_ALLOWED.get(), null)); 2305 } 2306 2307 2308 // If this operation type requires authentication, then ensure that the 2309 // client is authenticated. 2310 if ((authenticatedDN.isNullDN() && 2311 config.getAuthenticationRequiredOperationTypes().contains( 2312 OperationType.MODIFY))) 2313 { 2314 return new LDAPMessage(messageID, new ModifyResponseProtocolOp( 2315 ResultCode.INSUFFICIENT_ACCESS_RIGHTS_INT_VALUE, null, 2316 ERR_MEM_HANDLER_MODIFY_REQUIRES_AUTH.get(), null)); 2317 } 2318 2319 2320 // See if this modify request is part of a transaction. If so, then 2321 // perform appropriate processing for it and return success immediately 2322 // without actually doing any further processing. 2323 try 2324 { 2325 final ASN1OctetString txnID = 2326 processTransactionRequest(messageID, request, controlMap); 2327 if (txnID != null) 2328 { 2329 return new LDAPMessage(messageID, new ModifyResponseProtocolOp( 2330 ResultCode.SUCCESS_INT_VALUE, null, 2331 INFO_MEM_HANDLER_OP_IN_TXN.get(txnID.stringValue()), null)); 2332 } 2333 } 2334 catch (final LDAPException le) 2335 { 2336 Debug.debugException(le); 2337 return new LDAPMessage(messageID, 2338 new ModifyResponseProtocolOp(le.getResultCode().intValue(), 2339 le.getMatchedDN(), le.getDiagnosticMessage(), 2340 StaticUtils.toList(le.getReferralURLs())), 2341 le.getResponseControls()); 2342 } 2343 2344 2345 // Get the parsed target DN. 2346 final DN dn; 2347 final Schema schema = schemaRef.get(); 2348 try 2349 { 2350 dn = new DN(request.getDN(), schema); 2351 } 2352 catch (final LDAPException le) 2353 { 2354 Debug.debugException(le); 2355 return new LDAPMessage(messageID, new ModifyResponseProtocolOp( 2356 ResultCode.INVALID_DN_SYNTAX_INT_VALUE, null, 2357 ERR_MEM_HANDLER_MOD_MALFORMED_DN.get(request.getDN(), 2358 le.getMessage()), 2359 null)); 2360 } 2361 2362 // See if the target entry or one of its superiors is a smart referral. 2363 if (! controlMap.containsKey( 2364 ManageDsaITRequestControl.MANAGE_DSA_IT_REQUEST_OID)) 2365 { 2366 final Entry referralEntry = findNearestReferral(dn); 2367 if (referralEntry != null) 2368 { 2369 return new LDAPMessage(messageID, new ModifyResponseProtocolOp( 2370 ResultCode.REFERRAL_INT_VALUE, referralEntry.getDN(), 2371 INFO_MEM_HANDLER_REFERRAL_ENCOUNTERED.get(), 2372 getReferralURLs(dn, referralEntry))); 2373 } 2374 } 2375 2376 // See if the target entry is the root DSE, the subschema subentry, or a 2377 // changelog entry. 2378 if (dn.isNullDN()) 2379 { 2380 return new LDAPMessage(messageID, new ModifyResponseProtocolOp( 2381 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null, 2382 ERR_MEM_HANDLER_MOD_ROOT_DSE.get(), null)); 2383 } 2384 else if (dn.equals(subschemaSubentryDN)) 2385 { 2386 try 2387 { 2388 validateSchemaMods(request); 2389 } 2390 catch (final LDAPException le) 2391 { 2392 return new LDAPMessage(messageID, new ModifyResponseProtocolOp( 2393 le.getResultCode().intValue(), le.getMatchedDN(), 2394 le.getMessage(), null)); 2395 } 2396 } 2397 else if (dn.isDescendantOf(changeLogBaseDN, true)) 2398 { 2399 return new LDAPMessage(messageID, new ModifyResponseProtocolOp( 2400 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null, 2401 ERR_MEM_HANDLER_MOD_CHANGELOG.get(request.getDN()), null)); 2402 } 2403 2404 // Get the target entry. If it does not exist, then fail. 2405 Entry entry = entryMap.get(dn); 2406 if (entry == null) 2407 { 2408 if (dn.equals(subschemaSubentryDN)) 2409 { 2410 entry = subschemaSubentryRef.get().duplicate(); 2411 } 2412 else 2413 { 2414 return new LDAPMessage(messageID, new ModifyResponseProtocolOp( 2415 ResultCode.NO_SUCH_OBJECT_INT_VALUE, getMatchedDNString(dn), 2416 ERR_MEM_HANDLER_MOD_NO_SUCH_ENTRY.get(request.getDN()), null)); 2417 } 2418 } 2419 2420 2421 // If any of the modifications target password attributes, then make sure 2422 // they are properly encoded. 2423 final ReadOnlyEntry readOnlyEntry = new ReadOnlyEntry(entry); 2424 final List<Modification> unencodedMods = request.getModifications(); 2425 final ArrayList<Modification> modifications = 2426 new ArrayList<>(unencodedMods.size()); 2427 for (final Modification m : unencodedMods) 2428 { 2429 try 2430 { 2431 modifications.add(encodeModificationPasswords(m, readOnlyEntry, 2432 unencodedMods)); 2433 } 2434 catch (final LDAPException le) 2435 { 2436 Debug.debugException(le); 2437 if (le.getResultCode().isClientSideResultCode()) 2438 { 2439 return new LDAPMessage(messageID, new ModifyResponseProtocolOp( 2440 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, le.getMatchedDN(), 2441 le.getMessage(), null)); 2442 } 2443 else 2444 { 2445 return new LDAPMessage(messageID, new ModifyResponseProtocolOp( 2446 le.getResultCode().intValue(), le.getMatchedDN(), 2447 le.getMessage(), null)); 2448 } 2449 } 2450 } 2451 2452 2453 // Attempt to apply the modifications to the entry. If successful, then a 2454 // copy of the entry will be returned with the modifications applied. 2455 final Entry modifiedEntry; 2456 try 2457 { 2458 modifiedEntry = Entry.applyModifications(entry, 2459 controlMap.containsKey( 2460 PermissiveModifyRequestControl.PERMISSIVE_MODIFY_REQUEST_OID), 2461 modifications); 2462 } 2463 catch (final LDAPException le) 2464 { 2465 Debug.debugException(le); 2466 return new LDAPMessage(messageID, new ModifyResponseProtocolOp( 2467 le.getResultCode().intValue(), null, 2468 ERR_MEM_HANDLER_MOD_FAILED.get(request.getDN(), le.getMessage()), 2469 null)); 2470 } 2471 2472 // If a schema was provided, use it to validate the resulting entry. 2473 // Also, ensure that no NO-USER-MODIFICATION attributes were targeted. 2474 final EntryValidator entryValidator = entryValidatorRef.get(); 2475 if (entryValidator != null) 2476 { 2477 final ArrayList<String> invalidReasons = new ArrayList<>(1); 2478 if (! entryValidator.entryIsValid(modifiedEntry, invalidReasons)) 2479 { 2480 return new LDAPMessage(messageID, new ModifyResponseProtocolOp( 2481 ResultCode.OBJECT_CLASS_VIOLATION_INT_VALUE, null, 2482 ERR_MEM_HANDLER_MOD_VIOLATES_SCHEMA.get(request.getDN(), 2483 StaticUtils.concatenateStrings(invalidReasons)), 2484 null)); 2485 } 2486 2487 for (final Modification m : modifications) 2488 { 2489 final Attribute a = m.getAttribute(); 2490 final String baseName = a.getBaseName(); 2491 final AttributeTypeDefinition at = schema.getAttributeType(baseName); 2492 if ((! isInternalOp) && (at != null) && at.isNoUserModification()) 2493 { 2494 return new LDAPMessage(messageID, new ModifyResponseProtocolOp( 2495 ResultCode.CONSTRAINT_VIOLATION_INT_VALUE, null, 2496 ERR_MEM_HANDLER_MOD_NO_USER_MOD.get(request.getDN(), 2497 a.getName()), null)); 2498 } 2499 } 2500 } 2501 2502 2503 // Perform the appropriate processing for the assertion and proxied 2504 // authorization controls. 2505 // Perform the appropriate processing for the assertion, pre-read, 2506 // post-read, and proxied authorization controls. 2507 final DN authzDN; 2508 try 2509 { 2510 handleAssertionRequestControl(controlMap, entry); 2511 2512 authzDN = handleProxiedAuthControl(controlMap); 2513 } 2514 catch (final LDAPException le) 2515 { 2516 Debug.debugException(le); 2517 return new LDAPMessage(messageID, new ModifyResponseProtocolOp( 2518 le.getResultCode().intValue(), null, le.getMessage(), null)); 2519 } 2520 2521 // Update modifiersName and modifyTimestamp. 2522 if (generateOperationalAttributes) 2523 { 2524 modifiedEntry.setAttribute(new Attribute("modifiersName", 2525 DistinguishedNameMatchingRule.getInstance(), 2526 authzDN.toString())); 2527 modifiedEntry.setAttribute(new Attribute("modifyTimestamp", 2528 GeneralizedTimeMatchingRule.getInstance(), 2529 StaticUtils.encodeGeneralizedTime(new Date()))); 2530 } 2531 2532 // Perform the appropriate processing for the pre-read and post-read 2533 // controls. 2534 final PreReadResponseControl preReadResponse = 2535 handlePreReadControl(controlMap, entry); 2536 if (preReadResponse != null) 2537 { 2538 responseControls.add(preReadResponse); 2539 } 2540 2541 final PostReadResponseControl postReadResponse = 2542 handlePostReadControl(controlMap, modifiedEntry); 2543 if (postReadResponse != null) 2544 { 2545 responseControls.add(postReadResponse); 2546 } 2547 2548 2549 // Replace the entry in the map and return a success result. 2550 if (dn.equals(subschemaSubentryDN)) 2551 { 2552 final Schema newSchema = new Schema(modifiedEntry); 2553 subschemaSubentryRef.set(new ReadOnlyEntry(modifiedEntry)); 2554 schemaRef.set(newSchema); 2555 entryValidatorRef.set(new EntryValidator(newSchema)); 2556 } 2557 else 2558 { 2559 entryMap.put(dn, new ReadOnlyEntry(modifiedEntry)); 2560 indexDelete(entry); 2561 indexAdd(modifiedEntry); 2562 } 2563 addChangeLogEntry(request, authzDN); 2564 return new LDAPMessage(messageID, 2565 new ModifyResponseProtocolOp(ResultCode.SUCCESS_INT_VALUE, null, 2566 null, null), 2567 responseControls); 2568 } 2569 } 2570 2571 2572 2573 /** 2574 * Checks to see if the provided modification targets a password attribute. 2575 * If so, then it makes sure that the modification is properly encoded. 2576 * 2577 * @param mod The modification being processed. 2578 * @param entry The entry being modified. 2579 * @param mods The full set of modifications. 2580 * 2581 * @return The encoded form of the provided modification if appropriate, or 2582 * the original modification if no encoding is needed. 2583 * 2584 * @throws LDAPException If a problem is encountered during processing. 2585 */ 2586 @NotNull() 2587 private Modification encodeModificationPasswords( 2588 @NotNull final Modification mod, 2589 @NotNull final ReadOnlyEntry entry, 2590 @NotNull final List<Modification> mods) 2591 throws LDAPException 2592 { 2593 // If the modification doesn't have any values, then we don't need to do 2594 // anything. 2595 final ASN1OctetString[] originalValues = mod.getRawValues(); 2596 if (originalValues.length == 0) 2597 { 2598 return mod; 2599 } 2600 2601 2602 // If no password attributes are defined, or if no password encoders are 2603 // defined, then we don't need to do anything. 2604 // If no password attributes are defined, then we don't need to do anything. 2605 if (extendedPasswordAttributes.isEmpty() || passwordEncoders.isEmpty()) 2606 { 2607 return mod; 2608 } 2609 2610 2611 // If the modification doesn't target a password attribute, then we don't 2612 // need to do anything. 2613 boolean isPasswordAttribute = false; 2614 for (final String passwordAttribute : extendedPasswordAttributes) 2615 { 2616 if (mod.getAttribute().getBaseName().equalsIgnoreCase(passwordAttribute)) 2617 { 2618 isPasswordAttribute = true; 2619 break; 2620 } 2621 } 2622 2623 if (! isPasswordAttribute) 2624 { 2625 return mod; 2626 } 2627 2628 2629 // Process the modification based on its modification type. 2630 final ASN1OctetString[] newValues = 2631 new ASN1OctetString[originalValues.length]; 2632 for (int i=0; i < originalValues.length; i++) 2633 { 2634 newValues[i] = encodeModValue(originalValues[i], mod, entry, mods); 2635 } 2636 2637 return new Modification(mod.getModificationType(), mod.getAttributeName(), 2638 newValues); 2639 } 2640 2641 2642 2643 /** 2644 * Encodes the provided modification value, if necessary. 2645 * 2646 * @param value The modification value being processed. 2647 * @param mod The modification being processed. 2648 * @param entry The unaltered form of the entry being modified. 2649 * @param mods The full set of modifications being processed. 2650 * 2651 * @return The encoded modification value, or the original value if no 2652 * encoding is necessary. 2653 * 2654 * @throws LDAPException If a problem is encountered during processing. 2655 */ 2656 @NotNull() 2657 private ASN1OctetString encodeModValue(@NotNull final ASN1OctetString value, 2658 @NotNull final Modification mod, 2659 @NotNull final ReadOnlyEntry entry, 2660 @NotNull final List<Modification> mods) 2661 throws LDAPException 2662 { 2663 // First, see if the password is already encoded. If so, then just return 2664 // it if that encoded representation looks valid. 2665 for (final InMemoryPasswordEncoder encoder : passwordEncoders) 2666 { 2667 if (encoder.passwordStartsWithPrefix(value)) 2668 { 2669 encoder.ensurePreEncodedPasswordAppearsValid(value, entry, mods); 2670 return value; 2671 } 2672 } 2673 2674 2675 // If the modification type is add or replace, then we should just encode 2676 // the password in accordance with the primary encoder. 2677 final ModificationType modificationType = mod.getModificationType(); 2678 if ((modificationType == ModificationType.ADD) || 2679 (modificationType == ModificationType.REPLACE)) 2680 { 2681 // If there is no primary password encoder, then just leave the value in 2682 // the clear. Otherwise, encode it with the primary encoder. 2683 if (primaryPasswordEncoder == null) 2684 { 2685 return value; 2686 } 2687 else 2688 { 2689 return primaryPasswordEncoder.encodePassword(value, entry, mods); 2690 } 2691 } 2692 2693 2694 // If the modification type is a delete, then we should see if the 2695 // clear-text value matches any of the values stored in the entry, whether 2696 // encoded or not. If the provided clear-text password matches an existing 2697 // encoded value, then we'll return the encoded value. If the clear-text 2698 // password matches an existing clear-text password, then we'll return that 2699 // clear-text password. But even if it doesn't match anything, then we'll 2700 // still return the clear-text password. 2701 if (modificationType == ModificationType.DELETE) 2702 { 2703 final Attribute existingAttribute = 2704 entry.getAttribute(mod.getAttributeName()); 2705 if (existingAttribute == null) 2706 { 2707 return value; 2708 } 2709 2710 for (final ASN1OctetString existingValue : 2711 existingAttribute.getRawValues()) 2712 { 2713 if (value.equalsIgnoreType(existingValue)) 2714 { 2715 return value; 2716 } 2717 2718 for (final InMemoryPasswordEncoder encoder : passwordEncoders) 2719 { 2720 if (encoder.clearPasswordMatchesEncodedPassword(value, existingValue, 2721 entry)) 2722 { 2723 return existingValue; 2724 } 2725 } 2726 } 2727 2728 return value; 2729 } 2730 2731 2732 // The only way we should be able to get here is for an increment 2733 // modification type, which is just stupid. But in that case, we'll just 2734 // return the value as-is. 2735 return value; 2736 } 2737 2738 2739 2740 /** 2741 * Validates a modify request targeting the server schema. Modifications to 2742 * attribute syntaxes and matching rules will not be allowed. Modifications 2743 * to other schema elements will only be allowed for add and delete 2744 * modification types, and adds will only be allowed with a valid syntax. 2745 * 2746 * @param request The modify request to validate. 2747 * 2748 * @throws LDAPException If a problem is encountered. 2749 */ 2750 private void validateSchemaMods( 2751 @NotNull final ModifyRequestProtocolOp request) 2752 throws LDAPException 2753 { 2754 // If there is no schema, then we won't allow modifications at all. 2755 if (schemaRef.get() == null) 2756 { 2757 throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM, 2758 ERR_MEM_HANDLER_MOD_SCHEMA.get(subschemaSubentryDN.toString())); 2759 } 2760 2761 2762 for (final Modification m : request.getModifications()) 2763 { 2764 // If the modification targets attribute syntaxes or matching rules, then 2765 // reject it. 2766 final String attrName = m.getAttributeName(); 2767 if (attrName.equalsIgnoreCase(Schema.ATTR_ATTRIBUTE_SYNTAX) || 2768 attrName.equalsIgnoreCase(Schema.ATTR_MATCHING_RULE)) 2769 { 2770 throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM, 2771 ERR_MEM_HANDLER_MOD_SCHEMA_DISALLOWED_ATTR.get(attrName)); 2772 } 2773 else if (attrName.equalsIgnoreCase(Schema.ATTR_ATTRIBUTE_TYPE)) 2774 { 2775 if (m.getModificationType() == ModificationType.ADD) 2776 { 2777 for (final String value : m.getValues()) 2778 { 2779 new AttributeTypeDefinition(value); 2780 } 2781 } 2782 else if (m.getModificationType() != ModificationType.DELETE) 2783 { 2784 throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM, 2785 ERR_MEM_HANDLER_MOD_SCHEMA_DISALLOWED_MOD_TYPE.get( 2786 m.getModificationType().getName(), attrName)); 2787 } 2788 } 2789 else if (attrName.equalsIgnoreCase(Schema.ATTR_OBJECT_CLASS)) 2790 { 2791 if (m.getModificationType() == ModificationType.ADD) 2792 { 2793 for (final String value : m.getValues()) 2794 { 2795 new ObjectClassDefinition(value); 2796 } 2797 } 2798 else if (m.getModificationType() != ModificationType.DELETE) 2799 { 2800 throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM, 2801 ERR_MEM_HANDLER_MOD_SCHEMA_DISALLOWED_MOD_TYPE.get( 2802 m.getModificationType().getName(), attrName)); 2803 } 2804 } 2805 else if (attrName.equalsIgnoreCase(Schema.ATTR_NAME_FORM)) 2806 { 2807 if (m.getModificationType() == ModificationType.ADD) 2808 { 2809 for (final String value : m.getValues()) 2810 { 2811 new NameFormDefinition(value); 2812 } 2813 } 2814 else if (m.getModificationType() != ModificationType.DELETE) 2815 { 2816 throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM, 2817 ERR_MEM_HANDLER_MOD_SCHEMA_DISALLOWED_MOD_TYPE.get( 2818 m.getModificationType().getName(), attrName)); 2819 } 2820 } 2821 else if (attrName.equalsIgnoreCase(Schema.ATTR_DIT_CONTENT_RULE)) 2822 { 2823 if (m.getModificationType() == ModificationType.ADD) 2824 { 2825 for (final String value : m.getValues()) 2826 { 2827 new DITContentRuleDefinition(value); 2828 } 2829 } 2830 else if (m.getModificationType() != ModificationType.DELETE) 2831 { 2832 throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM, 2833 ERR_MEM_HANDLER_MOD_SCHEMA_DISALLOWED_MOD_TYPE.get( 2834 m.getModificationType().getName(), attrName)); 2835 } 2836 } 2837 else if (attrName.equalsIgnoreCase(Schema.ATTR_DIT_STRUCTURE_RULE)) 2838 { 2839 if (m.getModificationType() == ModificationType.ADD) 2840 { 2841 for (final String value : m.getValues()) 2842 { 2843 new DITStructureRuleDefinition(value); 2844 } 2845 } 2846 else if (m.getModificationType() != ModificationType.DELETE) 2847 { 2848 throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM, 2849 ERR_MEM_HANDLER_MOD_SCHEMA_DISALLOWED_MOD_TYPE.get( 2850 m.getModificationType().getName(), attrName)); 2851 } 2852 } 2853 else if (attrName.equalsIgnoreCase(Schema.ATTR_MATCHING_RULE_USE)) 2854 { 2855 if (m.getModificationType() == ModificationType.ADD) 2856 { 2857 for (final String value : m.getValues()) 2858 { 2859 new MatchingRuleUseDefinition(value); 2860 } 2861 } 2862 else if (m.getModificationType() != ModificationType.DELETE) 2863 { 2864 throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM, 2865 ERR_MEM_HANDLER_MOD_SCHEMA_DISALLOWED_MOD_TYPE.get( 2866 m.getModificationType().getName(), attrName)); 2867 } 2868 } 2869 } 2870 } 2871 2872 2873 2874 /** 2875 * Processes the provided modify DN request. 2876 * <BR><BR> 2877 * This method may be used regardless of whether the server is listening for 2878 * client connections, and regardless of whether modify DN operations are 2879 * allowed in the server. 2880 * 2881 * @param modifyDNRequest The modify DN request to be processed. It must 2882 * not be {@code null}. 2883 * 2884 * @return The result of processing the modify DN operation. 2885 * 2886 * @throws LDAPException If the server rejects the modify DN request, or if 2887 * a problem is encountered while sending the request 2888 * or reading the response. 2889 */ 2890 @NotNull() 2891 public LDAPResult modifyDN(@NotNull final ModifyDNRequest modifyDNRequest) 2892 throws LDAPException 2893 { 2894 final ArrayList<Control> requestControlList = 2895 new ArrayList<>(modifyDNRequest.getControlList()); 2896 requestControlList.add(new Control(OID_INTERNAL_OPERATION_REQUEST_CONTROL, 2897 false)); 2898 2899 final LDAPMessage responseMessage = processModifyDNRequest( 2900 1, new ModifyDNRequestProtocolOp(modifyDNRequest.getDN(), 2901 modifyDNRequest.getNewRDN(), modifyDNRequest.deleteOldRDN(), 2902 modifyDNRequest.getNewSuperiorDN()), 2903 requestControlList); 2904 2905 final ModifyDNResponseProtocolOp modifyDNResponse = 2906 responseMessage.getModifyDNResponseProtocolOp(); 2907 2908 final LDAPResult ldapResult = new LDAPResult(responseMessage.getMessageID(), 2909 ResultCode.valueOf(modifyDNResponse.getResultCode()), 2910 modifyDNResponse.getDiagnosticMessage(), 2911 modifyDNResponse.getMatchedDN(), modifyDNResponse.getReferralURLs(), 2912 responseMessage.getControls()); 2913 2914 switch (modifyDNResponse.getResultCode()) 2915 { 2916 case ResultCode.SUCCESS_INT_VALUE: 2917 case ResultCode.NO_OPERATION_INT_VALUE: 2918 return ldapResult; 2919 default: 2920 throw new LDAPException(ldapResult); 2921 } 2922 } 2923 2924 2925 2926 /** 2927 * Attempts to process the provided modify DN request. The attempt will fail 2928 * if any of the following conditions is true: 2929 * <UL> 2930 * <LI>There is a problem with any of the request controls.</LI> 2931 * <LI>The modify DN request contains a malformed target DN, new RDN, or 2932 * new superior DN.</LI> 2933 * <LI>The original or new DN is that of the root DSE.</LI> 2934 * <LI>The original or new DN is that of the subschema subentry.</LI> 2935 * <LI>The new DN of the entry would conflict with the DN of an existing 2936 * entry.</LI> 2937 * <LI>The new DN of the entry would exist outside the set of defined 2938 * base DNs.</LI> 2939 * <LI>The new DN of the entry is not a defined base DN and does not exist 2940 * immediately below an existing entry.</LI> 2941 * </UL> 2942 * 2943 * @param messageID The message ID of the LDAP message containing the modify 2944 * DN request. 2945 * @param request The modify DN request that was included in the LDAP 2946 * message that was received. 2947 * @param controls The set of controls included in the LDAP message. It 2948 * may be empty if there were no controls, but will not be 2949 * {@code null}. 2950 * 2951 * @return The {@link LDAPMessage} containing the response to send to the 2952 * client. The protocol op in the {@code LDAPMessage} must be an 2953 * {@code ModifyDNResponseProtocolOp}. 2954 */ 2955 @Override() 2956 @NotNull() 2957 public LDAPMessage processModifyDNRequest(final int messageID, 2958 @NotNull final ModifyDNRequestProtocolOp request, 2959 @NotNull final List<Control> controls) 2960 { 2961 synchronized (entryMap) 2962 { 2963 // Sleep before processing, if appropriate. 2964 sleepBeforeProcessing(); 2965 2966 // Process the provided request controls. 2967 final Map<String,Control> controlMap; 2968 try 2969 { 2970 controlMap = RequestControlPreProcessor.processControls( 2971 LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_DN_REQUEST, controls); 2972 } 2973 catch (final LDAPException le) 2974 { 2975 Debug.debugException(le); 2976 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp( 2977 le.getResultCode().intValue(), null, le.getMessage(), null)); 2978 } 2979 final ArrayList<Control> responseControls = new ArrayList<>(1); 2980 2981 2982 // If this operation type is not allowed, then reject it. 2983 final boolean isInternalOp = 2984 controlMap.containsKey(OID_INTERNAL_OPERATION_REQUEST_CONTROL); 2985 if ((! isInternalOp) && 2986 (! config.getAllowedOperationTypes().contains( 2987 OperationType.MODIFY_DN))) 2988 { 2989 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp( 2990 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null, 2991 ERR_MEM_HANDLER_MODIFY_DN_NOT_ALLOWED.get(), null)); 2992 } 2993 2994 2995 // If this operation type requires authentication, then ensure that the 2996 // client is authenticated. 2997 if ((authenticatedDN.isNullDN() && 2998 config.getAuthenticationRequiredOperationTypes().contains( 2999 OperationType.MODIFY_DN))) 3000 { 3001 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp( 3002 ResultCode.INSUFFICIENT_ACCESS_RIGHTS_INT_VALUE, null, 3003 ERR_MEM_HANDLER_MODIFY_DN_REQUIRES_AUTH.get(), null)); 3004 } 3005 3006 3007 // See if this modify DN request is part of a transaction. If so, then 3008 // perform appropriate processing for it and return success immediately 3009 // without actually doing any further processing. 3010 try 3011 { 3012 final ASN1OctetString txnID = 3013 processTransactionRequest(messageID, request, controlMap); 3014 if (txnID != null) 3015 { 3016 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp( 3017 ResultCode.SUCCESS_INT_VALUE, null, 3018 INFO_MEM_HANDLER_OP_IN_TXN.get(txnID.stringValue()), null)); 3019 } 3020 } 3021 catch (final LDAPException le) 3022 { 3023 Debug.debugException(le); 3024 return new LDAPMessage(messageID, 3025 new ModifyDNResponseProtocolOp(le.getResultCode().intValue(), 3026 le.getMatchedDN(), le.getDiagnosticMessage(), 3027 StaticUtils.toList(le.getReferralURLs())), 3028 le.getResponseControls()); 3029 } 3030 3031 3032 // Get the parsed target DN, new RDN, and new superior DN values. 3033 final DN dn; 3034 final Schema schema = schemaRef.get(); 3035 try 3036 { 3037 dn = new DN(request.getDN(), schema); 3038 } 3039 catch (final LDAPException le) 3040 { 3041 Debug.debugException(le); 3042 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp( 3043 ResultCode.INVALID_DN_SYNTAX_INT_VALUE, null, 3044 ERR_MEM_HANDLER_MOD_DN_MALFORMED_DN.get(request.getDN(), 3045 le.getMessage()), 3046 null)); 3047 } 3048 3049 final RDN newRDN; 3050 try 3051 { 3052 newRDN = new RDN(request.getNewRDN(), schema); 3053 } 3054 catch (final LDAPException le) 3055 { 3056 Debug.debugException(le); 3057 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp( 3058 ResultCode.INVALID_DN_SYNTAX_INT_VALUE, null, 3059 ERR_MEM_HANDLER_MOD_DN_MALFORMED_NEW_RDN.get(request.getDN(), 3060 request.getNewRDN(), le.getMessage()), 3061 null)); 3062 } 3063 3064 final DN newSuperiorDN; 3065 final String newSuperiorString = request.getNewSuperiorDN(); 3066 if (newSuperiorString == null) 3067 { 3068 newSuperiorDN = null; 3069 } 3070 else 3071 { 3072 try 3073 { 3074 newSuperiorDN = new DN(newSuperiorString, schema); 3075 } 3076 catch (final LDAPException le) 3077 { 3078 Debug.debugException(le); 3079 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp( 3080 ResultCode.INVALID_DN_SYNTAX_INT_VALUE, null, 3081 ERR_MEM_HANDLER_MOD_DN_MALFORMED_NEW_SUPERIOR.get( 3082 request.getDN(), request.getNewSuperiorDN(), 3083 le.getMessage()), 3084 null)); 3085 } 3086 } 3087 3088 // See if the target entry or one of its superiors is a smart referral. 3089 if (! controlMap.containsKey( 3090 ManageDsaITRequestControl.MANAGE_DSA_IT_REQUEST_OID)) 3091 { 3092 final Entry referralEntry = findNearestReferral(dn); 3093 if (referralEntry != null) 3094 { 3095 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp( 3096 ResultCode.REFERRAL_INT_VALUE, referralEntry.getDN(), 3097 INFO_MEM_HANDLER_REFERRAL_ENCOUNTERED.get(), 3098 getReferralURLs(dn, referralEntry))); 3099 } 3100 } 3101 3102 // See if the target is the root DSE, the subschema subentry, or a 3103 // changelog entry. 3104 if (dn.isNullDN()) 3105 { 3106 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp( 3107 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null, 3108 ERR_MEM_HANDLER_MOD_DN_ROOT_DSE.get(), null)); 3109 } 3110 else if (dn.equals(subschemaSubentryDN)) 3111 { 3112 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp( 3113 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null, 3114 ERR_MEM_HANDLER_MOD_DN_SOURCE_IS_SCHEMA.get(), null)); 3115 } 3116 else if (dn.isDescendantOf(changeLogBaseDN, true)) 3117 { 3118 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp( 3119 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null, 3120 ERR_MEM_HANDLER_MOD_DN_SOURCE_IS_CHANGELOG.get(), null)); 3121 } 3122 3123 // Construct the new DN. 3124 final DN newDN; 3125 if (newSuperiorDN == null) 3126 { 3127 final DN originalParent = dn.getParent(); 3128 if (originalParent == null) 3129 { 3130 newDN = new DN(newRDN); 3131 } 3132 else 3133 { 3134 newDN = new DN(newRDN, originalParent); 3135 } 3136 } 3137 else 3138 { 3139 newDN = new DN(newRDN, newSuperiorDN); 3140 } 3141 3142 // If the new DN matches the old DN, then fail. 3143 if (newDN.equals(dn)) 3144 { 3145 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp( 3146 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null, 3147 ERR_MEM_HANDLER_MOD_DN_NEW_DN_SAME_AS_OLD.get(request.getDN()), 3148 null)); 3149 } 3150 3151 // If the new DN is below a smart referral, then fail. 3152 if (! controlMap.containsKey( 3153 ManageDsaITRequestControl.MANAGE_DSA_IT_REQUEST_OID)) 3154 { 3155 final Entry referralEntry = findNearestReferral(newDN); 3156 if (referralEntry != null) 3157 { 3158 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp( 3159 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, referralEntry.getDN(), 3160 ERR_MEM_HANDLER_MOD_DN_NEW_DN_BELOW_REFERRAL.get(request.getDN(), 3161 referralEntry.getDN().toString(), newDN.toString()), 3162 null)); 3163 } 3164 } 3165 3166 // If the target entry doesn't exist, then fail. 3167 final Entry originalEntry = entryMap.get(dn); 3168 if (originalEntry == null) 3169 { 3170 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp( 3171 ResultCode.NO_SUCH_OBJECT_INT_VALUE, getMatchedDNString(dn), 3172 ERR_MEM_HANDLER_MOD_DN_NO_SUCH_ENTRY.get(request.getDN()), null)); 3173 } 3174 3175 // If the new DN matches the subschema subentry DN, then fail. 3176 if (newDN.equals(subschemaSubentryDN)) 3177 { 3178 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp( 3179 ResultCode.ENTRY_ALREADY_EXISTS_INT_VALUE, null, 3180 ERR_MEM_HANDLER_MOD_DN_TARGET_IS_SCHEMA.get(request.getDN(), 3181 newDN.toString()), 3182 null)); 3183 } 3184 3185 // If the new DN is at or below the changelog base DN, then fail. 3186 if (newDN.isDescendantOf(changeLogBaseDN, true)) 3187 { 3188 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp( 3189 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null, 3190 ERR_MEM_HANDLER_MOD_DN_TARGET_IS_CHANGELOG.get(request.getDN(), 3191 newDN.toString()), 3192 null)); 3193 } 3194 3195 // If the new DN already exists, then fail. 3196 if (entryMap.containsKey(newDN)) 3197 { 3198 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp( 3199 ResultCode.ENTRY_ALREADY_EXISTS_INT_VALUE, null, 3200 ERR_MEM_HANDLER_MOD_DN_TARGET_ALREADY_EXISTS.get(request.getDN(), 3201 newDN.toString()), 3202 null)); 3203 } 3204 3205 // If the new DN is not a base DN and its parent does not exist, then 3206 // fail. 3207 if (baseDNs.contains(newDN)) 3208 { 3209 // The modify DN can be processed. 3210 } 3211 else 3212 { 3213 final DN newParent = newDN.getParent(); 3214 if ((newParent != null) && entryMap.containsKey(newParent)) 3215 { 3216 // The modify DN can be processed. 3217 } 3218 else 3219 { 3220 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp( 3221 ResultCode.NO_SUCH_OBJECT_INT_VALUE, getMatchedDNString(newDN), 3222 ERR_MEM_HANDLER_MOD_DN_PARENT_DOESNT_EXIST.get(request.getDN(), 3223 newDN.toString()), 3224 null)); 3225 } 3226 } 3227 3228 // Create a copy of the entry and update it to reflect the new DN (with 3229 // attribute value changes). 3230 final RDN originalRDN = dn.getRDN(); 3231 final Entry updatedEntry = originalEntry.duplicate(); 3232 updatedEntry.setDN(newDN); 3233 if (request.deleteOldRDN()) 3234 { 3235 final String[] oldRDNNames = originalRDN.getAttributeNames(); 3236 final byte[][] oldRDNValues = originalRDN.getByteArrayAttributeValues(); 3237 for (int i=0; i < oldRDNNames.length; i++) 3238 { 3239 updatedEntry.removeAttributeValue(oldRDNNames[i], oldRDNValues[i]); 3240 } 3241 } 3242 3243 final String[] newRDNNames = newRDN.getAttributeNames(); 3244 final byte[][] newRDNValues = newRDN.getByteArrayAttributeValues(); 3245 for (int i=0; i < newRDNNames.length; i++) 3246 { 3247 final MatchingRule matchingRule = 3248 MatchingRule.selectEqualityMatchingRule(newRDNNames[i], schema); 3249 updatedEntry.addAttribute(new Attribute(newRDNNames[i], matchingRule, 3250 newRDNValues[i])); 3251 } 3252 3253 // If a schema was provided, then make sure the updated entry conforms to 3254 // the schema. Also, reject the attempt if any of the new RDN attributes 3255 // is marked with NO-USER-MODIFICATION. 3256 final EntryValidator entryValidator = entryValidatorRef.get(); 3257 if (entryValidator != null) 3258 { 3259 final ArrayList<String> invalidReasons = new ArrayList<>(1); 3260 if (! entryValidator.entryIsValid(updatedEntry, invalidReasons)) 3261 { 3262 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp( 3263 ResultCode.OBJECT_CLASS_VIOLATION_INT_VALUE, null, 3264 ERR_MEM_HANDLER_MOD_DN_VIOLATES_SCHEMA.get(request.getDN(), 3265 StaticUtils.concatenateStrings(invalidReasons)), 3266 null)); 3267 } 3268 3269 final String[] oldRDNNames = originalRDN.getAttributeNames(); 3270 for (int i=0; i < oldRDNNames.length; i++) 3271 { 3272 final String name = oldRDNNames[i]; 3273 final AttributeTypeDefinition at = schema.getAttributeType(name); 3274 if ((! isInternalOp) && (at != null) && at.isNoUserModification()) 3275 { 3276 final byte[] value = originalRDN.getByteArrayAttributeValues()[i]; 3277 if (! updatedEntry.hasAttributeValue(name, value)) 3278 { 3279 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp( 3280 ResultCode.CONSTRAINT_VIOLATION_INT_VALUE, null, 3281 ERR_MEM_HANDLER_MOD_DN_NO_USER_MOD.get(request.getDN(), 3282 name), null)); 3283 } 3284 } 3285 } 3286 3287 for (int i=0; i < newRDNNames.length; i++) 3288 { 3289 final String name = newRDNNames[i]; 3290 final AttributeTypeDefinition at = schema.getAttributeType(name); 3291 if ((! isInternalOp) && (at != null) && at.isNoUserModification()) 3292 { 3293 final byte[] value = newRDN.getByteArrayAttributeValues()[i]; 3294 if (! originalEntry.hasAttributeValue(name, value)) 3295 { 3296 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp( 3297 ResultCode.CONSTRAINT_VIOLATION_INT_VALUE, null, 3298 ERR_MEM_HANDLER_MOD_DN_NO_USER_MOD.get(request.getDN(), 3299 name), null)); 3300 } 3301 } 3302 } 3303 } 3304 3305 // Perform the appropriate processing for the assertion and proxied 3306 // authorization controls 3307 final DN authzDN; 3308 try 3309 { 3310 handleAssertionRequestControl(controlMap, originalEntry); 3311 3312 authzDN = handleProxiedAuthControl(controlMap); 3313 } 3314 catch (final LDAPException le) 3315 { 3316 Debug.debugException(le); 3317 return new LDAPMessage(messageID, new ModifyDNResponseProtocolOp( 3318 le.getResultCode().intValue(), null, le.getMessage(), null)); 3319 } 3320 3321 // Update the modifiersName, modifyTimestamp, and entryDN operational 3322 // attributes. 3323 if (generateOperationalAttributes) 3324 { 3325 updatedEntry.setAttribute(new Attribute("modifiersName", 3326 DistinguishedNameMatchingRule.getInstance(), 3327 authzDN.toString())); 3328 updatedEntry.setAttribute(new Attribute("modifyTimestamp", 3329 GeneralizedTimeMatchingRule.getInstance(), 3330 StaticUtils.encodeGeneralizedTime(new Date()))); 3331 updatedEntry.setAttribute(new Attribute("entryDN", 3332 DistinguishedNameMatchingRule.getInstance(), 3333 newDN.toNormalizedString())); 3334 } 3335 3336 // Perform the appropriate processing for the pre-read and post-read 3337 // controls. 3338 final PreReadResponseControl preReadResponse = 3339 handlePreReadControl(controlMap, originalEntry); 3340 if (preReadResponse != null) 3341 { 3342 responseControls.add(preReadResponse); 3343 } 3344 3345 final PostReadResponseControl postReadResponse = 3346 handlePostReadControl(controlMap, updatedEntry); 3347 if (postReadResponse != null) 3348 { 3349 responseControls.add(postReadResponse); 3350 } 3351 3352 // Remove the old entry and add the new one. 3353 entryMap.remove(dn); 3354 entryMap.put(newDN, new ReadOnlyEntry(updatedEntry)); 3355 indexDelete(originalEntry); 3356 indexAdd(updatedEntry); 3357 3358 // If the target entry had any subordinates, then rename them as well. 3359 final RDN[] oldDNComps = dn.getRDNs(); 3360 final RDN[] newDNComps = newDN.getRDNs(); 3361 final Set<DN> dnSet = new LinkedHashSet<>(entryMap.keySet()); 3362 for (final DN mapEntryDN : dnSet) 3363 { 3364 if (mapEntryDN.isDescendantOf(dn, false)) 3365 { 3366 final Entry o = entryMap.remove(mapEntryDN); 3367 final Entry e = o.duplicate(); 3368 3369 final RDN[] oldMapEntryComps = mapEntryDN.getRDNs(); 3370 final int compsToSave = oldMapEntryComps.length - oldDNComps.length; 3371 3372 final RDN[] newMapEntryComps = 3373 new RDN[compsToSave + newDNComps.length]; 3374 System.arraycopy(oldMapEntryComps, 0, newMapEntryComps, 0, 3375 compsToSave); 3376 System.arraycopy(newDNComps, 0, newMapEntryComps, compsToSave, 3377 newDNComps.length); 3378 3379 final DN newMapEntryDN = new DN(newMapEntryComps); 3380 e.setDN(newMapEntryDN); 3381 if (generateOperationalAttributes) 3382 { 3383 e.setAttribute(new Attribute("entryDN", 3384 DistinguishedNameMatchingRule.getInstance(), 3385 newMapEntryDN.toNormalizedString())); 3386 } 3387 entryMap.put(newMapEntryDN, new ReadOnlyEntry(e)); 3388 indexDelete(o); 3389 indexAdd(e); 3390 handleReferentialIntegrityModifyDN(mapEntryDN, newMapEntryDN); 3391 } 3392 } 3393 3394 addChangeLogEntry(request, authzDN); 3395 handleReferentialIntegrityModifyDN(dn, newDN); 3396 return new LDAPMessage(messageID, 3397 new ModifyDNResponseProtocolOp(ResultCode.SUCCESS_INT_VALUE, null, 3398 null, null), 3399 responseControls); 3400 } 3401 } 3402 3403 3404 3405 /** 3406 * Handles any appropriate referential integrity processing for a modify DN 3407 * operation. 3408 * 3409 * @param oldDN The old DN for the entry. 3410 * @param newDN The new DN for the entry. 3411 */ 3412 private void handleReferentialIntegrityModifyDN(@NotNull final DN oldDN, 3413 @NotNull final DN newDN) 3414 { 3415 if (referentialIntegrityAttributes.isEmpty()) 3416 { 3417 return; 3418 } 3419 3420 final ArrayList<DN> entryDNs = new ArrayList<>(entryMap.keySet()); 3421 for (final DN mapDN : entryDNs) 3422 { 3423 final ReadOnlyEntry e = entryMap.get(mapDN); 3424 3425 boolean referenceFound = false; 3426 final Schema schema = schemaRef.get(); 3427 for (final String attrName : referentialIntegrityAttributes) 3428 { 3429 final Attribute a = e.getAttribute(attrName, schema); 3430 if ((a != null) && 3431 a.hasValue(oldDN.toNormalizedString(), 3432 DistinguishedNameMatchingRule.getInstance())) 3433 { 3434 referenceFound = true; 3435 break; 3436 } 3437 } 3438 3439 if (referenceFound) 3440 { 3441 final Entry copy = e.duplicate(); 3442 for (final String attrName : referentialIntegrityAttributes) 3443 { 3444 if (copy.removeAttributeValue(attrName, oldDN.toNormalizedString(), 3445 DistinguishedNameMatchingRule.getInstance())) 3446 { 3447 copy.addAttribute(attrName, newDN.toString()); 3448 } 3449 } 3450 entryMap.put(mapDN, new ReadOnlyEntry(copy)); 3451 indexDelete(e); 3452 indexAdd(copy); 3453 } 3454 } 3455 } 3456 3457 3458 3459 /** 3460 * Attempts to process the provided search request. The attempt will fail 3461 * if any of the following conditions is true: 3462 * <UL> 3463 * <LI>There is a problem with any of the request controls.</LI> 3464 * <LI>The modify DN request contains a malformed target DN, new RDN, or 3465 * new superior DN.</LI> 3466 * <LI>The new DN of the entry would conflict with the DN of an existing 3467 * entry.</LI> 3468 * <LI>The new DN of the entry would exist outside the set of defined 3469 * base DNs.</LI> 3470 * <LI>The new DN of the entry is not a defined base DN and does not exist 3471 * immediately below an existing entry.</LI> 3472 * </UL> 3473 * 3474 * @param messageID The message ID of the LDAP message containing the search 3475 * request. 3476 * @param request The search request that was included in the LDAP message 3477 * that was received. 3478 * @param controls The set of controls included in the LDAP message. It 3479 * may be empty if there were no controls, but will not be 3480 * {@code null}. 3481 * 3482 * @return The {@link LDAPMessage} containing the response to send to the 3483 * client. The protocol op in the {@code LDAPMessage} must be an 3484 * {@code SearchResultDoneProtocolOp}. 3485 */ 3486 @Override() 3487 @NotNull() 3488 public LDAPMessage processSearchRequest(final int messageID, 3489 @NotNull final SearchRequestProtocolOp request, 3490 @NotNull final List<Control> controls) 3491 { 3492 synchronized (entryMap) 3493 { 3494 final List<SearchResultEntry> entryList = 3495 new ArrayList<>(entryMap.size()); 3496 final List<SearchResultReference> referenceList = 3497 new ArrayList<>(entryMap.size()); 3498 3499 final LDAPMessage returnMessage = processSearchRequest(messageID, request, 3500 controls, entryList, referenceList); 3501 3502 for (final SearchResultEntry e : entryList) 3503 { 3504 try 3505 { 3506 connection.sendSearchResultEntry(messageID, e, e.getControls()); 3507 } 3508 catch (final LDAPException le) 3509 { 3510 Debug.debugException(le); 3511 return new LDAPMessage(messageID, 3512 new SearchResultDoneProtocolOp(le.getResultCode().intValue(), 3513 le.getMatchedDN(), le.getDiagnosticMessage(), 3514 StaticUtils.toList(le.getReferralURLs())), 3515 le.getResponseControls()); 3516 } 3517 } 3518 3519 for (final SearchResultReference r : referenceList) 3520 { 3521 try 3522 { 3523 connection.sendSearchResultReference(messageID, 3524 new SearchResultReferenceProtocolOp( 3525 StaticUtils.toList(r.getReferralURLs())), 3526 r.getControls()); 3527 } 3528 catch (final LDAPException le) 3529 { 3530 Debug.debugException(le); 3531 return new LDAPMessage(messageID, 3532 new SearchResultDoneProtocolOp(le.getResultCode().intValue(), 3533 le.getMatchedDN(), le.getDiagnosticMessage(), 3534 StaticUtils.toList(le.getReferralURLs())), 3535 le.getResponseControls()); 3536 } 3537 } 3538 3539 return returnMessage; 3540 } 3541 } 3542 3543 3544 3545 /** 3546 * Attempts to process the provided search request. The attempt will fail 3547 * if any of the following conditions is true: 3548 * <UL> 3549 * <LI>There is a problem with any of the request controls.</LI> 3550 * <LI>The modify DN request contains a malformed target DN, new RDN, or 3551 * new superior DN.</LI> 3552 * <LI>The new DN of the entry would conflict with the DN of an existing 3553 * entry.</LI> 3554 * <LI>The new DN of the entry would exist outside the set of defined 3555 * base DNs.</LI> 3556 * <LI>The new DN of the entry is not a defined base DN and does not exist 3557 * immediately below an existing entry.</LI> 3558 * </UL> 3559 * 3560 * @param messageID The message ID of the LDAP message containing the 3561 * search request. 3562 * @param request The search request that was included in the LDAP 3563 * message that was received. 3564 * @param controls The set of controls included in the LDAP message. 3565 * It may be empty if there were no controls, but will 3566 * not be {@code null}. 3567 * @param entryList A list to which to add search result entries 3568 * intended for return to the client. It must not be 3569 * {@code null}. 3570 * @param referenceList A list to which to add search result references 3571 * intended for return to the client. It must not be 3572 * {@code null}. 3573 * 3574 * @return The {@link LDAPMessage} containing the response to send to the 3575 * client. The protocol op in the {@code LDAPMessage} must be an 3576 * {@code SearchResultDoneProtocolOp}. 3577 */ 3578 @NotNull() 3579 LDAPMessage processSearchRequest(final int messageID, 3580 @NotNull final SearchRequestProtocolOp request, 3581 @NotNull final List<Control> controls, 3582 @NotNull final List<SearchResultEntry> entryList, 3583 @NotNull final List<SearchResultReference> referenceList) 3584 { 3585 synchronized (entryMap) 3586 { 3587 // Sleep before processing, if appropriate. 3588 final long processingStartTime = System.currentTimeMillis(); 3589 sleepBeforeProcessing(); 3590 3591 // Look at the filter and see if it contains any unsupported elements. 3592 try 3593 { 3594 ensureFilterSupported(request.getFilter()); 3595 } 3596 catch (final LDAPException le) 3597 { 3598 Debug.debugException(le); 3599 return new LDAPMessage(messageID, new SearchResultDoneProtocolOp( 3600 le.getResultCode().intValue(), null, le.getMessage(), null)); 3601 } 3602 3603 // Look at the time limit for the search request and see if sleeping 3604 // would have caused us to exceed that time limit. It's extremely 3605 // unlikely that any search in the in-memory directory server would take 3606 // a second or more to complete, and that's the minimum time limit that 3607 // can be requested, so there's no need to check the time limit in most 3608 // cases. However, someone may want to force a "time limit exceeded" 3609 // response by configuring a delay that is greater than the requested time 3610 // limit, so we should check now to see if that's been exceeded. 3611 final long timeLimitMillis = 1000L * request.getTimeLimit(); 3612 if (timeLimitMillis > 0L) 3613 { 3614 final long timeLimitExpirationTime = 3615 processingStartTime + timeLimitMillis; 3616 if (System.currentTimeMillis() >= timeLimitExpirationTime) 3617 { 3618 return new LDAPMessage(messageID, new SearchResultDoneProtocolOp( 3619 ResultCode.TIME_LIMIT_EXCEEDED_INT_VALUE, null, 3620 ERR_MEM_HANDLER_TIME_LIMIT_EXCEEDED.get(), null)); 3621 } 3622 } 3623 3624 // Process the provided request controls. 3625 final Map<String,Control> controlMap; 3626 try 3627 { 3628 controlMap = RequestControlPreProcessor.processControls( 3629 LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_REQUEST, controls); 3630 } 3631 catch (final LDAPException le) 3632 { 3633 Debug.debugException(le); 3634 return new LDAPMessage(messageID, new SearchResultDoneProtocolOp( 3635 le.getResultCode().intValue(), null, le.getMessage(), null)); 3636 } 3637 final ArrayList<Control> responseControls = new ArrayList<>(1); 3638 3639 3640 // If this operation type is not allowed, then reject it. 3641 final boolean isInternalOp = 3642 controlMap.containsKey(OID_INTERNAL_OPERATION_REQUEST_CONTROL); 3643 if ((! isInternalOp) && 3644 (! config.getAllowedOperationTypes().contains(OperationType.SEARCH))) 3645 { 3646 return new LDAPMessage(messageID, new SearchResultDoneProtocolOp( 3647 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null, 3648 ERR_MEM_HANDLER_SEARCH_NOT_ALLOWED.get(), null)); 3649 } 3650 3651 3652 // If this operation type requires authentication, then ensure that the 3653 // client is authenticated. 3654 if ((authenticatedDN.isNullDN() && 3655 config.getAuthenticationRequiredOperationTypes().contains( 3656 OperationType.SEARCH))) 3657 { 3658 return new LDAPMessage(messageID, new SearchResultDoneProtocolOp( 3659 ResultCode.INSUFFICIENT_ACCESS_RIGHTS_INT_VALUE, null, 3660 ERR_MEM_HANDLER_SEARCH_REQUIRES_AUTH.get(), null)); 3661 } 3662 3663 3664 // Get the parsed base DN. 3665 final DN baseDN; 3666 final Schema schema = schemaRef.get(); 3667 try 3668 { 3669 baseDN = new DN(request.getBaseDN(), schema); 3670 } 3671 catch (final LDAPException le) 3672 { 3673 Debug.debugException(le); 3674 return new LDAPMessage(messageID, new SearchResultDoneProtocolOp( 3675 ResultCode.INVALID_DN_SYNTAX_INT_VALUE, null, 3676 ERR_MEM_HANDLER_SEARCH_MALFORMED_BASE.get(request.getBaseDN(), 3677 le.getMessage()), 3678 null)); 3679 } 3680 3681 // See if the search base or one of its superiors is a smart referral. 3682 final boolean hasManageDsaIT = controlMap.containsKey( 3683 ManageDsaITRequestControl.MANAGE_DSA_IT_REQUEST_OID); 3684 if (! hasManageDsaIT) 3685 { 3686 final Entry referralEntry = findNearestReferral(baseDN); 3687 if (referralEntry != null) 3688 { 3689 return new LDAPMessage(messageID, new SearchResultDoneProtocolOp( 3690 ResultCode.REFERRAL_INT_VALUE, referralEntry.getDN(), 3691 INFO_MEM_HANDLER_REFERRAL_ENCOUNTERED.get(), 3692 getReferralURLs(baseDN, referralEntry))); 3693 } 3694 } 3695 3696 // Make sure that the base entry exists. It may be the root DSE or 3697 // subschema subentry. 3698 final Entry baseEntry; 3699 boolean includeChangeLog = true; 3700 if (baseDN.isNullDN()) 3701 { 3702 baseEntry = generateRootDSE(); 3703 includeChangeLog = false; 3704 } 3705 else if (baseDN.equals(subschemaSubentryDN)) 3706 { 3707 baseEntry = subschemaSubentryRef.get(); 3708 } 3709 else 3710 { 3711 baseEntry = entryMap.get(baseDN); 3712 } 3713 3714 if (baseEntry == null) 3715 { 3716 return new LDAPMessage(messageID, new SearchResultDoneProtocolOp( 3717 ResultCode.NO_SUCH_OBJECT_INT_VALUE, getMatchedDNString(baseDN), 3718 ERR_MEM_HANDLER_SEARCH_BASE_DOES_NOT_EXIST.get( 3719 request.getBaseDN()), 3720 null)); 3721 } 3722 3723 // Perform any necessary processing for the assertion and proxied auth 3724 // controls. 3725 try 3726 { 3727 handleAssertionRequestControl(controlMap, baseEntry); 3728 handleProxiedAuthControl(controlMap); 3729 } 3730 catch (final LDAPException le) 3731 { 3732 Debug.debugException(le); 3733 return new LDAPMessage(messageID, new SearchResultDoneProtocolOp( 3734 le.getResultCode().intValue(), null, le.getMessage(), null)); 3735 } 3736 3737 // Determine whether to include subentries in search results. 3738 final boolean includeSubEntries; 3739 final boolean includeNonSubEntries; 3740 final SearchScope scope = request.getScope(); 3741 if (scope == SearchScope.BASE) 3742 { 3743 includeSubEntries = true; 3744 includeNonSubEntries = true; 3745 } 3746 else if (controlMap.containsKey( 3747 DraftLDUPSubentriesRequestControl.SUBENTRIES_REQUEST_OID)) 3748 { 3749 includeSubEntries = true; 3750 includeNonSubEntries = false; 3751 } 3752 else if (controlMap.containsKey( 3753 RFC3672SubentriesRequestControl.SUBENTRIES_REQUEST_OID)) 3754 { 3755 includeSubEntries = true; 3756 3757 final RFC3672SubentriesRequestControl c = 3758 (RFC3672SubentriesRequestControl) controlMap.get( 3759 RFC3672SubentriesRequestControl.SUBENTRIES_REQUEST_OID); 3760 includeNonSubEntries = (! c.returnOnlySubEntries()); 3761 } 3762 else if (baseEntry.hasObjectClass("ldapSubEntry") || 3763 baseEntry.hasObjectClass("inheritableLDAPSubEntry")) 3764 { 3765 includeSubEntries = true; 3766 includeNonSubEntries = true; 3767 } 3768 else if (filterIncludesLDAPSubEntry(request.getFilter())) 3769 { 3770 includeSubEntries = true; 3771 includeNonSubEntries = true; 3772 } 3773 else 3774 { 3775 includeSubEntries = false; 3776 includeNonSubEntries = true; 3777 } 3778 3779 // Create a temporary list to hold all of the entries to be returned. 3780 // These entries will not have been pared down based on the requested 3781 // attributes. 3782 final List<Entry> fullEntryList = new ArrayList<>(entryMap.size()); 3783 3784findEntriesAndRefs: 3785 { 3786 // Check the scope. If it is a base-level search, then we only need to 3787 // examine the base entry. Otherwise, we'll have to scan the entire 3788 // entry map. 3789 final Filter filter = request.getFilter(); 3790 if (scope == SearchScope.BASE) 3791 { 3792 try 3793 { 3794 if (filter.matchesEntry(baseEntry, schema)) 3795 { 3796 processSearchEntry(baseEntry, includeSubEntries, 3797 includeNonSubEntries, includeChangeLog, hasManageDsaIT, 3798 fullEntryList, referenceList); 3799 } 3800 } 3801 catch (final Exception e) 3802 { 3803 Debug.debugException(e); 3804 } 3805 3806 break findEntriesAndRefs; 3807 } 3808 3809 // If the search uses a single-level scope and the base DN is the root 3810 // DSE, then we will only examine the defined base entries for the data 3811 // set. 3812 if ((scope == SearchScope.ONE) && baseDN.isNullDN()) 3813 { 3814 for (final DN dn : baseDNs) 3815 { 3816 final Entry e = entryMap.get(dn); 3817 if (e != null) 3818 { 3819 try 3820 { 3821 if (filter.matchesEntry(e, schema)) 3822 { 3823 processSearchEntry(e, includeSubEntries, includeNonSubEntries, 3824 includeChangeLog, hasManageDsaIT, fullEntryList, 3825 referenceList); 3826 } 3827 } 3828 catch (final Exception ex) 3829 { 3830 Debug.debugException(ex); 3831 } 3832 } 3833 } 3834 3835 break findEntriesAndRefs; 3836 } 3837 3838 3839 // Try to use indexes to process the request. If we can't use any 3840 // indexes to get a candidate list, then just iterate over all the 3841 // entries. It's not necessary to consider the root DSE for non-base 3842 // scopes. 3843 final Set<DN> candidateDNs = indexSearch(filter); 3844 if (candidateDNs == null) 3845 { 3846 for (final Map.Entry<DN,ReadOnlyEntry> me : entryMap.entrySet()) 3847 { 3848 final DN dn = me.getKey(); 3849 final Entry entry = me.getValue(); 3850 try 3851 { 3852 if (dn.matchesBaseAndScope(baseDN, scope) && 3853 filter.matchesEntry(entry, schema)) 3854 { 3855 processSearchEntry(entry, includeSubEntries, 3856 includeNonSubEntries, includeChangeLog, hasManageDsaIT, 3857 fullEntryList, referenceList); 3858 } 3859 } 3860 catch (final Exception e) 3861 { 3862 Debug.debugException(e); 3863 } 3864 } 3865 } 3866 else 3867 { 3868 for (final DN dn : candidateDNs) 3869 { 3870 try 3871 { 3872 if (! dn.matchesBaseAndScope(baseDN, scope)) 3873 { 3874 continue; 3875 } 3876 3877 final Entry entry = entryMap.get(dn); 3878 if (filter.matchesEntry(entry, schema)) 3879 { 3880 processSearchEntry(entry, includeSubEntries, 3881 includeNonSubEntries, includeChangeLog, hasManageDsaIT, 3882 fullEntryList, referenceList); 3883 } 3884 } 3885 catch (final Exception e) 3886 { 3887 Debug.debugException(e); 3888 } 3889 } 3890 } 3891 } 3892 3893 3894 // If the request included the server-side sort request control, then sort 3895 // the matching entries appropriately. 3896 final ServerSideSortRequestControl sortRequestControl = 3897 (ServerSideSortRequestControl) controlMap.get( 3898 ServerSideSortRequestControl.SERVER_SIDE_SORT_REQUEST_OID); 3899 if (sortRequestControl != null) 3900 { 3901 final EntrySorter entrySorter = new EntrySorter(false, schema, 3902 sortRequestControl.getSortKeys()); 3903 final SortedSet<Entry> sortedEntrySet = entrySorter.sort(fullEntryList); 3904 fullEntryList.clear(); 3905 fullEntryList.addAll(sortedEntrySet); 3906 3907 responseControls.add(new ServerSideSortResponseControl( 3908 ResultCode.SUCCESS, null)); 3909 } 3910 3911 3912 // If the request included the simple paged results control, then handle 3913 // it. 3914 final SimplePagedResultsControl pagedResultsControl = 3915 (SimplePagedResultsControl) 3916 controlMap.get(SimplePagedResultsControl.PAGED_RESULTS_OID); 3917 if (pagedResultsControl != null) 3918 { 3919 final int totalSize = fullEntryList.size(); 3920 final int pageSize = pagedResultsControl.getSize(); 3921 final ASN1OctetString cookie = pagedResultsControl.getCookie(); 3922 3923 final int offset; 3924 if ((cookie == null) || (cookie.getValueLength() == 0)) 3925 { 3926 // This is the first request in the series, so start at the beginning 3927 // of the list. 3928 offset = 0; 3929 } 3930 else 3931 { 3932 // The cookie value will simply be an integer representation of the 3933 // offset within the result list at which to start the next batch. 3934 try 3935 { 3936 final ASN1Integer offsetInteger = 3937 ASN1Integer.decodeAsInteger(cookie.getValue()); 3938 offset = offsetInteger.intValue(); 3939 } 3940 catch (final Exception e) 3941 { 3942 Debug.debugException(e); 3943 return new LDAPMessage(messageID, 3944 new SearchResultDoneProtocolOp( 3945 ResultCode.PROTOCOL_ERROR_INT_VALUE, null, 3946 ERR_MEM_HANDLER_MALFORMED_PAGED_RESULTS_COOKIE.get(), 3947 null), 3948 responseControls); 3949 } 3950 } 3951 3952 // Create an iterator that will be used to remove entries from the 3953 // result set that are outside of the requested page of results. 3954 int pos = 0; 3955 final Iterator<Entry> iterator = fullEntryList.iterator(); 3956 3957 // First, remove entries at the beginning of the list until we hit the 3958 // offset. 3959 while (iterator.hasNext() && (pos < offset)) 3960 { 3961 iterator.next(); 3962 iterator.remove(); 3963 pos++; 3964 } 3965 3966 // Next, skip over the entries that should be returned. 3967 int keptEntries = 0; 3968 while (iterator.hasNext() && (keptEntries < pageSize)) 3969 { 3970 iterator.next(); 3971 pos++; 3972 keptEntries++; 3973 } 3974 3975 // If there are still entries left, then remove them and create a cookie 3976 // to include in the response. Otherwise, use an empty cookie. 3977 if (iterator.hasNext()) 3978 { 3979 responseControls.add(new SimplePagedResultsControl(totalSize, 3980 new ASN1OctetString(new ASN1Integer(pos).encode()), false)); 3981 while (iterator.hasNext()) 3982 { 3983 iterator.next(); 3984 iterator.remove(); 3985 } 3986 } 3987 else 3988 { 3989 responseControls.add(new SimplePagedResultsControl(totalSize, 3990 new ASN1OctetString(), false)); 3991 } 3992 } 3993 3994 3995 // If the request includes the virtual list view request control, then 3996 // handle it. 3997 final VirtualListViewRequestControl vlvRequest = 3998 (VirtualListViewRequestControl) controlMap.get( 3999 VirtualListViewRequestControl.VIRTUAL_LIST_VIEW_REQUEST_OID); 4000 if (vlvRequest != null) 4001 { 4002 final int totalEntries = fullEntryList.size(); 4003 final ASN1OctetString assertionValue = vlvRequest.getAssertionValue(); 4004 4005 // Figure out the position of the target entry in the list. 4006 int offset = vlvRequest.getTargetOffset(); 4007 if (assertionValue == null) 4008 { 4009 // The offset is one-based, so we need to adjust it for the list's 4010 // zero-based offset. Also, make sure to put it within the bounds of 4011 // the list. 4012 offset--; 4013 offset = Math.max(0, offset); 4014 offset = Math.min(fullEntryList.size(), offset); 4015 } 4016 else 4017 { 4018 final SortKey primarySortKey = sortRequestControl.getSortKeys()[0]; 4019 4020 final Entry testEntry = new Entry("cn=test", schema, 4021 new Attribute(primarySortKey.getAttributeName(), 4022 assertionValue)); 4023 4024 final EntrySorter entrySorter = 4025 new EntrySorter(false, schema, primarySortKey); 4026 4027 offset = fullEntryList.size(); 4028 for (int i=0; i < fullEntryList.size(); i++) 4029 { 4030 if (entrySorter.compare(fullEntryList.get(i), testEntry) >= 0) 4031 { 4032 offset = i; 4033 break; 4034 } 4035 } 4036 } 4037 4038 // Get the start and end positions based on the before and after counts. 4039 final int beforeCount = Math.max(0, vlvRequest.getBeforeCount()); 4040 final int afterCount = Math.max(0, vlvRequest.getAfterCount()); 4041 4042 final int start = Math.max(0, (offset - beforeCount)); 4043 final int end = 4044 Math.min(fullEntryList.size(), (offset + afterCount + 1)); 4045 4046 // Create an iterator to use to alter the list so that it only contains 4047 // the appropriate set of entries. 4048 int pos = 0; 4049 final Iterator<Entry> iterator = fullEntryList.iterator(); 4050 while (iterator.hasNext()) 4051 { 4052 iterator.next(); 4053 if ((pos < start) || (pos >= end)) 4054 { 4055 iterator.remove(); 4056 } 4057 pos++; 4058 } 4059 4060 // Create the appropriate response control. 4061 responseControls.add(new VirtualListViewResponseControl((offset+1), 4062 totalEntries, ResultCode.SUCCESS, null)); 4063 } 4064 4065 4066 // Process the set of requested attributes so that we can pare down the 4067 // entries. 4068 final SearchEntryParer parer = new SearchEntryParer( 4069 request.getAttributes(), schema); 4070 final int sizeLimit; 4071 if (request.getSizeLimit() > 0) 4072 { 4073 sizeLimit = Math.min(request.getSizeLimit(), maxSizeLimit); 4074 } 4075 else 4076 { 4077 sizeLimit = maxSizeLimit; 4078 } 4079 4080 int entryCount = 0; 4081 for (final Entry e : fullEntryList) 4082 { 4083 entryCount++; 4084 if (entryCount > sizeLimit) 4085 { 4086 return new LDAPMessage(messageID, 4087 new SearchResultDoneProtocolOp( 4088 ResultCode.SIZE_LIMIT_EXCEEDED_INT_VALUE, null, 4089 ERR_MEM_HANDLER_SEARCH_SIZE_LIMIT_EXCEEDED.get(), null), 4090 responseControls); 4091 } 4092 4093 final Entry trimmedEntry = parer.pareEntry(e); 4094 if (request.typesOnly()) 4095 { 4096 final Entry typesOnlyEntry = new Entry(trimmedEntry.getDN(), schema); 4097 for (final Attribute a : trimmedEntry.getAttributes()) 4098 { 4099 typesOnlyEntry.addAttribute(new Attribute(a.getName())); 4100 } 4101 entryList.add(new SearchResultEntry(typesOnlyEntry)); 4102 } 4103 else 4104 { 4105 entryList.add(new SearchResultEntry(trimmedEntry)); 4106 } 4107 } 4108 4109 return new LDAPMessage(messageID, 4110 new SearchResultDoneProtocolOp(ResultCode.SUCCESS_INT_VALUE, null, 4111 null, null), 4112 responseControls); 4113 } 4114 } 4115 4116 4117 4118 /** 4119 * Ensures that the provided filter is supported in the in-memory directory 4120 * server. 4121 * 4122 * @param filter The filter being validated. 4123 * 4124 * @throws LDAPException If the provided filter is not acceptable. 4125 */ 4126 private static void ensureFilterSupported(@NotNull final Filter filter) 4127 throws LDAPException 4128 { 4129 switch (filter.getFilterType()) 4130 { 4131 case Filter.FILTER_TYPE_AND: 4132 case Filter.FILTER_TYPE_OR: 4133 // Make sure that all of the embedded components are supported. 4134 for (final Filter component : filter.getComponents()) 4135 { 4136 ensureFilterSupported(component); 4137 } 4138 return; 4139 4140 case Filter.FILTER_TYPE_NOT: 4141 // Make sure that the embedded component is supported. 4142 ensureFilterSupported(filter.getNOTComponent()); 4143 return; 4144 4145 case Filter.FILTER_TYPE_EQUALITY: 4146 case Filter.FILTER_TYPE_SUBSTRING: 4147 case Filter.FILTER_TYPE_GREATER_OR_EQUAL: 4148 case Filter.FILTER_TYPE_LESS_OR_EQUAL: 4149 case Filter.FILTER_TYPE_PRESENCE: 4150 // These are always acceptable. 4151 return; 4152 4153 case Filter.FILTER_TYPE_APPROXIMATE_MATCH: 4154 // Approximate match filters are never supported. 4155 throw new LDAPException(ResultCode.INAPPROPRIATE_MATCHING, 4156 ERR_MEM_HANDLER_FILTER_UNSUPPORTED_APPROXIMATE_MATCH_FILTER.get()); 4157 4158 case Filter.FILTER_TYPE_EXTENSIBLE_MATCH: 4159 // Extensible match filters are never supported. 4160 throw new LDAPException(ResultCode.INAPPROPRIATE_MATCHING, 4161 ERR_MEM_HANDLER_FILTER_UNSUPPORTED_EXTENSIBLE_MATCH_FILTER.get()); 4162 4163 default: 4164 // Unrecognized filter types are never supported. 4165 throw new LDAPException(ResultCode.INAPPROPRIATE_MATCHING, 4166 ERR_MEM_HANDLER_FILTER_UNRECOGNIZED_FILTER_TYPE.get( 4167 StaticUtils.toHex(filter.getFilterType()))); 4168 } 4169 } 4170 4171 4172 4173 /** 4174 * Indicates whether the provided filter includes a component that targets the 4175 * ldapSubEntry object class. 4176 * 4177 * @param filter The filter for which to make the determination. 4178 * 4179 * @return {@code true} if the provided filter includes a component that 4180 * targets the ldapSubEntry object class or {@code false} if not. 4181 */ 4182 private static boolean filterIncludesLDAPSubEntry( 4183 @NotNull final Filter filter) 4184 { 4185 switch (filter.getFilterType()) 4186 { 4187 case Filter.FILTER_TYPE_AND: 4188 case Filter.FILTER_TYPE_OR: 4189 for (final Filter f : filter.getComponents()) 4190 { 4191 if (filterIncludesLDAPSubEntry(f)) 4192 { 4193 return true; 4194 } 4195 } 4196 return false; 4197 4198 case Filter.FILTER_TYPE_EQUALITY: 4199 return (filter.getAttributeName().equalsIgnoreCase("objectClass") || 4200 filter.getAttributeName().equals("2.5.4.0")); 4201 4202 default: 4203 return false; 4204 } 4205 } 4206 4207 4208 4209 /** 4210 * Performs any necessary index processing to add the provided entry. 4211 * 4212 * @param entry The entry that has been added. 4213 */ 4214 private void indexAdd(@NotNull final Entry entry) 4215 { 4216 for (final InMemoryDirectoryServerEqualityAttributeIndex i : 4217 equalityIndexes.values()) 4218 { 4219 try 4220 { 4221 i.processAdd(entry); 4222 } 4223 catch (final LDAPException le) 4224 { 4225 Debug.debugException(le); 4226 } 4227 } 4228 } 4229 4230 4231 4232 /** 4233 * Performs any necessary index processing to delete the provided entry. 4234 * 4235 * @param entry The entry that has been deleted. 4236 */ 4237 private void indexDelete(@NotNull final Entry entry) 4238 { 4239 for (final InMemoryDirectoryServerEqualityAttributeIndex i : 4240 equalityIndexes.values()) 4241 { 4242 try 4243 { 4244 i.processDelete(entry); 4245 } 4246 catch (final LDAPException le) 4247 { 4248 Debug.debugException(le); 4249 } 4250 } 4251 } 4252 4253 4254 4255 /** 4256 * Attempts to use indexes to obtain a candidate list for the provided filter. 4257 * 4258 * @param filter The filter to be processed. 4259 * 4260 * @return The DNs of entries which may match the given filter, or 4261 * {@code null} if the filter is not indexed. 4262 */ 4263 @Nullable() 4264 private Set<DN> indexSearch(@NotNull final Filter filter) 4265 { 4266 switch (filter.getFilterType()) 4267 { 4268 case Filter.FILTER_TYPE_AND: 4269 Filter[] comps = filter.getComponents(); 4270 if (comps.length == 0) 4271 { 4272 return null; 4273 } 4274 else if (comps.length == 1) 4275 { 4276 return indexSearch(comps[0]); 4277 } 4278 else 4279 { 4280 Set<DN> candidateSet = null; 4281 for (final Filter f : comps) 4282 { 4283 final Set<DN> dnSet = indexSearch(f); 4284 if (dnSet != null) 4285 { 4286 if (candidateSet == null) 4287 { 4288 candidateSet = new TreeSet<>(dnSet); 4289 } 4290 else 4291 { 4292 candidateSet.retainAll(dnSet); 4293 } 4294 } 4295 } 4296 return candidateSet; 4297 } 4298 4299 case Filter.FILTER_TYPE_OR: 4300 comps = filter.getComponents(); 4301 if (comps.length == 0) 4302 { 4303 return Collections.emptySet(); 4304 } 4305 else if (comps.length == 1) 4306 { 4307 return indexSearch(comps[0]); 4308 } 4309 else 4310 { 4311 Set<DN> candidateSet = null; 4312 for (final Filter f : comps) 4313 { 4314 final Set<DN> dnSet = indexSearch(f); 4315 if (dnSet == null) 4316 { 4317 return null; 4318 } 4319 4320 if (candidateSet == null) 4321 { 4322 candidateSet = new TreeSet<>(dnSet); 4323 } 4324 else 4325 { 4326 candidateSet.addAll(dnSet); 4327 } 4328 } 4329 return candidateSet; 4330 } 4331 4332 case Filter.FILTER_TYPE_EQUALITY: 4333 final Schema schema = schemaRef.get(); 4334 if (schema == null) 4335 { 4336 return null; 4337 } 4338 final AttributeTypeDefinition at = 4339 schema.getAttributeType(filter.getAttributeName()); 4340 if (at == null) 4341 { 4342 return null; 4343 } 4344 final InMemoryDirectoryServerEqualityAttributeIndex i = 4345 equalityIndexes.get(at); 4346 if (i == null) 4347 { 4348 return null; 4349 } 4350 try 4351 { 4352 return i.getMatchingEntries(filter.getRawAssertionValue()); 4353 } 4354 catch (final Exception e) 4355 { 4356 Debug.debugException(e); 4357 return null; 4358 } 4359 4360 default: 4361 return null; 4362 } 4363 } 4364 4365 4366 4367 /** 4368 * Determines whether the provided set of controls includes a transaction 4369 * specification request control. If so, then it will verify that it 4370 * references a valid transaction for the client. If the request is part of a 4371 * valid transaction, then the transaction specification request control will 4372 * be removed and the request will be stashed in the client connection state 4373 * so that it can be retrieved and processed when the transaction is 4374 * committed. 4375 * 4376 * @param messageID The message ID for the request to be processed. 4377 * @param request The protocol op for the request to be processed. 4378 * @param controls The set of controls for the request to be processed. 4379 * 4380 * @return The transaction ID for the associated transaction, or {@code null} 4381 * if the request is not part of any transaction. 4382 * 4383 * @throws LDAPException If the transaction specification request control is 4384 * present but does not refer to a valid transaction 4385 * for the associated client connection. 4386 */ 4387 @SuppressWarnings("unchecked") 4388 @Nullable() 4389 private ASN1OctetString processTransactionRequest(final int messageID, 4390 @NotNull final ProtocolOp request, 4391 @NotNull final Map<String,Control> controls) 4392 throws LDAPException 4393 { 4394 final TransactionSpecificationRequestControl txnControl = 4395 (TransactionSpecificationRequestControl) 4396 controls.remove(TransactionSpecificationRequestControl. 4397 TRANSACTION_SPECIFICATION_REQUEST_OID); 4398 if (txnControl == null) 4399 { 4400 return null; 4401 } 4402 4403 // See if the client has an active transaction. If not, then fail. 4404 final ASN1OctetString txnID = txnControl.getTransactionID(); 4405 final ObjectPair<ASN1OctetString,List<LDAPMessage>> txnInfo = 4406 (ObjectPair<ASN1OctetString,List<LDAPMessage>>) connectionState.get( 4407 TransactionExtendedOperationHandler.STATE_VARIABLE_TXN_INFO); 4408 if (txnInfo == null) 4409 { 4410 throw new LDAPException(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION, 4411 ERR_MEM_HANDLER_TXN_CONTROL_WITHOUT_TXN.get(txnID.stringValue())); 4412 } 4413 4414 4415 // Make sure that the active transaction has a transaction ID that matches 4416 // the transaction ID from the control. If not, then abort the existing 4417 // transaction and fail. 4418 final ASN1OctetString existingTxnID = txnInfo.getFirst(); 4419 if (! txnID.stringValue().equals(existingTxnID.stringValue())) 4420 { 4421 connectionState.remove( 4422 TransactionExtendedOperationHandler.STATE_VARIABLE_TXN_INFO); 4423 connection.sendUnsolicitedNotification( 4424 new AbortedTransactionExtendedResult(existingTxnID, 4425 ResultCode.CONSTRAINT_VIOLATION, 4426 ERR_MEM_HANDLER_TXN_ABORTED_BY_CONTROL_TXN_ID_MISMATCH.get( 4427 existingTxnID.stringValue(), txnID.stringValue()), 4428 null, null, null)); 4429 throw new LDAPException(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION, 4430 ERR_MEM_HANDLER_TXN_CONTROL_ID_MISMATCH.get(txnID.stringValue(), 4431 existingTxnID.stringValue())); 4432 } 4433 4434 4435 // Stash the request in the transaction state information so that it will 4436 // be processed when the transaction is committed. 4437 txnInfo.getSecond().add(new LDAPMessage(messageID, request, 4438 new ArrayList<>(controls.values()))); 4439 4440 return txnID; 4441 } 4442 4443 4444 4445 /** 4446 * Sleeps for a period of time (if appropriate) before beginning processing 4447 * for an operation. 4448 */ 4449 private void sleepBeforeProcessing() 4450 { 4451 final long delay = processingDelayMillis.get(); 4452 if (delay > 0) 4453 { 4454 try 4455 { 4456 Thread.sleep(delay); 4457 } 4458 catch (final Exception e) 4459 { 4460 Debug.debugException(e); 4461 4462 if (e instanceof InterruptedException) 4463 { 4464 Thread.currentThread().interrupt(); 4465 } 4466 } 4467 } 4468 } 4469 4470 4471 4472 /** 4473 * Retrieves the configured list of password attributes. 4474 * 4475 * @return The configured list of password attributes. 4476 */ 4477 @NotNull() 4478 public List<String> getPasswordAttributes() 4479 { 4480 return configuredPasswordAttributes; 4481 } 4482 4483 4484 4485 /** 4486 * Retrieves the primary password encoder that has been configured for the 4487 * server. 4488 * 4489 * @return The primary password encoder that has been configured for the 4490 * server. 4491 */ 4492 @Nullable() 4493 public InMemoryPasswordEncoder getPrimaryPasswordEncoder() 4494 { 4495 return primaryPasswordEncoder; 4496 } 4497 4498 4499 4500 /** 4501 * Retrieves a list of all password encoders configured for the server. 4502 * 4503 * @return A list of all password encoders configured for the server. 4504 */ 4505 @NotNull() 4506 public List<InMemoryPasswordEncoder> getAllPasswordEncoders() 4507 { 4508 return passwordEncoders; 4509 } 4510 4511 4512 4513 /** 4514 * Retrieves a list of the passwords contained in the provided entry. 4515 * 4516 * @param entry The entry from which to obtain the list of 4517 * passwords. It must not be {@code null}. 4518 * @param clearPasswordToMatch An optional clear-text password that should 4519 * match the values that are returned. If this 4520 * is {@code null}, then all passwords contained 4521 * in the provided entry will be returned. If 4522 * this is non-{@code null}, then only passwords 4523 * matching the clear-text password will be 4524 * returned. 4525 * 4526 * @return A list of the passwords contained in the provided entry, 4527 * optionally restricted to those matching the provided clear-text 4528 * password, or an empty list if the entry does not contain any 4529 * passwords. 4530 */ 4531 @NotNull() 4532 public List<InMemoryDirectoryServerPassword> getPasswordsInEntry( 4533 @NotNull final Entry entry, 4534 @Nullable final ASN1OctetString clearPasswordToMatch) 4535 { 4536 final ArrayList<InMemoryDirectoryServerPassword> passwordList = 4537 new ArrayList<>(5); 4538 final ReadOnlyEntry readOnlyEntry = new ReadOnlyEntry(entry); 4539 4540 for (final String passwordAttributeName : configuredPasswordAttributes) 4541 { 4542 final List<Attribute> passwordAttributeList = 4543 entry.getAttributesWithOptions(passwordAttributeName, null); 4544 4545 for (final Attribute passwordAttribute : passwordAttributeList) 4546 { 4547 for (final ASN1OctetString value : passwordAttribute.getRawValues()) 4548 { 4549 final InMemoryDirectoryServerPassword password = 4550 new InMemoryDirectoryServerPassword(value, readOnlyEntry, 4551 passwordAttribute.getName(), passwordEncoders); 4552 4553 if (clearPasswordToMatch != null) 4554 { 4555 try 4556 { 4557 if (! password.matchesClearPassword(clearPasswordToMatch)) 4558 { 4559 continue; 4560 } 4561 } 4562 catch (final Exception e) 4563 { 4564 Debug.debugException(e); 4565 continue; 4566 } 4567 } 4568 4569 passwordList.add(new InMemoryDirectoryServerPassword(value, 4570 readOnlyEntry, passwordAttribute.getName(), passwordEncoders)); 4571 } 4572 } 4573 } 4574 4575 return passwordList; 4576 } 4577 4578 4579 4580 /** 4581 * Retrieves the number of entries currently held in the server. 4582 * 4583 * @param includeChangeLog Indicates whether to include entries that are 4584 * part of the changelog in the count. 4585 * 4586 * @return The number of entries currently held in the server. 4587 */ 4588 public int countEntries(final boolean includeChangeLog) 4589 { 4590 synchronized (entryMap) 4591 { 4592 if (includeChangeLog || (maxChangelogEntries == 0)) 4593 { 4594 return entryMap.size(); 4595 } 4596 else 4597 { 4598 int count = 0; 4599 4600 for (final DN dn : entryMap.keySet()) 4601 { 4602 if (! dn.isDescendantOf(changeLogBaseDN, true)) 4603 { 4604 count++; 4605 } 4606 } 4607 4608 return count; 4609 } 4610 } 4611 } 4612 4613 4614 4615 /** 4616 * Retrieves the number of entries currently held in the server whose DN 4617 * matches or is subordinate to the provided base DN. 4618 * 4619 * @param baseDN The base DN to use for the determination. 4620 * 4621 * @return The number of entries currently held in the server whose DN 4622 * matches or is subordinate to the provided base DN. 4623 * 4624 * @throws LDAPException If the provided string cannot be parsed as a valid 4625 * DN. 4626 */ 4627 public int countEntriesBelow(@NotNull final String baseDN) 4628 throws LDAPException 4629 { 4630 synchronized (entryMap) 4631 { 4632 final DN parsedBaseDN = new DN(baseDN, schemaRef.get()); 4633 4634 int count = 0; 4635 for (final DN dn : entryMap.keySet()) 4636 { 4637 if (dn.isDescendantOf(parsedBaseDN, true)) 4638 { 4639 count++; 4640 } 4641 } 4642 4643 return count; 4644 } 4645 } 4646 4647 4648 4649 /** 4650 * Removes all entries currently held in the server. If a changelog is 4651 * enabled, then all changelog entries will also be cleared but the base 4652 * "cn=changelog" entry will be retained. 4653 */ 4654 public void clear() 4655 { 4656 synchronized (entryMap) 4657 { 4658 restoreSnapshot(initialSnapshot); 4659 } 4660 } 4661 4662 4663 4664 /** 4665 * Reads entries from the provided LDIF reader and adds them to the server, 4666 * optionally clearing any existing entries before beginning to add the new 4667 * entries. If an error is encountered while adding entries from LDIF then 4668 * the server will remain populated with the data it held before the import 4669 * attempt (even if the {@code clear} is given with a value of {@code true}). 4670 * 4671 * @param clear Indicates whether to remove all existing entries prior 4672 * to adding entries read from LDIF. 4673 * @param ldifReader The LDIF reader to use to obtain the entries to be 4674 * imported. It will be closed by this method. 4675 * 4676 * @return The number of entries read from LDIF and added to the server. 4677 * 4678 * @throws LDAPException If a problem occurs while reading entries or adding 4679 * them to the server. 4680 */ 4681 public int importFromLDIF(final boolean clear, 4682 @NotNull final LDIFReader ldifReader) 4683 throws LDAPException 4684 { 4685 synchronized (entryMap) 4686 { 4687 final InMemoryDirectoryServerSnapshot snapshot = createSnapshot(); 4688 boolean restoreSnapshot = true; 4689 4690 try 4691 { 4692 if (clear) 4693 { 4694 restoreSnapshot(initialSnapshot); 4695 } 4696 4697 int entriesAdded = 0; 4698 while (true) 4699 { 4700 final Entry entry; 4701 try 4702 { 4703 entry = ldifReader.readEntry(); 4704 if (entry == null) 4705 { 4706 restoreSnapshot = false; 4707 return entriesAdded; 4708 } 4709 } 4710 catch (final LDIFException le) 4711 { 4712 Debug.debugException(le); 4713 throw new LDAPException(ResultCode.LOCAL_ERROR, 4714 ERR_MEM_HANDLER_INIT_FROM_LDIF_READ_ERROR.get(le.getMessage()), 4715 le); 4716 } 4717 catch (final Exception e) 4718 { 4719 Debug.debugException(e); 4720 throw new LDAPException(ResultCode.LOCAL_ERROR, 4721 ERR_MEM_HANDLER_INIT_FROM_LDIF_READ_ERROR.get( 4722 StaticUtils.getExceptionMessage(e)), 4723 e); 4724 } 4725 4726 addEntry(entry, true); 4727 entriesAdded++; 4728 } 4729 } 4730 finally 4731 { 4732 try 4733 { 4734 ldifReader.close(); 4735 } 4736 catch (final Exception e) 4737 { 4738 Debug.debugException(e); 4739 } 4740 4741 if (restoreSnapshot) 4742 { 4743 restoreSnapshot(snapshot); 4744 } 4745 } 4746 } 4747 } 4748 4749 4750 4751 /** 4752 * Writes all entries contained in the server to LDIF using the provided 4753 * writer. 4754 * 4755 * @param ldifWriter The LDIF writer to use when writing the 4756 * entries. It must not be {@code null}. 4757 * @param excludeGeneratedAttrs Indicates whether to exclude automatically 4758 * generated operational attributes like 4759 * entryUUID, entryDN, creatorsName, etc. 4760 * @param excludeChangeLog Indicates whether to exclude entries 4761 * contained in the changelog. 4762 * @param closeWriter Indicates whether the LDIF writer should be 4763 * closed after all entries have been written. 4764 * 4765 * @return The number of entries written to LDIF. 4766 * 4767 * @throws LDAPException If a problem is encountered while attempting to 4768 * write an entry to LDIF. 4769 */ 4770 public int exportToLDIF(@NotNull final LDIFWriter ldifWriter, 4771 final boolean excludeGeneratedAttrs, 4772 final boolean excludeChangeLog, 4773 final boolean closeWriter) 4774 throws LDAPException 4775 { 4776 synchronized (entryMap) 4777 { 4778 boolean exceptionThrown = false; 4779 4780 try 4781 { 4782 int entriesWritten = 0; 4783 4784 for (final Map.Entry<DN,ReadOnlyEntry> me : entryMap.entrySet()) 4785 { 4786 final DN dn = me.getKey(); 4787 if (excludeChangeLog && dn.isDescendantOf(changeLogBaseDN, true)) 4788 { 4789 continue; 4790 } 4791 4792 final Entry entry; 4793 if (excludeGeneratedAttrs) 4794 { 4795 entry = me.getValue().duplicate(); 4796 entry.removeAttribute("entryDN"); 4797 entry.removeAttribute("entryUUID"); 4798 entry.removeAttribute("subschemaSubentry"); 4799 entry.removeAttribute("creatorsName"); 4800 entry.removeAttribute("createTimestamp"); 4801 entry.removeAttribute("modifiersName"); 4802 entry.removeAttribute("modifyTimestamp"); 4803 } 4804 else 4805 { 4806 entry = me.getValue(); 4807 } 4808 4809 try 4810 { 4811 ldifWriter.writeEntry(entry); 4812 entriesWritten++; 4813 } 4814 catch (final Exception e) 4815 { 4816 Debug.debugException(e); 4817 exceptionThrown = true; 4818 throw new LDAPException(ResultCode.LOCAL_ERROR, 4819 ERR_MEM_HANDLER_LDIF_WRITE_ERROR.get(entry.getDN(), 4820 StaticUtils.getExceptionMessage(e)), 4821 e); 4822 } 4823 } 4824 4825 return entriesWritten; 4826 } 4827 finally 4828 { 4829 if (closeWriter) 4830 { 4831 try 4832 { 4833 ldifWriter.close(); 4834 } 4835 catch (final Exception e) 4836 { 4837 Debug.debugException(e); 4838 if (! exceptionThrown) 4839 { 4840 throw new LDAPException(ResultCode.LOCAL_ERROR, 4841 ERR_MEM_HANDLER_LDIF_WRITE_CLOSE_ERROR.get( 4842 StaticUtils.getExceptionMessage(e)), 4843 e); 4844 } 4845 } 4846 } 4847 } 4848 } 4849 } 4850 4851 4852 4853 /** 4854 * Reads entries from the provided LDIF reader and adds them to the server, 4855 * optionally clearing any existing entries before beginning to add the new 4856 * entries. If an error is encountered while adding entries from LDIF then 4857 * the server will remain populated with the data it held before the import 4858 * attempt (even if the {@code clear} is given with a value of {@code true}). 4859 * <BR><BR> 4860 * This method may be used regardless of whether the server is listening for 4861 * client connections. 4862 * 4863 * @param ldifReader The LDIF reader to use to obtain the change records to 4864 * be applied. 4865 * 4866 * @return The number of changes applied from the LDIF file. 4867 * 4868 * @throws LDAPException If a problem occurs while reading change records 4869 * or applying them to the server. 4870 */ 4871 public int applyChangesFromLDIF(@NotNull final LDIFReader ldifReader) 4872 throws LDAPException 4873 { 4874 synchronized (entryMap) 4875 { 4876 final InMemoryDirectoryServerSnapshot snapshot = createSnapshot(); 4877 boolean restoreSnapshot = true; 4878 4879 try 4880 { 4881 int changesApplied = 0; 4882 while (true) 4883 { 4884 final LDIFChangeRecord changeRecord; 4885 try 4886 { 4887 changeRecord = ldifReader.readChangeRecord(true); 4888 if (changeRecord == null) 4889 { 4890 restoreSnapshot = false; 4891 return changesApplied; 4892 } 4893 } 4894 catch (final LDIFException le) 4895 { 4896 Debug.debugException(le); 4897 throw new LDAPException(ResultCode.LOCAL_ERROR, 4898 ERR_MEM_HANDLER_APPLY_CHANGES_FROM_LDIF_READ_ERROR.get( 4899 le.getMessage()), 4900 le); 4901 } 4902 catch (final Exception e) 4903 { 4904 Debug.debugException(e); 4905 throw new LDAPException(ResultCode.LOCAL_ERROR, 4906 ERR_MEM_HANDLER_APPLY_CHANGES_FROM_LDIF_READ_ERROR.get( 4907 StaticUtils.getExceptionMessage(e)), 4908 e); 4909 } 4910 4911 if (changeRecord instanceof LDIFAddChangeRecord) 4912 { 4913 final LDIFAddChangeRecord addChangeRecord = 4914 (LDIFAddChangeRecord) changeRecord; 4915 add(addChangeRecord.toAddRequest()); 4916 } 4917 else if (changeRecord instanceof LDIFDeleteChangeRecord) 4918 { 4919 final LDIFDeleteChangeRecord deleteChangeRecord = 4920 (LDIFDeleteChangeRecord) changeRecord; 4921 delete(deleteChangeRecord.toDeleteRequest()); 4922 } 4923 else if (changeRecord instanceof LDIFModifyChangeRecord) 4924 { 4925 final LDIFModifyChangeRecord modifyChangeRecord = 4926 (LDIFModifyChangeRecord) changeRecord; 4927 modify(modifyChangeRecord.toModifyRequest()); 4928 } 4929 else if (changeRecord instanceof LDIFModifyDNChangeRecord) 4930 { 4931 final LDIFModifyDNChangeRecord modifyDNChangeRecord = 4932 (LDIFModifyDNChangeRecord) changeRecord; 4933 modifyDN(modifyDNChangeRecord.toModifyDNRequest()); 4934 } 4935 else 4936 { 4937 throw new LDAPException(ResultCode.LOCAL_ERROR, 4938 ERR_MEM_HANDLER_APPLY_CHANGES_UNSUPPORTED_CHANGE.get( 4939 String.valueOf(changeRecord))); 4940 } 4941 4942 changesApplied++; 4943 } 4944 } 4945 finally 4946 { 4947 try 4948 { 4949 ldifReader.close(); 4950 } 4951 catch (final Exception e) 4952 { 4953 Debug.debugException(e); 4954 } 4955 4956 if (restoreSnapshot) 4957 { 4958 restoreSnapshot(snapshot); 4959 } 4960 } 4961 } 4962 } 4963 4964 4965 4966 /** 4967 * Attempts to add the provided entry to the in-memory data set. The attempt 4968 * will fail if any of the following conditions is true: 4969 * <UL> 4970 * <LI>The provided entry has a malformed DN.</LI> 4971 * <LI>The provided entry has the null DN.</LI> 4972 * <LI>The provided entry has a DN that is the same as or subordinate to the 4973 * subschema subentry.</LI> 4974 * <LI>An entry already exists with the same DN as the entry in the provided 4975 * request.</LI> 4976 * <LI>The entry is outside the set of base DNs for the server.</LI> 4977 * <LI>The entry is below one of the defined base DNs but the immediate 4978 * parent entry does not exist.</LI> 4979 * <LI>If a schema was provided, and the entry is not valid according to the 4980 * constraints of that schema.</LI> 4981 * </UL> 4982 * 4983 * @param entry The entry to be added. It must not be 4984 * {@code null}. 4985 * @param ignoreNoUserModification Indicates whether to ignore constraints 4986 * normally imposed by the 4987 * NO-USER-MODIFICATION element in attribute 4988 * type definitions. 4989 * 4990 * @throws LDAPException If a problem occurs while attempting to add the 4991 * provided entry. 4992 */ 4993 public void addEntry(@NotNull final Entry entry, 4994 final boolean ignoreNoUserModification) 4995 throws LDAPException 4996 { 4997 final List<Control> controls; 4998 if (ignoreNoUserModification) 4999 { 5000 controls = new ArrayList<>(1); 5001 controls.add(new Control(OID_INTERNAL_OPERATION_REQUEST_CONTROL, false)); 5002 } 5003 else 5004 { 5005 controls = Collections.emptyList(); 5006 } 5007 5008 final AddRequestProtocolOp addRequest = new AddRequestProtocolOp( 5009 entry.getDN(), new ArrayList<>(entry.getAttributes())); 5010 5011 final LDAPMessage resultMessage = 5012 processAddRequest(-1, addRequest, controls); 5013 5014 final AddResponseProtocolOp addResponse = 5015 resultMessage.getAddResponseProtocolOp(); 5016 if (addResponse.getResultCode() != ResultCode.SUCCESS_INT_VALUE) 5017 { 5018 throw new LDAPException(ResultCode.valueOf(addResponse.getResultCode()), 5019 addResponse.getDiagnosticMessage(), addResponse.getMatchedDN(), 5020 stringListToArray(addResponse.getReferralURLs())); 5021 } 5022 } 5023 5024 5025 5026 /** 5027 * Attempts to add all of the provided entries to the server. If an error is 5028 * encountered during processing, then the contents of the server will be the 5029 * same as they were before this method was called. 5030 * 5031 * @param entries The collection of entries to be added. 5032 * 5033 * @throws LDAPException If a problem was encountered while attempting to 5034 * add any of the entries to the server. 5035 */ 5036 public void addEntries(@NotNull final List<? extends Entry> entries) 5037 throws LDAPException 5038 { 5039 synchronized (entryMap) 5040 { 5041 final InMemoryDirectoryServerSnapshot snapshot = createSnapshot(); 5042 boolean restoreSnapshot = true; 5043 5044 try 5045 { 5046 for (final Entry e : entries) 5047 { 5048 addEntry(e, false); 5049 } 5050 restoreSnapshot = false; 5051 } 5052 finally 5053 { 5054 if (restoreSnapshot) 5055 { 5056 restoreSnapshot(snapshot); 5057 } 5058 } 5059 } 5060 } 5061 5062 5063 5064 /** 5065 * Removes the entry with the specified DN and any subordinate entries it may 5066 * have. 5067 * 5068 * @param baseDN The DN of the entry to be deleted. It must not be 5069 * {@code null} or represent the null DN. 5070 * 5071 * @return The number of entries actually removed, or zero if the specified 5072 * base DN does not represent an entry in the server. 5073 * 5074 * @throws LDAPException If the provided base DN is not a valid DN, or is 5075 * the DN of an entry that cannot be deleted (e.g., 5076 * the null DN). 5077 */ 5078 public int deleteSubtree(@NotNull final String baseDN) 5079 throws LDAPException 5080 { 5081 synchronized (entryMap) 5082 { 5083 final DN dn = new DN(baseDN, schemaRef.get()); 5084 if (dn.isNullDN()) 5085 { 5086 throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM, 5087 ERR_MEM_HANDLER_DELETE_ROOT_DSE.get()); 5088 } 5089 5090 int numDeleted = 0; 5091 5092 final Iterator<Map.Entry<DN,ReadOnlyEntry>> iterator = 5093 entryMap.entrySet().iterator(); 5094 while (iterator.hasNext()) 5095 { 5096 final Map.Entry<DN,ReadOnlyEntry> e = iterator.next(); 5097 if (e.getKey().isDescendantOf(dn, true)) 5098 { 5099 iterator.remove(); 5100 numDeleted++; 5101 } 5102 } 5103 5104 return numDeleted; 5105 } 5106 } 5107 5108 5109 5110 /** 5111 * Attempts to apply the provided set of modifications to the specified entry. 5112 * The attempt will fail if any of the following conditions is true: 5113 * <UL> 5114 * <LI>The target DN is malformed.</LI> 5115 * <LI>The target entry is the root DSE.</LI> 5116 * <LI>The target entry is the subschema subentry.</LI> 5117 * <LI>The target entry does not exist.</LI> 5118 * <LI>Any of the modifications cannot be applied to the entry.</LI> 5119 * <LI>If a schema was provided, and the entry violates any of the 5120 * constraints of that schema.</LI> 5121 * </UL> 5122 * 5123 * @param dn The DN of the entry to be modified. 5124 * @param mods The set of modifications to be applied to the entry. 5125 * 5126 * @throws LDAPException If a problem is encountered while attempting to 5127 * update the specified entry. 5128 */ 5129 public void modifyEntry(@NotNull final String dn, 5130 @NotNull final List<Modification> mods) 5131 throws LDAPException 5132 { 5133 final ModifyRequestProtocolOp modifyRequest = 5134 new ModifyRequestProtocolOp(dn, mods); 5135 5136 final LDAPMessage resultMessage = processModifyRequest(-1, modifyRequest, 5137 Collections.<Control>emptyList()); 5138 5139 final ModifyResponseProtocolOp modifyResponse = 5140 resultMessage.getModifyResponseProtocolOp(); 5141 if (modifyResponse.getResultCode() != ResultCode.SUCCESS_INT_VALUE) 5142 { 5143 throw new LDAPException( 5144 ResultCode.valueOf(modifyResponse.getResultCode()), 5145 modifyResponse.getDiagnosticMessage(), modifyResponse.getMatchedDN(), 5146 stringListToArray(modifyResponse.getReferralURLs())); 5147 } 5148 } 5149 5150 5151 5152 /** 5153 * Retrieves a read-only representation the entry with the specified DN, if 5154 * it exists. 5155 * 5156 * @param dn The DN of the entry to retrieve. 5157 * 5158 * @return The requested entry, or {@code null} if no entry exists with the 5159 * given DN. 5160 * 5161 * @throws LDAPException If the provided DN is malformed. 5162 */ 5163 @Nullable() 5164 public ReadOnlyEntry getEntry(@NotNull final String dn) 5165 throws LDAPException 5166 { 5167 return getEntry(new DN(dn, schemaRef.get())); 5168 } 5169 5170 5171 5172 /** 5173 * Retrieves a read-only representation the entry with the specified DN, if 5174 * it exists. 5175 * 5176 * @param dn The DN of the entry to retrieve. 5177 * 5178 * @return The requested entry, or {@code null} if no entry exists with the 5179 * given DN. 5180 */ 5181 @Nullable() 5182 public ReadOnlyEntry getEntry(@NotNull final DN dn) 5183 { 5184 synchronized (entryMap) 5185 { 5186 if (dn.isNullDN()) 5187 { 5188 return generateRootDSE(); 5189 } 5190 else if (dn.equals(subschemaSubentryDN)) 5191 { 5192 return subschemaSubentryRef.get(); 5193 } 5194 else 5195 { 5196 final Entry e = entryMap.get(dn); 5197 if (e == null) 5198 { 5199 return null; 5200 } 5201 else 5202 { 5203 return new ReadOnlyEntry(e); 5204 } 5205 } 5206 } 5207 } 5208 5209 5210 5211 /** 5212 * Retrieves a list of all entries in the server which match the given 5213 * search criteria. 5214 * 5215 * @param baseDN The base DN to use for the search. It must not be 5216 * {@code null}. 5217 * @param scope The scope to use for the search. It must not be 5218 * {@code null}. 5219 * @param filter The filter to use for the search. It must not be 5220 * {@code null}. 5221 * 5222 * @return A list of the entries that matched the provided search criteria. 5223 * 5224 * @throws LDAPException If a problem is encountered while performing the 5225 * search. 5226 */ 5227 @NotNull() 5228 public List<ReadOnlyEntry> search(@NotNull final String baseDN, 5229 @NotNull final SearchScope scope, 5230 @NotNull final Filter filter) 5231 throws LDAPException 5232 { 5233 synchronized (entryMap) 5234 { 5235 final DN parsedDN; 5236 final Schema schema = schemaRef.get(); 5237 try 5238 { 5239 parsedDN = new DN(baseDN, schema); 5240 } 5241 catch (final LDAPException le) 5242 { 5243 Debug.debugException(le); 5244 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 5245 ERR_MEM_HANDLER_SEARCH_MALFORMED_BASE.get(baseDN, le.getMessage()), 5246 le); 5247 } 5248 5249 final ReadOnlyEntry baseEntry; 5250 if (parsedDN.isNullDN()) 5251 { 5252 baseEntry = generateRootDSE(); 5253 } 5254 else if (parsedDN.equals(subschemaSubentryDN)) 5255 { 5256 baseEntry = subschemaSubentryRef.get(); 5257 } 5258 else 5259 { 5260 final Entry e = entryMap.get(parsedDN); 5261 if (e == null) 5262 { 5263 throw new LDAPException(ResultCode.NO_SUCH_OBJECT, 5264 ERR_MEM_HANDLER_SEARCH_BASE_DOES_NOT_EXIST.get(baseDN), 5265 getMatchedDNString(parsedDN), null); 5266 } 5267 5268 baseEntry = new ReadOnlyEntry(e); 5269 } 5270 5271 if (scope == SearchScope.BASE) 5272 { 5273 final List<ReadOnlyEntry> entryList = new ArrayList<>(1); 5274 5275 try 5276 { 5277 if (filter.matchesEntry(baseEntry, schema)) 5278 { 5279 entryList.add(baseEntry); 5280 } 5281 } 5282 catch (final LDAPException le) 5283 { 5284 Debug.debugException(le); 5285 } 5286 5287 return Collections.unmodifiableList(entryList); 5288 } 5289 5290 if ((scope == SearchScope.ONE) && parsedDN.isNullDN()) 5291 { 5292 final List<ReadOnlyEntry> entryList = 5293 new ArrayList<>(baseDNs.size()); 5294 5295 try 5296 { 5297 for (final DN dn : baseDNs) 5298 { 5299 final Entry e = entryMap.get(dn); 5300 if ((e != null) && filter.matchesEntry(e, schema)) 5301 { 5302 entryList.add(new ReadOnlyEntry(e)); 5303 } 5304 } 5305 } 5306 catch (final LDAPException le) 5307 { 5308 Debug.debugException(le); 5309 } 5310 5311 return Collections.unmodifiableList(entryList); 5312 } 5313 5314 final List<ReadOnlyEntry> entryList = new ArrayList<>(10); 5315 for (final Map.Entry<DN,ReadOnlyEntry> me : entryMap.entrySet()) 5316 { 5317 final DN dn = me.getKey(); 5318 if (dn.matchesBaseAndScope(parsedDN, scope)) 5319 { 5320 // We don't want to return changelog entries searches based at the 5321 // root DSE. 5322 if (parsedDN.isNullDN() && dn.isDescendantOf(changeLogBaseDN, true)) 5323 { 5324 continue; 5325 } 5326 5327 try 5328 { 5329 final Entry entry = me.getValue(); 5330 if (filter.matchesEntry(entry, schema)) 5331 { 5332 entryList.add(new ReadOnlyEntry(entry)); 5333 } 5334 } 5335 catch (final LDAPException le) 5336 { 5337 Debug.debugException(le); 5338 } 5339 } 5340 } 5341 5342 return Collections.unmodifiableList(entryList); 5343 } 5344 } 5345 5346 5347 5348 /** 5349 * Generates an entry to use as the server root DSE. 5350 * 5351 * @return The generated root DSE entry. 5352 */ 5353 @NotNull() 5354 private ReadOnlyEntry generateRootDSE() 5355 { 5356 final ReadOnlyEntry rootDSEFromCfg = config.getRootDSEEntry(); 5357 if (rootDSEFromCfg != null) 5358 { 5359 return rootDSEFromCfg; 5360 } 5361 5362 final Entry rootDSEEntry = new Entry(DN.NULL_DN, schemaRef.get()); 5363 rootDSEEntry.addAttribute("objectClass", "top", "ds-root-dse"); 5364 rootDSEEntry.addAttribute(new Attribute("supportedLDAPVersion", 5365 IntegerMatchingRule.getInstance(), "3")); 5366 5367 final String vendorName = config.getVendorName(); 5368 if (vendorName != null) 5369 { 5370 rootDSEEntry.addAttribute("vendorName", vendorName); 5371 } 5372 5373 final String vendorVersion = config.getVendorVersion(); 5374 if (vendorVersion != null) 5375 { 5376 rootDSEEntry.addAttribute("vendorVersion", vendorVersion); 5377 } 5378 5379 rootDSEEntry.addAttribute(new Attribute("subschemaSubentry", 5380 DistinguishedNameMatchingRule.getInstance(), 5381 subschemaSubentryDN.toString())); 5382 rootDSEEntry.addAttribute(new Attribute("entryDN", 5383 DistinguishedNameMatchingRule.getInstance(), "")); 5384 rootDSEEntry.addAttribute("entryUUID", UUID.randomUUID().toString()); 5385 5386 rootDSEEntry.addAttribute("supportedFeatures", 5387 "1.3.6.1.4.1.4203.1.5.1", // All operational attributes 5388 "1.3.6.1.4.1.4203.1.5.2", // Request attributes by object class 5389 "1.3.6.1.4.1.4203.1.5.3", // LDAP absolute true and false filters 5390 "1.3.6.1.1.14"); // Increment modification type 5391 5392 final TreeSet<String> ctlSet = new TreeSet<>(); 5393 5394 ctlSet.add(AssertionRequestControl.ASSERTION_REQUEST_OID); 5395 ctlSet.add(AuthorizationIdentityRequestControl. 5396 AUTHORIZATION_IDENTITY_REQUEST_OID); 5397 ctlSet.add(DontUseCopyRequestControl.DONT_USE_COPY_REQUEST_OID); 5398 ctlSet.add(ManageDsaITRequestControl.MANAGE_DSA_IT_REQUEST_OID); 5399 ctlSet.add(DraftLDUPSubentriesRequestControl.SUBENTRIES_REQUEST_OID); 5400 ctlSet.add(DraftZeilengaLDAPNoOp12RequestControl.NO_OP_REQUEST_OID); 5401 ctlSet.add(PermissiveModifyRequestControl.PERMISSIVE_MODIFY_REQUEST_OID); 5402 ctlSet.add(PostReadRequestControl.POST_READ_REQUEST_OID); 5403 ctlSet.add(PreReadRequestControl.PRE_READ_REQUEST_OID); 5404 ctlSet.add(ProxiedAuthorizationV1RequestControl. 5405 PROXIED_AUTHORIZATION_V1_REQUEST_OID); 5406 ctlSet.add(ProxiedAuthorizationV2RequestControl. 5407 PROXIED_AUTHORIZATION_V2_REQUEST_OID); 5408 ctlSet.add(RFC3672SubentriesRequestControl.SUBENTRIES_REQUEST_OID); 5409 ctlSet.add(ServerSideSortRequestControl.SERVER_SIDE_SORT_REQUEST_OID); 5410 ctlSet.add(SimplePagedResultsControl.PAGED_RESULTS_OID); 5411 ctlSet.add(SubtreeDeleteRequestControl.SUBTREE_DELETE_REQUEST_OID); 5412 ctlSet.add(TransactionSpecificationRequestControl. 5413 TRANSACTION_SPECIFICATION_REQUEST_OID); 5414 ctlSet.add(VirtualListViewRequestControl.VIRTUAL_LIST_VIEW_REQUEST_OID); 5415 ctlSet.add(IgnoreNoUserModificationRequestControl. 5416 IGNORE_NO_USER_MODIFICATION_REQUEST_OID); 5417 5418 final String[] controlOIDs = new String[ctlSet.size()]; 5419 rootDSEEntry.addAttribute("supportedControl", ctlSet.toArray(controlOIDs)); 5420 5421 5422 if (! extendedRequestHandlers.isEmpty()) 5423 { 5424 final String[] oidArray = new String[extendedRequestHandlers.size()]; 5425 rootDSEEntry.addAttribute("supportedExtension", 5426 extendedRequestHandlers.keySet().toArray(oidArray)); 5427 5428 for (final InMemoryListenerConfig c : config.getListenerConfigs()) 5429 { 5430 if (c.getStartTLSSocketFactory() != null) 5431 { 5432 rootDSEEntry.addAttribute("supportedExtension", 5433 StartTLSExtendedRequest.STARTTLS_REQUEST_OID); 5434 break; 5435 } 5436 } 5437 } 5438 5439 if (! saslBindHandlers.isEmpty()) 5440 { 5441 final String[] mechanismArray = new String[saslBindHandlers.size()]; 5442 rootDSEEntry.addAttribute("supportedSASLMechanisms", 5443 saslBindHandlers.keySet().toArray(mechanismArray)); 5444 } 5445 5446 int pos = 0; 5447 final String[] baseDNStrings = new String[baseDNs.size()]; 5448 for (final DN baseDN : baseDNs) 5449 { 5450 baseDNStrings[pos++] = baseDN.toString(); 5451 } 5452 rootDSEEntry.addAttribute(new Attribute("namingContexts", 5453 DistinguishedNameMatchingRule.getInstance(), baseDNStrings)); 5454 5455 if (maxChangelogEntries > 0) 5456 { 5457 rootDSEEntry.addAttribute(new Attribute("changeLog", 5458 DistinguishedNameMatchingRule.getInstance(), 5459 changeLogBaseDN.toString())); 5460 rootDSEEntry.addAttribute(new Attribute("firstChangeNumber", 5461 IntegerMatchingRule.getInstance(), firstChangeNumber.toString())); 5462 rootDSEEntry.addAttribute(new Attribute("lastChangeNumber", 5463 IntegerMatchingRule.getInstance(), lastChangeNumber.toString())); 5464 } 5465 5466 for (final Attribute customAttribute : config.getCustomRootDSEAttributes()) 5467 { 5468 rootDSEEntry.setAttribute(customAttribute); 5469 } 5470 5471 return new ReadOnlyEntry(rootDSEEntry); 5472 } 5473 5474 5475 5476 /** 5477 * Generates a subschema subentry from the provided schema object. 5478 * 5479 * @param schema The schema to use to generate the subschema subentry. It 5480 * may be {@code null} if a minimal default entry should be 5481 * generated. 5482 * 5483 * @return The generated subschema subentry. 5484 */ 5485 @NotNull() 5486 private static ReadOnlyEntry generateSubschemaSubentry( 5487 @Nullable final Schema schema) 5488 { 5489 final Entry e; 5490 5491 if (schema == null) 5492 { 5493 e = new Entry("cn=schema", schema); 5494 5495 e.addAttribute("objectClass", "namedObject", "ldapSubEntry", 5496 "subschema"); 5497 e.addAttribute("cn", "schema"); 5498 } 5499 else 5500 { 5501 e = schema.getSchemaEntry().duplicate(); 5502 } 5503 5504 try 5505 { 5506 e.addAttribute("entryDN", DN.normalize(e.getDN(), schema)); 5507 } 5508 catch (final LDAPException le) 5509 { 5510 // This should never happen. 5511 Debug.debugException(le); 5512 e.setAttribute("entryDN", StaticUtils.toLowerCase(e.getDN())); 5513 } 5514 5515 5516 e.addAttribute("entryUUID", UUID.randomUUID().toString()); 5517 return new ReadOnlyEntry(e); 5518 } 5519 5520 5521 5522 /** 5523 * Performs the necessary processing to determine whether the given entry 5524 * should be returned as a search result entry or reference, or if it should 5525 * not be returned at all. 5526 * 5527 * @param entry The entry to be processed. 5528 * @param includeSubEntries Indicates whether LDAP subentries should be 5529 * returned to the client. 5530 * @param includeNonSubEntries Indicates whether non-LDAP subentries should 5531 * be returned to the client. 5532 * @param includeChangeLog Indicates whether entries within the 5533 * changelog should be returned to the client. 5534 * @param hasManageDsaIT Indicates whether the request includes the 5535 * ManageDsaIT control, which can change how 5536 * smart referrals should be handled. 5537 * @param entryList The list to which the entry should be added 5538 * if it should be returned to the client as a 5539 * search result entry. 5540 * @param referenceList The list that should be updated if the 5541 * provided entry represents a smart referral 5542 * that should be returned as a search result 5543 * reference. 5544 */ 5545 private void processSearchEntry(@NotNull final Entry entry, 5546 final boolean includeSubEntries, 5547 final boolean includeNonSubEntries, 5548 final boolean includeChangeLog, 5549 final boolean hasManageDsaIT, 5550 @NotNull final List<Entry> entryList, 5551 @NotNull final List<SearchResultReference> referenceList) 5552 { 5553 // Check to see if the entry should be suppressed based on whether it's an 5554 // LDAP subentry. 5555 if (entry.hasObjectClass("ldapSubEntry") || 5556 entry.hasObjectClass("inheritableLDAPSubEntry")) 5557 { 5558 if (! includeSubEntries) 5559 { 5560 return; 5561 } 5562 } 5563 else if (! includeNonSubEntries) 5564 { 5565 return; 5566 } 5567 5568 // See if the entry should be suppressed as a changelog entry. 5569 try 5570 { 5571 if ((! includeChangeLog) && 5572 (entry.getParsedDN().isDescendantOf(changeLogBaseDN, true))) 5573 { 5574 return; 5575 } 5576 } 5577 catch (final Exception e) 5578 { 5579 // This should never happen. 5580 Debug.debugException(e); 5581 } 5582 5583 // See if the entry is a referral and should result in a reference rather 5584 // than an entry. 5585 if ((! hasManageDsaIT) && entry.hasObjectClass("referral") && 5586 entry.hasAttribute("ref")) 5587 { 5588 referenceList.add(new SearchResultReference( 5589 entry.getAttributeValues("ref"), NO_CONTROLS)); 5590 return; 5591 } 5592 5593 entryList.add(entry); 5594 } 5595 5596 5597 5598 /** 5599 * Retrieves the DN of the existing entry which is the closest hierarchical 5600 * match to the provided DN. 5601 * 5602 * @param dn The DN for which to retrieve the appropriate matched DN. 5603 * 5604 * @return The appropriate matched DN value, or {@code null} if there is 5605 * none. 5606 */ 5607 @Nullable() 5608 private String getMatchedDNString(@NotNull final DN dn) 5609 { 5610 DN parentDN = dn.getParent(); 5611 while (parentDN != null) 5612 { 5613 if (entryMap.containsKey(parentDN)) 5614 { 5615 return parentDN.toString(); 5616 } 5617 5618 parentDN = parentDN.getParent(); 5619 } 5620 5621 return null; 5622 } 5623 5624 5625 5626 /** 5627 * Converts the provided string list to an array. 5628 * 5629 * @param l The possibly null list to be converted. 5630 * 5631 * @return The string array with the same elements as the given list in the 5632 * same order, or {@code null} if the given list was null. 5633 */ 5634 @Nullable() 5635 private static String[] stringListToArray(@Nullable final List<String> l) 5636 { 5637 if (l == null) 5638 { 5639 return null; 5640 } 5641 else 5642 { 5643 final String[] a = new String[l.size()]; 5644 return l.toArray(a); 5645 } 5646 } 5647 5648 5649 5650 /** 5651 * Creates a changelog entry from the information in the provided add request 5652 * and adds it to the server changelog. 5653 * 5654 * @param addRequest The add request to use to construct the changelog 5655 * entry. 5656 * @param authzDN The authorization DN for the change. 5657 */ 5658 private void addChangeLogEntry(@NotNull final AddRequestProtocolOp addRequest, 5659 @NotNull final DN authzDN) 5660 { 5661 // If the changelog is disabled, then don't do anything. 5662 if (maxChangelogEntries <= 0) 5663 { 5664 return; 5665 } 5666 5667 final long changeNumber = lastChangeNumber.incrementAndGet(); 5668 final LDIFAddChangeRecord changeRecord = new LDIFAddChangeRecord( 5669 addRequest.getDN(), addRequest.getAttributes()); 5670 try 5671 { 5672 addChangeLogEntry( 5673 ChangeLogEntry.constructChangeLogEntry(changeNumber, changeRecord), 5674 authzDN); 5675 } 5676 catch (final LDAPException le) 5677 { 5678 // This should not happen. 5679 Debug.debugException(le); 5680 } 5681 } 5682 5683 5684 5685 /** 5686 * Creates a changelog entry from the information in the provided delete 5687 * request and adds it to the server changelog. 5688 * 5689 * @param e The entry to be deleted. 5690 * @param authzDN The authorization DN for the change. 5691 */ 5692 private void addDeleteChangeLogEntry(@NotNull final Entry e, 5693 @NotNull final DN authzDN) 5694 { 5695 // If the changelog is disabled, then don't do anything. 5696 if (maxChangelogEntries <= 0) 5697 { 5698 return; 5699 } 5700 5701 final long changeNumber = lastChangeNumber.incrementAndGet(); 5702 final LDIFDeleteChangeRecord changeRecord = 5703 new LDIFDeleteChangeRecord(e.getDN()); 5704 5705 // Create the changelog entry. 5706 try 5707 { 5708 final ChangeLogEntry cle = ChangeLogEntry.constructChangeLogEntry( 5709 changeNumber, changeRecord); 5710 5711 // Add a set of deleted entry attributes, which is simply an LDIF-encoded 5712 // representation of the entry, excluding the first line since it contains 5713 // the DN. 5714 final StringBuilder deletedEntryAttrsBuffer = new StringBuilder(); 5715 final String[] ldifLines = e.toLDIF(0); 5716 for (int i=1; i < ldifLines.length; i++) 5717 { 5718 deletedEntryAttrsBuffer.append(ldifLines[i]); 5719 deletedEntryAttrsBuffer.append(StaticUtils.EOL); 5720 } 5721 5722 final Entry copy = cle.duplicate(); 5723 copy.addAttribute(ChangeLogEntry.ATTR_DELETED_ENTRY_ATTRS, 5724 deletedEntryAttrsBuffer.toString()); 5725 addChangeLogEntry(new ChangeLogEntry(copy), authzDN); 5726 } 5727 catch (final LDAPException le) 5728 { 5729 // This should never happen. 5730 Debug.debugException(le); 5731 } 5732 } 5733 5734 5735 5736 /** 5737 * Creates a changelog entry from the information in the provided modify 5738 * request and adds it to the server changelog. 5739 * 5740 * @param modifyRequest The modify request to use to construct the changelog 5741 * entry. 5742 * @param authzDN The authorization DN for the change. 5743 */ 5744 private void addChangeLogEntry( 5745 @NotNull final ModifyRequestProtocolOp modifyRequest, 5746 @NotNull final DN authzDN) 5747 { 5748 // If the changelog is disabled, then don't do anything. 5749 if (maxChangelogEntries <= 0) 5750 { 5751 return; 5752 } 5753 5754 final long changeNumber = lastChangeNumber.incrementAndGet(); 5755 final LDIFModifyChangeRecord changeRecord = 5756 new LDIFModifyChangeRecord(modifyRequest.getDN(), 5757 modifyRequest.getModifications()); 5758 try 5759 { 5760 addChangeLogEntry( 5761 ChangeLogEntry.constructChangeLogEntry(changeNumber, changeRecord), 5762 authzDN); 5763 } 5764 catch (final LDAPException le) 5765 { 5766 // This should not happen. 5767 Debug.debugException(le); 5768 } 5769 } 5770 5771 5772 5773 /** 5774 * Creates a changelog entry from the information in the provided modify DN 5775 * request and adds it to the server changelog. 5776 * 5777 * @param modifyDNRequest The modify DN request to use to construct the 5778 * changelog entry. 5779 * @param authzDN The authorization DN for the change. 5780 */ 5781 private void addChangeLogEntry( 5782 @NotNull final ModifyDNRequestProtocolOp modifyDNRequest, 5783 @NotNull final DN authzDN) 5784 { 5785 // If the changelog is disabled, then don't do anything. 5786 if (maxChangelogEntries <= 0) 5787 { 5788 return; 5789 } 5790 5791 final long changeNumber = lastChangeNumber.incrementAndGet(); 5792 final LDIFModifyDNChangeRecord changeRecord = 5793 new LDIFModifyDNChangeRecord(modifyDNRequest.getDN(), 5794 modifyDNRequest.getNewRDN(), modifyDNRequest.deleteOldRDN(), 5795 modifyDNRequest.getNewSuperiorDN()); 5796 try 5797 { 5798 addChangeLogEntry( 5799 ChangeLogEntry.constructChangeLogEntry(changeNumber, changeRecord), 5800 authzDN); 5801 } 5802 catch (final LDAPException le) 5803 { 5804 // This should not happen. 5805 Debug.debugException(le); 5806 } 5807 } 5808 5809 5810 5811 /** 5812 * Adds the provided changelog entry to the data set, removing an old entry if 5813 * necessary to remain within the maximum allowed number of changes. This 5814 * must only be called from a synchronized method, and the change number for 5815 * the changelog entry must have been obtained by calling 5816 * {@code lastChangeNumber.incrementAndGet()}. 5817 * 5818 * @param e The changelog entry to add to the data set. 5819 * @param authzDN The authorization DN for the change. 5820 */ 5821 private void addChangeLogEntry(@NotNull final ChangeLogEntry e, 5822 @NotNull final DN authzDN) 5823 { 5824 // Construct the DN object to use for the entry and put it in the map. 5825 final long changeNumber = e.getChangeNumber(); 5826 final Schema schema = schemaRef.get(); 5827 final DN dn = new DN( 5828 new RDN("changeNumber", String.valueOf(changeNumber), schema), 5829 changeLogBaseDN); 5830 5831 final Entry entry = e.duplicate(); 5832 if (generateOperationalAttributes) 5833 { 5834 final Date d = new Date(); 5835 entry.addAttribute(new Attribute("entryDN", 5836 DistinguishedNameMatchingRule.getInstance(), 5837 dn.toNormalizedString())); 5838 entry.addAttribute(new Attribute("entryUUID", 5839 UUID.randomUUID().toString())); 5840 entry.addAttribute(new Attribute("subschemaSubentry", 5841 DistinguishedNameMatchingRule.getInstance(), 5842 subschemaSubentryDN.toString())); 5843 entry.addAttribute(new Attribute("creatorsName", 5844 DistinguishedNameMatchingRule.getInstance(), 5845 authzDN.toString())); 5846 entry.addAttribute(new Attribute("createTimestamp", 5847 GeneralizedTimeMatchingRule.getInstance(), 5848 StaticUtils.encodeGeneralizedTime(d))); 5849 entry.addAttribute(new Attribute("modifiersName", 5850 DistinguishedNameMatchingRule.getInstance(), 5851 authzDN.toString())); 5852 entry.addAttribute(new Attribute("modifyTimestamp", 5853 GeneralizedTimeMatchingRule.getInstance(), 5854 StaticUtils.encodeGeneralizedTime(d))); 5855 } 5856 5857 entryMap.put(dn, new ReadOnlyEntry(entry)); 5858 indexAdd(entry); 5859 5860 // Update the first change number and/or trim the changelog if necessary. 5861 final long firstNumber = firstChangeNumber.get(); 5862 if (changeNumber == 1L) 5863 { 5864 // It's the first change, so we need to set the first change number. 5865 firstChangeNumber.set(1); 5866 } 5867 else 5868 { 5869 // See if we need to trim an entry. 5870 final long numChangeLogEntries = changeNumber - firstNumber + 1; 5871 if (numChangeLogEntries > maxChangelogEntries) 5872 { 5873 // We need to delete the first changelog entry and increment the 5874 // first change number. 5875 firstChangeNumber.incrementAndGet(); 5876 final Entry deletedEntry = entryMap.remove(new DN( 5877 new RDN("changeNumber", String.valueOf(firstNumber), schema), 5878 changeLogBaseDN)); 5879 indexDelete(deletedEntry); 5880 } 5881 } 5882 } 5883 5884 5885 5886 /** 5887 * Checks to see if the provided control map includes a proxied authorization 5888 * control (v1 or v2) and if so then attempts to determine the appropriate 5889 * authorization identity to use for the operation. 5890 * 5891 * @param m The map of request controls, indexed by OID. 5892 * 5893 * @return The DN of the authorized user, or the current authentication DN 5894 * if the control map does not include a proxied authorization 5895 * request control. 5896 * 5897 * @throws LDAPException If a problem is encountered while attempting to 5898 * determine the authorization DN. 5899 */ 5900 @NotNull() 5901 private DN handleProxiedAuthControl(@NotNull final Map<String,Control> m) 5902 throws LDAPException 5903 { 5904 final ProxiedAuthorizationV1RequestControl p1 = 5905 (ProxiedAuthorizationV1RequestControl) m.get( 5906 ProxiedAuthorizationV1RequestControl. 5907 PROXIED_AUTHORIZATION_V1_REQUEST_OID); 5908 if (p1 != null) 5909 { 5910 final DN authzDN = new DN(p1.getProxyDN(), schemaRef.get()); 5911 if (authzDN.isNullDN() || 5912 entryMap.containsKey(authzDN) || 5913 additionalBindCredentials.containsKey(authzDN)) 5914 { 5915 return authzDN; 5916 } 5917 else 5918 { 5919 throw new LDAPException(ResultCode.AUTHORIZATION_DENIED, 5920 ERR_MEM_HANDLER_NO_SUCH_IDENTITY.get("dn:" + authzDN.toString())); 5921 } 5922 } 5923 5924 final ProxiedAuthorizationV2RequestControl p2 = 5925 (ProxiedAuthorizationV2RequestControl) m.get( 5926 ProxiedAuthorizationV2RequestControl. 5927 PROXIED_AUTHORIZATION_V2_REQUEST_OID); 5928 if (p2 != null) 5929 { 5930 return getDNForAuthzID(p2.getAuthorizationID()); 5931 } 5932 5933 return authenticatedDN; 5934 } 5935 5936 5937 5938 /** 5939 * Attempts to identify the DN of the user referenced by the provided 5940 * authorization ID string. It may be "dn:" followed by the target DN, or 5941 * "u:" followed by the value of the uid attribute in the entry. If it uses 5942 * the "dn:" form, then it may reference the DN of a regular entry or a DN 5943 * in the configured set of additional bind credentials. 5944 * 5945 * @param authzID The authorization ID to resolve to a user DN. 5946 * 5947 * @return The DN identified for the provided authorization ID. 5948 * 5949 * @throws LDAPException If a problem prevents resolving the authorization 5950 * ID to a user DN. 5951 */ 5952 @NotNull() 5953 public DN getDNForAuthzID(@NotNull final String authzID) 5954 throws LDAPException 5955 { 5956 synchronized (entryMap) 5957 { 5958 final String lowerAuthzID = StaticUtils.toLowerCase(authzID); 5959 if (lowerAuthzID.startsWith("dn:")) 5960 { 5961 if (lowerAuthzID.equals("dn:")) 5962 { 5963 return DN.NULL_DN; 5964 } 5965 else 5966 { 5967 final DN dn = new DN(authzID.substring(3), schemaRef.get()); 5968 if (entryMap.containsKey(dn) || 5969 additionalBindCredentials.containsKey(dn)) 5970 { 5971 return dn; 5972 } 5973 else 5974 { 5975 throw new LDAPException(ResultCode.AUTHORIZATION_DENIED, 5976 ERR_MEM_HANDLER_NO_SUCH_IDENTITY.get(authzID)); 5977 } 5978 } 5979 } 5980 else if (lowerAuthzID.startsWith("u:")) 5981 { 5982 final Filter f = 5983 Filter.createEqualityFilter("uid", authzID.substring(2)); 5984 final List<ReadOnlyEntry> entryList = search("", SearchScope.SUB, f); 5985 if (entryList.size() == 1) 5986 { 5987 return entryList.get(0).getParsedDN(); 5988 } 5989 else 5990 { 5991 throw new LDAPException(ResultCode.AUTHORIZATION_DENIED, 5992 ERR_MEM_HANDLER_NO_SUCH_IDENTITY.get(authzID)); 5993 } 5994 } 5995 else 5996 { 5997 throw new LDAPException(ResultCode.AUTHORIZATION_DENIED, 5998 ERR_MEM_HANDLER_NO_SUCH_IDENTITY.get(authzID)); 5999 } 6000 } 6001 } 6002 6003 6004 6005 /** 6006 * Checks to see if the provided control map includes an assertion request 6007 * control, and if so then checks to see whether the provided entry satisfies 6008 * the filter in that control. 6009 * 6010 * @param m The map of request controls, indexed by OID. 6011 * @param e The entry to examine against the assertion filter. 6012 * 6013 * @throws LDAPException If the control map includes an assertion request 6014 * control and the provided entry does not match the 6015 * filter contained in that control. 6016 */ 6017 private static void handleAssertionRequestControl( 6018 @NotNull final Map<String,Control> m, 6019 @NotNull final Entry e) 6020 throws LDAPException 6021 { 6022 final AssertionRequestControl c = (AssertionRequestControl) 6023 m.get(AssertionRequestControl.ASSERTION_REQUEST_OID); 6024 if (c == null) 6025 { 6026 return; 6027 } 6028 6029 try 6030 { 6031 if (c.getFilter().matchesEntry(e)) 6032 { 6033 return; 6034 } 6035 } 6036 catch (final LDAPException le) 6037 { 6038 Debug.debugException(le); 6039 } 6040 6041 // If we've gotten here, then the filter doesn't match. 6042 throw new LDAPException(ResultCode.ASSERTION_FAILED, 6043 ERR_MEM_HANDLER_ASSERTION_CONTROL_NOT_SATISFIED.get()); 6044 } 6045 6046 6047 6048 /** 6049 * Checks to see if the provided control map includes a pre-read request 6050 * control, and if so then generates the appropriate response control that 6051 * should be returned to the client. 6052 * 6053 * @param m The map of request controls, indexed by OID. 6054 * @param e The entry as it appeared before the operation. 6055 * 6056 * @return The pre-read response control that should be returned to the 6057 * client, or {@code null} if there is none. 6058 */ 6059 @Nullable() 6060 private PreReadResponseControl handlePreReadControl( 6061 @NotNull final Map<String,Control> m, @NotNull final Entry e) 6062 { 6063 final PreReadRequestControl c = (PreReadRequestControl) 6064 m.get(PreReadRequestControl.PRE_READ_REQUEST_OID); 6065 if (c == null) 6066 { 6067 return null; 6068 } 6069 6070 final SearchEntryParer parer = new SearchEntryParer( 6071 Arrays.asList(c.getAttributes()), schemaRef.get()); 6072 final Entry trimmedEntry = parer.pareEntry(e); 6073 return new PreReadResponseControl(new ReadOnlyEntry(trimmedEntry)); 6074 } 6075 6076 6077 6078 /** 6079 * Checks to see if the provided control map includes a post-read request 6080 * control, and if so then generates the appropriate response control that 6081 * should be returned to the client. 6082 * 6083 * @param m The map of request controls, indexed by OID. 6084 * @param e The entry as it appeared before the operation. 6085 * 6086 * @return The post-read response control that should be returned to the 6087 * client, or {@code null} if there is none. 6088 */ 6089 @Nullable() 6090 private PostReadResponseControl handlePostReadControl( 6091 @NotNull final Map<String,Control> m, @NotNull final Entry e) 6092 { 6093 final PostReadRequestControl c = (PostReadRequestControl) 6094 m.get(PostReadRequestControl.POST_READ_REQUEST_OID); 6095 if (c == null) 6096 { 6097 return null; 6098 } 6099 6100 final SearchEntryParer parer = new SearchEntryParer( 6101 Arrays.asList(c.getAttributes()), schemaRef.get()); 6102 final Entry trimmedEntry = parer.pareEntry(e); 6103 return new PostReadResponseControl(new ReadOnlyEntry(trimmedEntry)); 6104 } 6105 6106 6107 6108 /** 6109 * Finds the smart referral entry which is hierarchically nearest the entry 6110 * with the given DN. 6111 * 6112 * @param dn The DN for which to find the hierarchically nearest smart 6113 * referral entry. 6114 * 6115 * @return The hierarchically nearest smart referral entry for the provided 6116 * DN, or {@code null} if there are no smart referral entries with 6117 * the provided DN or any of its ancestors. 6118 */ 6119 @Nullable() 6120 private Entry findNearestReferral(@NotNull final DN dn) 6121 { 6122 DN d = dn; 6123 while (true) 6124 { 6125 final Entry e = entryMap.get(d); 6126 if (e == null) 6127 { 6128 d = d.getParent(); 6129 if (d == null) 6130 { 6131 return null; 6132 } 6133 } 6134 else if (e.hasObjectClass("referral")) 6135 { 6136 return e; 6137 } 6138 else 6139 { 6140 return null; 6141 } 6142 } 6143 } 6144 6145 6146 6147 /** 6148 * Retrieves the referral URLs that should be used for the provided target DN 6149 * based on the given referral entry. 6150 * 6151 * @param targetDN The target DN from the associated operation. 6152 * @param referralEntry The entry containing the smart referral. 6153 * 6154 * @return The referral URLs that should be returned. 6155 */ 6156 @Nullable() 6157 private static List<String> getReferralURLs(@NotNull final DN targetDN, 6158 @NotNull final Entry referralEntry) 6159 { 6160 final String[] refs = referralEntry.getAttributeValues("ref"); 6161 if (refs == null) 6162 { 6163 return null; 6164 } 6165 6166 final RDN[] retainRDNs; 6167 try 6168 { 6169 // If the target DN equals the referral entry DN, or if it's not 6170 // subordinate to the referral entry, then the URLs should be returned 6171 // as-is. 6172 final DN parsedEntryDN = referralEntry.getParsedDN(); 6173 if (targetDN.equals(parsedEntryDN) || 6174 (! targetDN.isDescendantOf(parsedEntryDN, true))) 6175 { 6176 return Arrays.asList(refs); 6177 } 6178 6179 final RDN[] targetRDNs = targetDN.getRDNs(); 6180 final RDN[] refEntryRDNs = referralEntry.getParsedDN().getRDNs(); 6181 retainRDNs = new RDN[targetRDNs.length - refEntryRDNs.length]; 6182 System.arraycopy(targetRDNs, 0, retainRDNs, 0, retainRDNs.length); 6183 } 6184 catch (final LDAPException le) 6185 { 6186 Debug.debugException(le); 6187 return Arrays.asList(refs); 6188 } 6189 6190 final List<String> refList = new ArrayList<>(refs.length); 6191 for (final String ref : refs) 6192 { 6193 try 6194 { 6195 final LDAPURL url = new LDAPURL(ref); 6196 final RDN[] refRDNs = url.getBaseDN().getRDNs(); 6197 final RDN[] newRefRDNs = new RDN[retainRDNs.length + refRDNs.length]; 6198 System.arraycopy(retainRDNs, 0, newRefRDNs, 0, retainRDNs.length); 6199 System.arraycopy(refRDNs, 0, newRefRDNs, retainRDNs.length, 6200 refRDNs.length); 6201 final DN newBaseDN = new DN(newRefRDNs); 6202 6203 final LDAPURL newURL = new LDAPURL(url.getScheme(), url.getHost(), 6204 url.getPort(), newBaseDN, null, null, null); 6205 refList.add(newURL.toString()); 6206 } 6207 catch (final LDAPException le) 6208 { 6209 Debug.debugException(le); 6210 refList.add(ref); 6211 } 6212 } 6213 6214 return refList; 6215 } 6216 6217 6218 6219 /** 6220 * Indicates whether the specified entry exists in the server. 6221 * 6222 * @param dn The DN of the entry for which to make the determination. 6223 * 6224 * @return {@code true} if the entry exists, or {@code false} if not. 6225 * 6226 * @throws LDAPException If a problem is encountered while trying to 6227 * communicate with the directory server. 6228 */ 6229 public boolean entryExists(@NotNull final String dn) 6230 throws LDAPException 6231 { 6232 return (getEntry(dn) != null); 6233 } 6234 6235 6236 6237 /** 6238 * Indicates whether the specified entry exists in the server and matches the 6239 * given filter. 6240 * 6241 * @param dn The DN of the entry for which to make the determination. 6242 * @param filter The filter the entry is expected to match. 6243 * 6244 * @return {@code true} if the entry exists and matches the specified filter, 6245 * or {@code false} if not. 6246 * 6247 * @throws LDAPException If a problem is encountered while trying to 6248 * communicate with the directory server. 6249 */ 6250 public boolean entryExists(@NotNull final String dn, 6251 @NotNull final String filter) 6252 throws LDAPException 6253 { 6254 synchronized (entryMap) 6255 { 6256 final Entry e = getEntry(dn); 6257 if (e == null) 6258 { 6259 return false; 6260 } 6261 6262 final Filter f = Filter.create(filter); 6263 try 6264 { 6265 return f.matchesEntry(e, schemaRef.get()); 6266 } 6267 catch (final LDAPException le) 6268 { 6269 Debug.debugException(le); 6270 return false; 6271 } 6272 } 6273 } 6274 6275 6276 6277 /** 6278 * Indicates whether the specified entry exists in the server. This will 6279 * return {@code true} only if the target entry exists and contains all values 6280 * for all attributes of the provided entry. The entry will be allowed to 6281 * have attribute values not included in the provided entry. 6282 * 6283 * @param entry The entry to compare against the directory server. 6284 * 6285 * @return {@code true} if the entry exists in the server and is a superset 6286 * of the provided entry, or {@code false} if not. 6287 * 6288 * @throws LDAPException If a problem is encountered while trying to 6289 * communicate with the directory server. 6290 */ 6291 public boolean entryExists(@NotNull final Entry entry) 6292 throws LDAPException 6293 { 6294 synchronized (entryMap) 6295 { 6296 final Entry e = getEntry(entry.getDN()); 6297 if (e == null) 6298 { 6299 return false; 6300 } 6301 6302 for (final Attribute a : entry.getAttributes()) 6303 { 6304 for (final byte[] value : a.getValueByteArrays()) 6305 { 6306 if (! e.hasAttributeValue(a.getName(), value)) 6307 { 6308 return false; 6309 } 6310 } 6311 } 6312 6313 return true; 6314 } 6315 } 6316 6317 6318 6319 /** 6320 * Ensures that an entry with the provided DN exists in the directory. 6321 * 6322 * @param dn The DN of the entry for which to make the determination. 6323 * 6324 * @throws LDAPException If a problem is encountered while trying to 6325 * communicate with the directory server. 6326 * 6327 * @throws AssertionError If the target entry does not exist. 6328 */ 6329 public void assertEntryExists(@NotNull final String dn) 6330 throws LDAPException, AssertionError 6331 { 6332 final Entry e = getEntry(dn); 6333 if (e == null) 6334 { 6335 throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn)); 6336 } 6337 } 6338 6339 6340 6341 /** 6342 * Ensures that an entry with the provided DN exists in the directory. 6343 * 6344 * @param dn The DN of the entry for which to make the determination. 6345 * @param filter A filter that the target entry must match. 6346 * 6347 * @throws LDAPException If a problem is encountered while trying to 6348 * communicate with the directory server. 6349 * 6350 * @throws AssertionError If the target entry does not exist or does not 6351 * match the provided filter. 6352 */ 6353 public void assertEntryExists(@NotNull final String dn, 6354 @NotNull final String filter) 6355 throws LDAPException, AssertionError 6356 { 6357 synchronized (entryMap) 6358 { 6359 final Entry e = getEntry(dn); 6360 if (e == null) 6361 { 6362 throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn)); 6363 } 6364 6365 final Filter f = Filter.create(filter); 6366 try 6367 { 6368 if (! f.matchesEntry(e, schemaRef.get())) 6369 { 6370 throw new AssertionError( 6371 ERR_MEM_HANDLER_TEST_ENTRY_DOES_NOT_MATCH_FILTER.get(dn, 6372 filter)); 6373 } 6374 } 6375 catch (final LDAPException le) 6376 { 6377 Debug.debugException(le); 6378 throw new AssertionError( 6379 ERR_MEM_HANDLER_TEST_ENTRY_DOES_NOT_MATCH_FILTER.get(dn, filter)); 6380 } 6381 } 6382 } 6383 6384 6385 6386 /** 6387 * Ensures that an entry exists in the directory with the same DN and all 6388 * attribute values contained in the provided entry. The server entry may 6389 * contain additional attributes and/or attribute values not included in the 6390 * provided entry. 6391 * 6392 * @param entry The entry expected to be present in the directory server. 6393 * 6394 * @throws LDAPException If a problem is encountered while trying to 6395 * communicate with the directory server. 6396 * 6397 * @throws AssertionError If the target entry does not exist or does not 6398 * match the provided filter. 6399 */ 6400 public void assertEntryExists(@NotNull final Entry entry) 6401 throws LDAPException, AssertionError 6402 { 6403 synchronized (entryMap) 6404 { 6405 final Entry e = getEntry(entry.getDN()); 6406 if (e == null) 6407 { 6408 throw new AssertionError( 6409 ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(entry.getDN())); 6410 } 6411 6412 6413 final Collection<Attribute> attrs = entry.getAttributes(); 6414 final List<String> messages = new ArrayList<>(attrs.size()); 6415 6416 final Schema schema = schemaRef.get(); 6417 for (final Attribute a : entry.getAttributes()) 6418 { 6419 final Filter presFilter = Filter.createPresenceFilter(a.getName()); 6420 if (! presFilter.matchesEntry(e, schema)) 6421 { 6422 messages.add(ERR_MEM_HANDLER_TEST_ATTR_MISSING.get(entry.getDN(), 6423 a.getName())); 6424 continue; 6425 } 6426 6427 for (final byte[] value : a.getValueByteArrays()) 6428 { 6429 final Filter eqFilter = Filter.createEqualityFilter(a.getName(), 6430 value); 6431 if (! eqFilter.matchesEntry(e, schema)) 6432 { 6433 messages.add(ERR_MEM_HANDLER_TEST_VALUE_MISSING.get(entry.getDN(), 6434 a.getName(), StaticUtils.toUTF8String(value))); 6435 } 6436 } 6437 } 6438 6439 if (! messages.isEmpty()) 6440 { 6441 throw new AssertionError(StaticUtils.concatenateStrings(messages)); 6442 } 6443 } 6444 } 6445 6446 6447 6448 /** 6449 * Retrieves a list containing the DNs of the entries which are missing from 6450 * the directory server. 6451 * 6452 * @param dns The DNs of the entries to try to find in the server. 6453 * 6454 * @return A list containing all of the provided DNs that were not found in 6455 * the server, or an empty list if all entries were found. 6456 * 6457 * @throws LDAPException If a problem is encountered while trying to 6458 * communicate with the directory server. 6459 */ 6460 @NotNull() 6461 public List<String> getMissingEntryDNs(@NotNull final Collection<String> dns) 6462 throws LDAPException 6463 { 6464 synchronized (entryMap) 6465 { 6466 final List<String> missingDNs = new ArrayList<>(dns.size()); 6467 for (final String dn : dns) 6468 { 6469 final Entry e = getEntry(dn); 6470 if (e == null) 6471 { 6472 missingDNs.add(dn); 6473 } 6474 } 6475 6476 return missingDNs; 6477 } 6478 } 6479 6480 6481 6482 /** 6483 * Ensures that all of the entries with the provided DNs exist in the 6484 * directory. 6485 * 6486 * @param dns The DNs of the entries for which to make the determination. 6487 * 6488 * @throws LDAPException If a problem is encountered while trying to 6489 * communicate with the directory server. 6490 * 6491 * @throws AssertionError If any of the target entries does not exist. 6492 */ 6493 public void assertEntriesExist(@NotNull final Collection<String> dns) 6494 throws LDAPException, AssertionError 6495 { 6496 synchronized (entryMap) 6497 { 6498 final List<String> missingDNs = getMissingEntryDNs(dns); 6499 if (missingDNs.isEmpty()) 6500 { 6501 return; 6502 } 6503 6504 final List<String> messages = new ArrayList<>(missingDNs.size()); 6505 for (final String dn : missingDNs) 6506 { 6507 messages.add(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn)); 6508 } 6509 6510 throw new AssertionError(StaticUtils.concatenateStrings(messages)); 6511 } 6512 } 6513 6514 6515 6516 /** 6517 * Retrieves a list containing all of the named attributes which do not exist 6518 * in the target entry. 6519 * 6520 * @param dn The DN of the entry to examine. 6521 * @param attributeNames The names of the attributes expected to be present 6522 * in the target entry. 6523 * 6524 * @return A list containing the names of the attributes which were not 6525 * present in the target entry, an empty list if all specified 6526 * attributes were found in the entry, or {@code null} if the target 6527 * entry does not exist. 6528 * 6529 * @throws LDAPException If a problem is encountered while trying to 6530 * communicate with the directory server. 6531 */ 6532 @Nullable() 6533 public List<String> getMissingAttributeNames(@NotNull final String dn, 6534 @NotNull final Collection<String> attributeNames) 6535 throws LDAPException 6536 { 6537 synchronized (entryMap) 6538 { 6539 final Entry e = getEntry(dn); 6540 if (e == null) 6541 { 6542 return null; 6543 } 6544 6545 final Schema schema = schemaRef.get(); 6546 final List<String> missingAttrs = 6547 new ArrayList<>(attributeNames.size()); 6548 for (final String attr : attributeNames) 6549 { 6550 final Filter f = Filter.createPresenceFilter(attr); 6551 if (! f.matchesEntry(e, schema)) 6552 { 6553 missingAttrs.add(attr); 6554 } 6555 } 6556 6557 return missingAttrs; 6558 } 6559 } 6560 6561 6562 6563 /** 6564 * Ensures that the specified entry exists in the directory with all of the 6565 * specified attributes. 6566 * 6567 * @param dn The DN of the entry to examine. 6568 * @param attributeNames The names of the attributes that are expected to be 6569 * present in the provided entry. 6570 * 6571 * @throws LDAPException If a problem is encountered while trying to 6572 * communicate with the directory server. 6573 * 6574 * @throws AssertionError If the target entry does not exist or does not 6575 * contain all of the specified attributes. 6576 */ 6577 public void assertAttributeExists(@NotNull final String dn, 6578 @NotNull final Collection<String> attributeNames) 6579 throws LDAPException, AssertionError 6580 { 6581 synchronized (entryMap) 6582 { 6583 final List<String> missingAttrs = 6584 getMissingAttributeNames(dn, attributeNames); 6585 if (missingAttrs == null) 6586 { 6587 throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn)); 6588 } 6589 else if (missingAttrs.isEmpty()) 6590 { 6591 return; 6592 } 6593 6594 final List<String> messages = new ArrayList<>(missingAttrs.size()); 6595 for (final String attr : missingAttrs) 6596 { 6597 messages.add(ERR_MEM_HANDLER_TEST_ATTR_MISSING.get(dn, attr)); 6598 } 6599 6600 throw new AssertionError(StaticUtils.concatenateStrings(messages)); 6601 } 6602 } 6603 6604 6605 6606 /** 6607 * Retrieves a list of all provided attribute values which are missing from 6608 * the specified entry. The target attribute may or may not contain 6609 * additional values. 6610 * 6611 * @param dn The DN of the entry to examine. 6612 * @param attributeName The attribute expected to be present in the target 6613 * entry with the given values. 6614 * @param attributeValues The values expected to be present in the target 6615 * entry. 6616 * 6617 * @return A list containing all of the provided values which were not found 6618 * in the entry, an empty list if all provided attribute values were 6619 * found, or {@code null} if the target entry does not exist. 6620 * 6621 * @throws LDAPException If a problem is encountered while trying to 6622 * communicate with the directory server. 6623 */ 6624 @Nullable() 6625 public List<String> getMissingAttributeValues(@NotNull final String dn, 6626 @NotNull final String attributeName, 6627 @NotNull final Collection<String> attributeValues) 6628 throws LDAPException 6629 { 6630 synchronized (entryMap) 6631 { 6632 final Entry e = getEntry(dn); 6633 if (e == null) 6634 { 6635 return null; 6636 } 6637 6638 final Schema schema = schemaRef.get(); 6639 final List<String> missingValues = 6640 new ArrayList<>(attributeValues.size()); 6641 for (final String value : attributeValues) 6642 { 6643 final Filter f = Filter.createEqualityFilter(attributeName, value); 6644 if (! f.matchesEntry(e, schema)) 6645 { 6646 missingValues.add(value); 6647 } 6648 } 6649 6650 return missingValues; 6651 } 6652 } 6653 6654 6655 6656 /** 6657 * Ensures that the specified entry exists in the directory with all of the 6658 * specified values for the given attribute. The attribute may or may not 6659 * contain additional values. 6660 * 6661 * @param dn The DN of the entry to examine. 6662 * @param attributeName The name of the attribute to examine. 6663 * @param attributeValues The set of values which must exist for the given 6664 * attribute. 6665 * 6666 * @throws LDAPException If a problem is encountered while trying to 6667 * communicate with the directory server. 6668 * 6669 * @throws AssertionError If the target entry does not exist, does not 6670 * contain the specified attribute, or that attribute 6671 * does not have all of the specified values. 6672 */ 6673 public void assertValueExists(@NotNull final String dn, 6674 @NotNull final String attributeName, 6675 @NotNull final Collection<String> attributeValues) 6676 throws LDAPException, AssertionError 6677 { 6678 synchronized (entryMap) 6679 { 6680 final List<String> missingValues = 6681 getMissingAttributeValues(dn, attributeName, attributeValues); 6682 if (missingValues == null) 6683 { 6684 throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn)); 6685 } 6686 else if (missingValues.isEmpty()) 6687 { 6688 return; 6689 } 6690 6691 // See if the attribute exists at all in the entry. 6692 final Entry e = getEntry(dn); 6693 final Filter f = Filter.createPresenceFilter(attributeName); 6694 if (! f.matchesEntry(e, schemaRef.get())) 6695 { 6696 throw new AssertionError( 6697 ERR_MEM_HANDLER_TEST_ATTR_MISSING.get(dn, attributeName)); 6698 } 6699 6700 final List<String> messages = new ArrayList<>(missingValues.size()); 6701 for (final String value : missingValues) 6702 { 6703 messages.add(ERR_MEM_HANDLER_TEST_VALUE_MISSING.get(dn, attributeName, 6704 value)); 6705 } 6706 6707 throw new AssertionError(StaticUtils.concatenateStrings(messages)); 6708 } 6709 } 6710 6711 6712 6713 /** 6714 * Ensures that the specified entry does not exist in the directory. 6715 * 6716 * @param dn The DN of the entry expected to be missing. 6717 * 6718 * @throws LDAPException If a problem is encountered while trying to 6719 * communicate with the directory server. 6720 * 6721 * @throws AssertionError If the target entry is found in the server. 6722 */ 6723 public void assertEntryMissing(@NotNull final String dn) 6724 throws LDAPException, AssertionError 6725 { 6726 final Entry e = getEntry(dn); 6727 if (e != null) 6728 { 6729 throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_EXISTS.get(dn)); 6730 } 6731 } 6732 6733 6734 6735 /** 6736 * Ensures that the specified entry exists in the directory but does not 6737 * contain any of the specified attributes. 6738 * 6739 * @param dn The DN of the entry expected to be present. 6740 * @param attributeNames The names of the attributes expected to be missing 6741 * from the entry. 6742 * 6743 * @throws LDAPException If a problem is encountered while trying to 6744 * communicate with the directory server. 6745 * 6746 * @throws AssertionError If the target entry is missing from the server, or 6747 * if it contains any of the target attributes. 6748 */ 6749 public void assertAttributeMissing(@NotNull final String dn, 6750 @NotNull final Collection<String> attributeNames) 6751 throws LDAPException, AssertionError 6752 { 6753 synchronized (entryMap) 6754 { 6755 final Entry e = getEntry(dn); 6756 if (e == null) 6757 { 6758 throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn)); 6759 } 6760 6761 final Schema schema = schemaRef.get(); 6762 final List<String> messages = new ArrayList<>(attributeNames.size()); 6763 for (final String name : attributeNames) 6764 { 6765 final Filter f = Filter.createPresenceFilter(name); 6766 if (f.matchesEntry(e, schema)) 6767 { 6768 messages.add(ERR_MEM_HANDLER_TEST_ATTR_EXISTS.get(dn, name)); 6769 } 6770 } 6771 6772 if (! messages.isEmpty()) 6773 { 6774 throw new AssertionError(StaticUtils.concatenateStrings(messages)); 6775 } 6776 } 6777 } 6778 6779 6780 6781 /** 6782 * Ensures that the specified entry exists in the directory but does not 6783 * contain any of the specified attribute values. 6784 * 6785 * @param dn The DN of the entry expected to be present. 6786 * @param attributeName The name of the attribute to examine. 6787 * @param attributeValues The values expected to be missing from the target 6788 * entry. 6789 * 6790 * @throws LDAPException If a problem is encountered while trying to 6791 * communicate with the directory server. 6792 * 6793 * @throws AssertionError If the target entry is missing from the server, or 6794 * if it contains any of the target attribute values. 6795 */ 6796 public void assertValueMissing(@NotNull final String dn, 6797 @NotNull final String attributeName, 6798 @NotNull final Collection<String> attributeValues) 6799 throws LDAPException, AssertionError 6800 { 6801 synchronized (entryMap) 6802 { 6803 final Entry e = getEntry(dn); 6804 if (e == null) 6805 { 6806 throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn)); 6807 } 6808 6809 final Schema schema = schemaRef.get(); 6810 final List<String> messages = new ArrayList<>(attributeValues.size()); 6811 for (final String value : attributeValues) 6812 { 6813 final Filter f = Filter.createEqualityFilter(attributeName, value); 6814 if (f.matchesEntry(e, schema)) 6815 { 6816 messages.add(ERR_MEM_HANDLER_TEST_VALUE_EXISTS.get(dn, attributeName, 6817 value)); 6818 } 6819 } 6820 6821 if (! messages.isEmpty()) 6822 { 6823 throw new AssertionError(StaticUtils.concatenateStrings(messages)); 6824 } 6825 } 6826 } 6827}