001    /*
002     * CDDL HEADER START
003     *
004     * The contents of this file are subject to the terms of the
005     * Common Development and Distribution License, Version 1.0 only
006     * (the "License").  You may not use this file except in compliance
007     * with the License.
008     *
009     * You can obtain a copy of the license at
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.util;
028    
029    
030    
031    import java.lang.reflect.Method;
032    import java.util.Arrays;
033    
034    import org.opends.server.api.DirectoryThread;
035    
036    
037    
038    /**
039     * This class provides a means of interactively reading a password from the
040     * command-line without echoing it to the console.  If it is running on a Java 6
041     * or higher VM, then it will use the System.console() method.  If it is running
042     * on Java 5, then it will use an ugly hack in which one thread will be used to
043     * repeatedly send backspace characters to the console while another reads the
044     * password.  Reflection is used to determine whether the Java 6 method is
045     * available and to invoke it if it is so that the code will still compile
046     * cleanly on Java 5 systems.
047     */
048    @org.opends.server.types.PublicAPI(
049         stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
050         mayInstantiate=false,
051         mayExtend=false,
052         mayInvoke=true)
053    public final class PasswordReader
054           extends DirectoryThread
055    {
056      // Indicates whether the backspace thread should keep looping, sending
057      // backspace characters to the console.
058      private volatile boolean keepLooping;
059    
060    
061    
062      /**
063       * Creates a new instance of this password reader.  A new instance should only
064       * be created from within this class.
065       */
066      private PasswordReader()
067      {
068        super("Password Reader Thread");
069    
070        // No implementation is required.  However, this constructor is private to
071        // help prevent it being used for external purposes.
072      }
073    
074    
075    
076      /**
077       * Operates in a loop, sending backspace characters to the console to attempt
078       * to prevent exposing what the user entered.  It sets the priority to the
079       * maximum allowed value to reduce the chance of one or more characters being
080       * displayed temporarily before they can be erased.
081       */
082      @org.opends.server.types.PublicAPI(
083           stability=org.opends.server.types.StabilityLevel.PRIVATE,
084           mayInstantiate=false,
085           mayExtend=false,
086           mayInvoke=false)
087      public void run()
088      {
089        Thread currentThread   = Thread.currentThread();
090        int    initialPriority = currentThread.getPriority();
091    
092        try
093        {
094          try
095          {
096            currentThread.setPriority(Thread.MAX_PRIORITY);
097          } catch (Exception e) {}
098    
099          keepLooping = true;
100          while (keepLooping)
101          {
102            System.out.print("\u0008 ");
103    
104            try
105            {
106              currentThread.sleep(1);
107            }
108            catch (InterruptedException ie)
109            {
110              currentThread.interrupt();
111              return;
112            }
113          }
114        }
115        finally
116        {
117          try
118          {
119            currentThread.setPriority(initialPriority);
120          } catch (Exception e) {}
121        }
122      }
123    
124    
125    
126      /**
127       * Indicates that the backspace thread should stop looping as the complete
128       * password has been entered.
129       */
130      private void stopLooping()
131      {
132        keepLooping = false;
133      }
134    
135    
136    
137      /**
138       * Reads a password from the console without echoing it to the client.
139       *
140       * @return  The password as an array of characters.
141       */
142      public static char[] readPassword()
143      {
144        // First, use reflection to determine whether the System.console() method
145        // is available.
146        try
147        {
148          Method consoleMethod = System.class.getDeclaredMethod("console",
149                                                                new Class[0]);
150          if (consoleMethod != null)
151          {
152            char[] password = readPasswordUsingConsole(consoleMethod);
153            if (password != null)
154            {
155              return password;
156            }
157          }
158        }
159        catch (Exception e)
160        {
161          // This must mean that we're running on a JVM that doesn't have the
162          // System.console() method, or that the call to Console.readPassword()
163          // isn't working.  Fall back to using backspaces.
164          return readPasswordUsingBackspaces();
165        }
166    
167    
168        // If we've gotten here, then the System.console() method must not exist.
169        // Fall back on using backspaces.
170        return readPasswordUsingBackspaces();
171      }
172    
173    
174    
175      /**
176       * Uses reflection to invoke the <CODE>java.io.Console.readPassword()</CODE>
177       * method in order to retrieve the password from the user.
178       *
179       * @param  consoleMethod  The <CODE>Method</CODE> object that may be used to
180       *                        obtain a <CODE>Console</CODE> instance.
181       *
182       * @return  The password as an array of characters.
183       *
184       * @throws  Exception  If any problem occurs while attempting to read the
185       *                     password.
186       */
187      private static char[] readPasswordUsingConsole(Method consoleMethod)
188              throws Exception
189      {
190        Object consoleObject  = consoleMethod.invoke(null);
191        Method passwordMethod =
192             consoleObject.getClass().getDeclaredMethod("readPassword",
193                                                        new Class[0]);
194        return (char[]) passwordMethod.invoke(consoleObject);
195      }
196    
197    
198    
199      /**
200       * Attempts to read a password from the console by repeatedly sending
201       * backspace characters to mask whatever the user may have entered.  This will
202       * be used if the <CODE>java.io.Console</CODE> class is not available.
203       *
204       * @return  The password read from the console.
205       */
206      private static char[] readPasswordUsingBackspaces()
207      {
208        char[] pwChars;
209        char[] pwBuffer = new char[100];
210        int    pos      = 0;
211    
212        PasswordReader backspaceThread = new PasswordReader();
213        backspaceThread.start();
214    
215        try
216        {
217          while (true)
218          {
219            int charRead = System.in.read();
220            if ((charRead == -1) || (charRead == '\n'))
221            {
222              // This is the end of the value.
223              pwChars = new char[pos];
224              if (0 < pos)
225              {
226                System.arraycopy(pwBuffer, 0, pwChars, 0, pos);
227                Arrays.fill(pwBuffer, '\u0000');
228              }
229              return pwChars;
230            }
231            else if (charRead == '\r')
232            {
233              int char2 = System.in.read();
234              if (char2 == '\n')
235              {
236                // This is the end of the value.
237                if (pos == 0)
238                {
239                  return null;
240                }
241                else
242                {
243                  pwChars = new char[pos];
244                  System.arraycopy(pwBuffer, 0, pwChars, 0, pos);
245                  Arrays.fill(pwBuffer, '\u0000');
246                  return pwChars;
247                }
248              }
249              else
250              {
251                // Append the characters to the buffer and continue.
252                pwBuffer[pos++] = (char) charRead;
253                if (pos >= pwBuffer.length)
254                {
255                  char[] newBuffer = new char[pwBuffer.length+100];
256                  System.arraycopy(pwBuffer, 0, newBuffer, 0, pwBuffer.length);
257                  Arrays.fill(pwBuffer, '\u0000');
258                  pwBuffer = newBuffer;
259                }
260    
261                pwBuffer[pos++] = (char) char2;
262                if (pos >= pwBuffer.length)
263                {
264                  char[] newBuffer = new char[pwBuffer.length+100];
265                  System.arraycopy(pwBuffer, 0, newBuffer, 0, pwBuffer.length);
266                  Arrays.fill(pwBuffer, '\u0000');
267                  pwBuffer = newBuffer;
268                }
269              }
270            }
271            else
272            {
273              // Append the value to the buffer and continue.
274              pwBuffer[pos++] = (char) charRead;
275    
276              if (pos >= pwBuffer.length)
277              {
278                char[] newBuffer = new char[pwBuffer.length+100];
279                System.arraycopy(pwBuffer, 0, newBuffer, 0, pwBuffer.length);
280                Arrays.fill(pwBuffer, '\u0000');
281                pwBuffer = newBuffer;
282              }
283            }
284          }
285        }
286        catch (Exception e)
287        {
288          // We must have encountered an error while attempting to read.  The only
289          // thing we can do is to dump a stack trace and return null.
290          e.printStackTrace();
291          return null;
292        }
293        finally
294        {
295          backspaceThread.stopLooping();
296        }
297      }
298    }
299