001/* 002 * Copyright 2013-2017 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2013-2017 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. 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 byte[] readPassword() 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 StaticUtils.getBytes(testReader.readLine()); 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 111 // If we're not in a test, then use the Java SE 6 password reader API to 112 // read the password. 113 final char[] pwChars = System.console().readPassword(); 114 115 final ByteStringBuffer buffer = new ByteStringBuffer(); 116 buffer.append(pwChars); 117 Arrays.fill(pwChars, '\u0000'); 118 final byte[] pwBytes = buffer.toByteArray(); 119 buffer.clear(true); 120 return pwBytes; 121 } 122 123 124 125 /** 126 * Repeatedly sends backspace and space characters to standard output in an 127 * attempt to try to hide what the user enters. 128 */ 129 @Override() 130 public void run() 131 { 132 synchronized (startMutex) 133 { 134 startMutex.notifyAll(); 135 } 136 137 while (! stopRequested.get()) 138 { 139 System.out.print("\u0008 "); 140 yield(); 141 } 142 } 143 144 145 146 /** 147 * Specifies the input stream from which to read the password. This should 148 * only be set when running unit tests. 149 * 150 * @param reader The input stream from which to read the password. It may 151 * be {@code null} to obtain the password from the normal 152 * means. 153 */ 154 @InternalUseOnly() 155 public static void setTestReader(final BufferedReader reader) 156 { 157 TEST_READER = reader; 158 } 159}