001/* 002 * Copyright 2011-2017 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2011-2017 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 final long processingStartTime = System.currentTimeMillis(); 2990 sleepBeforeProcessing(); 2991 2992 // Look at the time limit for the search request and see if sleeping 2993 // would have caused us to exceed that time limit. It's extremely 2994 // unlikely that any search in the in-memory directory server would take 2995 // a second or more to complete, and that's the minimum time limit that 2996 // can be requested, so there's no need to check the time limit in most 2997 // cases. However, someone may want to force a "time limit exceeded" 2998 // response by configuring a delay that is greater than the requested time 2999 // limit, so we should check now to see if that's been exceeded. 3000 final long timeLimitMillis = 1000L * request.getTimeLimit(); 3001 if (timeLimitMillis > 0L) 3002 { 3003 final long timeLimitExpirationTime = 3004 processingStartTime + timeLimitMillis; 3005 if (System.currentTimeMillis() >= timeLimitExpirationTime) 3006 { 3007 return new LDAPMessage(messageID, new SearchResultDoneProtocolOp( 3008 ResultCode.TIME_LIMIT_EXCEEDED_INT_VALUE, null, 3009 ERR_MEM_HANDLER_TIME_LIMIT_EXCEEDED.get(), null)); 3010 } 3011 } 3012 3013 // Process the provided request controls. 3014 final Map<String,Control> controlMap; 3015 try 3016 { 3017 controlMap = RequestControlPreProcessor.processControls( 3018 LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_REQUEST, controls); 3019 } 3020 catch (final LDAPException le) 3021 { 3022 Debug.debugException(le); 3023 return new LDAPMessage(messageID, new SearchResultDoneProtocolOp( 3024 le.getResultCode().intValue(), null, le.getMessage(), null)); 3025 } 3026 final ArrayList<Control> responseControls = new ArrayList<Control>(1); 3027 3028 3029 // If this operation type is not allowed, then reject it. 3030 final boolean isInternalOp = 3031 controlMap.containsKey(OID_INTERNAL_OPERATION_REQUEST_CONTROL); 3032 if ((! isInternalOp) && 3033 (! config.getAllowedOperationTypes().contains(OperationType.SEARCH))) 3034 { 3035 return new LDAPMessage(messageID, new SearchResultDoneProtocolOp( 3036 ResultCode.UNWILLING_TO_PERFORM_INT_VALUE, null, 3037 ERR_MEM_HANDLER_SEARCH_NOT_ALLOWED.get(), null)); 3038 } 3039 3040 3041 // If this operation type requires authentication, then ensure that the 3042 // client is authenticated. 3043 if ((authenticatedDN.isNullDN() && 3044 config.getAuthenticationRequiredOperationTypes().contains( 3045 OperationType.SEARCH))) 3046 { 3047 return new LDAPMessage(messageID, new SearchResultDoneProtocolOp( 3048 ResultCode.INSUFFICIENT_ACCESS_RIGHTS_INT_VALUE, null, 3049 ERR_MEM_HANDLER_SEARCH_REQUIRES_AUTH.get(), null)); 3050 } 3051 3052 3053 // Get the parsed base DN. 3054 final DN baseDN; 3055 final Schema schema = schemaRef.get(); 3056 try 3057 { 3058 baseDN = new DN(request.getBaseDN(), schema); 3059 } 3060 catch (final LDAPException le) 3061 { 3062 Debug.debugException(le); 3063 return new LDAPMessage(messageID, new SearchResultDoneProtocolOp( 3064 ResultCode.INVALID_DN_SYNTAX_INT_VALUE, null, 3065 ERR_MEM_HANDLER_SEARCH_MALFORMED_BASE.get(request.getBaseDN(), 3066 le.getMessage()), 3067 null)); 3068 } 3069 3070 // See if the search base or one of its superiors is a smart referral. 3071 final boolean hasManageDsaIT = controlMap.containsKey( 3072 ManageDsaITRequestControl.MANAGE_DSA_IT_REQUEST_OID); 3073 if (! hasManageDsaIT) 3074 { 3075 final Entry referralEntry = findNearestReferral(baseDN); 3076 if (referralEntry != null) 3077 { 3078 return new LDAPMessage(messageID, new SearchResultDoneProtocolOp( 3079 ResultCode.REFERRAL_INT_VALUE, referralEntry.getDN(), 3080 INFO_MEM_HANDLER_REFERRAL_ENCOUNTERED.get(), 3081 getReferralURLs(baseDN, referralEntry))); 3082 } 3083 } 3084 3085 // Make sure that the base entry exists. It may be the root DSE or 3086 // subschema subentry. 3087 final Entry baseEntry; 3088 boolean includeChangeLog = true; 3089 if (baseDN.isNullDN()) 3090 { 3091 baseEntry = generateRootDSE(); 3092 includeChangeLog = false; 3093 } 3094 else if (baseDN.equals(subschemaSubentryDN)) 3095 { 3096 baseEntry = subschemaSubentryRef.get(); 3097 } 3098 else 3099 { 3100 baseEntry = entryMap.get(baseDN); 3101 } 3102 3103 if (baseEntry == null) 3104 { 3105 return new LDAPMessage(messageID, new SearchResultDoneProtocolOp( 3106 ResultCode.NO_SUCH_OBJECT_INT_VALUE, getMatchedDNString(baseDN), 3107 ERR_MEM_HANDLER_SEARCH_BASE_DOES_NOT_EXIST.get( 3108 request.getBaseDN()), 3109 null)); 3110 } 3111 3112 // Perform any necessary processing for the assertion and proxied auth 3113 // controls. 3114 try 3115 { 3116 handleAssertionRequestControl(controlMap, baseEntry); 3117 handleProxiedAuthControl(controlMap); 3118 } 3119 catch (final LDAPException le) 3120 { 3121 Debug.debugException(le); 3122 return new LDAPMessage(messageID, new SearchResultDoneProtocolOp( 3123 le.getResultCode().intValue(), null, le.getMessage(), null)); 3124 } 3125 3126 // Create a temporary list to hold all of the entries to be returned. 3127 // These entries will not have been pared down based on the requested 3128 // attributes. 3129 final List<Entry> fullEntryList = new ArrayList<Entry>(entryMap.size()); 3130 3131findEntriesAndRefs: 3132 { 3133 // Check the scope. If it is a base-level search, then we only need to 3134 // examine the base entry. Otherwise, we'll have to scan the entire 3135 // entry map. 3136 final Filter filter = request.getFilter(); 3137 final SearchScope scope = request.getScope(); 3138 final boolean includeSubEntries = ((scope == SearchScope.BASE) || 3139 controlMap.containsKey( 3140 SubentriesRequestControl.SUBENTRIES_REQUEST_OID)); 3141 if (scope == SearchScope.BASE) 3142 { 3143 try 3144 { 3145 if (filter.matchesEntry(baseEntry, schema)) 3146 { 3147 processSearchEntry(baseEntry, includeSubEntries, includeChangeLog, 3148 hasManageDsaIT, fullEntryList, referenceList); 3149 } 3150 } 3151 catch (final Exception e) 3152 { 3153 Debug.debugException(e); 3154 } 3155 3156 break findEntriesAndRefs; 3157 } 3158 3159 // If the search uses a single-level scope and the base DN is the root 3160 // DSE, then we will only examine the defined base entries for the data 3161 // set. 3162 if ((scope == SearchScope.ONE) && baseDN.isNullDN()) 3163 { 3164 for (final DN dn : baseDNs) 3165 { 3166 final Entry e = entryMap.get(dn); 3167 if (e != null) 3168 { 3169 try 3170 { 3171 if (filter.matchesEntry(e, schema)) 3172 { 3173 processSearchEntry(e, includeSubEntries, includeChangeLog, 3174 hasManageDsaIT, fullEntryList, referenceList); 3175 } 3176 } 3177 catch (final Exception ex) 3178 { 3179 Debug.debugException(ex); 3180 } 3181 } 3182 } 3183 3184 break findEntriesAndRefs; 3185 } 3186 3187 3188 // Try to use indexes to process the request. If we can't use any 3189 // indexes to get a candidate list, then just iterate over all the 3190 // entries. It's not necessary to consider the root DSE for non-base 3191 // scopes. 3192 final Set<DN> candidateDNs = indexSearch(filter); 3193 if (candidateDNs == null) 3194 { 3195 for (final Map.Entry<DN,ReadOnlyEntry> me : entryMap.entrySet()) 3196 { 3197 final DN dn = me.getKey(); 3198 final Entry entry = me.getValue(); 3199 try 3200 { 3201 if (dn.matchesBaseAndScope(baseDN, scope) && 3202 filter.matchesEntry(entry, schema)) 3203 { 3204 processSearchEntry(entry, includeSubEntries, includeChangeLog, 3205 hasManageDsaIT, fullEntryList, referenceList); 3206 } 3207 } 3208 catch (final Exception e) 3209 { 3210 Debug.debugException(e); 3211 } 3212 } 3213 } 3214 else 3215 { 3216 for (final DN dn : candidateDNs) 3217 { 3218 try 3219 { 3220 if (! dn.matchesBaseAndScope(baseDN, scope)) 3221 { 3222 continue; 3223 } 3224 3225 final Entry entry = entryMap.get(dn); 3226 if (filter.matchesEntry(entry, schema)) 3227 { 3228 processSearchEntry(entry, includeSubEntries, includeChangeLog, 3229 hasManageDsaIT, fullEntryList, referenceList); 3230 } 3231 } 3232 catch (final Exception e) 3233 { 3234 Debug.debugException(e); 3235 } 3236 } 3237 } 3238 } 3239 3240 3241 // If the request included the server-side sort request control, then sort 3242 // the matching entries appropriately. 3243 final ServerSideSortRequestControl sortRequestControl = 3244 (ServerSideSortRequestControl) controlMap.get( 3245 ServerSideSortRequestControl.SERVER_SIDE_SORT_REQUEST_OID); 3246 if (sortRequestControl != null) 3247 { 3248 final EntrySorter entrySorter = new EntrySorter(false, schema, 3249 sortRequestControl.getSortKeys()); 3250 final SortedSet<Entry> sortedEntrySet = entrySorter.sort(fullEntryList); 3251 fullEntryList.clear(); 3252 fullEntryList.addAll(sortedEntrySet); 3253 3254 responseControls.add(new ServerSideSortResponseControl( 3255 ResultCode.SUCCESS, null, false)); 3256 } 3257 3258 3259 // If the request included the simple paged results control, then handle 3260 // it. 3261 final SimplePagedResultsControl pagedResultsControl = 3262 (SimplePagedResultsControl) 3263 controlMap.get(SimplePagedResultsControl.PAGED_RESULTS_OID); 3264 if (pagedResultsControl != null) 3265 { 3266 final int totalSize = fullEntryList.size(); 3267 final int pageSize = pagedResultsControl.getSize(); 3268 final ASN1OctetString cookie = pagedResultsControl.getCookie(); 3269 3270 final int offset; 3271 if ((cookie == null) || (cookie.getValueLength() == 0)) 3272 { 3273 // This is the first request in the series, so start at the beginning 3274 // of the list. 3275 offset = 0; 3276 } 3277 else 3278 { 3279 // The cookie value will simply be an integer representation of the 3280 // offset within the result list at which to start the next batch. 3281 try 3282 { 3283 final ASN1Integer offsetInteger = 3284 ASN1Integer.decodeAsInteger(cookie.getValue()); 3285 offset = offsetInteger.intValue(); 3286 } 3287 catch (final Exception e) 3288 { 3289 Debug.debugException(e); 3290 return new LDAPMessage(messageID, 3291 new SearchResultDoneProtocolOp( 3292 ResultCode.PROTOCOL_ERROR_INT_VALUE, null, 3293 ERR_MEM_HANDLER_MALFORMED_PAGED_RESULTS_COOKIE.get(), 3294 null), 3295 responseControls); 3296 } 3297 } 3298 3299 // Create an iterator that will be used to remove entries from the 3300 // result set that are outside of the requested page of results. 3301 int pos = 0; 3302 final Iterator<Entry> iterator = fullEntryList.iterator(); 3303 3304 // First, remove entries at the beginning of the list until we hit the 3305 // offset. 3306 while (iterator.hasNext() && (pos < offset)) 3307 { 3308 iterator.next(); 3309 iterator.remove(); 3310 pos++; 3311 } 3312 3313 // Next, skip over the entries that should be returned. 3314 int keptEntries = 0; 3315 while (iterator.hasNext() && (keptEntries < pageSize)) 3316 { 3317 iterator.next(); 3318 pos++; 3319 keptEntries++; 3320 } 3321 3322 // If there are still entries left, then remove them and create a cookie 3323 // to include in the response. Otherwise, use an empty cookie. 3324 if (iterator.hasNext()) 3325 { 3326 responseControls.add(new SimplePagedResultsControl(totalSize, 3327 new ASN1OctetString(new ASN1Integer(pos).encode()), false)); 3328 while (iterator.hasNext()) 3329 { 3330 iterator.next(); 3331 iterator.remove(); 3332 } 3333 } 3334 else 3335 { 3336 responseControls.add(new SimplePagedResultsControl(totalSize, 3337 new ASN1OctetString(), false)); 3338 } 3339 } 3340 3341 3342 // If the request includes the virtual list view request control, then 3343 // handle it. 3344 final VirtualListViewRequestControl vlvRequest = 3345 (VirtualListViewRequestControl) controlMap.get( 3346 VirtualListViewRequestControl.VIRTUAL_LIST_VIEW_REQUEST_OID); 3347 if (vlvRequest != null) 3348 { 3349 final int totalEntries = fullEntryList.size(); 3350 final ASN1OctetString assertionValue = vlvRequest.getAssertionValue(); 3351 3352 // Figure out the position of the target entry in the list. 3353 int offset = vlvRequest.getTargetOffset(); 3354 if (assertionValue == null) 3355 { 3356 // The offset is one-based, so we need to adjust it for the list's 3357 // zero-based offset. Also, make sure to put it within the bounds of 3358 // the list. 3359 offset--; 3360 offset = Math.max(0, offset); 3361 offset = Math.min(fullEntryList.size(), offset); 3362 } 3363 else 3364 { 3365 final SortKey primarySortKey = sortRequestControl.getSortKeys()[0]; 3366 3367 final Entry testEntry = new Entry("cn=test", schema, 3368 new Attribute(primarySortKey.getAttributeName(), 3369 assertionValue)); 3370 3371 final EntrySorter entrySorter = 3372 new EntrySorter(false, schema, primarySortKey); 3373 3374 offset = fullEntryList.size(); 3375 for (int i=0; i < fullEntryList.size(); i++) 3376 { 3377 if (entrySorter.compare(fullEntryList.get(i), testEntry) >= 0) 3378 { 3379 offset = i; 3380 break; 3381 } 3382 } 3383 } 3384 3385 // Get the start and end positions based on the before and after counts. 3386 final int beforeCount = Math.max(0, vlvRequest.getBeforeCount()); 3387 final int afterCount = Math.max(0, vlvRequest.getAfterCount()); 3388 3389 final int start = Math.max(0, (offset - beforeCount)); 3390 final int end = 3391 Math.min(fullEntryList.size(), (offset + afterCount + 1)); 3392 3393 // Create an iterator to use to alter the list so that it only contains 3394 // the appropriate set of entries. 3395 int pos = 0; 3396 final Iterator<Entry> iterator = fullEntryList.iterator(); 3397 while (iterator.hasNext()) 3398 { 3399 iterator.next(); 3400 if ((pos < start) || (pos >= end)) 3401 { 3402 iterator.remove(); 3403 } 3404 pos++; 3405 } 3406 3407 // Create the appropriate response control. 3408 responseControls.add(new VirtualListViewResponseControl((offset+1), 3409 totalEntries, ResultCode.SUCCESS, null)); 3410 } 3411 3412 3413 // Process the set of requested attributes so that we can pare down the 3414 // entries. 3415 final AtomicBoolean allUserAttrs = new AtomicBoolean(false); 3416 final AtomicBoolean allOpAttrs = new AtomicBoolean(false); 3417 final Map<String,List<List<String>>> returnAttrs = 3418 processRequestedAttributes(request.getAttributes(), allUserAttrs, 3419 allOpAttrs); 3420 3421 final int sizeLimit; 3422 if (request.getSizeLimit() > 0) 3423 { 3424 sizeLimit = Math.min(request.getSizeLimit(), maxSizeLimit); 3425 } 3426 else 3427 { 3428 sizeLimit = maxSizeLimit; 3429 } 3430 3431 int entryCount = 0; 3432 for (final Entry e : fullEntryList) 3433 { 3434 entryCount++; 3435 if (entryCount > sizeLimit) 3436 { 3437 return new LDAPMessage(messageID, 3438 new SearchResultDoneProtocolOp( 3439 ResultCode.SIZE_LIMIT_EXCEEDED_INT_VALUE, null, 3440 ERR_MEM_HANDLER_SEARCH_SIZE_LIMIT_EXCEEDED.get(), null), 3441 responseControls); 3442 } 3443 3444 final Entry trimmedEntry = trimForRequestedAttributes(e, 3445 allUserAttrs.get(), allOpAttrs.get(), returnAttrs); 3446 if (request.typesOnly()) 3447 { 3448 final Entry typesOnlyEntry = new Entry(trimmedEntry.getDN(), schema); 3449 for (final Attribute a : trimmedEntry.getAttributes()) 3450 { 3451 typesOnlyEntry.addAttribute(new Attribute(a.getName())); 3452 } 3453 entryList.add(new SearchResultEntry(typesOnlyEntry)); 3454 } 3455 else 3456 { 3457 entryList.add(new SearchResultEntry(trimmedEntry)); 3458 } 3459 } 3460 3461 return new LDAPMessage(messageID, 3462 new SearchResultDoneProtocolOp(ResultCode.SUCCESS_INT_VALUE, null, 3463 null, null), 3464 responseControls); 3465 } 3466 } 3467 3468 3469 3470 /** 3471 * Performs any necessary index processing to add the provided entry. 3472 * 3473 * @param entry The entry that has been added. 3474 */ 3475 private void indexAdd(final Entry entry) 3476 { 3477 for (final InMemoryDirectoryServerEqualityAttributeIndex i : 3478 equalityIndexes.values()) 3479 { 3480 try 3481 { 3482 i.processAdd(entry); 3483 } 3484 catch (final LDAPException le) 3485 { 3486 Debug.debugException(le); 3487 } 3488 } 3489 } 3490 3491 3492 3493 /** 3494 * Performs any necessary index processing to delete the provided entry. 3495 * 3496 * @param entry The entry that has been deleted. 3497 */ 3498 private void indexDelete(final Entry entry) 3499 { 3500 for (final InMemoryDirectoryServerEqualityAttributeIndex i : 3501 equalityIndexes.values()) 3502 { 3503 try 3504 { 3505 i.processDelete(entry); 3506 } 3507 catch (final LDAPException le) 3508 { 3509 Debug.debugException(le); 3510 } 3511 } 3512 } 3513 3514 3515 3516 /** 3517 * Attempts to use indexes to obtain a candidate list for the provided filter. 3518 * 3519 * @param filter The filter to be processed. 3520 * 3521 * @return The DNs of entries which may match the given filter, or 3522 * {@code null} if the filter is not indexed. 3523 */ 3524 private Set<DN> indexSearch(final Filter filter) 3525 { 3526 switch (filter.getFilterType()) 3527 { 3528 case Filter.FILTER_TYPE_AND: 3529 Filter[] comps = filter.getComponents(); 3530 if (comps.length == 0) 3531 { 3532 return null; 3533 } 3534 else if (comps.length == 1) 3535 { 3536 return indexSearch(comps[0]); 3537 } 3538 else 3539 { 3540 Set<DN> candidateSet = null; 3541 for (final Filter f : comps) 3542 { 3543 final Set<DN> dnSet = indexSearch(f); 3544 if (dnSet != null) 3545 { 3546 if (candidateSet == null) 3547 { 3548 candidateSet = new TreeSet<DN>(dnSet); 3549 } 3550 else 3551 { 3552 candidateSet.retainAll(dnSet); 3553 } 3554 } 3555 } 3556 return candidateSet; 3557 } 3558 3559 case Filter.FILTER_TYPE_OR: 3560 comps = filter.getComponents(); 3561 if (comps.length == 0) 3562 { 3563 return Collections.emptySet(); 3564 } 3565 else if (comps.length == 1) 3566 { 3567 return indexSearch(comps[0]); 3568 } 3569 else 3570 { 3571 Set<DN> candidateSet = null; 3572 for (final Filter f : comps) 3573 { 3574 final Set<DN> dnSet = indexSearch(f); 3575 if (dnSet == null) 3576 { 3577 return null; 3578 } 3579 3580 if (candidateSet == null) 3581 { 3582 candidateSet = new TreeSet<DN>(dnSet); 3583 } 3584 else 3585 { 3586 candidateSet.addAll(dnSet); 3587 } 3588 } 3589 return candidateSet; 3590 } 3591 3592 case Filter.FILTER_TYPE_EQUALITY: 3593 final Schema schema = schemaRef.get(); 3594 if (schema == null) 3595 { 3596 return null; 3597 } 3598 final AttributeTypeDefinition at = 3599 schema.getAttributeType(filter.getAttributeName()); 3600 if (at == null) 3601 { 3602 return null; 3603 } 3604 final InMemoryDirectoryServerEqualityAttributeIndex i = 3605 equalityIndexes.get(at); 3606 if (i == null) 3607 { 3608 return null; 3609 } 3610 try 3611 { 3612 return i.getMatchingEntries(filter.getRawAssertionValue()); 3613 } 3614 catch (final Exception e) 3615 { 3616 Debug.debugException(e); 3617 return null; 3618 } 3619 3620 default: 3621 return null; 3622 } 3623 } 3624 3625 3626 3627 /** 3628 * Determines whether the provided set of controls includes a transaction 3629 * specification request control. If so, then it will verify that it 3630 * references a valid transaction for the client. If the request is part of a 3631 * valid transaction, then the transaction specification request control will 3632 * be removed and the request will be stashed in the client connection state 3633 * so that it can be retrieved and processed when the transaction is 3634 * committed. 3635 * 3636 * @param messageID The message ID for the request to be processed. 3637 * @param request The protocol op for the request to be processed. 3638 * @param controls The set of controls for the request to be processed. 3639 * 3640 * @return The transaction ID for the associated transaction, or {@code null} 3641 * if the request is not part of any transaction. 3642 * 3643 * @throws LDAPException If the transaction specification request control is 3644 * present but does not refer to a valid transaction 3645 * for the associated client connection. 3646 */ 3647 @SuppressWarnings("unchecked") 3648 private ASN1OctetString processTransactionRequest(final int messageID, 3649 final ProtocolOp request, 3650 final Map<String,Control> controls) 3651 throws LDAPException 3652 { 3653 final TransactionSpecificationRequestControl txnControl = 3654 (TransactionSpecificationRequestControl) 3655 controls.remove(TransactionSpecificationRequestControl. 3656 TRANSACTION_SPECIFICATION_REQUEST_OID); 3657 if (txnControl == null) 3658 { 3659 return null; 3660 } 3661 3662 // See if the client has an active transaction. If not, then fail. 3663 final ASN1OctetString txnID = txnControl.getTransactionID(); 3664 final ObjectPair<ASN1OctetString,List<LDAPMessage>> txnInfo = 3665 (ObjectPair<ASN1OctetString,List<LDAPMessage>>) connectionState.get( 3666 TransactionExtendedOperationHandler.STATE_VARIABLE_TXN_INFO); 3667 if (txnInfo == null) 3668 { 3669 throw new LDAPException(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION, 3670 ERR_MEM_HANDLER_TXN_CONTROL_WITHOUT_TXN.get(txnID.stringValue())); 3671 } 3672 3673 3674 // Make sure that the active transaction has a transaction ID that matches 3675 // the transaction ID from the control. If not, then abort the existing 3676 // transaction and fail. 3677 final ASN1OctetString existingTxnID = txnInfo.getFirst(); 3678 if (! txnID.stringValue().equals(existingTxnID.stringValue())) 3679 { 3680 connectionState.remove( 3681 TransactionExtendedOperationHandler.STATE_VARIABLE_TXN_INFO); 3682 connection.sendUnsolicitedNotification( 3683 new AbortedTransactionExtendedResult(existingTxnID, 3684 ResultCode.CONSTRAINT_VIOLATION, 3685 ERR_MEM_HANDLER_TXN_ABORTED_BY_CONTROL_TXN_ID_MISMATCH.get( 3686 existingTxnID.stringValue(), txnID.stringValue()), 3687 null, null, null)); 3688 throw new LDAPException(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION, 3689 ERR_MEM_HANDLER_TXN_CONTROL_ID_MISMATCH.get(txnID.stringValue(), 3690 existingTxnID.stringValue())); 3691 } 3692 3693 3694 // Stash the request in the transaction state information so that it will 3695 // be processed when the transaction is committed. 3696 txnInfo.getSecond().add(new LDAPMessage(messageID, request, 3697 new ArrayList<Control>(controls.values()))); 3698 3699 return txnID; 3700 } 3701 3702 3703 3704 /** 3705 * Sleeps for a period of time (if appropriate) before beginning processing 3706 * for an operation. 3707 */ 3708 private void sleepBeforeProcessing() 3709 { 3710 final long delay = processingDelayMillis.get(); 3711 if (delay > 0) 3712 { 3713 try 3714 { 3715 Thread.sleep(delay); 3716 } 3717 catch (final Exception e) 3718 { 3719 Debug.debugException(e); 3720 3721 if (e instanceof InterruptedException) 3722 { 3723 Thread.currentThread().interrupt(); 3724 } 3725 } 3726 } 3727 } 3728 3729 3730 3731 /** 3732 * Retrieves the number of entries currently held in the server. 3733 * 3734 * @param includeChangeLog Indicates whether to include entries that are 3735 * part of the changelog in the count. 3736 * 3737 * @return The number of entries currently held in the server. 3738 */ 3739 public int countEntries(final boolean includeChangeLog) 3740 { 3741 synchronized (entryMap) 3742 { 3743 if (includeChangeLog || (maxChangelogEntries == 0)) 3744 { 3745 return entryMap.size(); 3746 } 3747 else 3748 { 3749 int count = 0; 3750 3751 for (final DN dn : entryMap.keySet()) 3752 { 3753 if (! dn.isDescendantOf(changeLogBaseDN, true)) 3754 { 3755 count++; 3756 } 3757 } 3758 3759 return count; 3760 } 3761 } 3762 } 3763 3764 3765 3766 /** 3767 * Retrieves the number of entries currently held in the server whose DN 3768 * matches or is subordinate to the provided base DN. 3769 * 3770 * @param baseDN The base DN to use for the determination. 3771 * 3772 * @return The number of entries currently held in the server whose DN 3773 * matches or is subordinate to the provided base DN. 3774 * 3775 * @throws LDAPException If the provided string cannot be parsed as a valid 3776 * DN. 3777 */ 3778 public int countEntriesBelow(final String baseDN) 3779 throws LDAPException 3780 { 3781 synchronized (entryMap) 3782 { 3783 final DN parsedBaseDN = new DN(baseDN, schemaRef.get()); 3784 3785 int count = 0; 3786 for (final DN dn : entryMap.keySet()) 3787 { 3788 if (dn.isDescendantOf(parsedBaseDN, true)) 3789 { 3790 count++; 3791 } 3792 } 3793 3794 return count; 3795 } 3796 } 3797 3798 3799 3800 /** 3801 * Removes all entries currently held in the server. If a changelog is 3802 * enabled, then all changelog entries will also be cleared but the base 3803 * "cn=changelog" entry will be retained. 3804 */ 3805 public void clear() 3806 { 3807 synchronized (entryMap) 3808 { 3809 restoreSnapshot(initialSnapshot); 3810 } 3811 } 3812 3813 3814 3815 /** 3816 * Reads entries from the provided LDIF reader and adds them to the server, 3817 * optionally clearing any existing entries before beginning to add the new 3818 * entries. If an error is encountered while adding entries from LDIF then 3819 * the server will remain populated with the data it held before the import 3820 * attempt (even if the {@code clear} is given with a value of {@code true}). 3821 * 3822 * @param clear Indicates whether to remove all existing entries prior 3823 * to adding entries read from LDIF. 3824 * @param ldifReader The LDIF reader to use to obtain the entries to be 3825 * imported. 3826 * 3827 * @return The number of entries read from LDIF and added to the server. 3828 * 3829 * @throws LDAPException If a problem occurs while reading entries or adding 3830 * them to the server. 3831 */ 3832 public int importFromLDIF(final boolean clear, final LDIFReader ldifReader) 3833 throws LDAPException 3834 { 3835 synchronized (entryMap) 3836 { 3837 final InMemoryDirectoryServerSnapshot snapshot = createSnapshot(); 3838 boolean restoreSnapshot = true; 3839 3840 try 3841 { 3842 if (clear) 3843 { 3844 restoreSnapshot(initialSnapshot); 3845 } 3846 3847 int entriesAdded = 0; 3848 while (true) 3849 { 3850 final Entry entry; 3851 try 3852 { 3853 entry = ldifReader.readEntry(); 3854 if (entry == null) 3855 { 3856 restoreSnapshot = false; 3857 return entriesAdded; 3858 } 3859 } 3860 catch (final LDIFException le) 3861 { 3862 Debug.debugException(le); 3863 throw new LDAPException(ResultCode.LOCAL_ERROR, 3864 ERR_MEM_HANDLER_INIT_FROM_LDIF_READ_ERROR.get(le.getMessage()), 3865 le); 3866 } 3867 catch (final Exception e) 3868 { 3869 Debug.debugException(e); 3870 throw new LDAPException(ResultCode.LOCAL_ERROR, 3871 ERR_MEM_HANDLER_INIT_FROM_LDIF_READ_ERROR.get( 3872 StaticUtils.getExceptionMessage(e)), 3873 e); 3874 } 3875 3876 addEntry(entry, true); 3877 entriesAdded++; 3878 } 3879 } 3880 finally 3881 { 3882 try 3883 { 3884 ldifReader.close(); 3885 } 3886 catch (final Exception e) 3887 { 3888 Debug.debugException(e); 3889 } 3890 3891 if (restoreSnapshot) 3892 { 3893 restoreSnapshot(snapshot); 3894 } 3895 } 3896 } 3897 } 3898 3899 3900 3901 /** 3902 * Writes all entries contained in the server to LDIF using the provided 3903 * writer. 3904 * 3905 * @param ldifWriter The LDIF writer to use when writing the 3906 * entries. It must not be {@code null}. 3907 * @param excludeGeneratedAttrs Indicates whether to exclude automatically 3908 * generated operational attributes like 3909 * entryUUID, entryDN, creatorsName, etc. 3910 * @param excludeChangeLog Indicates whether to exclude entries 3911 * contained in the changelog. 3912 * @param closeWriter Indicates whether the LDIF writer should be 3913 * closed after all entries have been written. 3914 * 3915 * @return The number of entries written to LDIF. 3916 * 3917 * @throws LDAPException If a problem is encountered while attempting to 3918 * write an entry to LDIF. 3919 */ 3920 public int exportToLDIF(final LDIFWriter ldifWriter, 3921 final boolean excludeGeneratedAttrs, 3922 final boolean excludeChangeLog, 3923 final boolean closeWriter) 3924 throws LDAPException 3925 { 3926 synchronized (entryMap) 3927 { 3928 boolean exceptionThrown = false; 3929 3930 try 3931 { 3932 int entriesWritten = 0; 3933 3934 for (final Map.Entry<DN,ReadOnlyEntry> me : entryMap.entrySet()) 3935 { 3936 final DN dn = me.getKey(); 3937 if (excludeChangeLog && dn.isDescendantOf(changeLogBaseDN, true)) 3938 { 3939 continue; 3940 } 3941 3942 final Entry entry; 3943 if (excludeGeneratedAttrs) 3944 { 3945 entry = me.getValue().duplicate(); 3946 entry.removeAttribute("entryDN"); 3947 entry.removeAttribute("entryUUID"); 3948 entry.removeAttribute("subschemaSubentry"); 3949 entry.removeAttribute("creatorsName"); 3950 entry.removeAttribute("createTimestamp"); 3951 entry.removeAttribute("modifiersName"); 3952 entry.removeAttribute("modifyTimestamp"); 3953 } 3954 else 3955 { 3956 entry = me.getValue(); 3957 } 3958 3959 try 3960 { 3961 ldifWriter.writeEntry(entry); 3962 entriesWritten++; 3963 } 3964 catch (final Exception e) 3965 { 3966 Debug.debugException(e); 3967 exceptionThrown = true; 3968 throw new LDAPException(ResultCode.LOCAL_ERROR, 3969 ERR_MEM_HANDLER_LDIF_WRITE_ERROR.get(entry.getDN(), 3970 StaticUtils.getExceptionMessage(e)), 3971 e); 3972 } 3973 } 3974 3975 return entriesWritten; 3976 } 3977 finally 3978 { 3979 if (closeWriter) 3980 { 3981 try 3982 { 3983 ldifWriter.close(); 3984 } 3985 catch (final Exception e) 3986 { 3987 Debug.debugException(e); 3988 if (! exceptionThrown) 3989 { 3990 throw new LDAPException(ResultCode.LOCAL_ERROR, 3991 ERR_MEM_HANDLER_LDIF_WRITE_CLOSE_ERROR.get( 3992 StaticUtils.getExceptionMessage(e)), 3993 e); 3994 } 3995 } 3996 } 3997 } 3998 } 3999 } 4000 4001 4002 4003 /** 4004 * Attempts to add the provided entry to the in-memory data set. The attempt 4005 * will fail if any of the following conditions is true: 4006 * <UL> 4007 * <LI>The provided entry has a malformed DN.</LI> 4008 * <LI>The provided entry has the null DN.</LI> 4009 * <LI>The provided entry has a DN that is the same as or subordinate to the 4010 * subschema subentry.</LI> 4011 * <LI>An entry already exists with the same DN as the entry in the provided 4012 * request.</LI> 4013 * <LI>The entry is outside the set of base DNs for the server.</LI> 4014 * <LI>The entry is below one of the defined base DNs but the immediate 4015 * parent entry does not exist.</LI> 4016 * <LI>If a schema was provided, and the entry is not valid according to the 4017 * constraints of that schema.</LI> 4018 * </UL> 4019 * 4020 * @param entry The entry to be added. It must not be 4021 * {@code null}. 4022 * @param ignoreNoUserModification Indicates whether to ignore constraints 4023 * normally imposed by the 4024 * NO-USER-MODIFICATION element in attribute 4025 * type definitions. 4026 * 4027 * @throws LDAPException If a problem occurs while attempting to add the 4028 * provided entry. 4029 */ 4030 public void addEntry(final Entry entry, 4031 final boolean ignoreNoUserModification) 4032 throws LDAPException 4033 { 4034 final List<Control> controls; 4035 if (ignoreNoUserModification) 4036 { 4037 controls = new ArrayList<Control>(1); 4038 controls.add(new Control(OID_INTERNAL_OPERATION_REQUEST_CONTROL, false)); 4039 } 4040 else 4041 { 4042 controls = Collections.emptyList(); 4043 } 4044 4045 final AddRequestProtocolOp addRequest = new AddRequestProtocolOp( 4046 entry.getDN(), new ArrayList<Attribute>(entry.getAttributes())); 4047 4048 final LDAPMessage resultMessage = 4049 processAddRequest(-1, addRequest, controls); 4050 4051 final AddResponseProtocolOp addResponse = 4052 resultMessage.getAddResponseProtocolOp(); 4053 if (addResponse.getResultCode() != ResultCode.SUCCESS_INT_VALUE) 4054 { 4055 throw new LDAPException(ResultCode.valueOf(addResponse.getResultCode()), 4056 addResponse.getDiagnosticMessage(), addResponse.getMatchedDN(), 4057 stringListToArray(addResponse.getReferralURLs())); 4058 } 4059 } 4060 4061 4062 4063 /** 4064 * Attempts to add all of the provided entries to the server. If an error is 4065 * encountered during processing, then the contents of the server will be the 4066 * same as they were before this method was called. 4067 * 4068 * @param entries The collection of entries to be added. 4069 * 4070 * @throws LDAPException If a problem was encountered while attempting to 4071 * add any of the entries to the server. 4072 */ 4073 public void addEntries(final List<? extends Entry> entries) 4074 throws LDAPException 4075 { 4076 synchronized (entryMap) 4077 { 4078 final InMemoryDirectoryServerSnapshot snapshot = createSnapshot(); 4079 boolean restoreSnapshot = true; 4080 4081 try 4082 { 4083 for (final Entry e : entries) 4084 { 4085 addEntry(e, false); 4086 } 4087 restoreSnapshot = false; 4088 } 4089 finally 4090 { 4091 if (restoreSnapshot) 4092 { 4093 restoreSnapshot(snapshot); 4094 } 4095 } 4096 } 4097 } 4098 4099 4100 4101 /** 4102 * Removes the entry with the specified DN and any subordinate entries it may 4103 * have. 4104 * 4105 * @param baseDN The DN of the entry to be deleted. It must not be 4106 * {@code null} or represent the null DN. 4107 * 4108 * @return The number of entries actually removed, or zero if the specified 4109 * base DN does not represent an entry in the server. 4110 * 4111 * @throws LDAPException If the provided base DN is not a valid DN, or is 4112 * the DN of an entry that cannot be deleted (e.g., 4113 * the null DN). 4114 */ 4115 public int deleteSubtree(final String baseDN) 4116 throws LDAPException 4117 { 4118 synchronized (entryMap) 4119 { 4120 final DN dn = new DN(baseDN, schemaRef.get()); 4121 if (dn.isNullDN()) 4122 { 4123 throw new LDAPException(ResultCode.UNWILLING_TO_PERFORM, 4124 ERR_MEM_HANDLER_DELETE_ROOT_DSE.get()); 4125 } 4126 4127 int numDeleted = 0; 4128 4129 final Iterator<Map.Entry<DN,ReadOnlyEntry>> iterator = 4130 entryMap.entrySet().iterator(); 4131 while (iterator.hasNext()) 4132 { 4133 final Map.Entry<DN,ReadOnlyEntry> e = iterator.next(); 4134 if (e.getKey().isDescendantOf(dn, true)) 4135 { 4136 iterator.remove(); 4137 numDeleted++; 4138 } 4139 } 4140 4141 return numDeleted; 4142 } 4143 } 4144 4145 4146 4147 /** 4148 * Attempts to apply the provided set of modifications to the specified entry. 4149 * The attempt will fail if any of the following conditions is true: 4150 * <UL> 4151 * <LI>The target DN is malformed.</LI> 4152 * <LI>The target entry is the root DSE.</LI> 4153 * <LI>The target entry is the subschema subentry.</LI> 4154 * <LI>The target entry does not exist.</LI> 4155 * <LI>Any of the modifications cannot be applied to the entry.</LI> 4156 * <LI>If a schema was provided, and the entry violates any of the 4157 * constraints of that schema.</LI> 4158 * </UL> 4159 * 4160 * @param dn The DN of the entry to be modified. 4161 * @param mods The set of modifications to be applied to the entry. 4162 * 4163 * @throws LDAPException If a problem is encountered while attempting to 4164 * update the specified entry. 4165 */ 4166 public void modifyEntry(final String dn, final List<Modification> mods) 4167 throws LDAPException 4168 { 4169 final ModifyRequestProtocolOp modifyRequest = 4170 new ModifyRequestProtocolOp(dn, mods); 4171 4172 final LDAPMessage resultMessage = processModifyRequest(-1, modifyRequest, 4173 Collections.<Control>emptyList()); 4174 4175 final ModifyResponseProtocolOp modifyResponse = 4176 resultMessage.getModifyResponseProtocolOp(); 4177 if (modifyResponse.getResultCode() != ResultCode.SUCCESS_INT_VALUE) 4178 { 4179 throw new LDAPException( 4180 ResultCode.valueOf(modifyResponse.getResultCode()), 4181 modifyResponse.getDiagnosticMessage(), modifyResponse.getMatchedDN(), 4182 stringListToArray(modifyResponse.getReferralURLs())); 4183 } 4184 } 4185 4186 4187 4188 /** 4189 * Retrieves a read-only representation the entry with the specified DN, if 4190 * it exists. 4191 * 4192 * @param dn The DN of the entry to retrieve. 4193 * 4194 * @return The requested entry, or {@code null} if no entry exists with the 4195 * given DN. 4196 * 4197 * @throws LDAPException If the provided DN is malformed. 4198 */ 4199 public ReadOnlyEntry getEntry(final String dn) 4200 throws LDAPException 4201 { 4202 return getEntry(new DN(dn, schemaRef.get())); 4203 } 4204 4205 4206 4207 /** 4208 * Retrieves a read-only representation the entry with the specified DN, if 4209 * it exists. 4210 * 4211 * @param dn The DN of the entry to retrieve. 4212 * 4213 * @return The requested entry, or {@code null} if no entry exists with the 4214 * given DN. 4215 */ 4216 public ReadOnlyEntry getEntry(final DN dn) 4217 { 4218 synchronized (entryMap) 4219 { 4220 if (dn.isNullDN()) 4221 { 4222 return generateRootDSE(); 4223 } 4224 else if (dn.equals(subschemaSubentryDN)) 4225 { 4226 return subschemaSubentryRef.get(); 4227 } 4228 else 4229 { 4230 final Entry e = entryMap.get(dn); 4231 if (e == null) 4232 { 4233 return null; 4234 } 4235 else 4236 { 4237 return new ReadOnlyEntry(e); 4238 } 4239 } 4240 } 4241 } 4242 4243 4244 4245 /** 4246 * Retrieves a list of all entries in the server which match the given 4247 * search criteria. 4248 * 4249 * @param baseDN The base DN to use for the search. It must not be 4250 * {@code null}. 4251 * @param scope The scope to use for the search. It must not be 4252 * {@code null}. 4253 * @param filter The filter to use for the search. It must not be 4254 * {@code null}. 4255 * 4256 * @return A list of the entries that matched the provided search criteria. 4257 * 4258 * @throws LDAPException If a problem is encountered while performing the 4259 * search. 4260 */ 4261 public List<ReadOnlyEntry> search(final String baseDN, 4262 final SearchScope scope, 4263 final Filter filter) 4264 throws LDAPException 4265 { 4266 synchronized (entryMap) 4267 { 4268 final DN parsedDN; 4269 final Schema schema = schemaRef.get(); 4270 try 4271 { 4272 parsedDN = new DN(baseDN, schema); 4273 } 4274 catch (final LDAPException le) 4275 { 4276 Debug.debugException(le); 4277 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX, 4278 ERR_MEM_HANDLER_SEARCH_MALFORMED_BASE.get(baseDN, le.getMessage()), 4279 le); 4280 } 4281 4282 final ReadOnlyEntry baseEntry; 4283 if (parsedDN.isNullDN()) 4284 { 4285 baseEntry = generateRootDSE(); 4286 } 4287 else if (parsedDN.equals(subschemaSubentryDN)) 4288 { 4289 baseEntry = subschemaSubentryRef.get(); 4290 } 4291 else 4292 { 4293 final Entry e = entryMap.get(parsedDN); 4294 if (e == null) 4295 { 4296 throw new LDAPException(ResultCode.NO_SUCH_OBJECT, 4297 ERR_MEM_HANDLER_SEARCH_BASE_DOES_NOT_EXIST.get(baseDN), 4298 getMatchedDNString(parsedDN), null); 4299 } 4300 4301 baseEntry = new ReadOnlyEntry(e); 4302 } 4303 4304 if (scope == SearchScope.BASE) 4305 { 4306 final List<ReadOnlyEntry> entryList = new ArrayList<ReadOnlyEntry>(1); 4307 4308 try 4309 { 4310 if (filter.matchesEntry(baseEntry, schema)) 4311 { 4312 entryList.add(baseEntry); 4313 } 4314 } 4315 catch (final LDAPException le) 4316 { 4317 Debug.debugException(le); 4318 } 4319 4320 return Collections.unmodifiableList(entryList); 4321 } 4322 4323 if ((scope == SearchScope.ONE) && parsedDN.isNullDN()) 4324 { 4325 final List<ReadOnlyEntry> entryList = 4326 new ArrayList<ReadOnlyEntry>(baseDNs.size()); 4327 4328 try 4329 { 4330 for (final DN dn : baseDNs) 4331 { 4332 final Entry e = entryMap.get(dn); 4333 if ((e != null) && filter.matchesEntry(e, schema)) 4334 { 4335 entryList.add(new ReadOnlyEntry(e)); 4336 } 4337 } 4338 } 4339 catch (final LDAPException le) 4340 { 4341 Debug.debugException(le); 4342 } 4343 4344 return Collections.unmodifiableList(entryList); 4345 } 4346 4347 final List<ReadOnlyEntry> entryList = new ArrayList<ReadOnlyEntry>(10); 4348 for (final Map.Entry<DN,ReadOnlyEntry> me : entryMap.entrySet()) 4349 { 4350 final DN dn = me.getKey(); 4351 if (dn.matchesBaseAndScope(parsedDN, scope)) 4352 { 4353 // We don't want to return changelog entries searches based at the 4354 // root DSE. 4355 if (parsedDN.isNullDN() && dn.isDescendantOf(changeLogBaseDN, true)) 4356 { 4357 continue; 4358 } 4359 4360 try 4361 { 4362 final Entry entry = me.getValue(); 4363 if (filter.matchesEntry(entry, schema)) 4364 { 4365 entryList.add(new ReadOnlyEntry(entry)); 4366 } 4367 } 4368 catch (final LDAPException le) 4369 { 4370 Debug.debugException(le); 4371 } 4372 } 4373 } 4374 4375 return Collections.unmodifiableList(entryList); 4376 } 4377 } 4378 4379 4380 4381 /** 4382 * Generates an entry to use as the server root DSE. 4383 * 4384 * @return The generated root DSE entry. 4385 */ 4386 private ReadOnlyEntry generateRootDSE() 4387 { 4388 final ReadOnlyEntry rootDSEFromCfg = config.getRootDSEEntry(); 4389 if (rootDSEFromCfg != null) 4390 { 4391 return rootDSEFromCfg; 4392 } 4393 4394 final Entry rootDSEEntry = new Entry(DN.NULL_DN, schemaRef.get()); 4395 rootDSEEntry.addAttribute("objectClass", "top", "ds-root-dse"); 4396 rootDSEEntry.addAttribute(new Attribute("supportedLDAPVersion", 4397 IntegerMatchingRule.getInstance(), "3")); 4398 4399 final String vendorName = config.getVendorName(); 4400 if (vendorName != null) 4401 { 4402 rootDSEEntry.addAttribute("vendorName", vendorName); 4403 } 4404 4405 final String vendorVersion = config.getVendorVersion(); 4406 if (vendorVersion != null) 4407 { 4408 rootDSEEntry.addAttribute("vendorVersion", vendorVersion); 4409 } 4410 4411 rootDSEEntry.addAttribute(new Attribute("subschemaSubentry", 4412 DistinguishedNameMatchingRule.getInstance(), 4413 subschemaSubentryDN.toString())); 4414 rootDSEEntry.addAttribute(new Attribute("entryDN", 4415 DistinguishedNameMatchingRule.getInstance(), "")); 4416 rootDSEEntry.addAttribute("entryUUID", UUID.randomUUID().toString()); 4417 4418 rootDSEEntry.addAttribute("supportedFeatures", 4419 "1.3.6.1.4.1.4203.1.5.1", // All operational attributes 4420 "1.3.6.1.4.1.4203.1.5.2", // Request attributes by object class 4421 "1.3.6.1.4.1.4203.1.5.3", // LDAP absolute true and false filters 4422 "1.3.6.1.1.14"); // Increment modification type 4423 4424 final TreeSet<String> ctlSet = new TreeSet<String>(); 4425 4426 ctlSet.add(AssertionRequestControl.ASSERTION_REQUEST_OID); 4427 ctlSet.add(AuthorizationIdentityRequestControl. 4428 AUTHORIZATION_IDENTITY_REQUEST_OID); 4429 ctlSet.add(DontUseCopyRequestControl.DONT_USE_COPY_REQUEST_OID); 4430 ctlSet.add(ManageDsaITRequestControl.MANAGE_DSA_IT_REQUEST_OID); 4431 ctlSet.add(DraftZeilengaLDAPNoOp12RequestControl.NO_OP_REQUEST_OID); 4432 ctlSet.add(PermissiveModifyRequestControl.PERMISSIVE_MODIFY_REQUEST_OID); 4433 ctlSet.add(PostReadRequestControl.POST_READ_REQUEST_OID); 4434 ctlSet.add(PreReadRequestControl.PRE_READ_REQUEST_OID); 4435 ctlSet.add(ProxiedAuthorizationV1RequestControl. 4436 PROXIED_AUTHORIZATION_V1_REQUEST_OID); 4437 ctlSet.add(ProxiedAuthorizationV2RequestControl. 4438 PROXIED_AUTHORIZATION_V2_REQUEST_OID); 4439 ctlSet.add(ServerSideSortRequestControl.SERVER_SIDE_SORT_REQUEST_OID); 4440 ctlSet.add(SimplePagedResultsControl.PAGED_RESULTS_OID); 4441 ctlSet.add(SubentriesRequestControl.SUBENTRIES_REQUEST_OID); 4442 ctlSet.add(SubtreeDeleteRequestControl.SUBTREE_DELETE_REQUEST_OID); 4443 ctlSet.add(TransactionSpecificationRequestControl. 4444 TRANSACTION_SPECIFICATION_REQUEST_OID); 4445 ctlSet.add(VirtualListViewRequestControl.VIRTUAL_LIST_VIEW_REQUEST_OID); 4446 4447 final String[] controlOIDs = new String[ctlSet.size()]; 4448 rootDSEEntry.addAttribute("supportedControl", ctlSet.toArray(controlOIDs)); 4449 4450 4451 if (! extendedRequestHandlers.isEmpty()) 4452 { 4453 final String[] oidArray = new String[extendedRequestHandlers.size()]; 4454 rootDSEEntry.addAttribute("supportedExtension", 4455 extendedRequestHandlers.keySet().toArray(oidArray)); 4456 4457 for (final InMemoryListenerConfig c : config.getListenerConfigs()) 4458 { 4459 if (c.getStartTLSSocketFactory() != null) 4460 { 4461 rootDSEEntry.addAttribute("supportedExtension", 4462 StartTLSExtendedRequest.STARTTLS_REQUEST_OID); 4463 break; 4464 } 4465 } 4466 } 4467 4468 if (! saslBindHandlers.isEmpty()) 4469 { 4470 final String[] mechanismArray = new String[saslBindHandlers.size()]; 4471 rootDSEEntry.addAttribute("supportedSASLMechanisms", 4472 saslBindHandlers.keySet().toArray(mechanismArray)); 4473 } 4474 4475 int pos = 0; 4476 final String[] baseDNStrings = new String[baseDNs.size()]; 4477 for (final DN baseDN : baseDNs) 4478 { 4479 baseDNStrings[pos++] = baseDN.toString(); 4480 } 4481 rootDSEEntry.addAttribute(new Attribute("namingContexts", 4482 DistinguishedNameMatchingRule.getInstance(), baseDNStrings)); 4483 4484 if (maxChangelogEntries > 0) 4485 { 4486 rootDSEEntry.addAttribute(new Attribute("changeLog", 4487 DistinguishedNameMatchingRule.getInstance(), 4488 changeLogBaseDN.toString())); 4489 rootDSEEntry.addAttribute(new Attribute("firstChangeNumber", 4490 IntegerMatchingRule.getInstance(), firstChangeNumber.toString())); 4491 rootDSEEntry.addAttribute(new Attribute("lastChangeNumber", 4492 IntegerMatchingRule.getInstance(), lastChangeNumber.toString())); 4493 } 4494 4495 return new ReadOnlyEntry(rootDSEEntry); 4496 } 4497 4498 4499 4500 /** 4501 * Generates a subschema subentry from the provided schema object. 4502 * 4503 * @param schema The schema to use to generate the subschema subentry. It 4504 * may be {@code null} if a minimal default entry should be 4505 * generated. 4506 * 4507 * @return The generated subschema subentry. 4508 */ 4509 private static ReadOnlyEntry generateSubschemaSubentry(final Schema schema) 4510 { 4511 final Entry e; 4512 4513 if (schema == null) 4514 { 4515 e = new Entry("cn=schema", schema); 4516 4517 e.addAttribute("objectClass", "namedObject", "ldapSubEntry", 4518 "subschema"); 4519 e.addAttribute("cn", "schema"); 4520 } 4521 else 4522 { 4523 e = schema.getSchemaEntry().duplicate(); 4524 } 4525 4526 try 4527 { 4528 e.addAttribute("entryDN", DN.normalize(e.getDN(), schema)); 4529 } 4530 catch (final LDAPException le) 4531 { 4532 // This should never happen. 4533 Debug.debugException(le); 4534 e.setAttribute("entryDN", StaticUtils.toLowerCase(e.getDN())); 4535 } 4536 4537 4538 e.addAttribute("entryUUID", UUID.randomUUID().toString()); 4539 return new ReadOnlyEntry(e); 4540 } 4541 4542 4543 4544 /** 4545 * Processes the set of requested attributes from the given search request. 4546 * 4547 * @param attrList The list of requested attributes to examine. 4548 * @param allUserAttrs Indicates whether to return all user attributes. It 4549 * should have an initial value of {@code false}. 4550 * @param allOpAttrs Indicates whether to return all operational 4551 * attributes. It should have an initial value of 4552 * {@code false}. 4553 * 4554 * @return A map of specific attribute types to be returned. The keys of the 4555 * map will be the lowercase OID and names of the attribute types, 4556 * and the values will be a list of option sets for the associated 4557 * attribute type. 4558 */ 4559 private Map<String,List<List<String>>> processRequestedAttributes( 4560 final List<String> attrList, final AtomicBoolean allUserAttrs, 4561 final AtomicBoolean allOpAttrs) 4562 { 4563 if (attrList.isEmpty()) 4564 { 4565 allUserAttrs.set(true); 4566 return Collections.emptyMap(); 4567 } 4568 4569 final Schema schema = schemaRef.get(); 4570 final HashMap<String,List<List<String>>> m = 4571 new HashMap<String,List<List<String>>>(attrList.size() * 2); 4572 for (final String s : attrList) 4573 { 4574 if (s.equals("*")) 4575 { 4576 // All user attributes. 4577 allUserAttrs.set(true); 4578 } 4579 else if (s.equals("+")) 4580 { 4581 // All operational attributes. 4582 allOpAttrs.set(true); 4583 } 4584 else if (s.startsWith("@")) 4585 { 4586 // Return attributes by object class. This can only be supported if a 4587 // schema has been defined. 4588 if (schema != null) 4589 { 4590 final String ocName = s.substring(1); 4591 final ObjectClassDefinition oc = schema.getObjectClass(ocName); 4592 if (oc != null) 4593 { 4594 for (final AttributeTypeDefinition at : 4595 oc.getRequiredAttributes(schema, true)) 4596 { 4597 addAttributeOIDAndNames(at, m, Collections.<String>emptyList()); 4598 } 4599 for (final AttributeTypeDefinition at : 4600 oc.getOptionalAttributes(schema, true)) 4601 { 4602 addAttributeOIDAndNames(at, m, Collections.<String>emptyList()); 4603 } 4604 } 4605 } 4606 } 4607 else 4608 { 4609 final ObjectPair<String,List<String>> nameWithOptions = 4610 getNameWithOptions(s); 4611 if (nameWithOptions == null) 4612 { 4613 continue; 4614 } 4615 4616 final String name = nameWithOptions.getFirst(); 4617 final List<String> options = nameWithOptions.getSecond(); 4618 4619 if (schema == null) 4620 { 4621 // Just use the name as provided. 4622 List<List<String>> optionLists = m.get(name); 4623 if (optionLists == null) 4624 { 4625 optionLists = new ArrayList<List<String>>(1); 4626 m.put(name, optionLists); 4627 } 4628 optionLists.add(options); 4629 } 4630 else 4631 { 4632 // If the attribute type is defined in the schema, then use it to get 4633 // all names and the OID. Otherwise, just use the name as provided. 4634 final AttributeTypeDefinition at = schema.getAttributeType(name); 4635 if (at == null) 4636 { 4637 List<List<String>> optionLists = m.get(name); 4638 if (optionLists == null) 4639 { 4640 optionLists = new ArrayList<List<String>>(1); 4641 m.put(name, optionLists); 4642 } 4643 optionLists.add(options); 4644 } 4645 else 4646 { 4647 addAttributeOIDAndNames(at, m, options); 4648 } 4649 } 4650 } 4651 } 4652 4653 return m; 4654 } 4655 4656 4657 4658 /** 4659 * Parses the provided string into an attribute type and set of options. 4660 * 4661 * @param s The string to be parsed. 4662 * 4663 * @return An {@code ObjectPair} in which the first element is the attribute 4664 * type name and the second is the list of options (or an empty 4665 * list if there are no options). Alternately, a value of 4666 * {@code null} may be returned if the provided string does not 4667 * represent a valid attribute type description. 4668 */ 4669 private static ObjectPair<String,List<String>> getNameWithOptions( 4670 final String s) 4671 { 4672 if (! Attribute.nameIsValid(s, true)) 4673 { 4674 return null; 4675 } 4676 4677 final String l = StaticUtils.toLowerCase(s); 4678 4679 int semicolonPos = l.indexOf(';'); 4680 if (semicolonPos < 0) 4681 { 4682 return new ObjectPair<String,List<String>>(l, 4683 Collections.<String>emptyList()); 4684 } 4685 4686 final String name = l.substring(0, semicolonPos); 4687 final ArrayList<String> optionList = new ArrayList<String>(1); 4688 while (true) 4689 { 4690 final int nextSemicolonPos = l.indexOf(';', semicolonPos+1); 4691 if (nextSemicolonPos < 0) 4692 { 4693 optionList.add(l.substring(semicolonPos+1)); 4694 break; 4695 } 4696 else 4697 { 4698 optionList.add(l.substring(semicolonPos+1, nextSemicolonPos)); 4699 semicolonPos = nextSemicolonPos; 4700 } 4701 } 4702 4703 return new ObjectPair<String,List<String>>(name, optionList); 4704 } 4705 4706 4707 4708 /** 4709 * Adds all-lowercase versions of the OID and all names for the provided 4710 * attribute type definition to the given map with the given options. 4711 * 4712 * @param d The attribute type definition to process. 4713 * @param m The map to which the OID and names should be added. 4714 * @param o The array of attribute options to use in the map. It should be 4715 * empty if no options are needed, and must not be {@code null}. 4716 */ 4717 private void addAttributeOIDAndNames(final AttributeTypeDefinition d, 4718 final Map<String,List<List<String>>> m, 4719 final List<String> o) 4720 { 4721 if (d == null) 4722 { 4723 return; 4724 } 4725 4726 final String lowerOID = StaticUtils.toLowerCase(d.getOID()); 4727 if (lowerOID != null) 4728 { 4729 List<List<String>> l = m.get(lowerOID); 4730 if (l == null) 4731 { 4732 l = new ArrayList<List<String>>(1); 4733 m.put(lowerOID, l); 4734 } 4735 4736 l.add(o); 4737 } 4738 4739 for (final String name : d.getNames()) 4740 { 4741 final String lowerName = StaticUtils.toLowerCase(name); 4742 List<List<String>> l = m.get(lowerName); 4743 if (l == null) 4744 { 4745 l = new ArrayList<List<String>>(1); 4746 m.put(lowerName, l); 4747 } 4748 4749 l.add(o); 4750 } 4751 4752 // If a schema is available, then see if the attribute type has any 4753 // subordinate types. If so, then add them. 4754 final Schema schema = schemaRef.get(); 4755 if (schema != null) 4756 { 4757 for (final AttributeTypeDefinition subordinateType : 4758 schema.getSubordinateAttributeTypes(d)) 4759 { 4760 addAttributeOIDAndNames(subordinateType, m, o); 4761 } 4762 } 4763 } 4764 4765 4766 4767 /** 4768 * Performs the necessary processing to determine whether the given entry 4769 * should be returned as a search result entry or reference, or if it should 4770 * not be returned at all. 4771 * 4772 * @param entry The entry to be processed. 4773 * @param includeSubEntries Indicates whether LDAP subentries should be 4774 * returned to the client. 4775 * @param includeChangeLog Indicates whether entries within the changelog 4776 * should be returned to the client. 4777 * @param hasManageDsaIT Indicates whether the request includes the 4778 * ManageDsaIT control, which can change how smart 4779 * referrals should be handled. 4780 * @param entryList The list to which the entry should be added if 4781 * it should be returned to the client as a search 4782 * result entry. 4783 * @param referenceList The list that should be updated if the provided 4784 * entry represents a smart referral that should be 4785 * returned as a search result reference. 4786 */ 4787 private void processSearchEntry(final Entry entry, 4788 final boolean includeSubEntries, 4789 final boolean includeChangeLog, 4790 final boolean hasManageDsaIT, 4791 final List<Entry> entryList, 4792 final List<SearchResultReference> referenceList) 4793 { 4794 // See if the entry should be suppressed as an LDAP subentry. 4795 if ((! includeSubEntries) && 4796 (entry.hasObjectClass("ldapSubEntry") || 4797 entry.hasObjectClass("inheritableLDAPSubEntry"))) 4798 { 4799 return; 4800 } 4801 4802 // See if the entry should be suppressed as a changelog entry. 4803 try 4804 { 4805 if ((! includeChangeLog) && 4806 (entry.getParsedDN().isDescendantOf(changeLogBaseDN, true))) 4807 { 4808 return; 4809 } 4810 } 4811 catch (final Exception e) 4812 { 4813 // This should never happen. 4814 Debug.debugException(e); 4815 } 4816 4817 // See if the entry is a referral and should result in a reference rather 4818 // than an entry. 4819 if ((! hasManageDsaIT) && entry.hasObjectClass("referral") && 4820 entry.hasAttribute("ref")) 4821 { 4822 referenceList.add(new SearchResultReference( 4823 entry.getAttributeValues("ref"), NO_CONTROLS)); 4824 return; 4825 } 4826 4827 entryList.add(entry); 4828 } 4829 4830 4831 4832 /** 4833 * Retrieves a copy of the provided entry that includes only the appropriate 4834 * set of requested attributes. 4835 * 4836 * @param entry The entry to be returned. 4837 * @param allUserAttrs Indicates whether to return all user attributes. 4838 * @param allOpAttrs Indicates whether to return all operational 4839 * attributes. 4840 * @param returnAttrs A map with information about the specific attribute 4841 * types to return. 4842 * 4843 * @return A copy of the provided entry that includes only the appropriate 4844 * set of requested attributes. 4845 */ 4846 private Entry trimForRequestedAttributes(final Entry entry, 4847 final boolean allUserAttrs, final boolean allOpAttrs, 4848 final Map<String,List<List<String>>> returnAttrs) 4849 { 4850 // See if we can return the entry without paring it down. 4851 final Schema schema = schemaRef.get(); 4852 if (allUserAttrs) 4853 { 4854 if (allOpAttrs || (schema == null)) 4855 { 4856 return entry; 4857 } 4858 } 4859 4860 4861 // If we've gotten here, then we may only need to return a partial entry. 4862 final Entry copy = new Entry(entry.getDN(), schema); 4863 4864 for (final Attribute a : entry.getAttributes()) 4865 { 4866 final ObjectPair<String,List<String>> nameWithOptions = 4867 getNameWithOptions(a.getName()); 4868 final String name = nameWithOptions.getFirst(); 4869 final List<String> options = nameWithOptions.getSecond(); 4870 4871 // If there is a schema, then see if it is an operational attribute, since 4872 // that needs to be handled in a manner different from user attributes 4873 if (schema != null) 4874 { 4875 final AttributeTypeDefinition at = schema.getAttributeType(name); 4876 if ((at != null) && at.isOperational()) 4877 { 4878 if (allOpAttrs) 4879 { 4880 copy.addAttribute(a); 4881 continue; 4882 } 4883 4884 final List<List<String>> optionLists = returnAttrs.get(name); 4885 if (optionLists == null) 4886 { 4887 continue; 4888 } 4889 4890 for (final List<String> optionList : optionLists) 4891 { 4892 boolean matchAll = true; 4893 for (final String option : optionList) 4894 { 4895 if (! options.contains(option)) 4896 { 4897 matchAll = false; 4898 break; 4899 } 4900 } 4901 4902 if (matchAll) 4903 { 4904 copy.addAttribute(a); 4905 break; 4906 } 4907 } 4908 continue; 4909 } 4910 } 4911 4912 // We'll assume that it's a user attribute, and we'll look for an exact 4913 // match on the base name. 4914 if (allUserAttrs) 4915 { 4916 copy.addAttribute(a); 4917 continue; 4918 } 4919 4920 final List<List<String>> optionLists = returnAttrs.get(name); 4921 if (optionLists == null) 4922 { 4923 continue; 4924 } 4925 4926 for (final List<String> optionList : optionLists) 4927 { 4928 boolean matchAll = true; 4929 for (final String option : optionList) 4930 { 4931 if (! options.contains(option)) 4932 { 4933 matchAll = false; 4934 break; 4935 } 4936 } 4937 4938 if (matchAll) 4939 { 4940 copy.addAttribute(a); 4941 break; 4942 } 4943 } 4944 } 4945 4946 return copy; 4947 } 4948 4949 4950 4951 /** 4952 * Retrieves the DN of the existing entry which is the closest hierarchical 4953 * match to the provided DN. 4954 * 4955 * @param dn The DN for which to retrieve the appropriate matched DN. 4956 * 4957 * @return The appropriate matched DN value, or {@code null} if there is 4958 * none. 4959 */ 4960 private String getMatchedDNString(final DN dn) 4961 { 4962 DN parentDN = dn.getParent(); 4963 while (parentDN != null) 4964 { 4965 if (entryMap.containsKey(parentDN)) 4966 { 4967 return parentDN.toString(); 4968 } 4969 4970 parentDN = parentDN.getParent(); 4971 } 4972 4973 return null; 4974 } 4975 4976 4977 4978 /** 4979 * Converts the provided string list to an array. 4980 * 4981 * @param l The possibly null list to be converted. 4982 * 4983 * @return The string array with the same elements as the given list in the 4984 * same order, or {@code null} if the given list was null. 4985 */ 4986 private static String[] stringListToArray(final List<String> l) 4987 { 4988 if (l == null) 4989 { 4990 return null; 4991 } 4992 else 4993 { 4994 final String[] a = new String[l.size()]; 4995 return l.toArray(a); 4996 } 4997 } 4998 4999 5000 5001 /** 5002 * Creates a changelog entry from the information in the provided add request 5003 * and adds it to the server changelog. 5004 * 5005 * @param addRequest The add request to use to construct the changelog 5006 * entry. 5007 * @param authzDN The authorization DN for the change. 5008 */ 5009 private void addChangeLogEntry(final AddRequestProtocolOp addRequest, 5010 final DN authzDN) 5011 { 5012 // If the changelog is disabled, then don't do anything. 5013 if (maxChangelogEntries <= 0) 5014 { 5015 return; 5016 } 5017 5018 final long changeNumber = lastChangeNumber.incrementAndGet(); 5019 final LDIFAddChangeRecord changeRecord = new LDIFAddChangeRecord( 5020 addRequest.getDN(), addRequest.getAttributes()); 5021 try 5022 { 5023 addChangeLogEntry( 5024 ChangeLogEntry.constructChangeLogEntry(changeNumber, changeRecord), 5025 authzDN); 5026 } 5027 catch (final LDAPException le) 5028 { 5029 // This should not happen. 5030 Debug.debugException(le); 5031 } 5032 } 5033 5034 5035 5036 /** 5037 * Creates a changelog entry from the information in the provided delete 5038 * request and adds it to the server changelog. 5039 * 5040 * @param e The entry to be deleted. 5041 * @param authzDN The authorization DN for the change. 5042 */ 5043 private void addDeleteChangeLogEntry(final Entry e, final DN authzDN) 5044 { 5045 // If the changelog is disabled, then don't do anything. 5046 if (maxChangelogEntries <= 0) 5047 { 5048 return; 5049 } 5050 5051 final long changeNumber = lastChangeNumber.incrementAndGet(); 5052 final LDIFDeleteChangeRecord changeRecord = 5053 new LDIFDeleteChangeRecord(e.getDN()); 5054 5055 // Create the changelog entry. 5056 try 5057 { 5058 final ChangeLogEntry cle = ChangeLogEntry.constructChangeLogEntry( 5059 changeNumber, changeRecord); 5060 5061 // Add a set of deleted entry attributes, which is simply an LDIF-encoded 5062 // representation of the entry, excluding the first line since it contains 5063 // the DN. 5064 final StringBuilder deletedEntryAttrsBuffer = new StringBuilder(); 5065 final String[] ldifLines = e.toLDIF(0); 5066 for (int i=1; i < ldifLines.length; i++) 5067 { 5068 deletedEntryAttrsBuffer.append(ldifLines[i]); 5069 deletedEntryAttrsBuffer.append(StaticUtils.EOL); 5070 } 5071 5072 final Entry copy = cle.duplicate(); 5073 copy.addAttribute(ChangeLogEntry.ATTR_DELETED_ENTRY_ATTRS, 5074 deletedEntryAttrsBuffer.toString()); 5075 addChangeLogEntry(new ChangeLogEntry(copy), authzDN); 5076 } 5077 catch (final LDAPException le) 5078 { 5079 // This should never happen. 5080 Debug.debugException(le); 5081 } 5082 } 5083 5084 5085 5086 /** 5087 * Creates a changelog entry from the information in the provided modify 5088 * request and adds it to the server changelog. 5089 * 5090 * @param modifyRequest The modify request to use to construct the changelog 5091 * entry. 5092 * @param authzDN The authorization DN for the change. 5093 */ 5094 private void addChangeLogEntry(final ModifyRequestProtocolOp modifyRequest, 5095 final DN authzDN) 5096 { 5097 // If the changelog is disabled, then don't do anything. 5098 if (maxChangelogEntries <= 0) 5099 { 5100 return; 5101 } 5102 5103 final long changeNumber = lastChangeNumber.incrementAndGet(); 5104 final LDIFModifyChangeRecord changeRecord = 5105 new LDIFModifyChangeRecord(modifyRequest.getDN(), 5106 modifyRequest.getModifications()); 5107 try 5108 { 5109 addChangeLogEntry( 5110 ChangeLogEntry.constructChangeLogEntry(changeNumber, changeRecord), 5111 authzDN); 5112 } 5113 catch (final LDAPException le) 5114 { 5115 // This should not happen. 5116 Debug.debugException(le); 5117 } 5118 } 5119 5120 5121 5122 /** 5123 * Creates a changelog entry from the information in the provided modify DN 5124 * request and adds it to the server changelog. 5125 * 5126 * @param modifyDNRequest The modify DN request to use to construct the 5127 * changelog entry. 5128 * @param authzDN The authorization DN for the change. 5129 */ 5130 private void addChangeLogEntry( 5131 final ModifyDNRequestProtocolOp modifyDNRequest, 5132 final DN authzDN) 5133 { 5134 // If the changelog is disabled, then don't do anything. 5135 if (maxChangelogEntries <= 0) 5136 { 5137 return; 5138 } 5139 5140 final long changeNumber = lastChangeNumber.incrementAndGet(); 5141 final LDIFModifyDNChangeRecord changeRecord = 5142 new LDIFModifyDNChangeRecord(modifyDNRequest.getDN(), 5143 modifyDNRequest.getNewRDN(), modifyDNRequest.deleteOldRDN(), 5144 modifyDNRequest.getNewSuperiorDN()); 5145 try 5146 { 5147 addChangeLogEntry( 5148 ChangeLogEntry.constructChangeLogEntry(changeNumber, changeRecord), 5149 authzDN); 5150 } 5151 catch (final LDAPException le) 5152 { 5153 // This should not happen. 5154 Debug.debugException(le); 5155 } 5156 } 5157 5158 5159 5160 /** 5161 * Adds the provided changelog entry to the data set, removing an old entry if 5162 * necessary to remain within the maximum allowed number of changes. This 5163 * must only be called from a synchronized method, and the change number for 5164 * the changelog entry must have been obtained by calling 5165 * {@code lastChangeNumber.incrementAndGet()}. 5166 * 5167 * @param e The changelog entry to add to the data set. 5168 * @param authzDN The authorization DN for the change. 5169 */ 5170 private void addChangeLogEntry(final ChangeLogEntry e, final DN authzDN) 5171 { 5172 // Construct the DN object to use for the entry and put it in the map. 5173 final long changeNumber = e.getChangeNumber(); 5174 final Schema schema = schemaRef.get(); 5175 final DN dn = new DN( 5176 new RDN("changeNumber", String.valueOf(changeNumber), schema), 5177 changeLogBaseDN); 5178 5179 final Entry entry = e.duplicate(); 5180 if (generateOperationalAttributes) 5181 { 5182 final Date d = new Date(); 5183 entry.addAttribute(new Attribute("entryDN", 5184 DistinguishedNameMatchingRule.getInstance(), 5185 dn.toNormalizedString())); 5186 entry.addAttribute(new Attribute("entryUUID", 5187 UUID.randomUUID().toString())); 5188 entry.addAttribute(new Attribute("subschemaSubentry", 5189 DistinguishedNameMatchingRule.getInstance(), 5190 subschemaSubentryDN.toString())); 5191 entry.addAttribute(new Attribute("creatorsName", 5192 DistinguishedNameMatchingRule.getInstance(), 5193 authzDN.toString())); 5194 entry.addAttribute(new Attribute("createTimestamp", 5195 GeneralizedTimeMatchingRule.getInstance(), 5196 StaticUtils.encodeGeneralizedTime(d))); 5197 entry.addAttribute(new Attribute("modifiersName", 5198 DistinguishedNameMatchingRule.getInstance(), 5199 authzDN.toString())); 5200 entry.addAttribute(new Attribute("modifyTimestamp", 5201 GeneralizedTimeMatchingRule.getInstance(), 5202 StaticUtils.encodeGeneralizedTime(d))); 5203 } 5204 5205 entryMap.put(dn, new ReadOnlyEntry(entry)); 5206 indexAdd(entry); 5207 5208 // Update the first change number and/or trim the changelog if necessary. 5209 final long firstNumber = firstChangeNumber.get(); 5210 if (changeNumber == 1L) 5211 { 5212 // It's the first change, so we need to set the first change number. 5213 firstChangeNumber.set(1); 5214 } 5215 else 5216 { 5217 // See if we need to trim an entry. 5218 final long numChangeLogEntries = changeNumber - firstNumber + 1; 5219 if (numChangeLogEntries > maxChangelogEntries) 5220 { 5221 // We need to delete the first changelog entry and increment the 5222 // first change number. 5223 firstChangeNumber.incrementAndGet(); 5224 final Entry deletedEntry = entryMap.remove(new DN( 5225 new RDN("changeNumber", String.valueOf(firstNumber), schema), 5226 changeLogBaseDN)); 5227 indexDelete(deletedEntry); 5228 } 5229 } 5230 } 5231 5232 5233 5234 /** 5235 * Checks to see if the provided control map includes a proxied authorization 5236 * control (v1 or v2) and if so then attempts to determine the appropriate 5237 * authorization identity to use for the operation. 5238 * 5239 * @param m The map of request controls, indexed by OID. 5240 * 5241 * @return The DN of the authorized user, or the current authentication DN 5242 * if the control map does not include a proxied authorization 5243 * request control. 5244 * 5245 * @throws LDAPException If a problem is encountered while attempting to 5246 * determine the authorization DN. 5247 */ 5248 private DN handleProxiedAuthControl(final Map<String,Control> m) 5249 throws LDAPException 5250 { 5251 final ProxiedAuthorizationV1RequestControl p1 = 5252 (ProxiedAuthorizationV1RequestControl) m.get( 5253 ProxiedAuthorizationV1RequestControl. 5254 PROXIED_AUTHORIZATION_V1_REQUEST_OID); 5255 if (p1 != null) 5256 { 5257 final DN authzDN = new DN(p1.getProxyDN(), schemaRef.get()); 5258 if (authzDN.isNullDN() || 5259 entryMap.containsKey(authzDN) || 5260 additionalBindCredentials.containsKey(authzDN)) 5261 { 5262 return authzDN; 5263 } 5264 else 5265 { 5266 throw new LDAPException(ResultCode.AUTHORIZATION_DENIED, 5267 ERR_MEM_HANDLER_NO_SUCH_IDENTITY.get("dn:" + authzDN.toString())); 5268 } 5269 } 5270 5271 final ProxiedAuthorizationV2RequestControl p2 = 5272 (ProxiedAuthorizationV2RequestControl) m.get( 5273 ProxiedAuthorizationV2RequestControl. 5274 PROXIED_AUTHORIZATION_V2_REQUEST_OID); 5275 if (p2 != null) 5276 { 5277 return getDNForAuthzID(p2.getAuthorizationID()); 5278 } 5279 5280 return authenticatedDN; 5281 } 5282 5283 5284 5285 /** 5286 * Attempts to identify the DN of the user referenced by the provided 5287 * authorization ID string. It may be "dn:" followed by the target DN, or 5288 * "u:" followed by the value of the uid attribute in the entry. If it uses 5289 * the "dn:" form, then it may reference the DN of a regular entry or a DN 5290 * in the configured set of additional bind credentials. 5291 * 5292 * @param authzID The authorization ID to resolve to a user DN. 5293 * 5294 * @return The DN identified for the provided authorization ID. 5295 * 5296 * @throws LDAPException If a problem prevents resolving the authorization 5297 * ID to a user DN. 5298 */ 5299 public DN getDNForAuthzID(final String authzID) 5300 throws LDAPException 5301 { 5302 synchronized (entryMap) 5303 { 5304 final String lowerAuthzID = StaticUtils.toLowerCase(authzID); 5305 if (lowerAuthzID.startsWith("dn:")) 5306 { 5307 if (lowerAuthzID.equals("dn:")) 5308 { 5309 return DN.NULL_DN; 5310 } 5311 else 5312 { 5313 final DN dn = new DN(authzID.substring(3), schemaRef.get()); 5314 if (entryMap.containsKey(dn) || 5315 additionalBindCredentials.containsKey(dn)) 5316 { 5317 return dn; 5318 } 5319 else 5320 { 5321 throw new LDAPException(ResultCode.AUTHORIZATION_DENIED, 5322 ERR_MEM_HANDLER_NO_SUCH_IDENTITY.get(authzID)); 5323 } 5324 } 5325 } 5326 else if (lowerAuthzID.startsWith("u:")) 5327 { 5328 final Filter f = 5329 Filter.createEqualityFilter("uid", authzID.substring(2)); 5330 final List<ReadOnlyEntry> entryList = search("", SearchScope.SUB, f); 5331 if (entryList.size() == 1) 5332 { 5333 return entryList.get(0).getParsedDN(); 5334 } 5335 else 5336 { 5337 throw new LDAPException(ResultCode.AUTHORIZATION_DENIED, 5338 ERR_MEM_HANDLER_NO_SUCH_IDENTITY.get(authzID)); 5339 } 5340 } 5341 else 5342 { 5343 throw new LDAPException(ResultCode.AUTHORIZATION_DENIED, 5344 ERR_MEM_HANDLER_NO_SUCH_IDENTITY.get(authzID)); 5345 } 5346 } 5347 } 5348 5349 5350 5351 /** 5352 * Checks to see if the provided control map includes an assertion request 5353 * control, and if so then checks to see whether the provided entry satisfies 5354 * the filter in that control. 5355 * 5356 * @param m The map of request controls, indexed by OID. 5357 * @param e The entry to examine against the assertion filter. 5358 * 5359 * @throws LDAPException If the control map includes an assertion request 5360 * control and the provided entry does not match the 5361 * filter contained in that control. 5362 */ 5363 private static void handleAssertionRequestControl(final Map<String,Control> m, 5364 final Entry e) 5365 throws LDAPException 5366 { 5367 final AssertionRequestControl c = (AssertionRequestControl) 5368 m.get(AssertionRequestControl.ASSERTION_REQUEST_OID); 5369 if (c == null) 5370 { 5371 return; 5372 } 5373 5374 try 5375 { 5376 if (c.getFilter().matchesEntry(e)) 5377 { 5378 return; 5379 } 5380 } 5381 catch (final LDAPException le) 5382 { 5383 Debug.debugException(le); 5384 } 5385 5386 // If we've gotten here, then the filter doesn't match. 5387 throw new LDAPException(ResultCode.ASSERTION_FAILED, 5388 ERR_MEM_HANDLER_ASSERTION_CONTROL_NOT_SATISFIED.get()); 5389 } 5390 5391 5392 5393 /** 5394 * Checks to see if the provided control map includes a pre-read request 5395 * control, and if so then generates the appropriate response control that 5396 * should be returned to the client. 5397 * 5398 * @param m The map of request controls, indexed by OID. 5399 * @param e The entry as it appeared before the operation. 5400 * 5401 * @return The pre-read response control that should be returned to the 5402 * client, or {@code null} if there is none. 5403 */ 5404 private PreReadResponseControl handlePreReadControl( 5405 final Map<String,Control> m, final Entry e) 5406 { 5407 final PreReadRequestControl c = (PreReadRequestControl) 5408 m.get(PreReadRequestControl.PRE_READ_REQUEST_OID); 5409 if (c == null) 5410 { 5411 return null; 5412 } 5413 5414 final AtomicBoolean allUserAttrs = new AtomicBoolean(false); 5415 final AtomicBoolean allOpAttrs = new AtomicBoolean(false); 5416 final Map<String,List<List<String>>> returnAttrs = 5417 processRequestedAttributes(Arrays.asList(c.getAttributes()), 5418 allUserAttrs, allOpAttrs); 5419 5420 final Entry trimmedEntry = trimForRequestedAttributes(e, allUserAttrs.get(), 5421 allOpAttrs.get(), returnAttrs); 5422 return new PreReadResponseControl(new ReadOnlyEntry(trimmedEntry)); 5423 } 5424 5425 5426 5427 /** 5428 * Checks to see if the provided control map includes a post-read request 5429 * control, and if so then generates the appropriate response control that 5430 * should be returned to the client. 5431 * 5432 * @param m The map of request controls, indexed by OID. 5433 * @param e The entry as it appeared before the operation. 5434 * 5435 * @return The post-read response control that should be returned to the 5436 * client, or {@code null} if there is none. 5437 */ 5438 private PostReadResponseControl handlePostReadControl( 5439 final Map<String,Control> m, final Entry e) 5440 { 5441 final PostReadRequestControl c = (PostReadRequestControl) 5442 m.get(PostReadRequestControl.POST_READ_REQUEST_OID); 5443 if (c == null) 5444 { 5445 return null; 5446 } 5447 5448 final AtomicBoolean allUserAttrs = new AtomicBoolean(false); 5449 final AtomicBoolean allOpAttrs = new AtomicBoolean(false); 5450 final Map<String,List<List<String>>> returnAttrs = 5451 processRequestedAttributes(Arrays.asList(c.getAttributes()), 5452 allUserAttrs, allOpAttrs); 5453 5454 final Entry trimmedEntry = trimForRequestedAttributes(e, allUserAttrs.get(), 5455 allOpAttrs.get(), returnAttrs); 5456 return new PostReadResponseControl(new ReadOnlyEntry(trimmedEntry)); 5457 } 5458 5459 5460 5461 /** 5462 * Finds the smart referral entry which is hierarchically nearest the entry 5463 * with the given DN. 5464 * 5465 * @param dn The DN for which to find the hierarchically nearest smart 5466 * referral entry. 5467 * 5468 * @return The hierarchically nearest smart referral entry for the provided 5469 * DN, or {@code null} if there are no smart referral entries with 5470 * the provided DN or any of its ancestors. 5471 */ 5472 private Entry findNearestReferral(final DN dn) 5473 { 5474 DN d = dn; 5475 while (true) 5476 { 5477 final Entry e = entryMap.get(d); 5478 if (e == null) 5479 { 5480 d = d.getParent(); 5481 if (d == null) 5482 { 5483 return null; 5484 } 5485 } 5486 else if (e.hasObjectClass("referral")) 5487 { 5488 return e; 5489 } 5490 else 5491 { 5492 return null; 5493 } 5494 } 5495 } 5496 5497 5498 5499 /** 5500 * Retrieves the referral URLs that should be used for the provided target DN 5501 * based on the given referral entry. 5502 * 5503 * @param targetDN The target DN from the associated operation. 5504 * @param referralEntry The entry containing the smart referral. 5505 * 5506 * @return The referral URLs that should be returned. 5507 */ 5508 private static List<String> getReferralURLs(final DN targetDN, 5509 final Entry referralEntry) 5510 { 5511 final String[] refs = referralEntry.getAttributeValues("ref"); 5512 if (refs == null) 5513 { 5514 return null; 5515 } 5516 5517 final RDN[] retainRDNs; 5518 try 5519 { 5520 // If the target DN equals the referral entry DN, or if it's not 5521 // subordinate to the referral entry, then the URLs should be returned 5522 // as-is. 5523 final DN parsedEntryDN = referralEntry.getParsedDN(); 5524 if (targetDN.equals(parsedEntryDN) || 5525 (! targetDN.isDescendantOf(parsedEntryDN, true))) 5526 { 5527 return Arrays.asList(refs); 5528 } 5529 5530 final RDN[] targetRDNs = targetDN.getRDNs(); 5531 final RDN[] refEntryRDNs = referralEntry.getParsedDN().getRDNs(); 5532 retainRDNs = new RDN[targetRDNs.length - refEntryRDNs.length]; 5533 System.arraycopy(targetRDNs, 0, retainRDNs, 0, retainRDNs.length); 5534 } 5535 catch (final LDAPException le) 5536 { 5537 Debug.debugException(le); 5538 return Arrays.asList(refs); 5539 } 5540 5541 final List<String> refList = new ArrayList<String>(refs.length); 5542 for (final String ref : refs) 5543 { 5544 try 5545 { 5546 final LDAPURL url = new LDAPURL(ref); 5547 final RDN[] refRDNs = url.getBaseDN().getRDNs(); 5548 final RDN[] newRefRDNs = new RDN[retainRDNs.length + refRDNs.length]; 5549 System.arraycopy(retainRDNs, 0, newRefRDNs, 0, retainRDNs.length); 5550 System.arraycopy(refRDNs, 0, newRefRDNs, retainRDNs.length, 5551 refRDNs.length); 5552 final DN newBaseDN = new DN(newRefRDNs); 5553 5554 final LDAPURL newURL = new LDAPURL(url.getScheme(), url.getHost(), 5555 url.getPort(), newBaseDN, null, null, null); 5556 refList.add(newURL.toString()); 5557 } 5558 catch (final LDAPException le) 5559 { 5560 Debug.debugException(le); 5561 refList.add(ref); 5562 } 5563 } 5564 5565 return refList; 5566 } 5567 5568 5569 5570 /** 5571 * Indicates whether the specified entry exists in the server. 5572 * 5573 * @param dn The DN of the entry for which to make the determination. 5574 * 5575 * @return {@code true} if the entry exists, or {@code false} if not. 5576 * 5577 * @throws LDAPException If a problem is encountered while trying to 5578 * communicate with the directory server. 5579 */ 5580 public boolean entryExists(final String dn) 5581 throws LDAPException 5582 { 5583 return (getEntry(dn) != null); 5584 } 5585 5586 5587 5588 /** 5589 * Indicates whether the specified entry exists in the server and matches the 5590 * given filter. 5591 * 5592 * @param dn The DN of the entry for which to make the determination. 5593 * @param filter The filter the entry is expected to match. 5594 * 5595 * @return {@code true} if the entry exists and matches the specified filter, 5596 * or {@code false} if not. 5597 * 5598 * @throws LDAPException If a problem is encountered while trying to 5599 * communicate with the directory server. 5600 */ 5601 public boolean entryExists(final String dn, final String filter) 5602 throws LDAPException 5603 { 5604 synchronized (entryMap) 5605 { 5606 final Entry e = getEntry(dn); 5607 if (e == null) 5608 { 5609 return false; 5610 } 5611 5612 final Filter f = Filter.create(filter); 5613 try 5614 { 5615 return f.matchesEntry(e, schemaRef.get()); 5616 } 5617 catch (final LDAPException le) 5618 { 5619 Debug.debugException(le); 5620 return false; 5621 } 5622 } 5623 } 5624 5625 5626 5627 /** 5628 * Indicates whether the specified entry exists in the server. This will 5629 * return {@code true} only if the target entry exists and contains all values 5630 * for all attributes of the provided entry. The entry will be allowed to 5631 * have attribute values not included in the provided entry. 5632 * 5633 * @param entry The entry to compare against the directory server. 5634 * 5635 * @return {@code true} if the entry exists in the server and is a superset 5636 * of the provided entry, or {@code false} if not. 5637 * 5638 * @throws LDAPException If a problem is encountered while trying to 5639 * communicate with the directory server. 5640 */ 5641 public boolean entryExists(final Entry entry) 5642 throws LDAPException 5643 { 5644 synchronized (entryMap) 5645 { 5646 final Entry e = getEntry(entry.getDN()); 5647 if (e == null) 5648 { 5649 return false; 5650 } 5651 5652 for (final Attribute a : entry.getAttributes()) 5653 { 5654 for (final byte[] value : a.getValueByteArrays()) 5655 { 5656 if (! e.hasAttributeValue(a.getName(), value)) 5657 { 5658 return false; 5659 } 5660 } 5661 } 5662 5663 return true; 5664 } 5665 } 5666 5667 5668 5669 /** 5670 * Ensures that an entry with the provided DN exists in the directory. 5671 * 5672 * @param dn The DN of the entry for which to make the determination. 5673 * 5674 * @throws LDAPException If a problem is encountered while trying to 5675 * communicate with the directory server. 5676 * 5677 * @throws AssertionError If the target entry does not exist. 5678 */ 5679 public void assertEntryExists(final String dn) 5680 throws LDAPException, AssertionError 5681 { 5682 final Entry e = getEntry(dn); 5683 if (e == null) 5684 { 5685 throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn)); 5686 } 5687 } 5688 5689 5690 5691 /** 5692 * Ensures that an entry with the provided DN exists in the directory. 5693 * 5694 * @param dn The DN of the entry for which to make the determination. 5695 * @param filter A filter that the target entry must match. 5696 * 5697 * @throws LDAPException If a problem is encountered while trying to 5698 * communicate with the directory server. 5699 * 5700 * @throws AssertionError If the target entry does not exist or does not 5701 * match the provided filter. 5702 */ 5703 public void assertEntryExists(final String dn, final String filter) 5704 throws LDAPException, AssertionError 5705 { 5706 synchronized (entryMap) 5707 { 5708 final Entry e = getEntry(dn); 5709 if (e == null) 5710 { 5711 throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn)); 5712 } 5713 5714 final Filter f = Filter.create(filter); 5715 try 5716 { 5717 if (! f.matchesEntry(e, schemaRef.get())) 5718 { 5719 throw new AssertionError( 5720 ERR_MEM_HANDLER_TEST_ENTRY_DOES_NOT_MATCH_FILTER.get(dn, 5721 filter)); 5722 } 5723 } 5724 catch (final LDAPException le) 5725 { 5726 Debug.debugException(le); 5727 throw new AssertionError( 5728 ERR_MEM_HANDLER_TEST_ENTRY_DOES_NOT_MATCH_FILTER.get(dn, filter)); 5729 } 5730 } 5731 } 5732 5733 5734 5735 /** 5736 * Ensures that an entry exists in the directory with the same DN and all 5737 * attribute values contained in the provided entry. The server entry may 5738 * contain additional attributes and/or attribute values not included in the 5739 * provided entry. 5740 * 5741 * @param entry The entry expected to be present in the directory server. 5742 * 5743 * @throws LDAPException If a problem is encountered while trying to 5744 * communicate with the directory server. 5745 * 5746 * @throws AssertionError If the target entry does not exist or does not 5747 * match the provided filter. 5748 */ 5749 public void assertEntryExists(final Entry entry) 5750 throws LDAPException, AssertionError 5751 { 5752 synchronized (entryMap) 5753 { 5754 final Entry e = getEntry(entry.getDN()); 5755 if (e == null) 5756 { 5757 throw new AssertionError( 5758 ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(entry.getDN())); 5759 } 5760 5761 5762 final Collection<Attribute> attrs = entry.getAttributes(); 5763 final List<String> messages = new ArrayList<String>(attrs.size()); 5764 5765 final Schema schema = schemaRef.get(); 5766 for (final Attribute a : entry.getAttributes()) 5767 { 5768 final Filter presFilter = Filter.createPresenceFilter(a.getName()); 5769 if (! presFilter.matchesEntry(e, schema)) 5770 { 5771 messages.add(ERR_MEM_HANDLER_TEST_ATTR_MISSING.get(entry.getDN(), 5772 a.getName())); 5773 continue; 5774 } 5775 5776 for (final byte[] value : a.getValueByteArrays()) 5777 { 5778 final Filter eqFilter = Filter.createEqualityFilter(a.getName(), 5779 value); 5780 if (! eqFilter.matchesEntry(e, schema)) 5781 { 5782 messages.add(ERR_MEM_HANDLER_TEST_VALUE_MISSING.get(entry.getDN(), 5783 a.getName(), StaticUtils.toUTF8String(value))); 5784 } 5785 } 5786 } 5787 5788 if (! messages.isEmpty()) 5789 { 5790 throw new AssertionError(StaticUtils.concatenateStrings(messages)); 5791 } 5792 } 5793 } 5794 5795 5796 5797 /** 5798 * Retrieves a list containing the DNs of the entries which are missing from 5799 * the directory server. 5800 * 5801 * @param dns The DNs of the entries to try to find in the server. 5802 * 5803 * @return A list containing all of the provided DNs that were not found in 5804 * the server, or an empty list if all entries were found. 5805 * 5806 * @throws LDAPException If a problem is encountered while trying to 5807 * communicate with the directory server. 5808 */ 5809 public List<String> getMissingEntryDNs(final Collection<String> dns) 5810 throws LDAPException 5811 { 5812 synchronized (entryMap) 5813 { 5814 final List<String> missingDNs = new ArrayList<String>(dns.size()); 5815 for (final String dn : dns) 5816 { 5817 final Entry e = getEntry(dn); 5818 if (e == null) 5819 { 5820 missingDNs.add(dn); 5821 } 5822 } 5823 5824 return missingDNs; 5825 } 5826 } 5827 5828 5829 5830 /** 5831 * Ensures that all of the entries with the provided DNs exist in the 5832 * directory. 5833 * 5834 * @param dns The DNs of the entries for which to make the determination. 5835 * 5836 * @throws LDAPException If a problem is encountered while trying to 5837 * communicate with the directory server. 5838 * 5839 * @throws AssertionError If any of the target entries does not exist. 5840 */ 5841 public void assertEntriesExist(final Collection<String> dns) 5842 throws LDAPException, AssertionError 5843 { 5844 synchronized (entryMap) 5845 { 5846 final List<String> missingDNs = getMissingEntryDNs(dns); 5847 if (missingDNs.isEmpty()) 5848 { 5849 return; 5850 } 5851 5852 final List<String> messages = new ArrayList<String>(missingDNs.size()); 5853 for (final String dn : missingDNs) 5854 { 5855 messages.add(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn)); 5856 } 5857 5858 throw new AssertionError(StaticUtils.concatenateStrings(messages)); 5859 } 5860 } 5861 5862 5863 5864 /** 5865 * Retrieves a list containing all of the named attributes which do not exist 5866 * in the target entry. 5867 * 5868 * @param dn The DN of the entry to examine. 5869 * @param attributeNames The names of the attributes expected to be present 5870 * in the target entry. 5871 * 5872 * @return A list containing the names of the attributes which were not 5873 * present in the target entry, an empty list if all specified 5874 * attributes were found in the entry, or {@code null} if the target 5875 * entry does not exist. 5876 * 5877 * @throws LDAPException If a problem is encountered while trying to 5878 * communicate with the directory server. 5879 */ 5880 public List<String> getMissingAttributeNames(final String dn, 5881 final Collection<String> attributeNames) 5882 throws LDAPException 5883 { 5884 synchronized (entryMap) 5885 { 5886 final Entry e = getEntry(dn); 5887 if (e == null) 5888 { 5889 return null; 5890 } 5891 5892 final Schema schema = schemaRef.get(); 5893 final List<String> missingAttrs = 5894 new ArrayList<String>(attributeNames.size()); 5895 for (final String attr : attributeNames) 5896 { 5897 final Filter f = Filter.createPresenceFilter(attr); 5898 if (! f.matchesEntry(e, schema)) 5899 { 5900 missingAttrs.add(attr); 5901 } 5902 } 5903 5904 return missingAttrs; 5905 } 5906 } 5907 5908 5909 5910 /** 5911 * Ensures that the specified entry exists in the directory with all of the 5912 * specified attributes. 5913 * 5914 * @param dn The DN of the entry to examine. 5915 * @param attributeNames The names of the attributes that are expected to be 5916 * present in the provided entry. 5917 * 5918 * @throws LDAPException If a problem is encountered while trying to 5919 * communicate with the directory server. 5920 * 5921 * @throws AssertionError If the target entry does not exist or does not 5922 * contain all of the specified attributes. 5923 */ 5924 public void assertAttributeExists(final String dn, 5925 final Collection<String> attributeNames) 5926 throws LDAPException, AssertionError 5927 { 5928 synchronized (entryMap) 5929 { 5930 final List<String> missingAttrs = 5931 getMissingAttributeNames(dn, attributeNames); 5932 if (missingAttrs == null) 5933 { 5934 throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn)); 5935 } 5936 else if (missingAttrs.isEmpty()) 5937 { 5938 return; 5939 } 5940 5941 final List<String> messages = new ArrayList<String>(missingAttrs.size()); 5942 for (final String attr : missingAttrs) 5943 { 5944 messages.add(ERR_MEM_HANDLER_TEST_ATTR_MISSING.get(dn, attr)); 5945 } 5946 5947 throw new AssertionError(StaticUtils.concatenateStrings(messages)); 5948 } 5949 } 5950 5951 5952 5953 /** 5954 * Retrieves a list of all provided attribute values which are missing from 5955 * the specified entry. The target attribute may or may not contain 5956 * additional values. 5957 * 5958 * @param dn The DN of the entry to examine. 5959 * @param attributeName The attribute expected to be present in the target 5960 * entry with the given values. 5961 * @param attributeValues The values expected to be present in the target 5962 * entry. 5963 * 5964 * @return A list containing all of the provided values which were not found 5965 * in the entry, an empty list if all provided attribute values were 5966 * found, or {@code null} if the target entry does not exist. 5967 * 5968 * @throws LDAPException If a problem is encountered while trying to 5969 * communicate with the directory server. 5970 */ 5971 public List<String> getMissingAttributeValues(final String dn, 5972 final String attributeName, 5973 final Collection<String> attributeValues) 5974 throws LDAPException 5975 { 5976 synchronized (entryMap) 5977 { 5978 final Entry e = getEntry(dn); 5979 if (e == null) 5980 { 5981 return null; 5982 } 5983 5984 final Schema schema = schemaRef.get(); 5985 final List<String> missingValues = 5986 new ArrayList<String>(attributeValues.size()); 5987 for (final String value : attributeValues) 5988 { 5989 final Filter f = Filter.createEqualityFilter(attributeName, value); 5990 if (! f.matchesEntry(e, schema)) 5991 { 5992 missingValues.add(value); 5993 } 5994 } 5995 5996 return missingValues; 5997 } 5998 } 5999 6000 6001 6002 /** 6003 * Ensures that the specified entry exists in the directory with all of the 6004 * specified values for the given attribute. The attribute may or may not 6005 * contain additional values. 6006 * 6007 * @param dn The DN of the entry to examine. 6008 * @param attributeName The name of the attribute to examine. 6009 * @param attributeValues The set of values which must exist for the given 6010 * attribute. 6011 * 6012 * @throws LDAPException If a problem is encountered while trying to 6013 * communicate with the directory server. 6014 * 6015 * @throws AssertionError If the target entry does not exist, does not 6016 * contain the specified attribute, or that attribute 6017 * does not have all of the specified values. 6018 */ 6019 public void assertValueExists(final String dn, 6020 final String attributeName, 6021 final Collection<String> attributeValues) 6022 throws LDAPException, AssertionError 6023 { 6024 synchronized (entryMap) 6025 { 6026 final List<String> missingValues = 6027 getMissingAttributeValues(dn, attributeName, attributeValues); 6028 if (missingValues == null) 6029 { 6030 throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn)); 6031 } 6032 else if (missingValues.isEmpty()) 6033 { 6034 return; 6035 } 6036 6037 // See if the attribute exists at all in the entry. 6038 final Entry e = getEntry(dn); 6039 final Filter f = Filter.createPresenceFilter(attributeName); 6040 if (! f.matchesEntry(e, schemaRef.get())) 6041 { 6042 throw new AssertionError( 6043 ERR_MEM_HANDLER_TEST_ATTR_MISSING.get(dn, attributeName)); 6044 } 6045 6046 final List<String> messages = new ArrayList<String>(missingValues.size()); 6047 for (final String value : missingValues) 6048 { 6049 messages.add(ERR_MEM_HANDLER_TEST_VALUE_MISSING.get(dn, attributeName, 6050 value)); 6051 } 6052 6053 throw new AssertionError(StaticUtils.concatenateStrings(messages)); 6054 } 6055 } 6056 6057 6058 6059 /** 6060 * Ensures that the specified entry does not exist in the directory. 6061 * 6062 * @param dn The DN of the entry expected to be missing. 6063 * 6064 * @throws LDAPException If a problem is encountered while trying to 6065 * communicate with the directory server. 6066 * 6067 * @throws AssertionError If the target entry is found in the server. 6068 */ 6069 public void assertEntryMissing(final String dn) 6070 throws LDAPException, AssertionError 6071 { 6072 final Entry e = getEntry(dn); 6073 if (e != null) 6074 { 6075 throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_EXISTS.get(dn)); 6076 } 6077 } 6078 6079 6080 6081 /** 6082 * Ensures that the specified entry exists in the directory but does not 6083 * contain any of the specified attributes. 6084 * 6085 * @param dn The DN of the entry expected to be present. 6086 * @param attributeNames The names of the attributes expected to be missing 6087 * from the entry. 6088 * 6089 * @throws LDAPException If a problem is encountered while trying to 6090 * communicate with the directory server. 6091 * 6092 * @throws AssertionError If the target entry is missing from the server, or 6093 * if it contains any of the target attributes. 6094 */ 6095 public void assertAttributeMissing(final String dn, 6096 final Collection<String> attributeNames) 6097 throws LDAPException, AssertionError 6098 { 6099 synchronized (entryMap) 6100 { 6101 final Entry e = getEntry(dn); 6102 if (e == null) 6103 { 6104 throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn)); 6105 } 6106 6107 final Schema schema = schemaRef.get(); 6108 final List<String> messages = 6109 new ArrayList<String>(attributeNames.size()); 6110 for (final String name : attributeNames) 6111 { 6112 final Filter f = Filter.createPresenceFilter(name); 6113 if (f.matchesEntry(e, schema)) 6114 { 6115 messages.add(ERR_MEM_HANDLER_TEST_ATTR_EXISTS.get(dn, name)); 6116 } 6117 } 6118 6119 if (! messages.isEmpty()) 6120 { 6121 throw new AssertionError(StaticUtils.concatenateStrings(messages)); 6122 } 6123 } 6124 } 6125 6126 6127 6128 /** 6129 * Ensures that the specified entry exists in the directory but does not 6130 * contain any of the specified attribute values. 6131 * 6132 * @param dn The DN of the entry expected to be present. 6133 * @param attributeName The name of the attribute to examine. 6134 * @param attributeValues The values expected to be missing from the target 6135 * entry. 6136 * 6137 * @throws LDAPException If a problem is encountered while trying to 6138 * communicate with the directory server. 6139 * 6140 * @throws AssertionError If the target entry is missing from the server, or 6141 * if it contains any of the target attribute values. 6142 */ 6143 public void assertValueMissing(final String dn, 6144 final String attributeName, 6145 final Collection<String> attributeValues) 6146 throws LDAPException, AssertionError 6147 { 6148 synchronized (entryMap) 6149 { 6150 final Entry e = getEntry(dn); 6151 if (e == null) 6152 { 6153 throw new AssertionError(ERR_MEM_HANDLER_TEST_ENTRY_MISSING.get(dn)); 6154 } 6155 6156 final Schema schema = schemaRef.get(); 6157 final List<String> messages = 6158 new ArrayList<String>(attributeValues.size()); 6159 for (final String value : attributeValues) 6160 { 6161 final Filter f = Filter.createEqualityFilter(attributeName, value); 6162 if (f.matchesEntry(e, schema)) 6163 { 6164 messages.add(ERR_MEM_HANDLER_TEST_VALUE_EXISTS.get(dn, attributeName, 6165 value)); 6166 } 6167 } 6168 6169 if (! messages.isEmpty()) 6170 { 6171 throw new AssertionError(StaticUtils.concatenateStrings(messages)); 6172 } 6173 } 6174 } 6175}