001/* 002 * Copyright 2013-2014 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2013-2014 UnboundID Corp. 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.lang.reflect.Method; 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 // Indicates whether a request has been made for the backspace thread to 051 // stop running. 052 private final AtomicBoolean stopRequested; 053 054 // An object that will be used to wait for the reader thread to be started. 055 private final Object startMutex; 056 057 058 059 /** 060 * Creates a new instance of this password reader thread. 061 */ 062 private PasswordReader() 063 { 064 startMutex = new Object(); 065 stopRequested = new AtomicBoolean(false); 066 067 setName("Password Reader Thread"); 068 setDaemon(true); 069 setPriority(Thread.MAX_PRIORITY); 070 } 071 072 073 074 /** 075 * Reads a password from the console. 076 * 077 * @return The characters that comprise the password that was read. 078 * 079 * @throws LDAPException If a problem is encountered while trying to read 080 * the password. 081 */ 082 public static byte[] readPassword() 083 throws LDAPException 084 { 085 // Try to use the Java SE 6 approach first. 086 try 087 { 088 final Method consoleMethod = System.class.getMethod("console"); 089 final Object consoleObject = consoleMethod.invoke(null); 090 091 final Method readPasswordMethod = 092 consoleObject.getClass().getMethod("readPassword"); 093 final char[] pwChars = (char[]) readPasswordMethod.invoke(consoleObject); 094 095 final ByteStringBuffer buffer = new ByteStringBuffer(); 096 buffer.append(pwChars); 097 Arrays.fill(pwChars, '\u0000'); 098 final byte[] pwBytes = buffer.toByteArray(); 099 buffer.clear(true); 100 return pwBytes; 101 } 102 catch (final Exception e) 103 { 104 Debug.debugException(e); 105 } 106 107 // Fall back to the an approach that should work with Java SE 5. 108 try 109 { 110 final PasswordReader r = new PasswordReader(); 111 try 112 { 113 synchronized (r.startMutex) 114 { 115 r.start(); 116 r.startMutex.wait(); 117 } 118 119 // NOTE: 0x0A is '\n' and 0x0D is '\r'. 120 final ByteStringBuffer buffer = new ByteStringBuffer(); 121 while (true) 122 { 123 final int byteRead = System.in.read(); 124 if ((byteRead < 0) || (byteRead == 0x0A)) 125 { 126 // This is the end of the value, as indicated by a UNIX line 127 // terminator sequence. 128 break; 129 } 130 else if (byteRead == 0x0D) 131 { 132 final int nextCharacter = System.in.read(); 133 if ((nextCharacter < 0) || (byteRead == 0x0A)) 134 { 135 // This is the end of the value as indicated by a Windows line 136 // terminator sequence. 137 break; 138 } 139 else 140 { 141 buffer.append((byte) byteRead); 142 buffer.append((byte) nextCharacter); 143 } 144 } 145 else 146 { 147 buffer.append((byte) byteRead); 148 } 149 } 150 151 final byte[] pwBytes = buffer.toByteArray(); 152 buffer.clear(true); 153 return pwBytes; 154 } 155 finally 156 { 157 r.stopRequested.set(true); 158 } 159 } 160 catch (final Exception e) 161 { 162 Debug.debugException(e); 163 throw new LDAPException(ResultCode.LOCAL_ERROR, 164 ERR_PW_READER_FAILURE.get(StaticUtils.getExceptionMessage(e)), 165 e); 166 } 167 } 168 169 170 171 172 /** 173 * Repeatedly sends backspace and space characters to standard output in an 174 * attempt to try to hide what the user enters. 175 */ 176 @Override() 177 public void run() 178 { 179 synchronized (startMutex) 180 { 181 startMutex.notifyAll(); 182 } 183 184 while (! stopRequested.get()) 185 { 186 System.out.print("\u0008 "); 187 yield(); 188 } 189 } 190}