codekingpro/portable-devtools
114k
1"""Utilities to get a password and/or the current user name.2 3getpass(prompt[, stream[, echo_char]]) - Prompt for a password, with echo4turned off and optional keyboard feedback.5getuser() - Get the user name from the environment or password database.6 7GetPassWarning - This UserWarning is issued when getpass() cannot prevent8 echoing of the password contents while reading.9 10On Windows, the msvcrt module will be used.11 12"""13 14# Authors: Piers Lauder (original)15# Guido van Rossum (Windows support and cleanup)16# Gregory P. Smith (tty support & GetPassWarning)17 18import contextlib19import io20import os21import sys22 23__all__ = ["getpass","getuser","GetPassWarning"]24 25 26class GetPassWarning(UserWarning): pass27 28 29def unix_getpass(prompt='Password: ', stream=None, *, echo_char=None):30 """Prompt for a password, with echo turned off.31 32 Args:33 prompt: Written on stream to ask for the input. Default: 'Password: '34 stream: A writable file object to display the prompt. Defaults to35 the tty. If no tty is available defaults to sys.stderr.36 echo_char: A single ASCII character to mask input (e.g., '*').37 If None, input is hidden.38 Returns:39 The seKr3t input.40 Raises:41 EOFError: If our input tty or stdin was closed.42 GetPassWarning: When we were unable to turn echo off on the input.43 44 Always restores terminal settings before returning.45 """46 _check_echo_char(echo_char)47 48 passwd = None49 with contextlib.ExitStack() as stack:50 try:51 # Always try reading and writing directly on the tty first.52 fd = os.open('/dev/tty', os.O_RDWR|os.O_NOCTTY)53 tty = io.FileIO(fd, 'w+')54 stack.enter_context(tty)55 input = io.TextIOWrapper(tty)56 stack.enter_context(input)57 if not stream:58 stream = input59 except OSError:60 # If that fails, see if stdin can be controlled.61 stack.close()62 try:63 fd = sys.stdin.fileno()64 except (AttributeError, ValueError):65 fd = None66 passwd = fallback_getpass(prompt, stream)67 input = sys.stdin68 if not stream:69 stream = sys.stderr70 71 if fd is not None:72 try:73 old = termios.tcgetattr(fd) # a copy to save74 new = old[:]75 new[3] &= ~termios.ECHO # 3 == 'lflags'76 if echo_char:77 new[3] &= ~termios.ICANON78 tcsetattr_flags = termios.TCSAFLUSH79 if hasattr(termios, 'TCSASOFT'):80 tcsetattr_flags |= termios.TCSASOFT81 try:82 termios.tcsetattr(fd, tcsetattr_flags, new)83 passwd = _raw_input(prompt, stream, input=input,84 echo_char=echo_char)85 86 finally:87 termios.tcsetattr(fd, tcsetattr_flags, old)88 stream.flush() # issue720889 except termios.error:90 if passwd is not None:91 # _raw_input succeeded. The final tcsetattr failed. Reraise92 # instead of leaving the terminal in an unknown state.93 raise94 # We can't control the tty or stdin. Give up and use normal IO.95 # fallback_getpass() raises an appropriate warning.96 if stream is not input:97 # clean up unused file objects before blocking98 stack.close()99 passwd = fallback_getpass(prompt, stream)100 101 stream.write('\n')102 return passwd103 104 105def win_getpass(prompt='Password: ', stream=None, *, echo_char=None):106 """Prompt for password with echo off, using Windows getwch()."""107 if sys.stdin is not sys.__stdin__:108 return fallback_getpass(prompt, stream)109 _check_echo_char(echo_char)110 111 for c in prompt:112 msvcrt.putwch(c)113 pw = ""114 while 1:115 c = msvcrt.getwch()116 if c == '\r' or c == '\n':117 break118 if c == '\003':119 raise KeyboardInterrupt120 if c == '\b':121 if echo_char and pw:122 msvcrt.putwch('\b')123 msvcrt.putwch(' ')124 msvcrt.putwch('\b')125 pw = pw[:-1]126 else:127 pw = pw + c128 if echo_char:129 msvcrt.putwch(echo_char)130 msvcrt.putwch('\r')131 msvcrt.putwch('\n')132 return pw133 134 135def fallback_getpass(prompt='Password: ', stream=None, *, echo_char=None):136 _check_echo_char(echo_char)137 import warnings138 warnings.warn("Can not control echo on the terminal.", GetPassWarning,139 stacklevel=2)140 if not stream:141 stream = sys.stderr142 print("Warning: Password input may be echoed.", file=stream)143 return _raw_input(prompt, stream, echo_char=echo_char)144 145 146def _check_echo_char(echo_char):147 # Single-character ASCII excluding control characters148 if echo_char is None:149 return150 if not isinstance(echo_char, str):151 raise TypeError("'echo_char' must be a str or None, not "152 f"{type(echo_char).__name__}")153 if not (154 len(echo_char) == 1155 and echo_char.isprintable()156 and echo_char.isascii()157 ):158 raise ValueError("'echo_char' must be a single printable ASCII "159 f"character, got: {echo_char!r}")160 161 162def _raw_input(prompt="", stream=None, input=None, echo_char=None):163 # This doesn't save the string in the GNU readline history.164 if not stream:165 stream = sys.stderr166 if not input:167 input = sys.stdin168 prompt = str(prompt)169 if prompt:170 try:171 stream.write(prompt)172 except UnicodeEncodeError:173 # Use replace error handler to get as much as possible printed.174 prompt = prompt.encode(stream.encoding, 'replace')175 prompt = prompt.decode(stream.encoding)176 stream.write(prompt)177 stream.flush()178 # NOTE: The Python C API calls flockfile() (and unlock) during readline.179 if echo_char:180 return _readline_with_echo_char(stream, input, echo_char)181 line = input.readline()182 if not line:183 raise EOFError184 if line[-1] == '\n':185 line = line[:-1]186 return line187 188 189def _readline_with_echo_char(stream, input, echo_char):190 passwd = ""191 eof_pressed = False192 while True:193 char = input.read(1)194 if char == '\n' or char == '\r':195 break196 elif char == '\x03':197 raise KeyboardInterrupt198 elif char == '\x7f' or char == '\b':199 if passwd:200 stream.write("\b \b")201 stream.flush()202 passwd = passwd[:-1]203 elif char == '\x04':204 if eof_pressed:205 break206 else:207 eof_pressed = True208 elif char == '\x00':209 continue210 else:211 passwd += char212 stream.write(echo_char)213 stream.flush()214 eof_pressed = False215 return passwd216 217 218def getuser():219 """Get the username from the environment or password database.220 221 First try various environment variables, then the password222 database. This works on Windows as long as USERNAME is set.223 Any failure to find a username raises OSError.224 225 .. versionchanged:: 3.13226 Previously, various exceptions beyond just :exc:`OSError`227 were raised.228 """229 230 for name in ('LOGNAME', 'USER', 'LNAME', 'USERNAME'):231 user = os.environ.get(name)232 if user:233 return user234 235 try:236 import pwd237 return pwd.getpwuid(os.getuid())[0]238 except (ImportError, KeyError) as e:239 raise OSError('No username set in the environment') from e240 241 242# Bind the name getpass to the appropriate function243try:244 import termios245 # it's possible there is an incompatible termios from the246 # McMillan Installer, make sure we have a UNIX-compatible termios247 termios.tcgetattr, termios.tcsetattr248except (ImportError, AttributeError):249 try:250 import msvcrt251 except ImportError:252 getpass = fallback_getpass253 else:254 getpass = win_getpass255else:256 getpass = unix_getpass257 