/*
* 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
*
*
* Copyright 2006-2010 Sun Microsystems, Inc.
* Portions copyright 2011-2012 ForgeRock AS
*/
package org.opends.server.admin;
import static org.opends.server.loggers.ErrorLogger.logError;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.messages.AdminMessages.*;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.naming.ldap.Rdn;
import org.opends.messages.Message;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.server.ServerManagementContext;
import org.opends.server.admin.std.meta.LDAPConnectionHandlerCfgDefn.
SSLClientAuthPolicy;
import org.opends.server.admin.std.server.AdministrationConnectorCfg;
import org.opends.server.admin.std.server.ConnectionHandlerCfg;
import org.opends.server.admin.std.server.KeyManagerProviderCfg;
import org.opends.server.admin.std.server.FileBasedKeyManagerProviderCfg;
import org.opends.server.admin.std.server.FileBasedTrustManagerProviderCfg;
import org.opends.server.admin.std.server.LDAPConnectionHandlerCfg;
import org.opends.server.admin.std.server.RootCfg;
import org.opends.server.config.ConfigException;
import org.opends.server.core.SynchronousStrategy;
import org.opends.server.protocols.ldap.LDAPConnectionHandler;
import org.opends.server.types.AddressMask;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DN;
import org.opends.server.types.InitializationException;
import org.opends.server.types.ResultCode;
import org.opends.server.util.CertificateManager;
import org.opends.server.util.SetupUtils;
import org.opends.server.admin.std.server.TrustManagerProviderCfg;
import org.opends.server.core.DirectoryServer;
import org.opends.server.loggers.ErrorLogger;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.FilePermission;
/**
* This class is a wrapper on top of LDAPConnectionHandler to manage
* the administration connector, which is an LDAPConnectionHandler
* with specific (limited) configuration properties.
*/
public final class AdministrationConnector implements
ConfigurationChangeListener<AdministrationConnectorCfg>
{
/**
* Default Administration Connector port.
*/
public static final int DEFAULT_ADMINISTRATION_CONNECTOR_PORT = 4444;
/**
* Validity (in days) of the generated certificate.
*/
public static final int ADMIN_CERT_VALIDITY = 20 * 365;
// Friendly name of the administration connector
private static final String FRIENDLY_NAME = "Administration Connector";
// The tracer object for the debug logger.
private static final DebugTracer TRACER = getTracer();
private LDAPConnectionHandler adminConnectionHandler;
private AdministrationConnectorCfg config; //
// Predefined values for Administration Connector configuration
//
private static final String ADMIN_CLASS_NAME =
"org.opends.server.protocols.ldap.LDAPConnectionHandler";
private static final boolean ADMIN_ALLOW_LDAP_V2 = false;
private static final boolean ADMIN_ALLOW_START_TLS = false;
private static final SortedSet<AddressMask> ADMIN_ALLOWED_CLIENT =
new TreeSet<AddressMask>();
private static final SortedSet<AddressMask> ADMIN_DENIED_CLIENT =
new TreeSet<AddressMask>();
private static final boolean ADMIN_ENABLED = true;
private static final boolean ADMIN_KEEP_STATS = true;
private static final boolean ADMIN_USE_SSL = true;
private static final int ADMIN_ACCEPT_BACKLOG = 128;
private static final boolean ADMIN_ALLOW_TCP_REUSE_ADDRESS = true;
private static final long ADMIN_MAX_BLOCKED_WRITE_TIME_LIMIT = 120000; // 2mn
private static final int ADMIN_MAX_REQUEST_SIZE = 5000000; // 5 Mb
private static final int ADMIN_WRITE_BUFFER_SIZE = 4096;
private static final int ADMIN_NUM_REQUEST_HANDLERS = 1;
private static final boolean ADMIN_SEND_REJECTION_NOTICE = true;
private static final boolean ADMIN_USE_TCP_KEEP_ALIVE = true;
private static final boolean ADMIN_USE_TCP_NO_DELAY = true;
private static final SSLClientAuthPolicy ADMIN_SSL_CLIENT_AUTH_POLICY =
SSLClientAuthPolicy.DISABLED;
private static final SortedSet<String> ADMIN_SSL_CIPHER_SUITE =
new TreeSet<String>();
private static final SortedSet<String> ADMIN_SSL_PROTOCOL =
new TreeSet<String>();
/**
* Initializes this administration connector provider based on the
* information in the provided administration connector
* configuration.
*
* @param configuration
* The connection handler configuration that contains the
* information to use to initialize this connection
* handler.
* @throws ConfigException
* If an unrecoverable problem arises in the process of
* performing the initialization as a result of the server
* configuration.
* @throws InitializationException
* If a problem occurs during initialization that is not
* related to the server configuration.
*/
public void initializeAdministrationConnector(
AdministrationConnectorCfg configuration) throws ConfigException,
InitializationException
{
this.config = configuration;
// Create a fake LDAP connection handler configuration
LDAPConnectionHandlerCfg ldapConnectionHandlerCfg =
new FakeLDAPConnectionHandlerCfg(config);
// Administration Connector uses the LDAP connection handler
// implementation
adminConnectionHandler = new LDAPConnectionHandler(
new SynchronousStrategy(), FRIENDLY_NAME);
adminConnectionHandler
.initializeConnectionHandler(ldapConnectionHandlerCfg);
adminConnectionHandler.setAdminConnectionHandler();
// Register this as a change listener.
config.addChangeListener(this);
}
/**
* Create an instance of the administration connector.
*/
public AdministrationConnector()
{
// Do nothing.
}
/**
* Retrieves the connection handler linked to this administration
* connector.
*
* @return The connection handler linked to this administration
* connector.
*/
public LDAPConnectionHandler getConnectionHandler()
{
return adminConnectionHandler;
}
/**
* {@inheritDoc}
*/
public boolean isConfigurationChangeAcceptable(
AdministrationConnectorCfg configuration,
List<Message> unacceptableReasons)
{
LDAPConnectionHandlerCfg cfg = new FakeLDAPConnectionHandlerCfg(
configuration);
return adminConnectionHandler.isConfigurationAcceptable(cfg,
unacceptableReasons);
}
/**
* {@inheritDoc}
*/
public ConfigChangeResult applyConfigurationChange(
AdministrationConnectorCfg configuration)
{
return new ConfigChangeResult(ResultCode.SUCCESS, true,
new ArrayList<Message>());
}
/**
* This private class implements a fake LDAP connection Handler
* configuration. This allows to re-use the LDAPConnectionHandler as
* it is.
*/
private static class FakeLDAPConnectionHandlerCfg implements
LDAPConnectionHandlerCfg
{
private final AdministrationConnectorCfg config;
public FakeLDAPConnectionHandlerCfg(AdministrationConnectorCfg config)
{
this.config = config;
}
/**
* {@inheritDoc}
*/
public Class<? extends LDAPConnectionHandlerCfg> configurationClass()
{
return LDAPConnectionHandlerCfg.class;
}
/**
* {@inheritDoc}
*/
public void addLDAPChangeListener(
ConfigurationChangeListener<LDAPConnectionHandlerCfg> listener)
{
// do nothing. change listener already added.
}
/**
* {@inheritDoc}
*/
public void removeLDAPChangeListener(
ConfigurationChangeListener<LDAPConnectionHandlerCfg> listener)
{
// do nothing. change listener already added.
}
/**
* {@inheritDoc}
*/
public int getAcceptBacklog()
{
return ADMIN_ACCEPT_BACKLOG;
}
/**
* {@inheritDoc}
*/
public boolean isAllowLDAPV2()
{
return ADMIN_ALLOW_LDAP_V2;
}
/**
* {@inheritDoc}
*/
public boolean isAllowStartTLS()
{
return ADMIN_ALLOW_START_TLS;
}
/**
* {@inheritDoc}
*/
public boolean isAllowTCPReuseAddress()
{
return ADMIN_ALLOW_TCP_REUSE_ADDRESS;
}
/**
* {@inheritDoc}
*/
public String getJavaClass()
{
return ADMIN_CLASS_NAME;
}
/**
* {@inheritDoc}
*/
public boolean isKeepStats()
{
return ADMIN_KEEP_STATS;
}
/**
* {@inheritDoc}
*/
public String getKeyManagerProvider()
{
return config.getKeyManagerProvider();
}
/**
* {@inheritDoc}
*/
public DN getKeyManagerProviderDN()
{
return config.getKeyManagerProviderDN();
}
/**
* {@inheritDoc}
*/
public SortedSet<InetAddress> getListenAddress()
{
return config.getListenAddress();
}
/**
* {@inheritDoc}
*/
public int getListenPort()
{
return config.getListenPort();
}
/**
* {@inheritDoc}
*/
public long getMaxBlockedWriteTimeLimit()
{
return ADMIN_MAX_BLOCKED_WRITE_TIME_LIMIT;
}
/**
* {@inheritDoc}
*/
public long getMaxRequestSize()
{
return ADMIN_MAX_REQUEST_SIZE;
}
/**
* {@inheritDoc}
*/
public long getBufferSize()
{
return ADMIN_WRITE_BUFFER_SIZE;
}
/**
* {@inheritDoc}
*/
public Integer getNumRequestHandlers()
{
return ADMIN_NUM_REQUEST_HANDLERS;
}
/**
* {@inheritDoc}
*/
public boolean isSendRejectionNotice()
{
return ADMIN_SEND_REJECTION_NOTICE;
}
/**
* {@inheritDoc}
*/
public String getSSLCertNickname()
{
return config.getSSLCertNickname();
}
/**
* {@inheritDoc}
*/
public SortedSet<String> getSSLCipherSuite()
{
return config.getSSLCipherSuite();
}
/**
* {@inheritDoc}
*/
public SSLClientAuthPolicy getSSLClientAuthPolicy()
{
return ADMIN_SSL_CLIENT_AUTH_POLICY;
}
/**
* {@inheritDoc}
*/
public SortedSet<String> getSSLProtocol()
{
return config.getSSLProtocol();
}
/**
* {@inheritDoc}
*/
public String getTrustManagerProvider()
{
return config.getTrustManagerProvider();
}
/**
* {@inheritDoc}
*/
public DN getTrustManagerProviderDN()
{
return config.getTrustManagerProviderDN();
}
/**
* {@inheritDoc}
*/
public boolean isUseSSL()
{
return ADMIN_USE_SSL;
}
/**
* {@inheritDoc}
*/
public boolean isUseTCPKeepAlive()
{
return ADMIN_USE_TCP_KEEP_ALIVE;
}
/**
* {@inheritDoc}
*/
public boolean isUseTCPNoDelay()
{
return ADMIN_USE_TCP_NO_DELAY;
}
/**
* {@inheritDoc}
*/
public void addChangeListener(
ConfigurationChangeListener<ConnectionHandlerCfg> listener)
{
// do nothing. change listener already added.
}
/**
* {@inheritDoc}
*/
public void removeChangeListener(
ConfigurationChangeListener<ConnectionHandlerCfg> listener)
{
// do nothing. change listener already added.
}
/**
* {@inheritDoc}
*/
public SortedSet<AddressMask> getAllowedClient()
{
return ADMIN_ALLOWED_CLIENT;
}
/**
* {@inheritDoc}
*/
public SortedSet<AddressMask> getDeniedClient()
{
return ADMIN_DENIED_CLIENT;
}
/**
* {@inheritDoc}
*/
public boolean isEnabled()
{
return ADMIN_ENABLED;
}
/**
* {@inheritDoc}
*/
public DN dn()
{
return config.dn();
}
}
/**
* Creates a self-signed JKS certificate if needed.
*
* @throws InitializationException
* If an unexpected error occurred whilst trying to create the
* certificate.
*/
public static void createSelfSignedCertificateIfNeeded()
throws InitializationException
{
try
{
RootCfg root = ServerManagementContext.getInstance()
.getRootConfiguration();
AdministrationConnectorCfg config = root.getAdministrationConnector();
// Check if certificate generation is needed
String certAlias = config.getSSLCertNickname();
KeyManagerProviderCfg keyMgrConfig = root.getKeyManagerProvider(config
.getKeyManagerProvider());
TrustManagerProviderCfg trustMgrConfig = root
.getTrustManagerProvider(config.getTrustManagerProvider());
if (!(keyMgrConfig instanceof FileBasedKeyManagerProviderCfg)
|| !(trustMgrConfig instanceof FileBasedTrustManagerProviderCfg))
{
// The default config has been changed, nothing to do
return;
}
FileBasedKeyManagerProviderCfg fbKeyManagerConfig =
(FileBasedKeyManagerProviderCfg) keyMgrConfig;
String keystorePath = getFullPath(fbKeyManagerConfig.getKeyStoreFile());
FileBasedTrustManagerProviderCfg fbTrustManagerConfig =
(FileBasedTrustManagerProviderCfg) trustMgrConfig;
String truststorePath = getFullPath(fbTrustManagerConfig
.getTrustStoreFile());
String pinFilePath = getFullPath(fbKeyManagerConfig.getKeyStorePinFile());
// Check that either we do not have any file,
// or we have the 3 required files (keystore, truststore, pin
// file)
boolean keystore = false;
boolean truststore = false;
boolean pinFile = false;
int nbFiles = 0;
if (new File(keystorePath).exists())
{
keystore = true;
nbFiles++;
}
if (new File(truststorePath).exists())
{
truststore = true;
nbFiles++;
}
if (new File(pinFilePath).exists())
{
pinFile = true;
nbFiles++;
}
if (nbFiles == 3)
{
// nothing to do
return;
}
if (nbFiles != 0)
{
// 1 or 2 files are missing : error
String err = "";
if (!keystore)
{
err += keystorePath + " ";
}
if (!truststore)
{
err += truststorePath + " ";
}
if (!pinFile)
{
err += pinFilePath + " ";
}
Message message = ERR_ADMIN_CERTIFICATE_GENERATION_MISSING_FILES
.get(err);
logError(message);
throw new InitializationException(message);
}
// Generate a password
String pwd = new String(SetupUtils.createSelfSignedCertificatePwd());
// Generate a self-signed certificate
CertificateManager certManager = new CertificateManager(
getFullPath(fbKeyManagerConfig.getKeyStoreFile()), fbKeyManagerConfig
.getKeyStoreType(), pwd);
String hostName =
SetupUtils.getHostNameForCertificate(DirectoryServer.getServerRoot());
String subjectDN = "cn="
+ Rdn.escapeValue(hostName) + ",O="
+ FRIENDLY_NAME + " Self-Signed Certificate";
certManager.generateSelfSignedCertificate(certAlias, subjectDN,
ADMIN_CERT_VALIDITY);
// Export the certificate
String tempCertPath = getFullPath("config" + File.separator
+ "admin-cert.txt");
SetupUtils.exportCertificate(certManager, certAlias, tempCertPath);
// Create a new trust store and import the server certificate
// into it
CertificateManager trustManager = new CertificateManager(truststorePath,
CertificateManager.KEY_STORE_TYPE_JKS, pwd);
trustManager.addCertificate(certAlias, new File(tempCertPath));
// Generate a password file
if (!new File(pinFilePath).exists())
{
FileWriter file = new FileWriter(pinFilePath);
PrintWriter out = new PrintWriter(file);
out.println(pwd);
out.flush();
out.close();
file.close();
}
// Change the password file permission if possible
if (FilePermission.canSetPermissions())
{
try
{
if (!FilePermission.setPermissions(new File(pinFilePath),
new FilePermission(0600)))
{
// Log a warning that the permissions were not set.
Message message = WARN_ADMIN_SET_PERMISSIONS_FAILED
.get(pinFilePath);
ErrorLogger.logError(message);
}
}
catch (DirectoryException e)
{
// Log a warning that the permissions were not set.
Message message = WARN_ADMIN_SET_PERMISSIONS_FAILED.get(pinFilePath);
ErrorLogger.logError(message);
}
}
// Delete the exported certificate
File f = new File(tempCertPath);
f.delete();
}
catch (InitializationException e)
{
throw e;
}
catch (Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
Message message = ERR_ADMIN_CERTIFICATE_GENERATION.get(e.getMessage());
logError(message);
throw new InitializationException(message);
}
}
private static String getFullPath(String path)
{
File file = new File(path);
if (!file.isAbsolute())
{
path = DirectoryServer.getInstanceRoot() + File.separator + path;
}
return path;
}
}