001/*
002 * Copyright 2013-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2013-2019 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    if (System.console() == null)
113    {
114      throw new LDAPException(ResultCode.LOCAL_ERROR,
115           ERR_PW_READER_CANNOT_READ_PW_WITH_NO_CONSOLE.get());
116    }
117
118    return System.console().readPassword();
119  }
120
121
122
123  /**
124   * Reads a password from the console as a byte array.
125   *
126   * @return  The characters that comprise the password that was read.
127   *
128   * @throws  LDAPException  If a problem is encountered while trying to read
129   *                         the password.
130   */
131  public static byte[] readPassword()
132         throws LDAPException
133  {
134    // Get the characters that make up the password.
135    final char[] pwChars = readPasswordChars();
136
137    // Convert the password to bytes.
138    final ByteStringBuffer buffer = new ByteStringBuffer();
139    buffer.append(pwChars);
140    Arrays.fill(pwChars, '\u0000');
141    final byte[] pwBytes = buffer.toByteArray();
142    buffer.clear(true);
143    return pwBytes;
144  }
145
146
147
148  /**
149   * Repeatedly sends backspace and space characters to standard output in an
150   * attempt to try to hide what the user enters.
151   */
152  @Override()
153  public void run()
154  {
155    synchronized (startMutex)
156    {
157      startMutex.notifyAll();
158    }
159
160    while (! stopRequested.get())
161    {
162      System.out.print("\u0008 ");
163      yield();
164    }
165  }
166
167
168
169  /**
170   * Specifies the lines that should be used as input when reading the password.
171   * This should only be set when running unit tests, and the
172   * {@link #setTestReader(BufferedReader)} method should be called with a value
173   * of {@code null} before the end of the test to ensure that the password
174   * reader is reverted back to its normal behavior.
175   *
176   * @param  lines  The lines of input that should be provided to the password
177   *                reader instead of actually obtaining them interactively.
178   *                It must not be {@code null} but may be empty.
179   */
180  @InternalUseOnly()
181  public static void setTestReaderLines(final String... lines)
182  {
183    final ByteStringBuffer buffer = new ByteStringBuffer();
184    for (final String line : lines)
185    {
186      buffer.append(line);
187      buffer.append(StaticUtils.EOL_BYTES);
188    }
189
190    TEST_READER = new BufferedReader(new InputStreamReader(
191         new ByteArrayInputStream(buffer.toByteArray())));
192  }
193
194
195
196  /**
197   * Specifies the input stream from which to read the password.  This should
198   * only be set when running unit tests, and this method should be called
199   * again with a value of {@code null} before the end of the test to ensure
200   * that the password reader is reverted back to its normal behavior.
201   *
202   * @param  reader  The input stream from which to read the password.  It may
203   *                 be {@code null} to obtain the password from the normal
204   *                 means.
205   */
206  @InternalUseOnly()
207  public static void setTestReader(final BufferedReader reader)
208  {
209    TEST_READER = reader;
210  }
211}