Table of Contents

A Better "getpass()" Function for Unix

UPDATE

It looks like my version of getpass (using the tty) is actually the default in the standard library version of getpass as of python 2.6. If you are using python 2.6 or greater, use the standard library version as it also falls back to stdin if using the tty fails.

What is This About?

I've had this problem in the past and it cropped up again today and pissed me off once again so I decided to tackle the issue. The issue is the Python standard library version of getpass() from the getpass module. It works fine in terms of really basic usage, but the big problem comes when you want to write a script that reads from STDIN and calls getpass(). Since the built in getpass() reads from STDIN, it ends up borking your script. Now, for anyone who has ever used ssh before (and I assume if you are reading this, you have), you will have noticed that the ssh client gets around this very issue since you can pipe info to ssh and enter your password when prompted. This is due to the fact that ssh client reads and writes directly to your terminal, rather than reading and writing from STDIN and STDOUT. This is exactly what I've done with my version of getpass(). I used most of the code used in the standard library getpass(), but made it write the prompt and read directly from a connection to the terminal itself. This has been tested on linux (Gentoo) and FreeBSD 6.2 and it works fine on both.

The Code

Here is my simple version of the standard library getpass(). Of course, this will only work on a *nix platform.

import termios , os , sys
 
def getPass(prompt='Password: '):
    """
    Writes the password prompt and reads from terminal directly, just like
    the openssh client
    """
    if not os.isatty(sys.stderr.fileno()):
        raise IOError , 'You can only call getPass() from a tty'
    ttyname = os.ttyname(sys.stderr.fileno())
    ttyh = open(ttyname , 'w+b')
    ttyfd = ttyh.fileno()
    old = termios.tcgetattr(ttyfd)     # a copy to save
    new = old[:]
    new[3] = new[3] & ~termios.ECHO # 3 == 'lflags'
    try:
        termios.tcsetattr(ttyfd , termios.TCSADRAIN, new)
        ttyh.write(prompt)
        ttyh.flush()
        passwd = ttyh.readline().strip()
    finally:
        termios.tcsetattr(ttyfd , termios.TCSADRAIN, old)
    ttyh.close()
    sys.stdout.write('\n')
    return passwd