001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2006-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2011-2016 ForgeRock AS. 016 */ 017package org.opends.server.backends; 018 019import static org.forgerock.opendj.ldap.schema.CoreSchema.*; 020import static org.forgerock.util.Reject.*; 021import static org.opends.messages.BackendMessages.*; 022import static org.opends.messages.ConfigMessages.*; 023import static org.opends.server.config.ConfigConstants.*; 024import static org.opends.server.util.CollectionUtils.*; 025import static org.opends.server.util.ServerConstants.*; 026import static org.opends.server.util.StaticUtils.*; 027 028import java.util.ArrayList; 029import java.util.Arrays; 030import java.util.Collection; 031import java.util.Collections; 032import java.util.HashMap; 033import java.util.List; 034import java.util.Map; 035import java.util.Set; 036import java.util.TreeSet; 037import java.util.concurrent.ConcurrentHashMap; 038 039import javax.net.ssl.SSLContext; 040import javax.net.ssl.SSLParameters; 041 042import org.forgerock.i18n.LocalizableMessage; 043import org.forgerock.i18n.slf4j.LocalizedLogger; 044import org.forgerock.opendj.config.server.ConfigChangeResult; 045import org.forgerock.opendj.config.server.ConfigException; 046import org.forgerock.opendj.config.server.ConfigurationChangeListener; 047import org.forgerock.opendj.ldap.ConditionResult; 048import org.forgerock.opendj.ldap.DN; 049import org.forgerock.opendj.ldap.ResultCode; 050import org.forgerock.opendj.ldap.schema.AttributeType; 051import org.forgerock.opendj.ldap.schema.ObjectClass; 052import org.forgerock.opendj.server.config.server.RootDSEBackendCfg; 053import org.forgerock.util.Reject; 054import org.forgerock.util.Utils; 055import org.opends.server.api.Backend; 056import org.opends.server.api.ClientConnection; 057import org.opends.server.core.AddOperation; 058import org.opends.server.core.DeleteOperation; 059import org.opends.server.core.DirectoryServer; 060import org.opends.server.core.ModifyDNOperation; 061import org.opends.server.core.ModifyOperation; 062import org.opends.server.core.SearchOperation; 063import org.opends.server.core.ServerContext; 064import org.opends.server.types.Attribute; 065import org.opends.server.types.AttributeBuilder; 066import org.opends.server.types.Attributes; 067import org.opends.server.types.BackupConfig; 068import org.opends.server.types.BackupDirectory; 069import org.opends.server.types.CanceledOperationException; 070import org.opends.server.types.DirectoryException; 071import org.opends.server.types.Entry; 072import org.opends.server.types.IndexType; 073import org.opends.server.types.InitializationException; 074import org.opends.server.types.LDIFExportConfig; 075import org.opends.server.types.LDIFImportConfig; 076import org.opends.server.types.LDIFImportResult; 077import org.opends.server.types.RestoreConfig; 078import org.opends.server.types.SearchFilter; 079import org.opends.server.util.BuildVersion; 080import org.opends.server.util.LDIFWriter; 081 082/** 083 * This class defines a backend to hold the Directory Server root DSE. It is a 084 * kind of meta-backend in that it will dynamically generate the root DSE entry 085 * (although there will be some caching) for base-level searches, and will 086 * simply redirect to other backends for operations in other scopes. 087 * <BR><BR> 088 * This should not be treated like a regular backend when it comes to 089 * initializing the server configuration. It should only be initialized after 090 * all other backends are configured. As such, it should have a special entry 091 * in the configuration rather than being placed under the cn=Backends branch 092 * with the other backends. 093 */ 094public class RootDSEBackend 095 extends Backend<RootDSEBackendCfg> 096 implements ConfigurationChangeListener<RootDSEBackendCfg> 097{ 098 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 099 100 /** 101 * The set of standard "static" attributes that we will always include in the 102 * root DSE entry and won't change while the server is running. 103 */ 104 private List<Attribute> staticDSEAttributes; 105 /** The set of user-defined attributes that will be included in the root DSE entry. */ 106 private List<Attribute> userDefinedAttributes; 107 /** 108 * Indicates whether the attributes of the root DSE should always be treated 109 * as user attributes even if they are defined as operational in the schema. 110 */ 111 private boolean showAllAttributes; 112 /** 113 * Indicates whether sub-suffixes should also be included in the list of public naming contexts. 114 */ 115 private boolean showSubordinatesNamingContexts; 116 117 /** The set of objectclasses that will be used in the root DSE entry. */ 118 private Map<ObjectClass, String> dseObjectClasses; 119 120 /** The current configuration state. */ 121 private RootDSEBackendCfg currentConfig; 122 /** The DN of the configuration entry for this backend. */ 123 private DN configEntryDN; 124 125 /** The DN for the root DSE. */ 126 private DN rootDSEDN; 127 /** The set of base DNs for this backend. */ 128 private Set<DN> baseDNs; 129 /** 130 * The set of subordinate base DNs and their associated backends that will be 131 * used for non-base searches. 132 */ 133 private ConcurrentHashMap<DN, Backend<?>> subordinateBaseDNs; 134 135 /** 136 * Creates a new backend with the provided information. All backend 137 * implementations must implement a default constructor that use 138 * <CODE>super()</CODE> to invoke this constructor. 139 */ 140 public RootDSEBackend() 141 { 142 super(); 143 144 // Perform all initialization in initializeBackend. 145 } 146 147 @Override 148 public void configureBackend(RootDSEBackendCfg config, ServerContext serverContext) throws ConfigException 149 { 150 Reject.ifNull(config); 151 currentConfig = config; 152 configEntryDN = config.dn(); 153 } 154 155 @Override 156 public void openBackend() throws ConfigException, InitializationException 157 { 158 Entry configEntry = DirectoryServer.getConfigEntry(configEntryDN); 159 160 // Make sure that a configuration entry was provided. If not, then we will 161 // not be able to complete initialization. 162 if (configEntry == null) 163 { 164 LocalizableMessage message = ERR_ROOTDSE_CONFIG_ENTRY_NULL.get(); 165 throw new ConfigException(message); 166 } 167 168 userDefinedAttributes = new ArrayList<>(); 169 addAllUserDefinedAttrs(userDefinedAttributes, configEntry); 170 171 // Create the set of base DNs that we will handle. In this case, it's just 172 // the root DSE. 173 rootDSEDN = DN.rootDN(); 174 baseDNs = Collections.singleton(rootDSEDN); 175 176 // Create the set of subordinate base DNs. If this is specified in the 177 // configuration, then use that set. Otherwise, use the set of non-private 178 // backends defined in the server. 179 try 180 { 181 Set<DN> subDNs = currentConfig.getSubordinateBaseDN(); 182 if (subDNs.isEmpty()) 183 { 184 // This is fine -- we'll just use the set of user-defined suffixes. 185 subordinateBaseDNs = null; 186 } 187 else 188 { 189 subordinateBaseDNs = new ConcurrentHashMap<>(); 190 for (DN baseDN : subDNs) 191 { 192 Backend<?> backend = DirectoryServer.getBackend(baseDN); 193 if (backend != null) 194 { 195 subordinateBaseDNs.put(baseDN, backend); 196 } 197 else 198 { 199 logger.warn(WARN_ROOTDSE_NO_BACKEND_FOR_SUBORDINATE_BASE, baseDN); 200 } 201 } 202 } 203 } 204 catch (Exception e) 205 { 206 logger.traceException(e); 207 208 LocalizableMessage message = WARN_ROOTDSE_SUBORDINATE_BASE_EXCEPTION.get( 209 stackTraceToSingleLineString(e)); 210 throw new InitializationException(message, e); 211 } 212 213 // Determine whether all root DSE attributes should be treated as user 214 // attributes. 215 showAllAttributes = currentConfig.isShowAllAttributes(); 216 showSubordinatesNamingContexts = currentConfig.isShowSubordinateNamingContexts(); 217 218 // Construct the set of "static" attributes that will always be present in 219 // the root DSE. 220 staticDSEAttributes = new ArrayList<>(); 221 staticDSEAttributes.add(Attributes.create(ATTR_VENDOR_NAME, SERVER_VENDOR_NAME)); 222 staticDSEAttributes.add(Attributes.create(ATTR_VENDOR_VERSION, 223 DirectoryServer.getVersionString())); 224 staticDSEAttributes.add(Attributes.create("fullVendorVersion", 225 BuildVersion.binaryVersion().toString())); 226 227 // Construct the set of objectclasses to include in the root DSE entry. 228 dseObjectClasses = new HashMap<>(2); 229 dseObjectClasses.put(getTopObjectClass(), OC_TOP); 230 dseObjectClasses.put(DirectoryServer.getSchema().getObjectClass(OC_ROOT_DSE), OC_ROOT_DSE); 231 232 // Set the backend ID for this backend. The identifier needs to be 233 // specific enough to avoid conflict with user backend identifiers. 234 setBackendID("__root.dse__"); 235 236 // Register as a change listener. 237 currentConfig.addChangeListener(this); 238 } 239 240 /** 241 * Get the set of user-defined attributes for the configuration entry. Any 242 * attributes that we do not recognize will be included directly in the root DSE. 243 */ 244 private void addAllUserDefinedAttrs(List<Attribute> userDefinedAttrs, Entry configEntry) 245 { 246 for (List<Attribute> attrs : configEntry.getUserAttributes().values()) 247 { 248 for (Attribute a : attrs) 249 { 250 if (!isDSEConfigAttribute(a)) 251 { 252 userDefinedAttrs.add(a); 253 } 254 } 255 } 256 for (List<Attribute> attrs : configEntry.getOperationalAttributes().values()) 257 { 258 for (Attribute a : attrs) 259 { 260 if (!isDSEConfigAttribute(a)) 261 { 262 userDefinedAttrs.add(a); 263 } 264 } 265 } 266 } 267 268 @Override 269 public void closeBackend() 270 { 271 currentConfig.removeChangeListener(this); 272 } 273 274 /** 275 * Indicates whether the provided attribute is one that is used in the 276 * configuration of this backend. 277 * 278 * @param attribute The attribute for which to make the determination. 279 * 280 * @return <CODE>true</CODE> if the provided attribute is one that is used in 281 * the configuration of this backend, <CODE>false</CODE> if not. 282 */ 283 private boolean isDSEConfigAttribute(Attribute attribute) 284 { 285 AttributeType attrType = attribute.getAttributeDescription().getAttributeType(); 286 return attrType.hasName(ATTR_ROOT_DSE_SUBORDINATE_BASE_DN) 287 || attrType.hasName(ATTR_ROOTDSE_SHOW_ALL_ATTRIBUTES) 288 || attrType.hasName(ATTR_COMMON_NAME); 289 } 290 291 @Override 292 public Set<DN> getBaseDNs() 293 { 294 return baseDNs; 295 } 296 297 @Override 298 public synchronized long getEntryCount() 299 { 300 // There is always just a single entry in this backend. 301 return 1; 302 } 303 304 @Override 305 public boolean isIndexed(AttributeType attributeType, IndexType indexType) 306 { 307 // All searches in this backend will always be considered indexed. 308 return true; 309 } 310 311 @Override 312 public ConditionResult hasSubordinates(DN entryDN) throws DirectoryException 313 { 314 final long ret = getNumberOfChildren(entryDN); 315 if(ret < 0) 316 { 317 return ConditionResult.UNDEFINED; 318 } 319 return ConditionResult.valueOf(ret != 0); 320 } 321 322 @Override 323 public long getNumberOfEntriesInBaseDN(DN baseDN) throws DirectoryException 324 { 325 checkNotNull(baseDN, "baseDN must not be null"); 326 if (!baseDN.isRootDN()) 327 { 328 return -1; 329 } 330 331 long count = 1; 332 for (Map.Entry<DN, Backend<?>> entry : getSubordinateBaseDNs().entrySet()) 333 { 334 DN subBase = entry.getKey(); 335 Backend<?> b = entry.getValue(); 336 Entry subBaseEntry = b.getEntry(subBase); 337 if (subBaseEntry != null) 338 { 339 count++; 340 count += b.getNumberOfEntriesInBaseDN(subBase); 341 } 342 } 343 344 return count; 345 } 346 347 @Override 348 public long getNumberOfChildren(DN parentDN) throws DirectoryException 349 { 350 checkNotNull(parentDN, "parentDN must not be null"); 351 if (!parentDN.isRootDN()) 352 { 353 return -1; 354 } 355 356 long count = 0; 357 358 for (Map.Entry<DN, Backend<?>> entry : getSubordinateBaseDNs().entrySet()) 359 { 360 DN subBase = entry.getKey(); 361 Entry subBaseEntry = entry.getValue().getEntry(subBase); 362 if (subBaseEntry != null) 363 { 364 count ++; 365 } 366 } 367 368 return count; 369 } 370 371 @Override 372 public Entry getEntry(DN entryDN) throws DirectoryException 373 { 374 // If the requested entry was the root DSE, then create and return it. 375 if (entryDN == null || entryDN.isRootDN()) 376 { 377 return getRootDSE(); 378 } 379 380 // This method should never be used to get anything other than the root DSE. 381 // If we got here, then that appears to be the case, so log a message. 382 logger.warn(WARN_ROOTDSE_GET_ENTRY_NONROOT, entryDN); 383 384 // Go ahead and check the subordinate backends to see if we can find the 385 // entry there. Note that in order to avoid potential loop conditions, this 386 // will only work if the set of subordinate bases has been explicitly 387 // specified. 388 if (subordinateBaseDNs != null) 389 { 390 for (Backend<?> b : subordinateBaseDNs.values()) 391 { 392 if (b.handlesEntry(entryDN)) 393 { 394 return b.getEntry(entryDN); 395 } 396 } 397 } 398 399 // If we've gotten here, then we couldn't find the entry so return null. 400 return null; 401 } 402 403 /** 404 * Retrieves the root DSE entry for the Directory Server. 405 * 406 * @return The root DSE entry for the Directory Server. 407 */ 408 public Entry getRootDSE() 409 { 410 return getRootDSE(null); 411 } 412 413 /** 414 * Retrieves the root DSE entry for the Directory Server. 415 * 416 * @param connection 417 * The client connection, or {@code null} if there is no associated 418 * client connection. 419 * @return The root DSE entry for the Directory Server. 420 */ 421 private Entry getRootDSE(ClientConnection connection) 422 { 423 Map<AttributeType, List<Attribute>> dseUserAttrs = new HashMap<>(); 424 Map<AttributeType, List<Attribute>> dseOperationalAttrs = new HashMap<>(); 425 426 Map<DN, Backend<?>> publicNamingContexts = showSubordinatesNamingContexts ? 427 DirectoryServer.getAllPublicNamingContexts() : 428 DirectoryServer.getPublicNamingContexts(); 429 Attribute publicNamingContextAttr = createAttribute(ATTR_NAMING_CONTEXTS, publicNamingContexts.keySet()); 430 addAttribute(publicNamingContextAttr, dseUserAttrs, dseOperationalAttrs); 431 432 // Add the "ds-private-naming-contexts" attribute. 433 Attribute privateNamingContextAttr = createAttribute( 434 ATTR_PRIVATE_NAMING_CONTEXTS, DirectoryServer.getPrivateNamingContexts().keySet()); 435 addAttribute(privateNamingContextAttr, dseUserAttrs, dseOperationalAttrs); 436 437 // Add the "supportedControl" attribute. 438 Attribute supportedControlAttr = createAttribute(ATTR_SUPPORTED_CONTROL, 439 DirectoryServer.getSupportedControls()); 440 addAttribute(supportedControlAttr, dseUserAttrs, dseOperationalAttrs); 441 442 // Add the "supportedExtension" attribute. 443 Attribute supportedExtensionAttr = createAttribute( 444 ATTR_SUPPORTED_EXTENSION, DirectoryServer.getSupportedExtensions()); 445 addAttribute(supportedExtensionAttr, dseUserAttrs, dseOperationalAttrs); 446 447 // Add the "supportedFeature" attribute. 448 Attribute supportedFeatureAttr = createAttribute(ATTR_SUPPORTED_FEATURE, 449 DirectoryServer.getSupportedFeatures()); 450 addAttribute(supportedFeatureAttr, dseUserAttrs, dseOperationalAttrs); 451 452 // Add the "supportedSASLMechanisms" attribute. 453 Attribute supportedSASLMechAttr = createAttribute( 454 ATTR_SUPPORTED_SASL_MECHANISMS, DirectoryServer.getSupportedSASLMechanisms()); 455 addAttribute(supportedSASLMechAttr, dseUserAttrs, dseOperationalAttrs); 456 457 // Add the "supportedLDAPVersions" attribute. 458 TreeSet<String> versionStrings = new TreeSet<>(); 459 for (Integer ldapVersion : DirectoryServer.getSupportedLDAPVersions()) 460 { 461 versionStrings.add(ldapVersion.toString()); 462 } 463 Attribute supportedLDAPVersionAttr = createAttribute( 464 ATTR_SUPPORTED_LDAP_VERSION, versionStrings); 465 addAttribute(supportedLDAPVersionAttr, dseUserAttrs, dseOperationalAttrs); 466 467 // Add the "supportedAuthPasswordSchemes" attribute. 468 Attribute supportedAuthPWSchemesAttr = createAttribute( 469 ATTR_SUPPORTED_AUTH_PW_SCHEMES, DirectoryServer.getAuthPasswordStorageSchemes().keySet()); 470 addAttribute(supportedAuthPWSchemesAttr, dseUserAttrs, dseOperationalAttrs); 471 472 // Obtain TLS protocol and cipher support. 473 Collection<String> supportedTlsProtocols; 474 Collection<String> supportedTlsCiphers; 475 if (connection != null) 476 { 477 // Only return the list of enabled protocols / ciphers for the connection 478 // handler to which the client is connected. 479 supportedTlsProtocols = connection.getConnectionHandler().getEnabledSSLProtocols(); 480 supportedTlsCiphers = connection.getConnectionHandler().getEnabledSSLCipherSuites(); 481 } 482 else 483 { 484 try 485 { 486 final SSLContext context = SSLContext.getDefault(); 487 final SSLParameters parameters = context.getSupportedSSLParameters(); 488 supportedTlsProtocols = Arrays.asList(parameters.getProtocols()); 489 supportedTlsCiphers = Arrays.asList(parameters.getCipherSuites()); 490 } 491 catch (Exception e) 492 { 493 // A default SSL context should always be available. 494 supportedTlsProtocols = Collections.emptyList(); 495 supportedTlsCiphers = Collections.emptyList(); 496 } 497 } 498 499 // Add the "supportedTLSProtocols" attribute. 500 Attribute supportedTLSProtocolsAttr = createAttribute( 501 ATTR_SUPPORTED_TLS_PROTOCOLS, supportedTlsProtocols); 502 addAttribute(supportedTLSProtocolsAttr, dseUserAttrs, dseOperationalAttrs); 503 504 // Add the "supportedTLSCiphers" attribute. 505 Attribute supportedTLSCiphersAttr = createAttribute( 506 ATTR_SUPPORTED_TLS_CIPHERS, supportedTlsCiphers); 507 addAttribute(supportedTLSCiphersAttr, dseUserAttrs, dseOperationalAttrs); 508 509 addAll(staticDSEAttributes, dseUserAttrs, dseOperationalAttrs); 510 addAll(userDefinedAttributes, dseUserAttrs, dseOperationalAttrs); 511 512 // Construct and return the entry. 513 Entry e = new Entry(rootDSEDN, dseObjectClasses, dseUserAttrs, 514 dseOperationalAttrs); 515 e.processVirtualAttributes(); 516 return e; 517 } 518 519 private void addAll(Collection<Attribute> attributes, 520 Map<AttributeType, List<Attribute>> userAttrs, Map<AttributeType, List<Attribute>> operationalAttrs) 521 { 522 for (Attribute a : attributes) 523 { 524 AttributeType type = a.getAttributeDescription().getAttributeType(); 525 526 final Map<AttributeType, List<Attribute>> attrsMap = type.isOperational() && !showAllAttributes 527 ? operationalAttrs 528 : userAttrs; 529 List<Attribute> attrs = attrsMap.get(type); 530 if (attrs == null) 531 { 532 attrs = new ArrayList<>(); 533 attrsMap.put(type, attrs); 534 } 535 attrs.add(a); 536 } 537 } 538 539 private void addAttribute(Attribute attribute, 540 Map<AttributeType, List<Attribute>> userAttrs, 541 Map<AttributeType, List<Attribute>> operationalAttrs) 542 { 543 if (!attribute.isEmpty()) 544 { 545 List<Attribute> attrs = newArrayList(attribute); 546 final AttributeType attrType = attribute.getAttributeDescription().getAttributeType(); 547 if (showAllAttributes || !attrType.isOperational()) 548 { 549 userAttrs.put(attrType, attrs); 550 } 551 else 552 { 553 operationalAttrs.put(attrType, attrs); 554 } 555 } 556 } 557 558 /** 559 * Creates an attribute for the root DSE with the following 560 * criteria. 561 * 562 * @param name 563 * The name for the attribute. 564 * @param values 565 * The set of values to use for the attribute. 566 * @return The constructed attribute. 567 */ 568 private Attribute createAttribute(String name, Collection<? extends Object> values) 569 { 570 AttributeBuilder builder = new AttributeBuilder(name); 571 builder.addAllStrings(values); 572 return builder.toAttribute(); 573 } 574 575 @Override 576 public boolean entryExists(DN entryDN) throws DirectoryException 577 { 578 // If the specified DN was the null DN, then it exists. 579 if (entryDN.isRootDN()) 580 { 581 return true; 582 } 583 584 // If it was not the null DN, then iterate through the associated 585 // subordinate backends to make the determination. 586 for (Map.Entry<DN, Backend<?>> entry : getSubordinateBaseDNs().entrySet()) 587 { 588 DN baseDN = entry.getKey(); 589 if (entryDN.isSubordinateOrEqualTo(baseDN)) 590 { 591 Backend<?> b = entry.getValue(); 592 if (b.entryExists(entryDN)) 593 { 594 return true; 595 } 596 } 597 } 598 599 return false; 600 } 601 602 @Override 603 public void addEntry(Entry entry, AddOperation addOperation) throws DirectoryException 604 { 605 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 606 ERR_BACKEND_ADD_NOT_SUPPORTED.get(entry.getName(), getBackendID())); 607 } 608 609 @Override 610 public void deleteEntry(DN entryDN, DeleteOperation deleteOperation) throws DirectoryException 611 { 612 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 613 ERR_BACKEND_DELETE_NOT_SUPPORTED.get(entryDN, getBackendID())); 614 } 615 616 @Override 617 public void replaceEntry(Entry oldEntry, Entry newEntry, 618 ModifyOperation modifyOperation) throws DirectoryException 619 { 620 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 621 ERR_ROOTDSE_MODIFY_NOT_SUPPORTED.get(newEntry.getName(), configEntryDN)); 622 } 623 624 @Override 625 public void renameEntry(DN currentDN, Entry entry, ModifyDNOperation modifyDNOperation) 626 throws DirectoryException 627 { 628 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 629 ERR_BACKEND_MODIFY_DN_NOT_SUPPORTED.get(currentDN, getBackendID())); 630 } 631 632 @Override 633 public void search(SearchOperation searchOperation) 634 throws DirectoryException, CanceledOperationException { 635 DN baseDN = searchOperation.getBaseDN(); 636 if (! baseDN.isRootDN()) 637 { 638 LocalizableMessage message = ERR_ROOTDSE_INVALID_SEARCH_BASE. 639 get(searchOperation.getConnectionID(), searchOperation.getOperationID(), baseDN); 640 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 641 } 642 643 SearchFilter filter = searchOperation.getFilter(); 644 switch (searchOperation.getScope().asEnum()) 645 { 646 case BASE_OBJECT: 647 Entry dseEntry = getRootDSE(searchOperation.getClientConnection()); 648 if (filter.matchesEntry(dseEntry)) 649 { 650 searchOperation.returnEntry(dseEntry, null); 651 } 652 break; 653 654 case SINGLE_LEVEL: 655 for (Map.Entry<DN, Backend<?>> entry : getSubordinateBaseDNs().entrySet()) 656 { 657 searchOperation.checkIfCanceled(false); 658 659 DN subBase = entry.getKey(); 660 Backend<?> b = entry.getValue(); 661 Entry subBaseEntry = b.getEntry(subBase); 662 if (subBaseEntry != null && filter.matchesEntry(subBaseEntry)) 663 { 664 searchOperation.returnEntry(subBaseEntry, null); 665 } 666 } 667 break; 668 669 case WHOLE_SUBTREE: 670 case SUBORDINATES: 671 try 672 { 673 for (Map.Entry<DN, Backend<?>> entry : getSubordinateBaseDNs().entrySet()) 674 { 675 searchOperation.checkIfCanceled(false); 676 677 DN subBase = entry.getKey(); 678 Backend<?> b = entry.getValue(); 679 680 searchOperation.setBaseDN(subBase); 681 try 682 { 683 b.search(searchOperation); 684 } 685 catch (DirectoryException de) 686 { 687 // If it's a "no such object" exception, then the base entry for 688 // the backend doesn't exist. This isn't an error, so ignore it. 689 // We'll propogate all other errors, though. 690 if (de.getResultCode() != ResultCode.NO_SUCH_OBJECT) 691 { 692 throw de; 693 } 694 } 695 } 696 } 697 catch (DirectoryException de) 698 { 699 logger.traceException(de); 700 701 throw de; 702 } 703 catch (Exception e) 704 { 705 logger.traceException(e); 706 707 LocalizableMessage message = ERR_ROOTDSE_UNEXPECTED_SEARCH_FAILURE. 708 get(searchOperation.getConnectionID(), 709 searchOperation.getOperationID(), 710 stackTraceToSingleLineString(e)); 711 throw new DirectoryException( 712 DirectoryServer.getServerErrorResultCode(), message, 713 e); 714 } 715 finally 716 { 717 searchOperation.setBaseDN(rootDSEDN); 718 } 719 break; 720 721 default: 722 LocalizableMessage message = ERR_ROOTDSE_INVALID_SEARCH_SCOPE. 723 get(searchOperation.getConnectionID(), 724 searchOperation.getOperationID(), 725 searchOperation.getScope()); 726 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message); 727 } 728 } 729 730 /** 731 * Returns the subordinate base DNs of the root DSE. 732 * 733 * @return the subordinate base DNs of the root DSE 734 */ 735 @SuppressWarnings({ "unchecked", "rawtypes" }) 736 public Map<DN, Backend<?>> getSubordinateBaseDNs() 737 { 738 if (subordinateBaseDNs != null) 739 { 740 return subordinateBaseDNs; 741 } 742 return DirectoryServer.getPublicNamingContexts(); 743 } 744 745 @Override 746 public Set<String> getSupportedControls() 747 { 748 return Collections.emptySet(); 749 } 750 751 @Override 752 public Set<String> getSupportedFeatures() 753 { 754 return Collections.emptySet(); 755 } 756 757 @Override 758 public boolean supports(BackendOperation backendOperation) 759 { 760 // We will only export the DSE entry itself. 761 return BackendOperation.LDIF_EXPORT.equals(backendOperation); 762 } 763 764 @Override 765 public void exportLDIF(LDIFExportConfig exportConfig) 766 throws DirectoryException 767 { 768 // Create the LDIF writer. 769 LDIFWriter ldifWriter; 770 try 771 { 772 ldifWriter = new LDIFWriter(exportConfig); 773 } 774 catch (Exception e) 775 { 776 logger.traceException(e); 777 778 LocalizableMessage message = ERR_ROOTDSE_UNABLE_TO_CREATE_LDIF_WRITER.get( 779 stackTraceToSingleLineString(e)); 780 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 781 message); 782 } 783 784 // Write the root DSE entry itself to it. Make sure to close the LDIF 785 // writer when we're done. 786 try 787 { 788 ldifWriter.writeEntry(getRootDSE()); 789 } 790 catch (Exception e) 791 { 792 logger.traceException(e); 793 794 LocalizableMessage message = 795 ERR_ROOTDSE_UNABLE_TO_EXPORT_DSE.get(stackTraceToSingleLineString(e)); 796 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 797 message); 798 } 799 finally 800 { 801 close(ldifWriter); 802 } 803 } 804 805 @Override 806 public LDIFImportResult importLDIF(LDIFImportConfig importConfig, ServerContext serverContext) 807 throws DirectoryException 808 { 809 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 810 ERR_BACKEND_IMPORT_AND_EXPORT_NOT_SUPPORTED.get(getBackendID())); 811 } 812 813 @Override 814 public void createBackup(BackupConfig backupConfig) throws DirectoryException 815 { 816 LocalizableMessage message = ERR_ROOTDSE_BACKUP_AND_RESTORE_NOT_SUPPORTED.get(); 817 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 818 } 819 820 @Override 821 public void removeBackup(BackupDirectory backupDirectory, String backupID) throws DirectoryException 822 { 823 LocalizableMessage message = ERR_ROOTDSE_BACKUP_AND_RESTORE_NOT_SUPPORTED.get(); 824 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 825 } 826 827 @Override 828 public void restoreBackup(RestoreConfig restoreConfig) throws DirectoryException 829 { 830 LocalizableMessage message = ERR_ROOTDSE_BACKUP_AND_RESTORE_NOT_SUPPORTED.get(); 831 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 832 } 833 834 @Override 835 public boolean isConfigurationAcceptable(RootDSEBackendCfg config, 836 List<LocalizableMessage> unacceptableReasons, 837 ServerContext serverContext) 838 { 839 return isConfigurationChangeAcceptable(config, unacceptableReasons); 840 } 841 842 @Override 843 public boolean isConfigurationChangeAcceptable(RootDSEBackendCfg cfg, List<LocalizableMessage> unacceptableReasons) 844 { 845 boolean configIsAcceptable = true; 846 847 try 848 { 849 Set<DN> subDNs = cfg.getSubordinateBaseDN(); 850 if (subDNs.isEmpty()) 851 { 852 // This is fine -- we'll just use the set of user-defined suffixes. 853 } 854 else 855 { 856 for (DN baseDN : subDNs) 857 { 858 Backend<?> backend = DirectoryServer.getBackend(baseDN); 859 if (backend == null) 860 { 861 unacceptableReasons.add(WARN_ROOTDSE_NO_BACKEND_FOR_SUBORDINATE_BASE.get(baseDN)); 862 configIsAcceptable = false; 863 } 864 } 865 } 866 } 867 catch (Exception e) 868 { 869 logger.traceException(e); 870 871 unacceptableReasons.add(WARN_ROOTDSE_SUBORDINATE_BASE_EXCEPTION.get( 872 stackTraceToSingleLineString(e))); 873 configIsAcceptable = false; 874 } 875 876 return configIsAcceptable; 877 } 878 879 @Override 880 public ConfigChangeResult applyConfigurationChange(RootDSEBackendCfg cfg) 881 { 882 final ConfigChangeResult ccr = new ConfigChangeResult(); 883 884 // Check to see if we should apply a new set of base DNs. 885 ConcurrentHashMap<DN, Backend<?>> subBases; 886 try 887 { 888 Set<DN> subDNs = cfg.getSubordinateBaseDN(); 889 if (subDNs.isEmpty()) 890 { 891 // This is fine -- we'll just use the set of user-defined suffixes. 892 subBases = null; 893 } 894 else 895 { 896 subBases = new ConcurrentHashMap<>(); 897 for (DN baseDN : subDNs) 898 { 899 Backend<?> backend = DirectoryServer.getBackend(baseDN); 900 if (backend == null) 901 { 902 // This is not fine. We can't use a suffix that doesn't exist. 903 ccr.addMessage(WARN_ROOTDSE_NO_BACKEND_FOR_SUBORDINATE_BASE.get(baseDN)); 904 ccr.setResultCodeIfSuccess(DirectoryServer.getServerErrorResultCode()); 905 } 906 else 907 { 908 subBases.put(baseDN, backend); 909 } 910 } 911 } 912 } 913 catch (Exception e) 914 { 915 logger.traceException(e); 916 917 ccr.setResultCodeIfSuccess(DirectoryServer.getServerErrorResultCode()); 918 ccr.addMessage(WARN_ROOTDSE_SUBORDINATE_BASE_EXCEPTION.get( 919 stackTraceToSingleLineString(e))); 920 921 subBases = null; 922 } 923 924 boolean newShowAll = cfg.isShowAllAttributes(); 925 926 // Check to see if there is a new set of user-defined attributes. 927 ArrayList<Attribute> userAttrs = new ArrayList<>(); 928 try 929 { 930 Entry configEntry = DirectoryServer.getConfigEntry(configEntryDN); 931 addAllUserDefinedAttrs(userAttrs, configEntry); 932 } 933 catch (ConfigException e) 934 { 935 logger.traceException(e); 936 937 ccr.addMessage(ERR_CONFIG_BACKEND_ERROR_INTERACTING_WITH_BACKEND_ENTRY.get( 938 configEntryDN, stackTraceToSingleLineString(e))); 939 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 940 } 941 942 if (ccr.getResultCode() == ResultCode.SUCCESS) 943 { 944 subordinateBaseDNs = subBases; 945 946 if (subordinateBaseDNs == null) 947 { 948 ccr.addMessage(INFO_ROOTDSE_USING_SUFFIXES_AS_BASE_DNS.get()); 949 } 950 else 951 { 952 String basesStr = "{ " + Utils.joinAsString(", ", subordinateBaseDNs.keySet()) + " }"; 953 ccr.addMessage(INFO_ROOTDSE_USING_NEW_SUBORDINATE_BASE_DNS.get(basesStr)); 954 } 955 956 if (showAllAttributes != newShowAll) 957 { 958 showAllAttributes = newShowAll; 959 ccr.addMessage(INFO_ROOTDSE_UPDATED_SHOW_ALL_ATTRS.get( 960 ATTR_ROOTDSE_SHOW_ALL_ATTRIBUTES, showAllAttributes)); 961 } 962 showSubordinatesNamingContexts = cfg.isShowSubordinateNamingContexts(); 963 userDefinedAttributes = userAttrs; 964 ccr.addMessage(INFO_ROOTDSE_USING_NEW_USER_ATTRS.get()); 965 } 966 967 return ccr; 968 } 969}