def prompt(self): # Obtain cached password, if any password = get_password(*normalize(env.host_string)) # Remove the prompt itself from the capture buffer. This is # backwards compatible with Fabric 0.9.x behavior; the user # will still see the prompt on their screen (no way to avoid # this) but at least it won't clutter up the captured text. del self.capture[-1 * len(env.sudo_prompt):] # If the password we just tried was bad, prompt the user again. if (not password) or self.reprompt: # Print the prompt and/or the "try again" notice if # output is being hidden. In other words, since we need # the user's input, they need to see why we're # prompting them. if not self.printing: self._flush(self.prefix) if self.reprompt: self._flush(env.again_prompt + '\n' + self.prefix) self._flush(env.sudo_prompt) # Prompt for, and store, password. Give empty prompt so the # initial display "hides" just after the actually-displayed # prompt from the remote end. self.chan.input_enabled = False password = fabric.network.prompt_for_password( prompt=" ", no_colon=True, stream=self.stream ) self.chan.input_enabled = True # Update env.password, env.passwords if necessary user, host, port = normalize(env.host_string) set_password(user, host, port, password) # Reset reprompt flag self.reprompt = False # Send current password down the pipe self.chan.sendall(password + '\n')
def prompt(self): # Obtain cached password, if any password = get_password(*normalize(env.host_string)) # Remove the prompt itself from the capture buffer. This is # backwards compatible with Fabric 0.9.x behavior; the user # will still see the prompt on their screen (no way to avoid # this) but at least it won't clutter up the captured text. # NOTE: Yes, the original RingBuffer from Fabric can do this more elegantly. # This removes the last N elements from the list. _pop_count = min(len(self.capture), len(env.sudo_prompt)) for i in range(0, _pop_count): self.capture.pop() # If the password we just tried was bad, prompt the user again. if (not password) or self.reprompt: # Print the prompt and/or the "try again" notice if # output is being hidden. In other words, since we need # the user's input, they need to see why we're # prompting them. if not self.printing: self._flush(self.prefix) if self.reprompt: self._flush(env.again_prompt + '\n' + self.prefix) self._flush(env.sudo_prompt) # Prompt for, and store, password. Give empty prompt so the # initial display "hides" just after the actually-displayed # prompt from the remote end. self.chan.input_enabled = False password = fabric.network.prompt_for_password( prompt=" ", no_colon=True, stream=self.stream ) self.chan.input_enabled = True # Update env.password, env.passwords if necessary user, host, port = normalize(env.host_string) # TODO: in 2.x, make sure to only update sudo-specific password # config values, not login ones. set_password(user, host, port, password) # Reset reprompt flag self.reprompt = False # Send current password down the pipe self.chan.sendall(password + '\n')
def connect(user, host, port, cache, seek_gateway=True): """ Create and return a new SSHClient instance connected to given host. :param user: Username to connect as. :param host: Network hostname. :param port: SSH daemon port. :param cache: A ``HostConnectionCache`` instance used to cache/store gateway hosts when gatewaying is enabled. :param seek_gateway: Whether to try setting up a gateway socket for this connection. Used so the actual gateway connection can prevent recursion. """ from state import env, output # # Initialization # # Init client client = ssh.SSHClient() # Load system hosts file (e.g. /etc/ssh/ssh_known_hosts) known_hosts = env.get('system_known_hosts') if known_hosts: client.load_system_host_keys(known_hosts) # Load known host keys (e.g. ~/.ssh/known_hosts) unless user says not to. if not env.disable_known_hosts: client.load_system_host_keys() # Unless user specified not to, accept/add new, unknown host keys if not env.reject_unknown_hosts: client.set_missing_host_key_policy(ssh.AutoAddPolicy()) # # Connection attempt loop # # Initialize loop variables connected = False password = get_password(user, host, port) tries = 0 sock = None # Loop until successful connect (keep prompting for new password) while not connected: # Attempt connection try: tries += 1 # (Re)connect gateway socket, if needed. # Nuke cached client object if not on initial try. if seek_gateway: sock = get_gateway(host, port, cache, replace=tries > 0) # Ready to connect client.connect( hostname=host, port=int(port), username=user, password=password, pkey=key_from_env(password), key_filename=key_filenames(), timeout=env.timeout, allow_agent=not env.no_agent, look_for_keys=not env.no_keys, sock=sock ) connected = True # set a keepalive if desired if env.keepalive: client.get_transport().set_keepalive(env.keepalive) return client # BadHostKeyException corresponds to key mismatch, i.e. what on the # command line results in the big banner error about man-in-the-middle # attacks. except ssh.BadHostKeyException, e: raise NetworkError("Host key for %s did not match pre-existing key! Server's key was changed recently, or possible man-in-the-middle attack." % host, e) # Prompt for new password to try on auth failure except ( ssh.AuthenticationException, ssh.PasswordRequiredException, ssh.SSHException ), e: msg = str(e) # If we get SSHExceptionError and the exception message indicates # SSH protocol banner read failures, assume it's caused by the # server load and try again. if e.__class__ is ssh.SSHException \ and msg == 'Error reading SSH protocol banner': if _tried_enough(tries): raise NetworkError(msg, e) continue # For whatever reason, empty password + no ssh key or agent # results in an SSHException instead of an # AuthenticationException. Since it's difficult to do # otherwise, we must assume empty password + SSHException == # auth exception. # # Conversely: if we get SSHException and there # *was* a password -- it is probably something non auth # related, and should be sent upwards. (This is not true if the # exception message does indicate key parse problems.) # # This also holds true for rejected/unknown host keys: we have to # guess based on other heuristics. if e.__class__ is ssh.SSHException \ and (password or msg.startswith('Unknown server')) \ and not is_key_load_error(e): raise NetworkError(msg, e) # Otherwise, assume an auth exception, and prompt for new/better # password. # Paramiko doesn't handle prompting for locked private # keys (i.e. keys with a passphrase and not loaded into an agent) # so we have to detect this and tweak our prompt slightly. # (Otherwise, however, the logic flow is the same, because # ssh's connect() method overrides the password argument to be # either the login password OR the private key passphrase. Meh.) # # NOTE: This will come up if you normally use a # passphrase-protected private key with ssh-agent, and enter an # incorrect remote username, because ssh.connect: # * Tries the agent first, which will fail as you gave the wrong # username, so obviously any loaded keys aren't gonna work for a # nonexistent remote account; # * Then tries the on-disk key file, which is passphrased; # * Realizes there's no password to try unlocking that key with, # because you didn't enter a password, because you're using # ssh-agent; # * In this condition (trying a key file, password is None) # ssh raises PasswordRequiredException. text = None if e.__class__ is ssh.PasswordRequiredException \ or is_key_load_error(e): # NOTE: we can't easily say WHICH key's passphrase is needed, # because ssh doesn't provide us with that info, and # env.key_filename may be a list of keys, so we can't know # which one raised the exception. Best not to try. prompt = "[%s] Passphrase for private key" text = prompt % env.host_string password = prompt_for_password(text) # Update env.password, env.passwords if empty set_password(user, host, port, password)
def output_loop(chan, which, capture): # Obtain stdout or stderr related values func = getattr(chan, which) if which == 'recv': prefix = "out" pipe = sys.stdout else: prefix = "err" pipe = sys.stderr printing = getattr(output, 'stdout' if (which == 'recv') else 'stderr') # Initialize loop variables reprompt = False initial_prefix_printed = False while True: # Handle actual read/write byte = func(1) if byte == '': break # A None capture variable implies that we're in open_shell() if capture is None: # Just print directly -- no prefixes, no capturing, nada # And since we know we're using a pty in this mode, just go # straight to stdout. _flush(sys.stdout, byte) # Otherwise, we're in run/sudo and need to handle capturing and # prompts. else: _prefix = "[%s] %s: " % (env.host_string, prefix) # Print to user if printing: # Initial prefix if not initial_prefix_printed: _flush(pipe, _prefix) initial_prefix_printed = True # Byte itself _flush(pipe, byte) # Trailing prefix to start off next line if byte in ("\n", "\r"): _flush(pipe, _prefix) # Store in capture buffer capture += byte # Handle prompts prompt = _endswith(capture, env.sudo_prompt) try_again = (_endswith(capture, env.again_prompt + '\n') or _endswith(capture, env.again_prompt + '\r\n')) if prompt: # Obtain cached password, if any password = get_password() # Remove the prompt itself from the capture buffer. This is # backwards compatible with Fabric 0.9.x behavior; the user # will still see the prompt on their screen (no way to avoid # this) but at least it won't clutter up the captured text. del capture[-1*len(env.sudo_prompt):] # If the password we just tried was bad, prompt the user again. if (not password) or reprompt: # Print the prompt and/or the "try again" notice if # output is being hidden. In other words, since we need # the user's input, they need to see why we're # prompting them. if not printing: _flush(pipe, _prefix) if reprompt: _flush(pipe, env.again_prompt + '\n' + _prefix) _flush(pipe, env.sudo_prompt) # Prompt for, and store, password. Give empty prompt so the # initial display "hides" just after the actually-displayed # prompt from the remote end. password = fabric.network.prompt_for_password( prompt=" ", no_colon=True, stream=pipe ) # Update env.password, env.passwords if necessary set_password(password) # Reset reprompt flag reprompt = False # Send current password down the pipe chan.sendall(password + '\n') elif try_again: # Remove text from capture buffer capture = capture[:len(env.again_prompt)] # Set state so we re-prompt the user at the next prompt. reprompt = True
def connect(user, host, port): """ Create and return a new SSHClient instance connected to given host. """ from state import env # # Initialization # # Init client client = ssh.SSHClient() # Load known host keys (e.g. ~/.ssh/known_hosts) unless user says not to. if not env.disable_known_hosts: client.load_system_host_keys() # Unless user specified not to, accept/add new, unknown host keys if not env.reject_unknown_hosts: client.set_missing_host_key_policy(ssh.AutoAddPolicy()) # # Connection attempt loop # # Initialize loop variables connected = False password = get_password() # Loop until successful connect (keep prompting for new password) while not connected: # Attempt connection try: client.connect( hostname=host, port=int(port), username=user, password=password, key_filename=env.key_filename, timeout=10, allow_agent=not env.no_agent, look_for_keys=not env.no_keys ) connected = True return client # BadHostKeyException corresponds to key mismatch, i.e. what on the # command line results in the big banner error about man-in-the-middle # attacks. except ssh.BadHostKeyException: abort("Host key for %s did not match pre-existing key! Server's key was changed recently, or possible man-in-the-middle attack." % env.host) # Prompt for new password to try on auth failure except ( ssh.AuthenticationException, ssh.PasswordRequiredException, ssh.SSHException ), e: # For whatever reason, empty password + no ssh key or agent results # in an SSHException instead of an AuthenticationException. Since # it's difficult to do otherwise, we must assume empty password + # SSHException == auth exception. Conversely: if we get # SSHException and there *was* a password -- it is probably # something non auth related, and should be sent upwards. if e.__class__ is ssh.SSHException and password: abort(str(e)) # Otherwise, assume an auth exception, and prompt for new/better # password. # Paramiko doesn't handle prompting for locked private keys (i.e. # keys with a passphrase and not loaded into an agent) so we have # to detect this and tweak our prompt slightly. (Otherwise, # however, the logic flow is the same, because Paramiko's connect() # method overrides the password argument to be either the login # password OR the private key passphrase. Meh.) # # NOTE: This will come up if you normally use a # passphrase-protected private key with ssh-agent, and enter an # incorrect remote username, because Paramiko: # * Tries the agent first, which will fail as you gave the wrong # username, so obviously any loaded keys aren't gonna work for a # nonexistent remote account; # * Then tries the on-disk key file, which is passphrased; # * Realizes there's no password to try unlocking that key with, # because you didn't enter a password, because you're using # ssh-agent; # * In this condition (trying a key file, password is None) # Paramiko raises PasswordRequiredException. text = None if e.__class__ is ssh.PasswordRequiredException: # NOTE: we can't easily say WHICH key's passphrase is needed, # because Paramiko doesn't provide us with that info, and # env.key_filename may be a list of keys, so we can't know # which one raised the exception. Best not to try. prompt = "[%s] Passphrase for private key" text = prompt % env.host_string password = prompt_for_password(text) # Update env.password, env.passwords if empty set_password(password) # Ctrl-D / Ctrl-C for exit except (EOFError, TypeError): # Print a newline (in case user was sitting at prompt) print('') sys.exit(0)
def connect(user, host, port, cache, seek_gateway=True): """ Create and return a new SSHClient instance connected to given host. :param user: Username to connect as. :param host: Network hostname. :param port: SSH daemon port. :param cache: A ``HostConnectionCache`` instance used to cache/store gateway hosts when gatewaying is enabled. :param seek_gateway: Whether to try setting up a gateway socket for this connection. Used so the actual gateway connection can prevent recursion. """ from state import env, output # # Initialization # # Init client client = ssh.SSHClient() # Load system hosts file (e.g. /etc/ssh/ssh_known_hosts) known_hosts = env.get('system_known_hosts') if known_hosts: client.load_system_host_keys(known_hosts) # Load known host keys (e.g. ~/.ssh/known_hosts) unless user says not to. if not env.disable_known_hosts: client.load_system_host_keys() # Unless user specified not to, accept/add new, unknown host keys if not env.reject_unknown_hosts: client.set_missing_host_key_policy(ssh.AutoAddPolicy()) # # Connection attempt loop # # Initialize loop variables connected = False password = get_password(user, host, port) tries = 0 sock = None # Loop until successful connect (keep prompting for new password) while not connected: # Attempt connection try: tries += 1 # (Re)connect gateway socket, if needed. # Nuke cached client object if not on initial try. if seek_gateway: sock = get_gateway(host, port, cache, replace=tries > 0) # Ready to connect client.connect( hostname=host, port=int(port), username=user, password=password, pkey=key_from_env(password), key_filename=key_filenames(), timeout=env.timeout, allow_agent=not env.no_agent, look_for_keys=not env.no_keys, sock=sock ) connected = True # set a keepalive if desired if env.keepalive: client.get_transport().set_keepalive(env.keepalive) return client # BadHostKeyException corresponds to key mismatch, i.e. what on the # command line results in the big banner error about man-in-the-middle # attacks. except ssh.BadHostKeyException as e: raise NetworkError("Host key for %s did not match pre-existing key! Server's key was changed recently, or possible man-in-the-middle attack." % host, e) # Prompt for new password to try on auth failure except (ssh.AuthenticationException, ssh.PasswordRequiredException, ssh.SSHException ) as e: msg = str(e) # If we get SSHExceptionError and the exception message indicates # SSH protocol banner read failures, assume it's caused by the # server load and try again. if e.__class__ is ssh.SSHException \ and msg == 'Error reading SSH protocol banner': if _tried_enough(tries): raise NetworkError(msg, e) continue # For whatever reason, empty password + no ssh key or agent # results in an SSHException instead of an # AuthenticationException. Since it's difficult to do # otherwise, we must assume empty password + SSHException == # auth exception. # # Conversely: if we get SSHException and there # *was* a password -- it is probably something non auth # related, and should be sent upwards. (This is not true if the # exception message does indicate key parse problems.) # # This also holds true for rejected/unknown host keys: we have to # guess based on other heuristics. if e.__class__ is ssh.SSHException \ and (password or msg.startswith('Unknown server')) \ and not is_key_load_error(e): raise NetworkError(msg, e) # Otherwise, assume an auth exception, and prompt for new/better # password. # Paramiko doesn't handle prompting for locked private # keys (i.e. keys with a passphrase and not loaded into an agent) # so we have to detect this and tweak our prompt slightly. # (Otherwise, however, the logic flow is the same, because # ssh's connect() method overrides the password argument to be # either the login password OR the private key passphrase. Meh.) # # NOTE: This will come up if you normally use a # passphrase-protected private key with ssh-agent, and enter an # incorrect remote username, because ssh.connect: # * Tries the agent first, which will fail as you gave the wrong # username, so obviously any loaded keys aren't gonna work for a # nonexistent remote account; # * Then tries the on-disk key file, which is passphrased; # * Realizes there's no password to try unlocking that key with, # because you didn't enter a password, because you're using # ssh-agent; # * In this condition (trying a key file, password is None) # ssh raises PasswordRequiredException. text = None if e.__class__ is ssh.PasswordRequiredException \ or is_key_load_error(e): # NOTE: we can't easily say WHICH key's passphrase is needed, # because ssh doesn't provide us with that info, and # env.key_filename may be a list of keys, so we can't know # which one raised the exception. Best not to try. prompt = "[%s] Passphrase for private key" text = prompt % env.host_string password = prompt_for_password(text) # Update env.password, env.passwords if empty set_password(user, host, port, password) # Ctrl-D / Ctrl-C for exit # TODO: this may no longer actually serve its original purpose and may # also hide TypeErrors from paramiko. Double check in v2. except (EOFError, TypeError): # Print a newline (in case user was sitting at prompt) print('') sys.exit(0) # Handle DNS error / name lookup failure except socket.gaierror as e: raise NetworkError('Name lookup failed for %s' % host, e) # Handle timeouts and retries, including generic errors # NOTE: In 2.6, socket.error subclasses IOError except socket.error as e: not_timeout = type(e) is not socket.timeout giving_up = _tried_enough(tries) # Baseline error msg for when debug is off msg = "Timed out trying to connect to %s" % host # Expanded for debug on err = msg + " (attempt %s of %s)" % (tries, env.connection_attempts) if giving_up: err += ", giving up" err += ")" # Debuggin' if output.debug: sys.stderr.write(err + '\n') # Having said our piece, try again if not giving_up: # Sleep if it wasn't a timeout, so we still get timeout-like # behavior if not_timeout: time.sleep(env.timeout) continue # Override eror msg if we were retrying other errors if not_timeout: msg = "Low level socket error connecting to host %s on port %s: %s" % ( host, port, e[1] ) # Here, all attempts failed. Tweak error msg to show # tries. # TODO: find good humanization module, jeez s = "s" if env.connection_attempts > 1 else "" msg += " (tried %s time%s)" % (env.connection_attempts, s) raise NetworkError(msg, e) # Ensure that if we terminated without connecting and we were given an # explicit socket, close it out. finally: if not connected and sock is not None: sock.close()
def connect(user, host, port): """ Create and return a new SSHClient instance connected to given host. """ from state import env, output # # Initialization # # Init client client = ssh.SSHClient() # Load known host keys (e.g. ~/.ssh/known_hosts) unless user says not to. if not env.disable_known_hosts: client.load_system_host_keys() # Unless user specified not to, accept/add new, unknown host keys if not env.reject_unknown_hosts: client.set_missing_host_key_policy(ssh.AutoAddPolicy()) # # Connection attempt loop # # Initialize loop variables connected = False password = get_password() tries = 0 # Loop until successful connect (keep prompting for new password) while not connected: # Attempt connection try: tries += 1 client.connect(hostname=host, port=int(port), username=user, password=password, key_filename=key_filenames(), timeout=env.timeout, allow_agent=not env.no_agent, look_for_keys=not env.no_keys) connected = True # set a keepalive if desired if env.keepalive: client.get_transport().set_keepalive(env.keepalive) return client # BadHostKeyException corresponds to key mismatch, i.e. what on the # command line results in the big banner error about man-in-the-middle # attacks. except ssh.BadHostKeyException, e: raise NetworkError( "Host key for %s did not match pre-existing key! Server's key was changed recently, or possible man-in-the-middle attack." % host, e) # Prompt for new password to try on auth failure except (ssh.AuthenticationException, ssh.PasswordRequiredException, ssh.SSHException), e: # For whatever reason, empty password + no ssh key or agent results # in an SSHException instead of an AuthenticationException. Since # it's difficult to do otherwise, we must assume empty password + # SSHException == auth exception. Conversely: if we get # SSHException and there *was* a password -- it is probably # something non auth related, and should be sent upwards. if e.__class__ is ssh.SSHException and password: raise NetworkError(str(e), e) # Otherwise, assume an auth exception, and prompt for new/better # password. # The 'ssh' library doesn't handle prompting for locked private # keys (i.e. keys with a passphrase and not loaded into an agent) # so we have to detect this and tweak our prompt slightly. # (Otherwise, however, the logic flow is the same, because # ssh's connect() method overrides the password argument to be # either the login password OR the private key passphrase. Meh.) # # NOTE: This will come up if you normally use a # passphrase-protected private key with ssh-agent, and enter an # incorrect remote username, because ssh.connect: # * Tries the agent first, which will fail as you gave the wrong # username, so obviously any loaded keys aren't gonna work for a # nonexistent remote account; # * Then tries the on-disk key file, which is passphrased; # * Realizes there's no password to try unlocking that key with, # because you didn't enter a password, because you're using # ssh-agent; # * In this condition (trying a key file, password is None) # ssh raises PasswordRequiredException. text = None if e.__class__ is ssh.PasswordRequiredException: # NOTE: we can't easily say WHICH key's passphrase is needed, # because ssh doesn't provide us with that info, and # env.key_filename may be a list of keys, so we can't know # which one raised the exception. Best not to try. prompt = "[%s] Passphrase for private key" text = prompt % env.host_string password = prompt_for_password(text) # Update env.password, env.passwords if empty set_password(password)
def connect(user, host, port, cache, seek_gateway=True): """ Create and return a new SSHClient instance connected to given host. :param user: Username to connect as. :param host: Network hostname. :param port: SSH daemon port. :param cache: A ``HostConnectionCache`` instance used to cache/store gateway hosts when gatewaying is enabled. :param seek_gateway: Whether to try setting up a gateway socket for this connection. Used so the actual gateway connection can prevent recursion. """ from fabric.state import env, output # # Initialization # # Init client client = ssh.SSHClient() # Load system hosts file (e.g. /etc/ssh/ssh_known_hosts) known_hosts = env.get('system_known_hosts') if known_hosts: client.load_system_host_keys(known_hosts) # Load known host keys (e.g. ~/.ssh/known_hosts) unless user says not to. if not env.disable_known_hosts: client.load_system_host_keys() # Unless user specified not to, accept/add new, unknown host keys if not env.reject_unknown_hosts: client.set_missing_host_key_policy(ssh.AutoAddPolicy()) # # Connection attempt loop # # Initialize loop variables connected = False password = get_password(user, host, port) tries = 0 sock = None # Loop until successful connect (keep prompting for new password) while not connected: # Attempt connection try: tries += 1 # (Re)connect gateway socket, if needed. # Nuke cached client object if not on initial try. if seek_gateway: sock = get_gateway(host, port, cache, replace=tries > 0) # Ready to connect client.connect( hostname=host, port=int(port), username=user, password=password, pkey=key_from_env(password), key_filename=key_filenames(), timeout=env.timeout, allow_agent=not env.no_agent, look_for_keys=not env.no_keys, sock=sock ) connected = True # set a keepalive if desired if env.keepalive: client.get_transport().set_keepalive(env.keepalive) return client # BadHostKeyException corresponds to key mismatch, i.e. what on the # command line results in the big banner error about man-in-the-middle # attacks. except ssh.BadHostKeyException as e: raise NetworkError("Host key for %s did not match pre-existing key! Server's key was changed recently, or possible man-in-the-middle attack." % host, e) # Prompt for new password to try on auth failure except ( ssh.AuthenticationException, ssh.PasswordRequiredException, ssh.SSHException ) as e: msg = str(e) # If we get SSHExceptionError and the exception message indicates # SSH protocol banner read failures, assume it's caused by the # server load and try again. if e.__class__ is ssh.SSHException \ and msg == 'Error reading SSH protocol banner': if _tried_enough(tries): raise NetworkError(msg, e) continue # For whatever reason, empty password + no ssh key or agent # results in an SSHException instead of an # AuthenticationException. Since it's difficult to do # otherwise, we must assume empty password + SSHException == # auth exception. # # Conversely: if we get SSHException and there # *was* a password -- it is probably something non auth # related, and should be sent upwards. (This is not true if the # exception message does indicate key parse problems.) # # This also holds true for rejected/unknown host keys: we have to # guess based on other heuristics. if e.__class__ is ssh.SSHException \ and (password or msg.startswith('Unknown server')) \ and not is_key_load_error(e): raise NetworkError(msg, e) # Otherwise, assume an auth exception, and prompt for new/better # password. # Paramiko doesn't handle prompting for locked private # keys (i.e. keys with a passphrase and not loaded into an agent) # so we have to detect this and tweak our prompt slightly. # (Otherwise, however, the logic flow is the same, because # ssh's connect() method overrides the password argument to be # either the login password OR the private key passphrase. Meh.) # # NOTE: This will come up if you normally use a # passphrase-protected private key with ssh-agent, and enter an # incorrect remote username, because ssh.connect: # * Tries the agent first, which will fail as you gave the wrong # username, so obviously any loaded keys aren't gonna work for a # nonexistent remote account; # * Then tries the on-disk key file, which is passphrased; # * Realizes there's no password to try unlocking that key with, # because you didn't enter a password, because you're using # ssh-agent; # * In this condition (trying a key file, password is None) # ssh raises PasswordRequiredException. text = None if e.__class__ is ssh.PasswordRequiredException \ or is_key_load_error(e): # NOTE: we can't easily say WHICH key's passphrase is needed, # because ssh doesn't provide us with that info, and # env.key_filename may be a list of keys, so we can't know # which one raised the exception. Best not to try. prompt = "[%s] Passphrase for private key" text = prompt % env.host_string password = prompt_for_password(text) # Update env.password, env.passwords if empty set_password(user, host, port, password) # Ctrl-D / Ctrl-C for exit # TODO: this may no longer actually serve its original purpose and may # also hide TypeErrors from paramiko. Double check in v2. except (EOFError, TypeError): # Print a newline (in case user was sitting at prompt) print('') sys.exit(0) # Handle DNS error / name lookup failure except socket.gaierror as e: raise NetworkError('Name lookup failed for %s' % host, e) # Handle timeouts and retries, including generic errors # NOTE: In 2.6, socket.error subclasses IOError except socket.error as e: not_timeout = type(e) is not socket.timeout giving_up = _tried_enough(tries) # Baseline error msg for when debug is off msg = "Timed out trying to connect to %s" % host # Expanded for debug on err = msg + " (attempt %s of %s)" % (tries, env.connection_attempts) if giving_up: err += ", giving up" err += ")" # Debuggin' if output.debug: sys.stderr.write(err + '\n') # Having said our piece, try again if not giving_up: # Sleep if it wasn't a timeout, so we still get timeout-like # behavior if not_timeout: time.sleep(env.timeout) continue # Override eror msg if we were retrying other errors if not_timeout: msg = "Low level socket error connecting to host %s on port %s: %s" % ( host, port, e.args[1] ) # Here, all attempts failed. Tweak error msg to show # tries. # TODO: find good humanization module, jeez s = "s" if env.connection_attempts > 1 else "" msg += " (tried %s time%s)" % (env.connection_attempts, s) raise NetworkError(msg, e) # Ensure that if we terminated without connecting and we were given an # explicit socket, close it out. finally: if not connected and sock is not None: sock.close()
def output_loop(chan, which, capture): # Internal capture-buffer-like buffer, used solely for state keeping. # Unlike 'capture', nothing is ever purged from this. _buffer = [] # Obtain stdout or stderr related values func = getattr(chan, which) if which == 'recv': prefix = "out" pipe = sys.stdout else: prefix = "err" pipe = sys.stderr _prefix = "[%s] %s: " % (env.host_string, prefix) printing = getattr(output, 'stdout' if (which == 'recv') else 'stderr') # Initialize loop variables reprompt = False initial_prefix_printed = False line = [] linewise = (env.linewise or env.parallel) while True: # Handle actual read/write byte = func(1) # Empty byte == EOS if byte == '': # If linewise, ensure we flush any leftovers in the buffer. if linewise and line: _flush(pipe, _prefix) _flush(pipe, "".join(line)) break # A None capture variable implies that we're in open_shell() if capture is None: # Just print directly -- no prefixes, no capturing, nada # And since we know we're using a pty in this mode, just go # straight to stdout. _flush(sys.stdout, byte) # Otherwise, we're in run/sudo and need to handle capturing and # prompts. else: # Allow prefix to be turned off. if not env.output_prefix: _prefix = "" # Print to user if printing: if linewise: # Print prefix + line after newline is seen if _was_newline(_buffer, byte): _flush(pipe, _prefix) _flush(pipe, "".join(line)) line = [] # Add to line buffer line += byte else: # Prefix, if necessary if (not initial_prefix_printed or _was_newline(_buffer, byte)): _flush(pipe, _prefix) initial_prefix_printed = True # Byte itself _flush(pipe, byte) # Store in capture buffer capture += byte # Store in internal buffer _buffer += byte # Handle prompts prompt = _endswith(capture, env.sudo_prompt) try_again = (_endswith(capture, env.again_prompt + '\n') or _endswith(capture, env.again_prompt + '\r\n')) if prompt: # Obtain cached password, if any password = get_password() # Remove the prompt itself from the capture buffer. This is # backwards compatible with Fabric 0.9.x behavior; the user # will still see the prompt on their screen (no way to avoid # this) but at least it won't clutter up the captured text. del capture[-1 * len(env.sudo_prompt):] # If the password we just tried was bad, prompt the user again. if (not password) or reprompt: # Print the prompt and/or the "try again" notice if # output is being hidden. In other words, since we need # the user's input, they need to see why we're # prompting them. if not printing: _flush(pipe, _prefix) if reprompt: _flush(pipe, env.again_prompt + '\n' + _prefix) _flush(pipe, env.sudo_prompt) # Prompt for, and store, password. Give empty prompt so the # initial display "hides" just after the actually-displayed # prompt from the remote end. chan.input_enabled = False password = fabric.network.prompt_for_password( prompt=" ", no_colon=True, stream=pipe) chan.input_enabled = True # Update env.password, env.passwords if necessary set_password(password) # Reset reprompt flag reprompt = False # Send current password down the pipe chan.sendall(password + '\n') elif try_again: # Remove text from capture buffer capture = capture[:len(env.again_prompt)] # Set state so we re-prompt the user at the next prompt. reprompt = True
def output_loop(chan, attr, stream, capture): """ Loop, reading from <chan>.<attr>(), writing to <stream> and buffering to <capture>. """ # Internal capture-buffer-like buffer, used solely for state keeping. # Unlike 'capture', nothing is ever purged from this. _buffer = [] # Obtain stdout or stderr related values func = getattr(chan, attr) _prefix = "[%s] %s: " % ( env.host_string, "out" if attr == 'recv' else "err" ) printing = getattr(output, 'stdout' if (attr == 'recv') else 'stderr') # Initialize loop variables reprompt = False initial_prefix_printed = False line = [] linewise = (env.linewise or env.parallel) while True: # Handle actual read/write byte = func(1) # Empty byte == EOS if byte == '': # If linewise, ensure we flush any leftovers in the buffer. if linewise and line: _flush(stream, _prefix) _flush(stream, "".join(line)) break # A None capture variable implies that we're in open_shell() if capture is None: # Just print directly -- no prefixes, no capturing, nada # And since we know we're using a pty in this mode, just go # straight to stdout. _flush(sys.stdout, byte) # Otherwise, we're in run/sudo and need to handle capturing and # prompts. else: # Allow prefix to be turned off. if not env.output_prefix: _prefix = "" # Print to user if printing: if linewise: # Print prefix + line after newline is seen if _was_newline(_buffer, byte): _flush(stream, _prefix) _flush(stream, "".join(line)) line = [] # Add to line buffer line += byte else: # Prefix, if necessary if ( not initial_prefix_printed or _was_newline(_buffer, byte) ): _flush(stream, _prefix) initial_prefix_printed = True # Byte itself _flush(stream, byte) # Store in capture buffer capture += byte # Store in internal buffer _buffer += byte # Handle prompts prompt = _endswith(capture, env.sudo_prompt) try_again = (_endswith(capture, env.again_prompt + '\n') or _endswith(capture, env.again_prompt + '\r\n')) if prompt: # Obtain cached password, if any password = get_password() # Remove the prompt itself from the capture buffer. This is # backwards compatible with Fabric 0.9.x behavior; the user # will still see the prompt on their screen (no way to avoid # this) but at least it won't clutter up the captured text. del capture[-1 * len(env.sudo_prompt):] # If the password we just tried was bad, prompt the user again. if (not password) or reprompt: # Print the prompt and/or the "try again" notice if # output is being hidden. In other words, since we need # the user's input, they need to see why we're # prompting them. if not printing: _flush(stream, _prefix) if reprompt: _flush(stream, env.again_prompt + '\n' + _prefix) _flush(stream, env.sudo_prompt) # Prompt for, and store, password. Give empty prompt so the # initial display "hides" just after the actually-displayed # prompt from the remote end. chan.input_enabled = False password = fabric.network.prompt_for_password( prompt=" ", no_colon=True, stream=stream ) chan.input_enabled = True # Update env.password, env.passwords if necessary set_password(password) # Reset reprompt flag reprompt = False # Send current password down the pipe chan.sendall(password + '\n') elif try_again: # Remove text from capture buffer capture = capture[:len(env.again_prompt)] # Set state so we re-prompt the user at the next prompt. reprompt = True
def connect(user, host, port): """ Create and return a new SSHClient instance connected to given host. """ from state import env, output # # Initialization # # Init client client = ssh.SSHClient() # Load known host keys (e.g. ~/.ssh/known_hosts) unless user says not to. if not env.disable_known_hosts: client.load_system_host_keys() # Unless user specified not to, accept/add new, unknown host keys if not env.reject_unknown_hosts: client.set_missing_host_key_policy(ssh.AutoAddPolicy()) # # Connection attempt loop # # Initialize loop variables connected = False password = get_password() tries = 0 proxy_command = None if env.use_ssh_config: host = env._ssh_config.lookup(env.host_string) proxy_command = host.get('proxycommand') user = host.get('user') # Loop until successful connect (keep prompting for new password) while not connected: # Attempt connection try: tries += 1 client.connect( hostname=host, port=int(port), username=user, password=password, key_filename=key_filenames(), timeout=env.timeout, allow_agent=not env.no_agent, look_for_keys=not env.no_keys, proxy_command=proxy_command, ) connected = True # set a keepalive if desired if env.keepalive: client.get_transport().set_keepalive(env.keepalive) return client # BadHostKeyException corresponds to key mismatch, i.e. what on the # command line results in the big banner error about man-in-the-middle # attacks. except ssh.BadHostKeyException, e: raise NetworkError("Host key for %s did not match pre-existing key! Server's key was changed recently, or possible man-in-the-middle attack." % host, e) # Prompt for new password to try on auth failure except ( ssh.AuthenticationException, ssh.PasswordRequiredException, ssh.SSHException ), e: msg = str(e) # For whatever reason, empty password + no ssh key or agent # results in an SSHException instead of an # AuthenticationException. Since it's difficult to do # otherwise, we must assume empty password + SSHException == # auth exception. Conversely: if we get SSHException and there # *was* a password -- it is probably something non auth # related, and should be sent upwards. # # This also holds true for rejected/unknown host keys: we have to # guess based on other heuristics. if e.__class__ is ssh.SSHException \ and (password or msg.startswith('Unknown server')): raise NetworkError(msg, e) # Otherwise, assume an auth exception, and prompt for new/better # password. # The 'ssh' library doesn't handle prompting for locked private # keys (i.e. keys with a passphrase and not loaded into an agent) # so we have to detect this and tweak our prompt slightly. # (Otherwise, however, the logic flow is the same, because # ssh's connect() method overrides the password argument to be # either the login password OR the private key passphrase. Meh.) # # NOTE: This will come up if you normally use a # passphrase-protected private key with ssh-agent, and enter an # incorrect remote username, because ssh.connect: # * Tries the agent first, which will fail as you gave the wrong # username, so obviously any loaded keys aren't gonna work for a # nonexistent remote account; # * Then tries the on-disk key file, which is passphrased; # * Realizes there's no password to try unlocking that key with, # because you didn't enter a password, because you're using # ssh-agent; # * In this condition (trying a key file, password is None) # ssh raises PasswordRequiredException. text = None if e.__class__ is ssh.PasswordRequiredException: # NOTE: we can't easily say WHICH key's passphrase is needed, # because ssh doesn't provide us with that info, and # env.key_filename may be a list of keys, so we can't know # which one raised the exception. Best not to try. prompt = "[%s] Passphrase for private key" text = prompt % env.host_string password = prompt_for_password(text) # Update env.password, env.passwords if empty set_password(password)
def output_loop(chan, which, capture): # Obtain stdout or stderr related values func = getattr(chan, which) if which == 'recv': prefix = "out" pipe = sys.stdout else: prefix = "err" pipe = sys.stderr host_prefix = "[%s]" % env.host_string if env.colors: host_prefix = env.color_settings['host_prefix'](host_prefix) printing = getattr(output, 'stdout' if (which == 'recv') else 'stderr') # Initialize loop variables reprompt = False initial_prefix_printed = False while True: # Handle actual read/write byte = func(1) if byte == '': break # A None capture variable implies that we're in open_shell() if capture is None: # Just print directly -- no prefixes, no capturing, nada # And since we know we're using a pty in this mode, just go # straight to stdout. _flush(sys.stdout, byte) # Otherwise, we're in run/sudo and need to handle capturing and # prompts. else: _prefix = "%s %s: " % (host_prefix, prefix) # Print to user if printing: # Initial prefix if not initial_prefix_printed: _flush(pipe, _prefix) initial_prefix_printed = True # Byte itself _flush(pipe, byte) # Trailing prefix to start off next line if byte in ("\n", "\r"): _flush(pipe, _prefix) # Store in capture buffer capture += byte # Handle prompts prompt = _endswith(capture, env.sudo_prompt) try_again = (_endswith(capture, env.again_prompt + '\n') or _endswith(capture, env.again_prompt + '\r\n')) if prompt: # Obtain cached password, if any password = get_password() # Remove the prompt itself from the capture buffer. This is # backwards compatible with Fabric 0.9.x behavior; the user # will still see the prompt on their screen (no way to avoid # this) but at least it won't clutter up the captured text. del capture[-1 * len(env.sudo_prompt):] # If the password we just tried was bad, prompt the user again. if (not password) or reprompt: # Print the prompt and/or the "try again" notice if # output is being hidden. In other words, since we need # the user's input, they need to see why we're # prompting them. if not printing: _flush(pipe, _prefix) if reprompt: _flush(pipe, env.again_prompt + '\n' + _prefix) _flush(pipe, env.sudo_prompt) # Prompt for, and store, password. Give empty prompt so the # initial display "hides" just after the actually-displayed # prompt from the remote end. password = prompt_for_password(prompt=" ", no_colon=True, stream=pipe) # Update env.password, env.passwords if necessary set_password(password) # Reset reprompt flag reprompt = False # Send current password down the pipe chan.sendall(password + '\n') elif try_again: # Remove text from capture buffer capture = capture[:len(env.again_prompt)] # Set state so we re-prompt the user at the next prompt. reprompt = True