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}