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