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