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