001/* 002 * Copyright 2013-2017 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2013-2017 UnboundID Corp. 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.util; 022 023 024 025import java.io.BufferedReader; 026import java.lang.reflect.Method; 027import java.util.Arrays; 028import java.util.concurrent.atomic.AtomicBoolean; 029 030import com.unboundid.ldap.sdk.LDAPException; 031import com.unboundid.ldap.sdk.ResultCode; 032 033import static com.unboundid.util.UtilityMessages.*; 034 035 036 037/** 038 * This class provides a mechanism for reading a password from the command line 039 * in a way that attempts to prevent it from being displayed. If it is 040 * available (i.e., Java SE 6 or later), the 041 * {@code java.io.Console.readPassword} method will be used to accomplish this. 042 * For Java SE 5 clients, a more primitive approach must be taken, which 043 * requires flooding standard output with backspace characters using a 044 * high-priority thread. This has only a limited effectiveness, but it is the 045 * best option available for older Java versions. 046 */ 047@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 048public final class PasswordReader 049 extends Thread 050{ 051 /** 052 * The input stream from which to read the password. This should only be set 053 * when running unit tests. 054 */ 055 private static volatile BufferedReader TEST_READER = null; 056 057 058 059 // Indicates whether a request has been made for the backspace thread to 060 // stop running. 061 private final AtomicBoolean stopRequested; 062 063 // An object that will be used to wait for the reader thread to be started. 064 private final Object startMutex; 065 066 067 068 /** 069 * Creates a new instance of this password reader thread. 070 */ 071 private PasswordReader() 072 { 073 startMutex = new Object(); 074 stopRequested = new AtomicBoolean(false); 075 076 setName("Password Reader Thread"); 077 setDaemon(true); 078 setPriority(Thread.MAX_PRIORITY); 079 } 080 081 082 083 /** 084 * Reads a password from the console. 085 * 086 * @return The characters that comprise the password that was read. 087 * 088 * @throws LDAPException If a problem is encountered while trying to read 089 * the password. 090 */ 091 public static byte[] readPassword() 092 throws LDAPException 093 { 094 // If an input stream is available, then read the password from it. 095 final BufferedReader testReader = TEST_READER; 096 if (testReader != null) 097 { 098 try 099 { 100 return StaticUtils.getBytes(testReader.readLine()); 101 } 102 catch (final Exception e) 103 { 104 Debug.debugException(e); 105 throw new LDAPException(ResultCode.LOCAL_ERROR, 106 ERR_PW_READER_FAILURE.get(StaticUtils.getExceptionMessage(e)), 107 e); 108 } 109 } 110 111 112 // Try to use the Java SE 6 approach first. 113 try 114 { 115 final Method consoleMethod = System.class.getMethod("console"); 116 final Object consoleObject = consoleMethod.invoke(null); 117 118 final Method readPasswordMethod = 119 consoleObject.getClass().getMethod("readPassword"); 120 final char[] pwChars = (char[]) readPasswordMethod.invoke(consoleObject); 121 122 final ByteStringBuffer buffer = new ByteStringBuffer(); 123 buffer.append(pwChars); 124 Arrays.fill(pwChars, '\u0000'); 125 final byte[] pwBytes = buffer.toByteArray(); 126 buffer.clear(true); 127 return pwBytes; 128 } 129 catch (final Exception e) 130 { 131 Debug.debugException(e); 132 } 133 134 // Fall back to the an approach that should work with Java SE 5. 135 try 136 { 137 final PasswordReader r = new PasswordReader(); 138 try 139 { 140 synchronized (r.startMutex) 141 { 142 r.start(); 143 r.startMutex.wait(); 144 } 145 146 // NOTE: 0x0A is '\n' and 0x0D is '\r'. 147 final ByteStringBuffer buffer = new ByteStringBuffer(); 148 while (true) 149 { 150 final int byteRead = System.in.read(); 151 if ((byteRead < 0) || (byteRead == 0x0A)) 152 { 153 // This is the end of the value, as indicated by a UNIX line 154 // terminator sequence. 155 break; 156 } 157 else if (byteRead == 0x0D) 158 { 159 final int nextCharacter = System.in.read(); 160 if ((nextCharacter < 0) || (byteRead == 0x0A)) 161 { 162 // This is the end of the value as indicated by a Windows line 163 // terminator sequence. 164 break; 165 } 166 else 167 { 168 buffer.append((byte) byteRead); 169 buffer.append((byte) nextCharacter); 170 } 171 } 172 else 173 { 174 buffer.append((byte) byteRead); 175 } 176 } 177 178 final byte[] pwBytes = buffer.toByteArray(); 179 buffer.clear(true); 180 return pwBytes; 181 } 182 finally 183 { 184 r.stopRequested.set(true); 185 } 186 } 187 catch (final Exception e) 188 { 189 Debug.debugException(e); 190 191 if (e instanceof InterruptedException) 192 { 193 Thread.currentThread().interrupt(); 194 } 195 196 throw new LDAPException(ResultCode.LOCAL_ERROR, 197 ERR_PW_READER_FAILURE.get(StaticUtils.getExceptionMessage(e)), 198 e); 199 } 200 } 201 202 203 204 /** 205 * Repeatedly sends backspace and space characters to standard output in an 206 * attempt to try to hide what the user enters. 207 */ 208 @Override() 209 public void run() 210 { 211 synchronized (startMutex) 212 { 213 startMutex.notifyAll(); 214 } 215 216 while (! stopRequested.get()) 217 { 218 System.out.print("\u0008 "); 219 yield(); 220 } 221 } 222 223 224 225 /** 226 * Specifies the input stream from which to read the password. This should 227 * only be set when running unit tests. 228 * 229 * @param reader The input stream from which to read the password. It may 230 * be {@code null} to obtain the password from the normal 231 * means. 232 */ 233 static void setTestReader(final BufferedReader reader) 234 { 235 TEST_READER = reader; 236 } 237}