/* * 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-2013 ForgeRock AS * Portions Copyright 2011 profiq s.r.o. */ package org.opends.server.tools; import static org.opends.messages.AdminToolMessages.*; import static org.opends.messages.QuickSetupMessages.*; import static org.opends.messages.ToolMessages.*; import static org.opends.messages.UtilityMessages.*; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.security.KeyStoreException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.logging.Level; import java.util.logging.Logger; import javax.naming.ldap.LdapName; import org.opends.messages.Message; import org.opends.messages.QuickSetupMessages; import org.opends.messages.ToolMessages; import org.opends.quicksetup.ApplicationException; import org.opends.quicksetup.Constants; import org.opends.quicksetup.CurrentInstallStatus; import org.opends.quicksetup.Installation; import org.opends.quicksetup.LicenseFile; import org.opends.quicksetup.QuickSetupLog; import org.opends.quicksetup.SecurityOptions; import org.opends.quicksetup.UserData; import org.opends.quicksetup.UserDataException; import org.opends.quicksetup.event.ProgressUpdateEvent; import org.opends.quicksetup.event.ProgressUpdateListener; import org.opends.quicksetup.installer.NewSuffixOptions; import org.opends.quicksetup.installer.offline.OfflineInstaller; import org.opends.quicksetup.util.IncompatibleVersionException; import org.opends.quicksetup.util.PlainTextProgressMessageFormatter; import org.opends.quicksetup.util.Utils; import org.opends.server.types.InitializationException; import org.opends.server.types.NullOutputStream; import org.opends.server.util.CertificateManager; import org.opends.server.util.SetupUtils; import org.opends.server.util.args.ArgumentException; import org.opends.server.util.args.IntegerArgument; import org.opends.server.util.args.StringArgument; import org.opends.server.util.cli.CLIException; import org.opends.server.util.cli.ConsoleApplication; import org.opends.server.util.cli.Menu; import org.opends.server.util.cli.MenuBuilder; import org.opends.server.util.cli.MenuResult; /** * This class provides a very simple mechanism for installing the OpenDS * Directory Service. It performs the following tasks: * <UL> * <LI>Checks if the server is already installed and running</LI> * <LI>Ask the user what base DN should be used for the data</LI> * <LI>Ask the user whether to create the base entry, or to import LDIF</LI> * <LI>Ask the user for the administration port and make sure it's available * </LI> * <LI>Ask the user for the LDAP port and make sure it's available</LI> * <LI>Ask the user for the default root DN and password</LI> * <LI>Ask the user to enable SSL or not and for the type of certificate that * the server must use</LI> * <LI>Ask the user if they want to start the server when done installing</LI> * </UL> */ public class InstallDS extends ConsoleApplication { private final PlainTextProgressMessageFormatter formatter = new PlainTextProgressMessageFormatter(); /** Prefix for log files. */ static public final String TMP_FILE_PREFIX = "opendj-setup-"; /** Suffix for log files. */ public static final String LOG_FILE_SUFFIX = ".log"; /** * The enumeration containing the different return codes that the command-line * can have. * */ enum ErrorReturnCode { /** * Successful setup. */ SUCCESSFUL(0), /** * We did no have an error but the setup was not executed (displayed * version or usage). */ SUCCESSFUL_NOP(0), /** * Unexpected error (potential bug). */ ERROR_UNEXPECTED(1), /** * Cannot parse arguments or data provided by user is not valid. */ ERROR_USER_DATA(2), /** * Error server already installed. */ ERROR_SERVER_ALREADY_INSTALLED(3), /** * Error initializing server. */ ERROR_INITIALIZING_SERVER(4), /** * The user failed providing password (for the keystore for instance). */ ERROR_PASSWORD_LIMIT(5), /** * The user cancelled the setup. */ ERROR_USER_CANCELLED(6), /** * The user doesn't accept the license. */ ERROR_LICENSE_NOT_ACCEPTED(7), /** * Incompatible java version. */ JAVA_VERSION_INCOMPATIBLE(8); private int returnCode; private ErrorReturnCode(int returnCode) { this.returnCode = returnCode; } /** * Get the corresponding return code value. * * @return The corresponding return code value. */ public int getReturnCode() { return returnCode; } } /** * Enumeration describing the different answer that the user can provide * when we ask to finalize the setup. Note that the code associated * correspond to the order in the confirmation menu that is displayed at the * end of the setup in interactive mode. */ private enum ConfirmCode { // Continue with the install CONTINUE(1), // Provide information again PROVIDE_INFORMATION_AGAIN(2), // Display equivalent command-line PRINT_EQUIVALENT_COMMAND_LINE(3), // Cancel the install CANCEL(3); private int returnCode; private ConfirmCode(int returnCode) { this.returnCode = returnCode; } /** * Get the corresponding return code value. * * @return The corresponding return code value. */ public int getReturnCode() { return returnCode; } } /** * The maximum number of times that we should ask the user to provide the * password to access to a keystore. */ public static final int LIMIT_KEYSTORE_PASSWORD_PROMPT = 7; // Different variables we use when the user decides to provide data again. private NewSuffixOptions.Type lastResetPopulateOption = null; private String lastResetImportFile = null; private String lastResetRejectedFile = null; private String lastResetSkippedFile = null; private Integer lastResetNumEntries = null; private Boolean lastResetEnableSSL = null; private Boolean lastResetEnableStartTLS = null; private SecurityOptions.CertificateType lastResetCertType = null; private String lastResetKeyStorePath = null; private Boolean lastResetEnableWindowsService = null; private Boolean lastResetStartServer = null; /** * The Logger. */ static private final Logger LOG = Logger.getLogger(InstallDS.class.getName()); // The argument parser private InstallDSArgumentParser argParser; /** * Constructor for the InstallDS object. * * @param out the print stream to use for standard output. * @param err the print stream to use for standard error. * @param in the input stream to use for standard input. */ public InstallDS(PrintStream out, PrintStream err, InputStream in) { super(in, out, err); } /** * The main method for the InstallDS CLI tool. * * @param args the command-line arguments provided to this program. */ public static void main(String[] args) { int retCode = mainCLI(args, System.out, System.err, System.in); System.exit(retCode); } /** * Parses the provided command-line arguments and uses that information to * run the setup tool. * * @param args the command-line arguments provided to this program. * * @return The error code. */ public static int mainCLI(String[] args) { return mainCLI(args, System.out, System.err, System.in); } /** * Parses the provided command-line arguments and uses that information to * run the setup tool. * * @param args The command-line arguments provided to this * program. * @param outStream The output stream to use for standard output, or * <CODE>null</CODE> if standard output is not * needed. * @param errStream The output stream to use for standard error, or * <CODE>null</CODE> if standard error is not * needed. * @param inStream The input stream to use for standard input. * @return The error code. */ public static int mainCLI(String[] args, OutputStream outStream, OutputStream errStream, InputStream inStream) { PrintStream out; if (outStream == null) { out = NullOutputStream.printStream(); } else { out = new PrintStream(outStream); } System.setProperty(Constants.CLI_JAVA_PROPERTY, "true"); PrintStream err; if (errStream == null) { err = NullOutputStream.printStream(); } else { err = new PrintStream(errStream); } try { QuickSetupLog.initLogFileHandler( QuickSetupLog.isInitialized() ? null : File.createTempFile(TMP_FILE_PREFIX, LOG_FILE_SUFFIX)); } catch (Throwable t) { System.err.println("Unable to initialize log"); t.printStackTrace(); } InstallDS install = new InstallDS(out, err, inStream); return install.execute(args); } /** * Parses the provided command-line arguments and uses that information to * run the setup CLI. * * @param args the command-line arguments provided to this program. * @return the return code (SUCCESSFUL, USER_DATA_ERROR or BUG). */ public int execute(String[] args) { argParser = new InstallDSArgumentParser(InstallDS.class.getName()); try { argParser.initializeArguments(); } catch (ArgumentException ae) { Message message = ToolMessages.ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage()); println(message); return ErrorReturnCode.ERROR_UNEXPECTED.getReturnCode(); } // Validate user provided data try { argParser.parseArguments(args); } catch (ArgumentException ae) { Message message = ERR_ERROR_PARSING_ARGS.get(ae.getMessage()); println(message); println(); println(Message.raw(argParser.getUsage())); return ErrorReturnCode.ERROR_USER_DATA.getReturnCode(); } // Delete the log file that does not contain any information. The test only // mode is called several times by the setup script and if we do not remove // it we have a lot of empty log files. if (argParser.testOnlyArg.isPresent()) { try { QuickSetupLog.getLogFile().deleteOnExit(); } catch (Throwable t) { LOG.log(Level.WARNING, "Error while trying to update the contents of "+ "the set-java-home file in test only mode: "+t, t); } // Test that we are running a compatible java 1.6 version. try { Utils.checkJavaVersion(); } catch (IncompatibleVersionException ive) { println(ive.getMessageObject()); return ErrorReturnCode.JAVA_VERSION_INCOMPATIBLE.getReturnCode(); } } // If either the showUsage or testOnly or version arguments were provided, // then we're done. if (argParser.usageOrVersionDisplayed() || argParser.testOnlyArg.isPresent()) { return ErrorReturnCode.SUCCESSFUL_NOP.getReturnCode(); } try { checkInstallStatus(); } catch (InitializationException ie) { println(ie.getMessageObject()); return ErrorReturnCode.ERROR_SERVER_ALREADY_INSTALLED.getReturnCode(); } // Check license if (LicenseFile.exists()) { PrintStream printStreamOut = getOutputStream(); String licenseString = LicenseFile.getText(); printStreamOut.println(licenseString); // If the user asks for acceptLicense, license is displayed // and automatically accepted. if (! argParser.acceptLicense.isPresent()) { String yes = INFO_LICENSE_CLI_ACCEPT_YES.get().toString(); String no = INFO_LICENSE_CLI_ACCEPT_NO.get().toString(); String yesShort = INFO_PROMPT_YES_FIRST_LETTER_ANSWER.get().toString(); String noShort = INFO_PROMPT_NO_FIRST_LETTER_ANSWER.get().toString(); println(QuickSetupMessages. INFO_LICENSE_DETAILS_CLI_LABEL.get()); BufferedReader in = getInputStream(); // No-prompt arg automatically rejects the license. if (!argParser.noPromptArg.isPresent()) { while (true) { print(INFO_LICENSE_CLI_ACCEPT_QUESTION.get(yes, no, no)); try { String response = in.readLine(); if ((response == null) || (response.equalsIgnoreCase(no)) || (response.equalsIgnoreCase(noShort)) || (response.length() == 0)) { return ErrorReturnCode.ERROR_LICENSE_NOT_ACCEPTED .getReturnCode(); } else if (response.equalsIgnoreCase(yes) || response.equalsIgnoreCase(yesShort)) { LicenseFile.setApproval(true); break; } else { println( QuickSetupMessages.INFO_LICENSE_CLI_ACCEPT_INVALID_RESPONSE .get()); } } catch (IOException e) { println( QuickSetupMessages.INFO_LICENSE_CLI_ACCEPT_INVALID_RESPONSE .get()); } } } else { return ErrorReturnCode.ERROR_LICENSE_NOT_ACCEPTED.getReturnCode(); } } else { print(INFO_LICENSE_ACCEPT.get()); print(INFO_PROMPT_YES_COMPLETE_ANSWER.get()); LicenseFile.setApproval(true); } } boolean userApproved = false; UserData uData = new UserData(); while (!userApproved) { try { if (isInteractive()) { promptIfRequired(uData); } else { initializeUserDataWithParser(uData); } } catch (UserDataException ude) { println(ude.getMessageObject()); if (isPasswordTriesError(ude.getMessageObject())) { return ErrorReturnCode.ERROR_PASSWORD_LIMIT.getReturnCode(); } else { return ErrorReturnCode.ERROR_USER_DATA.getReturnCode(); } } boolean promptAgain = true; if (!isInteractive()) { userApproved = true; } else { printSummary(uData); } while (isInteractive() && promptAgain) { promptAgain = false; ConfirmCode confirm = askForConfirmation(); switch (confirm) { case CONTINUE: userApproved = true; break; case CANCEL: LOG.log(Level.INFO, "User cancelled setup."); return ErrorReturnCode.ERROR_USER_CANCELLED.getReturnCode(); case PRINT_EQUIVALENT_COMMAND_LINE: printEquivalentCommandLine(uData); promptAgain = true; break; default: // Reset the arguments try { resetArguments(uData); argParser.parseArguments(args); } catch (Throwable t) { LOG.log(Level.WARNING, "Error resetting arg parser: "+t, t); } userApproved = false; } } if (!isInteractive()) { userApproved = true; } } System.setProperty(Constants.CLI_JAVA_PROPERTY, "true"); OfflineInstaller installer = new OfflineInstaller(); installer.setUserData(uData); installer.setProgressMessageFormatter(formatter); installer.addProgressUpdateListener( new ProgressUpdateListener() { public void progressUpdate(ProgressUpdateEvent ev) { if (ev.getNewLogs() != null) { printProgress(ev.getNewLogs()); } } }); printlnProgress(); installer.run(); ApplicationException ue = installer.getRunError(); String cmd; // Use this instead a call to Installation to avoid to launch a new JVM // just to retrieve a path. String root = Utils.getInstallPathFromClasspath(); if (SetupUtils.isWindows()) { String binDir = Utils.getPath(root, Installation.WINDOWS_BINARIES_PATH_RELATIVE); cmd = Utils.getPath(binDir, Installation.WINDOWS_STATUSCLI_FILE_NAME); } else { String binDir = Utils.getPath(root, Installation.UNIX_BINARIES_PATH_RELATIVE); cmd = Utils.getPath(binDir, Installation.UNIX_STATUSCLI_FILE_NAME); } printlnProgress(); printProgress(INFO_INSTALLDS_STATUS_COMMAND_LINE.get(cmd)); printlnProgress(); if (ue != null) { return ue.getType().getReturnCode(); } else { return ErrorReturnCode.SUCCESSFUL.getReturnCode(); } } /** * Checks if the server is installed or not. * @throws InitializationException if the server is already installed and * configured or if the user did not accept to overwrite the existing * databases. */ private void checkInstallStatus() throws InitializationException { CurrentInstallStatus installStatus = new CurrentInstallStatus(); if (installStatus.canOverwriteCurrentInstall()) { if (isInteractive()) { println(installStatus.getInstallationMsg()); try { if (!confirmAction(INFO_CLI_DO_YOU_WANT_TO_CONTINUE.get(), true)) { throw new InitializationException(Message.EMPTY, null); } } catch (CLIException ce) { LOG.log(Level.SEVERE, "Unexpected error: "+ce, ce); throw new InitializationException(Message.EMPTY, null); } } else { println(installStatus.getInstallationMsg()); } } else if (installStatus.isInstalled()) { throw new InitializationException(installStatus.getInstallationMsg(), null); } } /** * {@inheritDoc} */ @Override public boolean isQuiet() { return argParser.quietArg.isPresent(); } /** * {@inheritDoc} */ @Override public boolean isInteractive() { return !argParser.noPromptArg.isPresent(); } /** * {@inheritDoc} */ @Override public boolean isMenuDrivenMode() { return true; } /** * {@inheritDoc} */ @Override public boolean isScriptFriendly() { return false; } /** * {@inheritDoc} */ @Override public boolean isAdvancedMode() { return false; } /** * {@inheritDoc} */ @Override public boolean isVerbose() { return argParser.verboseArg.isPresent(); } /** * This method updates the contents of a UserData object with what the user * specified in the command-line. It assumes that it is being called in no * prompt mode. * @param uData the UserData object. * @throws UserDataException if something went wrong checking the data. */ private void initializeUserDataWithParser(UserData uData) throws UserDataException { LinkedList<Message> errorMessages = new LinkedList<Message>(); uData.setQuiet(isQuiet()); uData.setVerbose(isVerbose()); uData.setConnectTimeout(getConnectTimeout()); // Check the validity of the directory manager DNs String dmDN = argParser.directoryManagerDNArg.getValue(); try { new LdapName(dmDN); if (dmDN.trim().length() == 0) { errorMessages.add(ERR_INSTALLDS_EMPTY_DN_RESPONSE.get()); } } catch (Exception e) { Message message = ERR_INSTALLDS_CANNOT_PARSE_DN.get(dmDN, e.getMessage()); errorMessages.add(message); } uData.setDirectoryManagerDn(dmDN); uData.setDirectoryManagerPwd(argParser.getDirectoryManagerPassword()); // Check the validity of the base DNs LinkedList<String> baseDNs = argParser.baseDNArg.getValues(); if (baseDNs.isEmpty() && argParser.baseDNArg.getDefaultValue() != null) { baseDNs.add(argParser.baseDNArg.getDefaultValue()); } for (String baseDN : baseDNs) { try { new LdapName(baseDN); } catch (Exception e) { Message message = ERR_INSTALLDS_CANNOT_PARSE_DN.get(baseDN, e.getMessage()); errorMessages.add(message); } } try { int ldapPort = argParser.ldapPortArg.getIntValue(); uData.setServerPort(ldapPort); int adminConnectorPort = argParser.adminConnectorPortArg.getIntValue(); uData.setAdminConnectorPort(adminConnectorPort); if (!argParser.skipPortCheckArg.isPresent()) { // Check if the port can be used. if (!SetupUtils.canUseAsPort(ldapPort)) { Message message; if (SetupUtils.isPriviledgedPort(ldapPort)) { message = ERR_INSTALLDS_CANNOT_BIND_TO_PRIVILEGED_PORT.get( ldapPort); } else { message = ERR_INSTALLDS_CANNOT_BIND_TO_PORT.get(ldapPort); } errorMessages.add(message); } // Check if the port can be used. if (!SetupUtils.canUseAsPort(adminConnectorPort)) { Message message; if (SetupUtils.isPriviledgedPort(adminConnectorPort)) { message = ERR_INSTALLDS_CANNOT_BIND_TO_PRIVILEGED_PORT.get( adminConnectorPort); } else { message = ERR_INSTALLDS_CANNOT_BIND_TO_PORT.get(adminConnectorPort); } errorMessages.add(message); } } if (argParser.jmxPortArg.isPresent()) { int jmxPort = argParser.jmxPortArg.getIntValue(); uData.setServerJMXPort(jmxPort); // Check if the port can be used. if (!argParser.skipPortCheckArg.isPresent()) { if (!SetupUtils.canUseAsPort(jmxPort)) { Message message; if (SetupUtils.isPriviledgedPort(jmxPort)) { message = ERR_INSTALLDS_CANNOT_BIND_TO_PRIVILEGED_PORT.get( jmxPort); } else { message = ERR_INSTALLDS_CANNOT_BIND_TO_PORT.get(jmxPort); } errorMessages.add(message); } } } } catch (ArgumentException ae) { errorMessages.add(ae.getMessageObject()); } NewSuffixOptions dataOptions; if (argParser.importLDIFArg.isPresent()) { // Check that the files exist LinkedList<String> nonExistingFiles = new LinkedList<String>(); for (String file : argParser.importLDIFArg.getValues()) { if (!Utils.fileExists(file)) { nonExistingFiles.add(file); } } if (nonExistingFiles.size() > 0) { errorMessages.add(ERR_INSTALLDS_NO_SUCH_LDIF_FILE.get( Utils.getStringFromCollection(nonExistingFiles, ", "))); } String rejectedFile = argParser.rejectedImportFileArg.getValue(); if (rejectedFile != null) { if (!Utils.canWrite(rejectedFile)) { errorMessages.add( ERR_INSTALLDS_CANNOT_WRITE_REJECTED.get(rejectedFile)); } } String skippedFile = argParser.skippedImportFileArg.getValue(); if (skippedFile != null) { if (!Utils.canWrite(skippedFile)) { errorMessages.add(ERR_INSTALLDS_CANNOT_WRITE_SKIPPED.get( skippedFile)); } } dataOptions = NewSuffixOptions.createImportFromLDIF(baseDNs, argParser.importLDIFArg.getValues(), rejectedFile, skippedFile); } else if (argParser.addBaseEntryArg.isPresent()) { dataOptions = NewSuffixOptions.createBaseEntry(baseDNs); } else if (argParser.sampleDataArg.isPresent()) { dataOptions = NewSuffixOptions.createAutomaticallyGenerated(baseDNs, new Integer(argParser.sampleDataArg.getValue())); } else { dataOptions = NewSuffixOptions.createEmpty(baseDNs); } uData.setNewSuffixOptions(dataOptions); // Check that the security data provided is valid. String certNickname = argParser.certNicknameArg.getValue(); String pwd = argParser.getKeyStorePassword(); boolean enableSSL = argParser.ldapsPortArg.isPresent(); boolean enableStartTLS = argParser.enableStartTLSArg.isPresent(); int ldapsPort = -1; try { ldapsPort = enableSSL ? argParser.ldapsPortArg.getIntValue() : -1; } catch (ArgumentException ae) { errorMessages.add(ae.getMessageObject()); } if (enableSSL) { if (!argParser.skipPortCheckArg.isPresent()) { if (!SetupUtils.canUseAsPort(ldapsPort)) { if (SetupUtils.isPriviledgedPort(ldapsPort)) { errorMessages.add( ERR_INSTALLDS_CANNOT_BIND_TO_PRIVILEGED_PORT.get(ldapsPort)); } else { errorMessages.add(ERR_INSTALLDS_CANNOT_BIND_TO_PORT.get(ldapsPort)); } } } } SecurityOptions securityOptions; LinkedList<String> keystoreAliases = new LinkedList<String>(); uData.setHostName(argParser.hostNameArg.getValue()); if (argParser.generateSelfSignedCertificateArg.isPresent()) { securityOptions = SecurityOptions.createSelfSignedCertificateOptions( enableSSL, enableStartTLS, ldapsPort); } else if (argParser.useJavaKeyStoreArg.isPresent()) { String path = argParser.useJavaKeyStoreArg.getValue(); checkCertificateInKeystore(SecurityOptions.CertificateType.JKS, path, pwd, certNickname, errorMessages, keystoreAliases); if ((certNickname == null) && !keystoreAliases.isEmpty()) { certNickname = keystoreAliases.getFirst(); } securityOptions = SecurityOptions.createJKSCertificateOptions( path, pwd, enableSSL, enableStartTLS, ldapsPort, certNickname); } else if (argParser.useJCEKSArg.isPresent()) { String path = argParser.useJCEKSArg.getValue(); checkCertificateInKeystore(SecurityOptions.CertificateType.JCEKS, path, pwd, certNickname, errorMessages, keystoreAliases); if ((certNickname == null) && !keystoreAliases.isEmpty()) { certNickname = keystoreAliases.getFirst(); } securityOptions = SecurityOptions.createJCEKSCertificateOptions( path, pwd, enableSSL, enableStartTLS, ldapsPort, certNickname); } else if (argParser.usePkcs12Arg.isPresent()) { String path = argParser.usePkcs12Arg.getValue(); checkCertificateInKeystore(SecurityOptions.CertificateType.PKCS12, path, pwd, certNickname, errorMessages, keystoreAliases); if ((certNickname == null) && !keystoreAliases.isEmpty()) { certNickname = keystoreAliases.getFirst(); } securityOptions = SecurityOptions.createPKCS12CertificateOptions( path, pwd, enableSSL, enableStartTLS, ldapsPort, certNickname); } else if (argParser.usePkcs11Arg.isPresent()) { checkCertificateInKeystore(SecurityOptions.CertificateType.PKCS11, null, pwd, certNickname, errorMessages, keystoreAliases); if ((certNickname == null) && !keystoreAliases.isEmpty()) { certNickname = keystoreAliases.getFirst(); } securityOptions = SecurityOptions.createPKCS11CertificateOptions( pwd, enableSSL, enableStartTLS, ldapsPort, certNickname); } else { securityOptions = SecurityOptions.createNoCertificateOptions(); } uData.setSecurityOptions(securityOptions); uData.setEnableWindowsService( argParser.enableWindowsServiceArg.isPresent()); uData.setStartServer(!argParser.doNotStartArg.isPresent()); if (errorMessages.size() > 0) { throw new UserDataException(null, Utils.getMessageFromCollection(errorMessages, formatter.getLineBreak().toString())); } } /** * This method updates the contents of a UserData object with what the user * specified in the command-line. If the user did not provide explicitly some * data or if the provided data is not valid, it prompts the user to provide * it. * @param uData the UserData object to be updated. * @throws UserDataException if the user did not manage to provide the * keystore password after a certain number of tries. */ private void promptIfRequired(UserData uData) throws UserDataException { uData.setQuiet(isQuiet()); uData.setVerbose(isVerbose()); uData.setConnectTimeout(getConnectTimeout()); promptIfRequiredForDirectoryManager(uData); promptIfRequiredForPortData(uData); promptIfRequiredForImportData(uData); promptIfRequiredForSecurityData(uData); promptIfRequiredForWindowsService(uData); promptIfRequiredForStartServer(uData); } /** * This method updates the contents of a UserData object with what the user * specified in the command-line for the Directory Manager parameters. * If the user did not provide explicitly some data or if the provided data is * not valid, it prompts the user to provide it. * @param uData the UserData object to be updated. * @throws UserDataException if something went wrong checking the data. */ private void promptIfRequiredForDirectoryManager(UserData uData) throws UserDataException { LinkedList<String> dns = promptIfRequiredForDNs( argParser.directoryManagerDNArg, INFO_INSTALLDS_PROMPT_ROOT_DN.get(), true); uData.setDirectoryManagerDn(dns.getFirst()); String pwd = argParser.getDirectoryManagerPassword(); int nTries = 0; while (pwd == null) { if (nTries >= CONFIRMATION_MAX_TRIES) { throw new UserDataException(null, ERR_TRIES_LIMIT_REACHED.get(CONFIRMATION_MAX_TRIES)); } String pwd1 = null; // Prompt for password and confirm. while (pwd1 == null) { pwd1 = readPassword(INFO_INSTALLDS_PROMPT_ROOT_PASSWORD.get(), LOG); if ((pwd1 == null) || "".equals(pwd1)) { pwd1 = null; println(); println(INFO_EMPTY_PWD.get()); println(); } } String pwd2 = readPassword(INFO_INSTALLDS_PROMPT_CONFIRM_ROOT_PASSWORD.get(), LOG); if (pwd1.equals(pwd2)) { pwd = pwd1; } else { println(); println(ERR_INSTALLDS_PASSWORDS_DONT_MATCH.get()); } nTries++; } uData.setDirectoryManagerPwd(pwd); } /** * This method returns a list of DNs. It checks that the provided list of * DNs actually contain some values. If no valid values are found it prompts * the user to provide a valid DN. * @param arg the Argument that the user provided to specify the DNs. * @param promptMsg the prompt message to be displayed. * @param includeLineBreak whether to include a line break before the first * prompt or not. * @return a list of valid DNs. * @throws UserDataException if something went wrong checking the data. */ private LinkedList<String> promptIfRequiredForDNs(StringArgument arg, Message promptMsg, boolean includeLineBreak) throws UserDataException { LinkedList<String> dns = new LinkedList<String>(); boolean usedProvided = false; boolean firstPrompt = true; int nTries = 0; while (dns.isEmpty()) { if (nTries >= CONFIRMATION_MAX_TRIES) { throw new UserDataException(null, ERR_TRIES_LIMIT_REACHED.get(CONFIRMATION_MAX_TRIES)); } boolean prompted = false; if (usedProvided || !arg.isPresent()) { if (firstPrompt && includeLineBreak) { println(); } try { String dn = readInput(promptMsg, arg.getDefaultValue()); firstPrompt = false; dns.add(dn); prompted = true; } catch (CLIException ce) { LOG.log(Level.WARNING, "Error reading input: "+ce, ce); } } else { dns.addAll(arg.getValues()); usedProvided = true; } LinkedList<String> toRemove = new LinkedList<String>(); for (String dn : dns) { try { new LdapName(dn); if (dn.trim().length() == 0) { toRemove.add(dn); println(ERR_INSTALLDS_EMPTY_DN_RESPONSE.get()); } } catch (Exception e) { toRemove.add(dn); Message message = prompted ? ERR_INSTALLDS_INVALID_DN_RESPONSE.get() : ERR_INSTALLDS_CANNOT_PARSE_DN.get(dn, e.getMessage()); println(message); } } if (toRemove.size() > 0) { println(); } dns.removeAll(toRemove); nTries++; } return dns; } /** * This method updates the contents of a UserData object with what the user * specified in the command-line for the administration connector, LDAP and * JMX port parameters. * If the user did not provide explicitly some data or if the provided data is * not valid, it prompts the user to provide it. * Note: this method does not update nor check the LDAPS port. * @param uData the UserData object to be updated. */ private void promptIfRequiredForPortData(UserData uData) { String hostName = promptForHostNameIfRequired(); uData.setHostName(hostName); LinkedList<Integer> usedPorts = new LinkedList<Integer>(); // Determine the LDAP port number. int ldapPort = promptIfRequiredForPortData(argParser.ldapPortArg, INFO_INSTALLDS_PROMPT_LDAPPORT.get(), usedPorts, true); uData.setServerPort(ldapPort); usedPorts.add(ldapPort); // Determine the Admin Connector port number. int adminConnectorPort = promptIfRequiredForPortData(argParser.adminConnectorPortArg, INFO_INSTALLDS_PROMPT_ADMINCONNECTORPORT.get(), usedPorts, true); uData.setAdminConnectorPort(adminConnectorPort); usedPorts.add(adminConnectorPort); if (argParser.jmxPortArg.isPresent()) { int jmxPort = promptIfRequiredForPortData(argParser.jmxPortArg, INFO_INSTALLDS_PROMPT_JMXPORT.get(), usedPorts, true); uData.setServerJMXPort(jmxPort); } else { uData.setServerJMXPort(-1); } } /** * This method returns a valid port value. It checks that the provided * argument contains a valid port. If a valid port is not found it prompts * the user to provide a valid port. * @param portArg the Argument that the user provided to specify the port. * @param promptMsg the prompt message to be displayed. * @param usedPorts the list of ports the user provided before for other * connection handlers. * @param includeLineBreak whether to include a line break before the first * prompt or not. * @return a valid port number. */ private int promptIfRequiredForPortData(IntegerArgument portArg, Message promptMsg, Collection<Integer> usedPorts, boolean includeLineBreak) { int portNumber = -1; boolean usedProvided = false; boolean firstPrompt = true; while (portNumber == -1) { try { boolean prompted = false; if (usedProvided || !portArg.isPresent()) { int defaultValue = -1; if (portArg.getDefaultValue() != null) { defaultValue = Integer.parseInt(portArg.getDefaultValue()); } if (firstPrompt && includeLineBreak) { println(); } portNumber = -1; while (portNumber == -1) { try { portNumber = readPort(promptMsg, defaultValue); } catch (CLIException ce) { portNumber = -1; LOG.log(Level.WARNING, "Error reading input: "+ce, ce); } } prompted = true; firstPrompt = false; } else { portNumber = portArg.getIntValue(); usedProvided = true; } if (!argParser.skipPortCheckArg.isPresent()) { // Check if the port can be used. if (!SetupUtils.canUseAsPort(portNumber)) { Message message; if (SetupUtils.isPriviledgedPort(portNumber)) { if (prompted || includeLineBreak) { println(); } message = ERR_INSTALLDS_CANNOT_BIND_TO_PRIVILEGED_PORT.get( portNumber); println(message); portNumber = -1; } else { if (prompted || includeLineBreak) { println(); } message = ERR_INSTALLDS_CANNOT_BIND_TO_PORT.get(portNumber); println(message); println(); portNumber = -1; } } } if (portNumber != -1) { if (usedPorts.contains(portNumber)) { Message message = ERR_CONFIGDS_PORT_ALREADY_SPECIFIED.get( String.valueOf(portNumber)); println(message); println(); portNumber = -1; } } } catch (ArgumentException ae) { println(ae.getMessageObject()); } } return portNumber; } /** * This method updates the contents of a UserData object with what the user * specified in the command-line for the base DN and data import parameters. * If the user did not provide explicitly some data or if the provided data is * not valid, it prompts the user to provide it. * @param uData the UserData object to be updated. * @throws UserDataException if something went wrong checking the data. */ private void promptIfRequiredForImportData(UserData uData) throws UserDataException { boolean prompt = true; if (!argParser.baseDNArg.isPresent()) { println(); try { prompt = confirmAction(INFO_INSTALLDS_PROVIDE_BASE_DN_PROMPT.get(), true); } catch (CLIException ce) { prompt = true; LOG.log(Level.WARNING, "Error reading input: "+ce, ce); } } NewSuffixOptions dataOptions; if (!prompt) { LinkedList<String> baseDNs = new LinkedList<String>(); dataOptions = NewSuffixOptions.createEmpty(baseDNs); } else { // Check the validity of the base DNs LinkedList<String> baseDNs = promptIfRequiredForDNs( argParser.baseDNArg, INFO_INSTALLDS_PROMPT_BASEDN.get(), true); dataOptions = promptIfRequiredForDataOptions(baseDNs); } uData.setNewSuffixOptions(dataOptions); } private NewSuffixOptions promptIfRequiredForDataOptions( LinkedList<String> baseDNs) { NewSuffixOptions dataOptions; if (argParser.importLDIFArg.isPresent()) { // Check that the files exist LinkedList<String> nonExistingFiles = new LinkedList<String>(); LinkedList<String> importLDIFFiles = new LinkedList<String>(); for (String file : argParser.importLDIFArg.getValues()) { if (!Utils.fileExists(file)) { nonExistingFiles.add(file); } else { importLDIFFiles.add(file); } } if (nonExistingFiles.size() > 0) { println(); println(ERR_INSTALLDS_NO_SUCH_LDIF_FILE.get( Utils.getStringFromCollection(nonExistingFiles, ", "))); } while (importLDIFFiles.isEmpty()) { println(); try { String path = readInput(INFO_INSTALLDS_PROMPT_IMPORT_FILE.get(), lastResetImportFile); if (!Utils.fileExists(path)) { println(); println(ERR_INSTALLDS_NO_SUCH_LDIF_FILE.get(path)); } else { importLDIFFiles.add(path); } } catch (CLIException ce) { LOG.log(Level.WARNING, "Error reading input: "+ce, ce); } } String rejectedFile = argParser.rejectedImportFileArg.getValue(); if (rejectedFile != null) { while (!Utils.canWrite(rejectedFile)) { println(); println(ERR_INSTALLDS_CANNOT_WRITE_REJECTED.get(rejectedFile)); println(); try { rejectedFile = readInput(INFO_INSTALLDS_PROMPT_REJECTED_FILE.get(), lastResetRejectedFile); } catch (CLIException ce) { LOG.log(Level.WARNING, "Error reading input: "+ce, ce); } } } String skippedFile = argParser.skippedImportFileArg.getValue(); if (skippedFile != null) { while (!Utils.canWrite(skippedFile)) { println(); println(ERR_INSTALLDS_CANNOT_WRITE_SKIPPED.get(skippedFile)); println(); try { skippedFile = readInput(INFO_INSTALLDS_PROMPT_SKIPPED_FILE.get(), lastResetSkippedFile); } catch (CLIException ce) { LOG.log(Level.WARNING, "Error reading input: "+ce, ce); } } } dataOptions = NewSuffixOptions.createImportFromLDIF(baseDNs, importLDIFFiles, rejectedFile, skippedFile); } else if (argParser.addBaseEntryArg.isPresent()) { dataOptions = NewSuffixOptions.createBaseEntry(baseDNs); } else if (argParser.sampleDataArg.isPresent()) { int numUsers; try { numUsers = argParser.sampleDataArg.getIntValue(); } catch (ArgumentException ae) { println(); println(ae.getMessageObject()); Message message = INFO_INSTALLDS_PROMPT_NUM_ENTRIES.get(); numUsers = promptForInteger(message, 2000, 0, Integer.MAX_VALUE); } dataOptions = NewSuffixOptions.createAutomaticallyGenerated(baseDNs, numUsers); } else { final int POPULATE_TYPE_BASE_ONLY = 1; final int POPULATE_TYPE_LEAVE_EMPTY = 2; final int POPULATE_TYPE_IMPORT_FROM_LDIF = 3; final int POPULATE_TYPE_GENERATE_SAMPLE_DATA = 4; int[] indexes = {POPULATE_TYPE_BASE_ONLY, POPULATE_TYPE_LEAVE_EMPTY, POPULATE_TYPE_IMPORT_FROM_LDIF, POPULATE_TYPE_GENERATE_SAMPLE_DATA}; Message[] msgs = new Message[] { INFO_INSTALLDS_POPULATE_OPTION_BASE_ONLY.get(), INFO_INSTALLDS_POPULATE_OPTION_LEAVE_EMPTY.get(), INFO_INSTALLDS_POPULATE_OPTION_IMPORT_LDIF.get(), INFO_INSTALLDS_POPULATE_OPTION_GENERATE_SAMPLE.get() }; MenuBuilder<Integer> builder = new MenuBuilder<Integer>(this); builder.setPrompt(INFO_INSTALLDS_HEADER_POPULATE_TYPE.get()); for (int i=0; i<indexes.length; i++) { builder.addNumberedOption(msgs[i], MenuResult.success(indexes[i])); } if (lastResetPopulateOption == null) { builder.setDefault(Message.raw( String.valueOf(POPULATE_TYPE_BASE_ONLY)), MenuResult.success(POPULATE_TYPE_BASE_ONLY)); } else { switch (lastResetPopulateOption) { case LEAVE_DATABASE_EMPTY: builder.setDefault(Message.raw( String.valueOf(POPULATE_TYPE_LEAVE_EMPTY)), MenuResult.success(POPULATE_TYPE_LEAVE_EMPTY)); break; case IMPORT_FROM_LDIF_FILE: builder.setDefault(Message.raw( String.valueOf(POPULATE_TYPE_IMPORT_FROM_LDIF)), MenuResult.success(POPULATE_TYPE_IMPORT_FROM_LDIF)); break; case IMPORT_AUTOMATICALLY_GENERATED_DATA: builder.setDefault(Message.raw( String.valueOf(POPULATE_TYPE_GENERATE_SAMPLE_DATA)), MenuResult.success(POPULATE_TYPE_GENERATE_SAMPLE_DATA)); break; default: builder.setDefault(Message.raw( String.valueOf(POPULATE_TYPE_BASE_ONLY)), MenuResult.success(POPULATE_TYPE_BASE_ONLY)); } } Menu<Integer> menu = builder.toMenu(); int populateType; try { MenuResult<Integer> m = menu.run(); if (m.isSuccess()) { populateType = m.getValue(); } else { // Should never happen. throw new RuntimeException(); } } catch (CLIException ce) { populateType = POPULATE_TYPE_BASE_ONLY; LOG.log(Level.WARNING, "Error reading input: "+ce, ce); } if (populateType == POPULATE_TYPE_IMPORT_FROM_LDIF) { LinkedList<String> importLDIFFiles = new LinkedList<String>(); while (importLDIFFiles.isEmpty()) { Message message = INFO_INSTALLDS_PROMPT_IMPORT_FILE.get(); println(); try { String path = readInput(message, null); if (Utils.fileExists(path)) { importLDIFFiles.add(path); } else { message = ERR_INSTALLDS_NO_SUCH_LDIF_FILE.get(path); println(); println(message); } } catch (CLIException ce) { LOG.log(Level.WARNING, "Error reading input: "+ce, ce); } } String rejectedFile = argParser.rejectedImportFileArg.getValue(); if (rejectedFile != null) { while (!Utils.canWrite(rejectedFile)) { println(); println( ERR_INSTALLDS_CANNOT_WRITE_REJECTED.get(rejectedFile)); println(); try { rejectedFile = readInput(INFO_INSTALLDS_PROMPT_REJECTED_FILE.get(), null); } catch (CLIException ce) { LOG.log(Level.WARNING, "Error reading input: "+ce, ce); } } } String skippedFile = argParser.skippedImportFileArg.getValue(); if (skippedFile != null) { while (!Utils.canWrite(skippedFile)) { println(); println(ERR_INSTALLDS_CANNOT_WRITE_SKIPPED.get(skippedFile)); println(); try { skippedFile = readInput(INFO_INSTALLDS_PROMPT_SKIPPED_FILE.get(), null); } catch (CLIException ce) { LOG.log(Level.WARNING, "Error reading input: "+ce, ce); } } } dataOptions = NewSuffixOptions.createImportFromLDIF(baseDNs, importLDIFFiles, rejectedFile, skippedFile); } else if (populateType == POPULATE_TYPE_GENERATE_SAMPLE_DATA) { Message message = INFO_INSTALLDS_PROMPT_NUM_ENTRIES.get(); int defaultValue; if (lastResetNumEntries == null) { defaultValue = 2000; } else { defaultValue = lastResetNumEntries; } int numUsers = promptForInteger(message, defaultValue, 0, Integer.MAX_VALUE); dataOptions = NewSuffixOptions.createAutomaticallyGenerated(baseDNs, numUsers); } else if (populateType == POPULATE_TYPE_LEAVE_EMPTY) { dataOptions = NewSuffixOptions.createEmpty(baseDNs); } else if (populateType == POPULATE_TYPE_BASE_ONLY) { dataOptions = NewSuffixOptions.createBaseEntry(baseDNs); } else { throw new IllegalStateException("Unexpected populateType: "+ populateType); } } return dataOptions; } /** * This method updates the contents of a UserData object with what the user * specified in the command-line for the security parameters. * If the user did not provide explicitly some data or if the provided data is * not valid, it prompts the user to provide it. * @param uData the UserData object to be updated. * @throws UserDataException if the user did not manage to provide the * keystore password after a certain number of tries. */ private void promptIfRequiredForSecurityData(UserData uData) throws UserDataException { // Check that the security data provided is valid. boolean enableSSL = false; boolean enableStartTLS = false; int ldapsPort = -1; LinkedList<Integer> usedPorts = new LinkedList<Integer>(); usedPorts.add(uData.getServerPort()); if (uData.getServerJMXPort() != -1) { usedPorts.add(uData.getServerJMXPort()); } // Ask to enable SSL if (!argParser.ldapsPortArg.isPresent()) { println(); try { boolean defaultValue = lastResetEnableSSL != null ? lastResetEnableSSL : false; enableSSL = confirmAction(INFO_INSTALLDS_PROMPT_ENABLE_SSL.get(), defaultValue); if (enableSSL) { ldapsPort = promptIfRequiredForPortData(argParser.ldapsPortArg, INFO_INSTALLDS_PROMPT_LDAPSPORT.get(), usedPorts, false); } } catch (CLIException ce) { LOG.log(Level.WARNING, "Error reading input: "+ce, ce); } } else { ldapsPort = promptIfRequiredForPortData(argParser.ldapsPortArg, INFO_INSTALLDS_PROMPT_LDAPSPORT.get(), usedPorts, true); enableSSL = true; } // Ask to enable Start TLS if (!argParser.enableStartTLSArg.isPresent()) { println(); try { boolean defaultValue = lastResetEnableStartTLS != null ? lastResetEnableStartTLS : false; enableStartTLS = confirmAction(INFO_INSTALLDS_ENABLE_STARTTLS.get(), defaultValue); } catch (CLIException ce) { LOG.log(Level.WARNING, "Error reading input: "+ce, ce); } } else { enableStartTLS = true; } SecurityOptions securityOptions; if (argParser.generateSelfSignedCertificateArg.isPresent()) { securityOptions = SecurityOptions.createSelfSignedCertificateOptions( enableSSL, enableStartTLS, ldapsPort); } else if (argParser.useJavaKeyStoreArg.isPresent()) { securityOptions = createSecurityOptionsPrompting(SecurityOptions.CertificateType.JKS, enableSSL, enableStartTLS, ldapsPort); } else if (argParser.useJCEKSArg.isPresent()) { securityOptions = createSecurityOptionsPrompting(SecurityOptions.CertificateType.JCEKS, enableSSL, enableStartTLS, ldapsPort); } else if (argParser.usePkcs12Arg.isPresent()) { securityOptions = createSecurityOptionsPrompting(SecurityOptions.CertificateType.PKCS12, enableSSL, enableStartTLS, ldapsPort); } else if (argParser.usePkcs11Arg.isPresent()) { securityOptions = createSecurityOptionsPrompting(SecurityOptions.CertificateType.PKCS11, enableSSL, enableStartTLS, ldapsPort); } else { if (!enableSSL && !enableStartTLS) { // If the user did not want to enable SSL or start TLS do not ask // to create a certificate. securityOptions = SecurityOptions.createNoCertificateOptions(); } else { final int SELF_SIGNED = 1; final int JKS = 2; final int JCEKS = 3; final int PKCS12 = 4; final int PKCS11 = 5; int[] indexes = {SELF_SIGNED, JKS, JCEKS, PKCS12, PKCS11}; Message[] msgs = { INFO_INSTALLDS_CERT_OPTION_SELF_SIGNED.get(), INFO_INSTALLDS_CERT_OPTION_JKS.get(), INFO_INSTALLDS_CERT_OPTION_JCEKS.get(), INFO_INSTALLDS_CERT_OPTION_PKCS12.get(), INFO_INSTALLDS_CERT_OPTION_PKCS11.get() }; MenuBuilder<Integer> builder = new MenuBuilder<Integer>(this); builder.setPrompt(INFO_INSTALLDS_HEADER_CERT_TYPE.get()); for (int i=0; i<indexes.length; i++) { builder.addNumberedOption(msgs[i], MenuResult.success(indexes[i])); } if (lastResetCertType == null) { builder.setDefault(Message.raw(String.valueOf(SELF_SIGNED)), MenuResult.success(SELF_SIGNED)); } else { switch (lastResetCertType) { case JKS: builder.setDefault(Message.raw(String.valueOf(JKS)), MenuResult.success(JKS)); break; case JCEKS: builder.setDefault(Message.raw(String.valueOf(JCEKS)), MenuResult.success(JCEKS)); break; case PKCS11: builder.setDefault(Message.raw(String.valueOf(PKCS11)), MenuResult.success(PKCS11)); break; case PKCS12: builder.setDefault(Message.raw(String.valueOf(PKCS12)), MenuResult.success(PKCS12)); break; default: builder.setDefault(Message.raw(String.valueOf(SELF_SIGNED)), MenuResult.success(SELF_SIGNED)); } } Menu<Integer> menu = builder.toMenu(); int certType; try { MenuResult<Integer> m = menu.run(); if (m.isSuccess()) { certType = m.getValue(); } else { // Should never happen. throw new RuntimeException(); } } catch (CLIException ce) { LOG.log(Level.WARNING, "Error reading input: "+ce, ce); certType = SELF_SIGNED; } if (certType == SELF_SIGNED) { securityOptions = SecurityOptions.createSelfSignedCertificateOptions( enableSSL, enableStartTLS, ldapsPort); } else if (certType == JKS) { securityOptions = createSecurityOptionsPrompting(SecurityOptions.CertificateType.JKS, enableSSL, enableStartTLS, ldapsPort); } else if (certType == JCEKS) { securityOptions = createSecurityOptionsPrompting( SecurityOptions.CertificateType.JCEKS, enableSSL, enableStartTLS, ldapsPort); } else if (certType == PKCS12) { securityOptions = createSecurityOptionsPrompting( SecurityOptions.CertificateType.PKCS12, enableSSL, enableStartTLS, ldapsPort); } else if (certType == PKCS11) { securityOptions = createSecurityOptionsPrompting( SecurityOptions.CertificateType.PKCS11, enableSSL, enableStartTLS, ldapsPort); } else { throw new IllegalStateException("Unexpected cert type: "+ certType); } } } uData.setSecurityOptions(securityOptions); } /** * This method updates the contents of a UserData object with what the user * specified in the command-line for the Windows Service parameters. * If the user did not provide explicitly the data, it prompts the user to * provide it. * @param uData the UserData object to be updated. */ private void promptIfRequiredForWindowsService(UserData uData) { boolean enableService = false; // If we are in Windows ask if the server must run as a windows service. if (SetupUtils.isWindows()) { if (argParser.enableWindowsServiceArg.isPresent()) { enableService = true; } else { println(); Message message = INFO_INSTALLDS_PROMPT_ENABLE_SERVICE.get(); try { boolean defaultValue = (lastResetEnableWindowsService == null) ? false : lastResetEnableWindowsService; enableService = confirmAction(message, defaultValue); } catch (CLIException ce) { LOG.log(Level.WARNING, "Error reading input: "+ce, ce); } } } uData.setEnableWindowsService(enableService); } /** * This method updates the contents of a UserData object with what the user * specified in the command-line for the Directory Manager parameters. * If the user did not provide explicitly the data, it prompts the user to * provide it. * @param uData the UserData object to be updated. */ private void promptIfRequiredForStartServer(UserData uData) { boolean startServer = false; if (!argParser.doNotStartArg.isPresent()) { println(); Message message = INFO_INSTALLDS_PROMPT_START_SERVER.get(); try { boolean defaultValue = (lastResetStartServer == null) ? true : lastResetStartServer; startServer = confirmAction(message, defaultValue); } catch (CLIException ce) { LOG.log(Level.WARNING, "Error reading input: "+ce, ce); startServer = true; } } uData.setStartServer(startServer); } /** * Checks that the provided parameters are valid to access an existing * key store. This method adds the encountered errors to the provided * list of Message. It also adds the alias (nicknames) found to the provided * list of String. * @param type the type of key store. * @param path the path of the key store. * @param pwd the password (PIN) to access the key store. * @param certNickname the certificate nickname that we are looking for (or * null if we just one to get the one that is in the key store). * @param errorMessages the list that will be updated with the errors * encountered. * @param nicknameList the list that will be updated with the nicknames found * in the key store. */ public static void checkCertificateInKeystore( SecurityOptions.CertificateType type, String path, String pwd, String certNickname, Collection<Message> errorMessages, Collection<String> nicknameList) { boolean errorWithPath = false; if (type != SecurityOptions.CertificateType.PKCS11) { File f = new File(path); if (!f.exists()) { errorMessages.add(INFO_KEYSTORE_PATH_DOES_NOT_EXIST.get()); errorWithPath = true; } else if (!f.isFile()) { errorMessages.add(INFO_KEYSTORE_PATH_NOT_A_FILE.get()); errorWithPath = true; } } boolean pwdProvided = true; if (pwd == null) { pwdProvided = false; errorMessages.add(INFO_ERROR_NO_KEYSTORE_PASSWORD.get()); } else if (pwd.length() == 0) { pwdProvided = false; errorMessages.add(INFO_ERROR_EMPTY_KEYSTORE_PASSWORD.get()); } if (!errorWithPath && pwdProvided) { try { CertificateManager certManager; switch (type) { case JKS: certManager = new CertificateManager( path, CertificateManager.KEY_STORE_TYPE_JKS, pwd); break; case JCEKS: certManager = new CertificateManager( path, CertificateManager.KEY_STORE_TYPE_JCEKS, pwd); break; case PKCS12: certManager = new CertificateManager( path, CertificateManager.KEY_STORE_TYPE_PKCS12, pwd); break; case PKCS11: certManager = new CertificateManager( CertificateManager.KEY_STORE_PATH_PKCS11, CertificateManager.KEY_STORE_TYPE_PKCS11, pwd); break; default: throw new IllegalArgumentException("Invalid type: "+type); } String[] aliases = certManager.getCertificateAliases(); if ((aliases == null) || (aliases.length == 0)) { // Could not retrieve any certificate switch (type) { case JKS: errorMessages.add(INFO_JKS_KEYSTORE_DOES_NOT_EXIST.get()); break; case JCEKS: errorMessages.add(INFO_JCEKS_KEYSTORE_DOES_NOT_EXIST.get()); break; case PKCS12: errorMessages.add(INFO_PKCS12_KEYSTORE_DOES_NOT_EXIST.get()); break; case PKCS11: errorMessages.add(INFO_PKCS11_KEYSTORE_DOES_NOT_EXIST.get()); break; default: throw new IllegalArgumentException("Invalid type: "+type); } } else if (certManager.hasRealAliases()) { Collections.addAll(nicknameList, aliases); String aliasString = Utils.getStringFromCollection(nicknameList, ", "); if (certNickname != null) { // Check if the certificate alias is in the list. boolean found = false; for (int i=0; i<aliases.length && !found; i++) { found = aliases[i].equalsIgnoreCase(certNickname); } if (!found) { errorMessages.add(ERR_INSTALLDS_CERTNICKNAME_NOT_FOUND.get( aliasString)); } } else if (aliases.length > 1) { errorMessages.add(ERR_INSTALLDS_MUST_PROVIDE_CERTNICKNAME.get( aliasString)); } } } catch (KeyStoreException ke) { // Could not access to the key store: because the password is no good, // because the provided file is not a valid key store, etc. switch (type) { case JKS: errorMessages.add(INFO_ERROR_ACCESSING_JKS_KEYSTORE.get()); break; case JCEKS: errorMessages.add(INFO_ERROR_ACCESSING_JCEKS_KEYSTORE.get()); break; case PKCS12: errorMessages.add(INFO_ERROR_ACCESSING_PKCS12_KEYSTORE.get()); break; case PKCS11: errorMessages.add(INFO_ERROR_ACCESSING_PKCS11_KEYSTORE.get()); break; default: throw new IllegalArgumentException("Invalid type: "+type); } } } } /** * Creates a SecurityOptions object that corresponds to the provided * parameters. If the parameters are not valid, it prompts the user to * provide them. * @param type the keystore type. * @param enableSSL whether to enable SSL or not. * @param enableStartTLS whether to enable StartTLS or not. * @param ldapsPort the LDAPS port to use. * @return a SecurityOptions object that corresponds to the provided * parameters (or to what the user provided after being prompted). * @throws UserDataException if the user did not manage to provide the * keystore password after a certain number of tries. */ private SecurityOptions createSecurityOptionsPrompting( SecurityOptions.CertificateType type, boolean enableSSL, boolean enableStartTLS, int ldapsPort) throws UserDataException { SecurityOptions securityOptions; String path; String certNickname = argParser.certNicknameArg.getValue(); String pwd = argParser.getKeyStorePassword(); if (pwd != null) { if (pwd.length() == 0) { pwd = null; } } Message pathPrompt; String defaultPathValue; switch (type) { case JKS: path = argParser.useJavaKeyStoreArg.getValue(); pathPrompt = INFO_INSTALLDS_PROMPT_JKS_PATH.get(); defaultPathValue = argParser.useJavaKeyStoreArg.getValue(); if (defaultPathValue == null) { defaultPathValue = lastResetKeyStorePath; } break; case JCEKS: path = argParser.useJCEKSArg.getValue(); pathPrompt = INFO_INSTALLDS_PROMPT_JCEKS_PATH.get(); defaultPathValue = argParser.useJCEKSArg.getValue(); if (defaultPathValue == null) { defaultPathValue = lastResetKeyStorePath; } break; case PKCS11: path = null; defaultPathValue = null; pathPrompt = null; break; case PKCS12: path = argParser.usePkcs12Arg.getValue(); defaultPathValue = argParser.usePkcs12Arg.getValue(); if (defaultPathValue == null) { defaultPathValue = lastResetKeyStorePath; } pathPrompt = INFO_INSTALLDS_PROMPT_PKCS12_PATH.get(); break; default: throw new IllegalStateException( "Called promptIfRequiredCertificate with invalid type: "+type); } LinkedList<Message> errorMessages = new LinkedList<Message>(); LinkedList<String> keystoreAliases = new LinkedList<String>(); boolean firstTry = true; int nPasswordPrompts = 0; while ((errorMessages.size() > 0) || firstTry) { boolean prompted = false; if (errorMessages.size() > 0) { println(); println(Utils.getMessageFromCollection(errorMessages, formatter.getLineBreak().toString())); } if (type != SecurityOptions.CertificateType.PKCS11) { if (containsKeyStorePathErrorMessage(errorMessages) || (path == null)) { println(); try { path = readInput(pathPrompt, defaultPathValue); } catch (CLIException ce) { path = ""; LOG.log(Level.WARNING, "Error reading input: "+ce, ce); } prompted = true; if (pwd != null) { errorMessages.clear(); keystoreAliases.clear(); checkCertificateInKeystore(type, path, pwd, certNickname, errorMessages, keystoreAliases); if (!errorMessages.isEmpty()) { // Reset password: this might be a new keystore pwd = null; } } } } if (containsKeyStorePasswordErrorMessage(errorMessages) || (pwd == null)) { if (!prompted) { println(); } pwd = null; while (pwd == null) { if (nPasswordPrompts > LIMIT_KEYSTORE_PASSWORD_PROMPT) { throw new UserDataException(null, ERR_INSTALLDS_TOO_MANY_KEYSTORE_PASSWORD_TRIES.get( String.valueOf(LIMIT_KEYSTORE_PASSWORD_PROMPT))); } pwd = readPassword( INFO_INSTALLDS_PROMPT_KEYSTORE_PASSWORD.get(), LOG); nPasswordPrompts ++; } } if (containsCertNicknameErrorMessage(errorMessages)) { if (!prompted) { println(); } certNickname = promptForCertificateNickname(keystoreAliases); } errorMessages.clear(); keystoreAliases.clear(); checkCertificateInKeystore(type, path, pwd, certNickname, errorMessages, keystoreAliases); firstTry = false; } if ((certNickname == null) && !keystoreAliases.isEmpty()) { certNickname = keystoreAliases.getFirst(); } switch (type) { case JKS: securityOptions = SecurityOptions.createJKSCertificateOptions( path, pwd, enableSSL, enableStartTLS, ldapsPort, certNickname); break; case JCEKS: securityOptions = SecurityOptions.createJCEKSCertificateOptions( path, pwd, enableSSL, enableStartTLS, ldapsPort, certNickname); break; case PKCS12: securityOptions = SecurityOptions.createPKCS12CertificateOptions( path, pwd, enableSSL, enableStartTLS, ldapsPort, certNickname); break; case PKCS11: securityOptions = SecurityOptions.createPKCS11CertificateOptions( pwd, enableSSL, enableStartTLS, ldapsPort, certNickname); break; default: throw new IllegalStateException( "Called createSecurityOptionsPrompting with invalid type: "+type); } return securityOptions; } /** * Tells if any of the error messages provided corresponds to a problem * with the key store path. * @param msgs the messages to analyze. * @return <CODE>true</CODE> if any of the error messages provided corresponds * to a problem with the key store path and <CODE>false</CODE> otherwise. */ public static boolean containsKeyStorePathErrorMessage( Collection<Message> msgs) { boolean found = false; for (Message msg : msgs) { if (msg.getDescriptor().equals(INFO_KEYSTORE_PATH_DOES_NOT_EXIST) || msg.getDescriptor().equals(INFO_KEYSTORE_PATH_NOT_A_FILE) || msg.getDescriptor().equals(INFO_JKS_KEYSTORE_DOES_NOT_EXIST) || msg.getDescriptor().equals(INFO_JCEKS_KEYSTORE_DOES_NOT_EXIST) || msg.getDescriptor().equals(INFO_PKCS12_KEYSTORE_DOES_NOT_EXIST) || msg.getDescriptor().equals(INFO_PKCS11_KEYSTORE_DOES_NOT_EXIST) || msg.getDescriptor().equals(INFO_ERROR_ACCESSING_JKS_KEYSTORE) || msg.getDescriptor().equals(INFO_ERROR_ACCESSING_JCEKS_KEYSTORE) || msg.getDescriptor().equals(INFO_ERROR_ACCESSING_PKCS12_KEYSTORE) || msg.getDescriptor().equals(INFO_ERROR_ACCESSING_PKCS11_KEYSTORE)) { found = true; break; } } return found; } /** * Tells if any of the error messages provided corresponds to a problem * with the key store password. * @param msgs the messages to analyze. * @return <CODE>true</CODE> if any of the error messages provided corresponds * to a problem with the key store password and <CODE>false</CODE> otherwise. */ public static boolean containsKeyStorePasswordErrorMessage( Collection<Message> msgs) { boolean found = false; for (Message msg : msgs) { if (msg.getDescriptor().equals(INFO_JKS_KEYSTORE_DOES_NOT_EXIST) || msg.getDescriptor().equals(INFO_JCEKS_KEYSTORE_DOES_NOT_EXIST) || msg.getDescriptor().equals(INFO_PKCS12_KEYSTORE_DOES_NOT_EXIST) || msg.getDescriptor().equals(INFO_PKCS11_KEYSTORE_DOES_NOT_EXIST) || msg.getDescriptor().equals(INFO_ERROR_ACCESSING_JKS_KEYSTORE) || msg.getDescriptor().equals(INFO_ERROR_ACCESSING_JCEKS_KEYSTORE) || msg.getDescriptor().equals(INFO_ERROR_ACCESSING_PKCS12_KEYSTORE) || msg.getDescriptor().equals(INFO_ERROR_ACCESSING_PKCS11_KEYSTORE) || msg.getDescriptor().equals(INFO_ERROR_NO_KEYSTORE_PASSWORD) || msg.getDescriptor().equals(INFO_ERROR_EMPTY_KEYSTORE_PASSWORD)) { found = true; break; } } return found; } /** * Tells if any of the error messages provided corresponds to a problem * with the certificate nickname. * @param msgs the messages to analyze. * @return <CODE>true</CODE> if any of the error messages provided corresponds * to a problem with the certificate nickname and <CODE>false</CODE> * otherwise. */ public static boolean containsCertNicknameErrorMessage( Collection<Message> msgs) { boolean found = false; for (Message msg : msgs) { if (msg.getDescriptor().equals(ERR_INSTALLDS_CERTNICKNAME_NOT_FOUND) || msg.getDescriptor().equals(ERR_INSTALLDS_MUST_PROVIDE_CERTNICKNAME)) { found = true; break; } } return found; } /** * Tells if the error messages provided corresponds to a problem with the * password tries. * @param msg the message to analyze. * @return <CODE>true</CODE> if the error message provided corresponds to a * problem with the password tries and <CODE>false</CODE> otherwise. */ private boolean isPasswordTriesError(Message msg) { return msg.getDescriptor().equals( ERR_INSTALLDS_TOO_MANY_KEYSTORE_PASSWORD_TRIES); } /** * Interactively prompts (on standard output) the user to provide an integer * value. The answer provided must be parseable as an integer, and may be * required to be within a given set of bounds. It will keep prompting until * an acceptable value is given. * * @param prompt The prompt to present to the user. * @param defaultValue The default value to assume if the user presses ENTER * without typing anything, or <CODE>null</CODE> if * there should not be a default and the user must * explicitly provide a value. * @param lowerBound The lower bound that should be enforced, or * <CODE>null</CODE> if there is none. * @param upperBound The upper bound that should be enforced, or * <CODE>null</CODE> if there is none. * * @return The <CODE>int</CODE> value read from the user input. */ private int promptForInteger(Message prompt, Integer defaultValue, Integer lowerBound, Integer upperBound) { int returnValue = -1; while (returnValue == -1) { String s; try { s = readInput(prompt, String.valueOf(defaultValue)); } catch (CLIException ce) { s = ""; LOG.log(Level.WARNING, "Error reading input: "+ce, ce); } if (s.equals("")) { if (defaultValue == null) { Message message = ERR_INSTALLDS_INVALID_INTEGER_RESPONSE.get(); println(message); println(); } else { returnValue = defaultValue; } } else { try { int intValue = Integer.parseInt(s); if ((lowerBound != null) && (intValue < lowerBound)) { Message message = ERR_INSTALLDS_INTEGER_BELOW_LOWER_BOUND.get(lowerBound); println(message); println(); } else if ((upperBound != null) && (intValue > upperBound)) { Message message = ERR_INSTALLDS_INTEGER_ABOVE_UPPER_BOUND.get(upperBound); println(message); println(); } else { returnValue = intValue; } } catch (NumberFormatException nfe) { Message message = ERR_INSTALLDS_INVALID_INTEGER_RESPONSE.get(); println(message); println(); } } } return returnValue; } /** * Prompts the user to accept on the certificates that appears on the list * and returns the chosen certificate nickname. * @param nicknames the list of certificates the user must choose from. * @return the chosen certificate nickname. */ private String promptForCertificateNickname(LinkedList<String> nicknames) { String nickname = null; while (nickname == null) { for (String n : nicknames) { try { if (confirmAction(INFO_INSTALLDS_PROMPT_CERTNICKNAME.get(n), true)) { nickname = n; break; } } catch (CLIException ce) { LOG.log(Level.WARNING, "Error reading input: "+ce, ce); } } } return nickname; } /** * It displays the information provided by the user. * @param uData the UserData that the user provided. */ private void printSummary(UserData uData) { println(); println(); println(INFO_INSTALLDS_SUMMARY.get()); Message[] labels = { INFO_SERVER_PORT_LABEL.get(), INFO_ADMIN_CONNECTOR_PORT_LABEL.get(), INFO_INSTALLDS_SERVER_JMXPORT_LABEL.get(), INFO_SERVER_SECURITY_LABEL.get(), INFO_SERVER_DIRECTORY_MANAGER_DN_LABEL.get(), INFO_DIRECTORY_DATA_LABEL.get() }; int jmxPort = uData.getServerJMXPort(); Message[] values = { Message.raw(String.valueOf(uData.getServerPort())), Message.raw(String.valueOf(uData.getAdminConnectorPort())), Message.raw(jmxPort != -1 ? String.valueOf(jmxPort) : null), Message.raw( Utils.getSecurityOptionsString(uData.getSecurityOptions(), false)), Message.raw(uData.getDirectoryManagerDn()), Message.raw(Utils.getDataDisplayString(uData)), }; int maxWidth = 0; for (Message l : labels) { maxWidth = Math.max(maxWidth, l.length()); } for (int i=0; i<labels.length; i++) { StringBuilder sb = new StringBuilder(); if (values[i] != null) { Message l = labels[i]; sb.append(l.toString()); sb.append(" "); String[] lines = values[i].toString().split(Constants.LINE_SEPARATOR); for (int j=0; j<lines.length; j++) { if (j != 0) { for (int k=0; k <= maxWidth; k++) { sb.append(" "); } } else { for (int k=0; k<maxWidth - l.length(); k++) { sb.append(" "); } } sb.append(lines[j]); println(Message.raw(sb)); sb = new StringBuilder(); } } } println(); if (uData.getStartServer()) { println(INFO_INSTALLDS_START_SERVER.get()); } else { println(INFO_INSTALLDS_DO_NOT_START_SERVER.get()); } if (Utils.isWindows()) { if (uData.getEnableWindowsService()) { println(INFO_INSTALLDS_ENABLE_WINDOWS_SERVICE.get()); } else { println(INFO_INSTALLDS_DO_NOT_ENABLE_WINDOWS_SERVICE.get()); } } } private void printEquivalentCommandLine(UserData uData) { println(); println(INFO_INSTALL_SETUP_EQUIVALENT_COMMAND_LINE.get()); println(); ArrayList<String> cmd = Utils.getSetupEquivalentCommandLine(uData); println(Message.raw( Utils.getFormattedEquivalentCommandLine(cmd, formatter))); } /** * This method asks the user to confirm to continue the setup. It basically * displays the information provided by the user and at the end proposes a * menu with the different options to choose from. * @return the answer provided by the user: cancel setup, continue setup or * provide information again. */ private ConfirmCode askForConfirmation() { ConfirmCode returnValue; println(); println(); Message[] msgs = new Message[] { INFO_INSTALLDS_CONFIRM_INSTALL.get(), INFO_INSTALLDS_PROVIDE_DATA_AGAIN.get(), INFO_INSTALLDS_PRINT_EQUIVALENT_COMMAND_LINE.get(), INFO_INSTALLDS_CANCEL.get() }; MenuBuilder<ConfirmCode> builder = new MenuBuilder<ConfirmCode>(this); builder.setPrompt(INFO_INSTALLDS_CONFIRM_INSTALL_PROMPT.get()); int i=0; for (ConfirmCode code : ConfirmCode.values()) { builder.addNumberedOption(msgs[i], MenuResult.success(code)); i++; } builder.setDefault(Message.raw( String.valueOf(ConfirmCode.CONTINUE.getReturnCode())), MenuResult.success(ConfirmCode.CONTINUE)); Menu<ConfirmCode> menu = builder.toMenu(); try { MenuResult<ConfirmCode> m = menu.run(); if (m.isSuccess()) { returnValue = m.getValue(); } else { // Should never happen. throw new RuntimeException(); } } catch (CLIException ce) { returnValue = ConfirmCode.CANCEL; LOG.log(Level.WARNING, "Error reading input: "+ce, ce); } return returnValue; } private void resetArguments(UserData uData) { argParser = new InstallDSArgumentParser(InstallDS.class.getName()); try { argParser.initializeArguments(); argParser.directoryManagerDNArg.setDefaultValue( uData.getDirectoryManagerDn()); argParser.ldapPortArg.setDefaultValue( String.valueOf(uData.getServerPort())); argParser.adminConnectorPortArg.setDefaultValue( String.valueOf(uData.getAdminConnectorPort())); int jmxPort = uData.getServerJMXPort(); if (jmxPort != -1) { argParser.jmxPortArg.setDefaultValue(String.valueOf(jmxPort)); } LinkedList<String> baseDNs = uData.getNewSuffixOptions().getBaseDns(); if (!baseDNs.isEmpty()) { argParser.baseDNArg.setDefaultValue(baseDNs.getFirst()); } NewSuffixOptions suffixOptions = uData.getNewSuffixOptions(); lastResetPopulateOption = suffixOptions.getType(); if (lastResetPopulateOption == NewSuffixOptions.Type.IMPORT_AUTOMATICALLY_GENERATED_DATA) { lastResetNumEntries = suffixOptions.getNumberEntries(); } else if (lastResetPopulateOption == NewSuffixOptions.Type.IMPORT_FROM_LDIF_FILE) { lastResetImportFile = suffixOptions.getLDIFPaths().getFirst(); lastResetRejectedFile = suffixOptions.getRejectedFile(); lastResetSkippedFile = suffixOptions.getSkippedFile(); } SecurityOptions sec = uData.getSecurityOptions(); if (sec.getEnableSSL()) { argParser.ldapsPortArg.setDefaultValue( String.valueOf(sec.getSslPort())); } lastResetEnableSSL = sec.getEnableSSL(); lastResetEnableStartTLS = sec.getEnableStartTLS(); lastResetCertType = sec.getCertificateType(); if (lastResetCertType == SecurityOptions.CertificateType.JKS || lastResetCertType == SecurityOptions.CertificateType.JCEKS || lastResetCertType == SecurityOptions.CertificateType.PKCS12) { lastResetKeyStorePath = sec.getKeystorePath(); } else { lastResetKeyStorePath = null; } lastResetEnableWindowsService = uData.getEnableWindowsService(); lastResetStartServer = uData.getStartServer(); } catch (Throwable t) { LOG.log(Level.WARNING, "Error resetting arguments: "+t, t); } } private String promptForHostNameIfRequired() { String hostName = null; if (argParser.hostNameArg.isPresent()) { hostName = argParser.hostNameArg.getValue(); } else { println(); while (hostName == null) { try { hostName = readInput(INFO_INSTALLDS_PROMPT_HOST_NAME.get(), argParser.hostNameArg.getDefaultValue()); } catch (CLIException ce) { LOG.log(Level.WARNING, "Error reading input: "+ce, ce); } } } return hostName; } /** * Returns the timeout to be used to connect in milliseconds. The method * must be called after parsing the arguments. * @return the timeout to be used to connect in milliseconds. Returns * {@code 0} if there is no timeout. */ private int getConnectTimeout() { return argParser.getConnectTimeout(); } }