/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2006-2008 Sun Microsystems, Inc. */ package org.opends.server.util; import java.lang.reflect.Method; import java.util.Arrays; import org.opends.server.api.DirectoryThread; /** * This class provides a means of interactively reading a password from the * command-line without echoing it to the console. If it is running on a Java 6 * or higher VM, then it will use the System.console() method. If it is running * on Java 5, then it will use an ugly hack in which one thread will be used to * repeatedly send backspace characters to the console while another reads the * password. Reflection is used to determine whether the Java 6 method is * available and to invoke it if it is so that the code will still compile * cleanly on Java 5 systems. */ @org.opends.server.types.PublicAPI( stability=org.opends.server.types.StabilityLevel.UNCOMMITTED, mayInstantiate=false, mayExtend=false, mayInvoke=true) public final class PasswordReader extends DirectoryThread { // Indicates whether the backspace thread should keep looping, sending // backspace characters to the console. private volatile boolean keepLooping; /** * Creates a new instance of this password reader. A new instance should only * be created from within this class. */ private PasswordReader() { super("Password Reader Thread"); // No implementation is required. However, this constructor is private to // help prevent it being used for external purposes. } /** * Operates in a loop, sending backspace characters to the console to attempt * to prevent exposing what the user entered. It sets the priority to the * maximum allowed value to reduce the chance of one or more characters being * displayed temporarily before they can be erased. */ @org.opends.server.types.PublicAPI( stability=org.opends.server.types.StabilityLevel.PRIVATE, mayInstantiate=false, mayExtend=false, mayInvoke=false) public void run() { Thread currentThread = Thread.currentThread(); int initialPriority = currentThread.getPriority(); try { try { currentThread.setPriority(Thread.MAX_PRIORITY); } catch (Exception e) {} keepLooping = true; while (keepLooping) { System.out.print("\u0008 "); try { Thread.sleep(1); } catch (InterruptedException ie) { currentThread.interrupt(); return; } } } finally { try { currentThread.setPriority(initialPriority); } catch (Exception e) {} } } /** * Indicates that the backspace thread should stop looping as the complete * password has been entered. */ private void stopLooping() { keepLooping = false; } /** * Reads a password from the console without echoing it to the client. * * @return The password as an array of characters. */ public static char[] readPassword() { // First, use reflection to determine whether the System.console() method // is available. try { Method consoleMethod = System.class.getDeclaredMethod("console", new Class[0]); if (consoleMethod != null) { char[] password = readPasswordUsingConsole(consoleMethod); if (password != null) { return password; } } } catch (Exception e) { // This must mean that we're running on a JVM that doesn't have the // System.console() method, or that the call to Console.readPassword() // isn't working. Fall back to using backspaces. return readPasswordUsingBackspaces(); } // If we've gotten here, then the System.console() method must not exist. // Fall back on using backspaces. return readPasswordUsingBackspaces(); } /** * Uses reflection to invoke the <CODE>java.io.Console.readPassword()</CODE> * method in order to retrieve the password from the user. * * @param consoleMethod The <CODE>Method</CODE> object that may be used to * obtain a <CODE>Console</CODE> instance. * * @return The password as an array of characters. * * @throws Exception If any problem occurs while attempting to read the * password. */ private static char[] readPasswordUsingConsole(Method consoleMethod) throws Exception { Object consoleObject = consoleMethod.invoke(null); Method passwordMethod = consoleObject.getClass().getDeclaredMethod("readPassword", new Class[0]); return (char[]) passwordMethod.invoke(consoleObject); } /** * Attempts to read a password from the console by repeatedly sending * backspace characters to mask whatever the user may have entered. This will * be used if the <CODE>java.io.Console</CODE> class is not available. * * @return The password read from the console. */ private static char[] readPasswordUsingBackspaces() { char[] pwChars; char[] pwBuffer = new char[100]; int pos = 0; PasswordReader backspaceThread = new PasswordReader(); backspaceThread.start(); try { while (true) { int charRead = System.in.read(); if ((charRead == -1) || (charRead == '\n')) { // This is the end of the value. pwChars = new char[pos]; if (0 < pos) { System.arraycopy(pwBuffer, 0, pwChars, 0, pos); Arrays.fill(pwBuffer, '\u0000'); } return pwChars; } else if (charRead == '\r') { int char2 = System.in.read(); if (char2 == '\n') { // This is the end of the value. if (pos == 0) { return null; } else { pwChars = new char[pos]; System.arraycopy(pwBuffer, 0, pwChars, 0, pos); Arrays.fill(pwBuffer, '\u0000'); return pwChars; } } else { // Append the characters to the buffer and continue. pwBuffer[pos++] = (char) charRead; if (pos >= pwBuffer.length) { char[] newBuffer = new char[pwBuffer.length+100]; System.arraycopy(pwBuffer, 0, newBuffer, 0, pwBuffer.length); Arrays.fill(pwBuffer, '\u0000'); pwBuffer = newBuffer; } pwBuffer[pos++] = (char) char2; if (pos >= pwBuffer.length) { char[] newBuffer = new char[pwBuffer.length+100]; System.arraycopy(pwBuffer, 0, newBuffer, 0, pwBuffer.length); Arrays.fill(pwBuffer, '\u0000'); pwBuffer = newBuffer; } } } else { // Append the value to the buffer and continue. pwBuffer[pos++] = (char) charRead; if (pos >= pwBuffer.length) { char[] newBuffer = new char[pwBuffer.length+100]; System.arraycopy(pwBuffer, 0, newBuffer, 0, pwBuffer.length); Arrays.fill(pwBuffer, '\u0000'); pwBuffer = newBuffer; } } } } catch (Exception e) { // We must have encountered an error while attempting to read. The only // thing we can do is to dump a stack trace and return null. e.printStackTrace(); return null; } finally { backspaceThread.stopLooping(); } } }