Major improvements:

* Default to using /dev/tty for the password prompt and input before
  falling back to sys.stdin and sys.stderr.
* Use sys.stderr instead of sys.stdout.
* print the 'password may be echoed' warning to stream used to display
  the prompt rather than always sys.stderr.
* warn() with GetPassWarning when input may be echoed.
This commit is contained in:
Gregory P. Smith 2008-04-22 08:08:41 +00:00
parent 8e0319d82a
commit 19b4411181
2 changed files with 93 additions and 35 deletions

View File

@ -14,13 +14,29 @@ The :mod:`getpass` module provides two functions:
Prompt the user for a password without echoing. The user is prompted using the Prompt the user for a password without echoing. The user is prompted using the
string *prompt*, which defaults to ``'Password: '``. On Unix, the prompt is string *prompt*, which defaults to ``'Password: '``. On Unix, the prompt is
written to the file-like object *stream*, which defaults to ``sys.stdout`` (this written to the file-like object *stream*. *stream* defaults to the
argument is ignored on Windows). controlling terminal (/dev/tty) or if that is unavailable to ``sys.stderr``
(this argument is ignored on Windows).
If echo free input is unavailable getpass() falls back to printing
a warning message to *stream* and reading from ``sys.stdin`` and
issuing a :exc:`GetPassWarning`.
Availability: Macintosh, Unix, Windows. Availability: Macintosh, Unix, Windows.
.. versionchanged:: 2.5 .. versionchanged:: 2.5
The *stream* parameter was added. The *stream* parameter was added.
.. versionchanged:: 2.6
On Unix it defaults to using /dev/tty before falling back
to ``sys.stdin`` and ``sys.stderr``.
.. note::
If you call getpass from within idle, the input may be done in the
terminal you launched idle from rather than the idle window itself.
.. exception:: GetPassWarning
A :exc:`UserWarning` subclass issued when password input may be echoed.
.. function:: getuser() .. function:: getuser()

View File

