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