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