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