/*
|
* 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 legal-notices/CDDLv1_0.txt
|
* or http://forgerock.org/license/CDDLv1.0.html.
|
* 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 legal-notices/CDDLv1_0.txt.
|
* 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 2007-2010 Sun Microsystems, Inc.
|
* Portions Copyright 2014 ForgeRock AS
|
*/
|
package org.opends.server.tools.dsconfig;
|
|
|
|
import static com.forgerock.opendj.cli.ArgumentConstants.OPTION_LONG_HELP;
|
import static com.forgerock.opendj.cli.ArgumentConstants.OPTION_SHORT_HELP;
|
import static org.opends.messages.DSConfigMessages.*;
|
import static org.forgerock.util.Utils.closeSilently;
|
|
import java.security.GeneralSecurityException;
|
import java.security.cert.CertificateException;
|
import java.security.cert.X509Certificate;
|
import java.util.LinkedHashSet;
|
import java.util.concurrent.TimeUnit;
|
|
import javax.naming.AuthenticationException;
|
import javax.net.ssl.KeyManager;
|
import javax.net.ssl.SSLException;
|
import javax.net.ssl.TrustManager;
|
|
import org.forgerock.i18n.LocalizableMessage;
|
import org.forgerock.i18n.LocalizableMessageBuilder;
|
import org.forgerock.opendj.config.client.ManagementContext;
|
import org.forgerock.opendj.config.server.ConfigException;
|
import org.forgerock.opendj.ldap.ErrorResultException;
|
import org.forgerock.opendj.config.client.ldap.LDAPManagementContext;
|
import org.forgerock.opendj.config.LDAPProfile;
|
import org.forgerock.opendj.ldap.AuthorizationException;
|
import org.forgerock.opendj.ldap.Connection;
|
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
|
import org.forgerock.opendj.ldap.LDAPOptions;
|
import org.forgerock.opendj.ldap.SSLContextBuilder;
|
import org.forgerock.opendj.ldap.TrustManagers;
|
import org.opends.admin.ads.util.ApplicationTrustManager;
|
import org.opends.admin.ads.util.ConnectionUtils;
|
import org.opends.server.admin.client.cli.SecureConnectionCliArgs;
|
import org.opends.server.tools.JavaPropertiesTool.ErrorReturnCode;
|
import org.opends.server.util.cli.LDAPConnectionConsoleInteraction;
|
|
import com.forgerock.opendj.cli.Argument;
|
import com.forgerock.opendj.cli.ArgumentException;
|
import com.forgerock.opendj.cli.ClientException;
|
import com.forgerock.opendj.cli.CommandBuilder;
|
import com.forgerock.opendj.cli.ConsoleApplication;
|
import com.forgerock.opendj.cli.ReturnCode;
|
import com.forgerock.opendj.cli.SubCommandArgumentParser;
|
|
|
/**
|
* An LDAP management context factory.
|
*/
|
public final class LDAPManagementContextFactory implements
|
ManagementContextFactory {
|
|
/** The SecureConnectionCliArgsList object. */
|
private SecureConnectionCliArgs secureArgsList = null;
|
|
/** The management context. */
|
private ManagementContext context = null;
|
|
/** The connection parameters command builder. */
|
private CommandBuilder contextCommandBuilder;
|
|
/** This CLI is always using the administration connector with SSL. */
|
private boolean alwaysSSL = false;
|
|
/** Raw arguments. */
|
private String[] rawArgs = null;
|
|
/**
|
* Creates a new LDAP management context factory.
|
*
|
* @param alwaysSSL If true, always use the SSL connection type. In this case,
|
* the arguments useSSL and startTLS are not present.
|
*/
|
public LDAPManagementContextFactory(boolean alwaysSSL) {
|
this.alwaysSSL = alwaysSSL;
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public ManagementContext getManagementContext(ConsoleApplication app)
|
throws ArgumentException, ClientException
|
{
|
// Lazily create the LDAP management context.
|
if (context == null)
|
{
|
LDAPConnectionConsoleInteraction ci =
|
new LDAPConnectionConsoleInteraction(app, secureArgsList);
|
ci.run();
|
context = getManagementContext(app, ci);
|
contextCommandBuilder = ci.getCommandBuilder();
|
}
|
return context;
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public void close()
|
{
|
closeSilently(context);
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public CommandBuilder getContextCommandBuilder()
|
{
|
return contextCommandBuilder;
|
}
|
|
/**
|
* Gets the management context which sub-commands should use in
|
* order to manage the directory server. Implementations can use the
|
* application instance for retrieving passwords interactively.
|
*
|
* @param app
|
* The application instance.
|
* @param ci the LDAPConsoleInteraction object to be used. The code assumes
|
* that the LDAPConsoleInteraction has already been run.
|
* @return Returns the management context which sub-commands should
|
* use in order to manage the directory server.
|
* @throws ArgumentException
|
* If a management context related argument could not be
|
* parsed successfully.
|
* @throws ClientException
|
* If the management context could not be created.
|
*/
|
public ManagementContext getManagementContext(ConsoleApplication app,
|
LDAPConnectionConsoleInteraction ci)
|
throws ArgumentException, ClientException
|
{
|
// Lazily create the LDAP management context.
|
if (context == null)
|
{
|
// Interact with the user though the console to get
|
// LDAP connection information
|
String hostName = ConnectionUtils.getHostNameForLdapUrl(ci.getHostName());
|
Integer portNumber = ci.getPortNumber();
|
String bindDN = ci.getBindDN();
|
String bindPassword = ci.getBindPassword();
|
TrustManager trustManager = ci.getTrustManager();
|
KeyManager keyManager = ci.getKeyManager();
|
|
// Do we have a secure connection ?
|
Connection connection;
|
final LDAPOptions options = new LDAPOptions();
|
options.setConnectTimeout(ci.getConnectTimeout(), TimeUnit.MILLISECONDS);
|
LDAPConnectionFactory factory = null;
|
if (ci.useSSL())
|
{
|
while (true)
|
{
|
try
|
{
|
final SSLContextBuilder sslBuilder = new SSLContextBuilder();
|
sslBuilder.setTrustManager((trustManager == null ? TrustManagers
|
.trustAll() : trustManager));
|
sslBuilder.setKeyManager(keyManager);
|
if (ci.useStartTLS())
|
{
|
options.setUseStartTLS(true);
|
}
|
else
|
{
|
options.setUseStartTLS(false);
|
}
|
options.setSSLContext(sslBuilder.getSSLContext());
|
|
factory = new LDAPConnectionFactory(hostName, portNumber, options);
|
connection = factory.getConnection();
|
connection.bind(bindDN, bindPassword.toCharArray());
|
break;
|
}
|
catch (ErrorResultException e)
|
{
|
if (app.isInteractive()
|
&& ci.isTrustStoreInMemory()
|
&& e.getCause() != null
|
&& e.getCause() instanceof SSLException
|
&& e.getCause().getCause() instanceof CertificateException)
|
{
|
String authType = null;
|
if (trustManager instanceof ApplicationTrustManager)
|
{ // FIXME use PromptingTrustManager
|
ApplicationTrustManager appTrustManager =
|
(ApplicationTrustManager) trustManager;
|
authType = appTrustManager.getLastRefusedAuthType();
|
X509Certificate[] cert = appTrustManager.getLastRefusedChain();
|
|
if (ci.checkServerCertificate(cert, authType, hostName))
|
{
|
// If the certificate is trusted, update the trust manager.
|
trustManager = ci.getTrustManager();
|
// Try to connect again.
|
continue;
|
}
|
}
|
}
|
if (e.getCause() instanceof SSLException)
|
{
|
LocalizableMessage message =
|
ERR_DSCFG_ERROR_LDAP_FAILED_TO_CONNECT_NOT_TRUSTED.get(
|
hostName, portNumber);
|
throw new ClientException(ReturnCode.CLIENT_SIDE_CONNECT_ERROR,
|
message);
|
}
|
if (e.getCause() instanceof AuthorizationException)
|
{
|
LocalizableMessage message =
|
ERR_DSCFG_ERROR_LDAP_SIMPLE_BIND_NOT_SUPPORTED.get();
|
throw new ClientException(ReturnCode.AUTH_METHOD_NOT_SUPPORTED,
|
message);
|
}
|
else if (e.getCause() instanceof AuthenticationException)
|
{
|
LocalizableMessage message =
|
ERR_DSCFG_ERROR_LDAP_SIMPLE_BIND_FAILED.get(bindDN);
|
throw new ClientException(ReturnCode.INVALID_CREDENTIALS, message);
|
}
|
LocalizableMessage message =
|
ERR_DSCFG_ERROR_LDAP_FAILED_TO_CONNECT
|
.get(hostName, portNumber);
|
throw new ClientException(ReturnCode.CLIENT_SIDE_CONNECT_ERROR,
|
message);
|
}
|
catch (GeneralSecurityException e)
|
{
|
LocalizableMessage message =
|
ERR_DSCFG_ERROR_LDAP_FAILED_TO_CONNECT
|
.get(hostName, portNumber);
|
throw new ClientException(ReturnCode.CLIENT_SIDE_CONNECT_ERROR,
|
message);
|
}
|
}
|
}
|
else
|
{ // FIXME The dsconfig is always using secure connection. This code can be
|
// removed in this case but statusCli and uninstall are also using it. Cleanup needed.
|
// Create the management context.
|
try
|
{
|
factory = new LDAPConnectionFactory(hostName, portNumber, options);
|
connection = factory.getConnection();
|
connection.bind(bindDN, bindPassword.toCharArray());
|
}
|
catch (ErrorResultException e)
|
{
|
if (e.getCause() instanceof AuthorizationException)
|
{
|
LocalizableMessage message =
|
ERR_DSCFG_ERROR_LDAP_SIMPLE_BIND_NOT_SUPPORTED.get();
|
throw new ClientException(ReturnCode.AUTH_METHOD_NOT_SUPPORTED,
|
message);
|
}
|
else if (e.getCause() instanceof AuthenticationException)
|
{
|
LocalizableMessage message =
|
ERR_DSCFG_ERROR_LDAP_SIMPLE_BIND_FAILED.get(bindDN);
|
throw new ClientException(ReturnCode.INVALID_CREDENTIALS, message);
|
}
|
LocalizableMessage message =
|
ERR_DSCFG_ERROR_LDAP_FAILED_TO_CONNECT.get(hostName, portNumber);
|
throw new ClientException(ReturnCode.CLIENT_SIDE_CONNECT_ERROR,
|
message);
|
}
|
finally
|
{
|
factory.close();
|
}
|
}
|
context =
|
LDAPManagementContext.newManagementContext(connection, LDAPProfile.getInstance());
|
}
|
return context;
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public void setRawArguments(String[] args) {
|
this.rawArgs = args;
|
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public void registerGlobalArguments(SubCommandArgumentParser parser)
|
throws ArgumentException {
|
// Create the global arguments.
|
secureArgsList = new SecureConnectionCliArgs(alwaysSSL);
|
LinkedHashSet<Argument> args = secureArgsList.createGlobalArguments();
|
|
|
// Register the global arguments.
|
for (Argument arg : args)
|
{
|
parser.addGlobalArgument(arg);
|
}
|
|
try
|
{
|
if (rawArgs != null) {
|
for (String rawArg : rawArgs) {
|
if (rawArg.length() < 2) {
|
// This is not a help command
|
continue;
|
}
|
if (rawArg.contains(OPTION_LONG_HELP) ||
|
rawArg.charAt(1) == OPTION_SHORT_HELP || rawArg.charAt(1) == '?') {
|
// used for usage help default values only
|
secureArgsList.initArgumentsWithConfiguration();
|
}
|
}
|
}
|
}
|
catch (ConfigException ce)
|
{
|
// Ignore.
|
}
|
}
|
|
|
|
/** {@inheritDoc} */
|
@Override
|
public void validateGlobalArguments() throws ArgumentException {
|
// Make sure that the user didn't specify any conflicting
|
// arguments.
|
LocalizableMessageBuilder buf = new LocalizableMessageBuilder();
|
int v = secureArgsList.validateGlobalOptions(buf);
|
if (v != ErrorReturnCode.SUCCESSFUL_NOP.getReturnCode())
|
{
|
throw new ArgumentException(buf.toMessage());
|
}
|
}
|
|
}
|