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