/* * 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 legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * 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 legal-notices/CDDLv1_0.txt. * 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. * Portions copyright 2013 ForgeRock AS */ package org.opends.server.util; 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) @Override 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() { try { char[] password = System.console().readPassword(); 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(); } /** * 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 java.io.Console 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(); } } }