5

I'm writing a commandline program that prompts for a passwd and I don't want it to do local echo of the password characters. After some searches, I have stumbled upon System.console().readPassword(), which seems great, except when dealing with pipes in Unix. So, my example program (below) works fine when I invoke it as:

% java PasswdPrompt

but fails with Console == null when I invoke it as

% java PasswdPrompt | less

or

% java PasswdPrompt < inputfile

IMHO, this seems like a JVM issue, but I can't be the only one who has run into this problem so I have to imagine there are some easy solutions.

Any one?

Thanks in advance

import java.io.Console;

public class PasswdPrompt {
    public static void main(String args[]) {
        Console cons = System.console();
        if (cons == null) {
            System.err.println("Got null from System.console()!; exiting...");
            System.exit(1);
        }
        char passwd[] = cons.readPassword("Password: ");
        if (passwd == null) {
            System.err.println("Got null from Console.readPassword()!; exiting...");
            System.exit(1);
        }
        System.err.println("Successfully got passwd.");
    }
}
bharath
  • 14,283
  • 16
  • 57
  • 95
capveg
  • 107
  • 8

2 Answers2

0

From the Java documentation page :

If System.console returns NULL, then Console operations are not permitted, either because the OS doesn't support them or because the program was launched in a noninteractive environment.

The problem is most likely because using a pipe falls out of "interactive" mode and using an input file uses that as System.in, thus no Console.

** UPDATE **

Here's a quick fix. Add these lines at then end of your main method :

if (args.length > 0) {
   PrintStream out = null;
   try {
      out = new PrintStream(new FileOutputStream(args[0]));
      out.print(passwd);
      out.flush();
   } catch (Exception e) {
      e.printStackTrace();
   } finally {
      if (out != null) out.close();
   }
}

And invoke your application like

$ java PasswdPrompt .out.tmp; less .out.tmp; rm .out.tmp

However, your prompted password will reside in plaintext (though hidden) file until the command terminates.

Yanick Rochon
  • 51,409
  • 25
  • 133
  • 214
  • Thanks for the answer. The thing is this is silly -- the underlying shell still has a tty allocated and can still make all of the relevant system calls, so I can't understand what the JVM has done to break this. – capveg Mar 16 '11 at 14:18
  • The JVM hasn't actually broken anything. The problem is that it is not using "/dev/tty" to get the console. (Or maybe it is ... and the name of the device file is different on your platform.) – Stephen C Mar 16 '11 at 14:42
  • 1
    I appreciate the help, but your work around is just not something I can viably ask my users to do every time they run my tool. I may be wrong, but I can't imagine I'm the only one who has ever had this problem, so there must be some sort of reasonable solution. In any case, your solution doesn't handle the 2nd use case 'java PasswdPrompt < input'. Yes, I could add another similar hack, but in the end, I just want this cmdline tool to operate the way that all the other cmdline unix tools work. Thanks again. – capveg Mar 17 '11 at 13:55
  • just create a shell script to run your program. – Yanick Rochon Mar 17 '11 at 15:42
0

So, for some reason, when System.console() returns null, terminal echo is always off, so my problem becomes trivial. The following code works exactly as I wanted. Thanks for all the help.

import java.io.*;


public class PasswdPrompt {
    public static void main(String args[]) throws IOException{
        Console cons = System.console();
        char passwd[];
        if (cons == null) {
            // default to stderr; does NOT echo characters... not sure why
            System.err.print("Password: ");
            BufferedReader reader = new BufferedReader(new InputStreamReader(
                                            System.in));
            passwd= reader.readLine().toCharArray();
        }
        else {
            passwd = cons.readPassword("Password: ");
        }
        System.err.println("Successfully got passwd.: " + String.valueOf(passwd));
    }

}
capveg
  • 107
  • 8
  • 2
    I'm not sure that your solution is ideal. Basically, it means that the password has to be in the file or pipe that your application is reading from. A lot of people would consider that to be insecure. – Stephen C Mar 18 '11 at 04:29