/* * 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 2008-2010 Sun Microsystems, Inc. * Portions Copyright 2011-2015 ForgeRock AS */ package org.opends.server.util.args; import static org.opends.messages.ToolMessages.*; import static com.forgerock.opendj.cli.Utils.*; import java.io.PrintStream; import java.util.LinkedList; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import javax.net.ssl.SSLException; import org.forgerock.i18n.LocalizableMessage; import org.opends.server.admin.client.cli.SecureConnectionCliArgs; import org.opends.server.core.DirectoryServer.DirectoryServerVersionHandler; import org.opends.server.tools.LDAPConnection; import org.opends.server.tools.LDAPConnectionException; import org.opends.server.tools.LDAPConnectionOptions; import org.opends.server.tools.SSLConnectionException; import org.opends.server.tools.SSLConnectionFactory; import org.opends.server.types.OpenDsException; import org.opends.server.util.cli.LDAPConnectionConsoleInteraction; import com.forgerock.opendj.cli.Argument; import com.forgerock.opendj.cli.ArgumentException; import com.forgerock.opendj.cli.ArgumentGroup; import com.forgerock.opendj.cli.ArgumentParser; import com.forgerock.opendj.cli.ClientException; import com.forgerock.opendj.cli.ConsoleApplication; import com.forgerock.opendj.cli.FileBasedArgument; import com.forgerock.opendj.cli.StringArgument; /** * Creates an argument parser pre-populated with arguments for specifying * information for opening 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, LocalizableMessage toolDescription, boolean longArgumentsCaseSensitive, ArgumentGroup argumentGroup, boolean alwaysSSL) { super(mainClassName, toolDescription, longArgumentsCaseSensitive); addLdapConnectionArguments(argumentGroup, alwaysSSL); setVersionHandler(new DirectoryServerVersionHandler()); } /** * 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()) { printAndThrowException(err, ERR_LDAP_CONN_MUTUALLY_EXCLUSIVE_ARGUMENTS.get( args.bindPasswordArg.getLongIdentifier(), args.bindPasswordFileArg.getLongIdentifier())); } // If both a key store password and key store password file were provided, // then return an error. if (args.keyStorePasswordArg.isPresent() && args.keyStorePasswordFileArg.isPresent()) { printAndThrowException(err, ERR_LDAP_CONN_MUTUALLY_EXCLUSIVE_ARGUMENTS.get( args.keyStorePasswordArg.getLongIdentifier(), args.keyStorePasswordFileArg.getLongIdentifier())); } // If both a trust store password and trust store password file were // provided, then return an error. if (args.trustStorePasswordArg.isPresent() && args.trustStorePasswordFileArg.isPresent()) { printAndThrowException(err, ERR_LDAP_CONN_MUTUALLY_EXCLUSIVE_ARGUMENTS.get( args.trustStorePasswordArg.getLongIdentifier(), args.trustStorePasswordFileArg.getLongIdentifier())); } // 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()) { printAndThrowException(err, ERR_LDAP_CONN_MUTUALLY_EXCLUSIVE_ARGUMENTS.get( args.useSSLArg.getLongIdentifier(), args.useSSLArg.getLongIdentifier())); } 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) { printWrappedText(err, ERR_LDAP_CONN_CANNOT_INITIALIZE_SSL.get(sce.getMessage())); } } // 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<>(); for (String s : args.saslOptionArg.getValues()) { int equalPos = s.indexOf('='); if (equalPos <= 0) { printAndThrowException(err, ERR_LDAP_CONN_CANNOT_PARSE_SASL_OPTION.get(s)); } else { String name = s.substring(0, equalPos); if ("mech".equalsIgnoreCase(name)) { mechanism = s; } else { options.add(s); } } } if (mechanism == null) { printAndThrowException(err, ERR_LDAP_CONN_NO_SASL_MECHANISM.get()); } connectionOptions.setSASLMechanism(mechanism); for (String option : options) { connectionOptions.addSASLProperty(option); } } int timeout = args.connectTimeoutArg.getIntValue(); final String passwordValue = getPasswordValue( args.bindPasswordArg, args.bindPasswordFileArg, args.bindDnArg, out, err); return connect( args.hostNameArg.getValue(), args.portArg.getIntValue(), args.bindDnArg.getValue(), passwordValue, connectionOptions, timeout, out, err); } private void printAndThrowException(PrintStream err, LocalizableMessage message) throws ArgumentException { printWrappedText(err, message); throw new ArgumentException(message); } /** * Creates a connection using a console interaction that will be used to * potentially 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 * @throws ArgumentException * if there was a problem indicated by the input arguments */ public LDAPConnection connect(LDAPConnectionConsoleInteraction ui, PrintStream out, PrintStream err) throws LDAPConnectionException, ArgumentException { try { ui.run(); LDAPConnectionOptions options = new LDAPConnectionOptions(); options.setVersionNumber(3); return connect(ui.getHostName(), ui.getPortNumber(), ui.getBindDN(), ui.getBindPassword(), ui.populateLDAPOptions(options), ui.getConnectTimeout(), out, err); } catch (OpenDsException e) { err.println(isSSLException(e) ? ERR_TASKINFO_LDAP_EXCEPTION_SSL.get(ui.getHostName(), ui.getPortNumber()) : e.getMessageObject()); return null; } } private boolean isSSLException(Exception e) { return e.getCause() != null && e.getCause().getCause() != null && e.getCause().getCause() instanceof SSLException; } /** * 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) { try { return getPasswordValue(bindPwdArg, bindPwdFileArg, bindDnArg.getValue(), out, err); } catch (Exception ex) { printWrappedText(err, ex.getMessage()); return null; } } /** * 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 bindPassword * the string argument for the password. * @param bindPasswordFile * the file based argument for the password. * @param bindDNValue * the string value for the bindDN. * @param out * stream to write message. * @param err * stream to write error message. * @return the password value. * @throws ClientException * if the password cannot be read */ public static String getPasswordValue(StringArgument bindPassword, FileBasedArgument bindPasswordFile, String bindDNValue, PrintStream out, PrintStream err) throws ClientException { String bindPasswordValue = bindPassword.getValue(); if ("-".equals(bindPasswordValue) || (!bindPasswordFile.isPresent() && bindDNValue != null && bindPasswordValue == null)) { // read the password from the stdin. out.print(INFO_LDAPAUTH_PASSWORD_PROMPT.get(bindDNValue)); char[] pwChars = ConsoleApplication.readPassword(); // As per rfc 4513(section-5.1.2) a client should avoid sending // an empty password to the server. while (pwChars.length == 0) { printWrappedText(err, INFO_LDAPAUTH_NON_EMPTY_PASSWORD.get()); out.print(INFO_LDAPAUTH_PASSWORD_PROMPT.get(bindDNValue)); pwChars = ConsoleApplication.readPassword(); } return new String(pwChars); } else if (bindPasswordValue == null) { // Read from file if it exists. return bindPasswordFile.getValue(); } return bindPasswordValue; } private void addLdapConnectionArguments(ArgumentGroup argGroup, boolean alwaysSSL) { args = new SecureConnectionCliArgs(alwaysSSL); try { Set<Argument> argSet = args.createGlobalArguments(); for (Argument arg : argSet) { addArgument(arg, argGroup); } } catch (ArgumentException ae) { ae.printStackTrace(); // Should never happen } } }