001/*
002 * Copyright 2013-2018 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2013-2018 Ping Identity Corporation
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.io.ByteArrayInputStream;
027import java.io.InputStreamReader;
028import java.util.Arrays;
029import java.util.concurrent.atomic.AtomicBoolean;
030
031import com.unboundid.ldap.sdk.LDAPException;
032import com.unboundid.ldap.sdk.ResultCode;
033
034import static com.unboundid.util.UtilityMessages.*;
035
036
037
038/**
039 * This class provides a mechanism for reading a password from the command line
040 * in a way that attempts to prevent it from being displayed.  If it is
041 * available (i.e., Java SE 6 or later), the
042 * {@code java.io.Console.readPassword} method will be used to accomplish this.
043 * For Java SE 5 clients, a more primitive approach must be taken, which
044 * requires flooding standard output with backspace characters using a
045 * high-priority thread.  This has only a limited effectiveness, but it is the
046 * best option available for older Java versions.
047 */
048@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
049public final class PasswordReader
050       extends Thread
051{
052  /**
053   * The input stream from which to read the password.  This should only be set
054   * when running unit tests.
055   */
056  private static volatile BufferedReader TEST_READER = null;
057
058
059
060  // Indicates whether a request has been made for the backspace thread to
061  // stop running.
062  private final AtomicBoolean stopRequested;
063
064  // An object that will be used to wait for the reader thread to be started.
065  private final Object startMutex;
066
067
068
069  /**
070   * Creates a new instance of this password reader thread.
071   */
072  private PasswordReader()
073  {
074    startMutex = new Object();
075    stopRequested = new AtomicBoolean(false);
076
077    setName("Password Reader Thread");
078    setDaemon(true);
079    setPriority(Thread.MAX_PRIORITY);
080  }
081
082
083
084  /**
085   * Reads a password from the console as a character array.
086   *
087   * @return  The characters that comprise the password that was read.
088   *
089   * @throws  LDAPException  If a problem is encountered while trying to read
090   *                         the password.
091   */
092  public static char[] readPasswordChars()
093         throws LDAPException
094  {
095    // If an input stream is available, then read the password from it.
096    final BufferedReader testReader = TEST_READER;
097    if (testReader != null)
098    {
099      try
100      {
101        return testReader.readLine().toCharArray();
102      }
103      catch (final Exception e)
104      {
105        Debug.debugException(e);
106        throw new LDAPException(ResultCode.LOCAL_ERROR,
107             ERR_PW_READER_FAILURE.get(StaticUtils.getExceptionMessage(e)),
108             e);
109      }
110    }
111
112    return System.console().readPassword();
113  }
114
115
116
117  /**
118   * Reads a password from the console as a byte array.
119   *
120   * @return  The characters that comprise the password that was read.
121   *
122   * @throws  LDAPException  If a problem is encountered while trying to read
123   *                         the password.
124   */
125  public static byte[] readPassword()
126         throws LDAPException
127  {
128    // Get the characters that make up the password.
129    final char[] pwChars = readPasswordChars();
130
131    // Convert the password to bytes.
132    final ByteStringBuffer buffer = new ByteStringBuffer();
133    buffer.append(pwChars);
134    Arrays.fill(pwChars, '\u0000');
135    final byte[] pwBytes = buffer.toByteArray();
136    buffer.clear(true);
137    return pwBytes;
138  }
139
140
141
142  /**
143   * Repeatedly sends backspace and space characters to standard output in an
144   * attempt to try to hide what the user enters.
145   */
146  @Override()
147  public void run()
148  {
149    synchronized (startMutex)
150    {
151      startMutex.notifyAll();
152    }
153
154    while (! stopRequested.get())
155    {
156      System.out.print("\u0008 ");
157      yield();
158    }
159  }
160
161
162
163  /**
164   * Specifies the lines that should be used as input when reading the password.
165   * This should only be set when running unit tests, and the
166   * {@link #setTestReader(BufferedReader)} method should be called with a value
167   * of {@code null} before the end of the test to ensure that the password
168   * reader is reverted back to its normal behavior.
169   *
170   * @param  lines  The lines of input that should be provided to the password
171   *                reader instead of actually obtaining them interactively.
172   *                It must not be {@code null} but may be empty.
173   */
174  @InternalUseOnly()
175  public static void setTestReaderLines(final String... lines)
176  {
177    final ByteStringBuffer buffer = new ByteStringBuffer();
178    for (final String line : lines)
179    {
180      buffer.append(line);
181      buffer.append(StaticUtils.EOL_BYTES);
182    }
183
184    TEST_READER = new BufferedReader(new InputStreamReader(
185         new ByteArrayInputStream(buffer.toByteArray())));
186  }
187
188
189
190  /**
191   * Specifies the input stream from which to read the password.  This should
192   * only be set when running unit tests, and this method should be called
193   * again with a value of {@code null} before the end of the test to ensure
194   * that the password reader is reverted back to its normal behavior.
195   *
196   * @param  reader  The input stream from which to read the password.  It may
197   *                 be {@code null} to obtain the password from the normal
198   *                 means.
199   */
200  @InternalUseOnly()
201  public static void setTestReader(final BufferedReader reader)
202  {
203    TEST_READER = reader;
204  }
205}