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