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}