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