001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2006-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2011 profiq s.r.o.
026 *      Portions Copyright 2011-2015 ForgeRock AS
027 */
028package org.opends.server.tools;
029
030import static org.forgerock.util.Utils.*;
031import static org.opends.messages.AdminToolMessages.*;
032import static org.opends.messages.QuickSetupMessages.*;
033import static org.opends.messages.ToolMessages.*;
034import static org.opends.messages.UtilityMessages.*;
035
036import static com.forgerock.opendj.cli.Utils.*;
037import static com.forgerock.opendj.util.OperatingSystem.*;
038
039import java.io.BufferedReader;
040import java.io.File;
041import java.io.IOException;
042import java.io.InputStream;
043import java.io.InputStreamReader;
044import java.io.OutputStream;
045import java.io.PrintStream;
046import java.security.KeyStoreException;
047import java.util.ArrayList;
048import java.util.Arrays;
049import java.util.Collection;
050import java.util.Collections;
051import java.util.LinkedList;
052import java.util.List;
053
054import javax.naming.ldap.LdapName;
055
056import org.forgerock.i18n.LocalizableMessage;
057import org.forgerock.i18n.LocalizableMessageDescriptor.Arg0;
058import org.forgerock.i18n.LocalizableMessageDescriptor.Arg1;
059import org.forgerock.i18n.slf4j.LocalizedLogger;
060import org.forgerock.opendj.config.ManagedObjectDefinition;
061import org.forgerock.opendj.server.config.client.BackendCfgClient;
062import org.forgerock.opendj.server.config.server.BackendCfg;
063import org.opends.messages.QuickSetupMessages;
064import org.opends.messages.ToolMessages;
065import org.opends.quicksetup.ApplicationException;
066import org.opends.quicksetup.Constants;
067import org.opends.quicksetup.CurrentInstallStatus;
068import org.opends.quicksetup.Installation;
069import org.opends.quicksetup.LicenseFile;
070import org.opends.quicksetup.QuickSetupLog;
071import org.opends.quicksetup.SecurityOptions;
072import org.opends.quicksetup.UserData;
073import org.opends.quicksetup.UserDataException;
074import org.opends.quicksetup.event.ProgressUpdateEvent;
075import org.opends.quicksetup.event.ProgressUpdateListener;
076import org.opends.quicksetup.installer.NewSuffixOptions;
077import org.opends.quicksetup.installer.offline.OfflineInstaller;
078import org.opends.quicksetup.util.IncompatibleVersionException;
079import org.opends.quicksetup.util.PlainTextProgressMessageFormatter;
080import org.opends.quicksetup.util.Utils;
081import org.opends.server.types.InitializationException;
082import org.opends.server.types.NullOutputStream;
083import org.opends.server.util.CertificateManager;
084import org.opends.server.util.SetupUtils;
085import org.opends.server.util.StaticUtils;
086
087import com.forgerock.opendj.cli.ArgumentException;
088import com.forgerock.opendj.cli.ClientException;
089import com.forgerock.opendj.cli.ConsoleApplication;
090import com.forgerock.opendj.cli.IntegerArgument;
091import com.forgerock.opendj.cli.Menu;
092import com.forgerock.opendj.cli.MenuBuilder;
093import com.forgerock.opendj.cli.MenuResult;
094import com.forgerock.opendj.cli.StringArgument;
095
096/**
097 * This class provides a very simple mechanism for installing the OpenDS
098 * Directory Service.  It performs the following tasks:
099 * <UL>
100 *   <LI>Checks if the server is already installed and running</LI>
101 *   <LI>Ask the user what base DN should be used for the data</LI>
102 *   <LI>Ask the user whether to create the base entry, or to import LDIF</LI>
103 *   <LI>Ask the user for the administration port and make sure it's available
104 *   </LI>
105 *   <LI>Ask the user for the LDAP port and make sure it's available</LI>
106 *   <LI>Ask the user for the default root DN and password</LI>
107 *   <LI>Ask the user to enable SSL or not and for the type of certificate that
108 *   the server must use</LI>
109 *   <LI>Ask the user if they want to start the server when done installing</LI>
110 * </UL>
111 */
112public class InstallDS extends ConsoleApplication
113{
114
115  private final PlainTextProgressMessageFormatter formatter = new PlainTextProgressMessageFormatter();
116
117  /** Prefix for log files. */
118  public static final String TMP_FILE_PREFIX = "opendj-setup-";
119
120  /** Suffix for log files. */
121  public static final String LOG_FILE_SUFFIX = ".log";
122
123  /**
124   * The enumeration containing the different return codes that the command-line
125   * can have.
126   */
127  private enum InstallReturnCode
128  {
129    SUCCESSFUL(0),
130
131    /** We did no have an error but the setup was not executed (displayed version or usage). */
132    SUCCESSFUL_NOP(0),
133
134    /** Unexpected error (potential bug). */
135    ERROR_UNEXPECTED(1),
136
137    /** Cannot parse arguments or data provided by user is not valid. */
138    ERROR_USER_DATA(2),
139
140    /** Error server already installed. */
141    ERROR_SERVER_ALREADY_INSTALLED(3),
142
143    /** Error initializing server. */
144    ERROR_INITIALIZING_SERVER(4),
145
146    /** The user failed providing password (for the keystore for instance). */
147    ERROR_PASSWORD_LIMIT(5),
148
149    /** The user cancelled the setup. */
150    ERROR_USER_CANCELLED(6),
151
152    /** The user doesn't accept the license. */
153    ERROR_LICENSE_NOT_ACCEPTED(7),
154
155    /** Incompatible java version. */
156    JAVA_VERSION_INCOMPATIBLE(8);
157
158    private int returnCode;
159    private InstallReturnCode(int returnCode)
160    {
161      this.returnCode = returnCode;
162    }
163
164    /**
165     * Get the corresponding return code value.
166     *
167     * @return The corresponding return code value.
168     */
169    public int getReturnCode()
170    {
171      return returnCode;
172    }
173  }
174
175  /**
176   * Enumeration describing the different answer that the user can provide when
177   * we ask to finalize the setup. Note that the code associated correspond to
178   * the order in the confirmation menu that is displayed at the end of the
179   * setup in interactive mode.
180   */
181  private enum ConfirmCode
182  {
183    CONTINUE(1),
184    PROVIDE_INFORMATION_AGAIN(2),
185    PRINT_EQUIVALENT_COMMAND_LINE(3),
186    CANCEL(3);
187
188    private int returnCode;
189    private ConfirmCode(int returnCode)
190    {
191      this.returnCode = returnCode;
192    }
193
194    /**
195     * Get the corresponding return code value.
196     *
197     * @return The corresponding return code value.
198     */
199    public int getReturnCode()
200    {
201      return returnCode;
202    }
203  }
204
205  /**
206   * The maximum number of times that we should ask the user to provide the
207   * password to access to a keystore.
208   */
209  public static final int LIMIT_KEYSTORE_PASSWORD_PROMPT = 7;
210
211  private final BackendTypeHelper backendTypeHelper = new BackendTypeHelper();
212
213  /** Different variables we use when the user decides to provide data again. */
214  private NewSuffixOptions.Type lastResetPopulateOption;
215  private ManagedObjectDefinition<? extends BackendCfgClient, ? extends BackendCfg> lastResetBackendType;
216
217  private String lastResetImportFile;
218  private String lastResetRejectedFile;
219  private String lastResetSkippedFile;
220
221  private Integer lastResetNumEntries;
222  private Boolean lastResetEnableSSL;
223  private Boolean lastResetEnableStartTLS;
224
225  private SecurityOptions.CertificateType lastResetCertType;
226  private String lastResetKeyStorePath;
227
228  private Boolean lastResetEnableWindowsService;
229  private Boolean lastResetStartServer;
230
231  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
232
233  /** The argument parser. */
234  private InstallDSArgumentParser argParser;
235
236  /**
237   * Constructor for the InstallDS object.
238   *
239   * @param out
240   *          the print stream to use for standard output.
241   * @param err
242   *          the print stream to use for standard error.
243   * @param in
244   *          the input stream to use for standard input.
245   */
246  public InstallDS(PrintStream out, PrintStream err, InputStream in)
247  {
248    super(out, err);
249  }
250
251  /**
252   * The main method for the InstallDS CLI tool.
253   *
254   * @param args
255   *          the command-line arguments provided to this program.
256   */
257  public static void main(String[] args)
258  {
259    final int retCode = mainCLI(args, System.out, System.err, System.in);
260
261    System.exit(retCode);
262  }
263
264  /**
265   * Parses the provided command-line arguments and uses that information to run
266   * the setup tool.
267   *
268   * @param args
269   *          the command-line arguments provided to this program.
270   * @return The error code.
271   */
272  public static int mainCLI(String[] args)
273  {
274    return mainCLI(args, System.out, System.err, System.in);
275  }
276
277  /**
278   * Parses the provided command-line arguments and uses that information to run
279   * the setup tool.
280   *
281   * @param args
282   *          The command-line arguments provided to this program.
283   * @param outStream
284   *          The output stream to use for standard output, or <CODE>null</CODE>
285   *          if standard output is not needed.
286   * @param errStream
287   *          The output stream to use for standard error, or <CODE>null</CODE>
288   *          if standard error is not needed.
289   * @param inStream
290   *          The input stream to use for standard input.
291   * @return The error code.
292   */
293  public static int mainCLI(String[] args, OutputStream outStream, OutputStream errStream, InputStream inStream)
294  {
295    final PrintStream out = NullOutputStream.wrapOrNullStream(outStream);
296
297    System.setProperty(Constants.CLI_JAVA_PROPERTY, "true");
298
299    final PrintStream err = NullOutputStream.wrapOrNullStream(errStream);
300
301    try {
302      QuickSetupLog.initLogFileHandler(
303              QuickSetupLog.isInitialized() ? null :
304                File.createTempFile(TMP_FILE_PREFIX, LOG_FILE_SUFFIX));
305    } catch (final Throwable t) {
306      System.err.println("Unable to initialize log");
307      t.printStackTrace();
308    }
309
310    final InstallDS install = new InstallDS(out, err, inStream);
311
312    int retCode = install.execute(args);
313    if (retCode == 0)
314    {
315      QuickSetupLog.closeAndDeleteLogFile();
316    }
317    return retCode;
318  }
319
320  /**
321   * Parses the provided command-line arguments and uses that information to run
322   * the setup CLI.
323   *
324   * @param args
325   *          the command-line arguments provided to this program.
326   * @return the return code (SUCCESSFUL, USER_DATA_ERROR or BUG).
327   */
328  public int execute(String[] args)
329  {
330    argParser = new InstallDSArgumentParser(InstallDS.class.getName());
331    try
332    {
333      argParser.initializeArguments();
334    }
335    catch (final ArgumentException ae)
336    {
337      println(ToolMessages.ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage()));
338      return InstallReturnCode.ERROR_UNEXPECTED.getReturnCode();
339    }
340
341    // Validate user provided data
342    try
343    {
344      argParser.parseArguments(args);
345    }
346    catch (final ArgumentException ae)
347    {
348      argParser.displayMessageAndUsageReference(getErrStream(), ERR_ERROR_PARSING_ARGS.get(ae.getMessage()));
349      return InstallReturnCode.ERROR_USER_DATA.getReturnCode();
350    }
351
352    if (argParser.testOnlyArg.isPresent() && !testJVM())
353    {
354        return InstallReturnCode.JAVA_VERSION_INCOMPATIBLE.getReturnCode();
355    }
356
357    if (argParser.usageOrVersionDisplayed() || argParser.testOnlyArg.isPresent())
358    {
359      return InstallReturnCode.SUCCESSFUL_NOP.getReturnCode();
360    }
361
362    try
363    {
364      checkInstallStatus();
365    }
366    catch (final InitializationException ie)
367    {
368      println(ie.getMessageObject());
369      return InstallReturnCode.ERROR_SERVER_ALREADY_INSTALLED.getReturnCode();
370    }
371
372    if (!checkLicense())
373    {
374      return InstallReturnCode.ERROR_LICENSE_NOT_ACCEPTED.getReturnCode();
375    }
376
377    final UserData uData = new UserData();
378    InstallReturnCode fillUserDataRC;
379    try
380    {
381      fillUserDataRC = fillUserData(uData, args);
382      if (fillUserDataRC != InstallReturnCode.SUCCESSFUL)
383      {
384        return fillUserDataRC.getReturnCode();
385      }
386    }
387    catch (final UserDataException e)
388    {
389      return printAndReturnErrorCode(e.getMessageObject()).getReturnCode();
390    }
391
392
393    System.setProperty(Constants.CLI_JAVA_PROPERTY, "true");
394    final OfflineInstaller installer = new OfflineInstaller();
395    installer.setUserData(uData);
396    installer.setProgressMessageFormatter(formatter);
397    installer.addProgressUpdateListener(
398        new ProgressUpdateListener() {
399          @Override
400          public void progressUpdate(ProgressUpdateEvent ev) {
401            if (ev.getNewLogs() != null)
402            {
403              print(ev.getNewLogs());
404            }
405          }
406        });
407    println();
408
409    installer.run();
410    printStatusCommand();
411
412    final ApplicationException ue = installer.getRunError();
413    if (ue != null)
414    {
415      return ue.getType().getReturnCode();
416    }
417
418    return InstallReturnCode.SUCCESSFUL.getReturnCode();
419  }
420
421  private InstallReturnCode fillUserData(UserData uData, String[] args) throws UserDataException
422  {
423    if (!isInteractive())
424    {
425      initializeUserDataWithParser(uData);
426      return InstallReturnCode.SUCCESSFUL;
427    }
428
429    boolean userApproved = false;
430    while (!userApproved)
431    {
432      try
433      {
434        promptIfRequired(uData);
435      }
436      catch (final ClientException ce)
437      {
438        return printAndReturnErrorCode(ce.getMessageObject());
439      }
440
441      boolean promptAgain = true;
442      printSummary(uData);
443      while (isInteractive() && promptAgain)
444      {
445        promptAgain = false;
446        final ConfirmCode confirm = askForConfirmation();
447        switch (confirm)
448        {
449        case CONTINUE:
450          userApproved = true;
451          break;
452
453        case CANCEL:
454          logger.debug(LocalizableMessage.raw("User cancelled setup."));
455          return InstallReturnCode.ERROR_USER_CANCELLED;
456
457        case PRINT_EQUIVALENT_COMMAND_LINE:
458          printEquivalentCommandLine(uData);
459          promptAgain = true;
460          break;
461
462        case PROVIDE_INFORMATION_AGAIN:
463          // Reset the arguments
464          try
465          {
466            resetArguments(uData);
467            argParser.parseArguments(args);
468          }
469          catch (final Throwable t)
470          {
471            logger.warn(LocalizableMessage.raw("Error resetting arg parser: "+t, t));
472          }
473          userApproved = false;
474        }
475      }
476    }
477
478    return InstallReturnCode.SUCCESSFUL;
479  }
480
481  private boolean testJVM()
482  {
483    // Delete the log file that does not contain any information.  The test only
484    // mode is called several times by the setup script and if we do not remove
485    // it we have a lot of empty log files.
486    try
487    {
488      QuickSetupLog.getLogFile().deleteOnExit();
489    }
490    catch (final Throwable t)
491    {
492      logger.warn(LocalizableMessage.raw("Error while trying to update the contents of "
493          + "the set-java-home file in test only mode: " + t, t));
494    }
495    try
496    {
497      Utils.checkJavaVersion();
498      return true;
499    }
500    catch (final IncompatibleVersionException ive)
501    {
502      println(ive.getMessageObject());
503      return false;
504    }
505  }
506
507  private boolean checkLicense()
508  {
509    if (!LicenseFile.exists()) {
510      return true;
511    }
512
513    println(LocalizableMessage.raw(LicenseFile.getText()));
514    // If the user asks for acceptLicense, license is displayed
515    // and automatically accepted.
516    if (!argParser.acceptLicense.isPresent())
517    {
518      final String yes = INFO_LICENSE_CLI_ACCEPT_YES.get().toString();
519      final String no = INFO_LICENSE_CLI_ACCEPT_NO.get().toString();
520      final String yesShort = INFO_PROMPT_YES_FIRST_LETTER_ANSWER.get().toString();
521      final String noShort = INFO_PROMPT_NO_FIRST_LETTER_ANSWER.get().toString();
522      println(QuickSetupMessages.INFO_LICENSE_DETAILS_CLI_LABEL.get());
523
524      final BufferedReader in = new BufferedReader(new InputStreamReader(getInputStream()));
525
526      // No-prompt arg automatically rejects the license.
527      if (!argParser.noPromptArg.isPresent())
528      {
529        while (true)
530        {
531          print(INFO_LICENSE_CLI_ACCEPT_QUESTION.get(yes, no, no));
532          try
533          {
534            final String response = in.readLine();
535            if (response == null
536                || response.equalsIgnoreCase(no)
537                || response.equalsIgnoreCase(noShort)
538                || response.length() == 0)
539            {
540              return false;
541            }
542            else if (response.equalsIgnoreCase(yes)
543                  || response.equalsIgnoreCase(yesShort))
544            {
545              LicenseFile.setApproval(true);
546              break;
547            }
548            println(QuickSetupMessages.INFO_LICENSE_CLI_ACCEPT_INVALID_RESPONSE.get());
549          }
550          catch (final IOException e)
551          {
552            println(QuickSetupMessages.INFO_LICENSE_CLI_ACCEPT_INVALID_RESPONSE.get());
553          }
554        }
555      }
556      else
557      {
558        return false;
559      }
560    }
561    else
562    {
563      print(INFO_LICENSE_ACCEPT.get());
564      print(INFO_PROMPT_YES_COMPLETE_ANSWER.get());
565      LicenseFile.setApproval(true);
566    }
567
568    return true;
569  }
570
571  private void printStatusCommand()
572  {
573    // Use this instead a call to Installation to avoid to launch a new JVM just to retrieve a path.
574    final String binariesRelativePath = isWindows() ? Installation.WINDOWS_BINARIES_PATH_RELATIVE
575                                                    : Installation.UNIX_BINARIES_PATH_RELATIVE;
576    final String statusCliFileName = isWindows() ? Installation.WINDOWS_STATUSCLI_FILE_NAME
577                                                 : Installation.UNIX_STATUSCLI_FILE_NAME;
578    final String binDir = Utils.getPath(Utils.getInstallPathFromClasspath(), binariesRelativePath);
579    final String cmd = Utils.getPath(binDir, statusCliFileName);
580    println();
581    println(INFO_INSTALLDS_STATUS_COMMAND_LINE.get(cmd));
582    println();
583  }
584
585
586  private InstallReturnCode printAndReturnErrorCode(LocalizableMessage message)
587  {
588    println(message);
589    if (StaticUtils.hasDescriptor(message, ERR_INSTALLDS_TOO_MANY_KEYSTORE_PASSWORD_TRIES))
590    {
591      return InstallReturnCode.ERROR_PASSWORD_LIMIT;
592    }
593
594    return InstallReturnCode.ERROR_USER_DATA;
595  }
596
597  /**
598   * Checks if the server is installed or not.
599   *
600   * @throws InitializationException
601   *           if the server is already installed and configured or if the user
602   *           did not accept to overwrite the existing databases.
603   */
604  private void checkInstallStatus() throws InitializationException
605  {
606    final CurrentInstallStatus installStatus = new CurrentInstallStatus();
607    if (installStatus.canOverwriteCurrentInstall())
608    {
609      if (isInteractive())
610      {
611        println(installStatus.getInstallationMsg());
612        try
613        {
614          if (!confirmAction(INFO_CLI_DO_YOU_WANT_TO_CONTINUE.get(), true))
615          {
616            throw new InitializationException(LocalizableMessage.EMPTY);
617          }
618        }
619        catch (final ClientException ce)
620        {
621          logger.error(LocalizableMessage.raw("Unexpected error: "+ce, ce));
622          throw new InitializationException(LocalizableMessage.EMPTY, ce);
623        }
624      }
625      else
626      {
627        println(installStatus.getInstallationMsg());
628      }
629    }
630    else if (installStatus.isInstalled())
631    {
632      throw new InitializationException(installStatus.getInstallationMsg());
633    }
634  }
635
636  /** {@inheritDoc} */
637  @Override
638  public boolean isQuiet()
639  {
640    return argParser.quietArg.isPresent();
641  }
642
643  /** {@inheritDoc} */
644  @Override
645  public boolean isInteractive()
646  {
647    return !argParser.noPromptArg.isPresent();
648  }
649
650  /** {@inheritDoc} */
651  @Override
652  public boolean isMenuDrivenMode() {
653    return true;
654  }
655
656  /** {@inheritDoc} */
657  @Override
658  public boolean isScriptFriendly() {
659    return false;
660  }
661
662  /** {@inheritDoc} */
663  @Override
664  public boolean isAdvancedMode() {
665    return false;
666  }
667
668
669  /** {@inheritDoc} */
670  @Override
671  public boolean isVerbose() {
672    return argParser.verboseArg.isPresent();
673  }
674
675  /**
676   * This method updates the contents of a UserData object with what the user
677   * specified in the command-line. It assumes that it is being called in no
678   * prompt mode.
679   *
680   * @param uData
681   *          the UserData object.
682   * @throws UserDataException
683   *           if something went wrong checking the data.
684   */
685  private void initializeUserDataWithParser(UserData uData) throws UserDataException
686  {
687    uData.setQuiet(isQuiet());
688    uData.setVerbose(isVerbose());
689    uData.setConnectTimeout(getConnectTimeout());
690
691    final List<LocalizableMessage> errorMessages = new LinkedList<>();
692    setBackendType(uData, errorMessages);
693    final List<String> baseDNs = checkBaseDNs(errorMessages);
694    setDirectoryManagerData(uData, errorMessages);
695    setPorts(uData, errorMessages);
696    setImportData(baseDNs, uData, errorMessages);
697    setSecurityData(uData, errorMessages);
698
699    if (!errorMessages.isEmpty())
700    {
701      throw new UserDataException(null,
702          Utils.getMessageFromCollection(errorMessages, formatter.getLineBreak().toString()));
703    }
704  }
705
706  private void setBackendType(final UserData uData, final List<LocalizableMessage> errorMessages)
707  {
708    final String filledBackendType = argParser.backendTypeArg.getValue();
709    final ManagedObjectDefinition<? extends BackendCfgClient, ? extends BackendCfg> backend =
710        backendTypeHelper.retrieveBackendTypeFromName(filledBackendType);
711    if (backend != null)
712    {
713      uData.setBackendType(backend);
714    }
715    else
716    {
717      errorMessages.add(
718          ERR_INSTALLDS_NO_SUCH_BACKEND_TYPE.get(filledBackendType, backendTypeHelper.getPrintableBackendTypeNames()));
719    }
720  }
721
722  private List<String> checkBaseDNs(List<LocalizableMessage> errorMessages)
723  {
724    final List<String> baseDNs = argParser.baseDNArg.getValues();
725    if (baseDNs.isEmpty() && argParser.baseDNArg.getDefaultValue() != null)
726    {
727      baseDNs.add(argParser.baseDNArg.getDefaultValue());
728    }
729
730    for (final String baseDN : baseDNs)
731    {
732      checkBaseDN(baseDN, errorMessages);
733    }
734
735    return baseDNs;
736  }
737
738  private void setDirectoryManagerData(UserData uData, List<LocalizableMessage> errorMessages)
739  {
740    final String dmDN = argParser.directoryManagerDNArg.getValue();
741    if (dmDN.trim().length() == 0)
742    {
743      errorMessages.add(ERR_INSTALLDS_EMPTY_DN_RESPONSE.get());
744    }
745    checkBaseDN(dmDN, errorMessages);
746    uData.setDirectoryManagerDn(argParser.directoryManagerDNArg.getValue());
747
748    // Check the validity of the directory manager password
749    if (argParser.getDirectoryManagerPassword().isEmpty()) {
750      errorMessages.add(INFO_EMPTY_PWD.get());
751    }
752    uData.setDirectoryManagerPwd(argParser.getDirectoryManagerPassword());
753  }
754
755  private void checkBaseDN(String baseDN, List<LocalizableMessage> errorMessages)
756  {
757    try
758    {
759      new LdapName(baseDN);
760    }
761    catch (final Exception e)
762    {
763      errorMessages.add(ERR_INSTALLDS_CANNOT_PARSE_DN.get(baseDN, e.getMessage()));
764    }
765  }
766
767  private void setPorts(UserData uData, List<LocalizableMessage> errorMessages)
768  {
769    try
770    {
771      final int ldapPort = argParser.ldapPortArg.getIntValue();
772      uData.setServerPort(ldapPort);
773
774      final int adminConnectorPort = argParser.adminConnectorPortArg.getIntValue();
775      uData.setAdminConnectorPort(adminConnectorPort);
776
777      if (!argParser.skipPortCheckArg.isPresent())
778      {
779        checkCanUsePort(ldapPort, errorMessages);
780        checkCanUsePort(adminConnectorPort, errorMessages);
781      }
782      if (argParser.jmxPortArg.isPresent())
783      {
784        final int jmxPort = argParser.jmxPortArg.getIntValue();
785        uData.setServerJMXPort(jmxPort);
786        if (!argParser.skipPortCheckArg.isPresent())
787        {
788          checkCanUsePort(jmxPort, errorMessages);
789        }
790      }
791    }
792    catch (final ArgumentException ae)
793    {
794      errorMessages.add(ae.getMessageObject());
795    }
796  }
797
798  private void setImportData(List<String> baseDNs, UserData uData, List<LocalizableMessage> errorMessages)
799  {
800    NewSuffixOptions dataOptions;
801    if (argParser.importLDIFArg.isPresent())
802    {
803      // Check that the files exist
804      final List<String> nonExistingFiles = new LinkedList<>();
805      for (final String file : argParser.importLDIFArg.getValues())
806      {
807        if (!Utils.fileExists(file))
808        {
809          nonExistingFiles.add(file);
810        }
811      }
812
813      if (!nonExistingFiles.isEmpty())
814      {
815        errorMessages.add(ERR_INSTALLDS_NO_SUCH_LDIF_FILE.get(joinAsString(", ", nonExistingFiles)));
816      }
817
818      final String rejectedFile = argParser.rejectedImportFileArg.getValue();
819      if (rejectedFile != null && !canWrite(rejectedFile))
820      {
821        errorMessages.add(ERR_INSTALLDS_CANNOT_WRITE_REJECTED.get(rejectedFile));
822      }
823
824      final String skippedFile = argParser.skippedImportFileArg.getValue();
825      if (skippedFile != null && !canWrite(skippedFile))
826      {
827        errorMessages.add(ERR_INSTALLDS_CANNOT_WRITE_SKIPPED.get(skippedFile));
828      }
829      dataOptions = NewSuffixOptions.createImportFromLDIF(baseDNs, argParser.importLDIFArg.getValues(),
830          rejectedFile, skippedFile);
831    }
832    else if (argParser.addBaseEntryArg.isPresent())
833    {
834      dataOptions = NewSuffixOptions.createBaseEntry(baseDNs);
835    }
836    else if (argParser.sampleDataArg.isPresent())
837    {
838      dataOptions = NewSuffixOptions.createAutomaticallyGenerated(baseDNs,
839          Integer.valueOf(argParser.sampleDataArg.getValue()));
840    }
841    else
842    {
843      dataOptions = NewSuffixOptions.createEmpty(baseDNs);
844    }
845    uData.setNewSuffixOptions(dataOptions);
846  }
847
848  private void setSecurityData(UserData uData, List<LocalizableMessage> errorMessages)
849  {
850    final boolean enableSSL = argParser.ldapsPortArg.isPresent();
851    int sslPort = -1;
852
853    try
854    {
855      sslPort = enableSSL ? argParser.ldapsPortArg.getIntValue() : -1;
856    }
857    catch (final ArgumentException ae)
858    {
859      errorMessages.add(ae.getMessageObject());
860    }
861
862    if (enableSSL && !argParser.skipPortCheckArg.isPresent())
863    {
864      checkCanUsePort(sslPort, errorMessages);
865    }
866
867    checkCertificate(sslPort, enableSSL, uData, errorMessages);
868    uData.setEnableWindowsService(argParser.enableWindowsServiceArg.isPresent());
869    uData.setStartServer(!argParser.doNotStartArg.isPresent());
870  }
871
872  private void checkCertificate(int sslPort, boolean enableSSL, UserData uData, List<LocalizableMessage> errorMessages)
873  {
874    final LinkedList<String> keystoreAliases = new LinkedList<>();
875    uData.setHostName(argParser.hostNameArg.getValue());
876
877    final boolean enableStartTLS = argParser.enableStartTLSArg.isPresent();
878    final String pwd = argParser.getKeyStorePassword();
879    SecurityOptions.CertificateType certType = null;
880    String pathToCertificat = null;
881    if (argParser.generateSelfSignedCertificateArg.isPresent())
882    {
883      certType = SecurityOptions.CertificateType.SELF_SIGNED_CERTIFICATE;
884    }
885    else if (argParser.useJavaKeyStoreArg.isPresent())
886    {
887      certType = SecurityOptions.CertificateType.JKS;
888      pathToCertificat = argParser.useJavaKeyStoreArg.getValue();
889    }
890    else if (argParser.useJCEKSArg.isPresent())
891    {
892      certType = SecurityOptions.CertificateType.JCEKS;
893      pathToCertificat = argParser.useJCEKSArg.getValue();
894    }
895    else if (argParser.usePkcs11Arg.isPresent())
896    {
897      certType = SecurityOptions.CertificateType.PKCS11;
898      pathToCertificat = argParser.usePkcs11Arg.getValue();
899    }
900    else if (argParser.usePkcs12Arg.isPresent())
901    {
902      certType = SecurityOptions.CertificateType.PKCS12;
903      pathToCertificat = argParser.usePkcs12Arg.getValue();
904    }
905    else
906    {
907      certType = SecurityOptions.CertificateType.NO_CERTIFICATE;
908    }
909
910    Collection<String> certNicknames = argParser.certNicknameArg.getValues();
911    if (pathToCertificat != null)
912    {
913      checkCertificateInKeystore(certType, pathToCertificat, pwd, certNicknames, errorMessages, keystoreAliases);
914      if (certNicknames.isEmpty() && !keystoreAliases.isEmpty())
915      {
916        certNicknames = Arrays.asList(keystoreAliases.getFirst());
917      }
918    }
919
920    final SecurityOptions securityOptions = SecurityOptions.createOptionsForCertificatType(
921        certType, pathToCertificat, pwd, enableSSL, enableStartTLS, sslPort, certNicknames);
922    uData.setSecurityOptions(securityOptions);
923  }
924
925  private void checkCanUsePort(int port, List<LocalizableMessage> errorMessages)
926  {
927    if (!SetupUtils.canUseAsPort(port))
928    {
929      errorMessages.add(getCannotBindErrorMessage(port));
930    }
931  }
932
933  private LocalizableMessage getCannotBindErrorMessage(int port)
934  {
935    if (SetupUtils.isPrivilegedPort(port))
936    {
937      return ERR_INSTALLDS_CANNOT_BIND_TO_PRIVILEGED_PORT.get(port);
938    }
939    return ERR_INSTALLDS_CANNOT_BIND_TO_PORT.get(port);
940  }
941
942  /**
943   * This method updates the contents of a UserData object with what the user
944   * specified in the command-line. If the user did not provide explicitly some
945   * data or if the provided data is not valid, it prompts the user to provide
946   * it.
947   *
948   * @param uData
949   *          the UserData object to be updated.
950   * @throws UserDataException
951   *           if the user did not manage to provide the keystore password after
952   *           a certain number of tries.
953   * @throws ClientException
954   *           if something went wrong when reading inputs.
955   */
956  private void promptIfRequired(UserData uData) throws UserDataException, ClientException
957  {
958    uData.setQuiet(isQuiet());
959    uData.setVerbose(isVerbose());
960    uData.setConnectTimeout(getConnectTimeout());
961
962    promptIfRequiredForDirectoryManager(uData);
963    promptIfRequiredForPortData(uData);
964    uData.setNewSuffixOptions(promptIfRequiredForImportData(uData));
965    uData.setSecurityOptions(promptIfRequiredForSecurityData(uData));
966    uData.setEnableWindowsService(promptIfRequiredForWindowsService());
967    uData.setStartServer(promptIfRequiredForStartServer());
968  }
969
970  /**
971   * This method updates the contents of a UserData object with what the user
972   * specified in the command-line for the Directory Manager parameters. If the
973   * user did not provide explicitly some data or if the provided data is not
974   * valid, it prompts the user to provide it.
975   *
976   * @param uData
977   *          the UserData object to be updated.
978   * @throws UserDataException
979   *           if something went wrong checking the data.
980   * @throws ClientException
981   *           if something went wrong checking passwords.
982   */
983  private void promptIfRequiredForDirectoryManager(UserData uData) throws UserDataException, ClientException
984  {
985    final LinkedList<String> dns = promptIfRequiredForDNs(
986        argParser.directoryManagerDNArg, INFO_INSTALLDS_PROMPT_ROOT_DN.get(), true);
987    uData.setDirectoryManagerDn(dns.getFirst());
988
989    int nTries = 0;
990    String pwd = argParser.getDirectoryManagerPassword();
991    while (pwd == null)
992    {
993      if (nTries >= CONFIRMATION_MAX_TRIES)
994      {
995        throw new UserDataException(null, ERR_TRIES_LIMIT_REACHED.get(CONFIRMATION_MAX_TRIES));
996      }
997
998      // Prompt for password and confirm.
999      char[] pwd1 = readPassword(INFO_INSTALLDS_PROMPT_ROOT_PASSWORD.get());
1000      while (pwd1 == null || pwd1.length == 0)
1001      {
1002        println();
1003        println(INFO_EMPTY_PWD.get());
1004        println();
1005        pwd1 = readPassword(INFO_INSTALLDS_PROMPT_ROOT_PASSWORD.get());
1006      }
1007
1008      final char[] pwd2 = readPassword(INFO_INSTALLDS_PROMPT_CONFIRM_ROOT_PASSWORD.get());
1009      if (Arrays.equals(pwd1, pwd2))
1010      {
1011        pwd = String.valueOf(pwd1);
1012      }
1013      else
1014      {
1015        println();
1016        println(ERR_INSTALLDS_PASSWORDS_DONT_MATCH.get());
1017      }
1018
1019      nTries++;
1020    }
1021    uData.setDirectoryManagerPwd(pwd);
1022  }
1023
1024  /**
1025   * This method returns a list of DNs. It checks that the provided list of DNs
1026   * actually contain some values. If no valid values are found it prompts the
1027   * user to provide a valid DN.
1028   *
1029   * @param arg
1030   *          the Argument that the user provided to specify the DNs.
1031   * @param promptMsg
1032   *          the prompt message to be displayed.
1033   * @param includeLineBreak
1034   *          whether to include a line break before the first prompt or not.
1035   * @return a list of valid DNs.
1036   * @throws UserDataException
1037   *           if something went wrong checking the data.
1038   */
1039  private LinkedList<String> promptIfRequiredForDNs(StringArgument arg,
1040      LocalizableMessage promptMsg, boolean includeLineBreak) throws UserDataException
1041  {
1042    final LinkedList<String> dns = new LinkedList<>();
1043
1044    boolean usedProvided = false;
1045    boolean firstPrompt = true;
1046    int nTries = 0;
1047    while (dns.isEmpty())
1048    {
1049      if (nTries >= CONFIRMATION_MAX_TRIES)
1050      {
1051        throw new UserDataException(null, ERR_TRIES_LIMIT_REACHED.get(CONFIRMATION_MAX_TRIES));
1052      }
1053      boolean prompted = false;
1054      if (usedProvided || !arg.isPresent())
1055      {
1056        if (firstPrompt && includeLineBreak)
1057        {
1058          println();
1059        }
1060        try
1061        {
1062          final String dn = readInput(promptMsg, arg.getDefaultValue());
1063          firstPrompt = false;
1064          dns.add(dn);
1065          prompted = true;
1066        }
1067        catch (final ClientException ce)
1068        {
1069          logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
1070        }
1071      }
1072      else
1073      {
1074        dns.addAll(arg.getValues());
1075        usedProvided = true;
1076      }
1077      final List<String> toRemove = new LinkedList<>();
1078      for (final String dn : dns)
1079      {
1080        try
1081        {
1082          new LdapName(dn);
1083          if (dn.trim().length() == 0)
1084          {
1085            toRemove.add(dn);
1086            println(ERR_INSTALLDS_EMPTY_DN_RESPONSE.get());
1087          }
1088        }
1089        catch (final Exception e)
1090        {
1091          toRemove.add(dn);
1092          final LocalizableMessage message = prompted ? ERR_INSTALLDS_INVALID_DN_RESPONSE.get() :
1093            ERR_INSTALLDS_CANNOT_PARSE_DN.get(dn, e.getMessage());
1094          println(message);
1095        }
1096      }
1097      if (!toRemove.isEmpty())
1098      {
1099        println();
1100      }
1101      dns.removeAll(toRemove);
1102      nTries++;
1103    }
1104    return dns;
1105  }
1106
1107  /**
1108   * This method updates the contents of a UserData object with what the user
1109   * specified in the command-line for the administration connector, LDAP and
1110   * JMX port parameters. If the user did not provide explicitly some data or
1111   * if the provided data is not valid, it prompts the user to provide it.
1112   * Note: this method does not update nor check the LDAPS port.
1113   *
1114   * @param uData
1115   *          the UserData object to be updated.
1116   */
1117  private void promptIfRequiredForPortData(UserData uData)
1118  {
1119    uData.setHostName(promptForHostNameIfRequired());
1120
1121    final List<Integer> usedPorts = new LinkedList<>();
1122    //  Determine the LDAP port number.
1123    final int ldapPort = promptIfRequiredForPortData(argParser.ldapPortArg,
1124        INFO_INSTALLDS_PROMPT_LDAPPORT.get(), usedPorts, true);
1125    uData.setServerPort(ldapPort);
1126    usedPorts.add(ldapPort);
1127
1128    //  Determine the Admin Connector port number.
1129    final int adminConnectorPort = promptIfRequiredForPortData(argParser.adminConnectorPortArg,
1130        INFO_INSTALLDS_PROMPT_ADMINCONNECTORPORT.get(), usedPorts, true);
1131    uData.setAdminConnectorPort(adminConnectorPort);
1132    usedPorts.add(adminConnectorPort);
1133
1134    if (argParser.jmxPortArg.isPresent())
1135    {
1136      final int jmxPort = promptIfRequiredForPortData(argParser.jmxPortArg,
1137          INFO_INSTALLDS_PROMPT_JMXPORT.get(), usedPorts, true);
1138      uData.setServerJMXPort(jmxPort);
1139    }
1140    else
1141    {
1142      uData.setServerJMXPort(-1);
1143    }
1144  }
1145
1146  /**
1147   * This method returns a valid port value. It checks that the provided
1148   * argument contains a valid port. If a valid port is not found it prompts the
1149   * user to provide a valid port.
1150   *
1151   * @param portArg
1152   *          the Argument that the user provided to specify the port.
1153   * @param promptMsg
1154   *          the prompt message to be displayed.
1155   * @param usedPorts
1156   *          the list of ports the user provided before for other connection
1157   *          handlers.
1158   * @param includeLineBreak
1159   *          whether to include a line break before the first prompt or not.
1160   * @return a valid port number.
1161   */
1162  private int promptIfRequiredForPortData(IntegerArgument portArg, LocalizableMessage promptMsg,
1163      Collection<Integer> usedPorts, boolean includeLineBreak)
1164  {
1165    int portNumber = -1;
1166    boolean usedProvided = false;
1167    boolean firstPrompt = true;
1168    while (portNumber == -1)
1169    {
1170      try
1171      {
1172        boolean prompted = false;
1173        if (usedProvided || !portArg.isPresent())
1174        {
1175          int defaultValue = -1;
1176          if (portArg.getDefaultValue() != null)
1177          {
1178            defaultValue = Integer.parseInt(portArg.getDefaultValue());
1179          }
1180          if (firstPrompt && includeLineBreak)
1181          {
1182            println();
1183          }
1184          portNumber = -1;
1185          while (portNumber == -1)
1186          {
1187            try
1188            {
1189              portNumber = readPort(promptMsg, defaultValue);
1190            }
1191            catch (final ClientException ce)
1192            {
1193              portNumber = -1;
1194              logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
1195            }
1196          }
1197          prompted = true;
1198          firstPrompt = false;
1199        }
1200        else
1201        {
1202          portNumber = portArg.getIntValue();
1203          usedProvided = true;
1204        }
1205
1206        if (!argParser.skipPortCheckArg.isPresent() && !SetupUtils.canUseAsPort(portNumber))
1207        {
1208          final LocalizableMessage message = getCannotBindErrorMessage(portNumber);
1209          if (prompted || includeLineBreak)
1210          {
1211            println();
1212          }
1213          println(message);
1214          if (!SetupUtils.isPrivilegedPort(portNumber))
1215          {
1216            println();
1217          }
1218          portNumber = -1;
1219        }
1220        if (portNumber != -1 && usedPorts.contains(portNumber))
1221        {
1222          println(ERR_CONFIGDS_PORT_ALREADY_SPECIFIED.get(portNumber));
1223          println();
1224          portNumber = -1;
1225        }
1226      }
1227      catch (final ArgumentException ae)
1228      {
1229        println(ae.getMessageObject());
1230      }
1231    }
1232    return portNumber;
1233  }
1234
1235  /**
1236   * This method returns what the user specified in the command-line for the
1237   * base DN and data import parameters. If the user did not provide explicitly
1238   * some data or if the provided data is not valid, it prompts the user to
1239   * provide it.
1240   *
1241   * @param uData
1242   *          The UserData object to be updated.
1243   * @return the NewSuffixOptions telling how to import data
1244   * @throws UserDataException
1245   *           if something went wrong checking the data.
1246   */
1247  private NewSuffixOptions promptIfRequiredForImportData(final UserData uData) throws UserDataException
1248  {
1249    boolean prompt = true;
1250    if (!argParser.baseDNArg.isPresent())
1251    {
1252      println();
1253      try
1254      {
1255        prompt = confirmAction(INFO_INSTALLDS_PROVIDE_BASE_DN_PROMPT.get(), true);
1256      }
1257      catch (final ClientException ce)
1258      {
1259        prompt = true;
1260        logger.warn(LocalizableMessage.raw("Error reading input: " + ce, ce));
1261      }
1262    }
1263
1264    if (!prompt)
1265    {
1266      return NewSuffixOptions.createEmpty(new LinkedList<String>());
1267    }
1268
1269    uData.setBackendType(getOrPromptForBackendType());
1270
1271    // Add default value for base DN on first prompt
1272    if (argParser.baseDNArg.getDefaultValue() == null)
1273    {
1274      argParser.baseDNArg.setDefaultValue(Installation.DEFAULT_INTERACTIVE_BASE_DN);
1275    }
1276    // Check the validity of the base DNs
1277    final List<String> baseDNs = promptIfRequiredForDNs(argParser.baseDNArg, INFO_INSTALLDS_PROMPT_BASEDN.get(), true);
1278    return promptIfRequiredForDataOptions(baseDNs);
1279
1280  }
1281
1282  private ManagedObjectDefinition<? extends BackendCfgClient, ? extends BackendCfg> getOrPromptForBackendType()
1283  {
1284    if (argParser.backendTypeArg.isPresent())
1285    {
1286      final ManagedObjectDefinition<? extends BackendCfgClient, ? extends BackendCfg> backend =
1287          backendTypeHelper.retrieveBackendTypeFromName(argParser.backendTypeArg.getValue().toLowerCase());
1288      if ( backend != null)
1289      {
1290        return backend;
1291      }
1292      println();
1293      println(ERR_INSTALLDS_NO_SUCH_BACKEND_TYPE.get(
1294          argParser.backendTypeArg.getValue(), backendTypeHelper.getPrintableBackendTypeNames()));
1295    }
1296
1297    return promptForBackendType();
1298  }
1299
1300  private ManagedObjectDefinition<? extends BackendCfgClient,? extends BackendCfg> promptForBackendType()
1301  {
1302    println();
1303    int backendTypeIndex = 1;
1304    final List<ManagedObjectDefinition<? extends BackendCfgClient, ? extends BackendCfg>> backendTypes =
1305            backendTypeHelper.getBackendTypes();
1306    if (backendTypes.size() == 1) {
1307      final ManagedObjectDefinition<? extends BackendCfgClient, ? extends BackendCfg> backendType = backendTypes.get(0);
1308      println(INFO_INSTALLDS_BACKEND_TYPE_USED.get(backendType.getUserFriendlyName()));
1309      return backendType;
1310    }
1311
1312    try
1313    {
1314      final MenuResult<Integer> m = getBackendTypeMenu().run();
1315      if (m.isSuccess())
1316      {
1317        backendTypeIndex = m.getValue();
1318      }
1319    }
1320    catch (final ClientException ce)
1321    {
1322      logger.warn(LocalizableMessage.raw("Error reading input: " + ce, ce));
1323    }
1324
1325    return backendTypes.get(backendTypeIndex - 1);
1326  }
1327
1328  private Menu<Integer> getBackendTypeMenu()
1329  {
1330    final MenuBuilder<Integer> builder = new MenuBuilder<>(this);
1331    builder.setPrompt(INFO_INSTALLDS_PROMPT_BACKEND_TYPE.get());
1332    int index = 1;
1333    for (final ManagedObjectDefinition<?, ?> backendType : backendTypeHelper.getBackendTypes())
1334    {
1335      builder.addNumberedOption(backendType.getUserFriendlyName(), MenuResult.success(index++));
1336    }
1337
1338    final int printableIndex = getPromptedBackendTypeIndex();
1339    builder.setDefault(LocalizableMessage.raw(Integer.toString(printableIndex)), MenuResult.success(printableIndex));
1340    return builder.toMenu();
1341  }
1342
1343  private int getPromptedBackendTypeIndex()
1344  {
1345    if (lastResetBackendType != null)
1346    {
1347      return backendTypeHelper.getBackendTypes().indexOf(lastResetBackendType) + 1;
1348    }
1349    return 1;
1350  }
1351
1352  private NewSuffixOptions promptIfRequiredForDataOptions(List<String> baseDNs)
1353  {
1354    NewSuffixOptions dataOptions;
1355    if (argParser.importLDIFArg.isPresent())
1356    {
1357      // Check that the files exist
1358      final List<String> nonExistingFiles = new LinkedList<>();
1359      final List<String> importLDIFFiles = new LinkedList<>();
1360      for (final String file : argParser.importLDIFArg.getValues())
1361      {
1362        if (!Utils.fileExists(file))
1363        {
1364          nonExistingFiles.add(file);
1365        }
1366        else
1367        {
1368          importLDIFFiles.add(file);
1369        }
1370      }
1371      if (!nonExistingFiles.isEmpty())
1372      {
1373        println();
1374        println(ERR_INSTALLDS_NO_SUCH_LDIF_FILE.get(joinAsString(", ", nonExistingFiles)));
1375      }
1376
1377      readImportLdifFile(importLDIFFiles, lastResetImportFile);
1378      String rejectedFile = readValidFilePath(argParser.rejectedImportFileArg, lastResetRejectedFile,
1379          ERR_INSTALLDS_CANNOT_WRITE_REJECTED, INFO_INSTALLDS_PROMPT_REJECTED_FILE);
1380      String skippedFile = readValidFilePath(argParser.skippedImportFileArg, lastResetSkippedFile,
1381          ERR_INSTALLDS_CANNOT_WRITE_SKIPPED, INFO_INSTALLDS_PROMPT_SKIPPED_FILE);
1382      dataOptions = NewSuffixOptions.createImportFromLDIF(baseDNs,
1383          importLDIFFiles, rejectedFile, skippedFile);
1384    }
1385    else if (argParser.addBaseEntryArg.isPresent())
1386    {
1387      dataOptions = NewSuffixOptions.createBaseEntry(baseDNs);
1388    }
1389    else if (argParser.sampleDataArg.isPresent())
1390    {
1391      int numUsers;
1392      try
1393      {
1394        numUsers = argParser.sampleDataArg.getIntValue();
1395      }
1396      catch (final ArgumentException ae)
1397      {
1398        println();
1399        println(ae.getMessageObject());
1400        final LocalizableMessage message = INFO_INSTALLDS_PROMPT_NUM_ENTRIES.get();
1401        numUsers = promptForInteger(message, 2000, 0, Integer.MAX_VALUE);
1402      }
1403      dataOptions = NewSuffixOptions.createAutomaticallyGenerated(baseDNs, numUsers);
1404    }
1405    else
1406    {
1407      final int POPULATE_TYPE_LEAVE_EMPTY = 1;
1408      final int POPULATE_TYPE_BASE_ONLY = 2;
1409      final int POPULATE_TYPE_IMPORT_FROM_LDIF = 3;
1410      final int POPULATE_TYPE_GENERATE_SAMPLE_DATA = 4;
1411
1412      final int[] indexes = {POPULATE_TYPE_LEAVE_EMPTY, POPULATE_TYPE_BASE_ONLY,
1413          POPULATE_TYPE_IMPORT_FROM_LDIF, POPULATE_TYPE_GENERATE_SAMPLE_DATA};
1414      final LocalizableMessage[] msgs = new LocalizableMessage[] {
1415          INFO_INSTALLDS_POPULATE_OPTION_LEAVE_EMPTY.get(),
1416          INFO_INSTALLDS_POPULATE_OPTION_BASE_ONLY.get(),
1417          INFO_INSTALLDS_POPULATE_OPTION_IMPORT_LDIF.get(),
1418          INFO_INSTALLDS_POPULATE_OPTION_GENERATE_SAMPLE.get()
1419      };
1420
1421      final MenuBuilder<Integer> builder = new MenuBuilder<>(this);
1422      builder.setPrompt(INFO_INSTALLDS_HEADER_POPULATE_TYPE.get());
1423
1424      for (int i=0; i<indexes.length; i++)
1425      {
1426        builder.addNumberedOption(msgs[i], MenuResult.success(indexes[i]));
1427      }
1428
1429      if (lastResetPopulateOption == null)
1430      {
1431        builder.setDefault(LocalizableMessage.raw(
1432            String.valueOf(POPULATE_TYPE_LEAVE_EMPTY)),
1433            MenuResult.success(POPULATE_TYPE_LEAVE_EMPTY));
1434      }
1435      else
1436      {
1437        switch (lastResetPopulateOption)
1438        {
1439        case LEAVE_DATABASE_EMPTY:
1440          builder.setDefault(LocalizableMessage.raw(
1441              String.valueOf(POPULATE_TYPE_LEAVE_EMPTY)),
1442              MenuResult.success(POPULATE_TYPE_LEAVE_EMPTY));
1443          break;
1444        case IMPORT_FROM_LDIF_FILE:
1445          builder.setDefault(LocalizableMessage.raw(
1446              String.valueOf(POPULATE_TYPE_IMPORT_FROM_LDIF)),
1447              MenuResult.success(POPULATE_TYPE_IMPORT_FROM_LDIF));
1448          break;
1449        case IMPORT_AUTOMATICALLY_GENERATED_DATA:
1450          builder.setDefault(LocalizableMessage.raw(
1451              String.valueOf(POPULATE_TYPE_GENERATE_SAMPLE_DATA)),
1452              MenuResult.success(POPULATE_TYPE_GENERATE_SAMPLE_DATA));
1453          break;
1454        default:
1455          builder.setDefault(LocalizableMessage.raw(
1456              String.valueOf(POPULATE_TYPE_BASE_ONLY)),
1457              MenuResult.success(POPULATE_TYPE_BASE_ONLY));
1458        }
1459      }
1460
1461      final Menu<Integer> menu = builder.toMenu();
1462      int populateType;
1463      try
1464      {
1465        final MenuResult<Integer> m = menu.run();
1466        if (m.isSuccess())
1467        {
1468          populateType = m.getValue();
1469        }
1470        else
1471        {
1472          // Should never happen.
1473          throw new RuntimeException();
1474        }
1475      }
1476      catch (final ClientException ce)
1477      {
1478        populateType = POPULATE_TYPE_BASE_ONLY;
1479        logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
1480      }
1481
1482      if (populateType == POPULATE_TYPE_IMPORT_FROM_LDIF)
1483      {
1484        final List<String> importLDIFFiles = new LinkedList<>();
1485        readImportLdifFile(importLDIFFiles, null);
1486        String rejectedFile = readValidFilePath(argParser.rejectedImportFileArg, null,
1487            ERR_INSTALLDS_CANNOT_WRITE_REJECTED, INFO_INSTALLDS_PROMPT_REJECTED_FILE);
1488        String skippedFile = readValidFilePath(argParser.skippedImportFileArg, null,
1489            ERR_INSTALLDS_CANNOT_WRITE_SKIPPED, INFO_INSTALLDS_PROMPT_SKIPPED_FILE);
1490        dataOptions = NewSuffixOptions.createImportFromLDIF(baseDNs,
1491            importLDIFFiles, rejectedFile, skippedFile);
1492      }
1493      else if (populateType == POPULATE_TYPE_GENERATE_SAMPLE_DATA)
1494      {
1495        final LocalizableMessage message = INFO_INSTALLDS_PROMPT_NUM_ENTRIES.get();
1496        int defaultValue = lastResetNumEntries != null ? lastResetNumEntries : 2000;
1497        final int numUsers = promptForInteger(message, defaultValue, 0, Integer.MAX_VALUE);
1498        dataOptions = NewSuffixOptions.createAutomaticallyGenerated(baseDNs, numUsers);
1499      }
1500      else if (populateType == POPULATE_TYPE_LEAVE_EMPTY)
1501      {
1502        dataOptions = NewSuffixOptions.createEmpty(baseDNs);
1503      }
1504      else if (populateType == POPULATE_TYPE_BASE_ONLY)
1505      {
1506        dataOptions = NewSuffixOptions.createBaseEntry(baseDNs);
1507      }
1508      else
1509      {
1510        throw new IllegalStateException("Unexpected populateType: " + populateType);
1511      }
1512    }
1513    return dataOptions;
1514  }
1515
1516  private void readImportLdifFile(final List<String> importLDIFFiles, String defaultValue)
1517  {
1518    while (importLDIFFiles.isEmpty())
1519    {
1520      println();
1521      try
1522      {
1523        final String path = readInput(INFO_INSTALLDS_PROMPT_IMPORT_FILE.get(), defaultValue);
1524        if (Utils.fileExists(path))
1525        {
1526          importLDIFFiles.add(path);
1527        }
1528        else
1529        {
1530          println();
1531          println(ERR_INSTALLDS_NO_SUCH_LDIF_FILE.get(path));
1532        }
1533      }
1534      catch (final ClientException ce)
1535      {
1536        logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
1537      }
1538    }
1539  }
1540
1541  private String readValidFilePath(StringArgument arg, String defaultValue, Arg1<Object> errCannotWriteFile,
1542      Arg0 infoPromptFile)
1543  {
1544    String file = arg.getValue();
1545    if (file != null)
1546    {
1547      while (!canWrite(file))
1548      {
1549        println();
1550        println(errCannotWriteFile.get(file));
1551        println();
1552        try
1553        {
1554          file = readInput(infoPromptFile.get(), defaultValue);
1555        }
1556        catch (final ClientException ce)
1557        {
1558          logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
1559        }
1560      }
1561    }
1562    return file;
1563  }
1564
1565  /**
1566   * This method returns what the user specified in the command-line for the
1567   * security parameters. If the user did not provide explicitly some data or if
1568   * the provided data is not valid, it prompts the user to provide it.
1569   *
1570   * @param uData
1571   *          the current UserData object.
1572   * @return the {@link SecurityOptions} to be used when starting the server
1573   * @throws UserDataException
1574   *           if the user did not manage to provide the keystore password after
1575   *           a certain number of tries.
1576   * @throws ClientException
1577   *           If an error occurs when reading inputs.
1578   */
1579  private SecurityOptions promptIfRequiredForSecurityData(UserData uData) throws UserDataException, ClientException
1580  {
1581    // Check that the security data provided is valid.
1582    boolean enableSSL = false;
1583    boolean enableStartTLS = false;
1584    int ldapsPort = -1;
1585
1586    final List<Integer> usedPorts = new LinkedList<>();
1587    usedPorts.add(uData.getServerPort());
1588    if (uData.getServerJMXPort() != -1)
1589    {
1590      usedPorts.add(uData.getServerJMXPort());
1591    }
1592
1593    // Ask to enable SSL
1594    if (!argParser.ldapsPortArg.isPresent())
1595    {
1596      println();
1597      try
1598      {
1599        final boolean defaultValue = lastResetEnableSSL != null ? lastResetEnableSSL : false;
1600        enableSSL = confirmAction(INFO_INSTALLDS_PROMPT_ENABLE_SSL.get(), defaultValue);
1601        if (enableSSL)
1602        {
1603          ldapsPort = promptIfRequiredForPortData(argParser.ldapsPortArg,
1604              INFO_INSTALLDS_PROMPT_LDAPSPORT.get(), usedPorts, false);
1605        }
1606      }
1607      catch (final ClientException ce)
1608      {
1609        logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
1610      }
1611    }
1612    else
1613    {
1614      ldapsPort = promptIfRequiredForPortData(argParser.ldapsPortArg,
1615          INFO_INSTALLDS_PROMPT_LDAPSPORT.get(), usedPorts, true);
1616      enableSSL = true;
1617    }
1618
1619    // Ask to enable Start TLS
1620    if (!argParser.enableStartTLSArg.isPresent())
1621    {
1622      println();
1623      try
1624      {
1625        final boolean defaultValue = lastResetEnableStartTLS != null ?
1626            lastResetEnableStartTLS : false;
1627        enableStartTLS = confirmAction(INFO_INSTALLDS_ENABLE_STARTTLS.get(),
1628            defaultValue);
1629      }
1630      catch (final ClientException ce)
1631      {
1632        logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
1633      }
1634    }
1635    else
1636    {
1637      enableStartTLS = true;
1638    }
1639
1640    SecurityOptions securityOptions;
1641    if (argParser.generateSelfSignedCertificateArg.isPresent())
1642    {
1643      securityOptions = SecurityOptions.createSelfSignedCertificateOptions(
1644          enableSSL, enableStartTLS, ldapsPort);
1645    }
1646    else if (argParser.useJavaKeyStoreArg.isPresent())
1647    {
1648      securityOptions =
1649        createSecurityOptionsPrompting(SecurityOptions.CertificateType.JKS,
1650            enableSSL, enableStartTLS, ldapsPort);
1651    }
1652    else if (argParser.useJCEKSArg.isPresent())
1653    {
1654      securityOptions =
1655        createSecurityOptionsPrompting(SecurityOptions.CertificateType.JCEKS,
1656            enableSSL, enableStartTLS, ldapsPort);
1657    }
1658    else if (argParser.usePkcs12Arg.isPresent())
1659    {
1660      securityOptions =
1661        createSecurityOptionsPrompting(SecurityOptions.CertificateType.PKCS12,
1662            enableSSL, enableStartTLS, ldapsPort);
1663    }
1664    else if (argParser.usePkcs11Arg.isPresent())
1665    {
1666      securityOptions =
1667        createSecurityOptionsPrompting(SecurityOptions.CertificateType.PKCS11,
1668            enableSSL, enableStartTLS, ldapsPort);
1669    }
1670    else if (!enableSSL && !enableStartTLS)
1671    {
1672      // If the user did not want to enable SSL or start TLS do not ask
1673      // to create a certificate.
1674      securityOptions = SecurityOptions.createNoCertificateOptions();
1675    }
1676    else
1677    {
1678      final int SELF_SIGNED = 1;
1679      final int JKS = 2;
1680      final int JCEKS = 3;
1681      final int PKCS12 = 4;
1682      final int PKCS11 = 5;
1683      final int[] indexes = {SELF_SIGNED, JKS, JCEKS, PKCS12, PKCS11};
1684      final LocalizableMessage[] msgs = {
1685          INFO_INSTALLDS_CERT_OPTION_SELF_SIGNED.get(),
1686          INFO_INSTALLDS_CERT_OPTION_JKS.get(),
1687          INFO_INSTALLDS_CERT_OPTION_JCEKS.get(),
1688          INFO_INSTALLDS_CERT_OPTION_PKCS12.get(),
1689          INFO_INSTALLDS_CERT_OPTION_PKCS11.get()
1690      };
1691
1692
1693      final MenuBuilder<Integer> builder = new MenuBuilder<>(this);
1694      builder.setPrompt(INFO_INSTALLDS_HEADER_CERT_TYPE.get());
1695
1696      for (int i=0; i<indexes.length; i++)
1697      {
1698        builder.addNumberedOption(msgs[i], MenuResult.success(indexes[i]));
1699      }
1700
1701      if (lastResetCertType == null)
1702      {
1703        builder.setDefault(LocalizableMessage.raw(String.valueOf(SELF_SIGNED)),
1704          MenuResult.success(SELF_SIGNED));
1705      }
1706      else
1707      {
1708        switch (lastResetCertType)
1709        {
1710        case JKS:
1711          builder.setDefault(LocalizableMessage.raw(String.valueOf(JKS)),
1712              MenuResult.success(JKS));
1713          break;
1714        case JCEKS:
1715          builder.setDefault(LocalizableMessage.raw(String.valueOf(JCEKS)),
1716              MenuResult.success(JCEKS));
1717          break;
1718        case PKCS11:
1719          builder.setDefault(LocalizableMessage.raw(String.valueOf(PKCS11)),
1720              MenuResult.success(PKCS11));
1721          break;
1722        case PKCS12:
1723          builder.setDefault(LocalizableMessage.raw(String.valueOf(PKCS12)),
1724              MenuResult.success(PKCS12));
1725          break;
1726        default:
1727          builder.setDefault(LocalizableMessage.raw(String.valueOf(SELF_SIGNED)),
1728              MenuResult.success(SELF_SIGNED));
1729        }
1730      }
1731
1732      final Menu<Integer> menu = builder.toMenu();
1733      int certType;
1734      try
1735      {
1736        final MenuResult<Integer> m = menu.run();
1737        if (m.isSuccess())
1738        {
1739          certType = m.getValue();
1740        }
1741        else
1742        {
1743          // Should never happen.
1744          throw new RuntimeException();
1745        }
1746      }
1747      catch (final ClientException ce)
1748      {
1749        logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
1750        certType = SELF_SIGNED;
1751      }
1752      if (certType == SELF_SIGNED)
1753      {
1754        securityOptions = SecurityOptions.createSelfSignedCertificateOptions(
1755              enableSSL, enableStartTLS, ldapsPort);
1756      }
1757      else if (certType == JKS)
1758      {
1759        securityOptions =
1760          createSecurityOptionsPrompting(SecurityOptions.CertificateType.JKS,
1761              enableSSL, enableStartTLS, ldapsPort);
1762      }
1763      else if (certType == JCEKS)
1764      {
1765        securityOptions =
1766          createSecurityOptionsPrompting(
1767              SecurityOptions.CertificateType.JCEKS,
1768              enableSSL, enableStartTLS, ldapsPort);
1769      }
1770      else if (certType == PKCS12)
1771      {
1772        securityOptions =
1773          createSecurityOptionsPrompting(
1774              SecurityOptions.CertificateType.PKCS12, enableSSL,
1775              enableStartTLS, ldapsPort);
1776      }
1777      else if (certType == PKCS11)
1778      {
1779        securityOptions =
1780          createSecurityOptionsPrompting(
1781              SecurityOptions.CertificateType.PKCS11, enableSSL,
1782              enableStartTLS, ldapsPort);
1783      }
1784      else
1785      {
1786        throw new IllegalStateException("Unexpected cert type: "+ certType);
1787      }
1788    }
1789    return securityOptions;
1790  }
1791
1792  /**
1793   * This method returns what the user specified in the command-line for the
1794   * Windows Service parameters. If the user did not provide explicitly the
1795   * data, it prompts the user to provide it.
1796   *
1797   * @return whether windows service should be enabled
1798   */
1799  private boolean promptIfRequiredForWindowsService()
1800  {
1801    boolean enableService = false;
1802    // If we are in Windows ask if the server must run as a windows service.
1803    if (isWindows())
1804    {
1805      if (argParser.enableWindowsServiceArg.isPresent())
1806      {
1807        enableService = true;
1808      }
1809      else
1810      {
1811        println();
1812        final LocalizableMessage message = INFO_INSTALLDS_PROMPT_ENABLE_SERVICE.get();
1813        try
1814        {
1815          final boolean defaultValue = (lastResetEnableWindowsService == null) ?
1816              false : lastResetEnableWindowsService;
1817          enableService = confirmAction(message, defaultValue);
1818        }
1819        catch (final ClientException ce)
1820        {
1821          logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
1822        }
1823      }
1824    }
1825    return enableService;
1826  }
1827
1828  /**
1829   * This method returns what the user specified in the command-line for the
1830   * Directory Manager parameters. If the user did not provide explicitly the
1831   * data, it prompts the user to provide it.
1832   *
1833   * @return whether server should be started
1834   */
1835  private boolean promptIfRequiredForStartServer()
1836  {
1837    boolean startServer = false;
1838    if (!argParser.doNotStartArg.isPresent())
1839    {
1840      println();
1841      final LocalizableMessage message = INFO_INSTALLDS_PROMPT_START_SERVER.get();
1842      try
1843      {
1844        final boolean defaultValue = (lastResetStartServer == null) ?
1845            true : lastResetStartServer;
1846        startServer = confirmAction(message, defaultValue);
1847      }
1848      catch (final ClientException ce)
1849      {
1850        logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
1851        startServer = true;
1852      }
1853    }
1854    return startServer;
1855  }
1856
1857  /**
1858   * Checks that the provided parameters are valid to access an existing key
1859   * store. This method adds the encountered errors to the provided list of
1860   * LocalizableMessage. It also adds the alias (nicknames) found to the
1861   * provided list of String.
1862   *
1863   * @param type
1864   *          the type of key store.
1865   * @param path
1866   *          the path of the key store.
1867   * @param pwd
1868   *          the password (PIN) to access the key store.
1869   * @param certNicknames
1870   *          the certificate nicknames that we are looking for (or null if we
1871   *          just one to get the one that is in the key store).
1872   * @param errorMessages
1873   *          the list that will be updated with the errors encountered.
1874   * @param nicknameList
1875   *          the list that will be updated with the nicknames found in the key
1876   *          store.
1877   */
1878  public static void checkCertificateInKeystore(SecurityOptions.CertificateType type, String path, String pwd,
1879      Collection<String> certNicknames, Collection<LocalizableMessage> errorMessages, Collection<String> nicknameList)
1880  {
1881    boolean errorWithPath = false;
1882    if (type != SecurityOptions.CertificateType.PKCS11)
1883    {
1884      final File f = new File(path);
1885      if (!f.exists())
1886      {
1887        errorMessages.add(INFO_KEYSTORE_PATH_DOES_NOT_EXIST.get());
1888        errorWithPath = true;
1889      }
1890      else if (!f.isFile())
1891      {
1892        errorMessages.add(INFO_KEYSTORE_PATH_NOT_A_FILE.get());
1893        errorWithPath = true;
1894      }
1895    }
1896    if (!errorWithPath)
1897    {
1898      try
1899      {
1900        CertificateManager certManager;
1901        switch (type)
1902        {
1903          case JKS:
1904          certManager = new CertificateManager(
1905              path,
1906              CertificateManager.KEY_STORE_TYPE_JKS,
1907              pwd);
1908          break;
1909
1910          case JCEKS:
1911            certManager = new CertificateManager(
1912                path,
1913                CertificateManager.KEY_STORE_TYPE_JCEKS,
1914                pwd);
1915            break;
1916
1917          case PKCS12:
1918          certManager = new CertificateManager(
1919              path,
1920              CertificateManager.KEY_STORE_TYPE_PKCS12,
1921              pwd);
1922          break;
1923
1924          case PKCS11:
1925          certManager = new CertificateManager(
1926              CertificateManager.KEY_STORE_PATH_PKCS11,
1927              CertificateManager.KEY_STORE_TYPE_PKCS11,
1928              pwd);
1929          break;
1930
1931          default:
1932            throw new IllegalArgumentException("Invalid type: "+type);
1933        }
1934        final String[] aliases = certManager.getCertificateAliases();
1935        if (aliases == null || aliases.length == 0)
1936        {
1937          // Could not retrieve any certificate
1938          switch (type)
1939          {
1940          case JKS:
1941            errorMessages.add(INFO_JKS_KEYSTORE_DOES_NOT_EXIST.get());
1942            break;
1943          case JCEKS:
1944            errorMessages.add(INFO_JCEKS_KEYSTORE_DOES_NOT_EXIST.get());
1945            break;
1946          case PKCS12:
1947            errorMessages.add(INFO_PKCS12_KEYSTORE_DOES_NOT_EXIST.get());
1948            break;
1949          case PKCS11:
1950            errorMessages.add(INFO_PKCS11_KEYSTORE_DOES_NOT_EXIST.get());
1951            break;
1952          default:
1953            throw new IllegalArgumentException("Invalid type: "+type);
1954          }
1955        }
1956        else if (certManager.hasRealAliases())
1957        {
1958          Collections.addAll(nicknameList, aliases);
1959          final String aliasString = joinAsString(", ", nicknameList);
1960          if (certNicknames.isEmpty() && aliases.length > 1)
1961          {
1962            errorMessages.add(ERR_INSTALLDS_MUST_PROVIDE_CERTNICKNAME.get(aliasString));
1963          }
1964          for (String certNickname : certNicknames)
1965          {
1966            // Check if the certificate alias is in the list.
1967            boolean found = false;
1968            for (int i = 0; i < aliases.length && !found; i++)
1969            {
1970              found = aliases[i].equalsIgnoreCase(certNickname);
1971            }
1972            if (!found)
1973            {
1974              errorMessages.add(ERR_INSTALLDS_CERTNICKNAME_NOT_FOUND.get(aliasString));
1975            }
1976          }
1977        }
1978      }
1979      catch (final KeyStoreException ke)
1980      {
1981        // issue OPENDJ-18, related to JDK bug
1982        if (StaticUtils.stackTraceContainsCause(ke, ArithmeticException.class))
1983        {
1984          errorMessages.add(INFO_ERROR_ACCESSING_KEYSTORE_JDK_BUG.get());
1985        }
1986        else
1987        {
1988          // Could not access to the key store: because the password is no good,
1989          // because the provided file is not a valid key store, etc.
1990          switch (type)
1991          {
1992          case JKS:
1993            errorMessages.add(INFO_ERROR_ACCESSING_JKS_KEYSTORE.get());
1994            break;
1995          case JCEKS:
1996            errorMessages.add(INFO_ERROR_ACCESSING_JCEKS_KEYSTORE.get());
1997            break;
1998          case PKCS12:
1999            errorMessages.add(INFO_ERROR_ACCESSING_PKCS12_KEYSTORE.get());
2000            break;
2001          case PKCS11:
2002            errorMessages.add(INFO_ERROR_ACCESSING_PKCS11_KEYSTORE.get());
2003            break;
2004          default:
2005            throw new IllegalArgumentException("Invalid type: " + type, ke);
2006          }
2007        }
2008      }
2009    }
2010  }
2011
2012  /**
2013   * Creates a SecurityOptions object that corresponds to the provided
2014   * parameters. If the parameters are not valid, it prompts the user to provide
2015   * them.
2016   *
2017   * @param type
2018   *          the keystore type.
2019   * @param enableSSL
2020   *          whether to enable SSL or not.
2021   * @param enableStartTLS
2022   *          whether to enable StartTLS or not.
2023   * @param ldapsPort
2024   *          the LDAPS port to use.
2025   * @return a SecurityOptions object that corresponds to the provided
2026   *         parameters (or to what the user provided after being prompted).
2027   * @throws UserDataException
2028   *           if the user did not manage to provide the keystore password after
2029   *           a certain number of tries.
2030   * @throws ClientException
2031   */
2032  private SecurityOptions createSecurityOptionsPrompting(SecurityOptions.CertificateType type, boolean enableSSL,
2033      boolean enableStartTLS, int ldapsPort) throws UserDataException, ClientException
2034  {
2035    SecurityOptions securityOptions;
2036    String path;
2037    Collection<String> certNicknames = argParser.certNicknameArg.getValues();
2038    String pwd = argParser.getKeyStorePassword();
2039    if (pwd != null && pwd.length() == 0)
2040    {
2041      pwd = null;
2042    }
2043    LocalizableMessage pathPrompt;
2044    String defaultPathValue;
2045
2046    switch (type)
2047    {
2048    case JKS:
2049      path = argParser.useJavaKeyStoreArg.getValue();
2050      pathPrompt = INFO_INSTALLDS_PROMPT_JKS_PATH.get();
2051      defaultPathValue = argParser.useJavaKeyStoreArg.getValue();
2052      if (defaultPathValue == null)
2053      {
2054        defaultPathValue = lastResetKeyStorePath;
2055      }
2056      break;
2057    case JCEKS:
2058      path = argParser.useJCEKSArg.getValue();
2059      pathPrompt = INFO_INSTALLDS_PROMPT_JCEKS_PATH.get();
2060      defaultPathValue = argParser.useJCEKSArg.getValue();
2061      if (defaultPathValue == null)
2062      {
2063        defaultPathValue = lastResetKeyStorePath;
2064      }
2065      break;
2066    case PKCS11:
2067      path = null;
2068      defaultPathValue = null;
2069      pathPrompt = null;
2070      break;
2071    case PKCS12:
2072      path = argParser.usePkcs12Arg.getValue();
2073      defaultPathValue = argParser.usePkcs12Arg.getValue();
2074      if (defaultPathValue == null)
2075      {
2076        defaultPathValue = lastResetKeyStorePath;
2077      }
2078      pathPrompt = INFO_INSTALLDS_PROMPT_PKCS12_PATH.get();
2079      break;
2080    default:
2081      throw new IllegalStateException(
2082          "Called promptIfRequiredCertificate with invalid type: "+type);
2083    }
2084    final List<LocalizableMessage> errorMessages = new LinkedList<>();
2085    final LinkedList<String> keystoreAliases = new LinkedList<>();
2086    boolean firstTry = true;
2087    int nPasswordPrompts = 0;
2088
2089    while (!errorMessages.isEmpty() || firstTry)
2090    {
2091      boolean prompted = false;
2092      if (!errorMessages.isEmpty())
2093      {
2094        println();
2095        println(Utils.getMessageFromCollection(errorMessages,
2096            formatter.getLineBreak().toString()));
2097      }
2098
2099      if (type != SecurityOptions.CertificateType.PKCS11
2100          && (containsKeyStorePathErrorMessage(errorMessages) || path == null))
2101      {
2102        println();
2103        try
2104        {
2105          path = readInput(pathPrompt, defaultPathValue);
2106        }
2107        catch (final ClientException ce)
2108        {
2109          path = "";
2110          logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
2111        }
2112
2113        prompted = true;
2114        if (pwd != null)
2115        {
2116          errorMessages.clear();
2117          keystoreAliases.clear();
2118          checkCertificateInKeystore(type, path, pwd, certNicknames, errorMessages, keystoreAliases);
2119          if (!errorMessages.isEmpty())
2120          {
2121            // Reset password: this might be a new keystore
2122            pwd = null;
2123          }
2124        }
2125      }
2126      if (containsKeyStorePasswordErrorMessage(errorMessages) || pwd == null)
2127      {
2128        if (!prompted)
2129        {
2130          println();
2131        }
2132        pwd = null;
2133        while (pwd == null)
2134        {
2135          if (nPasswordPrompts > LIMIT_KEYSTORE_PASSWORD_PROMPT)
2136          {
2137            throw new UserDataException(null,
2138                ERR_INSTALLDS_TOO_MANY_KEYSTORE_PASSWORD_TRIES.get(LIMIT_KEYSTORE_PASSWORD_PROMPT));
2139          }
2140          pwd = String.valueOf(readPassword(INFO_INSTALLDS_PROMPT_KEYSTORE_PASSWORD.get()));
2141          nPasswordPrompts ++;
2142        }
2143      }
2144      if (containsCertNicknameErrorMessage(errorMessages))
2145      {
2146        if (!prompted)
2147        {
2148          println();
2149        }
2150        certNicknames = promptForCertificateNickname(keystoreAliases);
2151      }
2152      errorMessages.clear();
2153      keystoreAliases.clear();
2154      checkCertificateInKeystore(type, path, pwd, certNicknames, errorMessages,
2155          keystoreAliases);
2156      firstTry = false;
2157    }
2158    if (certNicknames.isEmpty() && !keystoreAliases.isEmpty())
2159    {
2160      certNicknames = Arrays.asList(keystoreAliases.getFirst());
2161    }
2162    switch (type)
2163    {
2164    case JKS:
2165      return SecurityOptions.createJKSCertificateOptions(path, pwd, enableSSL, enableStartTLS, ldapsPort,
2166          certNicknames);
2167    case JCEKS:
2168      return SecurityOptions.createJCEKSCertificateOptions(path, pwd, enableSSL, enableStartTLS, ldapsPort,
2169          certNicknames);
2170    case PKCS12:
2171      return SecurityOptions.createPKCS12CertificateOptions(path, pwd, enableSSL, enableStartTLS, ldapsPort,
2172          certNicknames);
2173    case PKCS11:
2174      return SecurityOptions.createPKCS11CertificateOptions(pwd, enableSSL, enableStartTLS, ldapsPort, certNicknames);
2175    default:
2176      throw new IllegalStateException("Called createSecurityOptionsPrompting with invalid type: " + type);
2177    }
2178  }
2179
2180  /**
2181   * Tells if any of the error messages provided corresponds to a problem with
2182   * the key store path.
2183   *
2184   * @param msgs
2185   *          the messages to analyze.
2186   * @return <CODE>true</CODE> if any of the error messages provided corresponds
2187   *         to a problem with the key store path and <CODE>false</CODE>
2188   *         otherwise.
2189   */
2190  public static boolean containsKeyStorePathErrorMessage(Collection<LocalizableMessage> msgs)
2191  {
2192    for (final LocalizableMessage msg : msgs)
2193    {
2194      if (StaticUtils.hasDescriptor(msg, INFO_KEYSTORE_PATH_DOES_NOT_EXIST) ||
2195          StaticUtils.hasDescriptor(msg, INFO_KEYSTORE_PATH_NOT_A_FILE) ||
2196          StaticUtils.hasDescriptor(msg, INFO_JKS_KEYSTORE_DOES_NOT_EXIST) ||
2197          StaticUtils.hasDescriptor(msg, INFO_JCEKS_KEYSTORE_DOES_NOT_EXIST) ||
2198          StaticUtils.hasDescriptor(msg, INFO_PKCS12_KEYSTORE_DOES_NOT_EXIST) ||
2199          StaticUtils.hasDescriptor(msg, INFO_PKCS11_KEYSTORE_DOES_NOT_EXIST) ||
2200          StaticUtils.hasDescriptor(msg, INFO_ERROR_ACCESSING_JKS_KEYSTORE) ||
2201          StaticUtils.hasDescriptor(msg, INFO_ERROR_ACCESSING_JCEKS_KEYSTORE) ||
2202          StaticUtils.hasDescriptor(msg, INFO_ERROR_ACCESSING_PKCS12_KEYSTORE) ||
2203          StaticUtils.hasDescriptor(msg, INFO_ERROR_ACCESSING_PKCS11_KEYSTORE))
2204      {
2205        return true;
2206      }
2207    }
2208    return false;
2209  }
2210
2211  /**
2212   * Tells if any of the error messages provided corresponds to a problem with
2213   * the key store password.
2214   *
2215   * @param msgs
2216   *          the messages to analyze.
2217   * @return <CODE>true</CODE> if any of the error messages provided corresponds
2218   *         to a problem with the key store password and <CODE>false</CODE>
2219   *         otherwise.
2220   */
2221  public static boolean containsKeyStorePasswordErrorMessage(Collection<LocalizableMessage> msgs)
2222  {
2223    for (final LocalizableMessage msg : msgs)
2224    {
2225      if (StaticUtils.hasDescriptor(msg, INFO_JKS_KEYSTORE_DOES_NOT_EXIST) ||
2226          StaticUtils.hasDescriptor(msg, INFO_JCEKS_KEYSTORE_DOES_NOT_EXIST) ||
2227          StaticUtils.hasDescriptor(msg, INFO_PKCS12_KEYSTORE_DOES_NOT_EXIST) ||
2228          StaticUtils.hasDescriptor(msg, INFO_PKCS11_KEYSTORE_DOES_NOT_EXIST) ||
2229          StaticUtils.hasDescriptor(msg, INFO_ERROR_ACCESSING_JKS_KEYSTORE) ||
2230          StaticUtils.hasDescriptor(msg, INFO_ERROR_ACCESSING_JCEKS_KEYSTORE) ||
2231          StaticUtils.hasDescriptor(msg, INFO_ERROR_ACCESSING_PKCS12_KEYSTORE) ||
2232          StaticUtils.hasDescriptor(msg, INFO_ERROR_ACCESSING_PKCS11_KEYSTORE) ||
2233          StaticUtils.hasDescriptor(msg, INFO_ERROR_ACCESSING_KEYSTORE_JDK_BUG))
2234      {
2235        return true;
2236      }
2237    }
2238    return false;
2239  }
2240
2241  /**
2242   * Tells if any of the error messages provided corresponds to a problem with
2243   * the certificate nickname.
2244   *
2245   * @param msgs
2246   *          the messages to analyze.
2247   * @return <CODE>true</CODE> if any of the error messages provided corresponds
2248   *         to a problem with the certificate nickname and <CODE>false</CODE>
2249   *         otherwise.
2250   */
2251  public static boolean containsCertNicknameErrorMessage(
2252      Collection<LocalizableMessage> msgs)
2253  {
2254    boolean found = false;
2255    for (final LocalizableMessage msg : msgs)
2256    {
2257      if (StaticUtils.hasDescriptor(msg, ERR_INSTALLDS_CERTNICKNAME_NOT_FOUND) ||
2258          StaticUtils.hasDescriptor(msg, ERR_INSTALLDS_MUST_PROVIDE_CERTNICKNAME))
2259      {
2260        found = true;
2261        break;
2262      }
2263    }
2264    return found;
2265  }
2266
2267  /**
2268   * Interactively prompts (on standard output) the user to provide an integer
2269   * value. The answer provided must be parseable as an integer, and may be
2270   * required to be within a given set of bounds. It will keep prompting until
2271   * an acceptable value is given.
2272   *
2273   * @param prompt
2274   *          The prompt to present to the user.
2275   * @param defaultValue
2276   *          The default value to assume if the user presses ENTER without
2277   *          typing anything, or <CODE>null</CODE> if there should not be a
2278   *          default and the user must explicitly provide a value.
2279   * @param lowerBound
2280   *          The lower bound that should be enforced, or <CODE>null</CODE> if
2281   *          there is none.
2282   * @param upperBound
2283   *          The upper bound that should be enforced, or <CODE>null</CODE> if
2284   *          there is none.
2285   * @return The <CODE>int</CODE> value read from the user input.
2286   */
2287  private int promptForInteger(LocalizableMessage prompt, Integer defaultValue, Integer lowerBound, Integer upperBound)
2288  {
2289    int returnValue = -1;
2290    while (returnValue == -1)
2291    {
2292      String s;
2293      try
2294      {
2295        s = readInput(prompt, String.valueOf(defaultValue));
2296      }
2297      catch (final ClientException ce)
2298      {
2299        s = "";
2300        logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
2301      }
2302      if ("".equals(s))
2303      {
2304        if (defaultValue == null)
2305        {
2306          println(ERR_INSTALLDS_INVALID_INTEGER_RESPONSE.get());
2307          println();
2308        }
2309        else
2310        {
2311          returnValue = defaultValue;
2312        }
2313      }
2314      else
2315      {
2316        try
2317        {
2318          final int intValue = Integer.parseInt(s);
2319          if (lowerBound != null && intValue < lowerBound)
2320          {
2321            println(ERR_INSTALLDS_INTEGER_BELOW_LOWER_BOUND.get(lowerBound));
2322            println();
2323          }
2324          else if (upperBound != null && intValue > upperBound)
2325          {
2326            println(ERR_INSTALLDS_INTEGER_ABOVE_UPPER_BOUND.get(upperBound));
2327            println();
2328          }
2329          else
2330          {
2331            returnValue = intValue;
2332          }
2333        }
2334        catch (final NumberFormatException nfe)
2335        {
2336          println(ERR_INSTALLDS_INVALID_INTEGER_RESPONSE.get());
2337          println();
2338        }
2339      }
2340    }
2341    return returnValue;
2342  }
2343
2344  /**
2345   * Prompts the user to accept on the certificates that appears on the list and
2346   * returns the chosen certificate nickname.
2347   *
2348   * @param nicknames
2349   *          the list of certificates the user must choose from.
2350   * @return the chosen certificate nickname.
2351   */
2352  private Collection<String> promptForCertificateNickname(List<String> nicknames)
2353  {
2354    Collection<String> choosenNicknames = new ArrayList<>();
2355    while (choosenNicknames.isEmpty())
2356    {
2357      for (final String n : nicknames)
2358      {
2359        try
2360        {
2361          if (confirmAction(INFO_INSTALLDS_PROMPT_CERTNICKNAME.get(n), true))
2362          {
2363            choosenNicknames.add(n);
2364          }
2365        }
2366        catch (final ClientException ce)
2367        {
2368          logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
2369        }
2370      }
2371    }
2372    return choosenNicknames;
2373  }
2374
2375  /**
2376   * It displays the information provided by the user.
2377   *
2378   * @param uData
2379   *          the UserData that the user provided.
2380   */
2381  private void printSummary(UserData uData)
2382  {
2383    println();
2384    println();
2385    println(INFO_INSTALLDS_SUMMARY.get());
2386    final LocalizableMessage[] labels =
2387    {
2388        INFO_SERVER_PORT_LABEL.get(),
2389        INFO_ADMIN_CONNECTOR_PORT_LABEL.get(),
2390        INFO_INSTALLDS_SERVER_JMXPORT_LABEL.get(),
2391        INFO_SERVER_SECURITY_LABEL.get(),
2392        INFO_SERVER_DIRECTORY_MANAGER_DN_LABEL.get(),
2393        INFO_DIRECTORY_DATA_LABEL.get()
2394    };
2395
2396    final int jmxPort = uData.getServerJMXPort();
2397
2398    final LocalizableMessage[] values =
2399    {
2400        LocalizableMessage.raw(String.valueOf(uData.getServerPort())),
2401        LocalizableMessage.raw(String.valueOf(uData.getAdminConnectorPort())),
2402        LocalizableMessage.raw(jmxPort != -1 ? String.valueOf(jmxPort) : ""),
2403        LocalizableMessage.raw(
2404            Utils.getSecurityOptionsString(uData.getSecurityOptions(), false)),
2405        LocalizableMessage.raw(uData.getDirectoryManagerDn()),
2406        LocalizableMessage.raw(Utils.getDataDisplayString(uData)),
2407    };
2408    int maxWidth = 0;
2409    for (final LocalizableMessage l : labels)
2410    {
2411      maxWidth = Math.max(maxWidth, l.length());
2412    }
2413
2414    for (int i=0; i<labels.length; i++)
2415    {
2416      StringBuilder sb = new StringBuilder();
2417      if (values[i] != null)
2418      {
2419        final LocalizableMessage l = labels[i];
2420        sb.append(l).append(" ");
2421
2422        final String[] lines = values[i].toString().split(Constants.LINE_SEPARATOR);
2423        for (int j=0; j<lines.length; j++)
2424        {
2425          if (j != 0)
2426          {
2427            for (int k=0; k <= maxWidth; k++)
2428            {
2429              sb.append(" ");
2430            }
2431          }
2432          else
2433          {
2434            for (int k=0; k<maxWidth - l.length(); k++)
2435            {
2436              sb.append(" ");
2437            }
2438          }
2439          sb.append(lines[j]);
2440          println(LocalizableMessage.raw(sb));
2441          sb = new StringBuilder();
2442        }
2443      }
2444    }
2445
2446    println();
2447    if (uData.getStartServer())
2448    {
2449      println(INFO_INSTALLDS_START_SERVER.get());
2450    }
2451    else
2452    {
2453      println(INFO_INSTALLDS_DO_NOT_START_SERVER.get());
2454    }
2455
2456    if (isWindows())
2457    {
2458      if (uData.getEnableWindowsService())
2459      {
2460        println(INFO_INSTALLDS_ENABLE_WINDOWS_SERVICE.get());
2461      }
2462      else
2463      {
2464        println(INFO_INSTALLDS_DO_NOT_ENABLE_WINDOWS_SERVICE.get());
2465      }
2466    }
2467  }
2468
2469  private void printEquivalentCommandLine(UserData uData)
2470  {
2471    println();
2472
2473    println(INFO_INSTALL_SETUP_EQUIVALENT_COMMAND_LINE.get());
2474    println();
2475    final List<String> cmd = Utils.getSetupEquivalentCommandLine(uData);
2476    println(LocalizableMessage.raw(Utils.getFormattedEquivalentCommandLine(cmd, formatter)));
2477  }
2478
2479  /**
2480   * This method asks the user to confirm to continue the setup. It basically
2481   * displays the information provided by the user and at the end proposes a
2482   * menu with the different options to choose from.
2483   *
2484   * @return the answer provided by the user: cancel setup, continue setup or
2485   *         provide information again.
2486   */
2487  private ConfirmCode askForConfirmation()
2488  {
2489    ConfirmCode returnValue;
2490
2491    println();
2492    println();
2493
2494    final LocalizableMessage[] msgs = new LocalizableMessage[] {
2495        INFO_INSTALLDS_CONFIRM_INSTALL.get(),
2496        INFO_INSTALLDS_PROVIDE_DATA_AGAIN.get(),
2497        INFO_INSTALLDS_PRINT_EQUIVALENT_COMMAND_LINE.get(),
2498        INFO_INSTALLDS_CANCEL.get()
2499      };
2500
2501    final MenuBuilder<ConfirmCode> builder = new MenuBuilder<>(this);
2502    builder.setPrompt(INFO_INSTALLDS_CONFIRM_INSTALL_PROMPT.get());
2503
2504    int i=0;
2505    for (final ConfirmCode code : ConfirmCode.values())
2506    {
2507      builder.addNumberedOption(msgs[i], MenuResult.success(code));
2508      i++;
2509    }
2510
2511    builder.setDefault(LocalizableMessage.raw(
2512            String.valueOf(ConfirmCode.CONTINUE.getReturnCode())),
2513            MenuResult.success(ConfirmCode.CONTINUE));
2514
2515    final Menu<ConfirmCode> menu = builder.toMenu();
2516
2517    try
2518    {
2519      final MenuResult<ConfirmCode> m = menu.run();
2520      if (m.isSuccess())
2521      {
2522        returnValue = m.getValue();
2523      }
2524      else
2525      {
2526        // Should never happen.
2527        throw new RuntimeException();
2528      }
2529    }
2530    catch (final ClientException ce)
2531    {
2532      returnValue = ConfirmCode.CANCEL;
2533      logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
2534    }
2535    return returnValue;
2536  }
2537
2538  private void resetArguments(UserData uData)
2539  {
2540    argParser = new InstallDSArgumentParser(InstallDS.class.getName());
2541    try
2542    {
2543      argParser.initializeArguments();
2544      argParser.directoryManagerDNArg.setDefaultValue(uData.getDirectoryManagerDn());
2545      argParser.ldapPortArg.setDefaultValue(String.valueOf(uData.getServerPort()));
2546      argParser.adminConnectorPortArg.setDefaultValue(String.valueOf(uData.getAdminConnectorPort()));
2547
2548      final int jmxPort = uData.getServerJMXPort();
2549      if (jmxPort != -1)
2550      {
2551        argParser.jmxPortArg.setDefaultValue(String.valueOf(jmxPort));
2552      }
2553
2554      final LinkedList<String> baseDNs = uData.getNewSuffixOptions().getBaseDns();
2555      if (!baseDNs.isEmpty())
2556      {
2557        argParser.baseDNArg.setDefaultValue(baseDNs.getFirst());
2558      }
2559
2560      final NewSuffixOptions suffixOptions = uData.getNewSuffixOptions();
2561      lastResetPopulateOption = suffixOptions.getType();
2562
2563      if (NewSuffixOptions.Type.IMPORT_AUTOMATICALLY_GENERATED_DATA == lastResetPopulateOption)
2564      {
2565        lastResetNumEntries = suffixOptions.getNumberEntries();
2566      }
2567      else if (NewSuffixOptions.Type.IMPORT_FROM_LDIF_FILE == lastResetPopulateOption)
2568      {
2569        lastResetImportFile = suffixOptions.getLDIFPaths().getFirst();
2570        lastResetRejectedFile = suffixOptions.getRejectedFile();
2571        lastResetSkippedFile = suffixOptions.getSkippedFile();
2572      }
2573
2574      final SecurityOptions sec = uData.getSecurityOptions();
2575      if (sec.getEnableSSL())
2576      {
2577        argParser.ldapsPortArg.setDefaultValue(String.valueOf(sec.getSslPort()));
2578      }
2579      lastResetEnableSSL = sec.getEnableSSL();
2580      lastResetEnableStartTLS = sec.getEnableStartTLS();
2581      lastResetCertType = sec.getCertificateType();
2582      if (SecurityOptions.CertificateType.JKS == lastResetCertType
2583          || SecurityOptions.CertificateType.JCEKS == lastResetCertType
2584          || SecurityOptions.CertificateType.PKCS12 == lastResetCertType)
2585      {
2586        lastResetKeyStorePath = sec.getKeystorePath();
2587      }
2588      else
2589      {
2590        lastResetKeyStorePath = null;
2591      }
2592
2593      lastResetEnableWindowsService = uData.getEnableWindowsService();
2594      lastResetStartServer = uData.getStartServer();
2595      lastResetBackendType = uData.getBackendType();
2596    }
2597    catch (final Throwable t)
2598    {
2599      logger.warn(LocalizableMessage.raw("Error resetting arguments: " + t, t));
2600    }
2601  }
2602
2603  private String promptForHostNameIfRequired()
2604  {
2605    String hostName = null;
2606    if (argParser.hostNameArg.isPresent())
2607    {
2608      hostName = argParser.hostNameArg.getValue();
2609    }
2610    else
2611    {
2612      println();
2613      while (hostName == null)
2614      {
2615        try
2616        {
2617          hostName = readInput(INFO_INSTALLDS_PROMPT_HOST_NAME.get(), argParser.hostNameArg.getDefaultValue());
2618        }
2619        catch (final ClientException ce)
2620        {
2621          logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
2622        }
2623      }
2624    }
2625    return hostName;
2626  }
2627
2628  /**
2629   * Returns the timeout to be used to connect in milliseconds. The method must
2630   * be called after parsing the arguments.
2631   *
2632   * @return the timeout to be used to connect in milliseconds. Returns
2633   *         {@code 0} if there is no timeout.
2634   */
2635  private int getConnectTimeout()
2636  {
2637    return argParser.getConnectTimeout();
2638  }
2639
2640}