1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at 10 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 11 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 12 * See the License for the specific language governing permissions 13 * and limitations under the License. 14 * 15 * When distributing Covered Code, include this CDDL HEADER in each 16 * file and include the License file at 17 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 18 * add the following below this CDDL HEADER, with the fields enclosed 19 * by brackets "[]" replaced with your own identifying information: 20 * Portions Copyright [yyyy] [name of copyright owner] 21 * 22 * CDDL HEADER END 23 * 24 * 25 * Copyright 2006-2008 Sun Microsystems, Inc. 26 */ 27 package org.opends.server.util; 28 29 30 31 import java.lang.reflect.Method; 32 import java.util.Arrays; 33 34 import org.opends.server.api.DirectoryThread; 35 36 37 38 /** 39 * This class provides a means of interactively reading a password from the 40 * command-line without echoing it to the console. If it is running on a Java 6 41 * or higher VM, then it will use the System.console() method. If it is running 42 * on Java 5, then it will use an ugly hack in which one thread will be used to 43 * repeatedly send backspace characters to the console while another reads the 44 * password. Reflection is used to determine whether the Java 6 method is 45 * available and to invoke it if it is so that the code will still compile 46 * cleanly on Java 5 systems. 47 */ 48 @org.opends.server.types.PublicAPI( 49 stability=org.opends.server.types.StabilityLevel.UNCOMMITTED, 50 mayInstantiate=false, 51 mayExtend=false, 52 mayInvoke=true) 53 public final class PasswordReader 54 extends DirectoryThread 55 { 56 // Indicates whether the backspace thread should keep looping, sending 57 // backspace characters to the console. 58 private volatile boolean keepLooping; 59 60 61 62 /** 63 * Creates a new instance of this password reader. A new instance should only 64 * be created from within this class. 65 */ 66 private PasswordReader() 67 { 68 super("Password Reader Thread"); 69 70 // No implementation is required. However, this constructor is private to 71 // help prevent it being used for external purposes. 72 } 73 74 75 76 /** 77 * Operates in a loop, sending backspace characters to the console to attempt 78 * to prevent exposing what the user entered. It sets the priority to the 79 * maximum allowed value to reduce the chance of one or more characters being 80 * displayed temporarily before they can be erased. 81 */ 82 @org.opends.server.types.PublicAPI( 83 stability=org.opends.server.types.StabilityLevel.PRIVATE, 84 mayInstantiate=false, 85 mayExtend=false, 86 mayInvoke=false) 87 public void run() 88 { 89 Thread currentThread = Thread.currentThread(); 90 int initialPriority = currentThread.getPriority(); 91 92 try 93 { 94 try 95 { 96 currentThread.setPriority(Thread.MAX_PRIORITY); 97 } catch (Exception e) {} 98 99 keepLooping = true; 100 while (keepLooping) 101 { 102 System.out.print("\u0008 "); 103 104 try 105 { 106 currentThread.sleep(1); 107 } 108 catch (InterruptedException ie) 109 { 110 currentThread.interrupt(); 111 return; 112 } 113 } 114 } 115 finally 116 { 117 try 118 { 119 currentThread.setPriority(initialPriority); 120 } catch (Exception e) {} 121 } 122 } 123 124 125 126 /** 127 * Indicates that the backspace thread should stop looping as the complete 128 * password has been entered. 129 */ 130 private void stopLooping() 131 { 132 keepLooping = false; 133 } 134 135 136 137 /** 138 * Reads a password from the console without echoing it to the client. 139 * 140 * @return The password as an array of characters. 141 */ 142 public static char[] readPassword() 143 { 144 // First, use reflection to determine whether the System.console() method 145 // is available. 146 try 147 { 148 Method consoleMethod = System.class.getDeclaredMethod("console", 149 new Class[0]); 150 if (consoleMethod != null) 151 { 152 char[] password = readPasswordUsingConsole(consoleMethod); 153 if (password != null) 154 { 155 return password; 156 } 157 } 158 } 159 catch (Exception e) 160 { 161 // This must mean that we're running on a JVM that doesn't have the 162 // System.console() method, or that the call to Console.readPassword() 163 // isn't working. Fall back to using backspaces. 164 return readPasswordUsingBackspaces(); 165 } 166 167 168 // If we've gotten here, then the System.console() method must not exist. 169 // Fall back on using backspaces. 170 return readPasswordUsingBackspaces(); 171 } 172 173 174 175 /** 176 * Uses reflection to invoke the <CODE>java.io.Console.readPassword()</CODE> 177 * method in order to retrieve the password from the user. 178 * 179 * @param consoleMethod The <CODE>Method</CODE> object that may be used to 180 * obtain a <CODE>Console</CODE> instance. 181 * 182 * @return The password as an array of characters. 183 * 184 * @throws Exception If any problem occurs while attempting to read the 185 * password. 186 */ 187 private static char[] readPasswordUsingConsole(Method consoleMethod) 188 throws Exception 189 { 190 Object consoleObject = consoleMethod.invoke(null); 191 Method passwordMethod = 192 consoleObject.getClass().getDeclaredMethod("readPassword", 193 new Class[0]); 194 return (char[]) passwordMethod.invoke(consoleObject); 195 } 196 197 198 199 /** 200 * Attempts to read a password from the console by repeatedly sending 201 * backspace characters to mask whatever the user may have entered. This will 202 * be used if the <CODE>java.io.Console</CODE> class is not available. 203 * 204 * @return The password read from the console. 205 */ 206 private static char[] readPasswordUsingBackspaces() 207 { 208 char[] pwChars; 209 char[] pwBuffer = new char[100]; 210 int pos = 0; 211 212 PasswordReader backspaceThread = new PasswordReader(); 213 backspaceThread.start(); 214 215 try 216 { 217 while (true) 218 { 219 int charRead = System.in.read(); 220 if ((charRead == -1) || (charRead == '\n')) 221 { 222 // This is the end of the value. 223 pwChars = new char[pos]; 224 if (0 < pos) 225 { 226 System.arraycopy(pwBuffer, 0, pwChars, 0, pos); 227 Arrays.fill(pwBuffer, '\u0000'); 228 } 229 return pwChars; 230 } 231 else if (charRead == '\r') 232 { 233 int char2 = System.in.read(); 234 if (char2 == '\n') 235 { 236 // This is the end of the value. 237 if (pos == 0) 238 { 239 return null; 240 } 241 else 242 { 243 pwChars = new char[pos]; 244 System.arraycopy(pwBuffer, 0, pwChars, 0, pos); 245 Arrays.fill(pwBuffer, '\u0000'); 246 return pwChars; 247 } 248 } 249 else 250 { 251 // Append the characters to the buffer and continue. 252 pwBuffer[pos++] = (char) charRead; 253 if (pos >= pwBuffer.length) 254 { 255 char[] newBuffer = new char[pwBuffer.length+100]; 256 System.arraycopy(pwBuffer, 0, newBuffer, 0, pwBuffer.length); 257 Arrays.fill(pwBuffer, '\u0000'); 258 pwBuffer = newBuffer; 259 } 260 261 pwBuffer[pos++] = (char) char2; 262 if (pos >= pwBuffer.length) 263 { 264 char[] newBuffer = new char[pwBuffer.length+100]; 265 System.arraycopy(pwBuffer, 0, newBuffer, 0, pwBuffer.length); 266 Arrays.fill(pwBuffer, '\u0000'); 267 pwBuffer = newBuffer; 268 } 269 } 270 } 271 else 272 { 273 // Append the value to the buffer and continue. 274 pwBuffer[pos++] = (char) charRead; 275 276 if (pos >= pwBuffer.length) 277 { 278 char[] newBuffer = new char[pwBuffer.length+100]; 279 System.arraycopy(pwBuffer, 0, newBuffer, 0, pwBuffer.length); 280 Arrays.fill(pwBuffer, '\u0000'); 281 pwBuffer = newBuffer; 282 } 283 } 284 } 285 } 286 catch (Exception e) 287 { 288 // We must have encountered an error while attempting to read. The only 289 // thing we can do is to dump a stack trace and return null. 290 e.printStackTrace(); 291 return null; 292 } 293 finally 294 { 295 backspaceThread.stopLooping(); 296 } 297 } 298 } 299 300