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