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