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