001    /*
002     * Copyright 2013-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2013-2015 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     */
021    package com.unboundid.util;
022    
023    
024    
025    import java.lang.reflect.Method;
026    import java.util.Arrays;
027    import java.util.concurrent.atomic.AtomicBoolean;
028    
029    import com.unboundid.ldap.sdk.LDAPException;
030    import com.unboundid.ldap.sdk.ResultCode;
031    
032    import 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)
047    public 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    }