/*
* The contents of this file are subject to the terms of the Common Development and
* Distribution License (the License). You may not use this file except in compliance with the
* License.
*
* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
* specific language governing permission and limitations under the License.
*
* When distributing Covered Software, include this CDDL Header Notice in each file and include
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
* Header, with the fields enclosed by brackets [] replaced by your own identifying
* information: "Portions Copyright [year] [name of copyright owner]".
*
* Copyright 2007-2010 Sun Microsystems, Inc.
* Portions Copyright 2013-2016 ForgeRock AS.
*/
package org.opends.admin.ads;
import static org.forgerock.opendj.ldap.SearchScope.*;
import static org.forgerock.opendj.ldap.requests.Requests.*;
import static org.opends.admin.ads.util.ConnectionUtils.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import javax.naming.ldap.Rdn;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.ldap.Attribute;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.requests.AddRequest;
import org.forgerock.opendj.ldap.requests.SearchRequest;
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
import org.forgerock.opendj.ldif.ConnectionEntryReader;
import org.forgerock.util.Pair;
import org.opends.admin.ads.util.ConnectionWrapper;
import org.opends.quicksetup.Constants;
import org.opends.server.config.ConfigConstants;
import org.opends.server.types.HostPort;
/**
* The object of this class represent an OpenDS server instance.
*
* It can represent either a DS-only, a RS-only or a combined DS-RS.
*/
public class ServerDescriptor
{
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
private static final String TRUSTSTORE_DN = "cn=ads-truststore";
private final Map adsProperties = new HashMap<>();
private final Set replicas = new HashSet<>();
private final Map serverProperties = new HashMap<>();
private TopologyCacheException lastException;
/**
* Enumeration containing the different server properties that we can keep in
* the ServerProperty object.
*/
public enum ServerProperty
{
/** The associated value is a String. */
HOST_NAME(ADSContext.ServerProperty.HOST_NAME),
/** The associated value is an List of Integer. */
LDAP_PORT(ADSContext.ServerProperty.LDAP_PORT),
/** The associated value is an List of Integer. */
LDAPS_PORT(ADSContext.ServerProperty.LDAPS_PORT),
/** The associated value is an Integer. */
ADMIN_PORT(ADSContext.ServerProperty.ADMIN_PORT),
/** The associated value is an List of Boolean. */
LDAP_ENABLED(ADSContext.ServerProperty.LDAP_ENABLED),
/** The associated value is an List of Boolean. */
LDAPS_ENABLED(ADSContext.ServerProperty.LDAPS_ENABLED),
/** The associated value is an List of Boolean. */
ADMIN_ENABLED(ADSContext.ServerProperty.ADMIN_ENABLED),
/** The associated value is an List of Boolean. */
STARTTLS_ENABLED(ADSContext.ServerProperty.STARTTLS_ENABLED),
/** The associated value is an List of Integer. */
JMX_PORT(ADSContext.ServerProperty.JMX_PORT),
/** The associated value is an List of Integer. */
JMXS_PORT(ADSContext.ServerProperty.JMXS_PORT),
/** The associated value is an List of Boolean. */
JMX_ENABLED(ADSContext.ServerProperty.JMX_ENABLED),
/** The associated value is an List of Boolean. */
JMXS_ENABLED(ADSContext.ServerProperty.JMXS_ENABLED),
/** The associated value is an Integer. */
REPLICATION_SERVER_PORT(null),
/** The associated value is a Boolean. */
IS_REPLICATION_SERVER(null),
/** The associated value is a Boolean. */
IS_REPLICATION_ENABLED(null),
/** The associated value is a Boolean. */
IS_REPLICATION_SECURE(null),
/** List of servers specified in the Replication Server configuration. This is a Set of String. */
EXTERNAL_REPLICATION_SERVERS(null),
/** The associated value is an Integer. */
REPLICATION_SERVER_ID(null),
/**
* The instance key-pair public-key certificate. The associated value is a
* byte[] (ds-cfg-public-key-certificate;binary).
*/
INSTANCE_PUBLIC_KEY_CERTIFICATE(ADSContext.ServerProperty.INSTANCE_PUBLIC_KEY_CERTIFICATE),
/** The schema generation ID. */
SCHEMA_GENERATION_ID(null);
private org.opends.admin.ads.ADSContext.ServerProperty adsEquivalent;
private ServerProperty(ADSContext.ServerProperty adsEquivalent)
{
this.adsEquivalent = adsEquivalent;
}
}
/** Default constructor. */
protected ServerDescriptor()
{
}
/**
* Returns the replicas contained on the server.
* @return the replicas contained on the server.
*/
public Set getReplicas()
{
return new HashSet<>(replicas);
}
/**
* Sets the replicas contained on the server.
* @param replicas the replicas contained on the server.
*/
public void setReplicas(Set replicas)
{
this.replicas.clear();
this.replicas.addAll(replicas);
}
/**
* Returns a Map containing the ADS properties of the server.
* @return a Map containing the ADS properties of the server.
*/
public Map getAdsProperties()
{
return adsProperties;
}
/**
* Returns a Map containing the properties of the server.
* @return a Map containing the properties of the server.
*/
public Map getServerProperties()
{
return serverProperties;
}
/**
* Tells whether this server is registered in the ADS.
* @return {@code true} if the server is registered in the ADS and {@code false} otherwise.
*/
public boolean isRegistered()
{
return !adsProperties.isEmpty();
}
/**
* Tells whether this server is a replication server.
* @return {@code true} if the server is a replication server and {@code false} otherwise.
*/
public boolean isReplicationServer()
{
return Boolean.TRUE.equals(
serverProperties.get(ServerProperty.IS_REPLICATION_SERVER));
}
/**
* Tells whether replication is enabled on this server.
* @return {@code true} if replication is enabled and {@code false} otherwise.
*/
public boolean isReplicationEnabled()
{
return Boolean.TRUE.equals(serverProperties.get(ServerProperty.IS_REPLICATION_ENABLED));
}
/**
* Returns the String representation of this replication server based
* on the information we have ("hostname":"replication port") and
* {@code null} if this is not a replication server.
* @return the String representation of this replication server based
* on the information we have ("hostname":"replication port") and
* {@code null} if this is not a replication server.
*/
public String getReplicationServerHostPort()
{
return isReplicationServer() ? getReplicationServer(getHostName(), getReplicationServerPort()) : null;
}
/**
* Returns the replication server ID of this server and -1 if this is not a
* replications server.
* @return the replication server ID of this server and -1 if this is not a
* replications server.
*/
public int getReplicationServerId()
{
return isReplicationServer() ? (Integer) serverProperties.get(ServerProperty.REPLICATION_SERVER_ID) : -1;
}
/**
* Returns the replication port of this server and -1 if this is not a
* replications server.
* @return the replication port of this server and -1 if this is not a
* replications server.
*/
public int getReplicationServerPort()
{
return isReplicationServer() ? (Integer) serverProperties.get(ServerProperty.REPLICATION_SERVER_PORT) : -1;
}
/**
* Returns whether the communication with the replication port on the server is encrypted.
* @return {@code true} if the communication with the replication port on
* the server is encrypted and {@code false} otherwise.
*/
public boolean isReplicationSecure()
{
return isReplicationServer()
&& Boolean.TRUE.equals(serverProperties.get(ServerProperty.IS_REPLICATION_SECURE));
}
/**
* Sets the ADS properties of the server.
* @param adsProperties a Map containing the ADS properties of the server.
*/
public void setAdsProperties(
Map adsProperties)
{
this.adsProperties.clear();
this.adsProperties.putAll(adsProperties);
}
/**
* Returns the host name of the server.
* @return the host name of the server.
*/
public String getHostName()
{
String host = (String)serverProperties.get(ServerProperty.HOST_NAME);
if (host != null)
{
return host;
}
return (String) adsProperties.get(ADSContext.ServerProperty.HOST_NAME);
}
/**
* Returns the URL to access this server using LDAP.
* @return the URL to access this server using LDAP,
* {@code null} if the server is not configured to listen on an LDAP port.
*/
public String getLDAPURL()
{
return getLDAPUrl0(ServerProperty.LDAP_ENABLED, ServerProperty.LDAP_PORT, false);
}
/**
* Returns the URL to access this server using LDAPS.
* @return the URL to access this server using LDAP,
* {@code null} if the server is not configured to listen on an LDAPS port.
*/
public String getLDAPsURL()
{
return getLDAPUrl0(ServerProperty.LDAPS_ENABLED, ServerProperty.LDAPS_PORT, true);
}
private String getLDAPUrl0(ServerProperty enabledProp, ServerProperty portProp, boolean useSSL)
{
int port = getPort(enabledProp, portProp);
if (port != -1)
{
String host = getHostName();
return getLDAPUrl(host, port, useSSL);
}
return null;
}
private int getPort(ServerProperty enabledProp, ServerProperty portProp)
{
if (!serverProperties.isEmpty())
{
return getPort(enabledProp, portProp, -1);
}
return -1;
}
/**
* Returns the URL to access this server using the administration connector.
* @return the URL to access this server using the administration connector,
* {@code null} if the server cannot get the administration connector.
*/
public String getAdminConnectorURL()
{
return getLDAPUrl0(ServerProperty.ADMIN_ENABLED, ServerProperty.ADMIN_PORT, true);
}
/**
* Returns the list of enabled administration ports.
* @return the list of enabled administration ports.
*/
public List getEnabledAdministrationPorts()
{
List ports = new ArrayList<>(1);
List> s = (List>) serverProperties.get(ServerProperty.ADMIN_ENABLED);
List> p = (List>) serverProperties.get(ServerProperty.ADMIN_PORT);
if (s != null)
{
for (int i=0; i enabledAttrs = new ArrayList<>();
if (securePreferred)
{
enabledAttrs.add(ADSContext.ServerProperty.ADMIN_ENABLED);
enabledAttrs.add(ADSContext.ServerProperty.LDAPS_ENABLED);
enabledAttrs.add(ADSContext.ServerProperty.LDAP_ENABLED);
}
else
{
enabledAttrs.add(ADSContext.ServerProperty.LDAP_ENABLED);
enabledAttrs.add(ADSContext.ServerProperty.ADMIN_ENABLED);
enabledAttrs.add(ADSContext.ServerProperty.LDAPS_ENABLED);
}
for (ADSContext.ServerProperty prop : enabledAttrs)
{
Object v = adsProperties.get(prop);
if (v != null && "true".equalsIgnoreCase(String.valueOf(v)))
{
ADSContext.ServerProperty portProp = getPortProperty(prop);
Object p = adsProperties.get(portProp);
if (p != null)
{
try
{
port = Integer.parseInt(String.valueOf(p));
}
catch (Throwable t)
{
logger.warn(LocalizableMessage.raw("Error calculating host port: "+t+" in "+
adsProperties, t));
}
break;
}
else
{
logger.warn(LocalizableMessage.raw("Value for "+portProp+" is null in "+
adsProperties));
}
}
}
}
return new HostPort(getHostName(), port);
}
private int getLdapPort(int port)
{
return getPort(ServerProperty.LDAP_ENABLED, ServerProperty.LDAP_PORT, port);
}
private int getLdapsPort(int port)
{
return getPort(ServerProperty.LDAPS_ENABLED, ServerProperty.LDAPS_PORT, port);
}
private int getAdminPort(int port)
{
return getPort(ServerProperty.ADMIN_ENABLED, ServerProperty.ADMIN_PORT, port);
}
private ADSContext.ServerProperty getPortProperty(ADSContext.ServerProperty prop)
{
switch (prop)
{
case ADMIN_ENABLED:
return ADSContext.ServerProperty.ADMIN_PORT;
case LDAPS_ENABLED:
return ADSContext.ServerProperty.LDAPS_PORT;
case LDAP_ENABLED:
return ADSContext.ServerProperty.LDAP_PORT;
default:
throw new IllegalStateException("Unexpected prop: "+prop);
}
}
private int getPort(ServerProperty enabledProp, ServerProperty portProp, int defaultValue)
{
List> s = (List>) serverProperties.get(enabledProp);
if (s != null)
{
List> p = (List>) serverProperties.get(portProp);
for (int i=0; i s = (List>) serverProperties.get(prop);
for (Object o : s) {
buf.append(":").append(o);
}
}
}
else
{
ADSContext.ServerProperty[] props = {
ADSContext.ServerProperty.HOST_NAME,
ADSContext.ServerProperty.LDAP_PORT,
ADSContext.ServerProperty.LDAPS_PORT,
ADSContext.ServerProperty.ADMIN_PORT,
ADSContext.ServerProperty.LDAP_ENABLED,
ADSContext.ServerProperty.LDAPS_ENABLED,
ADSContext.ServerProperty.ADMIN_ENABLED
};
for (int i=0; i s = (List>) serverProperties.get(sProps[i][0]);
List> p = (List>) serverProperties.get(sProps[i][1]);
if (s != null)
{
int port = getPort(s, p);
if (port == -1)
{
adsProperties.put(adsProps[i][0], "false");
if (!p.isEmpty())
{
port = (Integer)p.iterator().next();
}
}
else
{
adsProperties.put(adsProps[i][0], "true");
}
adsProperties.put(adsProps[i][1], String.valueOf(port));
}
}
List> array = (List>) serverProperties.get(ServerProperty.STARTTLS_ENABLED);
boolean startTLSEnabled = false;
if (array != null && !array.isEmpty())
{
startTLSEnabled = Boolean.TRUE.equals(array.get(array.size() -1));
}
adsProperties.put(ADSContext.ServerProperty.STARTTLS_ENABLED, Boolean.toString(startTLSEnabled));
adsProperties.put(ADSContext.ServerProperty.ID, getHostPort(true).toString());
adsProperties.put(ADSContext.ServerProperty.INSTANCE_PUBLIC_KEY_CERTIFICATE,
getInstancePublicKeyCertificate());
}
private int getPort(List> enabled, List> port)
{
for (int j = 0; j < enabled.size(); j++)
{
if (Boolean.TRUE.equals(enabled.get(j)))
{
return (Integer) port.get(j);
}
}
return -1;
}
/**
* Creates a ServerDescriptor object based on some ADS properties provided.
* @param adsProperties the ADS properties of the server.
* @return a ServerDescriptor object that corresponds to the provided ADS
* properties.
*/
public static ServerDescriptor createStandalone(
Map adsProperties)
{
ServerDescriptor desc = new ServerDescriptor();
desc.setAdsProperties(adsProperties);
return desc;
}
/**
* Creates a ServerDescriptor object based on the configuration that we read
* using the provided connection.
* @param conn the connection that will be used to read the configuration of the server.
* @param filter the topology cache filter describing the information that
* must be retrieved.
* @return a ServerDescriptor object that corresponds to the read configuration.
* @throws IOException if a problem occurred reading the server configuration.
*/
public static ServerDescriptor createStandalone(ConnectionWrapper conn, TopologyCacheFilter filter) throws IOException
{
ServerDescriptor desc = new ServerDescriptor();
updateLdapConfiguration(desc, conn);
updateAdminConnectorConfiguration(desc, conn);
updateJmxConfiguration(desc, conn);
updateReplicas(desc, conn, filter);
updateReplication(desc, conn, filter);
updatePublicKeyCertificate(desc, conn);
updateMiscellaneous(desc, conn);
desc.serverProperties.put(ServerProperty.HOST_NAME, conn.getHostPort().getHost());
return desc;
}
private static void updateLdapConfiguration(ServerDescriptor desc, ConnectionWrapper conn)
throws IOException
{
String filter = "(objectclass=ds-cfg-ldap-connection-handler)";
SearchRequest request = newSearchRequest("cn=config", WHOLE_SUBTREE, filter,
"ds-cfg-enabled",
"ds-cfg-listen-address",
"ds-cfg-listen-port",
"ds-cfg-use-ssl",
"ds-cfg-allow-start-tls",
"objectclass");
try (ConnectionEntryReader entryReader = conn.getConnection().search(request))
{
List ldapPorts = new ArrayList<>();
List ldapsPorts = new ArrayList<>();
List ldapEnabled = new ArrayList<>();
List ldapsEnabled = new ArrayList<>();
List startTLSEnabled = new ArrayList<>();
desc.serverProperties.put(ServerProperty.LDAP_PORT, ldapPorts);
desc.serverProperties.put(ServerProperty.LDAPS_PORT, ldapsPorts);
desc.serverProperties.put(ServerProperty.LDAP_ENABLED, ldapEnabled);
desc.serverProperties.put(ServerProperty.LDAPS_ENABLED, ldapsEnabled);
desc.serverProperties.put(ServerProperty.STARTTLS_ENABLED, startTLSEnabled);
while (entryReader.hasNext())
{
SearchResultEntry sr = entryReader.readEntry();
Integer portNumber = asInteger(sr, "ds-cfg-listen-port");
boolean enabled = asBoolean(sr, "ds-cfg-enabled");
if (asBoolean(sr, "ds-cfg-use-ssl"))
{
ldapsPorts.add(portNumber);
ldapsEnabled.add(enabled);
}
else
{
ldapPorts.add(portNumber);
ldapEnabled.add(enabled);
startTLSEnabled.add(asBoolean(sr, "ds-cfg-allow-start-tls"));
}
}
}
}
private static void updateAdminConnectorConfiguration(ServerDescriptor desc, ConnectionWrapper conn)
throws IOException
{
SearchRequest request = newSearchRequest(
"cn=config", WHOLE_SUBTREE, "(objectclass=ds-cfg-administration-connector)",
"ds-cfg-listen-port", "objectclass");
SearchResultEntry sr = conn.getConnection().searchSingleEntry(request);
Integer adminConnectorPort = asInteger(sr, "ds-cfg-listen-port");
// Even if we have a single port, use an array to be consistent with
// other protocols.
List adminPorts = new ArrayList<>();
List adminEnabled = new ArrayList<>();
if (adminConnectorPort != null)
{
adminPorts.add(adminConnectorPort);
adminEnabled.add(Boolean.TRUE);
}
desc.serverProperties.put(ServerProperty.ADMIN_PORT, adminPorts);
desc.serverProperties.put(ServerProperty.ADMIN_ENABLED, adminEnabled);
}
private static void updateJmxConfiguration(ServerDescriptor desc, ConnectionWrapper conn) throws IOException
{
List jmxPorts = new ArrayList<>();
List jmxsPorts = new ArrayList<>();
List jmxEnabled = new ArrayList<>();
List jmxsEnabled = new ArrayList<>();
desc.serverProperties.put(ServerProperty.JMX_PORT, jmxPorts);
desc.serverProperties.put(ServerProperty.JMXS_PORT, jmxsPorts);
desc.serverProperties.put(ServerProperty.JMX_ENABLED, jmxEnabled);
desc.serverProperties.put(ServerProperty.JMXS_ENABLED, jmxsEnabled);
String filter = "(objectclass=ds-cfg-jmx-connection-handler)";
SearchRequest request = newSearchRequest("cn=config", WHOLE_SUBTREE, filter,
"ds-cfg-enabled",
"ds-cfg-listen-address",
"ds-cfg-listen-port",
"ds-cfg-use-ssl",
"objectclass");
try (ConnectionEntryReader entryReader = conn.getConnection().search(request))
{
while (entryReader.hasNext())
{
SearchResultEntry sr = entryReader.readEntry();
Integer portNumber = asInteger(sr, "ds-cfg-listen-port");
boolean enabled = asBoolean(sr, "ds-cfg-enabled");
if (asBoolean(sr, "ds-cfg-use-ssl"))
{
jmxsPorts.add(portNumber);
jmxsEnabled.add(enabled);
}
else
{
jmxPorts.add(portNumber);
jmxEnabled.add(enabled);
}
}
}
}
private static void updateReplicas(ServerDescriptor desc, ConnectionWrapper conn, TopologyCacheFilter cacheFilter)
throws IOException
{
if (!cacheFilter.searchBaseDNInformation())
{
return;
}
SearchRequest request = newSearchRequest("cn=config", WHOLE_SUBTREE, "(objectclass=ds-cfg-backend)",
"ds-cfg-base-dn",
"ds-cfg-backend-id",
ConfigConstants.ATTR_OBJECTCLASS);
try (ConnectionEntryReader entryReader = conn.getConnection().search(request))
{
while (entryReader.hasNext())
{
SearchResultEntry sr = entryReader.readEntry();
String backendId = firstValueAsString(sr, "ds-cfg-backend-id");
if (!isConfigBackend(backendId) || isSchemaBackend(backendId))
{
Set entries;
if (cacheFilter.searchMonitoringInformation())
{
entries = getBaseDNEntryCount(conn, backendId);
}
else
{
entries = new HashSet<>();
}
Set replicas = desc.getReplicas();
Set baseDns = asSetOfDN(sr, "ds-cfg-base-dn");
for (DN baseDn : baseDns)
{
if (isAddReplica(cacheFilter, baseDn))
{
ReplicaDescriptor replica = new ReplicaDescriptor();
replica.setServer(desc);
replica.setObjectClasses(asSetOfString(sr, ConfigConstants.ATTR_OBJECTCLASS));
replica.setBackendId(backendId);
replica.setSuffix(new SuffixDescriptor(baseDn, replica));
replica.setEntries(getNumberOfEntriesForBaseDn(entries, baseDn));
replicas.add(replica);
}
}
desc.setReplicas(replicas);
}
}
}
}
private static int getNumberOfEntriesForBaseDn(Set entries, DN baseDn)
{
for (String s : entries)
{
int index = s.indexOf(" ");
if (index != -1)
{
DN dn = DN.valueOf(s.substring(index + 1));
if (baseDn.equals(dn))
{
try
{
return Integer.parseInt(s.substring(0, index));
}
catch (Throwable t)
{
/* Ignore */
}
break;
}
}
}
return -1;
}
private static boolean isAddReplica(TopologyCacheFilter cacheFilter, DN baseDn)
{
return cacheFilter.searchAllBaseDNs() || cacheFilter.getBaseDNsToSearch().contains(baseDn);
}
private static void updateReplication(ServerDescriptor desc, ConnectionWrapper conn, TopologyCacheFilter cacheFilter)
throws IOException
{
SearchRequest request = newSearchRequest(
"cn=Multimaster Synchronization,cn=Synchronization Providers,cn=config",
WHOLE_SUBTREE,
"(objectclass=ds-cfg-synchronization-provider)",
"ds-cfg-enabled");
SearchResultEntry sre = conn.getConnection().searchSingleEntry(request);
Boolean replicationEnabled = asBoolean(sre, "ds-cfg-enabled");
desc.serverProperties.put(ServerProperty.IS_REPLICATION_ENABLED, replicationEnabled);
Set allReplicationServers = new LinkedHashSet<>();
if (cacheFilter.searchBaseDNInformation())
{
request = newSearchRequest(
"cn=Multimaster Synchronization,cn=Synchronization Providers,cn=config",
WHOLE_SUBTREE,
"(objectclass=ds-cfg-replication-domain)",
"ds-cfg-base-dn",
"ds-cfg-replication-server",
"ds-cfg-server-id"
);
try (ConnectionEntryReader entryReader = conn.getConnection().search(request))
{
while (entryReader.hasNext())
{
SearchResultEntry sr = entryReader.readEntry();
int id = asInteger(sr, "ds-cfg-server-id");
Set replicationServers = asSetOfString(sr, "ds-cfg-replication-server");
Set dns = asSetOfDN(sr, "ds-cfg-base-dn");
for (DN dn : dns)
{
for (ReplicaDescriptor replica : desc.getReplicas())
{
if (replica.getSuffix().getDN().equals(dn))
{
replica.setReplicationId(id);
LinkedHashSet repServers = toLowercase(replicationServers);
replica.setReplicationServers(repServers);
allReplicationServers.addAll(repServers);
}
}
}
}
}
}
desc.serverProperties.put(ServerProperty.IS_REPLICATION_SERVER, Boolean.FALSE);
request = newSearchRequest(
"cn=Multimaster Synchronization,cn=Synchronization Providers,cn=config",
WHOLE_SUBTREE,
"(objectclass=ds-cfg-replication-server)",
"ds-cfg-replication-port",
"ds-cfg-replication-server",
"ds-cfg-replication-server-id"
);
try (ConnectionEntryReader entryReader = conn.getConnection().search(request))
{
while (entryReader.hasNext())
{
SearchResultEntry sr = entryReader.readEntry();
desc.serverProperties.put(ServerProperty.IS_REPLICATION_SERVER, Boolean.TRUE);
Integer port = asInteger(sr, "ds-cfg-replication-port");
desc.serverProperties.put(ServerProperty.REPLICATION_SERVER_PORT, port);
Integer serverId = asInteger(sr, "ds-cfg-replication-server-id");
desc.serverProperties.put(ServerProperty.REPLICATION_SERVER_ID, serverId);
LinkedHashSet repServers = toLowercase(asSetOfString(sr, "ds-cfg-replication-server"));
allReplicationServers.addAll(repServers);
desc.serverProperties.put(ServerProperty.EXTERNAL_REPLICATION_SERVERS, allReplicationServers);
}
}
Boolean replicationSecure = isReplicationSecure(conn, replicationEnabled);
desc.serverProperties.put(ServerProperty.IS_REPLICATION_SECURE, replicationSecure);
}
/**
* Keep the values of the replication servers in lower case to make use of Sets as String simpler.
*/
private static LinkedHashSet toLowercase(Set values)
{
LinkedHashSet repServers = new LinkedHashSet<>();
for (String s: values)
{
repServers.add(s.toLowerCase());
}
return repServers;
}
private static boolean isReplicationSecure(ConnectionWrapper conn, boolean replicationEnabled) throws IOException
{
if (replicationEnabled)
{
SearchRequest request = newSearchRequest(
"cn=Crypto Manager,cn=config", BASE_OBJECT, "(objectclass=ds-cfg-crypto-manager)",
"ds-cfg-ssl-encryption");
try (ConnectionEntryReader entryReader = conn.getConnection().search(request))
{
while (entryReader.hasNext())
{
SearchResultEntry sr = entryReader.readEntry();
return asBoolean(sr, "ds-cfg-ssl-encryption");
}
}
}
return false;
}
/**
* Updates the instance key public-key certificate value of this context from the local truststore
* of the instance bound by this context. Any current value of the certificate is overwritten. The
* intent of this method is to retrieve the instance-key public-key certificate when this context
* is bound to an instance, and cache it for later use in registering the instance into ADS.
*
* @param desc
* The map to update with the instance key-pair public-key certificate.
* @param connWrapper
* The connection to the server.
* @throws LdapException
* if unable to retrieve certificate from bound instance.
*/
private static void updatePublicKeyCertificate(ServerDescriptor desc, ConnectionWrapper connWrapper)
throws LdapException
{
/* TODO: this DN is declared in some core constants file. Create a constants
file for the installer and import it into the core. */
String dn = "ds-cfg-key-id=ads-certificate,cn=ads-truststore";
Connection conn = connWrapper.getConnection();
for (int i = 0; i < 2 ; ++i) {
/* If the entry does not exist in the instance's truststore backend, add
it (which induces the CryptoManager to create the public-key
certificate attribute), then repeat the search. */
try {
SearchRequest request = newSearchRequest(
dn,
BASE_OBJECT,
"(objectclass=ds-cfg-instance-key)",
"ds-cfg-public-key-certificate;binary");
SearchResultEntry certEntry = conn.searchSingleEntry(request);
final Attribute certAttr = certEntry.getAttribute("ds-cfg-public-key-certificate;binary");
if (null != certAttr) {
/* attribute ds-cfg-public-key-certificate is a MUST in the schema */
desc.serverProperties.put(
ServerProperty.INSTANCE_PUBLIC_KEY_CERTIFICATE,
certAttr.firstValue().toByteArray());
}
break;
}
catch (LdapException e)
{
if (0 != i || e.getResult().getResultCode() != ResultCode.NO_SUCH_OBJECT)
{
throw e;
}
// Poke CryptoManager to initialize truststore. Note the special attribute in the request.
AddRequest request = newAddRequest(dn)
.addAttribute("objectclass", "top", "ds-cfg-self-signed-cert-request");
conn.add(request);
}
}
}
private static void updateMiscellaneous(ServerDescriptor desc, ConnectionWrapper conn) throws IOException
{
String filter = "(|(objectclass=*)(objectclass=ldapsubentry))";
SearchRequest request = newSearchRequest("cn=schema", BASE_OBJECT, filter, "ds-sync-generation-id");
try (ConnectionEntryReader entryReader = conn.getConnection().search(request))
{
while (entryReader.hasNext())
{
SearchResultEntry sr = entryReader.readEntry();
desc.serverProperties.put(ServerProperty.SCHEMA_GENERATION_ID,
firstValueAsString(sr, "ds-sync-generation-id"));
}
}
}
/**
Seeds the bound instance's local ads-truststore with a set of instance
key-pair public key certificates. The result is the instance will trust any
instance possessing the private key corresponding to one of the public-key
certificates. This trust is necessary at least to initialize replication,
which uses the trusted certificate entries in the ads-truststore for server
authentication.
@param connWrapper The connection to the server.
@param keyEntryMap The set of valid (i.e., not tagged as compromised)
instance key-pair public-key certificate entries in ADS represented as a map
from keyID to public-key certificate (binary).
@throws LdapException in case an error occurs while updating the instance's
ads-truststore via LDAP.
*/
public static void seedAdsTrustStore(ConnectionWrapper connWrapper, Map keyEntryMap)
throws LdapException
{
Connection conn = connWrapper.getConnection();
/* TODO: this DN is declared in some core constants file. Create a
constants file for the installer and import it into the core. */
for (Map.Entry keyEntry : keyEntryMap.entrySet()){
String instanceKeyId = ADSContext.ServerProperty.INSTANCE_KEY_ID.getAttributeName();
String instancePublicKeyCertificate =
ADSContext.ServerProperty.INSTANCE_PUBLIC_KEY_CERTIFICATE.getAttributeName() + ";binary";
String dn = instanceKeyId + "=" + Rdn.escapeValue(keyEntry.getKey()) + "," + TRUSTSTORE_DN;
AddRequest request = newAddRequest(dn)
.addAttribute("objectclass", "top", "ds-cfg-instance-key")
.addAttribute(instanceKeyId, keyEntry.getKey())
.addAttribute(instancePublicKeyCertificate, keyEntry.getValue());
try
{
conn.add(request);
}
catch (LdapException e)
{
if (e.getResult().getResultCode() != ResultCode.ENTRY_ALREADY_EXISTS)
{
throw e;
}
conn.delete(dn);
conn.add(request);
}
}
}
/**
* Returns the values of the ds-base-dn-entry count attributes for the given backend monitor entry
* using the provided connection.
*
* @param conn
* the connection to use to update the configuration.
* @param backendID
* the id of the backend.
* @return the values of the ds-base-dn-entry count attribute.
* @throws IOException
* if there was an error.
*/
private static Set getBaseDNEntryCount(ConnectionWrapper conn, String backendID) throws IOException
{
LinkedHashSet results = new LinkedHashSet<>();
SearchRequest request =
newSearchRequest("cn=monitor", SINGLE_LEVEL, "(ds-backend-id=" + backendID + ")", "ds-base-dn-entry-count");
try (ConnectionEntryReader entryReader = conn.getConnection().search(request))
{
while (entryReader.hasNext())
{
SearchResultEntry sr = entryReader.readEntry();
results.addAll(asSetOfString(sr, "ds-base-dn-entry-count"));
}
}
return results;
}
/**
* Returns whether the provided backendID corresponds to a configuration backend.
* @param backendId the backend ID to analyze
* @return {@code true} if the the id corresponds to a configuration
* backend and {@code false} otherwise.
*/
private static boolean isConfigBackend(String backendId)
{
return "tasks".equalsIgnoreCase(backendId)
|| "schema".equalsIgnoreCase(backendId)
|| "config".equalsIgnoreCase(backendId)
|| "monitor".equalsIgnoreCase(backendId)
|| "backup".equalsIgnoreCase(backendId)
|| "ads-truststore".equalsIgnoreCase(backendId);
}
/**
* Returns whether the provided ID corresponds to the schema backend.
* @param backendId the backend ID to analyze
* @return {@code true} if the the id corresponds to the schema backend
* and {@code false} otherwise.
*/
private static boolean isSchemaBackend(String backendId)
{
return "schema".equalsIgnoreCase(backendId);
}
/**
* Returns the replication server normalized String for a given host name
* and replication port.
* @param hostName the host name.
* @param replicationPort the replication port.
* @return the replication server normalized String for a given host name
* and replication port.
*/
public static String getReplicationServer(String hostName, int replicationPort)
{
return HostPort.toString(hostName, replicationPort);
}
/**
* Returns a representation of a base DN for a set of servers.
* @param baseDN the base DN.
* @param servers the servers.
* @return a representation of a base DN for a set of servers.
*/
public static String getSuffixDisplay(DN baseDN, Set servers)
{
StringBuilder sb = new StringBuilder();
sb.append(baseDN);
for (ServerDescriptor server : servers)
{
sb.append(Constants.LINE_SEPARATOR).append(" ");
sb.append(server.getHostPort(true));
}
return sb.toString();
}
/**
* Tells whether the provided server descriptor represents the same server
* as this object.
* @param server the server to make the comparison.
* @return {@code true} if the provided server descriptor represents the same server
* as this object, {@code false} otherwise.
*/
public boolean isSameServer(ServerDescriptor server)
{
return getId().equals(server.getId());
}
@Override
public String toString()
{
final int defaultPort = -1;
final int adminPort = getAdminPort(defaultPort);
final int ldapPort = getLdapPort(defaultPort);
final int ldapsPort = getLdapsPort(defaultPort);
final boolean isRs = isReplicationServer();
StringBuilder sb = new StringBuilder(getClass().getSimpleName());
sb.append("(host-name=").append(getHostName());
if (adminPort != defaultPort)
{
sb.append(", adminPort=").append(adminPort);
}
if (ldapPort != defaultPort)
{
sb.append(", ldapPort=").append(ldapPort);
}
if (ldapsPort != defaultPort)
{
sb.append(", ldapsPort=").append(ldapsPort);
}
sb.append(", isReplicationServer=").append(isRs);
if (isRs)
{
sb.append(", replication-server-id=").append(getReplicationServerId());
}
appendInconsistencies(sb);
sb.append(")");
return sb.toString();
}
private void appendInconsistencies(StringBuilder sb)
{
Map> inconsistencies = new HashMap<>();
for (ServerProperty prop : ServerProperty.values())
{
if (prop.adsEquivalent != null)
{
Object propVal = toScalar(serverProperties.get(prop));
Object propValue = propVal instanceof byte[] ? (byte[]) propVal : toStringValue(propVal);
Object adsPropValue = adsProperties.get(prop.adsEquivalent);
if (!Objects.equals(propValue, adsPropValue))
{
inconsistencies.put(prop, Pair.of(propValue, adsPropValue));
}
}
}
if (!inconsistencies.isEmpty())
{
sb.append(", inconsistencies=").append(inconsistencies);
}
}
private Object toScalar(Object propValue)
{
if (propValue instanceof List)
{
List> propValues = (List>) propValue;
return !propValues.isEmpty() ? propValues.get(0) : null;
}
return propValue;
}
private String toStringValue(Object propValue)
{
return propValue != null ? propValue.toString() : null;
}
}