001/* 002 * Copyright 2011-2014 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2011-2014 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 Entry rootDSEEntry = new Entry(DN.NULL_DN, schemaRef.get()); 4361 rootDSEEntry.addAttribute("objectClass", "top", "ds-root-dse"); 4362 rootDSEEntry.addAttribute(new Attribute("supportedLDAPVersion", 4363 IntegerMatchingRule.getInstance(), "3")); 4364 4365 final String vendorName = config.getVendorName(); 4366 if (vendorName != null) 4367 { 4368 rootDSEEntry.addAttribute("vendorName", vendorName); 4369 } 4370 4371 final String vendorVersion = config.getVendorVersion(); 4372 if (vendorVersion != null) 4373 { 4374 rootDSEEntry.addAttribute("vendorVersion", vendorVersion); 4375 } 4376 4377 rootDSEEntry.addAttribute(new Attribute("subschemaSubentry", 4378 DistinguishedNameMatchingRule.getInstance(), 4379 subschemaSubentryDN.toString())); 4380 rootDSEEntry.addAttribute(new Attribute("entryDN", 4381 DistinguishedNameMatchingRule.getInstance(), "")); 4382 rootDSEEntry.addAttribute("entryUUID", UUID.randomUUID().toString()); 4383 4384 rootDSEEntry.addAttribute("supportedFeatures", 4385 "1.3.6.1.4.1.4203.1.5.1", // All operational attributes 4386 "1.3.6.1.4.1.4203.1.5.2", // Request attributes by object class 4387 "1.3.6.1.4.1.4203.1.5.3", // LDAP absolute true and false filters 4388 "1.3.6.1.1.14"); // Increment modification type 4389 4390 final TreeSet<String> ctlSet = new TreeSet<String>(); 4391 4392 ctlSet.add(AssertionRequestControl.ASSERTION_REQUEST_OID); 4393 ctlSet.add(AuthorizationIdentityRequestControl. 4394 AUTHORIZATION_IDENTITY_REQUEST_OID); 4395 ctlSet.add(DontUseCopyRequestControl.DONT_USE_COPY_REQUEST_OID); 4396 ctlSet.add(ManageDsaITRequestControl.MANAGE_DSA_IT_REQUEST_OID); 4397 ctlSet.add(PermissiveModifyRequestControl.PERMISSIVE_MODIFY_REQUEST_OID); 4398 ctlSet.add(PostReadRequestControl.POST_READ_REQUEST_OID); 4399 ctlSet.add(PreReadRequestControl.PRE_READ_REQUEST_OID); 4400 ctlSet.add(ProxiedAuthorizationV1RequestControl. 4401 PROXIED_AUTHORIZATION_V1_REQUEST_OID); 4402 ctlSet.add(ProxiedAuthorizationV2RequestControl. 4403 PROXIED_AUTHORIZATION_V2_REQUEST_OID); 4404 ctlSet.add(ServerSideSortRequestControl.SERVER_SIDE_SORT_REQUEST_OID); 4405 ctlSet.add(SimplePagedResultsControl.PAGED_RESULTS_OID); 4406 ctlSet.add(SubentriesRequestControl.SUBENTRIES_REQUEST_OID); 4407 ctlSet.add(SubtreeDeleteRequestControl.SUBTREE_DELETE_REQUEST_OID); 4408 ctlSet.add(TransactionSpecificationRequestControl. 4409 TRANSACTION_SPECIFICATION_REQUEST_OID); 4410 ctlSet.add(VirtualListViewRequestControl.VIRTUAL_LIST_VIEW_REQUEST_OID); 4411 4412 final String[] controlOIDs = new String[ctlSet.size()]; 4413 rootDSEEntry.addAttribute("supportedControl", ctlSet.toArray(controlOIDs)); 4414 4415 4416 if (! extendedRequestHandlers.isEmpty()) 4417 { 4418 final String[] oidArray = new String[extendedRequestHandlers.size()]; 4419 rootDSEEntry.addAttribute("supportedExtension", 4420 extendedRequestHandlers.keySet().toArray(oidArray)); 4421 4422 for (final InMemoryListenerConfig c : config.getListenerConfigs()) 4423 { 4424 if (c.getStartTLSSocketFactory() != null) 4425 { 4426 rootDSEEntry.addAttribute("supportedExtension", 4427 StartTLSExtendedRequest.STARTTLS_REQUEST_OID); 4428 break; 4429 } 4430 } 4431 } 4432 4433 if (! saslBindHandlers.isEmpty()) 4434 { 4435 final String[] mechanismArray = new String[saslBindHandlers.size()]; 4436 rootDSEEntry.addAttribute("supportedSASLMechanisms", 4437 saslBindHandlers.keySet().toArray(mechanismArray)); 4438 } 4439 4440 int pos = 0; 4441 final String[] baseDNStrings = new String[baseDNs.size()]; 4442 for (final DN baseDN : baseDNs) 4443 { 4444 baseDNStrings[pos++] = baseDN.toString(); 4445 } 4446 rootDSEEntry.addAttribute(new Attribute("namingContexts", 4447 DistinguishedNameMatchingRule.getInstance(), baseDNStrings)); 4448 4449 if (maxChangelogEntries > 0) 4450 { 4451 rootDSEEntry.addAttribute(new Attribute("changeLog", 4452 DistinguishedNameMatchingRule.getInstance(), 4453 changeLogBaseDN.toString())); 4454 rootDSEEntry.addAttribute(new Attribute("firstChangeNumber", 4455 IntegerMatchingRule.getInstance(), firstChangeNumber.toString())); 4456 rootDSEEntry.addAttribute(new Attribute("lastChangeNumber", 4457 IntegerMatchingRule.getInstance(), lastChangeNumber.toString())); 4458 } 4459 4460 return new ReadOnlyEntry(rootDSEEntry); 4461 } 4462 4463 4464 4465 /** 4466 * Generates a subschema subentry from the provided schema object. 4467 * 4468 * @param schema The schema to use to generate the subschema subentry. It 4469 * may be {@code null} if a minimal default entry should be 4470 * generated. 4471 * 4472 * @return The generated subschema subentry. 4473 */ 4474 private static ReadOnlyEntry generateSubschemaSubentry(final Schema schema) 4475 { 4476 final Entry e; 4477 4478 if (schema == null) 4479 { 4480 e = new Entry("cn=schema", schema); 4481 4482 e.addAttribute("objectClass", "namedObject", "ldapSubEntry", 4483 "subschema"); 4484 e.addAttribute("cn", "schema"); 4485 } 4486 else 4487 { 4488 e = schema.getSchemaEntry().duplicate(); 4489 } 4490 4491 try 4492 { 4493 e.addAttribute("entryDN", DN.normalize(e.getDN(), schema)); 4494 } 4495 catch (final LDAPException le) 4496 { 4497 // This should never happen. 4498 Debug.debugException(le); 4499 e.setAttribute("entryDN", StaticUtils.toLowerCase(e.getDN())); 4500 } 4501 4502 4503 e.addAttribute("entryUUID", UUID.randomUUID().toString()); 4504 return new ReadOnlyEntry(e); 4505 } 4506 4507 4508 4509 /** 4510 * Processes the set of requested attributes from the given search request. 4511 * 4512 * @param attrList The list of requested attributes to examine. 4513 * @param allUserAttrs Indicates whether to return all user attributes. It 4514 * should have an initial value of {@code false}. 4515 * @param allOpAttrs Indicates whether to return all operational 4516 * attributes. It should have an initial value of 4517 * {@code false}. 4518 * 4519 * @return A map of specific attribute types to be returned. The keys of the 4520 * map will be the lowercase OID and names of the attribute types, 4521 * and the values will be a list of option sets for the associated 4522 * attribute type. 4523 */ 4524 private Map<String,List<List<String>>> processRequestedAttributes( 4525 final List<String> attrList, final AtomicBoolean allUserAttrs, 4526 final AtomicBoolean allOpAttrs) 4527 { 4528 if (attrList.isEmpty()) 4529 { 4530 allUserAttrs.set(true); 4531 return Collections.emptyMap(); 4532 } 4533 4534 final Schema schema = schemaRef.get(); 4535 final HashMap<String,List<List<String>>> m = 4536 new HashMap<String,List<List<String>>>(attrList.size() * 2); 4537 for (final String s : attrList) 4538 { 4539 if (s.equals("*")) 4540 { 4541 // All user attributes. 4542 allUserAttrs.set(true); 4543 } 4544 else if (s.equals("+")) 4545 { 4546 // All operational attributes. 4547 allOpAttrs.set(true); 4548 } 4549 else if (s.startsWith("@")) 4550 { 4551 // Return attributes by object class. This can only be supported if a 4552 // schema has been defined. 4553 if (schema != null) 4554 { 4555 final String ocName = s.substring(1); 4556 final ObjectClassDefinition oc = schema.getObjectClass(ocName); 4557 if (oc != null) 4558 { 4559 for (final AttributeTypeDefinition at : 4560 oc.getRequiredAttributes(schema, true)) 4561 { 4562 addAttributeOIDAndNames(at, m, Collections.<String>emptyList()); 4563 } 4564 for (final AttributeTypeDefinition at : 4565 oc.getOptionalAttributes(schema, true)) 4566 { 4567 addAttributeOIDAndNames(at, m, Collections.<String>emptyList()); 4568 } 4569 } 4570 } 4571 } 4572 else 4573 { 4574 final ObjectPair<String,List<String>> nameWithOptions = 4575 getNameWithOptions(s); 4576 if (nameWithOptions == null) 4577 { 4578 continue; 4579 } 4580 4581 final String name = nameWithOptions.getFirst(); 4582 final List<String> options = nameWithOptions.getSecond(); 4583 4584 if (schema == null) 4585 { 4586 // Just use the name as provided. 4587 List<List<String>> optionLists = m.get(name); 4588 if (optionLists == null) 4589 { 4590 optionLists = new ArrayList<List<String>>(1); 4591 m.put(name, optionLists); 4592 } 4593 optionLists.add(options); 4594 } 4595 else 4596 { 4597 // If the attribute type is defined in the schema, then use it to get 4598 // all names and the OID. Otherwise, just use the name as provided. 4599 final AttributeTypeDefinition at = schema.getAttributeType(name); 4600 if (at == null) 4601 { 4602 List<List<String>> optionLists = m.get(name); 4603 if (optionLists == null) 4604 { 4605 optionLists = new ArrayList<List<String>>(1); 4606 m.put(name, optionLists); 4607 } 4608 optionLists.add(options); 4609 } 4610 else 4611 { 4612 addAttributeOIDAndNames(at, m, options); 4613 } 4614 } 4615 } 4616 } 4617 4618 return m; 4619 } 4620 4621 4622 4623 /** 4624 * Parses the provided string into an attribute type and set of options. 4625 * 4626 * @param s The string to be parsed. 4627 * 4628 * @return An {@code ObjectPair} in which the first element is the attribute 4629 * type name and the second is the list of options (or an empty 4630 * list if there are no options). Alternately, a value of 4631 * {@code null} may be returned if the provided string does not 4632 * represent a valid attribute type description. 4633 */ 4634 private static ObjectPair<String,List<String>> getNameWithOptions( 4635 final String s) 4636 { 4637 if (! Attribute.nameIsValid(s, true)) 4638 { 4639 return null; 4640 } 4641 4642 final String l = StaticUtils.toLowerCase(s); 4643 4644 int semicolonPos = l.indexOf(';'); 4645 if (semicolonPos < 0) 4646 { 4647 return new ObjectPair<String,List<String>>(l, 4648 Collections.<String>emptyList()); 4649 } 4650 4651 final String name = l.substring(0, semicolonPos); 4652 final ArrayList<String> optionList = new ArrayList<String>(1); 4653 while (true) 4654 { 4655 final int nextSemicolonPos = l.indexOf(';', semicolonPos+1); 4656 if (nextSemicolonPos < 0) 4657 { 4658 optionList.add(l.substring(semicolonPos+1)); 4659 break; 4660 } 4661 else 4662 { 4663 optionList.add(l.substring(semicolonPos+1, nextSemicolonPos)); 4664 semicolonPos = nextSemicolonPos; 4665 } 4666 } 4667 4668 return new ObjectPair<String,List<String>>(name, optionList); 4669 } 4670 4671 4672 4673 /** 4674 * Adds all-lowercase versions of the OID and all names for the provided 4675 * attribute type definition to the given map with the given options. 4676 * 4677 * @param d The attribute type definition to process. 4678 * @param m The map to which the OID and names should be added. 4679 * @param o The array of attribute options to use in the map. It should be 4680 * empty if no options are needed, and must not be {@code null}. 4681 */ 4682 private void addAttributeOIDAndNames(final AttributeTypeDefinition d, 4683 final Map<String,List<List<String>>> m, 4684 final List<String> o) 4685 { 4686 if (d == null) 4687 { 4688 return; 4689 } 4690 4691 final String lowerOID = StaticUtils.toLowerCase(d.getOID()); 4692 if (lowerOID != null) 4693 { 4694 List<List<String>> l = m.get(lowerOID); 4695 if (l == null) 4696 { 4697 l = new ArrayList<List<String>>(1); 4698 m.put(lowerOID, l); 4699 } 4700 4701 l.add(o); 4702 } 4703 4704 for (final String name : d.getNames()) 4705 { 4706 final String lowerName = StaticUtils.toLowerCase(name); 4707 List<List<String>> l = m.get(lowerName); 4708 if (l == null) 4709 { 4710 l = new ArrayList<List<String>>(1); 4711 m.put(lowerName, l); 4712 } 4713 4714 l.add(o); 4715 } 4716 4717 // If a schema is available, then see if the attribute type has any 4718 // subordinate types. If so, then add them. 4719 final Schema schema = schemaRef.get(); 4720 if (schema != null) 4721 { 4722 for (final AttributeTypeDefinition subordinateType : 4723 schema.getSubordinateAttributeTypes(d)) 4724 { 4725 addAttributeOIDAndNames(subordinateType, m, o); 4726 } 4727 } 4728 } 4729 4730 4731 4732 /** 4733 * Performs the necessary processing to determine whether the given entry 4734 * should be returned as a search result entry or reference, or if it should 4735 * not be returned at all. 4736 * 4737 * @param entry The entry to be processed. 4738 * @param includeSubEntries Indicates whether LDAP subentries should be 4739 * returned to the client. 4740 * @param includeChangeLog Indicates whether entries within the changelog 4741 * should be returned to the client. 4742 * @param hasManageDsaIT Indicates whether the request includes the 4743 * ManageDsaIT control, which can change how smart 4744 * referrals should be handled. 4745 * @param entryList The list to which the entry should be added if 4746 * it should be returned to the client as a search 4747 * result entry. 4748 * @param referenceList The list that should be updated if the provided 4749 * entry represents a smart referral that should be 4750 * returned as a search result reference. 4751 */ 4752 private void processSearchEntry(final Entry entry, 4753 final boolean includeSubEntries, 4754 final boolean includeChangeLog, 4755 final boolean hasManageDsaIT, 4756 final List<Entry> entryList, 4757 final List<SearchResultReference> referenceList) 4758 { 4759 // See if the entry should be suppressed as an LDAP subentry. 4760 if ((! includeSubEntries) && 4761 (entry.hasObjectClass("ldapSubEntry") || 4762 entry.hasObjectClass("inheritableLDAPSubEntry"))) 4763 { 4764 return; 4765 } 4766 4767 // See if the entry should be suppressed as a changelog entry. 4768 try 4769 { 4770 if ((! includeChangeLog) && 4771 (entry.getParsedDN().isDescendantOf(changeLogBaseDN, true))) 4772 { 4773 return; 4774 } 4775 } 4776 catch (final Exception e) 4777 { 4778 // This should never happen. 4779 Debug.debugException(e); 4780 } 4781 4782 // See if the entry is a referral and should result in a reference rather 4783 // than an entry. 4784 if ((! hasManageDsaIT) && entry.hasObjectClass("referral") && 4785 entry.hasAttribute("ref")) 4786 { 4787 referenceList.add(new SearchResultReference( 4788 entry.getAttributeValues("ref"), NO_CONTROLS)); 4789 return; 4790 } 4791 4792 entryList.add(entry); 4793 } 4794 4795 4796 4797 /** 4798 * Retrieves a copy of the provided entry that includes only the appropriate 4799 * set of requested attributes. 4800 * 4801 * @param entry The entry to be returned. 4802 * @param allUserAttrs Indicates whether to return all user attributes. 4803 * @param allOpAttrs Indicates whether to return all operational 4804 * attributes. 4805 * @param returnAttrs A map with information about the specific attribute 4806 * types to return. 4807 * 4808 * @return A copy of the provided entry that includes only the appropriate 4809 * set of requested attributes. 4810 */ 4811 private Entry trimForRequestedAttributes(final Entry entry, 4812 final boolean allUserAttrs, final boolean allOpAttrs, 4813 final Map<String,List<List<String>>> returnAttrs) 4814 { 4815 // See if we can return the entry without paring it down. 4816 final Schema schema = schemaRef.get(); 4817 if (allUserAttrs) 4818 { 4819 if (allOpAttrs || (schema == null)) 4820 { 4821 return entry; 4822 } 4823 } 4824 4825 4826 // If we've gotten here, then we may only need to return a partial entry. 4827 final Entry copy = new Entry(entry.getDN(), schema); 4828 4829 for (final Attribute a : entry.getAttributes()) 4830 { 4831 final ObjectPair<String,List<String>> nameWithOptions = 4832 getNameWithOptions(a.getName()); 4833 final String name = nameWithOptions.getFirst(); 4834 final List<String> options = nameWithOptions.getSecond(); 4835 4836 // If there is a schema, then see if it is an operational attribute, since 4837 // that needs to be handled in a manner different from user attributes 4838 if (schema != null) 4839 { 4840 final AttributeTypeDefinition at = schema.getAttributeType(name); 4841 if ((at != null) && at.isOperational()) 4842 { 4843 if (allOpAttrs) 4844 { 4845 copy.addAttribute(a); 4846 continue; 4847 } 4848 4849 final List<List<String>> optionLists = returnAttrs.get(name); 4850 if (optionLists == null) 4851 { 4852 continue; 4853 } 4854 4855 for (final List<String> optionList : optionLists) 4856 { 4857 boolean matchAll = true; 4858 for (final String option : optionList) 4859 { 4860 if (! options.contains(option)) 4861 { 4862 matchAll = false; 4863 break; 4864 } 4865 } 4866 4867 if (matchAll) 4868 { 4869 copy.addAttribute(a); 4870 break; 4871 } 4872 } 4873 continue; 4874 } 4875 } 4876 4877 // We'll assume that it's a user attribute, and we'll look for an exact 4878 // match on the base name. 4879 if (allUserAttrs) 4880 { 4881 copy.addAttribute(a); 4882 continue; 4883 } 4884 4885 final List<List<String>> optionLists = returnAttrs.get(name); 4886 if (optionLists == null) 4887 { 4888 continue; 4889 } 4890 4891 for (final List<String> optionList : optionLists) 4892 { 4893 boolean matchAll = true; 4894 for (final String option : optionList) 4895 { 4896 if (! options.contains(option)) 4897 { 4898 matchAll = false; 4899 break; 4900 } 4901 } 4902 4903 if (matchAll) 4904 { 4905 copy.addAttribute(a); 4906 break; 4907 } 4908 } 4909 } 4910 4911 return copy; 4912 } 4913 4914 4915 4916 /** 4917 * Retrieves the DN of the existing entry which is the closest hierarchical 4918 * match to the provided DN. 4919 * 4920 * @param dn The DN for which to retrieve the appropriate matched DN. 4921 * 4922 * @return The appropriate matched DN value, or {@code null} if there is 4923 * none. 4924 */ 4925 private String getMatchedDNString(final DN dn) 4926 { 4927 DN parentDN = dn.getParent(); 4928 while (parentDN != null) 4929 { 4930 if (entryMap.containsKey(parentDN)) 4931 { 4932 return parentDN.toString(); 4933 } 4934 4935 parentDN = parentDN.getParent(); 4936 } 4937 4938 return null; 4939 } 4940 4941 4942 4943 /** 4944 * Converts the provided string list to an array. 4945 * 4946 * @param l The possibly null list to be converted. 4947 * 4948 * @return The string array with the same elements as the given list in the 4949 * same order, or {@code null} if the given list was null. 4950 */ 4951 private static String[] stringListToArray(final List<String> l) 4952 { 4953 if (l == null) 4954 { 4955 return null; 4956 } 4957 else 4958 { 4959 final String[] a = new String[l.size()]; 4960 return l.toArray(a); 4961 } 4962 } 4963 4964 4965 4966 /** 4967 * Creates a changelog entry from the information in the provided add request 4968 * and adds it to the server changelog. 4969 * 4970 * @param addRequest The add request to use to construct the changelog 4971 * entry. 4972 * @param authzDN The authorization DN for the change. 4973 */ 4974 private void addChangeLogEntry(final AddRequestProtocolOp addRequest, 4975 final DN authzDN) 4976 { 4977 // If the changelog is disabled, then don't do anything. 4978 if (maxChangelogEntries <= 0) 4979 { 4980 return; 4981 } 4982 4983 final long changeNumber = lastChangeNumber.incrementAndGet(); 4984 final LDIFAddChangeRecord changeRecord = new LDIFAddChangeRecord( 4985 addRequest.getDN(), addRequest.getAttributes()); 4986 try 4987 { 4988 addChangeLogEntry( 4989 ChangeLogEntry.constructChangeLogEntry(changeNumber, changeRecord), 4990 authzDN); 4991 } 4992 catch (final LDAPException le) 4993 { 4994 // This should not happen. 4995 Debug.debugException(le); 4996 } 4997 } 4998 4999 5000 5001 /** 5002 * Creates a changelog entry from the information in the provided delete 5003 * request and adds it to the server changelog. 5004 * 5005 * @param e The entry to be deleted. 5006 * @param authzDN The authorization DN for the change. 5007 */ 5008 private void addDeleteChangeLogEntry(final Entry e, final DN authzDN) 5009 { 5010 // If the changelog is disabled, then don't do anything. 5011 if (maxChangelogEntries <= 0) 5012 { 5013 return; 5014 } 5015 5016 final long changeNumber = lastChangeNumber.incrementAndGet(); 5017 final LDIFDeleteChangeRecord changeRecord = 5018 new LDIFDeleteChangeRecord(e.getDN()); 5019 5020 // Create the changelog entry. 5021 try 5022 { 5023 final ChangeLogEntry cle = ChangeLogEntry.constructChangeLogEntry( 5024 changeNumber, changeRecord); 5025 5026 // Add a set of deleted entry attributes, which is simply an LDIF-encoded 5027 // representation of the entry, excluding the first line since it contains 5028 // the DN. 5029 final StringBuilder deletedEntryAttrsBuffer = new StringBuilder(); 5030 final String[] ldifLines = e.toLDIF(0); 5031 for (int i=1; i < ldifLines.length; i++) 5032 { 5033 deletedEntryAttrsBuffer.append(ldifLines[i]); 5034 deletedEntryAttrsBuffer.append(StaticUtils.EOL); 5035 } 5036 5037 final Entry copy = cle.duplicate(); 5038 copy.addAttribute(ChangeLogEntry.ATTR_DELETED_ENTRY_ATTRS, 5039 deletedEntryAttrsBuffer.toString()); 5040 addChangeLogEntry(new ChangeLogEntry(copy), authzDN); 5041 } 5042 catch (final LDAPException le) 5043 { 5044 // This should never happen. 5045 Debug.debugException(le); 5046 } 5047 } 5048 5049 5050 5051 /** 5052 * Creates a changelog entry from the information in the provided modify 5053 * request and adds it to the server changelog. 5054 * 5055 * @param modifyRequest The modify request to use to construct the changelog 5056 * entry. 5057 * @param authzDN The authorization DN for the change. 5058 */ 5059 private void addChangeLogEntry(final ModifyRequestProtocolOp modifyRequest, 5060 final DN authzDN) 5061 { 5062 // If the changelog is disabled, then don't do anything. 5063 if (maxChangelogEntries <= 0) 5064 { 5065 return; 5066 } 5067 5068 final long changeNumber = lastChangeNumber.incrementAndGet(); 5069 final LDIFModifyChangeRecord changeRecord = 5070 new LDIFModifyChangeRecord(modifyRequest.getDN(), 5071 modifyRequest.getModifications()); 5072 try 5073 { 5074 addChangeLogEntry( 5075 ChangeLogEntry.constructChangeLogEntry(changeNumber, changeRecord), 5076 authzDN); 5077 } 5078 catch (final LDAPException le) 5079 { 5080 // This should not happen. 5081 Debug.debugException(le); 5082 } 5083 } 5084 5085 5086 5087 /** 5088 * Creates a changelog entry from the information in the provided modify DN 5089 * request and adds it to the server changelog. 5090 * 5091 * @param modifyDNRequest The modify DN request to use to construct the 5092 * changelog entry. 5093 * @param authzDN The authorization DN for the change. 5094 */ 5095 private void addChangeLogEntry( 5096 final ModifyDNRequestProtocolOp modifyDNRequest, 5097 final DN authzDN) 5098 { 5099 // If the changelog is disabled, then don't do anything. 5100 if (maxChangelogEntries <= 0) 5101 { 5102 return; 5103 } 5104 5105 final long changeNumber = lastChangeNumber.incrementAndGet(); 5106 final LDIFModifyDNChangeRecord changeRecord = 5107 new LDIFModifyDNChangeRecord(modifyDNRequest.getDN(), 5108 modifyDNRequest.getNewRDN(), modifyDNRequest.deleteOldRDN(), 5109 modifyDNRequest.getNewSuperiorDN()); 5110 try 5111 { 5112 addChangeLogEntry( 5113 ChangeLogEntry.constructChangeLogEntry(changeNumber, changeRecord), 5114 authzDN); 5115 } 5116 catch (final LDAPException le) 5117 { 5118 // This should not happen. 5119 Debug.debugException(le); 5120 } 5121 } 5122 5123 5124 5125 /** 5126 * Adds the provided changelog entry to the data set, removing an old entry if 5127 * necessary to remain within the maximum allowed number of changes. This 5128 * must only be called from a synchronized method, and the change number for 5129 * the changelog entry must have been obtained by calling 5130 * {@code lastChangeNumber.incrementAndGet()}. 5131 * 5132 * @param e The changelog entry to add to the data set. 5133 * @param authzDN The authorization DN for the change. 5134 */ 5135 private void addChangeLogEntry(final ChangeLogEntry e, final DN authzDN) 5136 { 5137 // Construct the DN object to use for the entry and put it in the map. 5138 final long changeNumber = e.getChangeNumber(); 5139 final Schema schema = schemaRef.get(); 5140 final DN dn = new DN( 5141 new RDN("changeNumber", String.valueOf(changeNumber), schema), 5142 changeLogBaseDN); 5143 5144 final Entry entry = e.duplicate(); 5145 if (generateOperationalAttributes) 5146 { 5147 final Date d = new Date(); 5148 entry.addAttribute(new Attribute("entryDN", 5149 DistinguishedNameMatchingRule.getInstance(), 5150 dn.toNormalizedString())); 5151 entry.addAttribute(new Attribute("entryUUID", 5152 UUID.randomUUID().toString())); 5153 entry.addAttribute(new Attribute("subschemaSubentry", 5154 DistinguishedNameMatchingRule.getInstance(), 5155 subschemaSubentryDN.toString())); 5156 entry.addAttribute(new Attribute("creatorsName", 5157 DistinguishedNameMatchingRule.getInstance(), 5158 authzDN.toString())); 5159 entry.addAttribute(new Attribute("createTimestamp", 5160 GeneralizedTimeMatchingRule.getInstance(), 5161 StaticUtils.encodeGeneralizedTime(d))); 5162 entry.addAttribute(new Attribute("modifiersName", 5163 DistinguishedNameMatchingRule.getInstance(), 5164 authzDN.toString())); 5165 entry.addAttribute(new Attribute("modifyTimestamp", 5166 GeneralizedTimeMatchingRule.getInstance(), 5167 StaticUtils.encodeGeneralizedTime(d))); 5168 } 5169 5170 entryMap.put(dn, new ReadOnlyEntry(entry)); 5171 indexAdd(entry); 5172 5173 // Update the first change number and/or trim the changelog if necessary. 5174 final long firstNumber = firstChangeNumber.get(); 5175 if (changeNumber == 1L) 5176 { 5177 // It's the first change, so we need to set the first change number. 5178 firstChangeNumber.set(1); 5179 } 5180 else 5181 { 5182 // See if we need to trim an entry. 5183 final long numChangeLogEntries = changeNumber - firstNumber + 1; 5184 if (numChangeLogEntries > maxChangelogEntries) 5185 { 5186 // We need to delete the first changelog entry and increment the 5187 // first change number. 5188 firstChangeNumber.incrementAndGet(); 5189 final Entry deletedEntry = entryMap.remove(new DN( 5190 new RDN("changeNumber", String.valueOf(firstNumber), schema), 5191 changeLogBaseDN)); 5192 indexDelete(deletedEntry); 5193 } 5194 } 5195 } 5196 5197 5198 5199 /** 5200 * Checks to see if the provided control map includes a proxied authorization 5201 * control (v1 or v2) and if so then attempts to determine the appropriate 5202 * authorization identity to use for the operation. 5203 * 5204 * @param m The map of request controls, indexed by OID. 5205 * 5206 * @return The DN of the authorized user, or the current authentication DN 5207 * if the control map does not include a proxied authorization 5208 * request control. 5209 * 5210 * @throws LDAPException If a problem is encountered while attempting to 5211 * determine the authorization DN. 5212 */ 5213 private DN handleProxiedAuthControl(final Map<String,Control> m) 5214 throws LDAPException 5215 { 5216 final ProxiedAuthorizationV1RequestControl p1 = 5217 (ProxiedAuthorizationV1RequestControl) m.get( 5218 ProxiedAuthorizationV1RequestControl. 5219 PROXIED_AUTHORIZATION_V1_REQUEST_OID); 5220 if (p1 != null) 5221 { 5222 final DN authzDN = new DN(p1.getProxyDN(), schemaRef.get()); 5223 if (authzDN.isNullDN() || 5224 entryMap.containsKey(authzDN) || 5225 additionalBindCredentials.containsKey(authzDN)) 5226 { 5227 return authzDN; 5228 } 5229 else 5230 { 5231 throw new LDAPException(ResultCode.AUTHORIZATION_DENIED, 5232 ERR_MEM_HANDLER_NO_SUCH_IDENTITY.get("dn:" + authzDN.toString())); 5233 } 5234 } 5235 5236 final ProxiedAuthorizationV2RequestControl p2 = 5237 (ProxiedAuthorizationV2RequestControl) m.get( 5238 ProxiedAuthorizationV2RequestControl. 5239 PROXIED_AUTHORIZATION_V2_REQUEST_OID); 5240 if (p2 != null) 5241 { 5242 return getDNForAuthzID(p2.getAuthorizationID()); 5243 } 5244 5245 return authenticatedDN; 5246 } 5247 5248 5249 5250 /** 5251 * Attempts to identify the DN of the user referenced by the provided 5252 * authorization ID string. It may be "dn:" followed by the target DN, or 5253 * "u:" followed by the value of the uid attribute in the entry. If it uses 5254 * the "dn:" form, then it may reference the DN of a regular entry or a DN 5255 * in the configured set of additional bind credentials. 5256 * 5257 * @param authzID The authorization ID to resolve to a user DN. 5258 * 5259 * @return The DN identified for the provided authorization ID. 5260 * 5261 * @throws LDAPException If a problem prevents resolving the authorization 5262 * ID to a user DN. 5263 */ 5264 public DN getDNForAuthzID(final String authzID) 5265 throws LDAPException 5266 { 5267 synchronized (entryMap) 5268 { 5269 final String lowerAuthzID = StaticUtils.toLowerCase(authzID); 5270 if (lowerAuthzID.startsWith("dn:")) 5271 { 5272 if (lowerAuthzID.equals("dn:")) 5273 { 5274 return DN.NULL_DN; 5275 } 5276 else 5277 { 5278 final DN dn = new DN(authzID.substring(3), schemaRef.get()); 5279 if (entryMap.containsKey(dn) || 5280 additionalBindCredentials.containsKey(dn)) 5281 { 5282 return dn; 5283 } 5284 else 5285 { 5286 throw new LDAPException(ResultCode.AUTHORIZATION_DENIED, 5287 ERR_MEM_HANDLER_NO_SUCH_IDENTITY.get(authzID)); 5288 } 5289 } 5290 } 5291 else if (lowerAuthzID.startsWith("u:")) 5292 { 5293 final Filter f = 5294 Filter.createEqualityFilter("uid", authzID.substring(2)); 5295 final List<ReadOnlyEntry> entryList = search("", SearchScope.SUB, f); 5296 if (entryList.size() == 1) 5297 { 5298 return entryList.get(0).getParsedDN(); 5299 } 5300 else 5301 { 5302 throw new LDAPException(ResultCode.AUTHORIZATION_DENIED, 5303 ERR_MEM_HANDLER_NO_SUCH_IDENTITY.get(authzID)); 5304 } 5305 } 5306 else 5307 { 5308 throw new LDAPException(ResultCode.AUTHORIZATION_DENIED, 5309 ERR_MEM_HANDLER_NO_SUCH_IDENTITY.get(authzID)); 5310 } 5311 } 5312 } 5313 5314 5315 5316 /** 5317 * Checks to see if the provided control map includes an assertion request 5318 * control, and if so then checks to see whether the provided entry satisfies 5319 * the filter in that control. 5320 * 5321 * @param m The map of request controls, indexed by OID. 5322 * @param e The entry to examine against the assertion filter. 5323 * 5324 * @throws LDAPException If the control map includes an assertion request 5325 * control and the provided entry does not match the 5326 * filter contained in that control. 5327 */ 5328 private static void handleAssertionRequestControl(final Map<String,Control> m, 5329 final Entry e) 5330 throws LDAPException 5331 { 5332 final AssertionRequestControl c = (AssertionRequestControl) 5333 m.get(AssertionRequestControl.ASSERTION_REQUEST_OID); 5334 if (c == null) 5335 { 5336 return; 5337 } 5338 5339 try 5340 { 5341 if (c.getFilter().matchesEntry(e)) 5342 { 5343 return; 5344 } 5345 } 5346 catch (final LDAPException le) 5347 { 5348 Debug.debugException(le); 5349 } 5350 5351 // If we've gotten here, then the filter doesn't match. 5352 throw new LDAPException(ResultCode.ASSERTION_FAILED, 5353 ERR_MEM_HANDLER_ASSERTION_CONTROL_NOT_SATISFIED.get()); 5354 } 5355 5356 5357 5358 /** 5359 * Checks to see if the provided control map includes a pre-read request 5360 * control, and if so then generates the appropriate response control that 5361 * should be returned to the client. 5362 * 5363 * @param m The map of request controls, indexed by OID. 5364 * @param e The entry as it appeared before the operation. 5365 * 5366 * @return The pre-read response control that should be returned to the 5367 * client, or {@code null} if there is none. 5368 */ 5369 private PreReadResponseControl handlePreReadControl( 5370 final Map<String,Control> m, final Entry e) 5371 { 5372 final PreReadRequestControl c = (PreReadRequestControl) 5373 m.get(PreReadRequestControl.PRE_READ_REQUEST_OID); 5374 if (c == null) 5375 { 5376 return null; 5377 } 5378 5379 final AtomicBoolean allUserAttrs = new AtomicBoolean(false); 5380 final AtomicBoolean allOpAttrs = new AtomicBoolean(false); 5381 final Map<String,List<List<String>>> returnAttrs = 5382 processRequestedAttributes(Arrays.asList(c.getAttributes()), 5383 allUserAttrs, allOpAttrs); 5384 5385 final Entry trimmedEntry = trimForRequestedAttributes(e, allUserAttrs.get(), 5386 allOpAttrs.get(), returnAttrs); 5387 return new PreReadResponseControl(new ReadOnlyEntry(trimmedEntry)); 5388 } 5389 5390 5391 5392 /** 5393 * Checks to see if the provided control map includes a post-read request 5394 * control, and if so then generates the appropriate response control that 5395 * should be returned to the client. 5396 * 5397 * @param m The map of request controls, indexed by OID. 5398 * @param e The entry as it appeared before the operation. 5399 * 5400 * @return The post-read response control that should be returned to the 5401 * client, or {@code null} if there is none. 5402 */ 5403 private PostReadResponseControl handlePostReadControl( 5404 final Map<String,Control> m, final Entry e) 5405 { 5406 final PostReadRequestControl c = (PostReadRequestControl) 5407 m.get(PostReadRequestControl.POST_READ_REQUEST_OID); 5408 if (c == null) 5409 { 5410 return null; 5411 } 5412 5413 final AtomicBoolean allUserAttrs = new AtomicBoolean(false); 5414 final AtomicBoolean allOpAttrs = new AtomicBoolean(false); 5415 final Map<String,List<List<String>>> returnAttrs = 5416 processRequestedAttributes(Arrays.asList(c.getAttributes()), 5417 allUserAttrs, allOpAttrs); 5418 5419 final Entry trimmedEntry = trimForRequestedAttributes(e, allUserAttrs.get(), 5420 allOpAttrs.get(), returnAttrs); 5421 return new PostReadResponseControl(new ReadOnlyEntry(trimmedEntry)); 5422 } 5423 5424 5425 5426 /** 5427 * Finds the smart referral entry which is hierarchically nearest the entry 5428 * with the given DN. 5429 * 5430 * @param dn The DN for which to find the hierarchically nearest smart 5431 * referral entry. 5432 * 5433 * @return The hierarchically nearest smart referral entry for the provided 5434 * DN, or {@code null} if there are no smart referral entries with 5435 * the provided DN or any of its ancestors. 5436 */ 5437 private Entry findNearestReferral(final DN dn) 5438 { 5439 DN d = dn; 5440 while (true) 5441 { 5442 final Entry e = entryMap.get(d); 5443 if (e == null) 5444 { 5445 d = d.getParent(); 5446 if (d == null) 5447 { 5448 return null; 5449 } 5450 } 5451 else if (e.hasObjectClass("referral")) 5452 { 5453 return e; 5454 } 5455 else 5456 { 5457 return null; 5458 } 5459 } 5460 } 5461 5462 5463 5464 /** 5465 * Retrieves the referral URLs that should be used for the provided target DN 5466 * based on the given referral entry. 5467 * 5468 * @param targetDN The target DN from the associated operation. 5469 * @param referralEntry The entry containing the smart referral. 5470 * 5471 * @return The referral URLs that should be returned. 5472 */ 5473 private static List<String> getReferralURLs(final DN targetDN, 5474 final Entry referralEntry) 5475 { 5476 final String[] refs = referralEntry.getAttributeValues("ref"); 5477 if (refs == null) 5478 { 5479 return null; 5480 } 5481 5482 final RDN[] retainRDNs; 5483 try 5484 { 5485 // If the target DN equals the referral entry DN, or if it's not 5486 // subordinate to the referral entry, then the URLs should be returned 5487 // as-is. 5488 final DN parsedEntryDN = referralEntry.getParsedDN(); 5489 if (targetDN.equals(parsedEntryDN) || 5490 (! targetDN.isDescendantOf(parsedEntryDN, true))) 5491 { 5492 return Arrays.asList(refs); 5493 } 5494 5495 final RDN[] targetRDNs = targetDN.getRDNs(); 5496 final RDN[] refEntryRDNs = referralEntry.getParsedDN().getRDNs(); 5497 retainRDNs = new RDN[targetRDNs.length - refEntryRDNs.length]; 5498 System.arraycopy(targetRDNs, 0, retainRDNs, 0, retainRDNs.length); 5499 } 5500 catch (final LDAPException le) 5501 { 5502 Debug.debugException(le); 5503 return Arrays.asList(refs); 5504 } 5505 5506 final List<String> refList = new ArrayList<String>(refs.length); 5507 for (final String ref : refs) 5508 { 5509 try 5510 { 5511 final LDAPURL url = new LDAPURL(ref); 5512 final RDN[] refRDNs = url.getBaseDN().getRDNs(); 5513 final RDN[] newRefRDNs = new RDN[retainRDNs.length + refRDNs.length]; 5514 System.arraycopy(retainRDNs, 0, newRefRDNs, 0, retainRDNs.length); 5515 System.arraycopy(refRDNs, 0, newRefRDNs, retainRDNs.length, 5516 refRDNs.length); 5517 final DN newBaseDN = new DN(newRefRDNs); 5518 5519 final LDAPURL newURL = new LDAPURL(url.getScheme(), url.getHost(), 5520 url.getPort(), newBaseDN, null, null, null); 5521 refList.add(newURL.toString()); 5522 } 5523 catch (final LDAPException le) 5524 { 5525 Debug.debugException(le); 5526 refList.add(ref); 5527 } 5528 } 5529 5530 return refList; 5531 } 5532 5533 5534 5535 /** 5536 * Indicates whether the specified entry exists in the server. 5537 * 5538 * @param dn The DN of the entry for which to make the determination. 5539 * 5540 * @return {@code true} if the entry exists, or {@code false} if not. 5541 * 5542 * @throws LDAPException If a problem is encountered while trying to 5543 * communicate with the directory server. 5544 */ 5545 public boolean entryExists(final String dn) 5546 throws LDAPException 5547 { 5548 return (getEntry(dn) != null); 5549 } 5550 5551 5552 5553 /** 5554 * Indicates whether the specified entry exists in the server and matches the 5555 * given filter. 5556 * 5557 * @param dn The DN of the entry for which to make the determination. 5558 * @param filter The filter the entry is expected to match. 5559 * 5560 * @return {@code true} if the entry exists and matches the specified filter, 5561 * or {@code false} if not. 5562 * 5563 * @throws LDAPException If a problem is encountered while trying to 5564 * communicate with the directory server. 5565 */ 5566 public boolean entryExists(final String dn, final String filter) 5567 throws LDAPException 5568 { 5569 synchronized (entryMap) 5570 { 5571 final Entry e = getEntry(dn); 5572 if (e == null) 5573 { 5574 return false; 5575 } 5576 5577 final Filter f = Filter.create(filter); 5578 try 5579 { 5580 return f.matchesEntry(e, schemaRef.get()); 5581 } 5582 catch (final LDAPException le) 5583 { 5584 Debug.debugException(le); 5585 return false; 5586 } 5587 } 5588 } 5589 5590 5591 5592 /** 5593 * Indicates whether the specified entry exists in the server. This will 5594 * return {@code true} only if the target entry exists and contains all values 5595 * for all attributes of the provided entry. The entry will be allowed to 5596 * have attribute values not included in the provided entry. 5597 * 5598 * @param entry The entry to compare against the directory server. 5599 * 5600 * @return {@code true} if the entry exists in the server and is a superset 5601 * of the provided entry, or {@code false} if not. 5602 * 5603 * @throws LDAPException If a problem is encountered while trying to 5604 * communicate with the directory server. 5605 */ 5606 public boolean entryExists(final Entry entry) 5607 throws LDAPException 5608 { 5609 synchronized (entryMap) 5610 { 5611 final Entry e = getEntry(entry.getDN()); 5612 if (e == null) 5613 { 5614 return false; 5615 } 5616 5617 for (final Attribute a : entry.getAttributes()) 5618 { 5619 for (final byte[] value : a.getValueByteArrays()) 5620 { 5621 if (! e.hasAttributeValue(a.getName(), value)) 5622 { 5623 return false; 5624 } 5625 } 5626 } 5627 5628 return true; 5629 } 5630 } 5631 5632 5633 5634 /** 5635 * Ensures that an entry with the provided DN exists in the directory. 5636 * 5637 * @param dn The DN of the entry for which to make the determination. 5638 * 5639 * @throws LDAPException If a problem is encountered while trying to 5640 * communicate with the directory server. 5641 * 5642 * @throws AssertionError If the target entry does not exist. 5643 */ 5644 public void assertEntryExists(final String dn) 5645 throws LDAPException, AssertionError 5646 { 5647 final Entry e = getEntry(dn); 5648 if (e == null) 5649 { 5650 throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn)); 5651 } 5652 } 5653 5654 5655 5656 /** 5657 * Ensures that an entry with the provided DN exists in the directory. 5658 * 5659 * @param dn The DN of the entry for which to make the determination. 5660 * @param filter A filter that the target entry must match. 5661 * 5662 * @throws LDAPException If a problem is encountered while trying to 5663 * communicate with the directory server. 5664 * 5665 * @throws AssertionError If the target entry does not exist or does not 5666 * match the provided filter. 5667 */ 5668 public void assertEntryExists(final String dn, final String filter) 5669 throws LDAPException, AssertionError 5670 { 5671 synchronized (entryMap) 5672 { 5673 final Entry e = getEntry(dn); 5674 if (e == null) 5675 { 5676 throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn)); 5677 } 5678 5679 final Filter f = Filter.create(filter); 5680 try 5681 { 5682 if (! f.matchesEntry(e, schemaRef.get())) 5683 { 5684 throw new AssertionError( 5685 ERR_MEM_HANDLER_TEST_ENTRY_DOES_NOT_MATCH_FILTER.get(dn, 5686 filter)); 5687 } 5688 } 5689 catch (final LDAPException le) 5690 { 5691 Debug.debugException(le); 5692 throw new AssertionError( 5693 ERR_MEM_HANDLER_TEST_ENTRY_DOES_NOT_MATCH_FILTER.get(dn, filter)); 5694 } 5695 } 5696 } 5697 5698 5699 5700 /** 5701 * Ensures that an entry exists in the directory with the same DN and all 5702 * attribute values contained in the provided entry. The server entry may 5703 * contain additional attributes and/or attribute values not included in the 5704 * provided entry. 5705 * 5706 * @param entry The entry expected to be present in the directory server. 5707 * 5708 * @throws LDAPException If a problem is encountered while trying to 5709 * communicate with the directory server. 5710 * 5711 * @throws AssertionError If the target entry does not exist or does not 5712 * match the provided filter. 5713 */ 5714 public void assertEntryExists(final Entry entry) 5715 throws LDAPException, AssertionError 5716 { 5717 synchronized (entryMap) 5718 { 5719 final Entry e = getEntry(entry.getDN()); 5720 if (e == null) 5721 { 5722 throw new AssertionError( 5723 ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(entry.getDN())); 5724 } 5725 5726 5727 final Collection<Attribute> attrs = entry.getAttributes(); 5728 final List<String> messages = new ArrayList<String>(attrs.size()); 5729 5730 final Schema schema = schemaRef.get(); 5731 for (final Attribute a : entry.getAttributes()) 5732 { 5733 final Filter presFilter = Filter.createPresenceFilter(a.getName()); 5734 if (! presFilter.matchesEntry(e, schema)) 5735 { 5736 messages.add(ERR_MEM_HANDLER_TEST_ATTR_MISSING.get(entry.getDN(), 5737 a.getName())); 5738 continue; 5739 } 5740 5741 for (final byte[] value : a.getValueByteArrays()) 5742 { 5743 final Filter eqFilter = Filter.createEqualityFilter(a.getName(), 5744 value); 5745 if (! eqFilter.matchesEntry(e, schema)) 5746 { 5747 messages.add(ERR_MEM_HANDLER_TEST_VALUE_MISSING.get(entry.getDN(), 5748 a.getName(), StaticUtils.toUTF8String(value))); 5749 } 5750 } 5751 } 5752 5753 if (! messages.isEmpty()) 5754 { 5755 throw new AssertionError(StaticUtils.concatenateStrings(messages)); 5756 } 5757 } 5758 } 5759 5760 5761 5762 /** 5763 * Retrieves a list containing the DNs of the entries which are missing from 5764 * the directory server. 5765 * 5766 * @param dns The DNs of the entries to try to find in the server. 5767 * 5768 * @return A list containing all of the provided DNs that were not found in 5769 * the server, or an empty list if all entries were found. 5770 * 5771 * @throws LDAPException If a problem is encountered while trying to 5772 * communicate with the directory server. 5773 */ 5774 public List<String> getMissingEntryDNs(final Collection<String> dns) 5775 throws LDAPException 5776 { 5777 synchronized (entryMap) 5778 { 5779 final List<String> missingDNs = new ArrayList<String>(dns.size()); 5780 for (final String dn : dns) 5781 { 5782 final Entry e = getEntry(dn); 5783 if (e == null) 5784 { 5785 missingDNs.add(dn); 5786 } 5787 } 5788 5789 return missingDNs; 5790 } 5791 } 5792 5793 5794 5795 /** 5796 * Ensures that all of the entries with the provided DNs exist in the 5797 * directory. 5798 * 5799 * @param dns The DNs of the entries for which to make the determination. 5800 * 5801 * @throws LDAPException If a problem is encountered while trying to 5802 * communicate with the directory server. 5803 * 5804 * @throws AssertionError If any of the target entries does not exist. 5805 */ 5806 public void assertEntriesExist(final Collection<String> dns) 5807 throws LDAPException, AssertionError 5808 { 5809 synchronized (entryMap) 5810 { 5811 final List<String> missingDNs = getMissingEntryDNs(dns); 5812 if (missingDNs.isEmpty()) 5813 { 5814 return; 5815 } 5816 5817 final List<String> messages = new ArrayList<String>(missingDNs.size()); 5818 for (final String dn : missingDNs) 5819 { 5820 messages.add(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn)); 5821 } 5822 5823 throw new AssertionError(StaticUtils.concatenateStrings(messages)); 5824 } 5825 } 5826 5827 5828 5829 /** 5830 * Retrieves a list containing all of the named attributes which do not exist 5831 * in the target entry. 5832 * 5833 * @param dn The DN of the entry to examine. 5834 * @param attributeNames The names of the attributes expected to be present 5835 * in the target entry. 5836 * 5837 * @return A list containing the names of the attributes which were not 5838 * present in the target entry, an empty list if all specified 5839 * attributes were found in the entry, or {@code null} if the target 5840 * entry does not exist. 5841 * 5842 * @throws LDAPException If a problem is encountered while trying to 5843 * communicate with the directory server. 5844 */ 5845 public List<String> getMissingAttributeNames(final String dn, 5846 final Collection<String> attributeNames) 5847 throws LDAPException 5848 { 5849 synchronized (entryMap) 5850 { 5851 final Entry e = getEntry(dn); 5852 if (e == null) 5853 { 5854 return null; 5855 } 5856 5857 final Schema schema = schemaRef.get(); 5858 final List<String> missingAttrs = 5859 new ArrayList<String>(attributeNames.size()); 5860 for (final String attr : attributeNames) 5861 { 5862 final Filter f = Filter.createPresenceFilter(attr); 5863 if (! f.matchesEntry(e, schema)) 5864 { 5865 missingAttrs.add(attr); 5866 } 5867 } 5868 5869 return missingAttrs; 5870 } 5871 } 5872 5873 5874 5875 /** 5876 * Ensures that the specified entry exists in the directory with all of the 5877 * specified attributes. 5878 * 5879 * @param dn The DN of the entry to examine. 5880 * @param attributeNames The names of the attributes that are expected to be 5881 * present in the provided entry. 5882 * 5883 * @throws LDAPException If a problem is encountered while trying to 5884 * communicate with the directory server. 5885 * 5886 * @throws AssertionError If the target entry does not exist or does not 5887 * contain all of the specified attributes. 5888 */ 5889 public void assertAttributeExists(final String dn, 5890 final Collection<String> attributeNames) 5891 throws LDAPException, AssertionError 5892 { 5893 synchronized (entryMap) 5894 { 5895 final List<String> missingAttrs = 5896 getMissingAttributeNames(dn, attributeNames); 5897 if (missingAttrs == null) 5898 { 5899 throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn)); 5900 } 5901 else if (missingAttrs.isEmpty()) 5902 { 5903 return; 5904 } 5905 5906 final List<String> messages = new ArrayList<String>(missingAttrs.size()); 5907 for (final String attr : missingAttrs) 5908 { 5909 messages.add(ERR_MEM_HANDLER_TEST_ATTR_MISSING.get(dn, attr)); 5910 } 5911 5912 throw new AssertionError(StaticUtils.concatenateStrings(messages)); 5913 } 5914 } 5915 5916 5917 5918 /** 5919 * Retrieves a list of all provided attribute values which are missing from 5920 * the specified entry. The target attribute may or may not contain 5921 * additional values. 5922 * 5923 * @param dn The DN of the entry to examine. 5924 * @param attributeName The attribute expected to be present in the target 5925 * entry with the given values. 5926 * @param attributeValues The values expected to be present in the target 5927 * entry. 5928 * 5929 * @return A list containing all of the provided values which were not found 5930 * in the entry, an empty list if all provided attribute values were 5931 * found, or {@code null} if the target entry does not exist. 5932 * 5933 * @throws LDAPException If a problem is encountered while trying to 5934 * communicate with the directory server. 5935 */ 5936 public List<String> getMissingAttributeValues(final String dn, 5937 final String attributeName, 5938 final Collection<String> attributeValues) 5939 throws LDAPException 5940 { 5941 synchronized (entryMap) 5942 { 5943 final Entry e = getEntry(dn); 5944 if (e == null) 5945 { 5946 return null; 5947 } 5948 5949 final Schema schema = schemaRef.get(); 5950 final List<String> missingValues = 5951 new ArrayList<String>(attributeValues.size()); 5952 for (final String value : attributeValues) 5953 { 5954 final Filter f = Filter.createEqualityFilter(attributeName, value); 5955 if (! f.matchesEntry(e, schema)) 5956 { 5957 missingValues.add(value); 5958 } 5959 } 5960 5961 return missingValues; 5962 } 5963 } 5964 5965 5966 5967 /** 5968 * Ensures that the specified entry exists in the directory with all of the 5969 * specified values for the given attribute. The attribute may or may not 5970 * contain additional values. 5971 * 5972 * @param dn The DN of the entry to examine. 5973 * @param attributeName The name of the attribute to examine. 5974 * @param attributeValues The set of values which must exist for the given 5975 * attribute. 5976 * 5977 * @throws LDAPException If a problem is encountered while trying to 5978 * communicate with the directory server. 5979 * 5980 * @throws AssertionError If the target entry does not exist, does not 5981 * contain the specified attribute, or that attribute 5982 * does not have all of the specified values. 5983 */ 5984 public void assertValueExists(final String dn, 5985 final String attributeName, 5986 final Collection<String> attributeValues) 5987 throws LDAPException, AssertionError 5988 { 5989 synchronized (entryMap) 5990 { 5991 final List<String> missingValues = 5992 getMissingAttributeValues(dn, attributeName, attributeValues); 5993 if (missingValues == null) 5994 { 5995 throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn)); 5996 } 5997 else if (missingValues.isEmpty()) 5998 { 5999 return; 6000 } 6001 6002 // See if the attribute exists at all in the entry. 6003 final Entry e = getEntry(dn); 6004 final Filter f = Filter.createPresenceFilter(attributeName); 6005 if (! f.matchesEntry(e, schemaRef.get())) 6006 { 6007 throw new AssertionError( 6008 ERR_MEM_HANDLER_TEST_ATTR_MISSING.get(dn, attributeName)); 6009 } 6010 6011 final List<String> messages = new ArrayList<String>(missingValues.size()); 6012 for (final String value : missingValues) 6013 { 6014 messages.add(ERR_MEM_HANDLER_TEST_VALUE_MISSING.get(dn, attributeName, 6015 value)); 6016 } 6017 6018 throw new AssertionError(StaticUtils.concatenateStrings(messages)); 6019 } 6020 } 6021 6022 6023 6024 /** 6025 * Ensures that the specified entry does not exist in the directory. 6026 * 6027 * @param dn The DN of the entry expected to be missing. 6028 * 6029 * @throws LDAPException If a problem is encountered while trying to 6030 * communicate with the directory server. 6031 * 6032 * @throws AssertionError If the target entry is found in the server. 6033 */ 6034 public void assertEntryMissing(final String dn) 6035 throws LDAPException, AssertionError 6036 { 6037 final Entry e = getEntry(dn); 6038 if (e != null) 6039 { 6040 throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_EXISTS.get(dn)); 6041 } 6042 } 6043 6044 6045 6046 /** 6047 * Ensures that the specified entry exists in the directory but does not 6048 * contain any of the specified attributes. 6049 * 6050 * @param dn The DN of the entry expected to be present. 6051 * @param attributeNames The names of the attributes expected to be missing 6052 * from the entry. 6053 * 6054 * @throws LDAPException If a problem is encountered while trying to 6055 * communicate with the directory server. 6056 * 6057 * @throws AssertionError If the target entry is missing from the server, or 6058 * if it contains any of the target attributes. 6059 */ 6060 public void assertAttributeMissing(final String dn, 6061 final Collection<String> attributeNames) 6062 throws LDAPException, AssertionError 6063 { 6064 synchronized (entryMap) 6065 { 6066 final Entry e = getEntry(dn); 6067 if (e == null) 6068 { 6069 throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn)); 6070 } 6071 6072 final Schema schema = schemaRef.get(); 6073 final List<String> messages = 6074 new ArrayList<String>(attributeNames.size()); 6075 for (final String name : attributeNames) 6076 { 6077 final Filter f = Filter.createPresenceFilter(name); 6078 if (f.matchesEntry(e, schema)) 6079 { 6080 messages.add(ERR_MEM_HANDLER_TEST_ATTR_EXISTS.get(dn, name)); 6081 } 6082 } 6083 6084 if (! messages.isEmpty()) 6085 { 6086 throw new AssertionError(StaticUtils.concatenateStrings(messages)); 6087 } 6088 } 6089 } 6090 6091 6092 6093 /** 6094 * Ensures that the specified entry exists in the directory but does not 6095 * contain any of the specified attribute values. 6096 * 6097 * @param dn The DN of the entry expected to be present. 6098 * @param attributeName The name of the attribute to examine. 6099 * @param attributeValues The values expected to be missing from the target 6100 * entry. 6101 * 6102 * @throws LDAPException If a problem is encountered while trying to 6103 * communicate with the directory server. 6104 * 6105 * @throws AssertionError If the target entry is missing from the server, or 6106 * if it contains any of the target attribute values. 6107 */ 6108 public void assertValueMissing(final String dn, 6109 final String attributeName, 6110 final Collection<String> attributeValues) 6111 throws LDAPException, AssertionError 6112 { 6113 synchronized (entryMap) 6114 { 6115 final Entry e = getEntry(dn); 6116 if (e == null) 6117 { 6118 throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn)); 6119 } 6120 6121 final Schema schema = schemaRef.get(); 6122 final List<String> messages = 6123 new ArrayList<String>(attributeValues.size()); 6124 for (final String value : attributeValues) 6125 { 6126 final Filter f = Filter.createEqualityFilter(attributeName, value); 6127 if (f.matchesEntry(e, schema)) 6128 { 6129 messages.add(ERR_MEM_HANDLER_TEST_VALUE_EXISTS.get(dn, attributeName, 6130 value)); 6131 } 6132 } 6133 6134 if (! messages.isEmpty()) 6135 { 6136 throw new AssertionError(StaticUtils.concatenateStrings(messages)); 6137 } 6138 } 6139 } 6140}