001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2006-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2011 profiq s.r.o.
016 * Portions Copyright 2011-2016 ForgeRock AS.
017 */
018package org.opends.server.tools;
019
020import static org.forgerock.util.Utils.*;
021import static org.opends.messages.AdminToolMessages.*;
022import static org.opends.messages.QuickSetupMessages.*;
023import static org.opends.messages.ToolMessages.*;
024import static org.opends.messages.UtilityMessages.*;
025
026import static com.forgerock.opendj.cli.Utils.*;
027import static com.forgerock.opendj.util.OperatingSystem.*;
028
029import java.io.BufferedReader;
030import java.io.File;
031import java.io.IOException;
032import java.io.InputStream;
033import java.io.InputStreamReader;
034import java.io.OutputStream;
035import java.io.PrintStream;
036import java.security.KeyStoreException;
037import java.util.ArrayList;
038import java.util.Arrays;
039import java.util.Collection;
040import java.util.Collections;
041import java.util.LinkedList;
042import java.util.List;
043
044import javax.naming.ldap.LdapName;
045
046import org.forgerock.i18n.LocalizableMessage;
047import org.forgerock.i18n.LocalizableMessageDescriptor.Arg0;
048import org.forgerock.i18n.LocalizableMessageDescriptor.Arg1;
049import org.forgerock.i18n.slf4j.LocalizedLogger;
050import org.forgerock.opendj.config.ManagedObjectDefinition;
051import org.forgerock.opendj.server.config.client.BackendCfgClient;
052import org.forgerock.opendj.server.config.server.BackendCfg;
053import org.opends.messages.QuickSetupMessages;
054import org.opends.messages.ToolMessages;
055import org.opends.quicksetup.ApplicationException;
056import org.opends.quicksetup.Constants;
057import org.opends.quicksetup.CurrentInstallStatus;
058import org.opends.quicksetup.Installation;
059import org.opends.quicksetup.LicenseFile;
060import org.opends.quicksetup.QuickSetupLog;
061import org.opends.quicksetup.SecurityOptions;
062import org.opends.quicksetup.UserData;
063import org.opends.quicksetup.UserDataException;
064import org.opends.quicksetup.event.ProgressUpdateEvent;
065import org.opends.quicksetup.event.ProgressUpdateListener;
066import org.opends.quicksetup.installer.NewSuffixOptions;
067import org.opends.quicksetup.installer.offline.OfflineInstaller;
068import org.opends.quicksetup.util.IncompatibleVersionException;
069import org.opends.quicksetup.util.PlainTextProgressMessageFormatter;
070import org.opends.quicksetup.util.Utils;
071import org.opends.server.types.InitializationException;
072import org.opends.server.types.NullOutputStream;
073import org.opends.server.util.CertificateManager;
074import org.opends.server.util.SetupUtils;
075import org.opends.server.util.StaticUtils;
076
077import com.forgerock.opendj.cli.ArgumentException;
078import com.forgerock.opendj.cli.ClientException;
079import com.forgerock.opendj.cli.ConsoleApplication;
080import com.forgerock.opendj.cli.IntegerArgument;
081import com.forgerock.opendj.cli.Menu;
082import com.forgerock.opendj.cli.MenuBuilder;
083import com.forgerock.opendj.cli.MenuResult;
084import com.forgerock.opendj.cli.StringArgument;
085
086/**
087 * This class provides a very simple mechanism for installing the OpenDS
088 * Directory Service.  It performs the following tasks:
089 * <UL>
090 *   <LI>Checks if the server is already installed and running</LI>
091 *   <LI>Ask the user what base DN should be used for the data</LI>
092 *   <LI>Ask the user whether to create the base entry, or to import LDIF</LI>
093 *   <LI>Ask the user for the administration port and make sure it's available
094 *   </LI>
095 *   <LI>Ask the user for the LDAP port and make sure it's available</LI>
096 *   <LI>Ask the user for the default root DN and password</LI>
097 *   <LI>Ask the user to enable SSL or not and for the type of certificate that
098 *   the server must use</LI>
099 *   <LI>Ask the user if they want to start the server when done installing</LI>
100 * </UL>
101 */
102public class InstallDS extends ConsoleApplication
103{
104
105  private final PlainTextProgressMessageFormatter formatter = new PlainTextProgressMessageFormatter();
106
107  /** Prefix for log files. */
108  public static final String TMP_FILE_PREFIX = "opendj-setup-";
109
110  /** Suffix for log files. */
111  public static final String LOG_FILE_SUFFIX = ".log";
112
113  /**
114   * The enumeration containing the different return codes that the command-line
115   * can have.
116   */
117  private enum InstallReturnCode
118  {
119    SUCCESSFUL(0),
120
121    /** We did no have an error but the setup was not executed (displayed version or usage). */
122    SUCCESSFUL_NOP(0),
123
124    /** Unexpected error (potential bug). */
125    ERROR_UNEXPECTED(1),
126
127    /** Cannot parse arguments or data provided by user is not valid. */
128    ERROR_USER_DATA(2),
129
130    /** Error server already installed. */
131    ERROR_SERVER_ALREADY_INSTALLED(3),
132
133    /** Error initializing server. */
134    ERROR_INITIALIZING_SERVER(4),
135
136    /** The user failed providing password (for the keystore for instance). */
137    ERROR_PASSWORD_LIMIT(5),
138
139    /** The user cancelled the setup. */
140    ERROR_USER_CANCELLED(6),
141
142    /** The user doesn't accept the license. */
143    ERROR_LICENSE_NOT_ACCEPTED(7),
144
145    /** Incompatible java version. */
146    JAVA_VERSION_INCOMPATIBLE(8);
147
148    private int returnCode;
149    private InstallReturnCode(int returnCode)
150    {
151      this.returnCode = returnCode;
152    }
153
154    /**
155     * Get the corresponding return code value.
156     *
157     * @return The corresponding return code value.
158     */
159    public int getReturnCode()
160    {
161      return returnCode;
162    }
163  }
164
165  /**
166   * Enumeration describing the different answer that the user can provide when
167   * we ask to finalize the setup. Note that the code associated correspond to
168   * the order in the confirmation menu that is displayed at the end of the
169   * setup in interactive mode.
170   */
171  private enum ConfirmCode
172  {
173    CONTINUE(1),
174    PROVIDE_INFORMATION_AGAIN(2),
175    PRINT_EQUIVALENT_COMMAND_LINE(3),
176    CANCEL(3);
177
178    private int returnCode;
179    private ConfirmCode(int returnCode)
180    {
181      this.returnCode = returnCode;
182    }
183
184    /**
185     * Get the corresponding return code value.
186     *
187     * @return The corresponding return code value.
188     */
189    public int getReturnCode()
190    {
191      return returnCode;
192    }
193  }
194
195  /**
196   * The maximum number of times that we should ask the user to provide the
197   * password to access to a keystore.
198   */
199  public static final int LIMIT_KEYSTORE_PASSWORD_PROMPT = 7;
200
201  private final BackendTypeHelper backendTypeHelper = new BackendTypeHelper();
202
203  /** The argument parser. */
204  private InstallDSArgumentParser argParser;
205
206  /** Different variables we use when the user decides to provide data again. */
207  private NewSuffixOptions.Type lastResetPopulateOption;
208  private ManagedObjectDefinition<? extends BackendCfgClient, ? extends BackendCfg> lastResetBackendType;
209  private String lastResetImportFile;
210  private String lastResetRejectedFile;
211  private String lastResetSkippedFile;
212  private Integer lastResetNumEntries;
213  private Boolean lastResetEnableSSL;
214  private Boolean lastResetEnableStartTLS;
215  private SecurityOptions.CertificateType lastResetCertType;
216  private String lastResetKeyStorePath;
217  private Boolean lastResetEnableWindowsService;
218  private Boolean lastResetStartServer;
219  private String lastResetBaseDN = Installation.DEFAULT_INTERACTIVE_BASE_DN;
220  private String lastResetDirectoryManagerDN;
221  private Integer lastResetLdapPort;
222  private Integer lastResetLdapsPort;
223  private Integer lastResetAdminConnectorPort;
224  private Integer lastResetJmxPort;
225
226
227  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
228
229
230  /**
231   * Constructor for the InstallDS object.
232   *
233   * @param out
234   *          the print stream to use for standard output.
235   * @param err
236   *          the print stream to use for standard error.
237   * @param in
238   *          the input stream to use for standard input.
239   */
240  public InstallDS(PrintStream out, PrintStream err, InputStream in)
241  {
242    super(out, err);
243  }
244
245  /**
246   * The main method for the InstallDS CLI tool.
247   *
248   * @param args
249   *          the command-line arguments provided to this program.
250   */
251  public static void main(String[] args)
252  {
253    final int retCode = mainCLI(args, System.out, System.err, System.in);
254
255    System.exit(retCode);
256  }
257
258  /**
259   * Parses the provided command-line arguments and uses that information to run
260   * the setup tool.
261   *
262   * @param args
263   *          the command-line arguments provided to this program.
264   * @return The error code.
265   */
266  public static int mainCLI(String[] args)
267  {
268    return mainCLI(args, System.out, System.err, System.in);
269  }
270
271  /**
272   * Parses the provided command-line arguments and uses that information to run
273   * the setup tool.
274   *
275   * @param args
276   *          The command-line arguments provided to this program.
277   * @param outStream
278   *          The output stream to use for standard output, or <CODE>null</CODE>
279   *          if standard output is not needed.
280   * @param errStream
281   *          The output stream to use for standard error, or <CODE>null</CODE>
282   *          if standard error is not needed.
283   * @param inStream
284   *          The input stream to use for standard input.
285   * @return The error code.
286   */
287  public static int mainCLI(String[] args, OutputStream outStream, OutputStream errStream, InputStream inStream)
288  {
289    final PrintStream out = NullOutputStream.wrapOrNullStream(outStream);
290
291    System.setProperty(Constants.CLI_JAVA_PROPERTY, "true");
292
293    final PrintStream err = NullOutputStream.wrapOrNullStream(errStream);
294
295    try {
296      QuickSetupLog.initLogFileHandler(
297              QuickSetupLog.isInitialized() ? null :
298                File.createTempFile(TMP_FILE_PREFIX, LOG_FILE_SUFFIX));
299    } catch (final Throwable t) {
300      System.err.println("Unable to initialize log");
301      t.printStackTrace();
302    }
303
304    final InstallDS install = new InstallDS(out, err, inStream);
305
306    int retCode = install.execute(args);
307    if (retCode == 0)
308    {
309      QuickSetupLog.closeAndDeleteLogFile();
310    }
311    return retCode;
312  }
313
314  /**
315   * Parses the provided command-line arguments and uses that information to run
316   * the setup CLI.
317   *
318   * @param args
319   *          the command-line arguments provided to this program.
320   * @return the return code (SUCCESSFUL, USER_DATA_ERROR or BUG).
321   */
322  public int execute(String[] args)
323  {
324    argParser = new InstallDSArgumentParser(InstallDS.class.getName());
325    try
326    {
327      argParser.initializeArguments();
328    }
329    catch (final ArgumentException ae)
330    {
331      println(ToolMessages.ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage()));
332      return InstallReturnCode.ERROR_UNEXPECTED.getReturnCode();
333    }
334
335    lastResetDirectoryManagerDN = argParser.directoryManagerDNArg.getDefaultValue();
336    lastResetLdapPort = Integer.parseInt(argParser.ldapPortArg.getDefaultValue());
337    lastResetLdapsPort = Integer.parseInt(argParser.ldapsPortArg.getDefaultValue());
338    lastResetAdminConnectorPort = Integer.parseInt(argParser.adminConnectorPortArg.getDefaultValue());
339    lastResetJmxPort = Integer.parseInt(argParser.jmxPortArg.getDefaultValue());
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      initializeNonInteractiveUserDataWithParser(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 (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 initializeNonInteractiveUserDataWithParser(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, lastResetDirectoryManagerDN, 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 valueToSuggest
1032   *          the value to suggest by default on prompt.
1033   * @param promptMsg
1034   *          the prompt message to be displayed.
1035   * @param includeLineBreak
1036   *          whether to include a line break before the first prompt or not.
1037   * @return a list of valid DNs.
1038   * @throws UserDataException
1039   *           if something went wrong checking the data.
1040   */
1041  private LinkedList<String> promptIfRequiredForDNs(StringArgument arg, String valueToSuggest,
1042          LocalizableMessage promptMsg, boolean includeLineBreak) throws UserDataException
1043  {
1044    final LinkedList<String> dns = new LinkedList<>();
1045
1046    boolean usedProvided = false;
1047    boolean firstPrompt = true;
1048    int nTries = 0;
1049    while (dns.isEmpty())
1050    {
1051      if (nTries >= CONFIRMATION_MAX_TRIES)
1052      {
1053        throw new UserDataException(null, ERR_TRIES_LIMIT_REACHED.get(CONFIRMATION_MAX_TRIES));
1054      }
1055      boolean prompted = false;
1056      if (usedProvided || !arg.isPresent())
1057      {
1058        if (firstPrompt && includeLineBreak)
1059        {
1060          println();
1061        }
1062        try
1063        {
1064          final String dn = readInput(promptMsg, valueToSuggest);
1065          firstPrompt = false;
1066          dns.add(dn);
1067          prompted = true;
1068        }
1069        catch (final ClientException ce)
1070        {
1071          logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
1072        }
1073      }
1074      else
1075      {
1076        dns.addAll(arg.getValues());
1077        usedProvided = true;
1078      }
1079      final List<String> toRemove = new LinkedList<>();
1080      for (final String dn : dns)
1081      {
1082        try
1083        {
1084          new LdapName(dn);
1085          if (dn.trim().length() == 0)
1086          {
1087            toRemove.add(dn);
1088            println(ERR_INSTALLDS_EMPTY_DN_RESPONSE.get());
1089          }
1090        }
1091        catch (final Exception e)
1092        {
1093          toRemove.add(dn);
1094          final LocalizableMessage message = prompted ? ERR_INSTALLDS_INVALID_DN_RESPONSE.get() :
1095            ERR_INSTALLDS_CANNOT_PARSE_DN.get(dn, e.getMessage());
1096          println(message);
1097        }
1098      }
1099      if (!toRemove.isEmpty())
1100      {
1101        println();
1102      }
1103      dns.removeAll(toRemove);
1104      nTries++;
1105    }
1106    return dns;
1107  }
1108
1109  /**
1110   * This method updates the contents of a UserData object with what the user
1111   * specified in the command-line for the administration connector, LDAP and
1112   * JMX port parameters. If the user did not provide explicitly some data or
1113   * if the provided data is not valid, it prompts the user to provide it.
1114   * Note: this method does not update nor check the LDAPS port.
1115   *
1116   * @param uData
1117   *          the UserData object to be updated.
1118   */
1119  private void promptIfRequiredForPortData(UserData uData)
1120  {
1121    uData.setHostName(promptForHostNameIfRequired());
1122
1123    final List<Integer> usedPorts = new LinkedList<>();
1124    //  Determine the LDAP port number.
1125    final int ldapPort = promptIfRequiredForPortData(
1126            argParser.ldapPortArg, lastResetLdapPort, INFO_INSTALLDS_PROMPT_LDAPPORT.get(), usedPorts, true);
1127    uData.setServerPort(ldapPort);
1128    usedPorts.add(ldapPort);
1129
1130    //  Determine the Admin Connector port number.
1131    final int adminConnectorPort = promptIfRequiredForPortData(argParser.adminConnectorPortArg,
1132            lastResetAdminConnectorPort, INFO_INSTALLDS_PROMPT_ADMINCONNECTORPORT.get(), usedPorts, true);
1133    uData.setAdminConnectorPort(adminConnectorPort);
1134    usedPorts.add(adminConnectorPort);
1135
1136    if (argParser.jmxPortArg.isPresent())
1137    {
1138      final int jmxPort = promptIfRequiredForPortData(argParser.jmxPortArg, lastResetJmxPort,
1139          INFO_INSTALLDS_PROMPT_JMXPORT.get(), usedPorts, true);
1140      uData.setServerJMXPort(jmxPort);
1141    }
1142    else
1143    {
1144      uData.setServerJMXPort(-1);
1145    }
1146  }
1147
1148  /**
1149   * This method returns a valid port value. It checks that the provided
1150   * argument contains a valid port. If a valid port is not found it prompts the
1151   * user to provide a valid port.
1152   *
1153   * @param portArg
1154   *          the Argument that the user provided to specify the port.
1155   * @param valueToSuggest
1156   *          the value to suggest by default on prompt.
1157   * @param promptMsg
1158   *          the prompt message to be displayed.
1159   * @param usedPorts
1160   *          the list of ports the user provided before for other connection
1161   *          handlers.
1162   * @param includeLineBreak
1163   *          whether to include a line break before the first prompt or not.
1164   * @return a valid port number.
1165   */
1166  private int promptIfRequiredForPortData(IntegerArgument portArg, Integer valueToSuggest, LocalizableMessage promptMsg,
1167      Collection<Integer> usedPorts, boolean includeLineBreak)
1168  {
1169    int portNumber = -1;
1170    boolean usedProvided = false;
1171    boolean firstPrompt = true;
1172    while (portNumber == -1)
1173    {
1174      try
1175      {
1176        boolean prompted = false;
1177        if (usedProvided || !portArg.isPresent())
1178        {
1179          if (firstPrompt && includeLineBreak)
1180          {
1181            println();
1182          }
1183          portNumber = -1;
1184          while (portNumber == -1)
1185          {
1186            try
1187            {
1188              portNumber = readPort(promptMsg, valueToSuggest);
1189            }
1190            catch (final ClientException ce)
1191            {
1192              portNumber = -1;
1193              logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
1194            }
1195          }
1196          prompted = true;
1197          firstPrompt = false;
1198        }
1199        else
1200        {
1201          portNumber = portArg.getIntValue();
1202          usedProvided = true;
1203        }
1204
1205        if (!argParser.skipPortCheckArg.isPresent() && !SetupUtils.canUseAsPort(portNumber))
1206        {
1207          final LocalizableMessage message = getCannotBindErrorMessage(portNumber);
1208          if (prompted || includeLineBreak)
1209          {
1210            println();
1211          }
1212          println(message);
1213          if (!SetupUtils.isPrivilegedPort(portNumber))
1214          {
1215            println();
1216          }
1217          portNumber = -1;
1218        }
1219        if (portNumber != -1 && usedPorts.contains(portNumber))
1220        {
1221          println(ERR_CONFIGDS_PORT_ALREADY_SPECIFIED.get(portNumber));
1222          println();
1223          portNumber = -1;
1224        }
1225      }
1226      catch (final ArgumentException ae)
1227      {
1228        println(ae.getMessageObject());
1229      }
1230    }
1231    return portNumber;
1232  }
1233
1234  /**
1235   * This method returns what the user specified in the command-line for the
1236   * base DN and data import parameters. If the user did not provide explicitly
1237   * some data or if the provided data is not valid, it prompts the user to
1238   * provide it.
1239   *
1240   * @param uData
1241   *          The UserData object to be updated.
1242   * @return the NewSuffixOptions telling how to import data
1243   * @throws UserDataException
1244   *           if something went wrong checking the data.
1245   */
1246  private NewSuffixOptions promptIfRequiredForImportData(final UserData uData) throws UserDataException
1247  {
1248    boolean prompt = true;
1249    if (!argParser.baseDNArg.isPresent())
1250    {
1251      println();
1252      try
1253      {
1254        prompt = confirmAction(INFO_INSTALLDS_PROVIDE_BASE_DN_PROMPT.get(), true);
1255      }
1256      catch (final ClientException ce)
1257      {
1258        prompt = true;
1259        logger.warn(LocalizableMessage.raw("Error reading input: " + ce, ce));
1260      }
1261    }
1262
1263    if (!prompt)
1264    {
1265      return NewSuffixOptions.createEmpty(new LinkedList<String>());
1266    }
1267
1268    uData.setBackendType(getOrPromptForBackendType());
1269    // Check the validity of the base DNs
1270    final List<String> baseDNs = promptIfRequiredForDNs(
1271            argParser.baseDNArg, lastResetBaseDN, INFO_INSTALLDS_PROMPT_BASEDN.get(), true);
1272    return promptIfRequiredForDataOptions(baseDNs);
1273
1274  }
1275
1276  private ManagedObjectDefinition<? extends BackendCfgClient, ? extends BackendCfg> getOrPromptForBackendType()
1277  {
1278    if (argParser.backendTypeArg.isPresent())
1279    {
1280      final ManagedObjectDefinition<? extends BackendCfgClient, ? extends BackendCfg> backend =
1281          backendTypeHelper.retrieveBackendTypeFromName(argParser.backendTypeArg.getValue().toLowerCase());
1282      if ( backend != null)
1283      {
1284        return backend;
1285      }
1286      println();
1287      println(ERR_INSTALLDS_NO_SUCH_BACKEND_TYPE.get(
1288          argParser.backendTypeArg.getValue(), backendTypeHelper.getPrintableBackendTypeNames()));
1289    }
1290
1291    return promptForBackendType();
1292  }
1293
1294  private ManagedObjectDefinition<? extends BackendCfgClient,? extends BackendCfg> promptForBackendType()
1295  {
1296    println();
1297    int backendTypeIndex = 1;
1298    final List<ManagedObjectDefinition<? extends BackendCfgClient, ? extends BackendCfg>> backendTypes =
1299            backendTypeHelper.getBackendTypes();
1300    if (backendTypes.size() == 1) {
1301      final ManagedObjectDefinition<? extends BackendCfgClient, ? extends BackendCfg> backendType = backendTypes.get(0);
1302      println(INFO_INSTALLDS_BACKEND_TYPE_USED.get(backendType.getUserFriendlyName()));
1303      return backendType;
1304    }
1305
1306    try
1307    {
1308      final MenuResult<Integer> m = getBackendTypeMenu().run();
1309      if (m.isSuccess())
1310      {
1311        backendTypeIndex = m.getValue();
1312      }
1313    }
1314    catch (final ClientException ce)
1315    {
1316      logger.warn(LocalizableMessage.raw("Error reading input: " + ce, ce));
1317    }
1318
1319    return backendTypes.get(backendTypeIndex - 1);
1320  }
1321
1322  private Menu<Integer> getBackendTypeMenu()
1323  {
1324    final MenuBuilder<Integer> builder = new MenuBuilder<>(this);
1325    builder.setPrompt(INFO_INSTALLDS_PROMPT_BACKEND_TYPE.get());
1326    int index = 1;
1327    for (final ManagedObjectDefinition<?, ?> backendType : backendTypeHelper.getBackendTypes())
1328    {
1329      builder.addNumberedOption(backendType.getUserFriendlyName(), MenuResult.success(index++));
1330    }
1331
1332    final int printableIndex = getPromptedBackendTypeIndex();
1333    builder.setDefault(LocalizableMessage.raw(Integer.toString(printableIndex)), MenuResult.success(printableIndex));
1334    return builder.toMenu();
1335  }
1336
1337  private int getPromptedBackendTypeIndex()
1338  {
1339    if (lastResetBackendType != null)
1340    {
1341      return backendTypeHelper.getBackendTypes().indexOf(lastResetBackendType) + 1;
1342    }
1343    return 1;
1344  }
1345
1346  private NewSuffixOptions promptIfRequiredForDataOptions(List<String> baseDNs)
1347  {
1348    NewSuffixOptions dataOptions;
1349    if (argParser.importLDIFArg.isPresent())
1350    {
1351      // Check that the files exist
1352      final List<String> nonExistingFiles = new LinkedList<>();
1353      final List<String> importLDIFFiles = new LinkedList<>();
1354      for (final String file : argParser.importLDIFArg.getValues())
1355      {
1356        if (!Utils.fileExists(file))
1357        {
1358          nonExistingFiles.add(file);
1359        }
1360        else
1361        {
1362          importLDIFFiles.add(file);
1363        }
1364      }
1365      if (!nonExistingFiles.isEmpty())
1366      {
1367        println();
1368        println(ERR_INSTALLDS_NO_SUCH_LDIF_FILE.get(joinAsString(", ", nonExistingFiles)));
1369      }
1370
1371      readImportLdifFile(importLDIFFiles, lastResetImportFile);
1372      String rejectedFile = readValidFilePath(argParser.rejectedImportFileArg, lastResetRejectedFile,
1373          ERR_INSTALLDS_CANNOT_WRITE_REJECTED, INFO_INSTALLDS_PROMPT_REJECTED_FILE);
1374      String skippedFile = readValidFilePath(argParser.skippedImportFileArg, lastResetSkippedFile,
1375          ERR_INSTALLDS_CANNOT_WRITE_SKIPPED, INFO_INSTALLDS_PROMPT_SKIPPED_FILE);
1376      dataOptions = NewSuffixOptions.createImportFromLDIF(baseDNs,
1377          importLDIFFiles, rejectedFile, skippedFile);
1378    }
1379    else if (argParser.addBaseEntryArg.isPresent())
1380    {
1381      dataOptions = NewSuffixOptions.createBaseEntry(baseDNs);
1382    }
1383    else if (argParser.sampleDataArg.isPresent())
1384    {
1385      int numUsers;
1386      try
1387      {
1388        numUsers = argParser.sampleDataArg.getIntValue();
1389      }
1390      catch (final ArgumentException ae)
1391      {
1392        println();
1393        println(ae.getMessageObject());
1394        final LocalizableMessage message = INFO_INSTALLDS_PROMPT_NUM_ENTRIES.get();
1395        numUsers = promptForInteger(message, 2000, 0, Integer.MAX_VALUE);
1396      }
1397      dataOptions = NewSuffixOptions.createAutomaticallyGenerated(baseDNs, numUsers);
1398    }
1399    else
1400    {
1401      final int POPULATE_TYPE_LEAVE_EMPTY = 1;
1402      final int POPULATE_TYPE_BASE_ONLY = 2;
1403      final int POPULATE_TYPE_IMPORT_FROM_LDIF = 3;
1404      final int POPULATE_TYPE_GENERATE_SAMPLE_DATA = 4;
1405
1406      final int[] indexes = {POPULATE_TYPE_LEAVE_EMPTY, POPULATE_TYPE_BASE_ONLY,
1407          POPULATE_TYPE_IMPORT_FROM_LDIF, POPULATE_TYPE_GENERATE_SAMPLE_DATA};
1408      final LocalizableMessage[] msgs = new LocalizableMessage[] {
1409          INFO_INSTALLDS_POPULATE_OPTION_LEAVE_EMPTY.get(),
1410          INFO_INSTALLDS_POPULATE_OPTION_BASE_ONLY.get(),
1411          INFO_INSTALLDS_POPULATE_OPTION_IMPORT_LDIF.get(),
1412          INFO_INSTALLDS_POPULATE_OPTION_GENERATE_SAMPLE.get()
1413      };
1414
1415      final MenuBuilder<Integer> builder = new MenuBuilder<>(this);
1416      builder.setPrompt(INFO_INSTALLDS_HEADER_POPULATE_TYPE.get());
1417
1418      for (int i=0; i<indexes.length; i++)
1419      {
1420        builder.addNumberedOption(msgs[i], MenuResult.success(indexes[i]));
1421      }
1422
1423      if (lastResetPopulateOption == null)
1424      {
1425        builder.setDefault(LocalizableMessage.raw(
1426            String.valueOf(POPULATE_TYPE_LEAVE_EMPTY)),
1427            MenuResult.success(POPULATE_TYPE_LEAVE_EMPTY));
1428      }
1429      else
1430      {
1431        switch (lastResetPopulateOption)
1432        {
1433        case LEAVE_DATABASE_EMPTY:
1434          builder.setDefault(LocalizableMessage.raw(
1435              String.valueOf(POPULATE_TYPE_LEAVE_EMPTY)),
1436              MenuResult.success(POPULATE_TYPE_LEAVE_EMPTY));
1437          break;
1438        case IMPORT_FROM_LDIF_FILE:
1439          builder.setDefault(LocalizableMessage.raw(
1440              String.valueOf(POPULATE_TYPE_IMPORT_FROM_LDIF)),
1441              MenuResult.success(POPULATE_TYPE_IMPORT_FROM_LDIF));
1442          break;
1443        case IMPORT_AUTOMATICALLY_GENERATED_DATA:
1444          builder.setDefault(LocalizableMessage.raw(
1445              String.valueOf(POPULATE_TYPE_GENERATE_SAMPLE_DATA)),
1446              MenuResult.success(POPULATE_TYPE_GENERATE_SAMPLE_DATA));
1447          break;
1448        default:
1449          builder.setDefault(LocalizableMessage.raw(
1450              String.valueOf(POPULATE_TYPE_BASE_ONLY)),
1451              MenuResult.success(POPULATE_TYPE_BASE_ONLY));
1452        }
1453      }
1454
1455      final Menu<Integer> menu = builder.toMenu();
1456      int populateType;
1457      try
1458      {
1459        final MenuResult<Integer> m = menu.run();
1460        if (m.isSuccess())
1461        {
1462          populateType = m.getValue();
1463        }
1464        else
1465        {
1466          // Should never happen.
1467          throw new RuntimeException();
1468        }
1469      }
1470      catch (final ClientException ce)
1471      {
1472        populateType = POPULATE_TYPE_BASE_ONLY;
1473        logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
1474      }
1475
1476      if (populateType == POPULATE_TYPE_IMPORT_FROM_LDIF)
1477      {
1478        final List<String> importLDIFFiles = new LinkedList<>();
1479        readImportLdifFile(importLDIFFiles, null);
1480        String rejectedFile = readValidFilePath(argParser.rejectedImportFileArg, null,
1481            ERR_INSTALLDS_CANNOT_WRITE_REJECTED, INFO_INSTALLDS_PROMPT_REJECTED_FILE);
1482        String skippedFile = readValidFilePath(argParser.skippedImportFileArg, null,
1483            ERR_INSTALLDS_CANNOT_WRITE_SKIPPED, INFO_INSTALLDS_PROMPT_SKIPPED_FILE);
1484        dataOptions = NewSuffixOptions.createImportFromLDIF(baseDNs,
1485            importLDIFFiles, rejectedFile, skippedFile);
1486      }
1487      else if (populateType == POPULATE_TYPE_GENERATE_SAMPLE_DATA)
1488      {
1489        final LocalizableMessage message = INFO_INSTALLDS_PROMPT_NUM_ENTRIES.get();
1490        int defaultValue = lastResetNumEntries != null ? lastResetNumEntries : 2000;
1491        final int numUsers = promptForInteger(message, defaultValue, 0, Integer.MAX_VALUE);
1492        dataOptions = NewSuffixOptions.createAutomaticallyGenerated(baseDNs, numUsers);
1493      }
1494      else if (populateType == POPULATE_TYPE_LEAVE_EMPTY)
1495      {
1496        dataOptions = NewSuffixOptions.createEmpty(baseDNs);
1497      }
1498      else if (populateType == POPULATE_TYPE_BASE_ONLY)
1499      {
1500        dataOptions = NewSuffixOptions.createBaseEntry(baseDNs);
1501      }
1502      else
1503      {
1504        throw new IllegalStateException("Unexpected populateType: " + populateType);
1505      }
1506    }
1507    return dataOptions;
1508  }
1509
1510  private void readImportLdifFile(final List<String> importLDIFFiles, String defaultValue)
1511  {
1512    while (importLDIFFiles.isEmpty())
1513    {
1514      println();
1515      try
1516      {
1517        final String path = readInput(INFO_INSTALLDS_PROMPT_IMPORT_FILE.get(), defaultValue);
1518        if (Utils.fileExists(path))
1519        {
1520          importLDIFFiles.add(path);
1521        }
1522        else
1523        {
1524          println();
1525          println(ERR_INSTALLDS_NO_SUCH_LDIF_FILE.get(path));
1526        }
1527      }
1528      catch (final ClientException ce)
1529      {
1530        logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
1531      }
1532    }
1533  }
1534
1535  private String readValidFilePath(StringArgument arg, String defaultValue, Arg1<Object> errCannotWriteFile,
1536      Arg0 infoPromptFile)
1537  {
1538    String file = arg.getValue();
1539    if (file != null)
1540    {
1541      while (!canWrite(file))
1542      {
1543        println();
1544        println(errCannotWriteFile.get(file));
1545        println();
1546        try
1547        {
1548          file = readInput(infoPromptFile.get(), defaultValue);
1549        }
1550        catch (final ClientException ce)
1551        {
1552          logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
1553        }
1554      }
1555    }
1556    return file;
1557  }
1558
1559  /**
1560   * This method returns what the user specified in the command-line for the
1561   * security parameters. If the user did not provide explicitly some data or if
1562   * the provided data is not valid, it prompts the user to provide it.
1563   *
1564   * @param uData
1565   *          the current UserData object.
1566   * @return the {@link SecurityOptions} to be used when starting the server
1567   * @throws UserDataException
1568   *           if the user did not manage to provide the keystore password after
1569   *           a certain number of tries.
1570   * @throws ClientException
1571   *           If an error occurs when reading inputs.
1572   */
1573  private SecurityOptions promptIfRequiredForSecurityData(UserData uData) throws UserDataException, ClientException
1574  {
1575    // Check that the security data provided is valid.
1576    boolean enableSSL = false;
1577    boolean enableStartTLS = false;
1578    int ldapsPort = -1;
1579
1580    final List<Integer> usedPorts = new LinkedList<>();
1581    usedPorts.add(uData.getServerPort());
1582    if (uData.getServerJMXPort() != -1)
1583    {
1584      usedPorts.add(uData.getServerJMXPort());
1585    }
1586
1587    // Ask to enable SSL
1588    if (!argParser.ldapsPortArg.isPresent())
1589    {
1590      println();
1591      try
1592      {
1593        final boolean defaultValue = lastResetEnableSSL != null ? lastResetEnableSSL : false;
1594        enableSSL = confirmAction(INFO_INSTALLDS_PROMPT_ENABLE_SSL.get(), defaultValue);
1595        if (enableSSL)
1596        {
1597          ldapsPort = promptIfRequiredForPortData(
1598                  argParser.ldapsPortArg, lastResetLdapsPort, INFO_INSTALLDS_PROMPT_LDAPSPORT.get(), usedPorts, false);
1599        }
1600      }
1601      catch (final ClientException ce)
1602      {
1603        logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
1604      }
1605    }
1606    else
1607    {
1608      ldapsPort = promptIfRequiredForPortData(
1609              argParser.ldapsPortArg, lastResetLdapsPort, INFO_INSTALLDS_PROMPT_LDAPSPORT.get(), usedPorts, true);
1610      enableSSL = true;
1611    }
1612
1613    // Ask to enable Start TLS
1614    if (!argParser.enableStartTLSArg.isPresent())
1615    {
1616      println();
1617      try
1618      {
1619        final boolean defaultValue = lastResetEnableStartTLS != null ?
1620            lastResetEnableStartTLS : false;
1621        enableStartTLS = confirmAction(INFO_INSTALLDS_ENABLE_STARTTLS.get(),
1622            defaultValue);
1623      }
1624      catch (final ClientException ce)
1625      {
1626        logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
1627      }
1628    }
1629    else
1630    {
1631      enableStartTLS = true;
1632    }
1633
1634    SecurityOptions securityOptions;
1635    if (argParser.generateSelfSignedCertificateArg.isPresent())
1636    {
1637      securityOptions = SecurityOptions.createSelfSignedCertificateOptions(
1638          enableSSL, enableStartTLS, ldapsPort);
1639    }
1640    else if (argParser.useJavaKeyStoreArg.isPresent())
1641    {
1642      securityOptions =
1643        createSecurityOptionsPrompting(SecurityOptions.CertificateType.JKS,
1644            enableSSL, enableStartTLS, ldapsPort);
1645    }
1646    else if (argParser.useJCEKSArg.isPresent())
1647    {
1648      securityOptions =
1649        createSecurityOptionsPrompting(SecurityOptions.CertificateType.JCEKS,
1650            enableSSL, enableStartTLS, ldapsPort);
1651    }
1652    else if (argParser.usePkcs12Arg.isPresent())
1653    {
1654      securityOptions =
1655        createSecurityOptionsPrompting(SecurityOptions.CertificateType.PKCS12,
1656            enableSSL, enableStartTLS, ldapsPort);
1657    }
1658    else if (argParser.usePkcs11Arg.isPresent())
1659    {
1660      securityOptions =
1661        createSecurityOptionsPrompting(SecurityOptions.CertificateType.PKCS11,
1662            enableSSL, enableStartTLS, ldapsPort);
1663    }
1664    else if (!enableSSL && !enableStartTLS)
1665    {
1666      // If the user did not want to enable SSL or start TLS do not ask
1667      // to create a certificate.
1668      securityOptions = SecurityOptions.createNoCertificateOptions();
1669    }
1670    else
1671    {
1672      final int SELF_SIGNED = 1;
1673      final int JKS = 2;
1674      final int JCEKS = 3;
1675      final int PKCS12 = 4;
1676      final int PKCS11 = 5;
1677      final int[] indexes = {SELF_SIGNED, JKS, JCEKS, PKCS12, PKCS11};
1678      final LocalizableMessage[] msgs = {
1679          INFO_INSTALLDS_CERT_OPTION_SELF_SIGNED.get(),
1680          INFO_INSTALLDS_CERT_OPTION_JKS.get(),
1681          INFO_INSTALLDS_CERT_OPTION_JCEKS.get(),
1682          INFO_INSTALLDS_CERT_OPTION_PKCS12.get(),
1683          INFO_INSTALLDS_CERT_OPTION_PKCS11.get()
1684      };
1685
1686
1687      final MenuBuilder<Integer> builder = new MenuBuilder<>(this);
1688      builder.setPrompt(INFO_INSTALLDS_HEADER_CERT_TYPE.get());
1689
1690      for (int i=0; i<indexes.length; i++)
1691      {
1692        builder.addNumberedOption(msgs[i], MenuResult.success(indexes[i]));
1693      }
1694
1695      if (lastResetCertType == null)
1696      {
1697        builder.setDefault(LocalizableMessage.raw(String.valueOf(SELF_SIGNED)),
1698          MenuResult.success(SELF_SIGNED));
1699      }
1700      else
1701      {
1702        switch (lastResetCertType)
1703        {
1704        case JKS:
1705          builder.setDefault(LocalizableMessage.raw(String.valueOf(JKS)),
1706              MenuResult.success(JKS));
1707          break;
1708        case JCEKS:
1709          builder.setDefault(LocalizableMessage.raw(String.valueOf(JCEKS)),
1710              MenuResult.success(JCEKS));
1711          break;
1712        case PKCS11:
1713          builder.setDefault(LocalizableMessage.raw(String.valueOf(PKCS11)),
1714              MenuResult.success(PKCS11));
1715          break;
1716        case PKCS12:
1717          builder.setDefault(LocalizableMessage.raw(String.valueOf(PKCS12)),
1718              MenuResult.success(PKCS12));
1719          break;
1720        default:
1721          builder.setDefault(LocalizableMessage.raw(String.valueOf(SELF_SIGNED)),
1722              MenuResult.success(SELF_SIGNED));
1723        }
1724      }
1725
1726      final Menu<Integer> menu = builder.toMenu();
1727      int certType;
1728      try
1729      {
1730        final MenuResult<Integer> m = menu.run();
1731        if (m.isSuccess())
1732        {
1733          certType = m.getValue();
1734        }
1735        else
1736        {
1737          // Should never happen.
1738          throw new RuntimeException();
1739        }
1740      }
1741      catch (final ClientException ce)
1742      {
1743        logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
1744        certType = SELF_SIGNED;
1745      }
1746      if (certType == SELF_SIGNED)
1747      {
1748        securityOptions = SecurityOptions.createSelfSignedCertificateOptions(
1749              enableSSL, enableStartTLS, ldapsPort);
1750      }
1751      else if (certType == JKS)
1752      {
1753        securityOptions =
1754          createSecurityOptionsPrompting(SecurityOptions.CertificateType.JKS,
1755              enableSSL, enableStartTLS, ldapsPort);
1756      }
1757      else if (certType == JCEKS)
1758      {
1759        securityOptions =
1760          createSecurityOptionsPrompting(
1761              SecurityOptions.CertificateType.JCEKS,
1762              enableSSL, enableStartTLS, ldapsPort);
1763      }
1764      else if (certType == PKCS12)
1765      {
1766        securityOptions =
1767          createSecurityOptionsPrompting(
1768              SecurityOptions.CertificateType.PKCS12, enableSSL,
1769              enableStartTLS, ldapsPort);
1770      }
1771      else if (certType == PKCS11)
1772      {
1773        securityOptions =
1774          createSecurityOptionsPrompting(
1775              SecurityOptions.CertificateType.PKCS11, enableSSL,
1776              enableStartTLS, ldapsPort);
1777      }
1778      else
1779      {
1780        throw new IllegalStateException("Unexpected cert type: "+ certType);
1781      }
1782    }
1783    return securityOptions;
1784  }
1785
1786  /**
1787   * This method returns what the user specified in the command-line for the
1788   * Windows Service parameters. If the user did not provide explicitly the
1789   * data, it prompts the user to provide it.
1790   *
1791   * @return whether windows service should be enabled
1792   */
1793  private boolean promptIfRequiredForWindowsService()
1794  {
1795    boolean enableService = false;
1796    // If we are in Windows ask if the server must run as a windows service.
1797    if (isWindows())
1798    {
1799      if (argParser.enableWindowsServiceArg.isPresent())
1800      {
1801        enableService = true;
1802      }
1803      else
1804      {
1805        println();
1806        final LocalizableMessage message = INFO_INSTALLDS_PROMPT_ENABLE_SERVICE.get();
1807        try
1808        {
1809          final boolean defaultValue = (lastResetEnableWindowsService == null) ?
1810              false : lastResetEnableWindowsService;
1811          enableService = confirmAction(message, defaultValue);
1812        }
1813        catch (final ClientException ce)
1814        {
1815          logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
1816        }
1817      }
1818    }
1819    return enableService;
1820  }
1821
1822  /**
1823   * This method returns what the user specified in the command-line for the
1824   * Directory Manager parameters. If the user did not provide explicitly the
1825   * data, it prompts the user to provide it.
1826   *
1827   * @return whether server should be started
1828   */
1829  private boolean promptIfRequiredForStartServer()
1830  {
1831    boolean startServer = false;
1832    if (!argParser.doNotStartArg.isPresent())
1833    {
1834      println();
1835      final LocalizableMessage message = INFO_INSTALLDS_PROMPT_START_SERVER.get();
1836      try
1837      {
1838        final boolean defaultValue = (lastResetStartServer == null) ?
1839            true : lastResetStartServer;
1840        startServer = confirmAction(message, defaultValue);
1841      }
1842      catch (final ClientException ce)
1843      {
1844        logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
1845        startServer = true;
1846      }
1847    }
1848    return startServer;
1849  }
1850
1851  /**
1852   * Checks that the provided parameters are valid to access an existing key
1853   * store. This method adds the encountered errors to the provided list of
1854   * LocalizableMessage. It also adds the alias (nicknames) found to the
1855   * provided list of String.
1856   *
1857   * @param type
1858   *          the type of key store.
1859   * @param path
1860   *          the path of the key store.
1861   * @param pwd
1862   *          the password (PIN) to access the key store.
1863   * @param certNicknames
1864   *          the certificate nicknames that we are looking for (or null if we
1865   *          just one to get the one that is in the key store).
1866   * @param errorMessages
1867   *          the list that will be updated with the errors encountered.
1868   * @param nicknameList
1869   *          the list that will be updated with the nicknames found in the key
1870   *          store.
1871   */
1872  public static void checkCertificateInKeystore(SecurityOptions.CertificateType type, String path, String pwd,
1873      Collection<String> certNicknames, Collection<LocalizableMessage> errorMessages, Collection<String> nicknameList)
1874  {
1875    boolean errorWithPath = false;
1876    if (type != SecurityOptions.CertificateType.PKCS11)
1877    {
1878      final File f = new File(path);
1879      if (!f.exists())
1880      {
1881        errorMessages.add(INFO_KEYSTORE_PATH_DOES_NOT_EXIST.get());
1882        errorWithPath = true;
1883      }
1884      else if (!f.isFile())
1885      {
1886        errorMessages.add(INFO_KEYSTORE_PATH_NOT_A_FILE.get());
1887        errorWithPath = true;
1888      }
1889    }
1890    if (!errorWithPath)
1891    {
1892      try
1893      {
1894        CertificateManager certManager;
1895        switch (type)
1896        {
1897          case JKS:
1898          certManager = new CertificateManager(
1899              path,
1900              CertificateManager.KEY_STORE_TYPE_JKS,
1901              pwd);
1902          break;
1903
1904          case JCEKS:
1905            certManager = new CertificateManager(
1906                path,
1907                CertificateManager.KEY_STORE_TYPE_JCEKS,
1908                pwd);
1909            break;
1910
1911          case PKCS12:
1912          certManager = new CertificateManager(
1913              path,
1914              CertificateManager.KEY_STORE_TYPE_PKCS12,
1915              pwd);
1916          break;
1917
1918          case PKCS11:
1919          certManager = new CertificateManager(
1920              CertificateManager.KEY_STORE_PATH_PKCS11,
1921              CertificateManager.KEY_STORE_TYPE_PKCS11,
1922              pwd);
1923          break;
1924
1925          default:
1926            throw new IllegalArgumentException("Invalid type: "+type);
1927        }
1928        final String[] aliases = certManager.getCertificateAliases();
1929        if (aliases == null || aliases.length == 0)
1930        {
1931          // Could not retrieve any certificate
1932          switch (type)
1933          {
1934          case JKS:
1935            errorMessages.add(INFO_JKS_KEYSTORE_DOES_NOT_EXIST.get());
1936            break;
1937          case JCEKS:
1938            errorMessages.add(INFO_JCEKS_KEYSTORE_DOES_NOT_EXIST.get());
1939            break;
1940          case PKCS12:
1941            errorMessages.add(INFO_PKCS12_KEYSTORE_DOES_NOT_EXIST.get());
1942            break;
1943          case PKCS11:
1944            errorMessages.add(INFO_PKCS11_KEYSTORE_DOES_NOT_EXIST.get());
1945            break;
1946          default:
1947            throw new IllegalArgumentException("Invalid type: "+type);
1948          }
1949        }
1950        else if (certManager.hasRealAliases())
1951        {
1952          Collections.addAll(nicknameList, aliases);
1953          final String aliasString = joinAsString(", ", nicknameList);
1954          if (certNicknames.isEmpty() && aliases.length > 1)
1955          {
1956            errorMessages.add(ERR_INSTALLDS_MUST_PROVIDE_CERTNICKNAME.get(aliasString));
1957          }
1958          for (String certNickname : certNicknames)
1959          {
1960            // Check if the certificate alias is in the list.
1961            boolean found = false;
1962            for (int i = 0; i < aliases.length && !found; i++)
1963            {
1964              found = aliases[i].equalsIgnoreCase(certNickname);
1965            }
1966            if (!found)
1967            {
1968              errorMessages.add(ERR_INSTALLDS_CERTNICKNAME_NOT_FOUND.get(aliasString));
1969            }
1970          }
1971        }
1972      }
1973      catch (final KeyStoreException ke)
1974      {
1975        // issue OPENDJ-18, related to JDK bug
1976        if (StaticUtils.stackTraceContainsCause(ke, ArithmeticException.class))
1977        {
1978          errorMessages.add(INFO_ERROR_ACCESSING_KEYSTORE_JDK_BUG.get());
1979        }
1980        else
1981        {
1982          // Could not access to the key store: because the password is no good,
1983          // because the provided file is not a valid key store, etc.
1984          switch (type)
1985          {
1986          case JKS:
1987            errorMessages.add(INFO_ERROR_ACCESSING_JKS_KEYSTORE.get());
1988            break;
1989          case JCEKS:
1990            errorMessages.add(INFO_ERROR_ACCESSING_JCEKS_KEYSTORE.get());
1991            break;
1992          case PKCS12:
1993            errorMessages.add(INFO_ERROR_ACCESSING_PKCS12_KEYSTORE.get());
1994            break;
1995          case PKCS11:
1996            errorMessages.add(INFO_ERROR_ACCESSING_PKCS11_KEYSTORE.get());
1997            break;
1998          default:
1999            throw new IllegalArgumentException("Invalid type: " + type, ke);
2000          }
2001        }
2002      }
2003    }
2004  }
2005
2006  /**
2007   * Creates a SecurityOptions object that corresponds to the provided
2008   * parameters. If the parameters are not valid, it prompts the user to provide
2009   * them.
2010   *
2011   * @param type
2012   *          the keystore type.
2013   * @param enableSSL
2014   *          whether to enable SSL or not.
2015   * @param enableStartTLS
2016   *          whether to enable StartTLS or not.
2017   * @param ldapsPort
2018   *          the LDAPS port to use.
2019   * @return a SecurityOptions object that corresponds to the provided
2020   *         parameters (or to what the user provided after being prompted).
2021   * @throws UserDataException
2022   *           if the user did not manage to provide the keystore password after
2023   *           a certain number of tries.
2024   * @throws ClientException
2025   */
2026  private SecurityOptions createSecurityOptionsPrompting(SecurityOptions.CertificateType type, boolean enableSSL,
2027      boolean enableStartTLS, int ldapsPort) throws UserDataException, ClientException
2028  {
2029    SecurityOptions securityOptions;
2030    String path;
2031    Collection<String> certNicknames = argParser.certNicknameArg.getValues();
2032    String pwd = argParser.getKeyStorePassword();
2033    if (pwd != null && pwd.length() == 0)
2034    {
2035      pwd = null;
2036    }
2037    LocalizableMessage pathPrompt;
2038    String defaultPathValue;
2039
2040    switch (type)
2041    {
2042    case JKS:
2043      path = argParser.useJavaKeyStoreArg.getValue();
2044      pathPrompt = INFO_INSTALLDS_PROMPT_JKS_PATH.get();
2045      defaultPathValue = argParser.useJavaKeyStoreArg.getValue();
2046      if (defaultPathValue == null)
2047      {
2048        defaultPathValue = lastResetKeyStorePath;
2049      }
2050      break;
2051    case JCEKS:
2052      path = argParser.useJCEKSArg.getValue();
2053      pathPrompt = INFO_INSTALLDS_PROMPT_JCEKS_PATH.get();
2054      defaultPathValue = argParser.useJCEKSArg.getValue();
2055      if (defaultPathValue == null)
2056      {
2057        defaultPathValue = lastResetKeyStorePath;
2058      }
2059      break;
2060    case PKCS11:
2061      path = null;
2062      defaultPathValue = null;
2063      pathPrompt = null;
2064      break;
2065    case PKCS12:
2066      path = argParser.usePkcs12Arg.getValue();
2067      defaultPathValue = argParser.usePkcs12Arg.getValue();
2068      if (defaultPathValue == null)
2069      {
2070        defaultPathValue = lastResetKeyStorePath;
2071      }
2072      pathPrompt = INFO_INSTALLDS_PROMPT_PKCS12_PATH.get();
2073      break;
2074    default:
2075      throw new IllegalStateException(
2076          "Called promptIfRequiredCertificate with invalid type: "+type);
2077    }
2078    final List<LocalizableMessage> errorMessages = new LinkedList<>();
2079    final LinkedList<String> keystoreAliases = new LinkedList<>();
2080    boolean firstTry = true;
2081    int nPasswordPrompts = 0;
2082
2083    while (!errorMessages.isEmpty() || firstTry)
2084    {
2085      boolean prompted = false;
2086      if (!errorMessages.isEmpty())
2087      {
2088        println();
2089        println(Utils.getMessageFromCollection(errorMessages,
2090            formatter.getLineBreak().toString()));
2091      }
2092
2093      if (type != SecurityOptions.CertificateType.PKCS11
2094          && (containsKeyStorePathErrorMessage(errorMessages) || path == null))
2095      {
2096        println();
2097        try
2098        {
2099          path = readInput(pathPrompt, defaultPathValue);
2100        }
2101        catch (final ClientException ce)
2102        {
2103          path = "";
2104          logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
2105        }
2106
2107        prompted = true;
2108        if (pwd != null)
2109        {
2110          errorMessages.clear();
2111          keystoreAliases.clear();
2112          checkCertificateInKeystore(type, path, pwd, certNicknames, errorMessages, keystoreAliases);
2113          if (!errorMessages.isEmpty())
2114          {
2115            // Reset password: this might be a new keystore
2116            pwd = null;
2117          }
2118        }
2119      }
2120      if (containsKeyStorePasswordErrorMessage(errorMessages) || pwd == null)
2121      {
2122        if (!prompted)
2123        {
2124          println();
2125        }
2126        pwd = null;
2127        while (pwd == null)
2128        {
2129          if (nPasswordPrompts > LIMIT_KEYSTORE_PASSWORD_PROMPT)
2130          {
2131            throw new UserDataException(null,
2132                ERR_INSTALLDS_TOO_MANY_KEYSTORE_PASSWORD_TRIES.get(LIMIT_KEYSTORE_PASSWORD_PROMPT));
2133          }
2134          pwd = String.valueOf(readPassword(INFO_INSTALLDS_PROMPT_KEYSTORE_PASSWORD.get()));
2135          nPasswordPrompts ++;
2136        }
2137      }
2138      if (containsCertNicknameErrorMessage(errorMessages))
2139      {
2140        if (!prompted)
2141        {
2142          println();
2143        }
2144        certNicknames = promptForCertificateNickname(keystoreAliases);
2145      }
2146      errorMessages.clear();
2147      keystoreAliases.clear();
2148      checkCertificateInKeystore(type, path, pwd, certNicknames, errorMessages,
2149          keystoreAliases);
2150      firstTry = false;
2151    }
2152    if (certNicknames.isEmpty() && !keystoreAliases.isEmpty())
2153    {
2154      certNicknames = Arrays.asList(keystoreAliases.getFirst());
2155    }
2156    switch (type)
2157    {
2158    case JKS:
2159      return SecurityOptions.createJKSCertificateOptions(path, pwd, enableSSL, enableStartTLS, ldapsPort,
2160          certNicknames);
2161    case JCEKS:
2162      return SecurityOptions.createJCEKSCertificateOptions(path, pwd, enableSSL, enableStartTLS, ldapsPort,
2163          certNicknames);
2164    case PKCS12:
2165      return SecurityOptions.createPKCS12CertificateOptions(path, pwd, enableSSL, enableStartTLS, ldapsPort,
2166          certNicknames);
2167    case PKCS11:
2168      return SecurityOptions.createPKCS11CertificateOptions(pwd, enableSSL, enableStartTLS, ldapsPort, certNicknames);
2169    default:
2170      throw new IllegalStateException("Called createSecurityOptionsPrompting with invalid type: " + type);
2171    }
2172  }
2173
2174  /**
2175   * Tells if any of the error messages provided corresponds to a problem with
2176   * the key store path.
2177   *
2178   * @param msgs
2179   *          the messages to analyze.
2180   * @return <CODE>true</CODE> if any of the error messages provided corresponds
2181   *         to a problem with the key store path and <CODE>false</CODE>
2182   *         otherwise.
2183   */
2184  public static boolean containsKeyStorePathErrorMessage(Collection<LocalizableMessage> msgs)
2185  {
2186    for (final LocalizableMessage msg : msgs)
2187    {
2188      if (StaticUtils.hasDescriptor(msg, INFO_KEYSTORE_PATH_DOES_NOT_EXIST) ||
2189          StaticUtils.hasDescriptor(msg, INFO_KEYSTORE_PATH_NOT_A_FILE) ||
2190          StaticUtils.hasDescriptor(msg, INFO_JKS_KEYSTORE_DOES_NOT_EXIST) ||
2191          StaticUtils.hasDescriptor(msg, INFO_JCEKS_KEYSTORE_DOES_NOT_EXIST) ||
2192          StaticUtils.hasDescriptor(msg, INFO_PKCS12_KEYSTORE_DOES_NOT_EXIST) ||
2193          StaticUtils.hasDescriptor(msg, INFO_PKCS11_KEYSTORE_DOES_NOT_EXIST) ||
2194          StaticUtils.hasDescriptor(msg, INFO_ERROR_ACCESSING_JKS_KEYSTORE) ||
2195          StaticUtils.hasDescriptor(msg, INFO_ERROR_ACCESSING_JCEKS_KEYSTORE) ||
2196          StaticUtils.hasDescriptor(msg, INFO_ERROR_ACCESSING_PKCS12_KEYSTORE) ||
2197          StaticUtils.hasDescriptor(msg, INFO_ERROR_ACCESSING_PKCS11_KEYSTORE))
2198      {
2199        return true;
2200      }
2201    }
2202    return false;
2203  }
2204
2205  /**
2206   * Tells if any of the error messages provided corresponds to a problem with
2207   * the key store password.
2208   *
2209   * @param msgs
2210   *          the messages to analyze.
2211   * @return <CODE>true</CODE> if any of the error messages provided corresponds
2212   *         to a problem with the key store password and <CODE>false</CODE>
2213   *         otherwise.
2214   */
2215  public static boolean containsKeyStorePasswordErrorMessage(Collection<LocalizableMessage> msgs)
2216  {
2217    for (final LocalizableMessage msg : msgs)
2218    {
2219      if (StaticUtils.hasDescriptor(msg, INFO_JKS_KEYSTORE_DOES_NOT_EXIST) ||
2220          StaticUtils.hasDescriptor(msg, INFO_JCEKS_KEYSTORE_DOES_NOT_EXIST) ||
2221          StaticUtils.hasDescriptor(msg, INFO_PKCS12_KEYSTORE_DOES_NOT_EXIST) ||
2222          StaticUtils.hasDescriptor(msg, INFO_PKCS11_KEYSTORE_DOES_NOT_EXIST) ||
2223          StaticUtils.hasDescriptor(msg, INFO_ERROR_ACCESSING_JKS_KEYSTORE) ||
2224          StaticUtils.hasDescriptor(msg, INFO_ERROR_ACCESSING_JCEKS_KEYSTORE) ||
2225          StaticUtils.hasDescriptor(msg, INFO_ERROR_ACCESSING_PKCS12_KEYSTORE) ||
2226          StaticUtils.hasDescriptor(msg, INFO_ERROR_ACCESSING_PKCS11_KEYSTORE) ||
2227          StaticUtils.hasDescriptor(msg, INFO_ERROR_ACCESSING_KEYSTORE_JDK_BUG))
2228      {
2229        return true;
2230      }
2231    }
2232    return false;
2233  }
2234
2235  /**
2236   * Tells if any of the error messages provided corresponds to a problem with
2237   * the certificate nickname.
2238   *
2239   * @param msgs
2240   *          the messages to analyze.
2241   * @return <CODE>true</CODE> if any of the error messages provided corresponds
2242   *         to a problem with the certificate nickname and <CODE>false</CODE>
2243   *         otherwise.
2244   */
2245  public static boolean containsCertNicknameErrorMessage(
2246      Collection<LocalizableMessage> msgs)
2247  {
2248    boolean found = false;
2249    for (final LocalizableMessage msg : msgs)
2250    {
2251      if (StaticUtils.hasDescriptor(msg, ERR_INSTALLDS_CERTNICKNAME_NOT_FOUND) ||
2252          StaticUtils.hasDescriptor(msg, ERR_INSTALLDS_MUST_PROVIDE_CERTNICKNAME))
2253      {
2254        found = true;
2255        break;
2256      }
2257    }
2258    return found;
2259  }
2260
2261  /**
2262   * Interactively prompts (on standard output) the user to provide an integer
2263   * value. The answer provided must be parseable as an integer, and may be
2264   * required to be within a given set of bounds. It will keep prompting until
2265   * an acceptable value is given.
2266   *
2267   * @param prompt
2268   *          The prompt to present to the user.
2269   * @param defaultValue
2270   *          The default value to assume if the user presses ENTER without
2271   *          typing anything, or <CODE>null</CODE> if there should not be a
2272   *          default and the user must explicitly provide a value.
2273   * @param lowerBound
2274   *          The lower bound that should be enforced, or <CODE>null</CODE> if
2275   *          there is none.
2276   * @param upperBound
2277   *          The upper bound that should be enforced, or <CODE>null</CODE> if
2278   *          there is none.
2279   * @return The <CODE>int</CODE> value read from the user input.
2280   */
2281  private int promptForInteger(LocalizableMessage prompt, Integer defaultValue, Integer lowerBound, Integer upperBound)
2282  {
2283    int returnValue = -1;
2284    while (returnValue == -1)
2285    {
2286      String s;
2287      try
2288      {
2289        s = readInput(prompt, String.valueOf(defaultValue));
2290      }
2291      catch (final ClientException ce)
2292      {
2293        s = "";
2294        logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
2295      }
2296      if ("".equals(s))
2297      {
2298        if (defaultValue == null)
2299        {
2300          println(ERR_INSTALLDS_INVALID_INTEGER_RESPONSE.get());
2301          println();
2302        }
2303        else
2304        {
2305          returnValue = defaultValue;
2306        }
2307      }
2308      else
2309      {
2310        try
2311        {
2312          final int intValue = Integer.parseInt(s);
2313          if (lowerBound != null && intValue < lowerBound)
2314          {
2315            println(ERR_INSTALLDS_INTEGER_BELOW_LOWER_BOUND.get(lowerBound));
2316            println();
2317          }
2318          else if (upperBound != null && intValue > upperBound)
2319          {
2320            println(ERR_INSTALLDS_INTEGER_ABOVE_UPPER_BOUND.get(upperBound));
2321            println();
2322          }
2323          else
2324          {
2325            returnValue = intValue;
2326          }
2327        }
2328        catch (final NumberFormatException nfe)
2329        {
2330          println(ERR_INSTALLDS_INVALID_INTEGER_RESPONSE.get());
2331          println();
2332        }
2333      }
2334    }
2335    return returnValue;
2336  }
2337
2338  /**
2339   * Prompts the user to accept on the certificates that appears on the list and
2340   * returns the chosen certificate nickname.
2341   *
2342   * @param nicknames
2343   *          the list of certificates the user must choose from.
2344   * @return the chosen certificate nickname.
2345   */
2346  private Collection<String> promptForCertificateNickname(List<String> nicknames)
2347  {
2348    Collection<String> choosenNicknames = new ArrayList<>();
2349    while (choosenNicknames.isEmpty())
2350    {
2351      for (final String n : nicknames)
2352      {
2353        try
2354        {
2355          if (confirmAction(INFO_INSTALLDS_PROMPT_CERTNICKNAME.get(n), true))
2356          {
2357            choosenNicknames.add(n);
2358          }
2359        }
2360        catch (final ClientException ce)
2361        {
2362          logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
2363        }
2364      }
2365    }
2366    return choosenNicknames;
2367  }
2368
2369  /**
2370   * It displays the information provided by the user.
2371   *
2372   * @param uData
2373   *          the UserData that the user provided.
2374   */
2375  private void printSummary(UserData uData)
2376  {
2377    println();
2378    println();
2379    println(INFO_INSTALLDS_SUMMARY.get());
2380    final LocalizableMessage[] labels =
2381    {
2382        INFO_SERVER_PORT_LABEL.get(),
2383        INFO_ADMIN_CONNECTOR_PORT_LABEL.get(),
2384        INFO_INSTALLDS_SERVER_JMXPORT_LABEL.get(),
2385        INFO_SERVER_SECURITY_LABEL.get(),
2386        INFO_SERVER_DIRECTORY_MANAGER_DN_LABEL.get(),
2387        INFO_DIRECTORY_DATA_LABEL.get()
2388    };
2389
2390    final int jmxPort = uData.getServerJMXPort();
2391
2392    final LocalizableMessage[] values =
2393    {
2394        LocalizableMessage.raw(String.valueOf(uData.getServerPort())),
2395        LocalizableMessage.raw(String.valueOf(uData.getAdminConnectorPort())),
2396        LocalizableMessage.raw(jmxPort != -1 ? String.valueOf(jmxPort) : ""),
2397        LocalizableMessage.raw(
2398            Utils.getSecurityOptionsString(uData.getSecurityOptions(), false)),
2399        LocalizableMessage.raw(uData.getDirectoryManagerDn()),
2400        LocalizableMessage.raw(Utils.getDataDisplayString(uData)),
2401    };
2402    int maxWidth = 0;
2403    for (final LocalizableMessage l : labels)
2404    {
2405      maxWidth = Math.max(maxWidth, l.length());
2406    }
2407
2408    for (int i=0; i<labels.length; i++)
2409    {
2410      StringBuilder sb = new StringBuilder();
2411      if (values[i] != null)
2412      {
2413        final LocalizableMessage l = labels[i];
2414        sb.append(l).append(" ");
2415
2416        final String[] lines = values[i].toString().split(Constants.LINE_SEPARATOR);
2417        for (int j=0; j<lines.length; j++)
2418        {
2419          if (j != 0)
2420          {
2421            for (int k=0; k <= maxWidth; k++)
2422            {
2423              sb.append(" ");
2424            }
2425          }
2426          else
2427          {
2428            for (int k=0; k<maxWidth - l.length(); k++)
2429            {
2430              sb.append(" ");
2431            }
2432          }
2433          sb.append(lines[j]);
2434          println(LocalizableMessage.raw(sb));
2435          sb = new StringBuilder();
2436        }
2437      }
2438    }
2439
2440    println();
2441    if (uData.getStartServer())
2442    {
2443      println(INFO_INSTALLDS_START_SERVER.get());
2444    }
2445    else
2446    {
2447      println(INFO_INSTALLDS_DO_NOT_START_SERVER.get());
2448    }
2449
2450    if (isWindows())
2451    {
2452      if (uData.getEnableWindowsService())
2453      {
2454        println(INFO_INSTALLDS_ENABLE_WINDOWS_SERVICE.get());
2455      }
2456      else
2457      {
2458        println(INFO_INSTALLDS_DO_NOT_ENABLE_WINDOWS_SERVICE.get());
2459      }
2460    }
2461  }
2462
2463  private void printEquivalentCommandLine(UserData uData)
2464  {
2465    println();
2466
2467    println(INFO_INSTALL_SETUP_EQUIVALENT_COMMAND_LINE.get());
2468    println();
2469    final List<String> cmd = Utils.getSetupEquivalentCommandLine(uData);
2470    println(LocalizableMessage.raw(Utils.getFormattedEquivalentCommandLine(cmd, formatter)));
2471  }
2472
2473  /**
2474   * This method asks the user to confirm to continue the setup. It basically
2475   * displays the information provided by the user and at the end proposes a
2476   * menu with the different options to choose from.
2477   *
2478   * @return the answer provided by the user: cancel setup, continue setup or
2479   *         provide information again.
2480   */
2481  private ConfirmCode askForConfirmation()
2482  {
2483    ConfirmCode returnValue;
2484
2485    println();
2486    println();
2487
2488    final LocalizableMessage[] msgs = new LocalizableMessage[] {
2489        INFO_INSTALLDS_CONFIRM_INSTALL.get(),
2490        INFO_INSTALLDS_PROVIDE_DATA_AGAIN.get(),
2491        INFO_INSTALLDS_PRINT_EQUIVALENT_COMMAND_LINE.get(),
2492        INFO_INSTALLDS_CANCEL.get()
2493      };
2494
2495    final MenuBuilder<ConfirmCode> builder = new MenuBuilder<>(this);
2496    builder.setPrompt(INFO_INSTALLDS_CONFIRM_INSTALL_PROMPT.get());
2497
2498    int i=0;
2499    for (final ConfirmCode code : ConfirmCode.values())
2500    {
2501      builder.addNumberedOption(msgs[i], MenuResult.success(code));
2502      i++;
2503    }
2504
2505    builder.setDefault(LocalizableMessage.raw(
2506            String.valueOf(ConfirmCode.CONTINUE.getReturnCode())),
2507            MenuResult.success(ConfirmCode.CONTINUE));
2508
2509    final Menu<ConfirmCode> menu = builder.toMenu();
2510
2511    try
2512    {
2513      final MenuResult<ConfirmCode> m = menu.run();
2514      if (m.isSuccess())
2515      {
2516        returnValue = m.getValue();
2517      }
2518      else
2519      {
2520        // Should never happen.
2521        throw new RuntimeException();
2522      }
2523    }
2524    catch (final ClientException ce)
2525    {
2526      returnValue = ConfirmCode.CANCEL;
2527      logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
2528    }
2529    return returnValue;
2530  }
2531
2532  private void resetArguments(UserData uData)
2533  {
2534    argParser = new InstallDSArgumentParser(InstallDS.class.getName());
2535    try
2536    {
2537      argParser.initializeArguments();
2538      lastResetDirectoryManagerDN = uData.getDirectoryManagerDn();
2539      lastResetLdapPort = uData.getServerPort();
2540      lastResetAdminConnectorPort = uData.getAdminConnectorPort();
2541
2542      final int jmxPort = uData.getServerJMXPort();
2543      if (jmxPort != -1)
2544      {
2545        lastResetJmxPort = jmxPort;
2546      }
2547
2548      final LinkedList<String> baseDNs = uData.getNewSuffixOptions().getBaseDns();
2549      if (!baseDNs.isEmpty())
2550      {
2551        lastResetBaseDN = baseDNs.getFirst();
2552      }
2553
2554      final NewSuffixOptions suffixOptions = uData.getNewSuffixOptions();
2555      lastResetPopulateOption = suffixOptions.getType();
2556
2557      if (NewSuffixOptions.Type.IMPORT_AUTOMATICALLY_GENERATED_DATA == lastResetPopulateOption)
2558      {
2559        lastResetNumEntries = suffixOptions.getNumberEntries();
2560      }
2561      else if (NewSuffixOptions.Type.IMPORT_FROM_LDIF_FILE == lastResetPopulateOption)
2562      {
2563        lastResetImportFile = suffixOptions.getLDIFPaths().getFirst();
2564        lastResetRejectedFile = suffixOptions.getRejectedFile();
2565        lastResetSkippedFile = suffixOptions.getSkippedFile();
2566      }
2567
2568      final SecurityOptions sec = uData.getSecurityOptions();
2569      if (sec.getEnableSSL())
2570      {
2571        lastResetLdapsPort = sec.getSslPort();
2572      }
2573      lastResetEnableSSL = sec.getEnableSSL();
2574      lastResetEnableStartTLS = sec.getEnableStartTLS();
2575      lastResetCertType = sec.getCertificateType();
2576      if (SecurityOptions.CertificateType.JKS == lastResetCertType
2577          || SecurityOptions.CertificateType.JCEKS == lastResetCertType
2578          || SecurityOptions.CertificateType.PKCS12 == lastResetCertType)
2579      {
2580        lastResetKeyStorePath = sec.getKeystorePath();
2581      }
2582      else
2583      {
2584        lastResetKeyStorePath = null;
2585      }
2586
2587      lastResetEnableWindowsService = uData.getEnableWindowsService();
2588      lastResetStartServer = uData.getStartServer();
2589      lastResetBackendType = uData.getBackendType();
2590    }
2591    catch (final Throwable t)
2592    {
2593      logger.warn(LocalizableMessage.raw("Error resetting arguments: " + t, t));
2594    }
2595  }
2596
2597  private String promptForHostNameIfRequired()
2598  {
2599    String hostName = null;
2600    if (argParser.hostNameArg.isPresent())
2601    {
2602      hostName = argParser.hostNameArg.getValue();
2603    }
2604    else
2605    {
2606      println();
2607      while (hostName == null)
2608      {
2609        try
2610        {
2611          hostName = readInput(INFO_INSTALLDS_PROMPT_HOST_NAME.get(), argParser.hostNameArg.getDefaultValue());
2612        }
2613        catch (final ClientException ce)
2614        {
2615          logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
2616        }
2617      }
2618    }
2619    return hostName;
2620  }
2621
2622  /**
2623   * Returns the timeout to be used to connect in milliseconds. The method must
2624   * be called after parsing the arguments.
2625   *
2626   * @return the timeout to be used to connect in milliseconds. Returns
2627   *         {@code 0} if there is no timeout.
2628   */
2629  private int getConnectTimeout()
2630  {
2631    return argParser.getConnectTimeout();
2632  }
2633
2634}