/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Portions Copyright 2007 Sun Microsystems, Inc. */ package org.opends.statuspanel; import java.util.HashSet; import java.util.Set; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; import javax.naming.ldap.InitialLdapContext; import javax.naming.ldap.LdapName; import org.opends.statuspanel.i18n.ResourceProvider; import org.opends.quicksetup.util.Utils; /** * This class is used to retrieve configuration and monitoring information using * LDAP protocol. * */ public class ConfigFromLDAP { private HashSet listeners = new HashSet(); private HashSet databases = new HashSet(); private HashSet administrativeUsers = new HashSet(); private String errorMessage; private String dn; private String pwd; private String ldapUrl; private InitialLdapContext ctx; private String javaVersion; private int openConnections = -1; /** * Default constructor. * */ public ConfigFromLDAP() { } /** * Sets the connection information required to contact the server using LDAP. * @param ldapUrl the LDAP URL of the server. * @param dn the authentication Distinguished Name to bind. * @param pwd the authentication password to bind. */ public void setConnectionInfo(String ldapUrl, String dn, String pwd) { if (ldapUrl == null) { throw new IllegalArgumentException("ldapUrl cannot be null."); } if (dn == null) { throw new IllegalArgumentException("dn cannot be null."); } if (pwd == null) { throw new IllegalArgumentException("pwd cannot be null."); } if (!Utils.areDnsEqual(dn, this.dn) || !pwd.equals(this.pwd) || !ldapUrl.equals(this.ldapUrl)) { if (ctx != null) { try { ctx.close(); } catch (Throwable t) { } ctx = null; } } this.ldapUrl = ldapUrl; this.dn = dn; this.pwd = pwd; } /** * Reads the configuration and monitoring information of the server using * LDAP. * When calling this method the thread is blocked until all the configuration * is read. * * This method assumes that the setConnectionInfo has been called previously. * */ public void readConfiguration() { errorMessage = null; listeners.clear(); databases.clear(); administrativeUsers.clear(); javaVersion = null; openConnections = -1; try { InitialLdapContext ctx = getDirContext(); updateAdministrativeUsers(ctx); updateListeners(ctx); updateDatabases(ctx); javaVersion = getJavaVersion(ctx); openConnections = getOpenConnections(ctx); } catch (NamingException ne) { String detail; if (ne.getMessage() != null) { detail = ne.getMessage(); } else { detail = ne.toString(); } String[] arg = {detail}; errorMessage = getMsg("error-reading-config-ldap", arg); } catch (Throwable t) { // Bug t.printStackTrace(); String[] arg = {t.toString()}; errorMessage = getMsg("error-reading-config-ldap", arg); } } /** * Returns the Administrative User DNs found using LDAP. * @return the Administrative User DNs found using LDAP. */ public HashSet getAdministrativeUsers() { return administrativeUsers; } /** * Returns the database descriptors found using LDAP. * @return the database descriptors found using LDAP. */ public HashSet getDatabases() { return databases; } /** * Returns the listener descriptors found using LDAP. * @return the listeners descriptors found using LDAP. */ public HashSet getListeners() { return listeners; } /** * Return the java version we found using LDAP. * @return the java version we found using LDAP. */ public String getJavaVersion() { return javaVersion; } /** * Return the number of open connections we found using LDAP. * @return the number of open connections we found using LDAP. */ public int getOpenConnections() { return openConnections; } /** * Returns the error message that we got when retrieving the information * using LDAP. * @return the error message that we got when retrieving the information * using LDAP. */ public String getErrorMessage() { return errorMessage; } /** * Returns the InitialLdapContext object to be used to retrieve configuration * and monitoring information. * @return the InitialLdapContext object to be used to retrieve configuration * and monitoring information. * @throws NamingException if we could not get an InitialLdapContext. */ private InitialLdapContext getDirContext() throws NamingException { if (ctx != null) { try { pingDirContext(ctx); } catch (NamingException ne) { try { ctx.close(); } catch(NamingException xx) { } ctx = null; } } if (ctx == null) { ctx = Utils.createLdapContext(ldapUrl, dn, pwd, 3000, null); } return ctx; } /** * Ping the specified InitialLdapContext. * This method sends a search request on the root entry of the DIT * and forward the corresponding exception (if any). * @param ctx the InitialLdapContext to be "pinged". * @throws NamingException if the ping could not be performed. */ private void pingDirContext(InitialLdapContext ctx) throws NamingException { SearchControls sc = new SearchControls( SearchControls.OBJECT_SCOPE, 0, // count limit 0, // time limit new String[0], // No attributes false, // Don't return bound object false // Don't dereference link ); ctx.search("", "objectclass=*", sc); } /** * Updates the listener configuration data we expose to the user with the * provided InitialLdapContext. * @param ctx the InitialLdapContext to use to update the configuration. * @throws NamingException if there was an error. */ private void updateListeners(InitialLdapContext ctx) throws NamingException { SearchControls ctls = new SearchControls(); ctls.setSearchScope(SearchControls.SUBTREE_SCOPE); ctls.setReturningAttributes( new String[] { "ds-cfg-connection-handler-enabled", "ds-cfg-listen-address", "ds-cfg-listen-port", "ds-cfg-use-ssl", "objectclass" }); String filter = "(objectclass=ds-cfg-connection-handler)"; LdapName jndiName = new LdapName("cn=config"); NamingEnumeration listeners = ctx.search(jndiName, filter, ctls); while(listeners.hasMore()) { SearchResult sr = (SearchResult)listeners.next(); updateConfigWithConnectionHandler(sr); } } /** * Updates the database configuration data we expose to the user with the * provided InitialLdapContext. * @param ctx the InitialLdapContext to use to update the configuration. * @throws NamingException if there was an error. */ private void updateDatabases(InitialLdapContext ctx) throws NamingException { SearchControls ctls = new SearchControls(); ctls.setSearchScope(SearchControls.SUBTREE_SCOPE); ctls.setReturningAttributes( new String[] { "ds-cfg-backend-base-dn", "ds-cfg-backend-id" }); String filter = "(objectclass=ds-cfg-backend)"; LdapName jndiName = new LdapName("cn=config"); NamingEnumeration databases = ctx.search(jndiName, filter, ctls); while(databases.hasMore()) { SearchResult sr = (SearchResult)databases.next(); updateConfigWithBackend(sr, ctx); } } /** * Updates the administrative user configuration we expose to the user with * the provided InitialLdapContext. * @param ctx the InitialLdapContext to use to update the configuration. * @throws NamingException if there was an error. */ private void updateAdministrativeUsers(InitialLdapContext ctx) throws NamingException { SearchControls ctls = new SearchControls(); ctls.setSearchScope(SearchControls.SUBTREE_SCOPE); ctls.setReturningAttributes( new String[] { "ds-cfg-alternate-bind-dn" }); String filter = "(objectclass=ds-cfg-root-dn)"; LdapName jndiName = new LdapName("cn=config"); NamingEnumeration users = ctx.search(jndiName, filter, ctls); while(users.hasMore()) { SearchResult sr = (SearchResult)users.next(); updateConfigWithAdministrativeUser(sr); } } /** * Returns the java version we find using the provided InitialLdapContext. * @param ctx the InitialLdapContext to use to update the configuration. * @return the java version we find using the provided InitialLdapContext. * @throws NamingException if there was an error. */ private String getJavaVersion(InitialLdapContext ctx) throws NamingException { String v = null; SearchControls ctls = new SearchControls(); ctls.setSearchScope(SearchControls.OBJECT_SCOPE); ctls.setReturningAttributes( new String[] { "jvmVersion" }); String filter = "(objectclass=*)"; LdapName jndiName = new LdapName("cn=System Information,cn=monitor"); NamingEnumeration listeners = ctx.search(jndiName, filter, ctls); while(listeners.hasMore()) { SearchResult sr = (SearchResult)listeners.next(); v = getFirstValue(sr, "jvmVersion"); } return v; } /** * Returns the number of open connections we find using the provided * InitialLdapContext. * @param ctx the InitialLdapContext to use to update the configuration. * @return the number of open connections we find using the provided * InitialLdapContext. * @throws NamingException if there was an error. */ private int getOpenConnections(InitialLdapContext ctx) throws NamingException { int nConnections = -1; String v = null; SearchControls ctls = new SearchControls(); ctls.setSearchScope(SearchControls.OBJECT_SCOPE); ctls.setReturningAttributes( new String[] { "currentConnections" }); String filter = "(objectclass=*)"; LdapName jndiName = new LdapName("cn=monitor"); NamingEnumeration listeners = ctx.search(jndiName, filter, ctls); while(listeners.hasMore()) { SearchResult sr = (SearchResult)listeners.next(); v = getFirstValue(sr, "currentConnections"); } try { nConnections = Integer.parseInt(v); } catch (Exception ex) { } return nConnections; } /** * Returns the number of entries in a given backend using the provided * InitialLdapContext. * @param ctx the InitialLdapContext to use to update the configuration. * @param backenID the id of the backend. * @return the number of entries in the backend. * @throws NamingException if there was an error. */ private int getEntryCount(InitialLdapContext ctx, String backendID) throws NamingException { int nEntries = -1; String v = null; SearchControls ctls = new SearchControls(); ctls.setSearchScope(SearchControls.ONELEVEL_SCOPE); ctls.setReturningAttributes( new String[] { "ds-backend-entry-count" }); String filter = "(ds-backend-id="+backendID+")"; LdapName jndiName = new LdapName("cn=monitor"); NamingEnumeration listeners = ctx.search(jndiName, filter, ctls); while(listeners.hasMore()) { SearchResult sr = (SearchResult)listeners.next(); v = getFirstValue(sr, "ds-backend-entry-count"); } try { nEntries = Integer.parseInt(v); } catch (Exception ex) { } return nEntries; } /** * Updates the listener configuration data we expose to the user with the * provided SearchResult object. * @param entry the entry to analyze. * @throws NamingException if there was an error. */ private void updateConfigWithConnectionHandler(SearchResult entry) throws NamingException { String address = getFirstValue(entry, "ds-cfg-listen-address"); String port = getFirstValue(entry, "ds-cfg-listen-port"); String addressPort; boolean isSecure = "true".equalsIgnoreCase( getFirstValue(entry, "ds-cfg-use-ssl")); ListenerDescriptor.Protocol protocol; String protocolDescription; ListenerDescriptor.State state; if (hasObjectClass(entry, "ds-cfg-ldap-connection-handler")) { addressPort = address+":"+port; if (isSecure) { protocolDescription = getMsg("ldaps-protocol-label"); protocol = ListenerDescriptor.Protocol.LDAPS; } else { protocolDescription = getMsg("ldap-protocol-label"); protocol = ListenerDescriptor.Protocol.LDAP; } boolean enabled = "true".equalsIgnoreCase( getFirstValue(entry, "ds-cfg-connection-handler-enabled")); if (enabled) { state = ListenerDescriptor.State.ENABLED; } else { state = ListenerDescriptor.State.DISABLED; } } else if (hasObjectClass(entry, "ds-cfg-jmx-connection-handler")) { addressPort = "0.0.0.0:"+port; if (isSecure) { protocolDescription = getMsg("jmx-secure-protocol-label"); protocol = ListenerDescriptor.Protocol.JMXS; } else { protocolDescription = getMsg("jmx-protocol-label"); protocol = ListenerDescriptor.Protocol.JMX; } boolean enabled = "true".equalsIgnoreCase( getFirstValue(entry, "ds-cfg-connection-handler-enabled")); if (enabled) { state = ListenerDescriptor.State.ENABLED; } else { state = ListenerDescriptor.State.DISABLED; } } else { addressPort = getMsg("unknown-label"); protocolDescription = null; protocol = ListenerDescriptor.Protocol.OTHER; /* Try to figure a name from the cn */ String cn = getFirstValue(entry, "cn"); if (cn != null) { int index = cn.toLowerCase().indexOf("connection handler"); if (index > 0) { protocolDescription = cn.substring(0, index).trim(); } else { protocolDescription = cn; } } else { protocolDescription = getMsg("undefined-protocol-label"); } state = ListenerDescriptor.State.UNKNOWN; } listeners.add(new ListenerDescriptor(addressPort, protocol, protocolDescription, state)); } /** * Updates the database configuration data we expose to the user with the * provided SearchResult object. * @param entry the entry to analyze. * @throws NamingException if there was an error. */ private void updateConfigWithBackend(SearchResult entry, InitialLdapContext ctx) throws NamingException { String baseDn = getFirstValue(entry, "ds-cfg-backend-base-dn"); String id = getFirstValue(entry, "ds-cfg-backend-id"); if (!isConfigBackend(id)) { int nEntries = getEntryCount(ctx, id); databases.add(new DatabaseDescriptor(id, baseDn, nEntries)); } } /** * Updates the administrative user configuration data we expose to the user * with the provided SearchResult object. * @param entry the entry to analyze. * @throws NamingException if there was an error. */ private void updateConfigWithAdministrativeUser(SearchResult entry) throws NamingException { administrativeUsers.addAll(getValues(entry, "ds-cfg-alternate-bind-dn")); } /* * The following 2 methods are convenience methods to retrieve String values * from an entry. */ private String getFirstValue(SearchResult entry, String attrName) throws NamingException { String v = null; Attributes attrs = entry.getAttributes(); if (attrs != null) { Attribute attr = attrs.get(attrName); if ((attr != null) && (attr.size() > 0)) { v = (String)attr.get(); } } return v; } private Set getValues(SearchResult entry, String attrName) throws NamingException { Set values = new HashSet(); Attributes attrs = entry.getAttributes(); if (attrs != null) { Attribute attr = attrs.get(attrName); if (attr != null) { for (int i=0; itrue if the SearchResult is of a the objectclass and * false otherwise. * @throws NamingException if there was an error. */ private boolean hasObjectClass(SearchResult entry, String ocName) throws NamingException { boolean hasObjectClass = false; Attributes attrs = entry.getAttributes(); if (attrs != null) { Attribute attr = attrs.get("objectclass"); if (attr != null) { for (int i=0; i