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