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    }