001 /*
002 * Copyright 2013-2016 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2013-2016 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 */
021 package com.unboundid.util;
022
023
024
025 import java.io.BufferedReader;
026 import java.lang.reflect.Method;
027 import java.util.Arrays;
028 import java.util.concurrent.atomic.AtomicBoolean;
029
030 import com.unboundid.ldap.sdk.LDAPException;
031 import com.unboundid.ldap.sdk.ResultCode;
032
033 import 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)
048 public 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 throw new LDAPException(ResultCode.LOCAL_ERROR,
191 ERR_PW_READER_FAILURE.get(StaticUtils.getExceptionMessage(e)),
192 e);
193 }
194 }
195
196
197
198 /**
199 * Repeatedly sends backspace and space characters to standard output in an
200 * attempt to try to hide what the user enters.
201 */
202 @Override()
203 public void run()
204 {
205 synchronized (startMutex)
206 {
207 startMutex.notifyAll();
208 }
209
210 while (! stopRequested.get())
211 {
212 System.out.print("\u0008 ");
213 yield();
214 }
215 }
216
217
218
219 /**
220 * Specifies the input stream from which to read the password. This should
221 * only be set when running unit tests.
222 *
223 * @param reader The input stream from which to read the password. It may
224 * be {@code null} to obtain the password from the normal
225 * means.
226 */
227 static void setTestReader(final BufferedReader reader)
228 {
229 TEST_READER = reader;
230 }
231 }