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