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