]> granicus.if.org Git - python/commitdiff
Major improvements:
authorGregory P. Smith <greg@mad-scientist.com>
Tue, 22 Apr 2008 08:08:41 +0000 (08:08 +0000)
committerGregory P. Smith <greg@mad-scientist.com>
Tue, 22 Apr 2008 08:08:41 +0000 (08:08 +0000)
* 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.

Doc/library/getpass.rst
Lib/getpass.py

index da1f9f5807be11e99979078bd29a36ccd47442c3..25a82af94c9f6dfb5e02d5405b18bcde9e1f57a5 100644 (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
    string *prompt*, which defaults to ``'Password: '``. On Unix, the prompt is
-   written to the file-like object *stream*, which defaults to ``sys.stdout`` (this
-   argument is ignored on Windows).
+   written to the file-like object *stream*.  *stream* defaults to the
+   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.
 
    .. versionchanged:: 2.5
       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()
index 07c89ff0b978d3b44e3ff9c6316d29f1162b7354..05e9b72a75ab85163eb50917c5f64bd2416b15f0 100644 (file)
@@ -1,7 +1,10 @@
 """Utilities to get a password and/or the current user name.
 
-getpass(prompt) - prompt for a password, with echo turned off
-getuser() - get the user name from the environment or password database
+getpass(prompt[, stream]) - Prompt for a password, with echo turned off.
+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 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)
 #          Guido van Rossum (Windows support and cleanup)
+#          Gregory P. Smith (tty support & GetPassWarning)
 
-import sys
-
-__all__ = ["getpass","getuser"]
+import os, sys, warnings
 
-def unix_getpass(prompt='Password: ', stream=None):
-    """Prompt for a password, with echo turned off.
-    The prompt is written on stream, by default stdout.
+__all__ = ["getpass","getuser","GetPassWarning"]
 
-    Restore terminal settings at end.
-    """
-    if stream is None:
-        stream = sys.stdout
 
-    if not sys.stdin.isatty():
-        print >>sys.stderr, "Warning: sys.stdin is not a tty."
-        return default_getpass(prompt)
+class GetPassWarning(UserWarning): pass
 
-    try:
-        fd = sys.stdin.fileno()
-    except:
-        return default_getpass(prompt)
 
-    old = termios.tcgetattr(fd)     # a copy to save
-    new = old[:]
+def unix_getpass(prompt='Password: ', stream=None):
+    """Prompt for a password, with echo turned off.
 
-    new[3] = new[3] & ~termios.ECHO # 3 == 'lflags'
+    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.
+    """
+    fd = None
+    tty = None
     try:
-        termios.tcsetattr(fd, termios.TCSADRAIN, new)
-        passwd = _raw_input(prompt, stream)
-    finally:
-        termios.tcsetattr(fd, termios.TCSADRAIN, old)
+        # Always try reading and writing directly on the tty first.
+        fd = os.open('/dev/tty', os.O_RDWR|os.O_NOCTTY)
+        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
+
+    if fd is not None:
+        passwd = None
+        try:
+            old = termios.tcgetattr(fd)     # a copy to save
+            new = old[:]
+            new[3] &= ~termios.ECHO  # 3 == 'lflags'
+            try:
+                termios.tcsetattr(fd, termios.TCSADRAIN, new)
+                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')
     return passwd
@@ -50,7 +85,7 @@ def unix_getpass(prompt='Password: ', stream=None):
 def win_getpass(prompt='Password: ', stream=None):
     """Prompt for password with echo off, using Windows getch()."""
     if sys.stdin is not sys.__stdin__:
-        return default_getpass(prompt, stream)
+        return fallback_getpass(prompt, stream)
     import msvcrt
     for c in prompt:
         msvcrt.putch(c)
@@ -70,20 +105,27 @@ def win_getpass(prompt='Password: ', stream=None):
     return pw
 
 
-def default_getpass(prompt='Password: ', stream=None):
-    print >>sys.stderr, "Warning: Problem with getpass. Passwords may be echoed."
+def fallback_getpass(prompt='Password: ', stream=None):
+    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)
 
 
-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
     # GNU readline history.
-    if stream is None:
-        stream = sys.stdout
+    if not stream:
+        stream = sys.stderr
+    if not input:
+        input = sys.stdin
     prompt = str(prompt)
     if prompt:
         stream.write(prompt)
-    line = sys.stdin.readline()
+        stream.flush()
+    line = input.readline()
     if not line:
         raise EOFError
     if line[-1] == '\n':
@@ -123,7 +165,7 @@ except (ImportError, AttributeError):
         try:
             from EasyDialogs import AskPassword
         except ImportError:
-            getpass = default_getpass
+            getpass = fallback_getpass
         else:
             getpass = AskPassword
     else: