001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2008-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2011-2015 ForgeRock AS
026 */
027package org.opends.server.util.cli;
028
029import static com.forgerock.opendj.cli.Utils.isDN;
030import static com.forgerock.opendj.cli.Utils.getAdministratorDN;
031import static com.forgerock.opendj.cli.Utils.getThrowableMsg;
032import static com.forgerock.opendj.cli.CliMessages.*;
033
034import java.io.File;
035import java.io.FileInputStream;
036import java.io.FileNotFoundException;
037import java.io.FileOutputStream;
038import java.net.InetAddress;
039import java.net.URI;
040import java.net.UnknownHostException;
041import java.security.KeyStore;
042import java.security.KeyStoreException;
043import java.security.cert.X509Certificate;
044import java.util.Enumeration;
045import java.util.LinkedHashMap;
046
047import javax.net.ssl.KeyManager;
048
049import org.forgerock.i18n.LocalizableMessage;
050import org.forgerock.i18n.slf4j.LocalizedLogger;
051import org.opends.admin.ads.util.ApplicationKeyManager;
052import org.opends.admin.ads.util.ApplicationTrustManager;
053import org.opends.server.admin.client.cli.SecureConnectionCliArgs;
054import org.opends.server.tools.LDAPConnectionOptions;
055import org.opends.server.tools.SSLConnectionException;
056import org.opends.server.tools.SSLConnectionFactory;
057import org.opends.server.util.CollectionUtils;
058import org.opends.server.util.SelectableCertificateKeyManager;
059
060import com.forgerock.opendj.cli.ArgumentException;
061import com.forgerock.opendj.cli.ClientException;
062import com.forgerock.opendj.cli.CommandBuilder;
063import com.forgerock.opendj.cli.ConsoleApplication;
064import com.forgerock.opendj.cli.Menu;
065import com.forgerock.opendj.cli.MenuBuilder;
066import com.forgerock.opendj.cli.MenuResult;
067import com.forgerock.opendj.cli.ValidationCallback;
068
069/**
070 * Supports interacting with a user through the command line to prompt for
071 * information necessary to create an LDAP connection.
072 *
073 * Actually the LDAPConnectionConsoleInteraction is used by UninstallCliHelper, StatusCli,
074 * LDAPManagementContextFactory and ReplicationCliMain.
075 */
076public class LDAPConnectionConsoleInteraction
077{
078
079  /**
080   * Information from the latest console interaction.
081   * TODO: should it extend MonoServerReplicationUserData or a subclass?
082   */
083  private static class State
084  {
085    private boolean useSSL;
086    private boolean useStartTLS;
087    private String hostName;
088    private String bindDN;
089    private String providedBindDN;
090    private String adminUID;
091    private String providedAdminUID;
092    private String bindPassword;
093    /** The timeout to be used to connect. */
094    private int connectTimeout;
095    /** Indicate if we need to display the heading. */
096    private boolean isHeadingDisplayed;
097
098    private ApplicationTrustManager trustManager;
099    /** Indicate if the trust store in in memory. */
100    private boolean trustStoreInMemory;
101    /** Indicate if the all certificates are accepted. */
102    private boolean trustAll;
103    /** Indicate that the trust manager was created with the parameters provided. */
104    private boolean trustManagerInitialized;
105    /** The trust store to use for the SSL or STARTTLS connection. */
106    private KeyStore truststore;
107    private String truststorePath;
108    private String truststorePassword;
109
110    private KeyManager keyManager;
111    private String keystorePath;
112    private String keystorePassword;
113    private String certifNickname;
114
115    private State(SecureConnectionCliArgs secureArgs)
116    {
117      useSSL = secureArgs.useSSL();
118      useStartTLS = secureArgs.useStartTLS();
119      trustAll = secureArgs.trustAllArg.isPresent();
120    }
121
122    /**
123     * @return
124     */
125    protected LocalizableMessage getPrompt()
126    {
127      LocalizableMessage prompt;
128      if (providedAdminUID != null)
129      {
130        prompt = INFO_LDAPAUTH_PASSWORD_PROMPT.get(providedAdminUID);
131      }
132      else if (providedBindDN != null)
133      {
134        prompt = INFO_LDAPAUTH_PASSWORD_PROMPT.get(providedBindDN);
135      }
136      else if (bindDN != null)
137      {
138        prompt = INFO_LDAPAUTH_PASSWORD_PROMPT.get(bindDN);
139      }
140      else
141      {
142        prompt = INFO_LDAPAUTH_PASSWORD_PROMPT.get(adminUID);
143      }
144      return prompt;
145    }
146
147    /**
148     * @return
149     */
150    protected String getAdminOrBindDN()
151    {
152      String dn;
153      if (providedBindDN != null)
154      {
155        dn = providedBindDN;
156      }
157      else if (providedAdminUID != null)
158      {
159        dn = getAdministratorDN(providedAdminUID);
160      }
161      else if (bindDN != null)
162      {
163        dn = bindDN;
164      }
165      else if (adminUID != null)
166      {
167        dn = getAdministratorDN(adminUID);
168      }
169      else
170      {
171        dn = null;
172      }
173      return dn;
174    }
175
176  }
177
178  /** The console application. */
179  private ConsoleApplication app;
180
181  private State state;
182
183  /** The SecureConnectionCliArgsList object. */
184  private SecureConnectionCliArgs secureArgsList;
185
186  /** The command builder that we can return with the connection information. */
187  private CommandBuilder commandBuilder;
188
189  /** A copy of the secureArgList for convenience. */
190  private SecureConnectionCliArgs copySecureArgsList;
191
192  /**
193   * Boolean that tells if we must propose LDAP if it is available even if the
194   * user provided certificate parameters.
195   */
196  private boolean displayLdapIfSecureParameters;
197
198  private int portNumber;
199
200  private LocalizableMessage heading = INFO_LDAP_CONN_HEADING_CONNECTION_PARAMETERS.get();
201
202  /** Boolean that tells if we ask for bind DN or admin UID in the same prompt. */
203  private boolean useAdminOrBindDn;
204
205  /** Enumeration description protocols for interactive CLI choices. */
206  private enum Protocols
207  {
208    LDAP(1, INFO_LDAP_CONN_PROMPT_SECURITY_LDAP.get()),
209    SSL(2,  INFO_LDAP_CONN_PROMPT_SECURITY_USE_SSL.get()),
210    START_TLS(3, INFO_LDAP_CONN_PROMPT_SECURITY_USE_START_TLS.get());
211
212    private Integer choice;
213
214    private LocalizableMessage msg;
215
216    /**
217     * Private constructor.
218     *
219     * @param i
220     *          the menu return value.
221     * @param msg
222     *          the message message.
223     */
224    private Protocols(int i, LocalizableMessage msg)
225    {
226      choice = i;
227      this.msg = msg;
228    }
229
230    /**
231     * Returns the choice number.
232     *
233     * @return the attribute name.
234     */
235    public Integer getChoice()
236    {
237      return choice;
238    }
239
240    /**
241     * Return the menu message.
242     *
243     * @return the menu message.
244     */
245    public LocalizableMessage getMenuMessage()
246    {
247      return msg;
248    }
249  }
250
251  /**
252   * Enumeration description protocols for interactive CLI choices.
253   */
254  private enum TrustMethod
255  {
256    TRUSTALL(1, INFO_LDAP_CONN_PROMPT_SECURITY_USE_TRUST_ALL.get()),
257
258    TRUSTSTORE(2, INFO_LDAP_CONN_PROMPT_SECURITY_TRUSTSTORE.get()),
259
260    DISPLAY_CERTIFICATE(3, INFO_LDAP_CONN_PROMPT_SECURITY_MANUAL_CHECK.get());
261
262    private Integer choice;
263
264    private LocalizableMessage msg;
265
266    /**
267     * Private constructor.
268     *
269     * @param i
270     *          the menu return value.
271     * @param msg
272     *          the message message.
273     */
274    private TrustMethod(int i, LocalizableMessage msg)
275    {
276      choice = Integer.valueOf(i);
277      this.msg = msg;
278    }
279
280    /**
281     * Returns the choice number.
282     *
283     * @return the attribute name.
284     */
285    public Integer getChoice()
286    {
287      return choice;
288    }
289
290    /**
291     * Return the menu message.
292     *
293     * @return the menu message.
294     */
295    public LocalizableMessage getMenuMessage()
296    {
297      return msg;
298    }
299  }
300
301  /**
302   * Enumeration description server certificate trust option.
303   */
304  private enum TrustOption
305  {
306    UNTRUSTED(1, INFO_LDAP_CONN_PROMPT_SECURITY_TRUST_OPTION_NO.get()),
307    SESSION(2, INFO_LDAP_CONN_PROMPT_SECURITY_TRUST_OPTION_SESSION.get()),
308    PERMAMENT(3, INFO_LDAP_CONN_PROMPT_SECURITY_TRUST_OPTION_ALWAYS.get()),
309    CERTIFICATE_DETAILS(4, INFO_LDAP_CONN_PROMPT_SECURITY_CERTIFICATE_DETAILS.get());
310
311    private Integer choice;
312
313    private LocalizableMessage msg;
314
315    /**
316     * Private constructor.
317     *
318     * @param i
319     *          the menu return value.
320     * @param msg
321     *          the message message.
322     */
323    private TrustOption(int i, LocalizableMessage msg)
324    {
325      choice = Integer.valueOf(i);
326      this.msg = msg;
327    }
328
329    /**
330     * Returns the choice number.
331     *
332     * @return the attribute name.
333     */
334    public Integer getChoice()
335    {
336      return choice;
337    }
338
339    /**
340     * Return the menu message.
341     *
342     * @return the menu message.
343     */
344    public LocalizableMessage getMenuMessage()
345    {
346      return msg;
347    }
348  }
349
350  /**
351   * Constructs a parameterized instance.
352   *
353   * @param app
354   *          console application
355   * @param secureArgs
356   *          existing set of arguments that have already been parsed and
357   *          contain some potential command line specified LDAP arguments
358   */
359  public LDAPConnectionConsoleInteraction(ConsoleApplication app, SecureConnectionCliArgs secureArgs)
360  {
361    this.app = app;
362    this.secureArgsList = secureArgs;
363    this.commandBuilder = new CommandBuilder(null, null);
364    state = new State(secureArgs);
365    copySecureArgsList = new SecureConnectionCliArgs(secureArgs.alwaysSSL());
366    try
367    {
368      copySecureArgsList.createGlobalArguments();
369    }
370    catch (Throwable t)
371    {
372      // This is  a bug: we should always be able to create the global arguments
373      // no need to localize this one.
374      throw new RuntimeException("Unexpected error: " + t, t);
375    }
376  }
377
378  /**
379   * Interact with the user though the console to get information necessary to
380   * establish an LDAP connection.
381   *
382   * @throws ArgumentException
383   *           if there is a problem with the arguments
384   */
385  public void run() throws ArgumentException
386  {
387    run(true);
388  }
389
390  /**
391   * Interact with the user though the console to get information necessary to
392   * establish an LDAP connection.
393   *
394   * @param canUseStartTLS
395   *          whether we can propose to connect using Start TLS or not.
396   * @throws ArgumentException
397   *           if there is a problem with the arguments
398   */
399  public void run(boolean canUseStartTLS) throws ArgumentException
400  {
401    // Reset everything
402    commandBuilder.clearArguments();
403    copySecureArgsList.createGlobalArguments();
404
405    boolean secureConnection = true;
406
407    // Get the LDAP host.
408    state.hostName = secureArgsList.hostNameArg.getValue();
409    final String tmpHostName = state.hostName;
410    if (app.isInteractive() && !secureArgsList.hostNameArg.isPresent())
411    {
412      checkHeadingDisplayed();
413
414      ValidationCallback<String> callback = new ValidationCallback<String>()
415      {
416
417        @Override
418        public String validate(ConsoleApplication app, String input)
419            throws ClientException
420        {
421          String ninput = input.trim();
422          if (ninput.length() == 0)
423          {
424            return tmpHostName;
425          }
426          else
427          {
428            try
429            {
430              InetAddress.getByName(ninput);
431              return ninput;
432            }
433            catch (UnknownHostException e)
434            {
435              // Try again...
436              app.println();
437              app.println(ERR_LDAP_CONN_BAD_HOST_NAME.get(ninput));
438              app.println();
439              return null;
440            }
441          }
442        }
443
444      };
445
446      try
447      {
448        app.println();
449        state.hostName = app.readValidatedInput(INFO_LDAP_CONN_PROMPT_HOST_NAME.get(state.hostName), callback);
450      }
451      catch (ClientException e)
452      {
453        throw cannotReadConnectionParameters(e);
454      }
455    }
456
457    copySecureArgsList.hostNameArg.clearValues();
458    copySecureArgsList.hostNameArg.addValue(state.hostName);
459    commandBuilder.addArgument(copySecureArgsList.hostNameArg);
460
461    // Connection type
462    state.useSSL = secureArgsList.useSSL();
463    state.useStartTLS = secureArgsList.useStartTLS();
464    boolean connectionTypeIsSet =
465        secureArgsList.alwaysSSL()
466            || secureArgsList.useSSLArg.isPresent()
467            || secureArgsList.useStartTLSArg.isPresent()
468            || (secureArgsList.useSSLArg.isValueSetByProperty() && secureArgsList.useStartTLSArg
469                .isValueSetByProperty());
470    if (app.isInteractive() && !connectionTypeIsSet)
471    {
472      checkHeadingDisplayed();
473
474      MenuBuilder<Integer> builder = new MenuBuilder<>(app);
475      builder.setPrompt(INFO_LDAP_CONN_PROMPT_SECURITY_USE_SECURE_CTX.get());
476
477      Protocols defaultProtocol;
478      if (secureConnection)
479      {
480        defaultProtocol = Protocols.SSL;
481      }
482      else
483      {
484        defaultProtocol = Protocols.LDAP;
485      }
486      for (Protocols p : Protocols.values())
487      {
488        if (secureConnection && p.equals(Protocols.LDAP) && !displayLdapIfSecureParameters)
489        {
490          continue;
491        }
492        if (!canUseStartTLS && p.equals(Protocols.START_TLS))
493        {
494          continue;
495        }
496        int i =
497            builder.addNumberedOption(p.getMenuMessage(), MenuResult.success(p
498                .getChoice()));
499        if (p.equals(defaultProtocol))
500        {
501          builder.setDefault(
502              INFO_LDAP_CONN_PROMPT_SECURITY_PROTOCOL_DEFAULT_CHOICE.get(i),
503              MenuResult.success(p.getChoice()));
504        }
505      }
506
507      Menu<Integer> menu = builder.toMenu();
508      try
509      {
510        MenuResult<Integer> result = menu.run();
511        if (result.isSuccess())
512        {
513          if (result.getValue().equals(Protocols.SSL.getChoice()))
514          {
515            state.useSSL = true;
516          }
517          else if (result.getValue().equals(Protocols.START_TLS.getChoice()))
518          {
519            state.useStartTLS = true;
520          }
521        }
522        else
523        {
524          // Should never happen.
525          throw new RuntimeException();
526        }
527      }
528      catch (ClientException e)
529      {
530        throw new RuntimeException(e);
531      }
532    }
533
534    if (state.useSSL)
535    {
536      commandBuilder.addArgument(copySecureArgsList.useSSLArg);
537    }
538    else if (state.useStartTLS)
539    {
540      commandBuilder.addArgument(copySecureArgsList.useStartTLSArg);
541    }
542
543    // Get the LDAP port.
544    if (!state.useSSL)
545    {
546      portNumber = secureArgsList.portArg.getIntValue();
547    }
548    else
549    {
550      if (secureArgsList.portArg.isPresent())
551      {
552        portNumber = secureArgsList.portArg.getIntValue();
553      }
554      else
555      {
556        portNumber = secureArgsList.getPortFromConfig();
557      }
558    }
559
560    final int tmpPortNumber = portNumber;
561    if (app.isInteractive() && !secureArgsList.portArg.isPresent())
562    {
563      checkHeadingDisplayed();
564
565      ValidationCallback<Integer> callback = new ValidationCallback<Integer>()
566      {
567
568        @Override
569        public Integer validate(ConsoleApplication app, String input)
570            throws ClientException
571        {
572          String ninput = input.trim();
573          if (ninput.length() == 0)
574          {
575            return tmpPortNumber;
576          }
577          else
578          {
579            try
580            {
581              int i = Integer.parseInt(ninput);
582              if (i < 1 || i > 65535)
583              {
584                throw new NumberFormatException();
585              }
586              return i;
587            }
588            catch (NumberFormatException e)
589            {
590              // Try again...
591              app.println();
592              app.println(ERR_LDAP_CONN_BAD_PORT_NUMBER.get(ninput));
593              app.println();
594              return null;
595            }
596          }
597        }
598
599      };
600
601      try
602      {
603        app.println();
604        LocalizableMessage askPortNumber = null;
605        if (secureArgsList.alwaysSSL())
606        {
607          askPortNumber = INFO_ADMIN_CONN_PROMPT_PORT_NUMBER.get(portNumber);
608        }
609        else
610        {
611          askPortNumber = INFO_LDAP_CONN_PROMPT_PORT_NUMBER.get(portNumber);
612        }
613        portNumber = app.readValidatedInput(askPortNumber, callback);
614      }
615      catch (ClientException e)
616      {
617        throw cannotReadConnectionParameters(e);
618      }
619    }
620
621    copySecureArgsList.portArg.clearValues();
622    copySecureArgsList.portArg.addValue(String.valueOf(portNumber));
623    commandBuilder.addArgument(copySecureArgsList.portArg);
624
625    // Handle certificate
626    if ((state.useSSL || state.useStartTLS) && state.trustManager == null)
627    {
628      initializeTrustManager();
629    }
630
631    // Get the LDAP bind credentials.
632    state.bindDN = secureArgsList.bindDnArg.getValue();
633    state.adminUID= secureArgsList.adminUidArg.getValue();
634    final boolean useAdmin = secureArgsList.useAdminUID();
635    if (useAdmin && secureArgsList.adminUidArg.isPresent())
636    {
637      state.providedAdminUID = state.adminUID;
638    }
639    else
640    {
641      state.providedAdminUID = null;
642    }
643    if ((!useAdmin || useAdminOrBindDn) && secureArgsList.bindDnArg.isPresent())
644    {
645      state.providedBindDN = state.bindDN;
646    }
647    else
648    {
649      state.providedBindDN = null;
650    }
651    boolean argIsPresent = state.providedAdminUID != null || state.providedBindDN != null;
652    final String tmpBindDN = state.bindDN;
653    final String tmpAdminUID = state.adminUID;
654    if (state.keyManager == null)
655    {
656      if (app.isInteractive() && !argIsPresent)
657      {
658        checkHeadingDisplayed();
659
660        ValidationCallback<String> callback = new ValidationCallback<String>()
661        {
662
663          @Override
664          public String validate(ConsoleApplication app, String input)
665              throws ClientException
666          {
667            String ninput = input.trim();
668            if (ninput.length() == 0)
669            {
670              if (useAdmin)
671              {
672                return tmpAdminUID;
673              }
674              else
675              {
676                return tmpBindDN;
677              }
678            }
679            else
680            {
681              return ninput;
682            }
683          }
684
685        };
686
687        try
688        {
689          app.println();
690          if (useAdminOrBindDn)
691          {
692            String def = state.adminUID != null ? state.adminUID : state.bindDN;
693            String v =
694                app.readValidatedInput(
695                    INFO_LDAP_CONN_GLOBAL_ADMINISTRATOR_OR_BINDDN_PROMPT.get(def), callback);
696            if (isDN(v))
697            {
698              state.bindDN = v;
699              state.providedBindDN = v;
700              state.adminUID = null;
701              state.providedAdminUID = null;
702            }
703            else
704            {
705              state.bindDN = null;
706              state.providedBindDN = null;
707              state.adminUID = v;
708              state.providedAdminUID = v;
709            }
710          }
711          else if (useAdmin)
712          {
713            state.adminUID =
714                app.readValidatedInput(INFO_LDAP_CONN_PROMPT_ADMINISTRATOR_UID.get(state.adminUID), callback);
715            state.providedAdminUID = state.adminUID;
716          }
717          else
718          {
719            state.bindDN =
720                app.readValidatedInput(INFO_LDAP_CONN_PROMPT_BIND_DN.get(state.bindDN), callback);
721            state.providedBindDN = state.bindDN;
722          }
723        }
724        catch (ClientException e)
725        {
726          throw cannotReadConnectionParameters(e);
727        }
728      }
729      if (useAdminOrBindDn)
730      {
731        boolean addAdmin = state.providedAdminUID != null;
732        boolean addBindDN = state.providedBindDN != null;
733        if (!addAdmin && !addBindDN)
734        {
735          addAdmin = getAdministratorUID() != null;
736          addBindDN = getBindDN() != null;
737        }
738        if (addAdmin)
739        {
740          copySecureArgsList.adminUidArg.clearValues();
741          copySecureArgsList.adminUidArg.addValue(getAdministratorUID());
742          commandBuilder.addArgument(copySecureArgsList.adminUidArg);
743        }
744        else if (addBindDN)
745        {
746          copySecureArgsList.bindDnArg.clearValues();
747          copySecureArgsList.bindDnArg.addValue(getBindDN());
748          commandBuilder.addArgument(copySecureArgsList.bindDnArg);
749        }
750      }
751      else if (useAdmin)
752      {
753        copySecureArgsList.adminUidArg.clearValues();
754        copySecureArgsList.adminUidArg.addValue(getAdministratorUID());
755        commandBuilder.addArgument(copySecureArgsList.adminUidArg);
756      }
757      else
758      {
759        copySecureArgsList.bindDnArg.clearValues();
760        copySecureArgsList.bindDnArg.addValue(getBindDN());
761        commandBuilder.addArgument(copySecureArgsList.bindDnArg);
762      }
763    }
764    else
765    {
766      state.bindDN = null;
767      state.adminUID = null;
768    }
769
770    boolean addedPasswordFileArgument = false;
771    if (secureArgsList.bindPasswordArg.isPresent())
772    {
773      state.bindPassword = secureArgsList.bindPasswordArg.getValue();
774    }
775    if (state.keyManager == null)
776    {
777      if (secureArgsList.bindPasswordFileArg.isPresent())
778      {
779        // Read from file if it exists.
780        state.bindPassword = secureArgsList.bindPasswordFileArg.getValue();
781
782        if (state.bindPassword == null)
783        {
784          if (useAdmin)
785          {
786            throw new ArgumentException(ERR_ERROR_NO_ADMIN_PASSWORD.get(state.adminUID));
787          }
788          else
789          {
790            throw new ArgumentException(ERR_ERROR_NO_ADMIN_PASSWORD.get(state.bindDN));
791          }
792        }
793        copySecureArgsList.bindPasswordFileArg.clearValues();
794        copySecureArgsList.bindPasswordFileArg.getNameToValueMap().putAll(
795            secureArgsList.bindPasswordFileArg.getNameToValueMap());
796        commandBuilder.addArgument(copySecureArgsList.bindPasswordFileArg);
797        addedPasswordFileArgument = true;
798      }
799      else if (state.bindPassword == null || "-".equals(state.bindPassword))
800      {
801        // Read the password from the stdin.
802        if (!app.isInteractive())
803        {
804          throw new ArgumentException(ERR_ERROR_BIND_PASSWORD_NONINTERACTIVE.get());
805        }
806
807        checkHeadingDisplayed();
808
809        try
810        {
811          app.println();
812          state.bindPassword = readPassword(state.getPrompt());
813        }
814        catch (Exception e)
815        {
816          throw new ArgumentException(ERR_ERROR_CANNOT_READ_CONNECTION_PARAMETERS.get(e.getMessage()), e.getCause());
817        }
818      }
819      copySecureArgsList.bindPasswordArg.clearValues();
820      copySecureArgsList.bindPasswordArg.addValue(state.bindPassword);
821      if (!addedPasswordFileArgument)
822      {
823        commandBuilder.addObfuscatedArgument(copySecureArgsList.bindPasswordArg);
824      }
825    }
826    state.connectTimeout = secureArgsList.connectTimeoutArg.getIntValue();
827  }
828
829  private ArgumentException cannotReadConnectionParameters(ClientException e)
830  {
831    return new ArgumentException(ERR_ERROR_CANNOT_READ_CONNECTION_PARAMETERS.get(e.getMessage()), e.getCause());
832  }
833
834  private String readPassword(LocalizableMessage prompt) throws ClientException
835  {
836    final char[] pwd = app.readPassword(prompt);
837    if (pwd != null)
838    {
839      return String.valueOf(pwd);
840    }
841    return null;
842  }
843
844  /**
845   * Get the trust manager.
846   *
847   * @return The trust manager based on CLI args on interactive prompt.
848   * @throws ArgumentException
849   *           If an error occurs when getting args values.
850   */
851  private ApplicationTrustManager getTrustManagerInternal()
852      throws ArgumentException
853  {
854    // Remove these arguments since this method might be called several times.
855    commandBuilder.removeArgument(copySecureArgsList.trustAllArg);
856    commandBuilder.removeArgument(copySecureArgsList.trustStorePathArg);
857    commandBuilder.removeArgument(copySecureArgsList.trustStorePasswordArg);
858    commandBuilder.removeArgument(copySecureArgsList.trustStorePasswordFileArg);
859
860    // If we have the trustALL flag, don't do anything
861    // just return null
862    if (secureArgsList.trustAllArg.isPresent())
863    {
864      commandBuilder.addArgument(copySecureArgsList.trustAllArg);
865      return null;
866    }
867
868    // Check if some trust manager info are set
869    boolean weDontKnowTheTrustMethod =
870        !secureArgsList.trustAllArg.isPresent()
871        && !secureArgsList.trustStorePathArg.isPresent()
872        && !secureArgsList.trustStorePasswordArg.isPresent()
873        && !secureArgsList.trustStorePasswordFileArg.isPresent();
874    boolean askForTrustStore = false;
875
876    state.trustAll = secureArgsList.trustAllArg.isPresent();
877
878    // Try to use the local instance trust store, to avoid certificate
879    // validation when both the CLI and the server are in the same instance.
880    if (weDontKnowTheTrustMethod && addLocalTrustStore())
881    {
882      weDontKnowTheTrustMethod = false;
883    }
884
885    if (app.isInteractive() && weDontKnowTheTrustMethod)
886    {
887      checkHeadingDisplayed();
888
889      app.println();
890      MenuBuilder<Integer> builder = new MenuBuilder<>(app);
891      builder.setPrompt(INFO_LDAP_CONN_PROMPT_SECURITY_TRUST_METHOD.get());
892
893      TrustMethod defaultTrustMethod = TrustMethod.DISPLAY_CERTIFICATE;
894      for (TrustMethod t : TrustMethod.values())
895      {
896        int i =
897            builder.addNumberedOption(t.getMenuMessage(), MenuResult.success(t
898                .getChoice()));
899        if (t.equals(defaultTrustMethod))
900        {
901          builder.setDefault(
902              INFO_LDAP_CONN_PROMPT_SECURITY_PROTOCOL_DEFAULT_CHOICE
903                  .get(Integer.valueOf(i)), MenuResult.success(t.getChoice()));
904        }
905      }
906
907      Menu<Integer> menu = builder.toMenu();
908      state.trustStoreInMemory = false;
909      try
910      {
911        MenuResult<Integer> result = menu.run();
912        if (result.isSuccess())
913        {
914          if (result.getValue().equals(TrustMethod.TRUSTALL.getChoice()))
915          {
916            commandBuilder.addArgument(copySecureArgsList.trustAllArg);
917            state.trustAll = true;
918            // If we have the trustALL flag, don't do anything
919            // just return null
920            return null;
921          }
922          else if (result.getValue().equals(TrustMethod.TRUSTSTORE.getChoice()))
923          {
924            // We have to ask for trust store info
925            askForTrustStore = true;
926          }
927          else if (result.getValue().equals(
928              TrustMethod.DISPLAY_CERTIFICATE.getChoice()))
929          {
930            // The certificate will be displayed to the user
931            askForTrustStore = false;
932            state.trustStoreInMemory = true;
933
934            // There is no direct equivalent for this option, so propose the
935            // trust all option as command-line argument.
936            commandBuilder.addArgument(copySecureArgsList.trustAllArg);
937          }
938          else
939          {
940            // Should never happen.
941            throw new RuntimeException();
942          }
943        }
944        else
945        {
946          // Should never happen.
947          throw new RuntimeException();
948        }
949      }
950      catch (ClientException e)
951      {
952        throw new RuntimeException(e);
953
954      }
955    }
956
957    // If we do not trust all server certificates, we have to get info
958    // about trust store. First get the trust store path.
959    state.truststorePath = secureArgsList.trustStorePathArg.getValue();
960
961    if (app.isInteractive() && !secureArgsList.trustStorePathArg.isPresent() && askForTrustStore)
962    {
963      checkHeadingDisplayed();
964
965      ValidationCallback<String> callback = new ValidationCallback<String>()
966      {
967        @Override
968        public String validate(ConsoleApplication app, String input)
969            throws ClientException
970        {
971          String ninput = input.trim();
972          if (ninput.length() == 0)
973          {
974            app.println();
975            app.println(ERR_LDAP_CONN_PROMPT_SECURITY_INVALID_FILE_PATH.get());
976            app.println();
977            return null;
978          }
979          File f = new File(ninput);
980          if (f.exists() && f.canRead() && !f.isDirectory())
981          {
982            return ninput;
983          }
984          else
985          {
986            app.println();
987            app.println(ERR_LDAP_CONN_PROMPT_SECURITY_INVALID_FILE_PATH.get());
988            app.println();
989            return null;
990          }
991        }
992      };
993
994      try
995      {
996        app.println();
997        state.truststorePath = app.readValidatedInput(
998                INFO_LDAP_CONN_PROMPT_SECURITY_TRUSTSTORE_PATH.get(), callback);
999      }
1000      catch (ClientException e)
1001      {
1002        throw cannotReadConnectionParameters(e);
1003      }
1004    }
1005
1006    if (state.truststorePath != null)
1007    {
1008      copySecureArgsList.trustStorePathArg.clearValues();
1009      copySecureArgsList.trustStorePathArg.addValue(state.truststorePath);
1010      commandBuilder.addArgument(copySecureArgsList.trustStorePathArg);
1011    }
1012
1013    // Then the truststore password.
1014    //  As the most common case is to have no password for truststore,
1015    // we don't ask it in the interactive mode.
1016    if (secureArgsList.trustStorePasswordArg.isPresent())
1017    {
1018      state.truststorePassword = secureArgsList.trustStorePasswordArg.getValue();
1019    }
1020    if (secureArgsList.trustStorePasswordFileArg.isPresent())
1021    {
1022      // Read from file if it exists.
1023      state.truststorePassword = secureArgsList.trustStorePasswordFileArg.getValue();
1024    }
1025    if ("-".equals(state.truststorePassword))
1026    {
1027      // Read the password from the stdin.
1028      if (!app.isInteractive())
1029      {
1030        state.truststorePassword = null;
1031      }
1032      else
1033      {
1034        checkHeadingDisplayed();
1035
1036        try
1037        {
1038          app.println();
1039          LocalizableMessage prompt = INFO_LDAP_CONN_PROMPT_SECURITY_TRUSTSTORE_PASSWORD.get(state.truststorePath);
1040          state.truststorePassword = readPassword(prompt);
1041        }
1042        catch (Exception e)
1043        {
1044          throw new ArgumentException(ERR_ERROR_CANNOT_READ_CONNECTION_PARAMETERS.get(e.getMessage()), e.getCause());
1045        }
1046      }
1047    }
1048
1049    // We've got all the information to get the truststore manager
1050    try
1051    {
1052      state.truststore = KeyStore.getInstance(KeyStore.getDefaultType());
1053      if (state.truststorePath != null)
1054      {
1055        try (FileInputStream fos = new FileInputStream(state.truststorePath))
1056        {
1057          if (state.truststorePassword != null)
1058          {
1059            state.truststore.load(fos, state.truststorePassword.toCharArray());
1060          }
1061          else
1062          {
1063            state.truststore.load(fos, null);
1064          }
1065        }
1066      }
1067      else
1068      {
1069        state.truststore.load(null, null);
1070      }
1071
1072      if (secureArgsList.trustStorePasswordFileArg.isPresent() && state.truststorePath != null)
1073      {
1074        copySecureArgsList.trustStorePasswordFileArg.clearValues();
1075        copySecureArgsList.trustStorePasswordFileArg.getNameToValueMap()
1076            .putAll(secureArgsList.trustStorePasswordFileArg.getNameToValueMap());
1077        commandBuilder.addArgument(copySecureArgsList.trustStorePasswordFileArg);
1078      }
1079      else if (state.truststorePassword != null && state.truststorePath != null)
1080      {
1081        // Only add the trust store password if there is one AND if the user
1082        // specified a trust store path.
1083        copySecureArgsList.trustStorePasswordArg.clearValues();
1084        copySecureArgsList.trustStorePasswordArg.addValue(state.truststorePassword);
1085        commandBuilder.addObfuscatedArgument(copySecureArgsList.trustStorePasswordArg);
1086      }
1087
1088      return new ApplicationTrustManager(state.truststore);
1089    }
1090    catch (Exception e)
1091    {
1092      throw new ArgumentException(ERR_ERROR_CANNOT_READ_CONNECTION_PARAMETERS.get(e.getMessage()), e.getCause());
1093    }
1094  }
1095
1096  /**
1097   * Get the key manager.
1098   *
1099   * @return The key manager based on CLI args on interactive prompt.
1100   * @throws ArgumentException
1101   *           If an error occurs when getting args values.
1102   */
1103  private KeyManager getKeyManagerInternal() throws ArgumentException
1104  {
1105    //  Remove these arguments since this method might be called several times.
1106    commandBuilder.removeArgument(copySecureArgsList.certNicknameArg);
1107    commandBuilder.removeArgument(copySecureArgsList.keyStorePathArg);
1108    commandBuilder.removeArgument(copySecureArgsList.keyStorePasswordArg);
1109    commandBuilder.removeArgument(copySecureArgsList.keyStorePasswordFileArg);
1110
1111    // Do we need client side authentication ?
1112    // If one of the client side authentication args is set, we assume
1113    // that we
1114    // need client side authentication.
1115    boolean weDontKnowIfWeNeedKeystore =
1116        !secureArgsList.keyStorePathArg.isPresent()
1117        && !secureArgsList.keyStorePasswordArg.isPresent()
1118        && !secureArgsList.keyStorePasswordFileArg.isPresent()
1119        && !secureArgsList.certNicknameArg.isPresent();
1120
1121    // We don't have specific key manager parameter.
1122    // We assume that no client side authentication is required
1123    // Client side authentication is not the common use case. As a
1124    // consequence, interactive mode doesn't add an extra question
1125    // which will be in most cases useless.
1126    if (weDontKnowIfWeNeedKeystore)
1127    {
1128      return null;
1129    }
1130
1131    // Get info about keystore. First get the keystore path.
1132    state.keystorePath = secureArgsList.keyStorePathArg.getValue();
1133    if (app.isInteractive() && !secureArgsList.keyStorePathArg.isPresent())
1134    {
1135      checkHeadingDisplayed();
1136
1137      ValidationCallback<String> callback = new ValidationCallback<String>()
1138      {
1139        @Override
1140        public String validate(ConsoleApplication app, String input)
1141            throws ClientException
1142        {
1143          String ninput = input.trim();
1144          if (ninput.length() == 0)
1145          {
1146            return ninput;
1147          }
1148          File f = new File(ninput);
1149          if (f.exists() && f.canRead() && !f.isDirectory())
1150          {
1151            return ninput;
1152          }
1153          else
1154          {
1155            app.println();
1156            app.println(ERR_LDAP_CONN_PROMPT_SECURITY_INVALID_FILE_PATH.get());
1157            app.println();
1158            return null;
1159          }
1160        }
1161      };
1162
1163      try
1164      {
1165        app.println();
1166        state.keystorePath = app.readValidatedInput(INFO_LDAP_CONN_PROMPT_SECURITY_KEYSTORE_PATH.get(), callback);
1167      }
1168      catch (ClientException e)
1169      {
1170        throw cannotReadConnectionParameters(e);
1171      }
1172    }
1173
1174    if (state.keystorePath != null)
1175    {
1176      copySecureArgsList.keyStorePathArg.clearValues();
1177      copySecureArgsList.keyStorePathArg.addValue(state.keystorePath);
1178      commandBuilder.addArgument(copySecureArgsList.keyStorePathArg);
1179    }
1180    else
1181    {
1182      // KeystorePath is null. Either it's unspecified or there's a pb
1183      // We should throw an exception here, anyway since code below will
1184      // anyway
1185      throw new ArgumentException(ERR_ERROR_INCOMPATIBLE_PROPERTY_MOD.get("null keystorePath"));
1186    }
1187
1188    // Then the keystore password.
1189    state.keystorePassword = secureArgsList.keyStorePasswordArg.getValue();
1190
1191    if (secureArgsList.keyStorePasswordFileArg.isPresent())
1192    {
1193      // Read from file if it exists.
1194      state.keystorePassword = secureArgsList.keyStorePasswordFileArg.getValue();
1195
1196      if (state.keystorePassword == null)
1197      {
1198        throw new ArgumentException(ERR_ERROR_NO_ADMIN_PASSWORD.get(state.keystorePassword));
1199      }
1200    }
1201    else if (state.keystorePassword == null || "-".equals(state.keystorePassword))
1202    {
1203      // Read the password from the stdin.
1204      if (!app.isInteractive())
1205      {
1206        throw new ArgumentException(ERR_ERROR_BIND_PASSWORD_NONINTERACTIVE.get());
1207      }
1208
1209      checkHeadingDisplayed();
1210
1211      try
1212      {
1213        app.println();
1214        LocalizableMessage prompt = INFO_LDAP_CONN_PROMPT_SECURITY_KEYSTORE_PASSWORD.get(state.keystorePath);
1215        state.keystorePassword = readPassword(prompt);
1216      }
1217      catch (Exception e)
1218      {
1219        throw new ArgumentException(ERR_ERROR_CANNOT_READ_CONNECTION_PARAMETERS.get(e.getMessage()), e.getCause());
1220      }
1221    }
1222
1223    // finally the certificate name, if needed.
1224    KeyStore keystore = null;
1225    Enumeration<String> aliasesEnum = null;
1226    try (FileInputStream fos = new FileInputStream(state.keystorePath))
1227    {
1228      keystore = KeyStore.getInstance(KeyStore.getDefaultType());
1229      keystore.load(fos, state.keystorePassword.toCharArray());
1230      aliasesEnum = keystore.aliases();
1231    }
1232    catch (Exception e)
1233    {
1234      throw new ArgumentException(ERR_ERROR_CANNOT_READ_CONNECTION_PARAMETERS.get(e.getMessage()), e.getCause());
1235    }
1236
1237    state.certifNickname = secureArgsList.certNicknameArg.getValue();
1238    if (app.isInteractive() && !secureArgsList.certNicknameArg.isPresent() && aliasesEnum.hasMoreElements())
1239    {
1240      checkHeadingDisplayed();
1241
1242      try
1243      {
1244        MenuBuilder<String> builder = new MenuBuilder<>(app);
1245        builder.setPrompt(INFO_LDAP_CONN_PROMPT_SECURITY_CERTIFICATE_ALIASES.get());
1246        int certificateNumber = 0;
1247        for (; aliasesEnum.hasMoreElements();)
1248        {
1249          String alias = aliasesEnum.nextElement();
1250          if (keystore.isKeyEntry(alias))
1251          {
1252            X509Certificate certif =
1253                (X509Certificate) keystore.getCertificate(alias);
1254            certificateNumber++;
1255            builder.addNumberedOption(
1256                    INFO_LDAP_CONN_PROMPT_SECURITY_CERTIFICATE_ALIAS.get(alias,
1257                        certif.getSubjectDN().getName()), MenuResult.success(alias));
1258          }
1259        }
1260
1261        if (certificateNumber > 1)
1262        {
1263          app.println();
1264          Menu<String> menu = builder.toMenu();
1265          MenuResult<String> result = menu.run();
1266          if (result.isSuccess())
1267          {
1268            state.certifNickname = result.getValue();
1269          }
1270          else
1271          {
1272            // Should never happen.
1273            throw new RuntimeException();
1274          }
1275        }
1276        else
1277        {
1278          state.certifNickname = null;
1279        }
1280      }
1281      catch (KeyStoreException e)
1282      {
1283        throw new ArgumentException(ERR_ERROR_CANNOT_READ_CONNECTION_PARAMETERS.get(e.getMessage()), e.getCause());
1284      }
1285      catch (ClientException e)
1286      {
1287        throw cannotReadConnectionParameters(e);
1288      }
1289    }
1290
1291    // We'we got all the information to get the keys manager
1292    ApplicationKeyManager akm =
1293        new ApplicationKeyManager(keystore, state.keystorePassword.toCharArray());
1294
1295    if (secureArgsList.keyStorePasswordFileArg.isPresent())
1296    {
1297      copySecureArgsList.keyStorePasswordFileArg.clearValues();
1298      copySecureArgsList.keyStorePasswordFileArg.getNameToValueMap().putAll(
1299          secureArgsList.keyStorePasswordFileArg.getNameToValueMap());
1300      commandBuilder.addArgument(copySecureArgsList.keyStorePasswordFileArg);
1301    }
1302    else if (state.keystorePassword != null)
1303    {
1304      copySecureArgsList.keyStorePasswordArg.clearValues();
1305      copySecureArgsList.keyStorePasswordArg.addValue(state.keystorePassword);
1306      commandBuilder.addObfuscatedArgument(copySecureArgsList.keyStorePasswordArg);
1307    }
1308
1309    if (state.certifNickname != null)
1310    {
1311      copySecureArgsList.certNicknameArg.clearValues();
1312      copySecureArgsList.certNicknameArg.addValue(state.certifNickname);
1313      return SelectableCertificateKeyManager.wrap(
1314          new KeyManager[] { akm },
1315          CollectionUtils.newTreeSet(state.certifNickname))[0];
1316    }
1317    return akm;
1318  }
1319
1320  /**
1321   * Indicates whether or not a connection should use SSL based on this
1322   * interaction.
1323   *
1324   * @return boolean where true means use SSL
1325   */
1326  public boolean useSSL()
1327  {
1328    return state.useSSL;
1329  }
1330
1331  /**
1332   * Indicates whether or not a connection should use StartTLS based on this
1333   * interaction.
1334   *
1335   * @return boolean where true means use StartTLS
1336   */
1337  public boolean useStartTLS()
1338  {
1339    return state.useStartTLS;
1340  }
1341
1342  /**
1343   * Gets the host name that should be used for connections based on this
1344   * interaction.
1345   *
1346   * @return host name for connections
1347   */
1348  public String getHostName()
1349  {
1350    return state.hostName;
1351  }
1352
1353  /**
1354   * Gets the port number name that should be used for connections based on this
1355   * interaction.
1356   *
1357   * @return port number for connections
1358   */
1359  public int getPortNumber()
1360  {
1361    return portNumber;
1362  }
1363
1364  /**
1365   * Sets the port number name that should be used for connections based on this
1366   * interaction.
1367   *
1368   * @param portNumber
1369   *          port number for connections
1370   */
1371  public void setPortNumber(int portNumber)
1372  {
1373    this.portNumber = portNumber;
1374  }
1375
1376  /**
1377   * Gets the bind DN name that should be used for connections based on this
1378   * interaction.
1379   *
1380   * @return bind DN for connections
1381   */
1382  public String getBindDN()
1383  {
1384    if (useAdminOrBindDn)
1385    {
1386      return state.getAdminOrBindDN();
1387    }
1388    else if (secureArgsList.useAdminUID())
1389    {
1390      return getAdministratorDN(state.adminUID);
1391    }
1392    else
1393    {
1394      return state.bindDN;
1395    }
1396  }
1397
1398  /**
1399   * Gets the administrator UID name that should be used for connections based
1400   * on this interaction.
1401   *
1402   * @return administrator UID for connections
1403   */
1404  public String getAdministratorUID()
1405  {
1406    return state.adminUID;
1407  }
1408
1409  /**
1410   * Gets the bind password that should be used for connections based on this
1411   * interaction.
1412   *
1413   * @return bind password for connections
1414   */
1415  public String getBindPassword()
1416  {
1417    return state.bindPassword;
1418  }
1419
1420  /**
1421   * Gets the trust manager that should be used for connections based on this
1422   * interaction.
1423   *
1424   * @return trust manager for connections
1425   */
1426  public ApplicationTrustManager getTrustManager()
1427  {
1428    return state.trustManager;
1429  }
1430
1431  /**
1432   * Gets the key store that should be used for connections based on this
1433   * interaction.
1434   *
1435   * @return key store for connections
1436   */
1437  public KeyStore getKeyStore()
1438  {
1439    return state.truststore;
1440  }
1441
1442  /**
1443   * Gets the key manager that should be used for connections based on this
1444   * interaction.
1445   *
1446   * @return key manager for connections
1447   */
1448  public KeyManager getKeyManager()
1449  {
1450    return state.keyManager;
1451  }
1452
1453  /**
1454   * Indicate if the trust store is in memory.
1455   *
1456   * @return true if the trust store is in memory.
1457   */
1458  public boolean isTrustStoreInMemory()
1459  {
1460    return state.trustStoreInMemory;
1461  }
1462
1463  /**
1464   * Indicate if all certificates must be accepted.
1465   *
1466   * @return true all certificates must be accepted.
1467   */
1468  public boolean isTrustAll()
1469  {
1470    return state.trustAll;
1471  }
1472
1473  /**
1474   * Returns the timeout to be used to connect with the server.
1475   *
1476   * @return the timeout to be used to connect with the server.
1477   */
1478  public int getConnectTimeout()
1479  {
1480    return state.connectTimeout;
1481  }
1482
1483  /**
1484   * Indicate if the certificate chain can be trusted.
1485   *
1486   * @param chain
1487   *          The certificate chain to validate
1488   * @param authType
1489   *          the authentication type.
1490   * @param host
1491   *          the host we tried to connect and that presented the certificate.
1492   * @return true if the server certificate is trusted.
1493   */
1494  public boolean checkServerCertificate(X509Certificate[] chain,
1495      String authType, String host)
1496  {
1497    if (state.trustManager == null)
1498    {
1499      try
1500      {
1501        initializeTrustManager();
1502      }
1503      catch (ArgumentException ae)
1504      {
1505        // Should not occur
1506        throw new RuntimeException(ae);
1507      }
1508    }
1509    app.println();
1510    app.println(INFO_LDAP_CONN_PROMPT_SECURITY_SERVER_CERTIFICATE.get());
1511    app.println();
1512    for (int i = 0; i < chain.length; i++)
1513    {
1514      // Certificate DN
1515      app.println(INFO_LDAP_CONN_SECURITY_SERVER_CERTIFICATE_USER_DN.get(chain[i].getSubjectDN()));
1516
1517      // certificate validity
1518      app.println(INFO_LDAP_CONN_SECURITY_SERVER_CERTIFICATE_VALIDITY.get(
1519          chain[i].getNotBefore(), chain[i].getNotAfter()));
1520
1521      // certificate Issuer
1522      app.println(INFO_LDAP_CONN_SECURITY_SERVER_CERTIFICATE_ISSUER.get(chain[i].getIssuerDN()));
1523
1524      if (i + 1 < chain.length)
1525      {
1526        app.println();
1527        app.println();
1528      }
1529    }
1530    MenuBuilder<Integer> builder = new MenuBuilder<>(app);
1531    builder.setPrompt(INFO_LDAP_CONN_PROMPT_SECURITY_TRUST_OPTION.get());
1532
1533    TrustOption defaultTrustMethod = TrustOption.SESSION;
1534    for (TrustOption t : TrustOption.values())
1535    {
1536      int i = builder.addNumberedOption(t.getMenuMessage(), MenuResult.success(t.getChoice()));
1537      if (t.equals(defaultTrustMethod))
1538      {
1539        builder.setDefault(INFO_LDAP_CONN_PROMPT_SECURITY_PROTOCOL_DEFAULT_CHOICE.get(
1540            Integer.valueOf(i)), MenuResult.success(t.getChoice()));
1541      }
1542    }
1543
1544    app.println();
1545    app.println();
1546
1547    Menu<Integer> menu = builder.toMenu();
1548    while (true)
1549    {
1550      try
1551      {
1552        MenuResult<Integer> result = menu.run();
1553        if (result.isSuccess())
1554        {
1555          if (result.getValue().equals(TrustOption.UNTRUSTED.getChoice()))
1556          {
1557            return false;
1558          }
1559
1560          if (result.getValue().equals(
1561              TrustOption.CERTIFICATE_DETAILS.getChoice()))
1562          {
1563            for (X509Certificate cert : chain)
1564            {
1565              app.println();
1566              app.println(INFO_LDAP_CONN_SECURITY_SERVER_CERTIFICATE.get(cert));
1567            }
1568            continue;
1569          }
1570
1571          // We should add it in the memory truststore
1572          for (X509Certificate cert : chain)
1573          {
1574            String alias = cert.getSubjectDN().getName();
1575            try
1576            {
1577              state.truststore.setCertificateEntry(alias, cert);
1578            }
1579            catch (KeyStoreException e1)
1580            {
1581              // What else should we do?
1582              return false;
1583            }
1584          }
1585
1586          // Update the trust manager
1587          if (state.trustManager == null)
1588          {
1589            state.trustManager = new ApplicationTrustManager(state.truststore);
1590          }
1591          if (authType != null && host != null)
1592          {
1593            // Update the trust manager with the new certificate
1594            state.trustManager.acceptCertificate(chain, authType, host);
1595          }
1596          else
1597          {
1598            // Do a full reset of the contents of the keystore.
1599            state.trustManager = new ApplicationTrustManager(state.truststore);
1600          }
1601          if (result.getValue().equals(TrustOption.PERMAMENT.getChoice()))
1602          {
1603            ValidationCallback<String> callback =
1604                new ValidationCallback<String>()
1605                {
1606                  @Override
1607                  public String validate(ConsoleApplication app, String input)
1608                      throws ClientException
1609                  {
1610                    String ninput = input.trim();
1611                    if (ninput.length() == 0)
1612                    {
1613                      app.println();
1614                      app.println(ERR_LDAP_CONN_PROMPT_SECURITY_INVALID_FILE_PATH.get());
1615                      app.println();
1616                      return null;
1617                    }
1618                    File f = new File(ninput);
1619                    if (!f.isDirectory())
1620                    {
1621                      return ninput;
1622                    }
1623                    else
1624                    {
1625                      app.println();
1626                      app.println(ERR_LDAP_CONN_PROMPT_SECURITY_INVALID_FILE_PATH.get());
1627                      app.println();
1628                      return null;
1629                    }
1630                  }
1631                };
1632
1633            String truststorePath;
1634            try
1635            {
1636              app.println();
1637              truststorePath =
1638                  app.readValidatedInput(INFO_LDAP_CONN_PROMPT_SECURITY_TRUSTSTORE_PATH.get(), callback);
1639            }
1640            catch (ClientException e)
1641            {
1642              return true;
1643            }
1644
1645            // Read the password from the stdin.
1646            String truststorePassword;
1647            try
1648            {
1649              app.println();
1650              LocalizableMessage prompt = INFO_LDAP_CONN_PROMPT_SECURITY_KEYSTORE_PASSWORD.get(truststorePath);
1651              truststorePassword = readPassword(prompt);
1652            }
1653            catch (Exception e)
1654            {
1655              return true;
1656            }
1657            try
1658            {
1659              KeyStore ts = KeyStore.getInstance("JKS");
1660              FileInputStream fis;
1661              try
1662              {
1663                fis = new FileInputStream(truststorePath);
1664              }
1665              catch (FileNotFoundException e)
1666              {
1667                fis = null;
1668              }
1669              ts.load(fis, truststorePassword.toCharArray());
1670              if (fis != null)
1671              {
1672                fis.close();
1673              }
1674              for (X509Certificate cert : chain)
1675              {
1676                String alias = cert.getSubjectDN().getName();
1677                ts.setCertificateEntry(alias, cert);
1678              }
1679              FileOutputStream fos = new FileOutputStream(truststorePath);
1680              try
1681              {
1682                ts.store(fos, truststorePassword.toCharArray());
1683              }
1684              finally
1685              {
1686                fos.close();
1687              }
1688            }
1689            catch (Exception e)
1690            {
1691              return true;
1692            }
1693          }
1694          return true;
1695        }
1696        else
1697        {
1698          // Should never happen.
1699          throw new RuntimeException();
1700        }
1701      }
1702      catch (ClientException cliE)
1703      {
1704        throw new RuntimeException(cliE);
1705      }
1706    }
1707  }
1708
1709  /**
1710   * Populates a set of LDAP options with state from this interaction.
1711   *
1712   * @param options
1713   *          existing set of options; may be null in which case this method
1714   *          will create a new set of <code>LDAPConnectionOptions</code> to be
1715   *          returned
1716   * @return used during this interaction
1717   * @throws SSLConnectionException
1718   *           if this interaction has specified the use of SSL and there is a
1719   *           problem initializing the SSL connection factory
1720   */
1721  public LDAPConnectionOptions populateLDAPOptions(LDAPConnectionOptions options) throws SSLConnectionException
1722  {
1723    if (options == null)
1724    {
1725      options = new LDAPConnectionOptions();
1726    }
1727    options.setUseSSL(state.useSSL);
1728    options.setStartTLS(state.useStartTLS);
1729    if (state.useSSL)
1730    {
1731      SSLConnectionFactory sslConnectionFactory = new SSLConnectionFactory();
1732      sslConnectionFactory.init(getTrustManager() == null, state.keystorePath,
1733          state.keystorePassword, state.certifNickname, state.truststorePath, state.truststorePassword);
1734      options.setSSLConnectionFactory(sslConnectionFactory);
1735    }
1736
1737    return options;
1738  }
1739
1740  /**
1741   * Prompts the user to accept the certificate.
1742   *
1743   * @param t
1744   *          the throwable that was generated because the certificate was not
1745   *          trusted.
1746   * @param usedTrustManager
1747   *          the trustManager used when trying to establish the connection.
1748   * @param usedUrl
1749   *          the LDAP URL used to connect to the server.
1750   * @param logger
1751   *          the Logger used to log messages.
1752   * @return {@code true} if the user accepted the certificate and
1753   *         {@code false} otherwise.
1754   */
1755  public boolean promptForCertificateConfirmation(Throwable t,
1756      ApplicationTrustManager usedTrustManager, String usedUrl, LocalizedLogger logger)
1757  {
1758    ApplicationTrustManager.Cause cause;
1759    if (usedTrustManager != null)
1760    {
1761      cause = usedTrustManager.getLastRefusedCause();
1762    }
1763    else
1764    {
1765      cause = null;
1766    }
1767    if (logger != null)
1768    {
1769      logger.debug(LocalizableMessage.raw("Certificate exception cause: " + cause));
1770    }
1771
1772    if (cause != null)
1773    {
1774      String h;
1775      int p;
1776      try
1777      {
1778        URI uri = new URI(usedUrl);
1779        h = uri.getHost();
1780        p = uri.getPort();
1781      }
1782      catch (Throwable t1)
1783      {
1784        printLogger(logger, "Error parsing ldap url of ldap url. " + t1);
1785        h = INFO_NOT_AVAILABLE_LABEL.get().toString();
1786        p = -1;
1787      }
1788
1789      String authType = usedTrustManager.getLastRefusedAuthType();
1790      if (authType == null)
1791      {
1792        printLogger(logger, "Null auth type for this certificate exception.");
1793      }
1794      else
1795      {
1796        LocalizableMessage msg;
1797        if (authType.equals(ApplicationTrustManager.Cause.NOT_TRUSTED))
1798        {
1799          msg = INFO_CERTIFICATE_NOT_TRUSTED_TEXT_CLI.get(h, p);
1800        }
1801        else
1802        {
1803          msg = INFO_CERTIFICATE_NAME_MISMATCH_TEXT_CLI.get(h, p, h, h, p);
1804        }
1805        app.println(msg);
1806      }
1807
1808      X509Certificate[] chain = usedTrustManager.getLastRefusedChain();
1809      if (chain == null)
1810      {
1811        printLogger(logger, "Null chain for this certificate exception.");
1812        return false;
1813      }
1814      if (h == null)
1815      {
1816        printLogger(logger, "Null host name for this certificate exception.");
1817      }
1818      return checkServerCertificate(chain, authType, h);
1819    }
1820    else
1821    {
1822      app.println(getThrowableMsg(INFO_ERROR_CONNECTING_TO_LOCAL.get(), t));
1823    }
1824    return false;
1825  }
1826
1827  private void printLogger(final LocalizedLogger logger,
1828      final String msg)
1829  {
1830    if (logger != null)
1831    {
1832      logger.warn(LocalizableMessage.raw(msg));
1833    }
1834  }
1835
1836  /**
1837   * Sets the heading that is displayed in interactive mode.
1838   *
1839   * @param heading
1840   *          the heading that is displayed in interactive mode.
1841   */
1842  public void setHeadingMessage(LocalizableMessage heading)
1843  {
1844    this.heading = heading;
1845  }
1846
1847  /**
1848   * Returns the command builder with the equivalent arguments on the
1849   * non-interactive mode.
1850   *
1851   * @return the command builder with the equivalent arguments on the
1852   *         non-interactive mode.
1853   */
1854  public CommandBuilder getCommandBuilder()
1855  {
1856    return commandBuilder;
1857  }
1858
1859  /**
1860   * Displays the heading if it was not displayed before.
1861   */
1862  private void checkHeadingDisplayed()
1863  {
1864    if (!state.isHeadingDisplayed)
1865    {
1866      app.println();
1867      app.println();
1868      app.println(heading);
1869      state.isHeadingDisplayed = true;
1870    }
1871  }
1872
1873  /**
1874   * Tells whether during interaction we can ask for both the DN or the admin
1875   * UID.
1876   *
1877   * @return {@code true} if during interaction we can ask for both the DN
1878   *         and the admin UID and {@code false} otherwise.
1879   */
1880  public boolean isUseAdminOrBindDn()
1881  {
1882    return useAdminOrBindDn;
1883  }
1884
1885  /**
1886   * Tells whether we can ask during interaction for both the DN and the admin
1887   * UID or not.
1888   *
1889   * @param useAdminOrBindDn
1890   *          whether we can ask for both the DN and the admin UID during
1891   *          interaction or not.
1892   */
1893  public void setUseAdminOrBindDn(boolean useAdminOrBindDn)
1894  {
1895    this.useAdminOrBindDn = useAdminOrBindDn;
1896  }
1897
1898  /**
1899   * Tells whether we propose LDAP as protocol even if the user provided
1900   * security parameters. This is required in command-lines that access multiple
1901   * servers (like dsreplication).
1902   *
1903   * @param displayLdapIfSecureParameters
1904   *          whether propose LDAP as protocol even if the user provided
1905   *          security parameters or not.
1906   */
1907  public void setDisplayLdapIfSecureParameters(
1908      boolean displayLdapIfSecureParameters)
1909  {
1910    this.displayLdapIfSecureParameters = displayLdapIfSecureParameters;
1911  }
1912
1913  /**
1914   * Resets the heading displayed flag, so that next time we call run the
1915   * heading is displayed.
1916   */
1917  public void resetHeadingDisplayed()
1918  {
1919    state.isHeadingDisplayed = false;
1920  }
1921
1922  /**
1923   * Forces the initialization of the trust manager with the arguments provided
1924   * by the user.
1925   *
1926   * @throws ArgumentException
1927   *           if there is an error with the arguments provided by the user.
1928   */
1929  public void initializeTrustManagerIfRequired() throws ArgumentException
1930  {
1931    if (!state.trustManagerInitialized)
1932    {
1933      initializeTrustManager();
1934    }
1935  }
1936
1937  /**
1938   * Initializes the global arguments in the parser with the provided values.
1939   * This is useful when we want to call LDAPConnectionConsoleInteraction.run()
1940   * with some default values.
1941   *
1942   * @param hostName
1943   *          the host name.
1944   * @param port
1945   *          the port to connect to the server.
1946   * @param adminUid
1947   *          the administrator UID.
1948   * @param bindDn
1949   *          the bind DN to bind to the server.
1950   * @param bindPwd
1951   *          the password to bind.
1952   * @param pwdFile
1953   *          the Map containing the file and the password to bind.
1954   */
1955  public void initializeGlobalArguments(String hostName, int port,
1956      String adminUid, String bindDn, String bindPwd,
1957      LinkedHashMap<String, String> pwdFile)
1958  {
1959    resetConnectionArguments();
1960    if (hostName != null)
1961    {
1962      secureArgsList.hostNameArg.addValue(hostName);
1963      secureArgsList.hostNameArg.setPresent(true);
1964    }
1965    // resetConnectionArguments does not clear the values for the port
1966    secureArgsList.portArg.clearValues();
1967    if (port != -1)
1968    {
1969      secureArgsList.portArg.addValue(String.valueOf(port));
1970      secureArgsList.portArg.setPresent(true);
1971    }
1972    else
1973    {
1974      // This is done to be able to call IntegerArgument.getIntValue()
1975      secureArgsList.portArg.addValue(secureArgsList.portArg.getDefaultValue());
1976    }
1977    secureArgsList.useSSLArg.setPresent(state.useSSL);
1978    secureArgsList.useStartTLSArg.setPresent(state.useStartTLS);
1979    if (adminUid != null)
1980    {
1981      secureArgsList.adminUidArg.addValue(adminUid);
1982      secureArgsList.adminUidArg.setPresent(true);
1983    }
1984    if (bindDn != null)
1985    {
1986      secureArgsList.bindDnArg.addValue(bindDn);
1987      secureArgsList.bindDnArg.setPresent(true);
1988    }
1989    if (pwdFile != null)
1990    {
1991      secureArgsList.bindPasswordFileArg.getNameToValueMap().putAll(pwdFile);
1992      for (String value : pwdFile.keySet())
1993      {
1994        secureArgsList.bindPasswordFileArg.addValue(value);
1995      }
1996      secureArgsList.bindPasswordFileArg.setPresent(true);
1997    }
1998    else if (bindPwd != null)
1999    {
2000      secureArgsList.bindPasswordArg.addValue(bindPwd);
2001      secureArgsList.bindPasswordArg.setPresent(true);
2002    }
2003    state = new State(secureArgsList);
2004  }
2005
2006  /**
2007   * Resets the connection parameters for the LDAPConsoleInteraction object. The
2008   * reset does not apply to the certificate parameters. This is called in order
2009   * the LDAPConnectionConsoleInteraction object to ask for all this connection
2010   * parameters next time we call LDAPConnectionConsoleInteraction.run().
2011   */
2012  public void resetConnectionArguments()
2013  {
2014    secureArgsList.hostNameArg.clearValues();
2015    secureArgsList.hostNameArg.setPresent(false);
2016    secureArgsList.portArg.clearValues();
2017    secureArgsList.portArg.setPresent(false);
2018    //  This is done to be able to call IntegerArgument.getIntValue()
2019    secureArgsList.portArg.addValue(secureArgsList.portArg.getDefaultValue());
2020    secureArgsList.bindDnArg.clearValues();
2021    secureArgsList.bindDnArg.setPresent(false);
2022    secureArgsList.bindPasswordArg.clearValues();
2023    secureArgsList.bindPasswordArg.setPresent(false);
2024    secureArgsList.bindPasswordFileArg.clearValues();
2025    secureArgsList.bindPasswordFileArg.getNameToValueMap().clear();
2026    secureArgsList.bindPasswordFileArg.setPresent(false);
2027    state.bindPassword = null;
2028    secureArgsList.adminUidArg.clearValues();
2029    secureArgsList.adminUidArg.setPresent(false);
2030  }
2031
2032  private void initializeTrustManager() throws ArgumentException
2033  {
2034    // Get trust store info
2035    state.trustManager = getTrustManagerInternal();
2036
2037    // Check if we need client side authentication
2038    state.keyManager = getKeyManagerInternal();
2039
2040    state.trustManagerInitialized = true;
2041  }
2042
2043  /**
2044   * Returns the explicitly provided Admin UID from the user (interactively or
2045   * through the argument).
2046   *
2047   * @return the explicitly provided Admin UID from the user (interactively or
2048   *         through the argument).
2049   */
2050  public String getProvidedAdminUID()
2051  {
2052    return state.providedAdminUID;
2053  }
2054
2055  /**
2056   * Returns the explicitly provided bind DN from the user (interactively or
2057   * through the argument).
2058   *
2059   * @return the explicitly provided bind DN from the user (interactively or
2060   *         through the argument).
2061   */
2062  public String getProvidedBindDN()
2063  {
2064    return state.providedBindDN;
2065  }
2066
2067  /**
2068   * Add the TrustStore of the administration connector of the local instance.
2069   *
2070   * @return true if the local trust store has been added.
2071   */
2072  private boolean addLocalTrustStore()
2073  {
2074    try
2075    {
2076      // If remote host, return
2077      if (!InetAddress.getLocalHost().getHostName().equals(state.hostName)
2078          || secureArgsList.getAdminPortFromConfig() != portNumber)
2079      {
2080        return false;
2081      }
2082      // check if we are in a local instance. Already checked the host,
2083      // now check the port
2084      if (secureArgsList.getAdminPortFromConfig() != portNumber)
2085      {
2086        return false;
2087      }
2088
2089      String truststoreFileAbsolute = secureArgsList.getTruststoreFileFromConfig();
2090      if (truststoreFileAbsolute != null)
2091      {
2092        secureArgsList.trustStorePathArg.addValue(truststoreFileAbsolute);
2093        return true;
2094      }
2095      return false;
2096    }
2097    catch (Exception ex)
2098    {
2099      // do nothing
2100      return false;
2101    }
2102  }
2103}