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