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 }