@ -1,7 +1,10 @@
"""Utilities to get a password and/or the current user name. """Utilities to get a password and/or the current user name.
getpass(prompt) - prompt for a password, with echo turned off getpass(prompt[, stream]) - Prompt for a password, with echo turned off.
getuser() - get the user name from the environment or password database getuser() - Get the user name from the environment or password database.
GetPassWarning - This UserWarning is issued when getpass() cannot prevent
echoing of the password contents while reading.
On Windows, the msvcrt module will be used. On Windows, the msvcrt module will be used.
On the Mac EasyDialogs.AskPassword is used, if available. On the Mac EasyDialogs.AskPassword is used, if available.
@ -10,38 +13,70 @@ On the Mac EasyDialogs.AskPassword is used, if available.
# Authors: Piers Lauder (original) # Authors: Piers Lauder (original)
# Guido van Rossum (Windows support and cleanup) # Guido van Rossum (Windows support and cleanup)
# Gregory P. Smith (tty support & GetPassWarning)
import sys import os, sys, warnings
__all__ = ["getpass","getuser","GetPassWarning"]
class GetPassWarning(UserWarning): pass
__all__ = ["getpass","getuser"]
def unix_getpass(prompt='Password: ', stream=None): def unix_getpass(prompt='Password: ', stream=None):
"""Prompt for a password, with echo turned off. """Prompt for a password, with echo turned off.
The prompt is written on stream, by default stdout.
Restore terminal settings at end. Args:
prompt: Written on stream to ask for the input. Default: 'Password: '
stream: A writable file object to display the prompt. Defaults to
the tty. If no tty is available defaults to sys.stderr.
Returns:
The seKr3t input.
Raises:
EOFError: If our input tty or stdin was closed.
GetPassWarning: When we were unable to turn echo off on the input.
Always restores terminal settings before returning.
""" """
if stream is None: fd = None
stream = sys.stdout tty = None
if not sys.stdin.isatty():
print >>sys.stderr, "Warning: sys.stdin is not a tty."
return default_getpass(prompt)
try: try:
fd = sys.stdin.fileno() # Always try reading and writing directly on the tty first.
except: fd = os.open('/dev/tty', os.O_RDWR|os.O_NOCTTY)
return default_getpass(prompt) tty = os.fdopen(fd, 'w+', 1)
input = tty
if not stream:
stream = tty
except EnvironmentError, e:
# If that fails, see if stdin can be controlled.
try:
fd = sys.stdin.fileno()
except:
passwd = fallback_getpass(prompt, stream)
input = sys.stdin
if not stream:
stream = sys.stderr
old = termios.tcgetattr(fd) # a copy to save if fd is not None:
new = old[:] passwd = None
try:
new[3] = new[3] & ~termios.ECHO # 3 == 'lflags' old = termios.tcgetattr(fd) # a copy to save
try: new = old[:]
termios.tcsetattr(fd, termios.TCSADRAIN, new) new[3] &= ~termios.ECHO # 3 == 'lflags'
passwd = _raw_input(prompt, stream) try:
finally: termios.tcsetattr(fd, termios.TCSADRAIN, new)
termios.tcsetattr(fd, termios.TCSADRAIN, old) passwd = _raw_input(prompt, stream, input=input)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old)
except termios.error, e:
if passwd is not None:
# _raw_input succeeded. The final tcsetattr failed. Reraise
# instead of leaving the terminal in an unknown state.
raise
# We can't control the tty or stdin. Give up and use normal IO.
# fallback_getpass() raises an appropriate warning.
del input, tty # clean up unused file objects before blocking
passwd = fallback_getpass(prompt, stream)
stream.write('\n') stream.write('\n')
return passwd return passwd
@ -50,7 +85,7 @@ def unix_getpass(prompt='Password: ', stream=None):
def win_getpass(prompt='Password: ', stream=None): def win_getpass(prompt='Password: ', stream=None):
"""Prompt for password with echo off, using Windows getch().""" """Prompt for password with echo off, using Windows getch()."""
if sys.stdin is not sys.__stdin__: if sys.stdin is not sys.__stdin__:
return default_getpass(prompt, stream) return fallback_getpass(prompt, stream)
import msvcrt import msvcrt
for c in prompt: for c in prompt:
msvcrt.putch(c) msvcrt.putch(c)
@ -70,20 +105,27 @@ def win_getpass(prompt='Password: ', stream=None):
return pw return pw
def default_getpass(prompt='Password: ', stream=None): def fallback_getpass(prompt='Password: ', stream=None):
print >>sys.stderr, "Warning: Problem with getpass. Passwords may be echoed." warnings.warn("Can not control echo on the terminal.", GetPassWarning,
stacklevel=2)
if not stream:
stream = sys.stderr
print >>stream, "Warning: Password input may be echoed."
return _raw_input(prompt, stream) return _raw_input(prompt, stream)
def _raw_input(prompt="", stream=None): def _raw_input(prompt="", stream=None, input=None):
# A raw_input() replacement that doesn't save the string in the # A raw_input() replacement that doesn't save the string in the
# GNU readline history. # GNU readline history.
if stream is None: if not stream:
stream = sys.stdout stream = sys.stderr
if not input:
input = sys.stdin
prompt = str(prompt) prompt = str(prompt)
if prompt: if prompt:
stream.write(prompt) stream.write(prompt)
line = sys.stdin.readline() stream.flush()
line = input.readline()
if not line: if not line:
raise EOFError raise EOFError
if line[-1] == '\n': if line[-1] == '\n':
@ -123,7 +165,7 @@ except (ImportError, AttributeError):
try: try:
from EasyDialogs import AskPassword from EasyDialogs import AskPassword
except ImportError: except ImportError:
getpass = default_getpass getpass = fallback_getpass
else: else:
getpass = AskPassword getpass = AskPassword
else: else: