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}