001 /*
002 * Copyright 2013-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2013-2014 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.lang.reflect.Method;
026 import java.util.Arrays;
027 import java.util.concurrent.atomic.AtomicBoolean;
028
029 import com.unboundid.ldap.sdk.LDAPException;
030 import com.unboundid.ldap.sdk.ResultCode;
031
032 import static com.unboundid.util.UtilityMessages.*;
033
034
035
036 /**
037 * This class provides a mechanism for reading a password from the command line
038 * in a way that attempts to prevent it from being displayed. If it is
039 * available (i.e., Java SE 6 or later), the
040 * {@code java.io.Console.readPassword} method will be used to accomplish this.
041 * For Java SE 5 clients, a more primitive approach must be taken, which
042 * requires flooding standard output with backspace characters using a
043 * high-priority thread. This has only a limited effectiveness, but it is the
044 * best option available for older Java versions.
045 */
046 @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
047 public final class PasswordReader
048 extends Thread
049 {
050 // Indicates whether a request has been made for the backspace thread to
051 // stop running.
052 private final AtomicBoolean stopRequested;
053
054 // An object that will be used to wait for the reader thread to be started.
055 private final Object startMutex;
056
057
058
059 /**
060 * Creates a new instance of this password reader thread.
061 */
062 private PasswordReader()
063 {
064 startMutex = new Object();
065 stopRequested = new AtomicBoolean(false);
066
067 setName("Password Reader Thread");
068 setDaemon(true);
069 setPriority(Thread.MAX_PRIORITY);
070 }
071
072
073
074 /**
075 * Reads a password from the console.
076 *
077 * @return The characters that comprise the password that was read.
078 *
079 * @throws LDAPException If a problem is encountered while trying to read
080 * the password.
081 */
082 public static byte[] readPassword()
083 throws LDAPException
084 {
085 // Try to use the Java SE 6 approach first.
086 try
087 {
088 final Method consoleMethod = System.class.getMethod("console");
089 final Object consoleObject = consoleMethod.invoke(null);
090
091 final Method readPasswordMethod =
092 consoleObject.getClass().getMethod("readPassword");
093 final char[] pwChars = (char[]) readPasswordMethod.invoke(consoleObject);
094
095 final ByteStringBuffer buffer = new ByteStringBuffer();
096 buffer.append(pwChars);
097 Arrays.fill(pwChars, '\u0000');
098 final byte[] pwBytes = buffer.toByteArray();
099 buffer.clear(true);
100 return pwBytes;
101 }
102 catch (final Exception e)
103 {
104 Debug.debugException(e);
105 }
106
107 // Fall back to the an approach that should work with Java SE 5.
108 try
109 {
110 final PasswordReader r = new PasswordReader();
111 try
112 {
113 synchronized (r.startMutex)
114 {
115 r.start();
116 r.startMutex.wait();
117 }
118
119 // NOTE: 0x0A is '\n' and 0x0D is '\r'.
120 final ByteStringBuffer buffer = new ByteStringBuffer();
121 while (true)
122 {
123 final int byteRead = System.in.read();
124 if ((byteRead < 0) || (byteRead == 0x0A))
125 {
126 // This is the end of the value, as indicated by a UNIX line
127 // terminator sequence.
128 break;
129 }
130 else if (byteRead == 0x0D)
131 {
132 final int nextCharacter = System.in.read();
133 if ((nextCharacter < 0) || (byteRead == 0x0A))
134 {
135 // This is the end of the value as indicated by a Windows line
136 // terminator sequence.
137 break;
138 }
139 else
140 {
141 buffer.append((byte) byteRead);
142 buffer.append((byte) nextCharacter);
143 }
144 }
145 else
146 {
147 buffer.append((byte) byteRead);
148 }
149 }
150
151 final byte[] pwBytes = buffer.toByteArray();
152 buffer.clear(true);
153 return pwBytes;
154 }
155 finally
156 {
157 r.stopRequested.set(true);
158 }
159 }
160 catch (final Exception e)
161 {
162 Debug.debugException(e);
163 throw new LDAPException(ResultCode.LOCAL_ERROR,
164 ERR_PW_READER_FAILURE.get(StaticUtils.getExceptionMessage(e)),
165 e);
166 }
167 }
168
169
170
171
172 /**
173 * Repeatedly sends backspace and space characters to standard output in an
174 * attempt to try to hide what the user enters.
175 */
176 @Override()
177 public void run()
178 {
179 synchronized (startMutex)
180 {
181 startMutex.notifyAll();
182 }
183
184 while (! stopRequested.get())
185 {
186 System.out.print("\u0008 ");
187 yield();
188 }
189 }
190 }