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}