aboutsummaryrefslogtreecommitdiff
path: root/paramiko
diff options
context:
space:
mode:
Diffstat (limited to 'paramiko')
-rw-r--r--paramiko/_version.py2
-rw-r--r--paramiko/_winapi.py139
-rw-r--r--paramiko/buffered_pipe.py4
-rw-r--r--paramiko/channel.py4
-rw-r--r--paramiko/client.py135
-rw-r--r--paramiko/config.py4
-rw-r--r--paramiko/hostkeys.py2
-rw-r--r--paramiko/kex_gex.py13
-rw-r--r--paramiko/kex_group1.py1
-rw-r--r--paramiko/kex_group14.py2
-rw-r--r--paramiko/packet.py3
-rw-r--r--paramiko/pkey.py24
-rw-r--r--paramiko/py3compat.py5
-rw-r--r--paramiko/sftp_attr.py5
-rw-r--r--paramiko/sftp_client.py9
-rw-r--r--paramiko/sftp_file.py7
-rw-r--r--paramiko/ssh_exception.py38
-rw-r--r--paramiko/transport.py220
-rw-r--r--paramiko/util.py2
19 files changed, 486 insertions, 133 deletions
diff --git a/paramiko/_version.py b/paramiko/_version.py
index 25aac14..e82b866 100644
--- a/paramiko/_version.py
+++ b/paramiko/_version.py
@@ -1,2 +1,2 @@
-__version_info__ = (1, 15, 3)
+__version_info__ = (1, 16, 0)
__version__ = '.'.join(map(str, __version_info__))
diff --git a/paramiko/_winapi.py b/paramiko/_winapi.py
index cf4d68e..9a8bded 100644
--- a/paramiko/_winapi.py
+++ b/paramiko/_winapi.py
@@ -1,23 +1,16 @@
"""
Windows API functions implemented as ctypes functions and classes as found
-in jaraco.windows (2.10).
+in jaraco.windows (3.3).
If you encounter issues with this module, please consider reporting the issues
in jaraco.windows and asking the author to port the fixes back here.
"""
-import ctypes
+import sys
import ctypes.wintypes
-from paramiko.py3compat import u
-try:
- import builtins
-except ImportError:
- import __builtin__ as builtins
-try:
- USHORT = ctypes.wintypes.USHORT
-except AttributeError:
- USHORT = ctypes.c_ushort
+from paramiko.py3compat import u, builtins
+
######################
# jaraco.windows.error
@@ -29,11 +22,7 @@ def format_system_message(errno):
"""
# first some flags used by FormatMessageW
ALLOCATE_BUFFER = 0x100
- ARGUMENT_ARRAY = 0x2000
- FROM_HMODULE = 0x800
- FROM_STRING = 0x400
FROM_SYSTEM = 0x1000
- IGNORE_INSERTS = 0x200
# Let FormatMessageW allocate the buffer (we'll free it below)
# Also, let it know we want a system error message.
@@ -44,7 +33,7 @@ def format_system_message(errno):
result_buffer = ctypes.wintypes.LPWSTR()
buffer_size = 0
arguments = None
- format_bytes = ctypes.windll.kernel32.FormatMessageW(
+ bytes = ctypes.windll.kernel32.FormatMessageW(
flags,
source,
message_id,
@@ -52,11 +41,11 @@ def format_system_message(errno):
ctypes.byref(result_buffer),
buffer_size,
arguments,
- )
+ )
# note the following will cause an infinite loop if GetLastError
# repeatedly returns an error that cannot be formatted, although
# this should not happen.
- handle_nonzero_success(format_bytes)
+ handle_nonzero_success(bytes)
message = result_buffer.value
ctypes.windll.kernel32.LocalFree(result_buffer)
return message
@@ -69,7 +58,11 @@ class WindowsError(builtins.WindowsError):
if value is None:
value = ctypes.windll.kernel32.GetLastError()
strerror = format_system_message(value)
- super(WindowsError, self).__init__(value, strerror)
+ if sys.version_info > (3,3):
+ args = 0, strerror, None, value
+ else:
+ args = value, strerror
+ super(WindowsError, self).__init__(*args)
@property
def message(self):
@@ -90,6 +83,27 @@ def handle_nonzero_success(result):
raise WindowsError()
+###########################
+# jaraco.windows.api.memory
+
+GMEM_MOVEABLE = 0x2
+
+GlobalAlloc = ctypes.windll.kernel32.GlobalAlloc
+GlobalAlloc.argtypes = ctypes.wintypes.UINT, ctypes.c_ssize_t
+GlobalAlloc.restype = ctypes.wintypes.HANDLE
+
+GlobalLock = ctypes.windll.kernel32.GlobalLock
+GlobalLock.argtypes = ctypes.wintypes.HGLOBAL,
+GlobalLock.restype = ctypes.wintypes.LPVOID
+
+GlobalUnlock = ctypes.windll.kernel32.GlobalUnlock
+GlobalUnlock.argtypes = ctypes.wintypes.HGLOBAL,
+GlobalUnlock.restype = ctypes.wintypes.BOOL
+
+GlobalSize = ctypes.windll.kernel32.GlobalSize
+GlobalSize.argtypes = ctypes.wintypes.HGLOBAL,
+GlobalSize.restype = ctypes.c_size_t
+
CreateFileMapping = ctypes.windll.kernel32.CreateFileMappingW
CreateFileMapping.argtypes = [
ctypes.wintypes.HANDLE,
@@ -104,6 +118,9 @@ CreateFileMapping.restype = ctypes.wintypes.HANDLE
MapViewOfFile = ctypes.windll.kernel32.MapViewOfFile
MapViewOfFile.restype = ctypes.wintypes.HANDLE
+#####################
+# jaraco.windows.mmap
+
class MemoryMap(object):
"""
A memory map object which can have security attributes overridden.
@@ -136,10 +153,13 @@ class MemoryMap(object):
self.pos = pos
def write(self, msg):
+ assert isinstance(msg, bytes)
n = len(msg)
if self.pos + n >= self.length: # A little safety.
raise ValueError("Refusing to write %d bytes" % n)
- ctypes.windll.kernel32.RtlMoveMemory(self.view + self.pos, msg, n)
+ dest = self.view + self.pos
+ length = ctypes.wintypes.SIZE(n)
+ ctypes.windll.kernel32.RtlMoveMemory(dest, msg, length)
self.pos += n
def read(self, n):
@@ -147,7 +167,9 @@ class MemoryMap(object):
Read n bytes from mapped view.
"""
out = ctypes.create_string_buffer(n)
- ctypes.windll.kernel32.RtlMoveMemory(out, self.view + self.pos, n)
+ source = self.view + self.pos
+ length = ctypes.wintypes.SIZE(n)
+ ctypes.windll.kernel32.RtlMoveMemory(out, source, length)
self.pos += n
return out.raw
@@ -155,8 +177,71 @@ class MemoryMap(object):
ctypes.windll.kernel32.UnmapViewOfFile(self.view)
ctypes.windll.kernel32.CloseHandle(self.filemap)
-#########################
-# jaraco.windows.security
+#############################
+# jaraco.windows.api.security
+
+# from WinNT.h
+READ_CONTROL = 0x00020000
+STANDARD_RIGHTS_REQUIRED = 0x000F0000
+STANDARD_RIGHTS_READ = READ_CONTROL
+STANDARD_RIGHTS_WRITE = READ_CONTROL
+STANDARD_RIGHTS_EXECUTE = READ_CONTROL
+STANDARD_RIGHTS_ALL = 0x001F0000
+
+# from NTSecAPI.h
+POLICY_VIEW_LOCAL_INFORMATION = 0x00000001
+POLICY_VIEW_AUDIT_INFORMATION = 0x00000002
+POLICY_GET_PRIVATE_INFORMATION = 0x00000004
+POLICY_TRUST_ADMIN = 0x00000008
+POLICY_CREATE_ACCOUNT = 0x00000010
+POLICY_CREATE_SECRET = 0x00000020
+POLICY_CREATE_PRIVILEGE = 0x00000040
+POLICY_SET_DEFAULT_QUOTA_LIMITS = 0x00000080
+POLICY_SET_AUDIT_REQUIREMENTS = 0x00000100
+POLICY_AUDIT_LOG_ADMIN = 0x00000200
+POLICY_SERVER_ADMIN = 0x00000400
+POLICY_LOOKUP_NAMES = 0x00000800
+POLICY_NOTIFICATION = 0x00001000
+
+POLICY_ALL_ACCESS = (
+ STANDARD_RIGHTS_REQUIRED |
+ POLICY_VIEW_LOCAL_INFORMATION |
+ POLICY_VIEW_AUDIT_INFORMATION |
+ POLICY_GET_PRIVATE_INFORMATION |
+ POLICY_TRUST_ADMIN |
+ POLICY_CREATE_ACCOUNT |
+ POLICY_CREATE_SECRET |
+ POLICY_CREATE_PRIVILEGE |
+ POLICY_SET_DEFAULT_QUOTA_LIMITS |
+ POLICY_SET_AUDIT_REQUIREMENTS |
+ POLICY_AUDIT_LOG_ADMIN |
+ POLICY_SERVER_ADMIN |
+ POLICY_LOOKUP_NAMES)
+
+
+POLICY_READ = (
+ STANDARD_RIGHTS_READ |
+ POLICY_VIEW_AUDIT_INFORMATION |
+ POLICY_GET_PRIVATE_INFORMATION)
+
+POLICY_WRITE = (
+ STANDARD_RIGHTS_WRITE |
+ POLICY_TRUST_ADMIN |
+ POLICY_CREATE_ACCOUNT |
+ POLICY_CREATE_SECRET |
+ POLICY_CREATE_PRIVILEGE |
+ POLICY_SET_DEFAULT_QUOTA_LIMITS |
+ POLICY_SET_AUDIT_REQUIREMENTS |
+ POLICY_AUDIT_LOG_ADMIN |
+ POLICY_SERVER_ADMIN)
+
+POLICY_EXECUTE = (
+ STANDARD_RIGHTS_EXECUTE |
+ POLICY_VIEW_LOCAL_INFORMATION |
+ POLICY_LOOKUP_NAMES)
+
+class TokenAccess:
+ TOKEN_QUERY = 0x8
class TokenInformationClass:
TokenUser = 1
@@ -182,7 +267,7 @@ class SECURITY_DESCRIPTOR(ctypes.Structure):
PACL Dacl;
} SECURITY_DESCRIPTOR;
"""
- SECURITY_DESCRIPTOR_CONTROL = USHORT
+ SECURITY_DESCRIPTOR_CONTROL = ctypes.wintypes.USHORT
REVISION = 1
_fields_ = [
@@ -222,6 +307,9 @@ class SECURITY_ATTRIBUTES(ctypes.Structure):
self._descriptor = value
self.lpSecurityDescriptor = ctypes.addressof(value)
+#########################
+# jaraco.windows.security
+
def GetTokenInformation(token, information_class):
"""
Given a token, get the token information for it.
@@ -236,9 +324,6 @@ def GetTokenInformation(token, information_class):
ctypes.byref(data_size)))
return ctypes.cast(data, ctypes.POINTER(TOKEN_USER)).contents
-class TokenAccess:
- TOKEN_QUERY = 0x8
-
def OpenProcessToken(proc_handle, access):
result = ctypes.wintypes.HANDLE()
proc_handle = ctypes.wintypes.HANDLE(proc_handle)
diff --git a/paramiko/buffered_pipe.py b/paramiko/buffered_pipe.py
index ac35b3e..d5fe164 100644
--- a/paramiko/buffered_pipe.py
+++ b/paramiko/buffered_pipe.py
@@ -81,7 +81,7 @@ class BufferedPipe (object):
Feed new data into this pipe. This method is assumed to be called
from a separate thread, so synchronization is done.
- :param data: the data to add, as a `str`
+ :param data: the data to add, as a `str` or `bytes`
"""
self._lock.acquire()
try:
@@ -125,7 +125,7 @@ class BufferedPipe (object):
:param int nbytes: maximum number of bytes to read
:param float timeout:
maximum seconds to wait (or ``None``, the default, to wait forever)
- :return: the read data, as a `str`
+ :return: the read data, as a `bytes`
:raises PipeTimeout:
if a timeout was specified and no data was ready before that
diff --git a/paramiko/channel.py b/paramiko/channel.py
index 7e39a15..057b417 100644
--- a/paramiko/channel.py
+++ b/paramiko/channel.py
@@ -587,7 +587,7 @@ class Channel (ClosingContextManager):
is returned, the channel stream has closed.
:param int nbytes: maximum number of bytes to read.
- :return: received data, as a `str`
+ :return: received data, as a `bytes`
:raises socket.timeout:
if no data is ready before the timeout set by `settimeout`.
@@ -975,7 +975,7 @@ class Channel (ClosingContextManager):
else:
ok = server.check_channel_env_request(self, name, value)
elif key == 'exec':
- cmd = m.get_text()
+ cmd = m.get_string()
if server is None:
ok = False
else:
diff --git a/paramiko/client.py b/paramiko/client.py
index 9ee3028..8d899a1 100644
--- a/paramiko/client.py
+++ b/paramiko/client.py
@@ -25,6 +25,7 @@ import getpass
import os
import socket
import warnings
+from errno import ECONNREFUSED, EHOSTUNREACH
from paramiko.agent import Agent
from paramiko.common import DEBUG
@@ -35,7 +36,9 @@ from paramiko.hostkeys import HostKeys
from paramiko.py3compat import string_types
from paramiko.resource import ResourceManager
from paramiko.rsakey import RSAKey
-from paramiko.ssh_exception import SSHException, BadHostKeyException
+from paramiko.ssh_exception import (
+ SSHException, BadHostKeyException, NoValidConnectionsError
+)
from paramiko.transport import Transport
from paramiko.util import retry_on_signal, ClosingContextManager
@@ -172,10 +175,46 @@ class SSHClient (ClosingContextManager):
"""
self._policy = policy
- def connect(self, hostname, port=SSH_PORT, username=None, password=None, pkey=None,
- key_filename=None, timeout=None, allow_agent=True, look_for_keys=True,
- compress=False, sock=None, gss_auth=False, gss_kex=False,
- gss_deleg_creds=True, gss_host=None, banner_timeout=None):
+ def _families_and_addresses(self, hostname, port):
+ """
+ Yield pairs of address families and addresses to try for connecting.
+
+ :param str hostname: the server to connect to
+ :param int port: the server port to connect to
+ :returns: Yields an iterable of ``(family, address)`` tuples
+ """
+ guess = True
+ addrinfos = socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM)
+ for (family, socktype, proto, canonname, sockaddr) in addrinfos:
+ if socktype == socket.SOCK_STREAM:
+ yield family, sockaddr
+ guess = False
+
+ # some OS like AIX don't indicate SOCK_STREAM support, so just guess. :(
+ # We only do this if we did not get a single result marked as socktype == SOCK_STREAM.
+ if guess:
+ for family, _, _, _, sockaddr in addrinfos:
+ yield family, sockaddr
+
+ def connect(
+ self,
+ hostname,
+ port=SSH_PORT,
+ username=None,
+ password=None,
+ pkey=None,
+ key_filename=None,
+ timeout=None,
+ allow_agent=True,
+ look_for_keys=True,
+ compress=False,
+ sock=None,
+ gss_auth=False,
+ gss_kex=False,
+ gss_deleg_creds=True,
+ gss_host=None,
+ banner_timeout=None
+ ):
"""
Connect to an SSH server and authenticate to it. The server's host key
is checked against the system host keys (see `load_system_host_keys`)
@@ -206,8 +245,10 @@ class SSHClient (ClosingContextManager):
:param str key_filename:
the filename, or list of filenames, of optional private key(s) to
try for authentication
- :param float timeout: an optional timeout (in seconds) for the TCP connect
- :param bool allow_agent: set to False to disable connecting to the SSH agent
+ :param float timeout:
+ an optional timeout (in seconds) for the TCP connect
+ :param bool allow_agent:
+ set to False to disable connecting to the SSH agent
:param bool look_for_keys:
set to False to disable searching for discoverable private key
files in ``~/.ssh/``
@@ -215,10 +256,13 @@ class SSHClient (ClosingContextManager):
:param socket sock:
an open socket or socket-like object (such as a `.Channel`) to use
for communication to the target host
- :param bool gss_auth: ``True`` if you want to use GSS-API authentication
- :param bool gss_kex: Perform GSS-API Key Exchange and user authentication
+ :param bool gss_auth:
+ ``True`` if you want to use GSS-API authentication
+ :param bool gss_kex:
+ Perform GSS-API Key Exchange and user authentication
:param bool gss_deleg_creds: Delegate GSS-API client credentials or not
- :param str gss_host: The targets name in the kerberos database. default: hostname
+ :param str gss_host:
+ The targets name in the kerberos database. default: hostname
:param float banner_timeout: an optional timeout (in seconds) to wait
for the SSH banner to be presented.
@@ -234,21 +278,37 @@ class SSHClient (ClosingContextManager):
``gss_deleg_creds`` and ``gss_host`` arguments.
"""
if not sock:
- for (family, socktype, proto, canonname, sockaddr) in socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM):
- if socktype == socket.SOCK_STREAM:
- af = family
- addr = sockaddr
- break
- else:
- # some OS like AIX don't indicate SOCK_STREAM support, so just guess. :(
- af, _, _, _, addr = socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM)
- sock = socket.socket(af, socket.SOCK_STREAM)
- if timeout is not None:
+ errors = {}
+ # Try multiple possible address families (e.g. IPv4 vs IPv6)
+ to_try = list(self._families_and_addresses(hostname, port))
+ for af, addr in to_try:
try:
- sock.settimeout(timeout)
- except:
- pass
- retry_on_signal(lambda: sock.connect(addr))
+ sock = socket.socket(af, socket.SOCK_STREAM)
+ if timeout is not None:
+ try:
+ sock.settimeout(timeout)
+ except:
+ pass
+ retry_on_signal(lambda: sock.connect(addr))
+ # Break out of the loop on success
+ break
+ except socket.error as e:
+ # Raise anything that isn't a straight up connection error
+ # (such as a resolution error)
+ if e.errno not in (ECONNREFUSED, EHOSTUNREACH):
+ raise
+ # Capture anything else so we know how the run looks once
+ # iteration is complete. Retain info about which attempt
+ # this was.
+ errors[addr] = e
+
+ # Make sure we explode usefully if no address family attempts
+ # succeeded. We've no way of knowing which error is the "right"
+ # one, so we construct a hybrid exception containing all the real
+ # ones, of a subclass that client code should still be watching for
+ # (socket.error)
+ if len(errors) == len(to_try):
+ raise NoValidConnectionsError(errors)
t = self._transport = Transport(sock, gss_kex=gss_kex, gss_deleg_creds=gss_deleg_creds)
t.use_compression(compress=compress)
@@ -404,7 +464,8 @@ class SSHClient (ClosingContextManager):
"""
saved_exception = None
two_factor = False
- allowed_types = []
+ allowed_types = set()
+ two_factor_types = set(['keyboard-interactive','password'])
# If GSS-API support and GSS-PI Key Exchange was performed, we attempt
# authentication with gssapi-keyex.
@@ -430,8 +491,8 @@ class SSHClient (ClosingContextManager):
if pkey is not None:
try:
self._log(DEBUG, 'Trying SSH key %s' % hexlify(pkey.get_fingerprint()))
- allowed_types = self._transport.auth_publickey(username, pkey)
- two_factor = (allowed_types == ['password'])
+ allowed_types = set(self._transport.auth_publickey(username, pkey))
+ two_factor = (allowed_types & two_factor_types)
if not two_factor:
return
except SSHException as e:
@@ -443,8 +504,8 @@ class SSHClient (ClosingContextManager):
try:
key = pkey_class.from_private_key_file(key_filename, password)
self._log(DEBUG, 'Trying key %s from %s' % (hexlify(key.get_fingerprint()), key_filename))
- self._transport.auth_publickey(username, key)
- two_factor = (allowed_types == ['password'])
+ allowed_types = set(self._transport.auth_publickey(username, key))
+ two_factor = (allowed_types & two_factor_types)
if not two_factor:
return
break
@@ -458,9 +519,9 @@ class SSHClient (ClosingContextManager):
for key in self._agent.get_keys():
try:
self._log(DEBUG, 'Trying SSH agent key %s' % hexlify(key.get_fingerprint()))
- # for 2-factor auth a successfully auth'd key will result in ['password']
- allowed_types = self._transport.auth_publickey(username, key)
- two_factor = (allowed_types == ['password'])
+ # for 2-factor auth a successfully auth'd key password will return an allowed 2fac auth method
+ allowed_types = set(self._transport.auth_publickey(username, key))
+ two_factor = (allowed_types & two_factor_types)
if not two_factor:
return
break
@@ -497,8 +558,8 @@ class SSHClient (ClosingContextManager):
key = pkey_class.from_private_key_file(filename, password)
self._log(DEBUG, 'Trying discovered key %s in %s' % (hexlify(key.get_fingerprint()), filename))
# for 2-factor auth a successfully auth'd key will result in ['password']
- allowed_types = self._transport.auth_publickey(username, key)
- two_factor = (allowed_types == ['password'])
+ allowed_types = set(self._transport.auth_publickey(username, key))
+ two_factor = (allowed_types & two_factor_types)
if not two_factor:
return
break
@@ -512,7 +573,11 @@ class SSHClient (ClosingContextManager):
except SSHException as e:
saved_exception = e
elif two_factor:
- raise SSHException('Two-factor authentication requires a password')
+ try:
+ self._transport.auth_interactive_dumb(username)
+ return
+ except SSHException as e:
+ saved_exception = e
# if we got an auth-failed exception earlier, re-raise it
if saved_exception is not None:
diff --git a/paramiko/config.py b/paramiko/config.py
index 233a87d..0b1345f 100644
--- a/paramiko/config.py
+++ b/paramiko/config.py
@@ -53,7 +53,7 @@ class SSHConfig (object):
"""
Read an OpenSSH config from the given file object.
- :param file file_obj: a file-like object to read the config file from
+ :param file_obj: a file-like object to read the config file from
"""
host = {"host": ['*'], "config": {}}
for line in file_obj:
@@ -98,7 +98,7 @@ class SSHConfig (object):
The host-matching rules of OpenSSH's ``ssh_config`` man page are used:
For each parameter, the first obtained value will be used. The
- configuration files contain sections separated by ``Host''
+ configuration files contain sections separated by ``Host``
specifications, and that section is only applied for hosts that match
one of the patterns given in the specification.
diff --git a/paramiko/hostkeys.py b/paramiko/hostkeys.py
index c7e1f72..38ac866 100644
--- a/paramiko/hostkeys.py
+++ b/paramiko/hostkeys.py
@@ -93,7 +93,7 @@ class HostKeys (MutableMapping):
:raises IOError: if there was an error reading the file
"""
with open(filename, 'r') as f:
- for lineno, line in enumerate(f):
+ for lineno, line in enumerate(f, 1):
line = line.strip()
if (len(line) == 0) or (line[0] == '#'):
continue
diff --git a/paramiko/kex_gex.py b/paramiko/kex_gex.py
index cb548f3..c980b69 100644
--- a/paramiko/kex_gex.py
+++ b/paramiko/kex_gex.py
@@ -23,7 +23,7 @@ client side, and a **lot** more on the server side.
"""
import os
-from hashlib import sha1
+from hashlib import sha1, sha256
from paramiko import util
from paramiko.common import DEBUG
@@ -44,6 +44,7 @@ class KexGex (object):
min_bits = 1024
max_bits = 8192
preferred_bits = 2048
+ hash_algo = sha1
def __init__(self, transport):
self.transport = transport
@@ -87,7 +88,7 @@ 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 asked to handle packet type %d' % ptype)
+ raise SSHException('KexGex %s asked to handle packet type %d' % self.name, ptype)
### internals...
@@ -204,7 +205,7 @@ class KexGex (object):
hm.add_mpint(self.e)
hm.add_mpint(self.f)
hm.add_mpint(K)
- H = sha1(hm.asbytes()).digest()
+ H = self.hash_algo(hm.asbytes()).digest()
self.transport._set_K_H(K, H)
# sign it
sig = self.transport.get_server_key().sign_ssh_data(H)
@@ -239,6 +240,10 @@ class KexGex (object):
hm.add_mpint(self.e)
hm.add_mpint(self.f)
hm.add_mpint(K)
- self.transport._set_K_H(K, sha1(hm.asbytes()).digest())
+ self.transport._set_K_H(K, self.hash_algo(hm.asbytes()).digest())
self.transport._verify_key(host_key, sig)
self.transport._activate_outbound()
+
+class KexGexSHA256(KexGex):
+ name = 'diffie-hellman-group-exchange-sha256'
+ hash_algo = sha256
diff --git a/paramiko/kex_group1.py b/paramiko/kex_group1.py
index a88f00d..9eee066 100644
--- a/paramiko/kex_group1.py
+++ b/paramiko/kex_group1.py
@@ -45,6 +45,7 @@ class KexGroup1(object):
G = 2
name = 'diffie-hellman-group1-sha1'
+ hash_algo = sha1
def __init__(self, transport):
self.transport = transport
diff --git a/paramiko/kex_group14.py b/paramiko/kex_group14.py
index a914aea..9f7dd21 100644
--- a/paramiko/kex_group14.py
+++ b/paramiko/kex_group14.py
@@ -22,6 +22,7 @@ Standard SSH key exchange ("kex" if you wanna sound cool). Diffie-Hellman of
"""
from paramiko.kex_group1 import KexGroup1
+from hashlib import sha1
class KexGroup14(KexGroup1):
@@ -31,3 +32,4 @@ class KexGroup14(KexGroup1):
G = 2
name = 'diffie-hellman-group14-sha1'
+ hash_algo = sha1
diff --git a/paramiko/packet.py b/paramiko/packet.py
index b922000..2be2bb2 100644
--- a/paramiko/packet.py
+++ b/paramiko/packet.py
@@ -389,7 +389,8 @@ class Packetizer (object):
if self.__dump_packets:
self._log(DEBUG, util.format_binary(header, 'IN: '))
packet_size = struct.unpack('>I', header[:4])[0]
- # leftover contains decrypted bytes from the first block (after the length field)
+ # leftover contains decrypted bytes from the first block (after the
+ # length field)
leftover = header[4:]
if (packet_size - len(leftover)) % self.__block_size_in != 0:
raise SSHException('Invalid packet blocking')
diff --git a/paramiko/pkey.py b/paramiko/pkey.py
index 1b4af01..e95d60b 100644
--- a/paramiko/pkey.py
+++ b/paramiko/pkey.py
@@ -171,8 +171,9 @@ class PKey (object):
is useless on the abstract PKey class.
:param str filename: name of the file to read
- :param str password: an optional password to use to decrypt the key file,
- if it's encrypted
+ :param str password:
+ an optional password to use to decrypt the key file, if it's
+ encrypted
:return: a new `.PKey` based on the given private key
:raises IOError: if there was an error reading the file
@@ -187,18 +188,18 @@ class PKey (object):
def from_private_key(cls, file_obj, password=None):
"""
Create a key object by reading a private key from a file (or file-like)
- object. If the private key is encrypted and ``password`` is not ``None``,
- the given password will be used to decrypt the key (otherwise
+ object. If the private key is encrypted and ``password`` is not
+ ``None``, the given password will be used to decrypt the key (otherwise
`.PasswordRequiredException` is thrown).
- :param file file_obj: the file to read from
+ :param file_obj: the file-like object to read from
:param str password:
an optional password to use to decrypt the key, if it's encrypted
:return: a new `.PKey` based on the given private key
:raises IOError: if there was an error reading the key
- :raises PasswordRequiredException: if the private key file is encrypted,
- and ``password`` is ``None``
+ :raises PasswordRequiredException:
+ if the private key file is encrypted, and ``password`` is ``None``
:raises SSHException: if the key file is invalid
"""
key = cls(file_obj=file_obj, password=password)
@@ -223,7 +224,7 @@ class PKey (object):
Write private key contents into a file (or file-like) object. If the
password is not ``None``, the key is encrypted before writing.
- :param file file_obj: the file object to write into
+ :param file_obj: the file-like object to write into
:param str password: an optional password to use to encrypt the key
:raises IOError: if there was an error writing to the file
@@ -273,7 +274,7 @@ class PKey (object):
start += 1
# find end
end = start
- while (lines[end].strip() != '-----END ' + tag + ' PRIVATE KEY-----') and (end < len(lines)):
+ while end < len(lines) and lines[end].strip() != '-----END ' + tag + ' PRIVATE KEY-----':
end += 1
# if we trudged to the end of the file, just try to cope.
try:
@@ -309,8 +310,9 @@ class PKey (object):
a trivially-encoded format (base64) which is completely insecure. If
a password is given, DES-EDE3-CBC is used.
- :param str tag: ``"RSA"`` or ``"DSA"``, the tag used to mark the data block.
- :param file filename: name of the file to write.
+ :param str tag:
+ ``"RSA"`` or ``"DSA"``, the tag used to mark the data block.
+ :param filename: name of the file to write.
:param str data: data blob that makes up the private key.
:param str password: an optional password to use to encrypt the file.
diff --git a/paramiko/py3compat.py b/paramiko/py3compat.py
index 57c096b..6fafc31 100644
--- a/paramiko/py3compat.py
+++ b/paramiko/py3compat.py
@@ -3,7 +3,7 @@ import base64
__all__ = ['PY2', 'string_types', 'integer_types', 'text_type', 'bytes_types', 'bytes', 'long', 'input',
'decodebytes', 'encodebytes', 'bytestring', 'byte_ord', 'byte_chr', 'byte_mask',
- 'b', 'u', 'b2s', 'StringIO', 'BytesIO', 'is_callable', 'MAXSIZE', 'next']
+ 'b', 'u', 'b2s', 'StringIO', 'BytesIO', 'is_callable', 'MAXSIZE', 'next', 'builtins']
PY2 = sys.version_info[0] < 3
@@ -18,6 +18,8 @@ if PY2:
decodebytes = base64.decodestring
encodebytes = base64.encodestring
+ import __builtin__ as builtins
+
def bytestring(s): # NOQA
if isinstance(s, unicode):
@@ -102,6 +104,7 @@ if PY2:
else:
import collections
import struct
+ import builtins
string_types = str
text_type = str
bytes = bytes
diff --git a/paramiko/sftp_attr.py b/paramiko/sftp_attr.py
index cf48f65..0eaca30 100644
--- a/paramiko/sftp_attr.py
+++ b/paramiko/sftp_attr.py
@@ -209,12 +209,15 @@ class SFTPAttributes (object):
# not all servers support uid/gid
uid = self.st_uid
gid = self.st_gid
+ size = self.st_size
if uid is None:
uid = 0
if gid is None:
gid = 0
+ if size is None:
+ size = 0
- return '%s 1 %-8d %-8d %8d %-12s %s' % (ks, uid, gid, self.st_size, datestr, filename)
+ return '%s 1 %-8d %-8d %8d %-12s %s' % (ks, uid, gid, size, datestr, filename)
def asbytes(self):
return b(str(self))
diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py
index 89840ea..5722555 100644
--- a/paramiko/sftp_client.py
+++ b/paramiko/sftp_client.py
@@ -589,6 +589,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager):
.. versionadded:: 1.4
"""
+ # TODO: make class initialize with self._cwd set to self.normalize('.')
return self._cwd and u(self._cwd)
def putfo(self, fl, remotepath, file_size=0, callback=None, confirm=True):
@@ -599,7 +600,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager):
The SFTP operations use pipelining for speed.
- :param file fl: opened file or file-like object to copy
+ :param fl: opened file or file-like object to copy
:param str remotepath: the destination path on the SFTP server
:param int file_size:
optional size parameter passed to callback. If none is specified,
@@ -685,9 +686,10 @@ class SFTPClient(BaseSFTP, ClosingContextManager):
.. versionadded:: 1.10
"""
+ file_size = self.stat(remotepath).st_size
with self.open(remotepath, 'rb') as fr:
- file_size = self.stat(remotepath).st_size
- fr.prefetch()
+ fr.prefetch(file_size)
+
size = 0
while True:
data = fr.read(32768)
@@ -715,7 +717,6 @@ class SFTPClient(BaseSFTP, ClosingContextManager):
.. versionchanged:: 1.7.4
Added the ``callback`` param
"""
- file_size = self.stat(remotepath).st_size
with open(localpath, 'wb') as fl:
size = self.getfo(remotepath, fl, callback)
s = os.stat(localpath)
diff --git a/paramiko/sftp_file.py b/paramiko/sftp_file.py
index d0a37da..c5b6548 100644
--- a/paramiko/sftp_file.py
+++ b/paramiko/sftp_file.py
@@ -379,7 +379,7 @@ class SFTPFile (BufferedFile):
"""
self.pipelined = pipelined
- def prefetch(self):
+ def prefetch(self, file_size):
"""
Pre-fetch the remaining contents of this file in anticipation of future
`.read` calls. If reading the entire file, pre-fetching can
@@ -393,12 +393,11 @@ class SFTPFile (BufferedFile):
.. versionadded:: 1.5.1
"""
- size = self.stat().st_size
# queue up async reads for the rest of the file
chunks = []
n = self._realpos
- while n < size:
- chunk = min(self.MAX_REQUEST_SIZE, size - n)
+ while n < file_size:
+ chunk = min(self.MAX_REQUEST_SIZE, file_size - n)
chunks.append((n, chunk))
n += chunk
if len(chunks) > 0:
diff --git a/paramiko/ssh_exception.py b/paramiko/ssh_exception.py
index e120a45..02f3e52 100644
--- a/paramiko/ssh_exception.py
+++ b/paramiko/ssh_exception.py
@@ -16,6 +16,8 @@
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+import socket
+
class SSHException (Exception):
"""
@@ -133,3 +135,39 @@ class ProxyCommandFailure (SSHException):
self.error = error
# for unpickling
self.args = (command, error, )
+
+
+class NoValidConnectionsError(socket.error):
+ """
+ Multiple connection attempts were made and no families succeeded.
+
+ This exception class wraps multiple "real" underlying connection errors,
+ all of which represent failed connection attempts. Because these errors are
+ not guaranteed to all be of the same error type (i.e. different errno,
+ `socket.error` subclass, message, etc) we expose a single unified error
+ message and a ``None`` errno so that instances of this class match most
+ normal handling of `socket.error` objects.
+
+ To see the wrapped exception objects, access the ``errors`` attribute.
+ ``errors`` is a dict whose keys are address tuples (e.g. ``('127.0.0.1',
+ 22)``) and whose values are the exception encountered trying to connect to
+ that address.
+
+ It is implied/assumed that all the errors given to a single instance of
+ this class are from connecting to the same hostname + port (and thus that
+ the differences are in the resolution of the hostname - e.g. IPv4 vs v6).
+ """
+ def __init__(self, errors):
+ """
+ :param dict errors:
+ The errors dict to store, as described by class docstring.
+ """
+ addrs = errors.keys()
+ body = ', '.join([x[0] for x in addrs[:-1]])
+ tail = addrs[-1][0]
+ msg = "Unable to connect to port {0} on {1} or {2}"
+ super(NoValidConnectionsError, self).__init__(
+ None, # stand-in for errno
+ msg.format(addrs[0][1], body, tail)
+ )
+ self.errors = errors
diff --git a/paramiko/transport.py b/paramiko/transport.py
index 31c27a2..18fb103 100644
--- a/paramiko/transport.py
+++ b/paramiko/transport.py
@@ -20,13 +20,14 @@
Core protocol implementation
"""
+from __future__ import print_function
import os
import socket
import sys
import threading
import time
import weakref
-from hashlib import md5, sha1
+from hashlib import md5, sha1, sha256, sha512
import paramiko
from paramiko import util
@@ -47,7 +48,7 @@ from paramiko.common import xffffffff, cMSG_CHANNEL_OPEN, cMSG_IGNORE, \
MAX_WINDOW_SIZE, DEFAULT_WINDOW_SIZE, DEFAULT_MAX_PACKET_SIZE
from paramiko.compress import ZlibCompressor, ZlibDecompressor
from paramiko.dsskey import DSSKey
-from paramiko.kex_gex import KexGex
+from paramiko.kex_gex import KexGex, KexGexSHA256
from paramiko.kex_group1 import KexGroup1
from paramiko.kex_group14 import KexGroup14
from paramiko.kex_gss import KexGSSGex, KexGSSGroup1, KexGSSGroup14, NullHostKey
@@ -94,27 +95,109 @@ class Transport (threading.Thread, ClosingContextManager):
_PROTO_ID = '2.0'
_CLIENT_ID = 'paramiko_%s' % paramiko.__version__
- _preferred_ciphers = ('aes128-ctr', 'aes256-ctr', 'aes128-cbc', 'blowfish-cbc',
- 'aes256-cbc', '3des-cbc', 'arcfour128', 'arcfour256')
- _preferred_macs = ('hmac-sha1', 'hmac-md5', 'hmac-sha1-96', 'hmac-md5-96')
- _preferred_keys = ('ssh-rsa', 'ssh-dss', 'ecdsa-sha2-nistp256')
- _preferred_kex = ( 'diffie-hellman-group14-sha1', 'diffie-hellman-group-exchange-sha1' , 'diffie-hellman-group1-sha1')
+ # These tuples of algorithm identifiers are in preference order; do not
+ # reorder without reason!
+ _preferred_ciphers = (
+ 'aes128-ctr',
+ 'aes192-ctr',
+ 'aes256-ctr',
+ 'aes128-cbc',
+ 'blowfish-cbc',
+ 'aes192-cbc',
+ 'aes256-cbc',
+ '3des-cbc',
+ 'arcfour128',
+ 'arcfour256',
+ )
+ _preferred_macs = (
+ 'hmac-sha2-256',
+ 'hmac-sha2-512',
+ 'hmac-md5',
+ 'hmac-sha1-96',
+ 'hmac-md5-96',
+ 'hmac-sha1',
+ )
+ _preferred_keys = (
+ 'ssh-rsa',
+ 'ssh-dss',
+ 'ecdsa-sha2-nistp256',
+ )
+ _preferred_kex = (
+ 'diffie-hellman-group1-sha1',
+ 'diffie-hellman-group14-sha1',
+ 'diffie-hellman-group-exchange-sha1',
+ 'diffie-hellman-group-exchange-sha256',
+ )
_preferred_compression = ('none',)
_cipher_info = {
- 'aes128-ctr': {'class': AES, 'mode': AES.MODE_CTR, 'block-size': 16, 'key-size': 16},
- 'aes256-ctr': {'class': AES, 'mode': AES.MODE_CTR, 'block-size': 16, 'key-size': 32},
- 'blowfish-cbc': {'class': Blowfish, 'mode': Blowfish.MODE_CBC, 'block-size': 8, 'key-size': 16},
- 'aes128-cbc': {'class': AES, 'mode': AES.MODE_CBC, 'block-size': 16, 'key-size': 16},
- 'aes256-cbc': {'class': AES, 'mode': AES.MODE_CBC, 'block-size': 16, 'key-size': 32},
- '3des-cbc': {'class': DES3, 'mode': DES3.MODE_CBC, 'block-size': 8, 'key-size': 24},
- 'arcfour128': {'class': ARC4, 'mode': None, 'block-size': 8, 'key-size': 16},
- 'arcfour256': {'class': ARC4, 'mode': None, 'block-size': 8, 'key-size': 32},
+ 'aes128-ctr': {
+ 'class': AES,
+ 'mode': AES.MODE_CTR,
+ 'block-size': 16,
+ 'key-size': 16
+ },
+ 'aes192-ctr': {
+ 'class': AES,
+ 'mode': AES.MODE_CTR,
+ 'block-size': 16,
+ 'key-size': 24
+ },
+ 'aes256-ctr': {
+ 'class': AES,
+ 'mode': AES.MODE_CTR,
+ 'block-size': 16,
+ 'key-size': 32
+ },
+ 'blowfish-cbc': {
+ 'class': Blowfish,
+ 'mode': Blowfish.MODE_CBC,
+ 'block-size': 8,
+ 'key-size': 16
+ },
+ 'aes128-cbc': {
+ 'class': AES,
+ 'mode': AES.MODE_CBC,
+ 'block-size': 16,
+ 'key-size': 16
+ },
+ 'aes192-cbc': {
+ 'class': AES,
+ 'mode': AES.MODE_CBC,
+ 'block-size': 16,
+ 'key-size': 24
+ },
+ 'aes256-cbc': {
+ 'class': AES,
+ 'mode': AES.MODE_CBC,
+ 'block-size': 16,
+ 'key-size': 32
+ },
+ '3des-cbc': {
+ 'class': DES3,
+ 'mode': DES3.MODE_CBC,
+ 'block-size': 8,
+ 'key-size': 24
+ },
+ 'arcfour128': {
+ 'class': ARC4,
+ 'mode': None,
+ 'block-size': 8,
+ 'key-size': 16
+ },
+ 'arcfour256': {
+ 'class': ARC4,
+ 'mode': None,
+ 'block-size': 8,
+ 'key-size': 32
+ },
}
_mac_info = {
'hmac-sha1': {'class': sha1, 'size': 20},
'hmac-sha1-96': {'class': sha1, 'size': 12},
+ 'hmac-sha2-256': {'class': sha256, 'size': 32},
+ 'hmac-sha2-512': {'class': sha512, 'size': 64},
'hmac-md5': {'class': md5, 'size': 16},
'hmac-md5-96': {'class': md5, 'size': 12},
}
@@ -129,6 +212,7 @@ class Transport (threading.Thread, ClosingContextManager):
'diffie-hellman-group1-sha1': KexGroup1,
'diffie-hellman-group14-sha1': KexGroup14,
'diffie-hellman-group-exchange-sha1': KexGex,
+ 'diffie-hellman-group-exchange-sha256': KexGexSHA256,
'gss-group1-sha1-toWM5Slw5Ew8Mqkay+al2g==': KexGSSGroup1,
'gss-group14-sha1-toWM5Slw5Ew8Mqkay+al2g==': KexGSSGroup14,
'gss-gex-sha1-toWM5Slw5Ew8Mqkay+al2g==': KexGSSGex
@@ -1294,6 +1378,27 @@ class Transport (threading.Thread, ClosingContextManager):
self.auth_handler.auth_interactive(username, handler, my_event, submethods)
return self.auth_handler.wait_for_response(my_event)
+ def auth_interactive_dumb(self, username, handler=None, submethods=''):
+ """
+ Autenticate to the server interactively but dumber.
+ Just print the prompt and / or instructions to stdout and send back
+ the response. This is good for situations where partial auth is
+ achieved by key and then the user has to enter a 2fac token.
+ """
+
+ if not handler:
+ def handler(title, instructions, prompt_list):
+ answers = []
+ if title:
+ print(title.strip())
+ if instructions:
+ print(instructions.strip())
+ for prompt,show_input in prompt_list:
+ print(prompt.strip(),end=' ')
+ answers.append(raw_input())
+ return answers
+ return self.auth_interactive(username, handler, submethods)
+
def auth_gssapi_with_mic(self, username, gss_host, gss_deleg_creds):
"""
Authenticate to the Server using GSS-API / SSPI.
@@ -1507,13 +1612,23 @@ class Transport (threading.Thread, ClosingContextManager):
m.add_bytes(self.H)
m.add_byte(b(id))
m.add_bytes(self.session_id)
- out = sofar = sha1(m.asbytes()).digest()
+ # 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)
+ if hash_algo is None:
+ hash_algo = sha1
+ hash_select_msg += ", falling back to sha1"
+ if not hasattr(self, '_logged_hash_selection'):
+ self._log(DEBUG, hash_select_msg)
+ setattr(self, '_logged_hash_selection', True)
+ out = sofar = hash_algo(m.asbytes()).digest()
while len(out) < nbytes:
m = Message()
m.add_mpint(self.K)
m.add_bytes(self.H)
m.add_bytes(sofar)
- digest = sha1(m.asbytes()).digest()
+ digest = hash_algo(m.asbytes()).digest()
out += digest
sofar += digest
return out[:nbytes]
@@ -1591,10 +1706,13 @@ class Transport (threading.Thread, ClosingContextManager):
try:
try:
self.packetizer.write_all(b(self.local_version + '\r\n'))
+ self._log(DEBUG, 'Local version/idstring: %s' % self.local_version)
self._check_banner()
- # The above is actually very much part of the handshake, but sometimes the banner can be read
- # but the machine is not responding, for example when the remote ssh daemon is loaded in to memory
- # but we can not read from the disk/spawn a new shell.
+ # The above is actually very much part of the handshake, but
+ # sometimes the banner can be read but the machine is not
+ # responding, for example when the remote ssh daemon is loaded
+ # in to memory but we can not read from the disk/spawn a new
+ # shell.
# Make sure we can specify a timeout for the initial handshake.
# Re-use the banner timeout for now.
self.packetizer.start_handshake(self.handshake_timeout)
@@ -1696,6 +1814,18 @@ class Transport (threading.Thread, ClosingContextManager):
if self.sys.modules is not None:
raise
+
+ def _log_agreement(self, which, local, remote):
+ # 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)
+ if local == remote:
+ msg += local
+ else:
+ msg += "local={0}, remote={1}".format(local, remote)
+ self._log(DEBUG, msg)
+
### protocol stages
def _negotiate_keys(self, m):
@@ -1733,6 +1863,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)
# pull off any attached comment
comment = ''
i = buf.find(' ')
@@ -1761,10 +1892,12 @@ class Transport (threading.Thread, ClosingContextManager):
self.clear_to_send_lock.release()
self.in_kex = True
if self.server_mode:
- if (self._modulus_pack is None) and ('diffie-hellman-group-exchange-sha1' in self._preferred_kex):
+ mp_required_prefix = 'diffie-hellman-group-exchange-sha'
+ kex_mp = [k for k in self._preferred_kex if k.startswith(mp_required_prefix)]
+ if (self._modulus_pack is None) and (len(kex_mp) > 0):
# can't do group-exchange if we don't have a pack of potential primes
- pkex = list(self.get_security_options().kex)
- pkex.remove('diffie-hellman-group-exchange-sha1')
+ pkex = [k for k in self.get_security_options().kex
+ if not k.startswith(mp_required_prefix)]
self.get_security_options().kex = pkex
available_server_keys = list(filter(list(self.server_key_dict.keys()).__contains__,
self._preferred_keys))
@@ -1816,15 +1949,24 @@ class Transport (threading.Thread, ClosingContextManager):
' server lang:' + str(server_lang_list) +
' kex follows?' + str(kex_follows))
- # as a server, we pick the first item in the client's list that we support.
- # as a client, we pick the first item in our list that the server supports.
+ # as a server, we pick the first item in the client's list that we
+ # support.
+ # as a client, we pick the first item in our list that the server
+ # supports.
if self.server_mode:
- agreed_kex = list(filter(self._preferred_kex.__contains__, kex_algo_list))
+ agreed_kex = list(filter(
+ self._preferred_kex.__contains__,
+ kex_algo_list
+ ))
else:
- agreed_kex = list(filter(kex_algo_list.__contains__, self._preferred_kex))
+ agreed_kex = list(filter(
+ kex_algo_list.__contains__,
+ self._preferred_kex
+ ))
if len(agreed_kex) == 0:
raise SSHException('Incompatible ssh peer (no acceptable kex algorithm)')
self.kex_engine = self._kex_info[agreed_kex[0]](self)
+ self._log(DEBUG, "Kex agreed: %s" % agreed_kex[0])
if self.server_mode:
available_server_keys = list(filter(list(self.server_key_dict.keys()).__contains__,
@@ -1852,7 +1994,9 @@ class Transport (threading.Thread, ClosingContextManager):
raise SSHException('Incompatible ssh server (no acceptable ciphers)')
self.local_cipher = agreed_local_ciphers[0]
self.remote_cipher = agreed_remote_ciphers[0]
- self._log(DEBUG, 'Ciphers agreed: local=%s, remote=%s' % (self.local_cipher, self.remote_cipher))
+ self._log_agreement(
+ 'Cipher', local=self.local_cipher, remote=self.remote_cipher
+ )
if self.server_mode:
agreed_remote_macs = list(filter(self._preferred_macs.__contains__, client_mac_algo_list))
@@ -1864,6 +2008,9 @@ class Transport (threading.Thread, ClosingContextManager):
raise SSHException('Incompatible ssh server (no acceptable macs)')
self.local_mac = agreed_local_macs[0]
self.remote_mac = agreed_remote_macs[0]
+ self._log_agreement(
+ 'MAC', local=self.local_mac, remote=self.remote_mac
+ )
if self.server_mode:
agreed_remote_compression = list(filter(self._preferred_compression.__contains__, client_compress_algo_list))
@@ -1875,10 +2022,11 @@ class Transport (threading.Thread, ClosingContextManager):
raise SSHException('Incompatible ssh server (no acceptable compression) %r %r %r' % (agreed_local_compression, agreed_remote_compression, self._preferred_compression))
self.local_compression = agreed_local_compression[0]
self.remote_compression = agreed_remote_compression[0]
-
- self._log(DEBUG, 'using kex %s; server key type %s; cipher: local %s, remote %s; mac: local %s, remote %s; compression: local %s, remote %s' %
- (agreed_kex[0], self.host_key_type, self.local_cipher, self.remote_cipher, self.local_mac,
- self.remote_mac, self.local_compression, self.remote_compression))
+ self._log_agreement(
+ 'Compression',
+ local=self.local_compression,
+ remote=self.remote_compression
+ )
# save for computing hash later...
# now wait! openssh has a bug (and others might too) where there are
@@ -1899,8 +2047,8 @@ class Transport (threading.Thread, ClosingContextManager):
engine = self._get_cipher(self.remote_cipher, key_in, IV_in)
mac_size = self._mac_info[self.remote_mac]['size']
mac_engine = self._mac_info[self.remote_mac]['class']
- # initial mac keys are done in the hash's natural size (not the potentially truncated
- # transmission size)
+ # initial mac keys are done in the hash's natural size (not the
+ # potentially truncated transmission size)
if self.server_mode:
mac_key = self._compute_key('E', mac_engine().digest_size)
else:
@@ -1926,8 +2074,8 @@ class Transport (threading.Thread, ClosingContextManager):
engine = self._get_cipher(self.local_cipher, key_out, IV_out)
mac_size = self._mac_info[self.local_mac]['size']
mac_engine = self._mac_info[self.local_mac]['class']
- # initial mac keys are done in the hash's natural size (not the potentially truncated
- # transmission size)
+ # initial mac keys are done in the hash's natural size (not the
+ # potentially truncated transmission size)
if self.server_mode:
mac_key = self._compute_key('F', mac_engine().digest_size)
else:
diff --git a/paramiko/util.py b/paramiko/util.py
index d9a29d7..855e575 100644
--- a/paramiko/util.py
+++ b/paramiko/util.py
@@ -118,7 +118,7 @@ def safe_string(s):
def bit_length(n):
try:
- return n.bitlength()
+ return n.bit_length()
except AttributeError:
norm = deflate_long(n, False)
hbyte = byte_ord(norm[0])