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 */
021package com.unboundid.util;
022
023
024
025import java.lang.reflect.Method;
026import java.util.Arrays;
027import java.util.concurrent.atomic.AtomicBoolean;
028
029import com.unboundid.ldap.sdk.LDAPException;
030import com.unboundid.ldap.sdk.ResultCode;
031
032import 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)
047public 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}