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.File;
028import java.io.InputStreamReader;
029import java.util.Arrays;
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{
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  /**
060   * The default value to use for the environment variable.  This should only
061   * be set when running unit tests.
062   */
063  private static volatile String DEFAULT_ENVIRONMENT_VARIABLE_VALUE = null;
064
065
066
067  /**
068   * The name of an environment variable that can be used to specify the path
069   * to a file that contains the password to be read.  This is also
070   * predominantly intended for use when running unit tests, and may be
071   * necessary for tests running in a separate process that can't use the
072   * {@code TEST_READER}.
073   */
074  private static final String PASSWORD_FILE_ENVIRONMENT_VARIABLE =
075       "LDAP_SDK_PASSWORD_READER_PASSWORD_FILE";
076
077
078
079  /**
080   * Creates a new instance of this password reader thread.
081   */
082  private PasswordReader()
083  {
084    // No implementation is required.
085  }
086
087
088
089  /**
090   * Reads a password from the console as a character array.
091   *
092   * @return  The characters that comprise the password that was read.
093   *
094   * @throws  LDAPException  If a problem is encountered while trying to read
095   *                         the password.
096   */
097  public static char[] readPasswordChars()
098         throws LDAPException
099  {
100    // If an input stream is available, then read the password from it.
101    final BufferedReader testReader = TEST_READER;
102    if (testReader != null)
103    {
104      try
105      {
106        return testReader.readLine().toCharArray();
107      }
108      catch (final Exception e)
109      {
110        Debug.debugException(e);
111        throw new LDAPException(ResultCode.LOCAL_ERROR,
112             ERR_PW_READER_FAILURE.get(StaticUtils.getExceptionMessage(e)),
113             e);
114      }
115    }
116
117
118    // If a password input file environment variable has been set, then read
119    // the password from that file.
120    final String environmentVariableValue = StaticUtils.getEnvironmentVariable(
121         PASSWORD_FILE_ENVIRONMENT_VARIABLE,
122         DEFAULT_ENVIRONMENT_VARIABLE_VALUE);
123    if (environmentVariableValue != null)
124    {
125      try
126      {
127        final File f = new File(environmentVariableValue);
128        final PasswordFileReader r = new PasswordFileReader();
129        return r.readPassword(f);
130      }
131      catch (final Exception e)
132      {
133        Debug.debugException(e);
134        throw new LDAPException(ResultCode.LOCAL_ERROR,
135             ERR_PW_READER_FAILURE.get(StaticUtils.getExceptionMessage(e)),
136             e);
137      }
138    }
139
140
141    if (System.console() == null)
142    {
143      throw new LDAPException(ResultCode.LOCAL_ERROR,
144           ERR_PW_READER_CANNOT_READ_PW_WITH_NO_CONSOLE.get());
145    }
146
147    return System.console().readPassword();
148  }
149
150
151
152  /**
153   * Reads a password from the console as a byte array.
154   *
155   * @return  The characters that comprise the password that was read.
156   *
157   * @throws  LDAPException  If a problem is encountered while trying to read
158   *                         the password.
159   */
160  public static byte[] readPassword()
161         throws LDAPException
162  {
163    // Get the characters that make up the password.
164    final char[] pwChars = readPasswordChars();
165
166    // Convert the password to bytes.
167    final ByteStringBuffer buffer = new ByteStringBuffer();
168    buffer.append(pwChars);
169    Arrays.fill(pwChars, '\u0000');
170    final byte[] pwBytes = buffer.toByteArray();
171    buffer.clear(true);
172    return pwBytes;
173  }
174
175
176
177  /**
178   * This is a legacy method that now does nothing.  It was required by a
179   * former version of this class when older versions of Java were still
180   * supported, and is retained only for the purpose of API backward
181   * compatibility.
182   *
183   * @deprecated  This method is no longer used.
184   */
185  @Deprecated()
186  public void run()
187  {
188    // No implementation is required.
189  }
190
191
192
193  /**
194   * Specifies the lines that should be used as input when reading the password.
195   * This should only be set when running unit tests, and the
196   * {@link #setTestReader(BufferedReader)} method should be called with a value
197   * of {@code null} before the end of the test to ensure that the password
198   * reader is reverted back to its normal behavior.
199   *
200   * @param  lines  The lines of input that should be provided to the password
201   *                reader instead of actually obtaining them interactively.
202   *                It must not be {@code null} but may be empty.
203   */
204  @InternalUseOnly()
205  public static void setTestReaderLines(final String... lines)
206  {
207    final ByteStringBuffer buffer = new ByteStringBuffer();
208    for (final String line : lines)
209    {
210      buffer.append(line);
211      buffer.append(StaticUtils.EOL_BYTES);
212    }
213
214    TEST_READER = new BufferedReader(new InputStreamReader(
215         new ByteArrayInputStream(buffer.toByteArray())));
216  }
217
218
219
220  /**
221   * Specifies the input stream from which to read the password.  This should
222   * only be set when running unit tests, and this method should be called
223   * again with a value of {@code null} before the end of the test to ensure
224   * that the password reader is reverted back to its normal behavior.
225   *
226   * @param  reader  The input stream from which to read the password.  It may
227   *                 be {@code null} to obtain the password from the normal
228   *                 means.
229   */
230  @InternalUseOnly()
231  public static void setTestReader(final BufferedReader reader)
232  {
233    TEST_READER = reader;
234  }
235
236
237
238  /**
239   * Sets the default value that should be used for the environment variable if
240   * it is not set.  This is only intended for use in testing purposes.
241   *
242   * @param  value  The default value that should be used for the environment
243   *                variable if it is not set.  It may be {@code null} if
244   */
245  @InternalUseOnly()
246  static void setDefaultEnvironmentVariableValue(final String value)
247  {
248    DEFAULT_ENVIRONMENT_VARIABLE_VALUE = value;
249  }
250}