/*
* 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 2008-2010 Sun Microsystems, Inc.
* Portions Copyright 2011 ForgeRock AS
*/
package org.opends.server.util.args;
import org.opends.messages.Message;
import static org.opends.messages.ToolMessages.*;
import org.opends.server.tools.LDAPConnection;
import org.opends.server.tools.LDAPConnectionOptions;
import org.opends.server.tools.SSLConnectionFactory;
import org.opends.server.tools.SSLConnectionException;
import org.opends.server.tools.LDAPConnectionException;
import static org.opends.server.util.ServerConstants.MAX_LINE_WIDTH;
import static org.opends.server.util.StaticUtils.wrapText;
import org.opends.server.util.cli.LDAPConnectionConsoleInteraction;
import org.opends.server.admin.client.cli.SecureConnectionCliArgs;
import org.opends.server.types.OpenDsException;
import java.util.LinkedList;
import java.util.LinkedHashSet;
import java.util.concurrent.atomic.AtomicInteger;
import java.io.PrintStream;
import javax.net.ssl.SSLException;
import org.opends.server.util.PasswordReader;
/**
* Creates an argument parser pre-populated with arguments for specifying
* information for openning and LDAPConnection an LDAP connection.
*/
public class LDAPConnectionArgumentParser extends ArgumentParser {
private SecureConnectionCliArgs args;
/**
* Creates a new instance of this argument parser with no arguments.
* Unnamed trailing arguments will not be allowed.
*
* @param mainClassName The fully-qualified name of the Java
* class that should be invoked to launch
* the program with which this argument
* parser is associated.
* @param toolDescription A human-readable description for the
* tool, which will be included when
* displaying usage information.
* @param longArgumentsCaseSensitive Indicates whether long arguments should
* @param argumentGroup Group to which LDAP arguments will be
* added to the parser. May be null to
* indicate that arguments should be
* added to the default group
* @param alwaysSSL If true, always use the SSL connection type. In this case,
* the arguments useSSL and startTLS are not present.
*/
public LDAPConnectionArgumentParser(String mainClassName,
Message toolDescription,
boolean longArgumentsCaseSensitive,
ArgumentGroup argumentGroup,
boolean alwaysSSL) {
super(mainClassName, toolDescription, longArgumentsCaseSensitive);
addLdapConnectionArguments(argumentGroup, alwaysSSL);
}
/**
* Creates a new instance of this argument parser with no arguments that may
* or may not be allowed to have unnamed trailing arguments.
*
* @param mainClassName The fully-qualified name of the Java
* class that should be invoked to launch
* the program with which this argument
* parser is associated.
* @param toolDescription A human-readable description for the
* tool, which will be included when
* displaying usage information.
* @param longArgumentsCaseSensitive Indicates whether long arguments should
* be treated in a case-sensitive manner.
* @param allowsTrailingArguments Indicates whether this parser allows
* unnamed trailing arguments to be
* provided.
* @param minTrailingArguments The minimum number of unnamed trailing
* arguments that must be provided. A
* value less than or equal to zero
* indicates that no minimum will be
* enforced.
* @param maxTrailingArguments The maximum number of unnamed trailing
* arguments that may be provided. A
* value less than or equal to zero
* indicates that no maximum will be
* enforced.
* @param trailingArgsDisplayName The display name that should be used
* as a placeholder for unnamed trailing
* arguments in the generated usage
* information.
* @param argumentGroup Group to which LDAP arguments will be
* added to the parser. May be null to
* indicate that arguments should be
* added to the default group
* @param alwaysSSL If true, always use the SSL connection type. In this case,
* the arguments useSSL and startTLS are not present.
*/
public LDAPConnectionArgumentParser(String mainClassName,
Message toolDescription,
boolean longArgumentsCaseSensitive,
boolean allowsTrailingArguments,
int minTrailingArguments,
int maxTrailingArguments,
String trailingArgsDisplayName,
ArgumentGroup argumentGroup,
boolean alwaysSSL) {
super(mainClassName, toolDescription, longArgumentsCaseSensitive,
allowsTrailingArguments, minTrailingArguments, maxTrailingArguments,
trailingArgsDisplayName);
addLdapConnectionArguments(argumentGroup, alwaysSSL);
}
/**
* Indicates whether or not the user has indicated that they would like
* to perform a remote operation based on the arguments.
*
* @return true if the user wants to perform a remote operation;
* false otherwise
*/
public boolean connectionArgumentsPresent() {
return args != null && args.argumentsPresent();
}
/**
* Creates a new LDAPConnection and invokes a connect operation using
* information provided in the parsed set of arguments that were provided
* by the user.
*
* @param out stream to write messages
* @param err stream to write error messages
* @return LDAPConnection created by this class from parsed arguments
* @throws LDAPConnectionException if there was a problem connecting
* to the server indicated by the input arguments
* @throws ArgumentException if there was a problem processing the input
* arguments
*/
public LDAPConnection connect(PrintStream out, PrintStream err)
throws LDAPConnectionException, ArgumentException
{
return connect(this.args, out, err);
}
/**
* Creates a new LDAPConnection and invokes a connect operation using
* information provided in the parsed set of arguments that were provided
* by the user.
*
* @param args with which to connect
* @param out stream to write messages
* @param err stream to write error messages
* @return LDAPConnection created by this class from parsed arguments
* @throws LDAPConnectionException if there was a problem connecting
* to the server indicated by the input arguments
* @throws ArgumentException if there was a problem processing the input
* arguments
*/
private LDAPConnection connect(SecureConnectionCliArgs args,
PrintStream out, PrintStream err)
throws LDAPConnectionException, ArgumentException
{
// If both a bind password and bind password file were provided, then return
// an error.
if (args.bindPasswordArg.isPresent() &&
args.bindPasswordFileArg.isPresent())
{
Message message = ERR_LDAP_CONN_MUTUALLY_EXCLUSIVE_ARGUMENTS.get(
args.bindPasswordArg.getLongIdentifier(),
args.bindPasswordFileArg.getLongIdentifier());
err.println(wrapText(message, MAX_LINE_WIDTH));
throw new ArgumentException(message);
}
// If both a key store password and key store password file were provided,
// then return an error.
if (args.keyStorePasswordArg.isPresent() &&
args.keyStorePasswordFileArg.isPresent())
{
Message message = ERR_LDAP_CONN_MUTUALLY_EXCLUSIVE_ARGUMENTS.get(
args.keyStorePasswordArg.getLongIdentifier(),
args.keyStorePasswordFileArg.getLongIdentifier());
throw new ArgumentException(message);
}
// If both a trust store password and trust store password file were
// provided, then return an error.
if (args.trustStorePasswordArg.isPresent() &&
args.trustStorePasswordFileArg.isPresent())
{
Message message = ERR_LDAP_CONN_MUTUALLY_EXCLUSIVE_ARGUMENTS.get(
args.trustStorePasswordArg.getLongIdentifier(),
args.trustStorePasswordFileArg.getLongIdentifier());
err.println(wrapText(message, MAX_LINE_WIDTH));
throw new ArgumentException(message);
}
// Create the LDAP connection options object, which will be used to
// customize the way that we connect to the server and specify a set of
// basic defaults.
LDAPConnectionOptions connectionOptions = new LDAPConnectionOptions();
connectionOptions.setVersionNumber(3);
// See if we should use SSL or StartTLS when establishing the connection.
// If so, then make sure only one of them was specified.
if (args.useSSLArg.isPresent())
{
if (args.useStartTLSArg.isPresent())
{
Message message = ERR_LDAP_CONN_MUTUALLY_EXCLUSIVE_ARGUMENTS.get(
args.useSSLArg.getLongIdentifier(),
args.useSSLArg.getLongIdentifier());
err.println(wrapText(message, MAX_LINE_WIDTH));
throw new ArgumentException(message);
}
else
{
connectionOptions.setUseSSL(true);
}
}
else if (args.useStartTLSArg.isPresent())
{
connectionOptions.setStartTLS(true);
}
// If we should blindly trust any certificate, then install the appropriate
// SSL connection factory.
if (args.useSSLArg.isPresent() || args.useStartTLSArg.isPresent())
{
try
{
String clientAlias;
if (args.certNicknameArg.isPresent())
{
clientAlias = args.certNicknameArg.getValue();
}
else
{
clientAlias = null;
}
SSLConnectionFactory sslConnectionFactory = new SSLConnectionFactory();
sslConnectionFactory.init(args.trustAllArg.isPresent(),
args.keyStorePathArg.getValue(),
args.keyStorePasswordArg.getValue(),
clientAlias,
args.trustStorePathArg.getValue(),
args.trustStorePasswordArg.getValue());
connectionOptions.setSSLConnectionFactory(sslConnectionFactory);
}
catch (SSLConnectionException sce)
{
Message message =
ERR_LDAP_CONN_CANNOT_INITIALIZE_SSL.get(sce.getMessage());
err.println(wrapText(message, MAX_LINE_WIDTH));
}
}
// If one or more SASL options were provided, then make sure that one of
// them was "mech" and specified a valid SASL mechanism.
if (args.saslOptionArg.isPresent())
{
String mechanism = null;
LinkedList<String> options = new LinkedList<String>();
for (String s : args.saslOptionArg.getValues())
{
int equalPos = s.indexOf('=');
if (equalPos <= 0)
{
Message message = ERR_LDAP_CONN_CANNOT_PARSE_SASL_OPTION.get(s);
err.println(wrapText(message, MAX_LINE_WIDTH));
throw new ArgumentException(message);
}
else
{
String name = s.substring(0, equalPos);
if (name.equalsIgnoreCase("mech"))
{
mechanism = s;
}
else
{
options.add(s);
}
}
}
if (mechanism == null)
{
Message message = ERR_LDAP_CONN_NO_SASL_MECHANISM.get();
err.println(wrapText(message, MAX_LINE_WIDTH));
throw new ArgumentException(message);
}
connectionOptions.setSASLMechanism(mechanism);
for (String option : options)
{
connectionOptions.addSASLProperty(option);
}
}
int timeout = args.connectTimeoutArg.getIntValue();
return connect(
args.hostNameArg.getValue(),
args.portArg.getIntValue(),
args.bindDnArg.getValue(),
getPasswordValue(args.bindPasswordArg,
args.bindPasswordFileArg,
args.bindDnArg, out, err),
connectionOptions, timeout, out, err);
}
/**
* Creates a connection using a console interaction that will be used
* to potientially interact with the user to prompt for necessary
* information for establishing the connection.
*
* @param ui user interaction for prompting the user
* @param out stream to write messages
* @param err stream to write error messages
* @return LDAPConnection created by this class from parsed arguments
* @throws LDAPConnectionException if there was a problem connecting
* to the server indicated by the input arguments
*/
public LDAPConnection connect(LDAPConnectionConsoleInteraction ui,
PrintStream out, PrintStream err)
throws LDAPConnectionException
{
LDAPConnection connection = null;
try {
ui.run();
LDAPConnectionOptions options = new LDAPConnectionOptions();
options.setVersionNumber(3);
connection = connect(
ui.getHostName(),
ui.getPortNumber(),
ui.getBindDN(),
ui.getBindPassword(),
ui.populateLDAPOptions(options),
ui.getConnectTimeout(),
out, err);
} catch (OpenDsException e) {
if ((e.getCause() != null) && (e.getCause().getCause() != null) &&
e.getCause().getCause() instanceof SSLException) {
err.println(ERR_TASKINFO_LDAP_EXCEPTION_SSL.get(ui.getHostName(),
String.valueOf(ui.getPortNumber())));
} else {
err.println(e.getMessageObject());
}
}
return connection;
}
/**
* Creates a connection from information provided.
*
* @param host of the server
* @param port of the server
* @param bindDN with which to connect
* @param bindPw with which to connect
* @param options with which to connect
* @param out stream to write messages
* @param err stream to write error messages
* @return LDAPConnection created by this class from parsed arguments
* @throws LDAPConnectionException if there was a problem connecting
* to the server indicated by the input arguments
*/
public LDAPConnection connect(String host, int port,
String bindDN, String bindPw,
LDAPConnectionOptions options,
PrintStream out,
PrintStream err)
throws LDAPConnectionException
{
return connect(host, port, bindDN, bindPw, options, 0, out, err);
}
/**
* Creates a connection from information provided.
*
* @param host of the server
* @param port of the server
* @param bindDN with which to connect
* @param bindPw with which to connect
* @param options with which to connect
* @param timeout the timeout to establish the connection in milliseconds.
* Use {@code 0} to express no timeout
* @param out stream to write messages
* @param err stream to write error messages
* @return LDAPConnection created by this class from parsed arguments
* @throws LDAPConnectionException if there was a problem connecting
* to the server indicated by the input arguments
*/
public LDAPConnection connect(String host, int port,
String bindDN, String bindPw,
LDAPConnectionOptions options,
int timeout,
PrintStream out,
PrintStream err)
throws LDAPConnectionException
{
// Attempt to connect and authenticate to the Directory Server.
AtomicInteger nextMessageID = new AtomicInteger(1);
LDAPConnection connection = new LDAPConnection(
host, port, options, out, err);
connection.connectToHost(bindDN, bindPw, nextMessageID, timeout);
return connection;
}
/**
* Gets the arguments associated with this parser.
*
* @return arguments for this parser.
*/
public SecureConnectionCliArgs getArguments() {
return args;
}
/**
* Commodity method that retrieves the password value analyzing the contents
* of a string argument and of a file based argument. It assumes that the
* arguments have already been parsed and validated.
* If the string is a dash, or no password is available, it will prompt for
* it on the command line.
*
* @param bindPwdArg the string argument for the password.
* @param bindPwdFileArg the file based argument for the password.
* @param bindDnArg the string argument for the bindDN.
* @param out stream to write message.
* @param err stream to write error message.
* @return the password value.
*/
public static String getPasswordValue(StringArgument bindPwdArg,
FileBasedArgument bindPwdFileArg,
StringArgument bindDnArg,
PrintStream out,
PrintStream err)
{
String pwd = bindPwdArg.getValue();
String bindDN = bindDnArg.getValue();
if(pwd != null && pwd.equals("-") ||
(!bindPwdFileArg.isPresent() &&
(bindDN != null && pwd == null)))
{
// read the password from the stdin.
try
{
out.print(INFO_LDAPAUTH_PASSWORD_PROMPT.get(bindDN));
char[] pwChars = PasswordReader.readPassword();
pwd = new String(pwChars);
//As per rfc 4513(section-5.1.2) a client should avoid sending
//an empty password to the server.
while(pwChars.length ==0)
{
err.println(wrapText(
INFO_LDAPAUTH_NON_EMPTY_PASSWORD.get(),
MAX_LINE_WIDTH));
out.print(INFO_LDAPAUTH_PASSWORD_PROMPT.get(bindDN));
pwChars = PasswordReader.readPassword();
}
pwd = new String(pwChars);
} catch(Exception ex)
{
err.println(wrapText(ex.getMessage(), MAX_LINE_WIDTH));
return null;
}
}
else if (pwd == null) {
pwd = bindPwdFileArg.getValue();
}
return pwd;
}
private void addLdapConnectionArguments(ArgumentGroup argGroup,
boolean alwaysSSL) {
args = new SecureConnectionCliArgs(alwaysSSL);
try {
LinkedHashSet<Argument> argSet = args.createGlobalArguments();
for (Argument arg : argSet) {
addArgument(arg, argGroup);
}
}
catch (ArgumentException ae) {
ae.printStackTrace(); // Should never happen
}
}
}