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