Home | History | Annotate | Download | only in util
      1 /*
      2  * CDDL HEADER START
      3  *
      4  * The contents of this file are subject to the terms of the
      5  * Common Development and Distribution License, Version 1.0 only
      6  * (the "License").  You may not use this file except in compliance
      7  * with the License.
      8  *
      9  * You can obtain a copy of the license at
     10  * trunk/opends/resource/legal-notices/OpenDS.LICENSE
     11  * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
     12  * See the License for the specific language governing permissions
     13  * and limitations under the License.
     14  *
     15  * When distributing Covered Code, include this CDDL HEADER in each
     16  * file and include the License file at
     17  * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
     18  * add the following below this CDDL HEADER, with the fields enclosed
     19  * by brackets "[]" replaced with your own identifying information:
     20  *      Portions Copyright [yyyy] [name of copyright owner]
     21  *
     22  * CDDL HEADER END
     23  *
     24  *
     25  *      Copyright 2006-2008 Sun Microsystems, Inc.
     26  */
     27 package org.opends.server.util;
     28 
     29 
     30 
     31 import java.lang.reflect.Method;
     32 import java.util.Arrays;
     33 
     34 import org.opends.server.api.DirectoryThread;
     35 
     36 
     37 
     38 /**
     39  * This class provides a means of interactively reading a password from the
     40  * command-line without echoing it to the console.  If it is running on a Java 6
     41  * or higher VM, then it will use the System.console() method.  If it is running
     42  * on Java 5, then it will use an ugly hack in which one thread will be used to
     43  * repeatedly send backspace characters to the console while another reads the
     44  * password.  Reflection is used to determine whether the Java 6 method is
     45  * available and to invoke it if it is so that the code will still compile
     46  * cleanly on Java 5 systems.
     47  */
     48 @org.opends.server.types.PublicAPI(
     49      stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
     50      mayInstantiate=false,
     51      mayExtend=false,
     52      mayInvoke=true)
     53 public final class PasswordReader
     54        extends DirectoryThread
     55 {
     56   // Indicates whether the backspace thread should keep looping, sending
     57   // backspace characters to the console.
     58   private volatile boolean keepLooping;
     59 
     60 
     61 
     62   /**
     63    * Creates a new instance of this password reader.  A new instance should only
     64    * be created from within this class.
     65    */
     66   private PasswordReader()
     67   {
     68     super("Password Reader Thread");
     69 
     70     // No implementation is required.  However, this constructor is private to
     71     // help prevent it being used for external purposes.
     72   }
     73 
     74 
     75 
     76   /**
     77    * Operates in a loop, sending backspace characters to the console to attempt
     78    * to prevent exposing what the user entered.  It sets the priority to the
     79    * maximum allowed value to reduce the chance of one or more characters being
     80    * displayed temporarily before they can be erased.
     81    */
     82   @org.opends.server.types.PublicAPI(
     83        stability=org.opends.server.types.StabilityLevel.PRIVATE,
     84        mayInstantiate=false,
     85        mayExtend=false,
     86        mayInvoke=false)
     87   public void run()
     88   {
     89     Thread currentThread   = Thread.currentThread();
     90     int    initialPriority = currentThread.getPriority();
     91 
     92     try
     93     {
     94       try
     95       {
     96         currentThread.setPriority(Thread.MAX_PRIORITY);
     97       } catch (Exception e) {}
     98 
     99       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 
    300