001/* 002 * Copyright 2013-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2013-2018 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.util.Arrays; 027import java.util.concurrent.atomic.AtomicBoolean; 028 029import com.unboundid.ldap.sdk.LDAPException; 030import com.unboundid.ldap.sdk.ResultCode; 031 032import static com.unboundid.util.UtilityMessages.*; 033 034 035 036/** 037 * This class provides a mechanism for reading a password from the command line 038 * in a way that attempts to prevent it from being displayed. If it is 039 * available (i.e., Java SE 6 or later), the 040 * {@code java.io.Console.readPassword} method will be used to accomplish this. 041 * For Java SE 5 clients, a more primitive approach must be taken, which 042 * requires flooding standard output with backspace characters using a 043 * high-priority thread. This has only a limited effectiveness, but it is the 044 * best option available for older Java versions. 045 */ 046@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 047public final class PasswordReader 048 extends Thread 049{ 050 /** 051 * The input stream from which to read the password. This should only be set 052 * when running unit tests. 053 */ 054 private static volatile BufferedReader TEST_READER = null; 055 056 057 058 // Indicates whether a request has been made for the backspace thread to 059 // stop running. 060 private final AtomicBoolean stopRequested; 061 062 // An object that will be used to wait for the reader thread to be started. 063 private final Object startMutex; 064 065 066 067 /** 068 * Creates a new instance of this password reader thread. 069 */ 070 private PasswordReader() 071 { 072 startMutex = new Object(); 073 stopRequested = new AtomicBoolean(false); 074 075 setName("Password Reader Thread"); 076 setDaemon(true); 077 setPriority(Thread.MAX_PRIORITY); 078 } 079 080 081 082 /** 083 * Reads a password from the console as a character array. 084 * 085 * @return The characters that comprise the password that was read. 086 * 087 * @throws LDAPException If a problem is encountered while trying to read 088 * the password. 089 */ 090 public static char[] readPasswordChars() 091 throws LDAPException 092 { 093 // If an input stream is available, then read the password from it. 094 final BufferedReader testReader = TEST_READER; 095 if (testReader != null) 096 { 097 try 098 { 099 return testReader.readLine().toCharArray(); 100 } 101 catch (final Exception e) 102 { 103 Debug.debugException(e); 104 throw new LDAPException(ResultCode.LOCAL_ERROR, 105 ERR_PW_READER_FAILURE.get(StaticUtils.getExceptionMessage(e)), 106 e); 107 } 108 } 109 110 return System.console().readPassword(); 111 } 112 113 114 115 /** 116 * Reads a password from the console as a byte array. 117 * 118 * @return The characters that comprise the password that was read. 119 * 120 * @throws LDAPException If a problem is encountered while trying to read 121 * the password. 122 */ 123 public static byte[] readPassword() 124 throws LDAPException 125 { 126 // Get the characters that make up the password. 127 final char[] pwChars = readPasswordChars(); 128 129 // Convert the password to bytes. 130 final ByteStringBuffer buffer = new ByteStringBuffer(); 131 buffer.append(pwChars); 132 Arrays.fill(pwChars, '\u0000'); 133 final byte[] pwBytes = buffer.toByteArray(); 134 buffer.clear(true); 135 return pwBytes; 136 } 137 138 139 140 /** 141 * Repeatedly sends backspace and space characters to standard output in an 142 * attempt to try to hide what the user enters. 143 */ 144 @Override() 145 public void run() 146 { 147 synchronized (startMutex) 148 { 149 startMutex.notifyAll(); 150 } 151 152 while (! stopRequested.get()) 153 { 154 System.out.print("\u0008 "); 155 yield(); 156 } 157 } 158 159 160 161 /** 162 * Specifies the input stream from which to read the password. This should 163 * only be set when running unit tests. 164 * 165 * @param reader The input stream from which to read the password. It may 166 * be {@code null} to obtain the password from the normal 167 * means. 168 */ 169 @InternalUseOnly() 170 public static void setTestReader(final BufferedReader reader) 171 { 172 TEST_READER = reader; 173 } 174}