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