diff options
44 files changed, 576 insertions, 391 deletions
diff --git a/.travis.yml b/.travis.yml index 83b68be0..772ff5be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,9 +4,7 @@ cache: directories: - $HOME/.cache/pip python: - - "2.6" - "2.7" - - "3.3" - "3.4" - "3.5" - "3.6" @@ -23,7 +21,7 @@ matrix: - python: 3.6 env: "CRYPTO_BEFORE=1.6" install: - # Ensure modern pip/etc on Python 3.3 workers (not sure WTF, but, eh) + # Ensure modern pip/etc to avoid some issues w/ older worker environs - pip install pip==9.0.1 setuptools==36.6.0 # Grab a specific version of Cryptography if desired. Doing this before other # installations ensures we don't have to do any downgrading/overriding. @@ -37,19 +35,16 @@ install: # TODO: use pipenv + whatever contexty-type stuff it has - pip install codecov # For codecov specifically - pip install -r dev-requirements.txt -script: | - # NOTE: the below hilarity should only exist in 2.0-2.3, 2.4+ should've gone - # back to vague normalcy! - if [[ $TRAVIS_PYTHON_VERSION == '2.6' || $TRAVIS_PYTHON_VERSION == '3.3' ]]; - then - flake8 - coverage run --source=paramiko -m pytest - else - inv travis.blacken - flake8 - inv coverage - inv sites - fi +script: + # Fast syntax check failures for more rapid feedback to submitters + # (Travis-oriented metatask that version checks Python, installs, runs.) + - inv travis.blacken + # I have this in my git pre-push hook, but contributors probably don't + - flake8 + # All (including slow) tests, w/ coverage! + - inv coverage + # Ensure documentation builds, both sites, maxxed nitpicking + - inv sites notifications: irc: channels: "irc.freenode.org#paramiko" @@ -22,7 +22,7 @@ What ---- "Paramiko" is a combination of the Esperanto words for "paranoid" and -"friend". It's a module for Python 2.6+/3.3+ that implements the SSH2 protocol +"friend". It's a module for Python 2.7/3.4+ that implements the SSH2 protocol for secure (encrypted and authenticated) connections to remote machines. Unlike SSL (aka TLS), SSH2 protocol does not require hierarchical certificates signed by a powerful central authority. You may know SSH2 as the protocol that diff --git a/dev-requirements.txt b/dev-requirements.txt index c192f144..8814627f 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -3,7 +3,9 @@ invoke>=1.0,<2.0 invocations>=1.2.0,<2.0 # NOTE: pytest-relaxed currently only works with pytest >=3, <3.3 pytest>=3.2,<3.3 -pytest-relaxed==1.1.2 +pytest-relaxed==1.1.4 +# pytest-xdist for test dir watching and the inv guard task +pytest-xdist>=1.22,<2.0 mock==2.0.0 # Linting! flake8==2.4.0 diff --git a/paramiko/__init__.py b/paramiko/__init__.py index 5780a9c7..f77a2bcc 100644 --- a/paramiko/__init__.py +++ b/paramiko/__init__.py @@ -19,15 +19,6 @@ # flake8: noqa import sys from paramiko._version import __version__, __version_info__ - -if sys.version_info < (2, 6): - raise RuntimeError("You need Python 2.6+ for this module.") - - -__author__ = "Jeff Forcier <jeff@bitprophet.org>" -__license__ = "GNU Lesser General Public License (LGPL)" - - from paramiko.transport import SecurityOptions, Transport from paramiko.client import ( SSHClient, @@ -94,42 +85,46 @@ from paramiko.sftp import ( from paramiko.common import io_sleep + +__author__ = "Jeff Forcier <jeff@bitprophet.org>" +__license__ = "GNU Lesser General Public License (LGPL)" + __all__ = [ - "Transport", - "SSHClient", - "MissingHostKeyPolicy", + "Agent", + "AgentKey", + "AuthenticationException", "AutoAddPolicy", - "RejectPolicy", - "WarningPolicy", - "SecurityOptions", - "SubsystemHandler", + "BadAuthenticationType", + "BadHostKeyException", + "BufferedFile", "Channel", - "PKey", - "RSAKey", + "ChannelException", "DSSKey", + "HostKeys", "Message", - "SSHException", - "AuthenticationException", + "MissingHostKeyPolicy", + "PKey", "PasswordRequiredException", - "BadAuthenticationType", - "ChannelException", - "BadHostKeyException", "ProxyCommand", "ProxyCommandFailure", + "RSAKey", + "RejectPolicy", "SFTP", + "SFTPAttributes", + "SFTPClient", + "SFTPError", "SFTPFile", "SFTPHandle", - "SFTPClient", "SFTPServer", - "SFTPError", - "SFTPAttributes", "SFTPServerInterface", - "ServerInterface", - "BufferedFile", - "Agent", - "AgentKey", - "HostKeys", + "SSHClient", "SSHConfig", - "util", + "SSHException", + "SecurityOptions", + "ServerInterface", + "SubsystemHandler", + "Transport", + "WarningPolicy", "io_sleep", + "util", ] diff --git a/paramiko/_version.py b/paramiko/_version.py index fcdc1c31..2e797d40 100644 --- a/paramiko/_version.py +++ b/paramiko/_version.py @@ -1,2 +1,2 @@ -__version_info__ = (2, 3, 3) +__version_info__ = (2, 4, 2) __version__ = ".".join(map(str, __version_info__)) diff --git a/paramiko/auth_handler.py b/paramiko/auth_handler.py index b1d3e699..26605a16 100644 --- a/paramiko/auth_handler.py +++ b/paramiko/auth_handler.py @@ -46,7 +46,6 @@ from paramiko.common import ( MSG_USERAUTH_REQUEST, MSG_USERAUTH_SUCCESS, MSG_USERAUTH_FAILURE, - cMSG_USERAUTH_BANNER, MSG_USERAUTH_BANNER, MSG_USERAUTH_INFO_REQUEST, MSG_USERAUTH_INFO_RESPONSE, @@ -59,6 +58,7 @@ from paramiko.common import ( MSG_USERAUTH_GSSAPI_ERRTOK, MSG_USERAUTH_GSSAPI_MIC, MSG_NAMES, + cMSG_USERAUTH_BANNER, ) from paramiko.message import Message from paramiko.py3compat import bytestring @@ -95,6 +95,9 @@ class AuthHandler(object): self.gss_host = None self.gss_deleg_creds = True + def _log(self, *args): + return self.transport._log(*args) + def is_authenticated(self): return self.authenticated @@ -269,7 +272,7 @@ class AuthHandler(object): def _parse_service_accept(self, m): service = m.get_text() if service == "ssh-userauth": - self.transport._log(DEBUG, "userauth is OK") + self._log(DEBUG, "userauth is OK") m = Message() m.add_byte(cMSG_USERAUTH_REQUEST) m.add_string(self.username) @@ -347,7 +350,7 @@ class AuthHandler(object): self.transport.send_message(m) else: raise SSHException( - "Received Package: %s" % MSG_NAMES[ptype] + "Received Package: {}".format(MSG_NAMES[ptype]) ) m = Message() m.add_byte(cMSG_USERAUTH_GSSAPI_MIC) @@ -365,16 +368,20 @@ class AuthHandler(object): err_msg = m.get_string() m.get_string() # Lang tag - discarded raise SSHException( - "GSS-API Error:\nMajor Status: %s\n\ - Minor Status: %s\ \nError Message:\ - %s\n" - ) % (str(maj_status), str(min_status), err_msg) + """GSS-API Error: +Major Status: {} +Minor Status: {} +Error Message: {} +""".format( + maj_status, min_status, err_msg + ) + ) elif ptype == MSG_USERAUTH_FAILURE: self._parse_userauth_failure(m) return else: raise SSHException( - "Received Package: %s" % MSG_NAMES[ptype] + "Received Package: {}".format(MSG_NAMES[ptype]) ) elif ( self.auth_method == "gssapi-keyex" @@ -388,23 +395,23 @@ class AuthHandler(object): pass else: raise SSHException( - 'Unknown auth method "%s"' % self.auth_method + 'Unknown auth method "{}"'.format(self.auth_method) ) self.transport._send_message(m) else: - self.transport._log( - DEBUG, 'Service request "%s" accepted (?)' % service + self._log( + DEBUG, 'Service request "{}" accepted (?)'.format(service) ) def _send_auth_result(self, username, method, result): # okay, send result m = Message() if result == AUTH_SUCCESSFUL: - self.transport._log(INFO, "Auth granted (%s)." % method) + self._log(INFO, "Auth granted ({}).".format(method)) m.add_byte(cMSG_USERAUTH_SUCCESS) self.authenticated = True else: - self.transport._log(INFO, "Auth rejected (%s)." % method) + self._log(INFO, "Auth rejected ({}).".format(method)) m.add_byte(cMSG_USERAUTH_FAILURE) m.add_string( self.transport.server_object.get_allowed_auths(username) @@ -448,10 +455,11 @@ class AuthHandler(object): username = m.get_text() service = m.get_text() method = m.get_text() - self.transport._log( + self._log( DEBUG, - "Auth request (type=%s) service=%s, username=%s" - % (method, service, username), + "Auth request (type={}) service={}, username={}".format( + method, service, username + ), ) if service != "ssh-connection": self._disconnect_service_not_available() @@ -459,7 +467,7 @@ class AuthHandler(object): if (self.auth_username is not None) and ( self.auth_username != username ): - self.transport._log( + self._log( WARNING, "Auth rejected because the client attempted to change username in mid-flight", # noqa ) @@ -484,9 +492,7 @@ class AuthHandler(object): # always treated as failure, since we don't support changing # passwords, but collect the list of valid auth types from # the callback anyway - self.transport._log( - DEBUG, "Auth request to change passwords (rejected)" - ) + self._log(DEBUG, "Auth request to change passwords (rejected)") newpassword = m.get_binary() try: newpassword = newpassword.decode("UTF-8", "replace") @@ -504,13 +510,13 @@ class AuthHandler(object): try: key = self.transport._key_info[keytype](Message(keyblob)) except SSHException as e: - self.transport._log( - INFO, "Auth rejected: public key: %s" % str(e) - ) + self._log(INFO, "Auth rejected: public key: {}".format(str(e))) key = None except Exception as e: - msg = "Auth rejected: unsupported or mangled public key ({0}: {1})" # noqa - self.transport._log(INFO, msg.format(e.__class__.__name__, e)) + msg = ( + "Auth rejected: unsupported or mangled public key ({}: {})" + ) # noqa + self._log(INFO, msg.format(e.__class__.__name__, e)) key = None if key is None: self._disconnect_no_more_auth() @@ -533,9 +539,7 @@ class AuthHandler(object): sig = Message(m.get_binary()) blob = self._get_session_blob(key, service, username) if not key.verify_ssh_sig(blob, sig): - self.transport._log( - INFO, "Auth rejected: invalid signature" - ) + self._log(INFO, "Auth rejected: invalid signature") result = AUTH_FAILED elif method == "keyboard-interactive": submethods = m.get_string() @@ -555,7 +559,7 @@ class AuthHandler(object): # We can't accept more than one OID, so if the SSH client sends # more than one, disconnect. if mechs > 1: - self.transport._log( + self._log( INFO, "Disconnect: Received more than one GSS-API OID mechanism", ) @@ -564,7 +568,7 @@ class AuthHandler(object): mech_ok = sshgss.ssh_check_mech(desired_mech) # if we don't support the mechanism, disconnect. if not mech_ok: - self.transport._log( + self._log( INFO, "Disconnect: Received an invalid GSS-API OID mechanism", ) @@ -611,8 +615,8 @@ class AuthHandler(object): self._send_auth_result(username, method, result) def _parse_userauth_success(self, m): - self.transport._log( - INFO, "Authentication (%s) successful!" % self.auth_method + self._log( + INFO, "Authentication ({}) successful!".format(self.auth_method) ) self.authenticated = True self.transport._auth_trigger() @@ -623,21 +627,23 @@ class AuthHandler(object): authlist = m.get_list() partial = m.get_boolean() if partial: - self.transport._log(INFO, "Authentication continues...") - self.transport._log(DEBUG, "Methods: " + str(authlist)) + self._log(INFO, "Authentication continues...") + self._log(DEBUG, "Methods: " + str(authlist)) self.transport.saved_exception = PartialAuthentication(authlist) elif self.auth_method not in authlist: - self.transport._log( - DEBUG, - "Authentication type (%s) not permitted." % self.auth_method, - ) - self.transport._log(DEBUG, "Allowed methods: " + str(authlist)) + for msg in ( + "Authentication type ({}) not permitted.".format( + self.auth_method + ), + "Allowed methods: {}".format(authlist), + ): + self._log(DEBUG, msg) self.transport.saved_exception = BadAuthenticationType( "Bad authentication type", authlist ) else: - self.transport._log( - INFO, "Authentication (%s) failed." % self.auth_method + self._log( + INFO, "Authentication ({}) failed.".format(self.auth_method) ) self.authenticated = False self.username = None @@ -647,7 +653,7 @@ class AuthHandler(object): def _parse_userauth_banner(self, m): banner = m.get_string() self.banner = banner - self.transport._log(INFO, "Auth banner: %s" % banner) + self._log(INFO, "Auth banner: {}".format(banner)) # who cares. def _parse_userauth_info_request(self, m): @@ -691,10 +697,8 @@ class AuthHandler(object): def _handle_local_gss_failure(self, e): self.transport.saved_exception = e - self.transport._log(DEBUG, "GSSAPI failure: %s" % str(e)) - self.transport._log( - INFO, "Authentication (%s) failed." % self.auth_method - ) + self._log(DEBUG, "GSSAPI failure: {}".format(e)) + self._log(INFO, "Authentication ({}) failed.".format(self.auth_method)) self.authenticated = False self.username = None if self.auth_event is not None: diff --git a/paramiko/ber.py b/paramiko/ber.py index 0991b0dd..92d7121e 100644 --- a/paramiko/ber.py +++ b/paramiko/ber.py @@ -89,9 +89,8 @@ class BER(object): return util.inflate_long(data) else: # 1: boolean (00 false, otherwise true) - raise BERException( - "Unknown ber encoding type %d (robey is lazy)" % ident - ) + msg = "Unknown ber encoding type {:d} (robey is lazy)" + raise BERException(msg.format(ident)) @staticmethod def decode_sequence(data): @@ -127,7 +126,9 @@ class BER(object): elif (type(x) is list) or (type(x) is tuple): self.encode_tlv(0x30, self.encode_sequence(x)) else: - raise BERException("Unknown type for encoding: %s" % repr(type(x))) + raise BERException( + "Unknown type for encoding: {!r}".format(type(x)) + ) @staticmethod def encode_sequence(data): diff --git a/paramiko/channel.py b/paramiko/channel.py index fd61bd65..41b18958 100644 --- a/paramiko/channel.py +++ b/paramiko/channel.py @@ -145,7 +145,7 @@ class Channel(ClosingContextManager): """ Return a string representation of this object, for debugging. """ - out = "<paramiko.Channel %d" % self.chanid + out = "<paramiko.Channel {}".format(self.chanid) if self.closed: out += " (closed)" elif self.active: @@ -153,9 +153,9 @@ class Channel(ClosingContextManager): out += " (EOF received)" if self.eof_sent: out += " (EOF sent)" - out += " (open) window=%d" % self.out_window_size + out += " (open) window={}".format(self.out_window_size) if len(self.in_buffer) > 0: - out += " in-buffer=%d" % (len(self.in_buffer),) + out += " in-buffer={}".format(len(self.in_buffer)) out += " -> " + repr(self.transport) out += ">" return out @@ -331,7 +331,7 @@ class Channel(ClosingContextManager): try: self.set_environment_variable(name, value) except SSHException as e: - err = 'Failed to set environment variable "{0}".' + err = 'Failed to set environment variable "{}".' raise SSHException(err.format(name), e) @open_only @@ -991,7 +991,7 @@ class Channel(ClosingContextManager): # a window update self.in_window_threshold = window_size // 10 self.in_window_sofar = 0 - self._log(DEBUG, "Max packet in: %d bytes" % max_packet_size) + self._log(DEBUG, "Max packet in: {} bytes".format(max_packet_size)) def _set_remote_channel(self, chanid, window_size, max_packet_size): self.remote_chanid = chanid @@ -1000,10 +1000,12 @@ class Channel(ClosingContextManager): max_packet_size ) self.active = 1 - self._log(DEBUG, "Max packet out: %d bytes" % self.out_max_packet_size) + self._log( + DEBUG, "Max packet out: {} bytes".format(self.out_max_packet_size) + ) def _request_success(self, m): - self._log(DEBUG, "Sesch channel %d request ok" % self.chanid) + self._log(DEBUG, "Sesch channel {} request ok".format(self.chanid)) self.event_ready = True self.event.set() return @@ -1031,7 +1033,7 @@ class Channel(ClosingContextManager): s = m.get_binary() if code != 1: self._log( - ERROR, "unknown extended_data type %d; discarding" % code + ERROR, "unknown extended_data type {}; discarding".format(code) ) return if self.combine_stderr: @@ -1044,7 +1046,7 @@ class Channel(ClosingContextManager): self.lock.acquire() try: if self.ultra_debug: - self._log(DEBUG, "window up %d" % nbytes) + self._log(DEBUG, "window up {}".format(nbytes)) self.out_window_size += nbytes self.out_buffer_cv.notifyAll() finally: @@ -1131,7 +1133,7 @@ class Channel(ClosingContextManager): else: ok = server.check_channel_forward_agent_request(self) else: - self._log(DEBUG, 'Unhandled channel request "%s"' % key) + self._log(DEBUG, 'Unhandled channel request "{}"'.format(key)) ok = False if want_reply: m = Message() @@ -1153,7 +1155,7 @@ class Channel(ClosingContextManager): self._pipe.set_forever() finally: self.lock.release() - self._log(DEBUG, "EOF received (%s)", self._name) + self._log(DEBUG, "EOF received ({})".format(self._name)) def _handle_close(self, m): self.lock.acquire() @@ -1225,7 +1227,7 @@ class Channel(ClosingContextManager): m.add_byte(cMSG_CHANNEL_EOF) m.add_int(self.remote_chanid) self.eof_sent = True - self._log(DEBUG, "EOF sent (%s)", self._name) + self._log(DEBUG, "EOF sent ({})".format(self._name)) return m def _close_internal(self): @@ -1259,12 +1261,14 @@ class Channel(ClosingContextManager): if self.closed or self.eof_received or not self.active: return 0 if self.ultra_debug: - self._log(DEBUG, "addwindow %d" % n) + self._log(DEBUG, "addwindow {}".format(n)) self.in_window_sofar += n if self.in_window_sofar <= self.in_window_threshold: return 0 if self.ultra_debug: - self._log(DEBUG, "addwindow send %d" % self.in_window_sofar) + self._log( + DEBUG, "addwindow send {}".format(self.in_window_sofar) + ) out = self.in_window_sofar self.in_window_sofar = 0 return out @@ -1307,7 +1311,7 @@ class Channel(ClosingContextManager): size = self.out_max_packet_size - 64 self.out_window_size -= size if self.ultra_debug: - self._log(DEBUG, "window down to %d" % self.out_window_size) + self._log(DEBUG, "window down to {}".format(self.out_window_size)) return size diff --git a/paramiko/client.py b/paramiko/client.py index 49a9a301..2538d582 100644 --- a/paramiko/client.py +++ b/paramiko/client.py @@ -146,7 +146,9 @@ class SSHClient(ClosingContextManager): for hostname, keys in self._host_keys.items(): for keytype, key in keys.items(): f.write( - "%s %s %s\n" % (hostname, keytype, key.get_base64()) + "{} {} {}\n".format( + hostname, keytype, key.get_base64() + ) ) def get_host_keys(self): @@ -233,6 +235,7 @@ class SSHClient(ClosingContextManager): banner_timeout=None, auth_timeout=None, gss_trust_dns=True, + passphrase=None, ): """ Connect to an SSH server and authenticate to it. The server's host key @@ -273,7 +276,10 @@ class SSHClient(ClosingContextManager): the username to authenticate as (defaults to the current local username) :param str password: - a password to use for authentication or for unlocking a private key + Used for password authentication; is also used for private key + decryption if ``passphrase`` is not given. + :param str passphrase: + Used for decrypting private keys. :param .PKey pkey: an optional private key to use for authentication :param str key_filename: the filename, or list of filenames, of optional private key(s) @@ -319,6 +325,8 @@ class SSHClient(ClosingContextManager): ``gss_deleg_creds`` and ``gss_host`` arguments. .. versionchanged:: 2.3 Added the ``gss_trust_dns`` argument. + .. versionchanged:: 2.4 + Added the ``passphrase`` argument. """ if not sock: errors = {} @@ -374,7 +382,7 @@ class SSHClient(ClosingContextManager): if port == SSH_PORT: server_hostkey_name = hostname else: - server_hostkey_name = "[%s]:%d" % (hostname, port) + server_hostkey_name = "[{}]:{}".format(hostname, port) our_server_keys = None our_server_keys = self._system_host_keys.get(server_hostkey_name) @@ -426,6 +434,7 @@ class SSHClient(ClosingContextManager): gss_kex, gss_deleg_creds, t.gss_host, + passphrase, ) def close(self): @@ -563,14 +572,14 @@ class SSHClient(ClosingContextManager): # TODO: change this to 'Loading' instead of 'Trying' sometime; probably # when #387 is released, since this is a critical log message users are # likely testing/filtering for (bah.) - msg = "Trying discovered key {0} in {1}".format( + msg = "Trying discovered key {} in {}".format( hexlify(key.get_fingerprint()), key_path ) self._log(DEBUG, msg) # Attempt to load cert if it exists. if os.path.isfile(cert_path): key.load_certificate(cert_path) - self._log(DEBUG, "Adding public certificate {0}".format(cert_path)) + self._log(DEBUG, "Adding public certificate {}".format(cert_path)) return key def _auth( @@ -585,6 +594,7 @@ class SSHClient(ClosingContextManager): gss_kex, gss_deleg_creds, gss_host, + passphrase, ): """ Try, in order: @@ -595,13 +605,16 @@ class SSHClient(ClosingContextManager): (if allowed). - Plain username/password auth, if a password was given. - (The password might be needed to unlock a private key, or for - two-factor authentication [for which it is required].) + (The password might be needed to unlock a private key [if 'passphrase' + isn't also given], or for two-factor authentication [for which it is + required].) """ saved_exception = None two_factor = False allowed_types = set() - two_factor_types = set(["keyboard-interactive", "password"]) + two_factor_types = {"keyboard-interactive", "password"} + if passphrase is None and password is not None: + passphrase = password # If GSS-API support and GSS-PI Key Exchange was performed, we attempt # authentication with gssapi-keyex. @@ -628,7 +641,9 @@ class SSHClient(ClosingContextManager): try: self._log( DEBUG, - "Trying SSH key %s" % hexlify(pkey.get_fingerprint()), + "Trying SSH key {}".format( + hexlify(pkey.get_fingerprint()) + ), ) allowed_types = set( self._transport.auth_publickey(username, pkey) @@ -644,7 +659,7 @@ class SSHClient(ClosingContextManager): for pkey_class in (RSAKey, DSSKey, ECDSAKey, Ed25519Key): try: key = self._key_from_filepath( - key_filename, pkey_class, password + key_filename, pkey_class, passphrase ) allowed_types = set( self._transport.auth_publickey(username, key) @@ -662,11 +677,8 @@ class SSHClient(ClosingContextManager): for key in self._agent.get_keys(): try: - self._log( - DEBUG, - "Trying SSH agent key %s" - % hexlify(key.get_fingerprint()), - ) + id_ = hexlify(key.get_fingerprint()) + self._log(DEBUG, "Trying SSH agent key {}".format(id_)) # for 2-factor auth a successfully auth'd key password # will return an allowed 2fac auth method allowed_types = set( @@ -691,7 +703,7 @@ class SSHClient(ClosingContextManager): # ~/ssh/ is for windows for directory in [".ssh", "ssh"]: full_path = os.path.expanduser( - "~/%s/id_%s" % (directory, name) + "~/{}/id_{}".format(directory, name) ) if os.path.isfile(full_path): # TODO: only do this append if below did not run @@ -705,7 +717,7 @@ class SSHClient(ClosingContextManager): for pkey_class, filename in keyfiles: try: key = self._key_from_filepath( - filename, pkey_class, password + filename, pkey_class, passphrase ) # for 2-factor auth a successfully auth'd key will result # in ['password'] @@ -774,8 +786,9 @@ class AutoAddPolicy(MissingHostKeyPolicy): client.save_host_keys(client._host_keys_filename) client._log( DEBUG, - "Adding %s host key for %s: %s" - % (key.get_name(), hostname, hexlify(key.get_fingerprint())), + "Adding {} host key for {}: {}".format( + key.get_name(), hostname, hexlify(key.get_fingerprint()) + ), ) @@ -788,10 +801,13 @@ class RejectPolicy(MissingHostKeyPolicy): def missing_host_key(self, client, hostname, key): client._log( DEBUG, - "Rejecting %s host key for %s: %s" - % (key.get_name(), hostname, hexlify(key.get_fingerprint())), + "Rejecting {} host key for {}: {}".format( + key.get_name(), hostname, hexlify(key.get_fingerprint()) + ), + ) + raise SSHException( + "Server {!r} not found in known_hosts".format(hostname) ) - raise SSHException("Server %r not found in known_hosts" % hostname) class WarningPolicy(MissingHostKeyPolicy): @@ -802,6 +818,7 @@ class WarningPolicy(MissingHostKeyPolicy): def missing_host_key(self, client, hostname, key): warnings.warn( - "Unknown %s host key for %s: %s" - % (key.get_name(), hostname, hexlify(key.get_fingerprint())) + "Unknown {} host key for {}: {}".format( + key.get_name(), hostname, hexlify(key.get_fingerprint()) + ) ) diff --git a/paramiko/config.py b/paramiko/config.py index f7941e0d..21c9dab8 100644 --- a/paramiko/config.py +++ b/paramiko/config.py @@ -65,7 +65,7 @@ class SSHConfig(object): match = re.match(self.SETTINGS_REGEX, line) if not match: - raise Exception("Unparsable line %s" % line) + raise Exception("Unparsable line {}".format(line)) key = match.group(1).lower() value = match.group(2) @@ -235,7 +235,7 @@ class SSHConfig(object): try: return shlex.split(host) except ValueError: - raise Exception("Unparsable host %s" % host) + raise Exception("Unparsable host {}".format(host)) class LazyFqdn(object): diff --git a/paramiko/ecdsakey.py b/paramiko/ecdsakey.py index ab9ec15f..b73a969e 100644 --- a/paramiko/ecdsakey.py +++ b/paramiko/ecdsakey.py @@ -147,14 +147,16 @@ class ECDSAKey(PKey): ) key_types = self._ECDSA_CURVES.get_key_format_identifier_list() cert_types = [ - "{0}-cert-v01@openssh.com".format(x) for x in key_types + "{}-cert-v01@openssh.com".format(x) for x in key_types ] self._check_type_and_load_cert( msg=msg, key_type=key_types, cert_type=cert_types ) curvename = msg.get_text() if curvename != self.ecdsa_curve.nist_name: - raise SSHException("Can't handle curve of type %s" % curvename) + raise SSHException( + "Can't handle curve of type {}".format(curvename) + ) pointinfo = msg.get_binary() try: @@ -264,7 +266,7 @@ class ECDSAKey(PKey): if bits is not None: curve = cls._ECDSA_CURVES.get_by_key_length(bits) if curve is None: - raise ValueError("Unsupported key length: %d" % bits) + raise ValueError("Unsupported key length: {:d}".format(bits)) curve = curve.curve_class() private_key = ec.generate_private_key(curve, backend=default_backend()) diff --git a/paramiko/file.py b/paramiko/file.py index a6933e02..9e9f6eb8 100644 --- a/paramiko/file.py +++ b/paramiko/file.py @@ -341,7 +341,7 @@ class BufferedFile(ClosingContextManager): after rounding up to an internal buffer size) are read. :param int sizehint: desired maximum number of bytes to read. - :returns: `list` of lines read from the file. + :returns: list of lines read from the file. """ lines = [] byte_count = 0 diff --git a/paramiko/hostkeys.py b/paramiko/hostkeys.py index 1cdddb62..f31b8819 100644 --- a/paramiko/hostkeys.py +++ b/paramiko/hostkeys.py @@ -302,7 +302,7 @@ class HostKeys(MutableMapping): salt = decodebytes(b(salt)) assert len(salt) == sha1().digest_size hmac = HMAC(salt, b(hostname), sha1).digest() - hostkey = "|1|%s|%s" % (u(encodebytes(salt)), u(encodebytes(hmac))) + hostkey = "|1|{}|{}".format(u(encodebytes(salt)), u(encodebytes(hmac))) return hostkey.replace("\n", "") @@ -340,10 +340,8 @@ class HostKeyEntry: fields = line.split(" ") if len(fields) < 3: # Bad number of fields - log.info( - "Not enough fields found in known_hosts in line %s (%r)" - % (lineno, line) - ) + msg = "Not enough fields found in known_hosts in line {} ({!r})" + log.info(msg.format(lineno, line)) return None fields = fields[:3] @@ -363,7 +361,7 @@ class HostKeyEntry: elif keytype == "ssh-ed25519": key = Ed25519Key(data=decodebytes(key)) else: - log.info("Unable to handle key of type %s" % (keytype,)) + log.info("Unable to handle key of type {}".format(keytype)) return None except binascii.Error as e: @@ -378,7 +376,7 @@ class HostKeyEntry: included. """ if self.valid: - return "%s %s %s\n" % ( + return "{} {} {}\n".format( ",".join(self.hostnames), self.key.get_name(), self.key.get_base64(), @@ -386,4 +384,4 @@ class HostKeyEntry: return None def __repr__(self): - return "<HostKeyEntry %r: %r>" % (self.hostnames, self.key) + return "<HostKeyEntry {!r}: {!r}>".format(self.hostnames, self.key) diff --git a/paramiko/kex_ecdh_nist.py b/paramiko/kex_ecdh_nist.py index 496805ab..1d87442a 100644 --- a/paramiko/kex_ecdh_nist.py +++ b/paramiko/kex_ecdh_nist.py @@ -45,7 +45,9 @@ class KexNistp256: return self._parse_kexecdh_init(m) elif not self.transport.server_mode and (ptype == _MSG_KEXECDH_REPLY): return self._parse_kexecdh_reply(m) - raise SSHException("KexECDH asked to handle packet type %d" % ptype) + raise SSHException( + "KexECDH asked to handle packet type {:d}".format(ptype) + ) def _generate_key_pair(self): self.P = ec.generate_private_key(self.curve, default_backend()) diff --git a/paramiko/kex_gex.py b/paramiko/kex_gex.py index 371db488..fb8f01fd 100644 --- a/paramiko/kex_gex.py +++ b/paramiko/kex_gex.py @@ -101,9 +101,8 @@ class KexGex(object): return self._parse_kexdh_gex_reply(m) elif ptype == _MSG_KEXDH_GEX_REQUEST_OLD: return self._parse_kexdh_gex_request_old(m) - raise SSHException( - "KexGex %s asked to handle packet type %d" % self.name, ptype - ) + msg = "KexGex {} asked to handle packet type {:d}" + raise SSHException(msg.format(self.name, ptype)) # ...internals... @@ -151,8 +150,9 @@ class KexGex(object): raise SSHException("Can't do server-side gex with no modulus pack") self.transport._log( DEBUG, - "Picking p (%d <= %d <= %d bits)" - % (minbits, preferredbits, maxbits), + "Picking p ({} <= {} <= {} bits)".format( + minbits, preferredbits, maxbits + ), ) self.g, self.p = pack.get_modulus(minbits, preferredbits, maxbits) m = Message() @@ -176,7 +176,7 @@ class KexGex(object): if pack is None: raise SSHException("Can't do server-side gex with no modulus pack") self.transport._log( - DEBUG, "Picking p (~ %d bits)" % (self.preferred_bits,) + DEBUG, "Picking p (~ {} bits)".format(self.preferred_bits) ) self.g, self.p = pack.get_modulus( self.min_bits, self.preferred_bits, self.max_bits @@ -197,9 +197,9 @@ class KexGex(object): if (bitlen < 1024) or (bitlen > 8192): raise SSHException( "Server-generated gex p (don't ask) is out of range " - "(%d bits)" % bitlen + "({} bits)".format(bitlen) ) - self.transport._log(DEBUG, "Got server p (%d bits)" % bitlen) + self.transport._log(DEBUG, "Got server p ({} bits)".format(bitlen)) self._generate_x() # now compute e = g^x mod p self.e = pow(self.g, self.x, self.p) diff --git a/paramiko/kex_group1.py b/paramiko/kex_group1.py index e6cbc4c5..904835d7 100644 --- a/paramiko/kex_group1.py +++ b/paramiko/kex_group1.py @@ -73,7 +73,8 @@ class KexGroup1(object): return self._parse_kexdh_init(m) elif not self.transport.server_mode and (ptype == _MSG_KEXDH_REPLY): return self._parse_kexdh_reply(m) - raise SSHException("KexGroup1 asked to handle packet type %d" % ptype) + msg = "KexGroup1 asked to handle packet type {:d}" + raise SSHException(msg.format(ptype)) # ...internals... diff --git a/paramiko/kex_gss.py b/paramiko/kex_gss.py index d76bb2dd..f83a2dc4 100644 --- a/paramiko/kex_gss.py +++ b/paramiko/kex_gss.py @@ -131,9 +131,8 @@ class KexGSSGroup1(object): return self._parse_kexgss_complete(m) elif ptype == MSG_KEXGSS_ERROR: return self._parse_kexgss_error(m) - raise SSHException( - "GSS KexGroup1 asked to handle packet type %d" % ptype - ) + msg = "GSS KexGroup1 asked to handle packet type {:d}" + raise SSHException(msg.format(ptype)) # ## internals... @@ -306,9 +305,14 @@ class KexGSSGroup1(object): err_msg = m.get_string() m.get_string() # we don't care about the language! raise SSHException( - "GSS-API Error:\nMajor Status: %s\nMinor Status: %s\ - \nError Message: %s\n" - ) % (str(maj_status), str(min_status), err_msg) + """GSS-API Error: +Major Status: {} +Minor Status: {} +Error Message: {} +""".format( + maj_status, min_status, err_msg + ) + ) class KexGSSGroup14(KexGSSGroup1): @@ -386,7 +390,8 @@ class KexGSSGex(object): return self._parse_kexgss_complete(m) elif ptype == MSG_KEXGSS_ERROR: return self._parse_kexgss_error(m) - raise SSHException("KexGex asked to handle packet type %d" % ptype) + msg = "KexGex asked to handle packet type {:d}" + raise SSHException(msg.format(ptype)) # ## internals... @@ -440,8 +445,9 @@ class KexGSSGex(object): raise SSHException("Can't do server-side gex with no modulus pack") self.transport._log( DEBUG, # noqa - "Picking p (%d <= %d <= %d bits)" - % (minbits, preferredbits, maxbits), + "Picking p ({} <= {} <= {} bits)".format( + minbits, preferredbits, maxbits + ), ) self.g, self.p = pack.get_modulus(minbits, preferredbits, maxbits) m = Message() @@ -464,9 +470,11 @@ class KexGSSGex(object): if (bitlen < 1024) or (bitlen > 8192): raise SSHException( "Server-generated gex p (don't ask) is out of range " - "(%d bits)" % bitlen + "({} bits)".format(bitlen) ) - self.transport._log(DEBUG, "Got server p (%d bits)" % bitlen) # noqa + self.transport._log( + DEBUG, "Got server p ({} bits)".format(bitlen) + ) # noqa self._generate_x() # now compute e = g^x mod p self.e = pow(self.g, self.x, self.p) @@ -645,9 +653,14 @@ class KexGSSGex(object): err_msg = m.get_string() m.get_string() # we don't care about the language (lang_tag)! raise SSHException( - "GSS-API Error:\nMajor Status: %s\nMinor Status: %s\ - \nError Message: %s\n" - ) % (str(maj_status), str(min_status), err_msg) + """GSS-API Error: +Major Status: {} +Minor Status: {} +Error Message: {} +""".format( + maj_status, min_status, err_msg + ) + ) class NullHostKey(object): diff --git a/paramiko/message.py b/paramiko/message.py index 869ac6c6..dead3508 100644 --- a/paramiko/message.py +++ b/paramiko/message.py @@ -187,7 +187,7 @@ class Message(object): def get_list(self): """ - Fetch a `list` of `strings <str>` from the stream. + Fetch a list of `strings <str>` from the stream. These are trivially encoded as comma-separated values in a string. """ @@ -281,7 +281,7 @@ class Message(object): a single string of values separated by commas. (Yes, really, that's how SSH2 does it.) - :param list l: list of strings to add + :param l: list of strings to add """ self.add_string(",".join(l)) return self diff --git a/paramiko/packet.py b/paramiko/packet.py index b538f3ba..cb46835e 100644 --- a/paramiko/packet.py +++ b/paramiko/packet.py @@ -382,7 +382,7 @@ class Packetizer(object): if cmd in MSG_NAMES: cmd_name = MSG_NAMES[cmd] else: - cmd_name = "$%x" % cmd + cmd_name = "${:x}".format(cmd) orig_len = len(data) self.__write_lock.acquire() try: @@ -392,7 +392,7 @@ class Packetizer(object): if self.__dump_packets: self._log( DEBUG, - "Write packet <%s>, length %d" % (cmd_name, orig_len), + "Write packet <{}>, length {}".format(cmd_name, orig_len), ) self._log(DEBUG, util.format_binary(packet, "OUT: ")) if self.__block_engine_out is not None: @@ -420,10 +420,9 @@ class Packetizer(object): ) if sent_too_much and not self.__need_rekey: # only ask once for rekeying + msg = "Rekeying (hit {} packets, {} bytes sent)" self._log( - DEBUG, - "Rekeying (hit %d packets, %d bytes sent)" - % (self.__sent_packets, self.__sent_bytes), + DEBUG, msg.format(self.__sent_packets, self.__sent_bytes) ) self.__received_bytes_overflow = 0 self.__received_packets_overflow = 0 @@ -476,7 +475,9 @@ class Packetizer(object): if self.__dump_packets: self._log( DEBUG, - "Got payload (%d bytes, %d padding)" % (packet_size, padding), + "Got payload ({} bytes, {} padding)".format( + packet_size, padding + ), ) if self.__compress_engine_in is not None: @@ -508,10 +509,10 @@ class Packetizer(object): self.__received_bytes >= self.REKEY_BYTES ): # only ask once for rekeying + err = "Rekeying (hit {} packets, {} bytes received)" self._log( DEBUG, - "Rekeying (hit %d packets, %d bytes received)" - % (self.__received_packets, self.__received_bytes), + err.format(self.__received_packets, self.__received_bytes), ) self.__received_bytes_overflow = 0 self.__received_packets_overflow = 0 @@ -521,10 +522,11 @@ class Packetizer(object): if cmd in MSG_NAMES: cmd_name = MSG_NAMES[cmd] else: - cmd_name = "$%x" % cmd + cmd_name = "${:x}".format(cmd) if self.__dump_packets: self._log( - DEBUG, "Read packet <%s>, length %d" % (cmd_name, len(payload)) + DEBUG, + "Read packet <{}>, length {}".format(cmd_name, len(payload)), ) return cmd, msg diff --git a/paramiko/pkey.py b/paramiko/pkey.py index a6e7800a..fa014800 100644 --- a/paramiko/pkey.py +++ b/paramiko/pkey.py @@ -310,9 +310,10 @@ class PKey(object): # unencryped: done return data # encrypted keyfile: will need a password - if headers["proc-type"] != "4,ENCRYPTED": + proc_type = headers["proc-type"] + if proc_type != "4,ENCRYPTED": raise SSHException( - 'Unknown private key structure "%s"' % headers["proc-type"] + 'Unknown private key structure "{}"'.format(proc_type) ) try: encryption_type, saltstr = headers["dek-info"].split(",") @@ -320,7 +321,7 @@ class PKey(object): raise SSHException("Can't parse DEK-info in private key file") if encryption_type not in self._CIPHER_TABLE: raise SSHException( - 'Unknown private key cipher "%s"' % encryption_type + 'Unknown private key cipher "{}"'.format(encryption_type) ) # if no password was passed in, # raise an exception pointing out that we need one @@ -411,7 +412,7 @@ class PKey(object): # (requires going back into per-type subclasses.) msg.get_string() else: - err = "Invalid key (class: {0}, data type: {1}" + err = "Invalid key (class: {}, data type: {}" raise SSHException(err.format(self.__class__.__name__, type_)) def load_certificate(self, value): @@ -441,7 +442,7 @@ class PKey(object): constructor = "from_string" blob = getattr(PublicBlob, constructor)(value) if not blob.key_type.startswith(self.get_name()): - err = "PublicBlob type {0} incompatible with key type {1}" + err = "PublicBlob type {} incompatible with key type {}" raise ValueError(err.format(blob.key_type, self.get_name())) self.public_blob = blob @@ -493,7 +494,7 @@ class PublicBlob(object): """ fields = string.split(None, 2) if len(fields) < 2: - msg = "Not enough fields for public blob: {0}" + msg = "Not enough fields for public blob: {}" raise ValueError(msg.format(fields)) key_type = fields[0] key_blob = decodebytes(b(fields[1])) @@ -506,8 +507,10 @@ class PublicBlob(object): m = Message(key_blob) blob_type = m.get_text() if blob_type != key_type: - msg = "Invalid PublicBlob contents: key type={0!r}, but blob type={1!r}" # noqa - raise ValueError(msg.format(key_type, blob_type)) + deets = "key type={!r}, but blob type={!r}".format( + key_type, blob_type + ) + raise ValueError("Invalid PublicBlob contents: {}".format(deets)) # All good? All good. return cls(type_=key_type, blob=key_blob, comment=comment) @@ -523,9 +526,9 @@ class PublicBlob(object): return cls(type_=type_, blob=message.asbytes()) def __str__(self): - ret = "{0} public key/certificate".format(self.key_type) + ret = "{} public key/certificate".format(self.key_type) if self.comment: - ret += "- {0}".format(self.comment) + ret += "- {}".format(self.comment) return ret def __eq__(self, other): diff --git a/paramiko/primes.py b/paramiko/primes.py index 22e536b8..8dff7683 100644 --- a/paramiko/primes.py +++ b/paramiko/primes.py @@ -61,9 +61,15 @@ class ModulusPack(object): self.discarded = [] def _parse_modulus(self, line): - timestamp, mod_type, tests, tries, size, generator, modulus = ( - line.split() - ) + ( + timestamp, + mod_type, + tests, + tries, + size, + generator, + modulus, + ) = line.split() mod_type = int(mod_type) tests = int(tests) tries = int(tries) @@ -93,7 +99,7 @@ class ModulusPack(object): bl = util.bit_length(modulus) if (bl != size) and (bl != size + 1): self.discarded.append( - (modulus, "incorrectly reported bit length %d" % size) + (modulus, "incorrectly reported bit length {}".format(size)) ) return if bl not in self.pack: diff --git a/paramiko/py3compat.py b/paramiko/py3compat.py index 314c557a..cbe20ca6 100644 --- a/paramiko/py3compat.py +++ b/paramiko/py3compat.py @@ -62,7 +62,7 @@ if PY2: elif isinstance(s, buffer): # NOQA return s else: - raise TypeError("Expected unicode or bytes, got %r" % s) + raise TypeError("Expected unicode or bytes, got {!r}".format(s)) def u(s, encoding="utf8"): # NOQA """cast bytes or unicode to unicode""" @@ -73,7 +73,7 @@ if PY2: elif isinstance(s, buffer): # NOQA return s.decode(encoding) else: - raise TypeError("Expected unicode or bytes, got %r" % s) + raise TypeError("Expected unicode or bytes, got {!r}".format(s)) def b2s(s): return s @@ -148,7 +148,7 @@ else: elif isinstance(s, str): return s.encode(encoding) else: - raise TypeError("Expected unicode or bytes, got %r" % s) + raise TypeError("Expected unicode or bytes, got {!r}".format(s)) def u(s, encoding="utf8"): """cast bytes or unicode to unicode""" @@ -157,7 +157,7 @@ else: elif isinstance(s, str): return s else: - raise TypeError("Expected unicode or bytes, got %r" % s) + raise TypeError("Expected unicode or bytes, got {!r}".format(s)) def b2s(s): return s.decode() if isinstance(s, bytes) else s diff --git a/paramiko/server.py b/paramiko/server.py index 3eb1a170..2fe9cc19 100644 --- a/paramiko/server.py +++ b/paramiko/server.py @@ -226,7 +226,7 @@ class ServerInterface(object): The default implementation always returns ``AUTH_FAILED``. - :param list responses: list of `str` responses from the client + :param responses: list of `str` responses from the client :return: ``AUTH_FAILED`` if the authentication fails; ``AUTH_SUCCESSFUL`` if it succeeds; ``AUTH_PARTIALLY_SUCCESSFUL`` if the interactive auth @@ -678,13 +678,13 @@ class SubsystemHandler(threading.Thread): def _run(self): try: self.__transport._log( - DEBUG, "Starting handler for subsystem %s" % self.__name + DEBUG, "Starting handler for subsystem {}".format(self.__name) ) self.start_subsystem(self.__name, self.__transport, self.__channel) except Exception as e: self.__transport._log( ERROR, - 'Exception in subsystem handler for "{0}": {1}'.format( + 'Exception in subsystem handler for "{}": {}'.format( self.__name, e ), ) diff --git a/paramiko/sftp_attr.py b/paramiko/sftp_attr.py index 5ef8ff14..f16ac746 100644 --- a/paramiko/sftp_attr.py +++ b/paramiko/sftp_attr.py @@ -82,7 +82,7 @@ class SFTPAttributes(object): return attr def __repr__(self): - return "<SFTPAttributes: %s>" % self._debug_str() + return "<SFTPAttributes: {}>".format(self._debug_str()) # ...internals... @classmethod @@ -146,15 +146,15 @@ class SFTPAttributes(object): def _debug_str(self): out = "[ " if self.st_size is not None: - out += "size=%d " % self.st_size + out += "size={} ".format(self.st_size) if (self.st_uid is not None) and (self.st_gid is not None): - out += "uid=%d gid=%d " % (self.st_uid, self.st_gid) + out += "uid={} gid={} ".format(self.st_uid, self.st_gid) if self.st_mode is not None: out += "mode=" + oct(self.st_mode) + " " if (self.st_atime is not None) and (self.st_mtime is not None): - out += "atime=%d mtime=%d " % (self.st_atime, self.st_mtime) + out += "atime={} mtime={} ".format(self.st_atime, self.st_mtime) for k, v in self.attr.items(): - out += '"%s"=%r ' % (str(k), v) + out += '"{}"={!r} '.format(str(k), v) out += "]" return out @@ -227,6 +227,9 @@ class SFTPAttributes(object): if size is None: size = 0 + # TODO: not sure this actually worked as expected beforehand, leaving + # it untouched for the time being, re: .format() upgrade, until someone + # has time to doublecheck return "%s 1 %-8d %-8d %8d %-12s %s" % ( ks, uid, diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py index 425aa87d..1a9147fc 100644 --- a/paramiko/sftp_client.py +++ b/paramiko/sftp_client.py @@ -131,7 +131,10 @@ class SFTPClient(BaseSFTP, ClosingContextManager): except EOFError: raise SSHException("EOF during negotiation") self._log( - INFO, "Opened sftp connection (server version %d)" % server_version + INFO, + "Opened sftp connection (server version {})".format( + server_version + ), ) @classmethod @@ -171,6 +174,8 @@ class SFTPClient(BaseSFTP, ClosingContextManager): for m in msg: self._log(level, m, *args) else: + # NOTE: these bits MUST continue using %-style format junk because + # logging.Logger.log() explicitly requires it. Grump. # escape '%' in msg (they could come from file or directory names) # before logging msg = msg.replace("%", "%%") @@ -230,7 +235,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): .. versionadded:: 1.2 """ path = self._adjust_cwd(path) - self._log(DEBUG, "listdir(%r)" % path) + self._log(DEBUG, "listdir({!r})".format(path)) t, msg = self._request(CMD_OPENDIR, path) if t != CMD_HANDLE: raise SFTPError("Expected handle") @@ -269,7 +274,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): .. versionadded:: 1.15 """ path = self._adjust_cwd(path) - self._log(DEBUG, "listdir(%r)" % path) + self._log(DEBUG, "listdir({!r})".format(path)) t, msg = self._request(CMD_OPENDIR, path) if t != CMD_HANDLE: @@ -351,7 +356,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): :raises: ``IOError`` -- if the file could not be opened. """ filename = self._adjust_cwd(filename) - self._log(DEBUG, "open(%r, %r)" % (filename, mode)) + self._log(DEBUG, "open({!r}, {!r})".format(filename, mode)) imode = 0 if ("r" in mode) or ("+" in mode): imode |= SFTP_FLAG_READ @@ -369,7 +374,10 @@ class SFTPClient(BaseSFTP, ClosingContextManager): raise SFTPError("Expected handle") handle = msg.get_binary() self._log( - DEBUG, "open(%r, %r) -> %s" % (filename, mode, u(hexlify(handle))) + DEBUG, + "open({!r}, {!r}) -> {}".format( + filename, mode, u(hexlify(handle)) + ), ) return SFTPFile(self, handle, mode, bufsize) @@ -386,7 +394,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): :raises: ``IOError`` -- if the path refers to a folder (directory) """ path = self._adjust_cwd(path) - self._log(DEBUG, "remove(%r)" % path) + self._log(DEBUG, "remove({!r})".format(path)) self._request(CMD_REMOVE, path) unlink = remove @@ -411,7 +419,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): """ oldpath = self._adjust_cwd(oldpath) newpath = self._adjust_cwd(newpath) - self._log(DEBUG, "rename(%r, %r)" % (oldpath, newpath)) + self._log(DEBUG, "rename({!r}, {!r})".format(oldpath, newpath)) self._request(CMD_RENAME, oldpath, newpath) def posix_rename(self, oldpath, newpath): @@ -431,7 +439,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): """ oldpath = self._adjust_cwd(oldpath) newpath = self._adjust_cwd(newpath) - self._log(DEBUG, "posix_rename(%r, %r)" % (oldpath, newpath)) + self._log(DEBUG, "posix_rename({!r}, {!r})".format(oldpath, newpath)) self._request( CMD_EXTENDED, "posix-rename@openssh.com", oldpath, newpath ) @@ -446,7 +454,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): :param int mode: permissions (posix-style) for the newly-created folder """ path = self._adjust_cwd(path) - self._log(DEBUG, "mkdir(%r, %r)" % (path, mode)) + self._log(DEBUG, "mkdir({!r}, {!r})".format(path, mode)) attr = SFTPAttributes() attr.st_mode = mode self._request(CMD_MKDIR, path, attr) @@ -458,7 +466,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): :param str path: name of the folder to remove """ path = self._adjust_cwd(path) - self._log(DEBUG, "rmdir(%r)" % path) + self._log(DEBUG, "rmdir({!r})".format(path)) self._request(CMD_RMDIR, path) def stat(self, path): @@ -481,7 +489,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): file """ path = self._adjust_cwd(path) - self._log(DEBUG, "stat(%r)" % path) + self._log(DEBUG, "stat({!r})".format(path)) t, msg = self._request(CMD_STAT, path) if t != CMD_ATTRS: raise SFTPError("Expected attributes") @@ -499,7 +507,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): file """ path = self._adjust_cwd(path) - self._log(DEBUG, "lstat(%r)" % path) + self._log(DEBUG, "lstat({!r})".format(path)) t, msg = self._request(CMD_LSTAT, path) if t != CMD_ATTRS: raise SFTPError("Expected attributes") @@ -513,7 +521,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): :param str dest: path of the newly created symlink """ dest = self._adjust_cwd(dest) - self._log(DEBUG, "symlink(%r, %r)" % (source, dest)) + self._log(DEBUG, "symlink({!r}, {!r})".format(source, dest)) source = bytestring(source) self._request(CMD_SYMLINK, source, dest) @@ -527,7 +535,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): :param int mode: new permissions """ path = self._adjust_cwd(path) - self._log(DEBUG, "chmod(%r, %r)" % (path, mode)) + self._log(DEBUG, "chmod({!r}, {!r})".format(path, mode)) attr = SFTPAttributes() attr.st_mode = mode self._request(CMD_SETSTAT, path, attr) @@ -544,7 +552,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): :param int gid: new group id """ path = self._adjust_cwd(path) - self._log(DEBUG, "chown(%r, %r, %r)" % (path, uid, gid)) + self._log(DEBUG, "chown({!r}, {!r}, {!r})".format(path, uid, gid)) attr = SFTPAttributes() attr.st_uid, attr.st_gid = uid, gid self._request(CMD_SETSTAT, path, attr) @@ -566,7 +574,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): path = self._adjust_cwd(path) if times is None: times = (time.time(), time.time()) - self._log(DEBUG, "utime(%r, %r)" % (path, times)) + self._log(DEBUG, "utime({!r}, {!r})".format(path, times)) attr = SFTPAttributes() attr.st_atime, attr.st_mtime = times self._request(CMD_SETSTAT, path, attr) @@ -581,7 +589,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): :param int size: the new size of the file """ path = self._adjust_cwd(path) - self._log(DEBUG, "truncate(%r, %r)" % (path, size)) + self._log(DEBUG, "truncate({!r}, {!r})".format(path, size)) attr = SFTPAttributes() attr.st_size = size self._request(CMD_SETSTAT, path, attr) @@ -596,7 +604,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): :return: target path, as a `str` """ path = self._adjust_cwd(path) - self._log(DEBUG, "readlink(%r)" % path) + self._log(DEBUG, "readlink({!r})".format(path)) t, msg = self._request(CMD_READLINK, path) if t != CMD_NAME: raise SFTPError("Expected name response") @@ -604,7 +612,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): if count == 0: return None if count != 1: - raise SFTPError("Readlink returned %d results" % count) + raise SFTPError("Readlink returned {} results".format(count)) return _to_unicode(msg.get_string()) def normalize(self, path): @@ -620,13 +628,13 @@ class SFTPClient(BaseSFTP, ClosingContextManager): :raises: ``IOError`` -- if the path can't be resolved on the server """ path = self._adjust_cwd(path) - self._log(DEBUG, "normalize(%r)" % path) + self._log(DEBUG, "normalize({!r})".format(path)) t, msg = self._request(CMD_REALPATH, path) if t != CMD_NAME: raise SFTPError("Expected name response") count = msg.get_int() if count != 1: - raise SFTPError("Realpath returned %d results" % count) + raise SFTPError("Realpath returned {} results".format(count)) return msg.get_text() def chdir(self, path=None): @@ -649,9 +657,8 @@ class SFTPClient(BaseSFTP, ClosingContextManager): self._cwd = None return if not stat.S_ISDIR(self.stat(path).st_mode): - raise SFTPError( - errno.ENOTDIR, "%s: %s" % (os.strerror(errno.ENOTDIR), path) - ) + code = errno.ENOTDIR + raise SFTPError(code, "{}: {}".format(os.strerror(code), path)) self._cwd = b(self.normalize(path)) def getcwd(self): @@ -713,7 +720,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): s = self.stat(remotepath) if s.st_size != size: raise IOError( - "size mismatch in put! %d != %d" % (s.st_size, size) + "size mismatch in put! {} != {}".format(s.st_size, size) ) else: s = SFTPAttributes() @@ -796,7 +803,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): s = os.stat(localpath) if s.st_size != size: raise IOError( - "size mismatch in get! %d != %d" % (s.st_size, size) + "size mismatch in get! {} != {}".format(s.st_size, size) ) # ...internals... @@ -835,7 +842,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): try: t, data = self._read_packet() except EOFError as e: - raise SSHException("Server connection dropped: %s" % str(e)) + raise SSHException("Server connection dropped: {}".format(e)) msg = Message(data) num = msg.get_int() self._lock.acquire() @@ -843,7 +850,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): if num not in self._expecting: # might be response for a file that was closed before # responses came back - self._log(DEBUG, "Unexpected response #%d" % (num,)) + self._log(DEBUG, "Unexpected response #{}".format(num)) if waitfor is None: # just doing a single check break diff --git a/paramiko/sftp_file.py b/paramiko/sftp_file.py index 08003e43..0104d857 100644 --- a/paramiko/sftp_file.py +++ b/paramiko/sftp_file.py @@ -91,7 +91,7 @@ class SFTPFile(BufferedFile): # __del__.) if self._closed: return - self.sftp._log(DEBUG, "close(%s)" % u(hexlify(self.handle))) + self.sftp._log(DEBUG, "close({})".format(u(hexlify(self.handle)))) if self.pipelined: self.sftp._finish_responses(self) BufferedFile.close(self) @@ -293,7 +293,9 @@ class SFTPFile(BufferedFile): :param int mode: new permissions """ - self.sftp._log(DEBUG, "chmod(%s, %r)" % (hexlify(self.handle), mode)) + self.sftp._log( + DEBUG, "chmod({}, {!r})".format(hexlify(self.handle), mode) + ) attr = SFTPAttributes() attr.st_mode = mode self.sftp._request(CMD_FSETSTAT, self.handle, attr) @@ -309,7 +311,8 @@ class SFTPFile(BufferedFile): :param int gid: new group id """ self.sftp._log( - DEBUG, "chown(%s, %r, %r)" % (hexlify(self.handle), uid, gid) + DEBUG, + "chown({}, {!r}, {!r})".format(hexlify(self.handle), uid, gid), ) attr = SFTPAttributes() attr.st_uid, attr.st_gid = uid, gid @@ -330,7 +333,9 @@ class SFTPFile(BufferedFile): """ if times is None: times = (time.time(), time.time()) - self.sftp._log(DEBUG, "utime(%s, %r)" % (hexlify(self.handle), times)) + self.sftp._log( + DEBUG, "utime({}, {!r})".format(hexlify(self.handle), times) + ) attr = SFTPAttributes() attr.st_atime, attr.st_mtime = times self.sftp._request(CMD_FSETSTAT, self.handle, attr) @@ -344,7 +349,7 @@ class SFTPFile(BufferedFile): :param size: the new size of the file """ self.sftp._log( - DEBUG, "truncate(%s, %r)" % (hexlify(self.handle), size) + DEBUG, "truncate({}, {!r})".format(hexlify(self.handle), size) ) attr = SFTPAttributes() attr.st_size = size @@ -484,7 +489,9 @@ class SFTPFile(BufferedFile): .. versionadded:: 1.5.4 """ - self.sftp._log(DEBUG, "readv(%s, %r)" % (hexlify(self.handle), chunks)) + self.sftp._log( + DEBUG, "readv({}, {!r})".format(hexlify(self.handle), chunks) + ) read_chunks = [] for offset, size in chunks: diff --git a/paramiko/sftp_server.py b/paramiko/sftp_server.py index 5c23ea2b..8265df96 100644 --- a/paramiko/sftp_server.py +++ b/paramiko/sftp_server.py @@ -138,7 +138,7 @@ class SFTPServer(BaseSFTP, SubsystemHandler): def start_subsystem(self, name, transport, channel): self.sock = channel - self._log(DEBUG, "Started sftp server on channel %s" % repr(channel)) + self._log(DEBUG, "Started sftp server on channel {!r}".format(channel)) self._send_server_version() self.server.session_started() while True: @@ -238,9 +238,7 @@ class SFTPServer(BaseSFTP, SubsystemHandler): item._pack(msg) else: raise Exception( - "unknown type for {0!r} type {1!r}".format( - item, type(item) - ) + "unknown type for {!r} type {!r}".format(item, type(item)) ) self._send_packet(t, msg) @@ -249,7 +247,7 @@ class SFTPServer(BaseSFTP, SubsystemHandler): # must be error code self._send_status(request_number, handle) return - handle._set_name(b("hx%d" % self.next_handle)) + handle._set_name(b("hx{:d}".format(self.next_handle))) self.next_handle += 1 if folder: self.folder_table[handle._get_name()] = handle @@ -378,7 +376,7 @@ class SFTPServer(BaseSFTP, SubsystemHandler): return flags def _process(self, t, request_number, msg): - self._log(DEBUG, "Request: %s" % CMD_NAMES[t]) + self._log(DEBUG, "Request: {}".format(CMD_NAMES[t])) if t == CMD_OPEN: path = msg.get_text() flags = self._convert_pflags(msg.get_int()) diff --git a/paramiko/ssh_exception.py b/paramiko/ssh_exception.py index 4865288f..52bb23be 100644 --- a/paramiko/ssh_exception.py +++ b/paramiko/ssh_exception.py @@ -67,7 +67,7 @@ class BadAuthenticationType(AuthenticationException): self.args = (explanation, types) def __str__(self): - return "{0} (allowed_types={1!r})".format( + return "{} (allowed_types={!r})".format( SSHException.__str__(self), self.allowed_types ) @@ -115,7 +115,7 @@ class BadHostKeyException(SSHException): def __init__(self, hostname, got_key, expected_key): message = ( - "Host key for server {0} does not match: got {1}, expected {2}" + "Host key for server {} does not match: got {}, expected {}" ) # noqa message = message.format( hostname, got_key.get_base64(), expected_key.get_base64() @@ -139,8 +139,9 @@ class ProxyCommandFailure(SSHException): def __init__(self, command, error): SSHException.__init__( self, - '"ProxyCommand (%s)" returned non-zero exit status: %s' - % (command, error), + '"ProxyCommand ({})" returned non-zero exit status: {}'.format( + command, error + ), ) self.error = error # for unpickling diff --git a/paramiko/ssh_gss.py b/paramiko/ssh_gss.py index 025d9928..eb8826e0 100644 --- a/paramiko/ssh_gss.py +++ b/paramiko/ssh_gss.py @@ -296,9 +296,7 @@ class _SSH_GSSAPI(_SSH_GSSAuth): else: token = self._gss_ctxt.step(recv_token) except gssapi.GSSException: - message = "{0} Target: {1}".format( - sys.exc_info()[1], self._gss_host - ) + message = "{} Target: {}".format(sys.exc_info()[1], self._gss_host) raise gssapi.GSSException(message) self._gss_ctxt_status = self._gss_ctxt.established return token @@ -461,7 +459,7 @@ class _SSH_SSPI(_SSH_GSSAuth): error, token = self._gss_ctxt.authorize(recv_token) token = token[0].Buffer except pywintypes.error as e: - e.strerror += ", Target: {1}".format(e, self._gss_host) + e.strerror += ", Target: {}".format(e, self._gss_host) raise if error == 0: diff --git a/paramiko/transport.py b/paramiko/transport.py index 22348f87..f72eebaf 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -139,7 +139,7 @@ class Transport(threading.Thread, ClosingContextManager): _DECRYPT = object() _PROTO_ID = "2.0" - _CLIENT_ID = "paramiko_%s" % paramiko.__version__ + _CLIENT_ID = "paramiko_{}".format(paramiko.__version__) # These tuples of algorithm identifiers are in preference order; do not # reorder without reason! @@ -369,7 +369,7 @@ class Transport(threading.Thread, ClosingContextManager): break else: raise SSHException( - "Unable to connect to %s: %s" % (hostname, reason) + "Unable to connect to {}: {}".format(hostname, reason) ) # okay, normal socket-ish flow here... threading.Thread.__init__(self) @@ -456,17 +456,20 @@ class Transport(threading.Thread, ClosingContextManager): """ Returns a string representation of this object, for debugging. """ - out = "<paramiko.Transport at %s" % hex(long(id(self)) & xffffffff) + id_ = hex(long(id(self)) & xffffffff) + out = "<paramiko.Transport at {}".format(id_) if not self.active: out += " (unconnected)" else: if self.local_cipher != "": - out += " (cipher %s, %d bits)" % ( + out += " (cipher {}, {:d} bits)".format( self.local_cipher, self._cipher_info[self.local_cipher]["key-size"] * 8, ) if self.is_authenticated(): - out += " (active; %d open channel(s))" % len(self._channels) + out += " (active; {} open channel(s))".format( + len(self._channels) + ) elif self.initial_kex_done: out += " (connected; awaiting auth)" else: @@ -1106,7 +1109,7 @@ class Transport(threading.Thread, ClosingContextManager): m.add_boolean(wait) if data is not None: m.add(*data) - self._log(DEBUG, 'Sending global request "%s"' % kind) + self._log(DEBUG, 'Sending global request "{}"'.format(kind)) self._send_user_message(m) if not wait: return None @@ -1226,15 +1229,20 @@ class Transport(threading.Thread, ClosingContextManager): self._log(DEBUG, "Bad host key from server") self._log( DEBUG, - "Expected: %s: %s" - % (hostkey.get_name(), repr(hostkey.asbytes())), + "Expected: {}: {}".format( + hostkey.get_name(), repr(hostkey.asbytes()) + ), ) self._log( DEBUG, - "Got : %s: %s" % (key.get_name(), repr(key.asbytes())), + "Got : {}: {}".format( + key.get_name(), repr(key.asbytes()) + ), ) raise SSHException("Bad host key from server") - self._log(DEBUG, "Host key verified (%s)" % hostkey.get_name()) + self._log( + DEBUG, "Host key verified ({})".format(hostkey.get_name()) + ) if (pkey is not None) or (password is not None) or gss_auth or gss_kex: if gss_auth: @@ -1345,7 +1353,7 @@ class Transport(threading.Thread, ClosingContextManager): :param str username: the username to authenticate as :return: - `list` of auth types permissible for the next stage of + list of auth types permissible for the next stage of authentication (normally empty) :raises: @@ -1400,7 +1408,7 @@ class Transport(threading.Thread, ClosingContextManager): ``True`` if an attempt at an automated "interactive" password auth should be made if the server doesn't support normal password auth :return: - `list` of auth types permissible for the next stage of + list of auth types permissible for the next stage of authentication (normally empty) :raises: @@ -1473,7 +1481,7 @@ class Transport(threading.Thread, ClosingContextManager): an event to trigger when the authentication attempt is complete (whether it was successful or not) :return: - `list` of auth types permissible for the next stage of + list of auth types permissible for the next stage of authentication (normally empty) :raises: @@ -1531,7 +1539,7 @@ class Transport(threading.Thread, ClosingContextManager): :param callable handler: a handler for responding to server questions :param str submethods: a string list of desired submethods (optional) :return: - `list` of auth types permissible for the next stage of + list of auth types permissible for the next stage of authentication (normally empty). :raises: `.BadAuthenticationType` -- if public-key authentication isn't @@ -1583,7 +1591,6 @@ class Transport(threading.Thread, ClosingContextManager): :param bool gss_deleg_creds: Delegate credentials or not :return: list of auth types permissible for the next stage of authentication (normally empty) - :rtype: list :raises: `.BadAuthenticationType` -- if gssapi-with-mic isn't allowed by the server (and no event was passed in) :raises: @@ -1607,7 +1614,7 @@ class Transport(threading.Thread, ClosingContextManager): :param str username: The username to authenticate as. :returns: - a `list` of auth types permissible for the next stage of + a list of auth types permissible for the next stage of authentication (normally empty) :raises: `.BadAuthenticationType` -- if GSS-API Key Exchange was not performed (and no event was passed @@ -1805,7 +1812,9 @@ class Transport(threading.Thread, ClosingContextManager): raise SSHException("Unknown host key type") if not key.verify_ssh_sig(self.H, Message(sig)): raise SSHException( - "Signature verification (%s) failed." % self.host_key_type + "Signature verification ({}) failed.".format( + self.host_key_type + ) ) # noqa self.host_key = key @@ -1819,9 +1828,8 @@ class Transport(threading.Thread, ClosingContextManager): # Fallback to SHA1 for kex engines that fail to specify a hex # algorithm, or for e.g. transport tests that don't run kexinit. hash_algo = getattr(self.kex_engine, "hash_algo", None) - hash_select_msg = "kex engine %s specified hash_algo %r" % ( - self.kex_engine.__class__.__name__, - hash_algo, + hash_select_msg = "kex engine {} specified hash_algo {!r}".format( + self.kex_engine.__class__.__name__, hash_algo ) if hash_algo is None: hash_algo = sha1 @@ -1945,14 +1953,15 @@ class Transport(threading.Thread, ClosingContextManager): _active_threads.append(self) tid = hex(long(id(self)) & xffffffff) if self.server_mode: - self._log(DEBUG, "starting thread (server mode): %s" % tid) + self._log(DEBUG, "starting thread (server mode): {}".format(tid)) else: - self._log(DEBUG, "starting thread (client mode): %s" % tid) + self._log(DEBUG, "starting thread (client mode): {}".format(tid)) try: try: self.packetizer.write_all(b(self.local_version + "\r\n")) self._log( - DEBUG, "Local version/idstring: %s" % self.local_version + DEBUG, + "Local version/idstring: {}".format(self.local_version), ) # noqa self._check_banner() # The above is actually very much part of the handshake, but @@ -1984,8 +1993,9 @@ class Transport(threading.Thread, ClosingContextManager): if len(self._expected_packet) > 0: if ptype not in self._expected_packet: raise SSHException( - "Expecting packet from %r, got %d" - % (self._expected_packet, ptype) + "Expecting packet from {!r}, got {:d}".format( + self._expected_packet, ptype + ) ) # noqa self._expected_packet = tuple() if (ptype >= 30) and (ptype <= 41): @@ -2006,15 +2016,17 @@ class Transport(threading.Thread, ClosingContextManager): elif chanid in self.channels_seen: self._log( DEBUG, - "Ignoring message for dead channel %d" - % chanid, - ) # noqa + "Ignoring message for dead channel {:d}".format( # noqa + chanid + ), + ) else: self._log( ERROR, - "Channel request for unknown channel %d" - % chanid, - ) # noqa + "Channel request for unknown channel {:d}".format( # noqa + chanid + ), + ) break elif ( self.auth_handler is not None @@ -2030,7 +2042,7 @@ class Transport(threading.Thread, ClosingContextManager): # itself literally MSG_UNIMPLEMENTED, in which case, we # just shut up to avoid causing a useless loop). name = MSG_NAMES[ptype] - warning = "Oops, unhandled type {0} ({1!r})".format( + warning = "Oops, unhandled type {} ({!r})".format( ptype, name ) self._log(WARNING, warning) @@ -2050,7 +2062,7 @@ class Transport(threading.Thread, ClosingContextManager): except socket.error as e: if type(e.args) is tuple: if e.args: - emsg = "%s (%d)" % (e.args[1], e.args[0]) + emsg = "{} ({:d})".format(e.args[1], e.args[0]) else: # empty tuple, e.g. socket.timeout emsg = str(e) or repr(e) else: @@ -2091,11 +2103,11 @@ class Transport(threading.Thread, ClosingContextManager): # Log useful, non-duplicative line re: an agreed-upon algorithm. # Old code implied algorithms could be asymmetrical (different for # inbound vs outbound) so we preserve that possibility. - msg = "{0} agreed: ".format(which) + msg = "{} agreed: ".format(which) if local == remote: msg += local else: - msg += "local={0}, remote={1}".format(local, remote) + msg += "local={}, remote={}".format(local, remote) self._log(DEBUG, msg) # protocol stages @@ -2137,7 +2149,7 @@ class Transport(threading.Thread, ClosingContextManager): raise SSHException('Indecipherable protocol version "' + buf + '"') # save this server version string for later self.remote_version = buf - self._log(DEBUG, "Remote version/idstring: %s" % buf) + self._log(DEBUG, "Remote version/idstring: {}".format(buf)) # pull off any attached comment # NOTE: comment used to be stored in a variable and then...never used. # since 2003. ca 877cd974b8182d26fa76d566072917ea67b64e67 @@ -2151,9 +2163,9 @@ class Transport(threading.Thread, ClosingContextManager): version = segs[1] client = segs[2] if version != "1.99" and version != "2.0": - msg = "Incompatible version ({0} instead of 2.0)" + msg = "Incompatible version ({} instead of 2.0)" raise SSHException(msg.format(version)) - msg = "Connected (version {0}, client {1})".format(version, client) + msg = "Connected (version {}, client {})".format(version, client) self._log(INFO, msg) def _send_kex_init(self): @@ -2270,7 +2282,7 @@ class Transport(threading.Thread, ClosingContextManager): "Incompatible ssh peer (no acceptable kex algorithm)" ) # noqa self.kex_engine = self._kex_info[agreed_kex[0]](self) - self._log(DEBUG, "Kex agreed: %s" % agreed_kex[0]) + self._log(DEBUG, "Kex agreed: {}".format(agreed_kex[0])) if self.server_mode: available_server_keys = list( @@ -2387,7 +2399,8 @@ class Transport(threading.Thread, ClosingContextManager): len(agreed_local_compression) == 0 or len(agreed_remote_compression) == 0 ): - msg = "Incompatible ssh server (no acceptable compression) {0!r} {1!r} {2!r}" # noqa + msg = "Incompatible ssh server (no acceptable compression)" + msg += " {!r} {!r} {!r}" raise SSHException( msg.format( agreed_local_compression, @@ -2529,15 +2542,16 @@ class Transport(threading.Thread, ClosingContextManager): def _parse_disconnect(self, m): code = m.get_int() desc = m.get_text() - self._log(INFO, "Disconnect (code %d): %s" % (code, desc)) + self._log(INFO, "Disconnect (code {:d}): {}".format(code, desc)) def _parse_global_request(self, m): kind = m.get_text() - self._log(DEBUG, 'Received global request "%s"' % kind) + self._log(DEBUG, 'Received global request "{}"'.format(kind)) want_reply = m.get_boolean() if not self.server_mode: self._log( - DEBUG, 'Rejecting "%s" global request from server.' % kind + DEBUG, + 'Rejecting "{}" global request from server.'.format(kind), ) ok = False elif kind == "tcpip-forward": @@ -2592,7 +2606,7 @@ class Transport(threading.Thread, ClosingContextManager): chan._set_remote_channel( server_chanid, server_window_size, server_max_packet_size ) - self._log(DEBUG, "Secsh channel %d opened." % chanid) + self._log(DEBUG, "Secsh channel {:d} opened.".format(chanid)) if chanid in self.channel_events: self.channel_events[chanid].set() del self.channel_events[chanid] @@ -2608,8 +2622,9 @@ class Transport(threading.Thread, ClosingContextManager): reason_text = CONNECTION_FAILED_CODE.get(reason, "(unknown code)") self._log( ERROR, - "Secsh channel %d open FAILED: %s: %s" - % (chanid, reason_str, reason_text), + "Secsh channel {:d} open FAILED: {}: {}".format( + chanid, reason_str, reason_text + ), ) self.lock.acquire() try: @@ -2644,8 +2659,9 @@ class Transport(threading.Thread, ClosingContextManager): origin_port = m.get_int() self._log( DEBUG, - "Incoming x11 connection from %s:%d" - % (origin_addr, origin_port), + "Incoming x11 connection from {}:{:d}".format( + origin_addr, origin_port + ), ) self.lock.acquire() try: @@ -2659,8 +2675,9 @@ class Transport(threading.Thread, ClosingContextManager): origin_port = m.get_int() self._log( DEBUG, - "Incoming tcp forwarded connection from %s:%d" - % (origin_addr, origin_port), + "Incoming tcp forwarded connection from {}:{:d}".format( + origin_addr, origin_port + ), ) self.lock.acquire() try: @@ -2669,7 +2686,8 @@ class Transport(threading.Thread, ClosingContextManager): self.lock.release() elif not self.server_mode: self._log( - DEBUG, 'Rejecting "%s" channel request from server.' % kind + DEBUG, + 'Rejecting "{}" channel request from server.'.format(kind), ) reject = True reason = OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED @@ -2696,7 +2714,8 @@ class Transport(threading.Thread, ClosingContextManager): ) if reason != OPEN_SUCCEEDED: self._log( - DEBUG, 'Rejecting "%s" channel request from client.' % kind + DEBUG, + 'Rejecting "{}" channel request from client.'.format(kind), ) reject = True if reject: @@ -2730,7 +2749,9 @@ class Transport(threading.Thread, ClosingContextManager): m.add_int(self.default_window_size) m.add_int(self.default_max_packet_size) self._send_message(m) - self._log(DEBUG, "Secsh channel %d (%s) opened.", my_chanid, kind) + self._log( + DEBUG, "Secsh channel {:d} ({}) opened.".format(my_chanid, kind) + ) if kind == "auth-agent@openssh.com": self._forward_agent_handler(chan) elif kind == "x11": @@ -2747,7 +2768,7 @@ class Transport(threading.Thread, ClosingContextManager): m.get_boolean() # always_display msg = m.get_string() m.get_string() # language - self._log(DEBUG, "Debug msg: {0}".format(util.safe_string(msg))) + self._log(DEBUG, "Debug msg: {}".format(util.safe_string(msg))) def _get_subsystem_handler(self, name): try: @@ -2803,7 +2824,7 @@ class SecurityOptions(object): """ Returns a string representation of this object, for debugging. """ - return "<paramiko.SecurityOptions for %s>" % repr(self._transport) + return "<paramiko.SecurityOptions for {!r}>".format(self._transport) def _set(self, name, orig, x): if type(x) is list: diff --git a/paramiko/util.py b/paramiko/util.py index 181c3892..de4a5647 100644 --- a/paramiko/util.py +++ b/paramiko/util.py @@ -102,19 +102,21 @@ def format_binary(data, prefix=""): def format_binary_line(data): - left = " ".join(["%02X" % byte_ord(c) for c in data]) - right = "".join([(".%c.." % c)[(byte_ord(c) + 63) // 95] for c in data]) - return "%-50s %s" % (left, right) + left = " ".join(["{:02X}".format(byte_ord(c)) for c in data]) + right = "".join( + [".{:c}..".format(byte_ord(c))[(byte_ord(c) + 63) // 95] for c in data] + ) + return "{:50s} {}".format(left, right) def safe_string(s): - out = b("") + out = b"" for c in s: i = byte_ord(c) if 32 <= i <= 127: out += byte_chr(i) else: - out += b("%%%02X" % i) + out += b("%{:02X}".format(i)) return out @@ -249,7 +251,8 @@ def log_to_file(filename, level=DEBUG): l.setLevel(level) f = open(filename, "a") lh = logging.StreamHandler(f) - frm = "%(levelname)-.3s [%(asctime)s.%(msecs)03d] thr=%(_threadid)-3d %(name)s: %(message)s" # noqa + frm = "%(levelname)-.3s [%(asctime)s.%(msecs)03d] thr=%(_threadid)-3d" + frm += " %(name)s: %(message)s" lh.setFormatter(logging.Formatter(frm, "%Y%m%d-%H:%M:%S")) l.addHandler(lh) diff --git a/paramiko/win_pageant.py b/paramiko/win_pageant.py index d6afd5bd..a550b7f3 100644 --- a/paramiko/win_pageant.py +++ b/paramiko/win_pageant.py @@ -44,7 +44,7 @@ win32con_WM_COPYDATA = 74 def _get_pageant_window_object(): - return ctypes.windll.user32.FindWindowA(b("Pageant"), b("Pageant")) + return ctypes.windll.user32.FindWindowA(b"Pageant", b"Pageant") def can_talk_to_agent(): @@ -65,11 +65,8 @@ setup( "Topic :: Security :: Cryptography", "Programming Language :: Python", "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.2", - "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", diff --git a/sites/shared_conf.py b/sites/shared_conf.py index aaea43cc..f4806cf1 100644 --- a/sites/shared_conf.py +++ b/sites/shared_conf.py @@ -20,12 +20,12 @@ html_sidebars = { } # Everything intersphinx's to Python -intersphinx_mapping = {"python": ("http://docs.python.org/2.6", None)} +intersphinx_mapping = {"python": ("https://docs.python.org/2.7/", None)} # Regular settings project = "Paramiko" year = datetime.now().year -copyright = "%d Jeff Forcier" % year +copyright = "{} Jeff Forcier".format(year) master_doc = "index" templates_path = ["_templates"] exclude_trees = ["_build"] diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 281f1d08..ba755aee 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,7 @@ Changelog ========= +- :release:`2.4.2 <2018-09-18>` - :release:`2.3.3 <2018-09-18>` - :release:`2.2.4 <2018-09-18>` - :release:`2.1.6 <2018-09-18>` @@ -43,6 +44,7 @@ Changelog - :support:`1262 backported` Add ``*.pub`` files to the MANIFEST so distributed source packages contain some necessary test assets. Credit: Alexander Kapshuna. +- :release:`2.4.1 <2018-03-12>` - :release:`2.3.2 <2018-03-12>` - :release:`2.2.3 <2018-03-12>` - :release:`2.1.5 <2018-03-12>` @@ -57,10 +59,32 @@ Changelog - :bug:`1039` Ed25519 auth key decryption raised an unexpected exception when given a unicode password string (typical in python 3). Report by Theodor van Nahl and fix by Pierce Lopez. +- :release:`2.4.0 <2017-11-14>` +- :feature:`-` Add a new ``passphrase`` kwarg to `SSHClient.connect + <paramiko.client.SSHClient.connect>` so users may disambiguate key-decryption + passphrases from password-auth passwords. (This is a backwards compatible + change; ``password`` will still pull double duty as a passphrase when + ``passphrase`` is not given.) +- :support:`-` Update ``tearDown`` of client test suite to avoid hangs due to + eternally blocking ``accept()`` calls on the internal server thread (which + can occur when test code raises an exception before actually connecting to + the server.) - :bug:`1108 (1.17+)` Rename a private method keyword argument (which was named ``async``) so that we're compatible with the upcoming Python 3.7 release (where ``async`` is a new keyword.) Thanks to ``@vEpiphyte`` for the report. +- :support:`1100` Updated the test suite & related docs/metadata/config to be + compatible with pytest instead of using the old, custom, crufty + unittest-based ``test.py``. + + This includes marking known-slow tests (mostly the SFTP ones) so they can be + filtered out by ``inv test``'s default behavior; as well as other minor + tweaks to test collection and/or display (for example, GSSAPI tests are + collected, but skipped, instead of not even being collected by default as in + ``test.py``.) - :support:`- backported` Include LICENSE file in wheel archives. +- :support:`1070` Drop Python 2.6 and Python 3.3 support; now only 2.7 and 3.4+ + are supported. If you're unable to upgrade from 2.6 or 3.3, please stick to + the Paramiko 2.3.x (or below) release lines. - :release:`2.3.1 <2017-09-22>` - :bug:`1071` Certificate support broke the no-certificate case for Ed25519 keys (symptom is an ``AttributeError`` about ``public_blob``.) This went diff --git a/sites/www/index.rst b/sites/www/index.rst index f0a5db8a..26961f24 100644 --- a/sites/www/index.rst +++ b/sites/www/index.rst @@ -1,11 +1,11 @@ Welcome to Paramiko! ==================== -Paramiko is a Python (2.6+, 3.3+) implementation of the SSHv2 protocol [#]_, +Paramiko is a Python (2.7, 3.4+) implementation of the SSHv2 protocol [#]_, providing both client and server functionality. While it leverages a Python C -extension for low level cryptography -(`Cryptography <https://cryptography.io>`_), Paramiko itself is a pure Python -interface around SSH networking concepts. +extension for low level cryptography (`Cryptography +<https://cryptography.io>`_), Paramiko itself is a pure Python interface around +SSH networking concepts. This website covers project information for Paramiko such as the changelog, contribution guidelines, development roadmap, news/blog, and so forth. Detailed diff --git a/sites/www/installing.rst b/sites/www/installing.rst index f335a9e7..e6db2dca 100644 --- a/sites/www/installing.rst +++ b/sites/www/installing.rst @@ -19,8 +19,8 @@ via `pip <http://pip-installer.org>`_:: $ pip install paramiko -We currently support **Python 2.6, 2.7, 3.3+, and PyPy**. Users on Python 2.5 -or older (or 3.2 or older) are urged to upgrade. +We currently support **Python 2.7, 3.4+, and PyPy**. Users on Python 2.6 or +older (or 3.3 or older) are urged to upgrade. Paramiko has only one direct hard dependency: the Cryptography library. See :ref:`cryptography`. @@ -7,6 +7,7 @@ from invocations import travis from invocations.checks import blacken from invocations.docs import docs, www, sites from invocations.packaging.release import ns as release_coll, publish +from invocations.testing import count_errors # TODO: this screams out for the invoke missing-feature of "I just wrap task X, @@ -39,7 +40,7 @@ def test( # running headless? Probably? if color: opts += " --color=yes" - opts += " --capture={0}".format(capture) + opts += " --capture={}".format(capture) if "-m" not in opts and not include_slow: opts += " -m 'not slow'" if k is not None and not ("-k" in opts if opts else False): @@ -122,7 +123,16 @@ def release(ctx, sdist=True, wheel=True, sign=True, dry_run=False, index=None): release_coll.tasks["publish"] = release ns = Collection( - test, coverage, guard, release_coll, docs, www, sites, travis, blacken + test, + coverage, + guard, + release_coll, + docs, + www, + sites, + count_errors, + travis, + blacken, ) ns.configure( { diff --git a/tests/test_auth.py b/tests/test_auth.py index 6358a053..fe1a32a1 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -255,6 +255,7 @@ class AuthTest(unittest.TestCase): etype, evalue, etb = sys.exc_info() self.assertTrue(issubclass(etype, AuthenticationException)) + @slow def test_9_auth_non_responsive(self): """ verify that authentication times out if server takes to long to diff --git a/tests/test_client.py b/tests/test_client.py index fec1485e..80b28adf 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -20,7 +20,7 @@ Some unit tests for SSHClient. """ -from __future__ import with_statement +from __future__ import with_statement, print_function import gc import os @@ -33,6 +33,8 @@ import warnings import weakref from tempfile import mkstemp +from pytest_relaxed import raises + import paramiko from paramiko.pkey import PublicBlob from paramiko.common import PY2 @@ -41,6 +43,10 @@ from paramiko.ssh_exception import SSHException, AuthenticationException from .util import _support, slow +requires_gss_auth = unittest.skipUnless( + paramiko.GSS_AUTH_AVAILABLE, "GSS auth not available" +) + FINGERPRINTS = { "ssh-dss": b"\x44\x78\xf0\xb9\xa2\x3c\xc5\x18\x20\x09\xff\x75\x5b\xc1\xd2\x6c", "ssh-rsa": b"\x60\x73\x38\x44\xcb\x51\x86\x65\x7f\xde\xda\xa2\x2b\x5a\x57\xd5", @@ -107,7 +113,7 @@ class NullServer(paramiko.ServerInterface): return True -class SSHClientTest(unittest.TestCase): +class ClientTest(unittest.TestCase): def setUp(self): self.sockl = socket.socket() self.sockl.bind(("localhost", 0)) @@ -120,16 +126,42 @@ class SSHClientTest(unittest.TestCase): look_for_keys=False, ) self.event = threading.Event() + self.kill_event = threading.Event() def tearDown(self): - for attr in "tc ts socks sockl".split(): - if hasattr(self, attr): - getattr(self, attr).close() - - def _run(self, allowed_keys=None, delay=0, public_blob=None): + # Shut down client Transport + if hasattr(self, "tc"): + self.tc.close() + # Shut down shared socket + if hasattr(self, "sockl"): + # Signal to server thread that it should shut down early; it checks + # this immediately after accept(). (In scenarios where connection + # actually succeeded during the test, this becomes a no-op.) + self.kill_event.set() + # Forcibly connect to server sock in case the server thread is + # hanging out in its accept() (e.g. if the client side of the test + # fails before it even gets to connecting); there's no other good + # way to force an accept() to exit. + put_a_sock_in_it = socket.socket() + put_a_sock_in_it.connect((self.addr, self.port)) + put_a_sock_in_it.close() + # Then close "our" end of the socket (which _should_ cause the + # accept() to bail out, but does not, for some reason. I blame + # threading.) + self.sockl.close() + + def _run( + self, allowed_keys=None, delay=0, public_blob=None, kill_event=None + ): if allowed_keys is None: allowed_keys = FINGERPRINTS.keys() self.socks, addr = self.sockl.accept() + # If the kill event was set at this point, it indicates an early + # shutdown, so bail out now and don't even try setting up a Transport + # (which will just verbosely die.) + if kill_event and kill_event.is_set(): + self.socks.close() + return self.ts = paramiko.Transport(self.socks) keypath = _support("test_rsa.key") host_key = paramiko.RSAKey.from_private_key_file(keypath) @@ -149,7 +181,7 @@ class SSHClientTest(unittest.TestCase): The exception is ``allowed_keys`` which is stripped and handed to the ``NullServer`` used for testing. """ - run_kwargs = {} + run_kwargs = {"kill_event": self.kill_event} for key in ("allowed_keys", "public_blob"): run_kwargs[key] = kwargs.pop(key, None) # Server setup @@ -194,6 +226,8 @@ class SSHClientTest(unittest.TestCase): stdout.close() stderr.close() + +class SSHClientTest(ClientTest): def test_1_client(self): """ verify that the SSHClient stuff works too. @@ -242,7 +276,7 @@ class SSHClientTest(unittest.TestCase): try: self._test_connection( key_filename=[ - _support("test_{0}.key".format(x)) for x in attempt + _support("test_{}.key".format(x)) for x in attempt ], allowed_keys=[types_[x] for x in accept], ) @@ -271,7 +305,7 @@ class SSHClientTest(unittest.TestCase): # server-side behavior is 100% identical.) # NOTE: only bothered whipping up one cert per overall class/family. for type_ in ("rsa", "dss", "ecdsa_256", "ed25519"): - cert_name = "test_{0}.key-cert.pub".format(type_) + cert_name = "test_{}.key-cert.pub".format(type_) cert_path = _support(os.path.join("cert_support", cert_name)) self._test_connection( key_filename=cert_path, @@ -286,12 +320,12 @@ class SSHClientTest(unittest.TestCase): # that a specific cert was found, along with regular authorization # succeeding proving that the overall flow works. for type_ in ("rsa", "dss", "ecdsa_256", "ed25519"): - key_name = "test_{0}.key".format(type_) + key_name = "test_{}.key".format(type_) key_path = _support(os.path.join("cert_support", key_name)) self._test_connection( key_filename=key_path, public_blob=PublicBlob.from_file( - "{0}-cert.pub".format(key_path) + "{}-cert.pub".format(key_path) ), ) @@ -447,6 +481,7 @@ class SSHClientTest(unittest.TestCase): ) self._test_connection(**kwargs) + @slow def test_9_auth_timeout(self): """ verify that the SSHClient has a configurable auth timeout @@ -459,21 +494,19 @@ class SSHClientTest(unittest.TestCase): auth_timeout=0.5, ) + @requires_gss_auth def test_10_auth_trickledown_gsskex(self): """ Failed gssapi-keyex auth doesn't prevent subsequent key auth from succeeding """ - if not paramiko.GSS_AUTH_AVAILABLE: - return # for python 2.6 lacks skipTest kwargs = dict(gss_kex=True, key_filename=[_support("test_rsa.key")]) self._test_connection(**kwargs) + @requires_gss_auth def test_11_auth_trickledown_gssauth(self): """ Failed gssapi-with-mic auth doesn't prevent subsequent key auth from succeeding """ - if not paramiko.GSS_AUTH_AVAILABLE: - return # for python 2.6 lacks skipTest kwargs = dict(gss_auth=True, key_filename=[_support("test_rsa.key")]) self._test_connection(**kwargs) @@ -493,14 +526,13 @@ class SSHClientTest(unittest.TestCase): **self.connect_kwargs ) + @requires_gss_auth def test_13_reject_policy_gsskex(self): """ verify that SSHClient's RejectPolicy works, even if gssapi-keyex was enabled but not used. """ # Test for a bug present in paramiko versions released before 2017-08-01 - if not paramiko.GSS_AUTH_AVAILABLE: - return # for python 2.6 lacks skipTest threading.Thread(target=self._run).start() self.tc = paramiko.SSHClient() @@ -560,10 +592,7 @@ class SSHClientTest(unittest.TestCase): def test_host_key_negotiation_4(self): self._client_host_key_good(paramiko.RSAKey, "test_rsa.key") - def test_update_environment(self): - """ - Verify that environment variables can be set by the client. - """ + def _setup_for_env(self): threading.Thread(target=self._run).start() self.tc = paramiko.SSHClient() @@ -577,6 +606,11 @@ class SSHClientTest(unittest.TestCase): self.assertTrue(self.event.isSet()) self.assertTrue(self.ts.is_active()) + def test_update_environment(self): + """ + Verify that environment variables can be set by the client. + """ + self._setup_for_env() target_env = {b"A": b"B", b"C": b"d"} self.tc.exec_command("yes", environment=target_env) @@ -584,22 +618,20 @@ class SSHClientTest(unittest.TestCase): self.assertEqual(target_env, getattr(schan, "env", {})) schan.close() - # Cannot use assertRaises in context manager mode as it is not supported - # in Python 2.6. - try: + @unittest.skip("Clients normally fail silently, thus so do we, for now") + def test_env_update_failures(self): + self._setup_for_env() + with self.assertRaises(SSHException) as manager: # Verify that a rejection by the server can be detected self.tc.exec_command("yes", environment={b"INVALID_ENV": b""}) - except SSHException as e: - self.assertTrue( - "INVALID_ENV" in str(e), - "Expected variable name in error message", - ) - self.assertTrue( - isinstance(e.args[1], SSHException), - "Expected original SSHException in exception", - ) - else: - self.assertFalse(False, "SSHException was not thrown.") + self.assertTrue( + "INVALID_ENV" in str(manager.exception), + "Expected variable name in error message", + ) + self.assertTrue( + isinstance(manager.exception.args[1], SSHException), + "Expected original SSHException in exception", + ) def test_missing_key_policy_accepts_classes_or_instances(self): """ @@ -616,3 +648,49 @@ class SSHClientTest(unittest.TestCase): # Hand in just the class (new behavior) client.set_missing_host_key_policy(paramiko.AutoAddPolicy) assert isinstance(client._policy, paramiko.AutoAddPolicy) + + +class PasswordPassphraseTests(ClientTest): + # TODO: most of these could reasonably be set up to use mocks/assertions + # (e.g. "gave passphrase -> expect PKey was given it as the passphrase") + # instead of suffering a real connection cycle. + # TODO: in that case, move the below to be part of an integration suite? + + def test_password_kwarg_works_for_password_auth(self): + # Straightforward / duplicate of earlier basic password test. + self._test_connection(password="pygmalion") + + # TODO: more granular exception pending #387; should be signaling "no auth + # methods available" because no key and no password + @raises(SSHException) + def test_passphrase_kwarg_not_used_for_password_auth(self): + # Using the "right" password in the "wrong" field shouldn't work. + self._test_connection(passphrase="pygmalion") + + def test_passphrase_kwarg_used_for_key_passphrase(self): + # Straightforward again, with new passphrase kwarg. + self._test_connection( + key_filename=_support("test_rsa_password.key"), + passphrase="television", + ) + + def test_password_kwarg_used_for_passphrase_when_no_passphrase_kwarg_given( + self + ): # noqa + # Backwards compatibility: passphrase in the password field. + self._test_connection( + key_filename=_support("test_rsa_password.key"), + password="television", + ) + + @raises(AuthenticationException) # TODO: more granular + def test_password_kwarg_not_used_for_passphrase_when_passphrase_kwarg_given( + self + ): # noqa + # Sanity: if we're given both fields, the password field is NOT used as + # a passphrase. + self._test_connection( + key_filename=_support("test_rsa_password.key"), + password="television", + passphrase="wat? lol no", + ) diff --git a/tests/test_kex.py b/tests/test_kex.py index 65eb9a17..62512beb 100644 --- a/tests/test_kex.py +++ b/tests/test_kex.py @@ -33,8 +33,6 @@ from paramiko.kex_gex import KexGex, KexGexSHA256 from paramiko import Message from paramiko.common import byte_chr from paramiko.kex_ecdh_nist import KexNistp256 -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives.asymmetric import ec def dummy_urandom(n): diff --git a/tests/test_sftp.py b/tests/test_sftp.py index 87c57340..fdb7c9bc 100644 --- a/tests/test_sftp.py +++ b/tests/test_sftp.py @@ -194,20 +194,15 @@ class TestSFTP(object): sftp.rename(sftp.FOLDER + "/a", sftp.FOLDER + "/b") with sftp.open(sftp.FOLDER + "/a", "w") as f: f.write("two") - try: + with pytest.raises(IOError): # actual message seems generic sftp.rename(sftp.FOLDER + "/a", sftp.FOLDER + "/b") - self.assertTrue( - False, "no exception when rename-ing onto existing file" - ) - except (OSError, IOError): - pass # now check with the posix_rename sftp.posix_rename(sftp.FOLDER + "/a", sftp.FOLDER + "/b") with sftp.open(sftp.FOLDER + "/b", "r") as f: data = u(f.read()) err = "Contents of renamed file not the same as original file" - assert data == "two", err + assert "two" == data, err finally: try: diff --git a/tests/test_util.py b/tests/test_util.py index 12256b39..705baa14 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -419,8 +419,7 @@ IdentityFile something_%l_using_fqdn f = StringIO(test_config_file) config = paramiko.util.parse_ssh_config(f) self.assertEqual( - config.get_hostnames(), - set(["*", "*.example.com", "spoo.example.com"]), + config.get_hostnames(), {"*", "*.example.com", "spoo.example.com"} ) def test_quoted_host_names(self): @@ -512,12 +511,12 @@ Host param3 parara self.assertRaises(Exception, conf._get_hosts, host) def test_safe_string(self): - vanilla = b("vanilla") - has_bytes = b("has \7\3 bytes") + vanilla = b"vanilla" + has_bytes = b"has \7\3 bytes" safe_vanilla = safe_string(vanilla) safe_has_bytes = safe_string(has_bytes) - expected_bytes = b("has %07%03 bytes") - err = "{0!r} != {1!r}" + expected_bytes = b"has %07%03 bytes" + err = "{!r} != {!r}" msg = err.format(safe_vanilla, vanilla) assert safe_vanilla == vanilla, msg msg = err.format(safe_has_bytes, expected_bytes) diff --git a/tests/util.py b/tests/util.py index c1ba4b2c..4ca02374 100644 --- a/tests/util.py +++ b/tests/util.py @@ -20,7 +20,7 @@ def needs_builtin(name): """ Skip decorated test if builtin name does not exist. """ - reason = "Test requires a builtin '{0}'".format(name) + reason = "Test requires a builtin '{}'".format(name) return pytest.mark.skipif(not hasattr(builtins, name), reason=reason) |