001 /* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2008 Sun Microsystems, Inc. 026 */ 027 package org.opends.server.util.cli; 028 029 030 031 import static org.opends.messages.AdminToolMessages.*; 032 import static org.opends.messages.DSConfigMessages.*; 033 import static org.opends.messages.QuickSetupMessages.*; 034 import static org.opends.messages.UtilityMessages.*; 035 import static org.opends.server.util.ServerConstants.*; 036 import static org.opends.server.util.StaticUtils.*; 037 038 import java.io.BufferedReader; 039 import java.io.EOFException; 040 import java.io.IOException; 041 import java.io.InputStream; 042 import java.io.InputStreamReader; 043 import java.io.OutputStream; 044 import java.io.PrintStream; 045 import java.io.Reader; 046 import java.util.logging.Level; 047 import java.util.logging.Logger; 048 049 import javax.naming.NamingException; 050 import javax.naming.NoPermissionException; 051 import javax.naming.ldap.InitialLdapContext; 052 import javax.net.ssl.KeyManager; 053 import javax.net.ssl.TrustManager; 054 055 import org.opends.admin.ads.util.ApplicationTrustManager; 056 import org.opends.admin.ads.util.ConnectionUtils; 057 import org.opends.admin.ads.util.OpendsCertificateException; 058 import org.opends.messages.Message; 059 import org.opends.quicksetup.util.Utils; 060 import org.opends.server.protocols.ldap.LDAPResultCode; 061 import org.opends.server.tools.ClientException; 062 import org.opends.server.types.NullOutputStream; 063 import org.opends.server.util.PasswordReader; 064 065 066 /** 067 * This class provides an abstract base class which can be used as the 068 * basis of a console-based application. 069 */ 070 public abstract class ConsoleApplication { 071 072 /** 073 * A null reader. 074 */ 075 private static final class NullReader extends Reader { 076 077 /** 078 * {@inheritDoc} 079 */ 080 @Override 081 public void close() throws IOException { 082 // Do nothing. 083 } 084 085 086 087 /** 088 * {@inheritDoc} 089 */ 090 @Override 091 public int read(char[] cbuf, int off, int len) throws IOException { 092 return -1; 093 } 094 } 095 096 // The error stream which this application should use. 097 private final PrintStream err; 098 099 // The input stream reader which this application should use. 100 private final BufferedReader in; 101 102 // The output stream which this application should use. 103 private final PrintStream out; 104 105 /** 106 * The maximum number of times we try to confirm. 107 */ 108 protected final static int CONFIRMATION_MAX_TRIES = 5; 109 110 /** 111 * Creates a new console application instance. 112 * 113 * @param in 114 * The application input stream. 115 * @param out 116 * The application output stream. 117 * @param err 118 * The application error stream. 119 */ 120 protected ConsoleApplication(BufferedReader in, PrintStream out, 121 PrintStream err) { 122 if (in != null) { 123 this.in = in; 124 } else { 125 this.in = new BufferedReader(new NullReader()); 126 } 127 128 if (out != null) { 129 this.out = out; 130 } else { 131 this.out = NullOutputStream.printStream(); 132 } 133 134 if (err != null) { 135 this.err = out; 136 } else { 137 this.err = NullOutputStream.printStream(); 138 } 139 } 140 141 142 143 /** 144 * Creates a new console application instance. 145 * 146 * @param in 147 * The application input stream. 148 * @param out 149 * The application output stream. 150 * @param err 151 * The application error stream. 152 */ 153 protected ConsoleApplication(InputStream in, OutputStream out, 154 OutputStream err) { 155 if (in != null) { 156 this.in = new BufferedReader(new InputStreamReader(in)); 157 } else { 158 this.in = new BufferedReader(new NullReader()); 159 } 160 161 if (out != null) { 162 this.out = new PrintStream(out); 163 } else { 164 this.out = NullOutputStream.printStream(); 165 } 166 167 if (err != null) { 168 this.err = new PrintStream(err); 169 } else { 170 this.err = NullOutputStream.printStream(); 171 } 172 } 173 174 175 176 /** 177 * Interactively confirms whether a user wishes to perform an 178 * action. If the application is non-interactive, then the provided 179 * default is returned automatically. 180 * 181 * @param prompt 182 * The prompt describing the action. 183 * @param defaultValue 184 * The default value for the confirmation message. This 185 * will be returned if the application is non-interactive 186 * or if the user just presses return. 187 * @return Returns <code>true</code> if the user wishes the action 188 * to be performed, or <code>false</code> if they refused, 189 * or if an exception occurred. 190 * @throws CLIException 191 * If the user's response could not be read from the 192 * console for some reason. 193 */ 194 public final boolean confirmAction(Message prompt, final boolean defaultValue) 195 throws CLIException { 196 if (!isInteractive()) { 197 return defaultValue; 198 } 199 200 final Message yes = INFO_GENERAL_YES.get(); 201 final Message no = INFO_GENERAL_NO.get(); 202 final Message errMsg = ERR_CONSOLE_APP_CONFIRM.get(yes, no); 203 prompt = INFO_MENU_PROMPT_CONFIRM.get(prompt, yes, no, defaultValue ? yes 204 : no); 205 206 ValidationCallback<Boolean> validator = new ValidationCallback<Boolean>() { 207 208 public Boolean validate(ConsoleApplication app, String input) { 209 String ninput = input.toLowerCase().trim(); 210 if (ninput.length() == 0) { 211 return defaultValue; 212 } else if (no.toString().startsWith(ninput)) { 213 return false; 214 } else if (yes.toString().startsWith(ninput)) { 215 return true; 216 } else { 217 // Try again... 218 app.println(); 219 app.println(errMsg); 220 app.println(); 221 } 222 223 return null; 224 } 225 }; 226 227 return readValidatedInput(prompt, validator, CONFIRMATION_MAX_TRIES); 228 } 229 230 231 232 /** 233 * Gets the application error stream. 234 * 235 * @return Returns the application error stream. 236 */ 237 public final PrintStream getErrorStream() { 238 return err; 239 } 240 241 242 243 /** 244 * Gets the application input stream. 245 * 246 * @return Returns the application input stream. 247 */ 248 public final BufferedReader getInputStream() { 249 return in; 250 } 251 252 253 254 /** 255 * Gets the application output stream. 256 * 257 * @return Returns the application output stream. 258 */ 259 public final PrintStream getOutputStream() { 260 return out; 261 } 262 263 264 265 /** 266 * Indicates whether or not the user has requested advanced mode. 267 * 268 * @return Returns <code>true</code> if the user has requested 269 * advanced mode. 270 */ 271 public abstract boolean isAdvancedMode(); 272 273 274 275 /** 276 * Indicates whether or not the user has requested interactive 277 * behavior. 278 * 279 * @return Returns <code>true</code> if the user has requested 280 * interactive behavior. 281 */ 282 public abstract boolean isInteractive(); 283 284 285 286 /** 287 * Indicates whether or not this console application is running in 288 * its menu-driven mode. This can be used to dictate whether output 289 * should go to the error stream or not. In addition, it may also 290 * dictate whether or not sub-menus should display a cancel option 291 * as well as a quit option. 292 * 293 * @return Returns <code>true</code> if this console application 294 * is running in its menu-driven mode. 295 */ 296 public abstract boolean isMenuDrivenMode(); 297 298 299 300 /** 301 * Indicates whether or not the user has requested quiet output. 302 * 303 * @return Returns <code>true</code> if the user has requested 304 * quiet output. 305 */ 306 public abstract boolean isQuiet(); 307 308 309 310 /** 311 * Indicates whether or not the user has requested script-friendly 312 * output. 313 * 314 * @return Returns <code>true</code> if the user has requested 315 * script-friendly output. 316 */ 317 public abstract boolean isScriptFriendly(); 318 319 320 321 /** 322 * Indicates whether or not the user has requested verbose output. 323 * 324 * @return Returns <code>true</code> if the user has requested 325 * verbose output. 326 */ 327 public abstract boolean isVerbose(); 328 329 330 331 /** 332 * Interactively prompts the user to press return to continue. This 333 * method should be called in situations where a user needs to be 334 * given a chance to read some documentation before continuing 335 * (continuing may cause the documentation to be scrolled out of 336 * view). 337 */ 338 public final void pressReturnToContinue() { 339 Message msg = INFO_MENU_PROMPT_RETURN_TO_CONTINUE.get(); 340 try { 341 readLineOfInput(msg); 342 } catch (CLIException e) { 343 // Ignore the exception - applications don't care. 344 } 345 } 346 347 348 349 /** 350 * Displays a blank line to the error stream. 351 */ 352 public final void println() { 353 err.println(); 354 } 355 356 357 358 /** 359 * Displays a message to the error stream. 360 * 361 * @param msg 362 * The message. 363 */ 364 public final void println(Message msg) { 365 err.println(wrapText(msg, MAX_LINE_WIDTH)); 366 } 367 368 369 /** 370 * Displays a message to the error stream. 371 * 372 * @param msg 373 * The message. 374 */ 375 public final void print(Message msg) { 376 err.print(wrapText(msg, MAX_LINE_WIDTH)); 377 } 378 379 /** 380 * Displays a blank line to the output stream if we are not in quiet mode. 381 */ 382 public final void printlnProgress() { 383 if (!isQuiet()) 384 { 385 out.println(); 386 } 387 } 388 389 390 /** 391 * Displays a message to the output stream if we are not in quiet mode. 392 * 393 * @param msg 394 * The message. 395 */ 396 public final void printProgress(Message msg) { 397 if (!isQuiet()) 398 { 399 out.print(msg); 400 } 401 } 402 403 404 /** 405 * Displays a message to the error stream indented by the specified 406 * number of columns. 407 * 408 * @param msg 409 * The message. 410 * @param indent 411 * The number of columns to indent. 412 */ 413 public final void println(Message msg, int indent) { 414 err.println(wrapText(msg, MAX_LINE_WIDTH, indent)); 415 } 416 417 418 419 /** 420 * Displays a message to the error stream if verbose mode is 421 * enabled. 422 * 423 * @param msg 424 * The verbose message. 425 */ 426 public final void printVerboseMessage(Message msg) { 427 if (isVerbose() || isInteractive()) { 428 err.println(wrapText(msg, MAX_LINE_WIDTH)); 429 } 430 } 431 432 433 434 /** 435 * Interactively retrieves a line of input from the console. 436 * 437 * @param prompt 438 * The prompt. 439 * @return Returns the line of input, or <code>null</code> if the 440 * end of input has been reached. 441 * @throws CLIException 442 * If the line of input could not be retrieved for some 443 * reason. 444 */ 445 public final String readLineOfInput(Message prompt) throws CLIException { 446 if (prompt != null) 447 { 448 err.print(wrapText(prompt + " ", MAX_LINE_WIDTH)); 449 } 450 try { 451 String s = in.readLine(); 452 if (s == null) { 453 throw CLIException 454 .adaptInputException(new EOFException("End of input")); 455 } else { 456 return s; 457 } 458 } catch (IOException e) { 459 throw CLIException.adaptInputException(e); 460 } 461 } 462 463 464 /** 465 * Commodity method that interactively prompts (on error output) the user to 466 * provide a string value. Any non-empty string will be allowed (the empty 467 * string will indicate that the default should be used, if there is one). 468 * 469 * @param prompt The prompt to present to the user. 470 * @param defaultValue The default value to assume if the user presses ENTER 471 * without typing anything, or <CODE>null</CODE> if 472 * there should not be a default and the user must 473 * explicitly provide a value. 474 * 475 * @throws CLIException 476 * If the line of input could not be retrieved for some 477 * reason. 478 * @return The string value read from the user. 479 */ 480 public String readInput(Message prompt, String defaultValue) 481 throws CLIException { 482 while (true) { 483 if (defaultValue != null) { 484 prompt = INFO_PROMPT_SINGLE_DEFAULT.get(prompt.toString(), 485 defaultValue); 486 } 487 String response = readLineOfInput(prompt); 488 489 if ("".equals(response)) { 490 if (defaultValue == null) { 491 print(INFO_ERROR_EMPTY_RESPONSE.get()); 492 } else { 493 return defaultValue; 494 } 495 } else { 496 return response; 497 } 498 } 499 } 500 501 /** 502 * Commodity method that interactively prompts (on error output) the user to 503 * provide a string value. Any non-empty string will be allowed (the empty 504 * string will indicate that the default should be used, if there is one). 505 * If an error occurs a message will be logged to the provided logger. 506 * 507 * @param prompt The prompt to present to the user. 508 * @param defaultValue The default value to assume if the user presses ENTER 509 * without typing anything, or <CODE>null</CODE> if 510 * there should not be a default and the user must 511 * explicitly provide a value. 512 * 513 * @param logger the Logger to be used to log the error message. 514 * @return The string value read from the user. 515 */ 516 public String readInput(Message prompt, String defaultValue, Logger logger) 517 { 518 String s = defaultValue; 519 try 520 { 521 s = readInput(prompt, defaultValue); 522 } 523 catch (CLIException ce) 524 { 525 logger.log(Level.WARNING, "Error reading input: "+ce, ce); 526 } 527 return s; 528 } 529 530 /** 531 * Interactively retrieves a password from the console. 532 * 533 * @param prompt 534 * The password prompt. 535 * @return Returns the password. 536 * @throws CLIException 537 * If the password could not be retrieved for some reason. 538 */ 539 public final String readPassword(Message prompt) throws CLIException { 540 err.print(wrapText(prompt + " ", MAX_LINE_WIDTH)); 541 char[] pwChars; 542 try { 543 pwChars = PasswordReader.readPassword(); 544 } catch (Exception e) { 545 throw CLIException.adaptInputException(e); 546 } 547 return new String(pwChars); 548 } 549 550 /** 551 * Commodity method that interactively retrieves a password from the 552 * console. If there is an error an error message is logged to the provided 553 * Logger and <CODE>null</CODE> is returned. 554 * 555 * @param prompt 556 * The password prompt. 557 * @param logger the Logger to be used to log the error message. 558 * @return Returns the password. 559 */ 560 protected final String readPassword(Message prompt, Logger logger) 561 { 562 String pwd = null; 563 try 564 { 565 pwd = readPassword(prompt); 566 } 567 catch (CLIException ce) 568 { 569 logger.log(Level.WARNING, "Error reading input: "+ce, ce); 570 } 571 return pwd; 572 } 573 574 /** 575 * Interactively retrieves a port value from the console. 576 * 577 * @param prompt 578 * The port prompt. 579 * @param defaultValue 580 * The port default value. 581 * @return Returns the port. 582 * @throws CLIException 583 * If the port could not be retrieved for some reason. 584 */ 585 public final int readPort(Message prompt, final int defaultValue) 586 throws CLIException 587 { 588 ValidationCallback<Integer> callback = new ValidationCallback<Integer>() 589 { 590 public Integer validate(ConsoleApplication app, String input) 591 throws CLIException 592 { 593 String ninput = input.trim(); 594 if (ninput.length() == 0) 595 { 596 return defaultValue; 597 } 598 else 599 { 600 try 601 { 602 int i = Integer.parseInt(ninput); 603 if (i < 1 || i > 65535) 604 { 605 throw new NumberFormatException(); 606 } 607 return i; 608 } 609 catch (NumberFormatException e) 610 { 611 // Try again... 612 app.println(); 613 app.println(ERR_LDAP_CONN_BAD_PORT_NUMBER.get(ninput)); 614 app.println(); 615 return null; 616 } 617 } 618 } 619 620 }; 621 622 if (defaultValue != -1) { 623 prompt = INFO_PROMPT_SINGLE_DEFAULT.get(prompt.toString(), 624 String.valueOf(defaultValue)); 625 } 626 627 return readValidatedInput(prompt, callback); 628 } 629 630 /** 631 * Interactively prompts for user input and continues until valid 632 * input is provided. 633 * 634 * @param <T> 635 * The type of decoded user input. 636 * @param prompt 637 * The interactive prompt which should be displayed on each 638 * input attempt. 639 * @param validator 640 * An input validator responsible for validating and 641 * decoding the user's response. 642 * @return Returns the decoded user's response. 643 * @throws CLIException 644 * If an unexpected error occurred which prevented 645 * validation. 646 */ 647 public final <T> T readValidatedInput(Message prompt, 648 ValidationCallback<T> validator) throws CLIException { 649 while (true) { 650 String response = readLineOfInput(prompt); 651 T value = validator.validate(this, response); 652 if (value != null) { 653 return value; 654 } 655 } 656 } 657 658 /** 659 * Interactively prompts for user input and continues until valid 660 * input is provided. 661 * 662 * @param <T> 663 * The type of decoded user input. 664 * @param prompt 665 * The interactive prompt which should be displayed on each 666 * input attempt. 667 * @param validator 668 * An input validator responsible for validating and 669 * decoding the user's response. 670 * @param maxTries 671 * The maximum number of tries that we can make. 672 * @return Returns the decoded user's response. 673 * @throws CLIException 674 * If an unexpected error occurred which prevented 675 * validation or if the maximum number of tries was reached. 676 */ 677 public final <T> T readValidatedInput(Message prompt, 678 ValidationCallback<T> validator, int maxTries) throws CLIException { 679 int nTries = 0; 680 while (nTries < maxTries) { 681 String response = readLineOfInput(prompt); 682 T value = validator.validate(this, response); 683 if (value != null) { 684 return value; 685 } 686 nTries++; 687 } 688 throw new CLIException(ERR_TRIES_LIMIT_REACHED.get(maxTries)); 689 } 690 691 /** 692 * Commodity method that interactively confirms whether a user wishes to 693 * perform an action. If the application is non-interactive, then the provided 694 * default is returned automatically. If there is an error an error message 695 * is logged to the provided Logger and the defaul value is returned. 696 * 697 * @param prompt 698 * The prompt describing the action. 699 * @param defaultValue 700 * The default value for the confirmation message. This 701 * will be returned if the application is non-interactive 702 * or if the user just presses return. 703 * @param logger the Logger to be used to log the error message. 704 * @return Returns <code>true</code> if the user wishes the action 705 * to be performed, or <code>false</code> if they refused. 706 * @throws CLIException if the user did not provide valid answer after 707 * a certain number of tries 708 * (ConsoleApplication.CONFIRMATION_MAX_TRIES) 709 */ 710 protected final boolean askConfirmation(Message prompt, boolean defaultValue, 711 Logger logger) throws CLIException 712 { 713 boolean v = defaultValue; 714 715 boolean done = false; 716 int nTries = 0; 717 718 while (!done && (nTries < CONFIRMATION_MAX_TRIES)) 719 { 720 nTries++; 721 try 722 { 723 v = confirmAction(prompt, defaultValue); 724 done = true; 725 } 726 catch (CLIException ce) 727 { 728 if (ce.getMessageObject().getDescriptor().equals( 729 ERR_CONFIRMATION_TRIES_LIMIT_REACHED) || 730 ce.getMessageObject().getDescriptor().equals( 731 ERR_TRIES_LIMIT_REACHED)) 732 { 733 throw ce; 734 } 735 logger.log(Level.WARNING, "Error reading input: "+ce, ce); 736 // Try again... 737 println(); 738 } 739 } 740 741 if (!done) 742 { 743 // This means we reached the maximum number of tries 744 throw new CLIException(ERR_CONFIRMATION_TRIES_LIMIT_REACHED.get( 745 CONFIRMATION_MAX_TRIES)); 746 } 747 return v; 748 } 749 750 /** 751 * Returns an InitialLdapContext using the provided parameters. We try 752 * to guarantee that the connection is able to read the configuration. 753 * @param host the host name. 754 * @param port the port to connect. 755 * @param useSSL whether to use SSL or not. 756 * @param useStartTLS whether to use StartTLS or not. 757 * @param bindDn the bind dn to be used. 758 * @param pwd the password. 759 * @param trustManager the trust manager. 760 * @return an InitialLdapContext connected. 761 * @throws NamingException if there was an error establishing the connection. 762 */ 763 protected InitialLdapContext createAdministrativeContext(String host, 764 int port, boolean useSSL, boolean useStartTLS, String bindDn, String pwd, 765 ApplicationTrustManager trustManager) 766 throws NamingException 767 { 768 InitialLdapContext ctx; 769 String ldapUrl = ConnectionUtils.getLDAPUrl(host, port, useSSL); 770 if (useSSL) 771 { 772 ctx = Utils.createLdapsContext(ldapUrl, bindDn, pwd, 773 Utils.getDefaultLDAPTimeout(), null, trustManager); 774 } 775 else if (useStartTLS) 776 { 777 ctx = Utils.createStartTLSContext(ldapUrl, bindDn, pwd, 778 Utils.getDefaultLDAPTimeout(), null, trustManager, 779 null); 780 } 781 else 782 { 783 ctx = Utils.createLdapContext(ldapUrl, bindDn, pwd, 784 Utils.getDefaultLDAPTimeout(), null); 785 } 786 if (!ConnectionUtils.connectedAsAdministrativeUser(ctx)) 787 { 788 throw new NoPermissionException( 789 ERR_NOT_ADMINISTRATIVE_USER.get().toString()); 790 } 791 return ctx; 792 } 793 794 /** 795 * Creates an Initial LDAP Context interacting with the user if the 796 * application is interactive. 797 * @param ci the LDAPConnectionConsoleInteraction object that is assumed 798 * to have been already run. 799 * @return the initial LDAP context or <CODE>null</CODE> if the user did 800 * not accept to trust the certificates. 801 * @throws ClientException if there was an error establishing the connection. 802 */ 803 protected InitialLdapContext createInitialLdapContextInteracting( 804 LDAPConnectionConsoleInteraction ci) throws ClientException 805 { 806 // Interact with the user though the console to get 807 // LDAP connection information 808 String hostName = ConnectionUtils.getHostNameForLdapUrl(ci.getHostName()); 809 Integer portNumber = ci.getPortNumber(); 810 String bindDN = ci.getBindDN(); 811 String bindPassword = ci.getBindPassword(); 812 TrustManager trustManager = ci.getTrustManager(); 813 KeyManager keyManager = ci.getKeyManager(); 814 815 InitialLdapContext ctx; 816 817 if (ci.useSSL()) 818 { 819 String ldapsUrl = "ldaps://" + hostName + ":" + portNumber; 820 while (true) 821 { 822 try 823 { 824 ctx = ConnectionUtils.createLdapsContext(ldapsUrl, bindDN, 825 bindPassword, ConnectionUtils.getDefaultLDAPTimeout(), null, 826 trustManager, keyManager); 827 ctx.reconnect(null); 828 break; 829 } 830 catch (NamingException e) 831 { 832 if ( isInteractive() && ci.isTrustStoreInMemory()) 833 { 834 if ((e.getRootCause() != null) 835 && (e.getRootCause().getCause() 836 instanceof OpendsCertificateException)) 837 { 838 OpendsCertificateException oce = 839 (OpendsCertificateException) e.getRootCause().getCause(); 840 String authType = null; 841 if (trustManager instanceof ApplicationTrustManager) 842 { 843 ApplicationTrustManager appTrustManager = 844 (ApplicationTrustManager)trustManager; 845 authType = appTrustManager.getLastRefusedAuthType(); 846 } 847 if (ci.checkServerCertificate(oce.getChain(), authType, 848 hostName)) 849 { 850 // If the certificate is trusted, update the trust manager. 851 trustManager = ci.getTrustManager(); 852 853 // Try to connect again. 854 continue ; 855 } 856 else 857 { 858 // Assume user cancelled. 859 return null; 860 } 861 } 862 else 863 { 864 Message message = ERR_DSCFG_ERROR_LDAP_FAILED_TO_CONNECT.get( 865 hostName, String.valueOf(portNumber)); 866 throw new ClientException( 867 LDAPResultCode.CLIENT_SIDE_CONNECT_ERROR, message); 868 } 869 } 870 Message message = ERR_DSCFG_ERROR_LDAP_FAILED_TO_CONNECT.get( 871 hostName, String.valueOf(portNumber)); 872 throw new ClientException( 873 LDAPResultCode.CLIENT_SIDE_CONNECT_ERROR, message); 874 } 875 } 876 } 877 else if (ci.useStartTLS()) 878 { 879 String ldapUrl = "ldap://" + hostName + ":" + portNumber; 880 while (true) 881 { 882 try 883 { 884 ctx = ConnectionUtils.createStartTLSContext(ldapUrl, bindDN, 885 bindPassword, ConnectionUtils.getDefaultLDAPTimeout(), null, 886 trustManager, keyManager, null); 887 ctx.reconnect(null); 888 break; 889 } 890 catch (NamingException e) 891 { 892 if ( isInteractive() && ci.isTrustStoreInMemory()) 893 { 894 if ((e.getRootCause() != null) 895 && (e.getRootCause().getCause() 896 instanceof OpendsCertificateException)) 897 { 898 String authType = null; 899 if (trustManager instanceof ApplicationTrustManager) 900 { 901 ApplicationTrustManager appTrustManager = 902 (ApplicationTrustManager)trustManager; 903 authType = appTrustManager.getLastRefusedAuthType(); 904 } 905 OpendsCertificateException oce = 906 (OpendsCertificateException) e.getRootCause().getCause(); 907 if (ci.checkServerCertificate(oce.getChain(), authType, 908 hostName)) 909 { 910 // If the certificate is trusted, update the trust manager. 911 trustManager = ci.getTrustManager(); 912 913 // Try to connect again. 914 continue ; 915 } 916 else 917 { 918 // Assume user cancelled. 919 return null; 920 } 921 } 922 else 923 { 924 Message message = ERR_DSCFG_ERROR_LDAP_FAILED_TO_CONNECT.get( 925 hostName, String.valueOf(portNumber)); 926 throw new ClientException( 927 LDAPResultCode.CLIENT_SIDE_CONNECT_ERROR, message); 928 } 929 } 930 Message message = ERR_DSCFG_ERROR_LDAP_FAILED_TO_CONNECT.get( 931 hostName, String.valueOf(portNumber)); 932 throw new ClientException( 933 LDAPResultCode.CLIENT_SIDE_CONNECT_ERROR, message); 934 } 935 } 936 } 937 else 938 { 939 String ldapUrl = "ldap://" + hostName + ":" + portNumber; 940 while (true) 941 { 942 try 943 { 944 ctx = ConnectionUtils.createLdapContext(ldapUrl, bindDN, 945 bindPassword, ConnectionUtils.getDefaultLDAPTimeout(), null); 946 ctx.reconnect(null); 947 break; 948 } 949 catch (NamingException e) 950 { 951 if ( isInteractive() && ci.isTrustStoreInMemory()) 952 { 953 if ((e.getRootCause() != null) 954 && (e.getRootCause().getCause() 955 instanceof OpendsCertificateException)) 956 { 957 String authType = null; 958 if (trustManager instanceof ApplicationTrustManager) 959 { 960 ApplicationTrustManager appTrustManager = 961 (ApplicationTrustManager)trustManager; 962 authType = appTrustManager.getLastRefusedAuthType(); 963 } 964 OpendsCertificateException oce = 965 (OpendsCertificateException) e.getRootCause().getCause(); 966 if (ci.checkServerCertificate(oce.getChain(), authType, 967 hostName)) 968 { 969 // If the certificate is trusted, update the trust manager. 970 trustManager = ci.getTrustManager(); 971 972 // Try to connect again. 973 continue ; 974 } 975 else 976 { 977 // Assume user cancelled. 978 return null; 979 } 980 } 981 else 982 { 983 Message message = ERR_DSCFG_ERROR_LDAP_FAILED_TO_CONNECT.get( 984 hostName, String.valueOf(portNumber)); 985 throw new ClientException( 986 LDAPResultCode.CLIENT_SIDE_CONNECT_ERROR, message); 987 } 988 } 989 Message message = ERR_DSCFG_ERROR_LDAP_FAILED_TO_CONNECT.get( 990 hostName, String.valueOf(portNumber)); 991 throw new ClientException( 992 LDAPResultCode.CLIENT_SIDE_CONNECT_ERROR, message); 993 } 994 } 995 } 996 return ctx; 997 } 998 }