001/*
002 * Copyright 2013-2020 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2013-2020 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2013-2020 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.util;
037
038
039
040import java.io.BufferedReader;
041import java.io.ByteArrayInputStream;
042import java.io.File;
043import java.io.InputStreamReader;
044import java.util.Arrays;
045
046import com.unboundid.ldap.sdk.LDAPException;
047import com.unboundid.ldap.sdk.ResultCode;
048
049import static com.unboundid.util.UtilityMessages.*;
050
051
052
053/**
054 * This class provides a mechanism for reading a password from the command line
055 * in a way that attempts to prevent it from being displayed.  If it is
056 * available (i.e., Java SE 6 or later), the
057 * {@code java.io.Console.readPassword} method will be used to accomplish this.
058 * For Java SE 5 clients, a more primitive approach must be taken, which
059 * requires flooding standard output with backspace characters using a
060 * high-priority thread.  This has only a limited effectiveness, but it is the
061 * best option available for older Java versions.
062 */
063@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
064public final class PasswordReader
065{
066  /**
067   * The input stream from which to read the password.  This should only be set
068   * when running unit tests.
069   */
070  private static volatile BufferedReader TEST_READER = null;
071
072
073
074  /**
075   * The default value to use for the environment variable.  This should only
076   * be set when running unit tests.
077   */
078  private static volatile String DEFAULT_ENVIRONMENT_VARIABLE_VALUE = null;
079
080
081
082  /**
083   * The name of an environment variable that can be used to specify the path
084   * to a file that contains the password to be read.  This is also
085   * predominantly intended for use when running unit tests, and may be
086   * necessary for tests running in a separate process that can't use the
087   * {@code TEST_READER}.
088   */
089  private static final String PASSWORD_FILE_ENVIRONMENT_VARIABLE =
090       "LDAP_SDK_PASSWORD_READER_PASSWORD_FILE";
091
092
093
094  /**
095   * Creates a new instance of this password reader thread.
096   */
097  private PasswordReader()
098  {
099    // No implementation is required.
100  }
101
102
103
104  /**
105   * Reads a password from the console as a character array.
106   *
107   * @return  The characters that comprise the password that was read.
108   *
109   * @throws  LDAPException  If a problem is encountered while trying to read
110   *                         the password.
111   */
112  public static char[] readPasswordChars()
113         throws LDAPException
114  {
115    // If an input stream is available, then read the password from it.
116    final BufferedReader testReader = TEST_READER;
117    if (testReader != null)
118    {
119      try
120      {
121        return testReader.readLine().toCharArray();
122      }
123      catch (final Exception e)
124      {
125        Debug.debugException(e);
126        throw new LDAPException(ResultCode.LOCAL_ERROR,
127             ERR_PW_READER_FAILURE.get(StaticUtils.getExceptionMessage(e)),
128             e);
129      }
130    }
131
132
133    // If a password input file environment variable has been set, then read
134    // the password from that file.
135    final String environmentVariableValue = StaticUtils.getEnvironmentVariable(
136         PASSWORD_FILE_ENVIRONMENT_VARIABLE,
137         DEFAULT_ENVIRONMENT_VARIABLE_VALUE);
138    if (environmentVariableValue != null)
139    {
140      try
141      {
142        final File f = new File(environmentVariableValue);
143        final PasswordFileReader r = new PasswordFileReader();
144        return r.readPassword(f);
145      }
146      catch (final Exception e)
147      {
148        Debug.debugException(e);
149        throw new LDAPException(ResultCode.LOCAL_ERROR,
150             ERR_PW_READER_FAILURE.get(StaticUtils.getExceptionMessage(e)),
151             e);
152      }
153    }
154
155
156    if (System.console() == null)
157    {
158      throw new LDAPException(ResultCode.LOCAL_ERROR,
159           ERR_PW_READER_CANNOT_READ_PW_WITH_NO_CONSOLE.get());
160    }
161
162    return System.console().readPassword();
163  }
164
165
166
167  /**
168   * Reads a password from the console as a byte array.
169   *
170   * @return  The characters that comprise the password that was read.
171   *
172   * @throws  LDAPException  If a problem is encountered while trying to read
173   *                         the password.
174   */
175  public static byte[] readPassword()
176         throws LDAPException
177  {
178    // Get the characters that make up the password.
179    final char[] pwChars = readPasswordChars();
180
181    // Convert the password to bytes.
182    final ByteStringBuffer buffer = new ByteStringBuffer();
183    buffer.append(pwChars);
184    Arrays.fill(pwChars, '\u0000');
185    final byte[] pwBytes = buffer.toByteArray();
186    buffer.clear(true);
187    return pwBytes;
188  }
189
190
191
192  /**
193   * This is a legacy method that now does nothing.  It was required by a
194   * former version of this class when older versions of Java were still
195   * supported, and is retained only for the purpose of API backward
196   * compatibility.
197   *
198   * @deprecated  This method is no longer used.
199   */
200  @Deprecated()
201  public void run()
202  {
203    // No implementation is required.
204  }
205
206
207
208  /**
209   * Specifies the lines that should be used as input when reading the password.
210   * This should only be set when running unit tests, and the
211   * {@link #setTestReader(BufferedReader)} method should be called with a value
212   * of {@code null} before the end of the test to ensure that the password
213   * reader is reverted back to its normal behavior.
214   *
215   * @param  lines  The lines of input that should be provided to the password
216   *                reader instead of actually obtaining them interactively.
217   *                It must not be {@code null} but may be empty.
218   */
219  @InternalUseOnly()
220  public static void setTestReaderLines(final String... lines)
221  {
222    final ByteStringBuffer buffer = new ByteStringBuffer();
223    for (final String line : lines)
224    {
225      buffer.append(line);
226      buffer.append(StaticUtils.EOL_BYTES);
227    }
228
229    TEST_READER = new BufferedReader(new InputStreamReader(
230         new ByteArrayInputStream(buffer.toByteArray())));
231  }
232
233
234
235  /**
236   * Specifies the input stream from which to read the password.  This should
237   * only be set when running unit tests, and this method should be called
238   * again with a value of {@code null} before the end of the test to ensure
239   * that the password reader is reverted back to its normal behavior.
240   *
241   * @param  reader  The input stream from which to read the password.  It may
242   *                 be {@code null} to obtain the password from the normal
243   *                 means.
244   */
245  @InternalUseOnly()
246  public static void setTestReader(final BufferedReader reader)
247  {
248    TEST_READER = reader;
249  }
250
251
252
253  /**
254   * Sets the default value that should be used for the environment variable if
255   * it is not set.  This is only intended for use in testing purposes.
256   *
257   * @param  value  The default value that should be used for the environment
258   *                variable if it is not set.  It may be {@code null} if
259   */
260  @InternalUseOnly()
261  static void setDefaultEnvironmentVariableValue(final String value)
262  {
263    DEFAULT_ENVIRONMENT_VARIABLE_VALUE = value;
264  }
265}