bug 363163: copy the SSHClient AF patch to transport.py too.
This commit is contained in:
parent
d23ae79466
commit
e80cec73b6
|
@ -43,10 +43,10 @@ class MissingHostKeyPolicy (object):
|
||||||
application's keys. Pre-made classes implement policies for automatically
|
application's keys. Pre-made classes implement policies for automatically
|
||||||
adding the key to the application's L{HostKeys} object (L{AutoAddPolicy}),
|
adding the key to the application's L{HostKeys} object (L{AutoAddPolicy}),
|
||||||
and for automatically rejecting the key (L{RejectPolicy}).
|
and for automatically rejecting the key (L{RejectPolicy}).
|
||||||
|
|
||||||
This function may be used to ask the user to verify the key, for example.
|
This function may be used to ask the user to verify the key, for example.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def missing_host_key(self, client, hostname, key):
|
def missing_host_key(self, client, hostname, key):
|
||||||
"""
|
"""
|
||||||
Called when an L{SSHClient} receives a server key for a server that
|
Called when an L{SSHClient} receives a server key for a server that
|
||||||
|
@ -62,7 +62,7 @@ class AutoAddPolicy (MissingHostKeyPolicy):
|
||||||
Policy for automatically adding the hostname and new host key to the
|
Policy for automatically adding the hostname and new host key to the
|
||||||
local L{HostKeys} object, and saving it. This is used by L{SSHClient}.
|
local L{HostKeys} object, and saving it. This is used by L{SSHClient}.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def missing_host_key(self, client, hostname, key):
|
def missing_host_key(self, client, hostname, key):
|
||||||
client._host_keys.add(hostname, key.get_name(), key)
|
client._host_keys.add(hostname, key.get_name(), key)
|
||||||
if client._host_keys_filename is not None:
|
if client._host_keys_filename is not None:
|
||||||
|
@ -76,7 +76,7 @@ class RejectPolicy (MissingHostKeyPolicy):
|
||||||
Policy for automatically rejecting the unknown hostname & key. This is
|
Policy for automatically rejecting the unknown hostname & key. This is
|
||||||
used by L{SSHClient}.
|
used by L{SSHClient}.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def missing_host_key(self, client, hostname, key):
|
def missing_host_key(self, client, hostname, key):
|
||||||
client._log(DEBUG, 'Rejecting %s host key for %s: %s' %
|
client._log(DEBUG, 'Rejecting %s host key for %s: %s' %
|
||||||
(key.get_name(), hostname, hexlify(key.get_fingerprint())))
|
(key.get_name(), hostname, hexlify(key.get_fingerprint())))
|
||||||
|
@ -98,16 +98,16 @@ class SSHClient (object):
|
||||||
A high-level representation of a session with an SSH server. This class
|
A high-level representation of a session with an SSH server. This class
|
||||||
wraps L{Transport}, L{Channel}, and L{SFTPClient} to take care of most
|
wraps L{Transport}, L{Channel}, and L{SFTPClient} to take care of most
|
||||||
aspects of authenticating and opening channels. A typical use case is::
|
aspects of authenticating and opening channels. A typical use case is::
|
||||||
|
|
||||||
client = SSHClient()
|
client = SSHClient()
|
||||||
client.load_system_host_keys()
|
client.load_system_host_keys()
|
||||||
client.connect('ssh.example.com')
|
client.connect('ssh.example.com')
|
||||||
stdin, stdout, stderr = client.exec_command('ls -l')
|
stdin, stdout, stderr = client.exec_command('ls -l')
|
||||||
|
|
||||||
You may pass in explicit overrides for authentication and server host key
|
You may pass in explicit overrides for authentication and server host key
|
||||||
checking. The default mechanism is to try to use local key files or an
|
checking. The default mechanism is to try to use local key files or an
|
||||||
SSH agent (if one is running).
|
SSH agent (if one is running).
|
||||||
|
|
||||||
@since: 1.6
|
@since: 1.6
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -121,16 +121,16 @@ class SSHClient (object):
|
||||||
self._log_channel = None
|
self._log_channel = None
|
||||||
self._policy = RejectPolicy()
|
self._policy = RejectPolicy()
|
||||||
self._transport = None
|
self._transport = None
|
||||||
|
|
||||||
def load_system_host_keys(self, filename=None):
|
def load_system_host_keys(self, filename=None):
|
||||||
"""
|
"""
|
||||||
Load host keys from a system (read-only) file. Host keys read with
|
Load host keys from a system (read-only) file. Host keys read with
|
||||||
this method will not be saved back by L{save_host_keys}.
|
this method will not be saved back by L{save_host_keys}.
|
||||||
|
|
||||||
This method can be called multiple times. Each new set of host keys
|
This method can be called multiple times. Each new set of host keys
|
||||||
will be merged with the existing set (new replacing old if there are
|
will be merged with the existing set (new replacing old if there are
|
||||||
conflicts).
|
conflicts).
|
||||||
|
|
||||||
If C{filename} is left as C{None}, an attempt will be made to read
|
If C{filename} is left as C{None}, an attempt will be made to read
|
||||||
keys from the user's local "known hosts" file, as used by OpenSSH,
|
keys from the user's local "known hosts" file, as used by OpenSSH,
|
||||||
and no exception will be raised if the file can't be read. This is
|
and no exception will be raised if the file can't be read. This is
|
||||||
|
@ -138,7 +138,7 @@ class SSHClient (object):
|
||||||
|
|
||||||
@param filename: the filename to read, or C{None}
|
@param filename: the filename to read, or C{None}
|
||||||
@type filename: str
|
@type filename: str
|
||||||
|
|
||||||
@raise IOError: if a filename was provided and the file could not be
|
@raise IOError: if a filename was provided and the file could not be
|
||||||
read
|
read
|
||||||
"""
|
"""
|
||||||
|
@ -151,7 +151,7 @@ class SSHClient (object):
|
||||||
pass
|
pass
|
||||||
return
|
return
|
||||||
self._system_host_keys.load(filename)
|
self._system_host_keys.load(filename)
|
||||||
|
|
||||||
def load_host_keys(self, filename):
|
def load_host_keys(self, filename):
|
||||||
"""
|
"""
|
||||||
Load host keys from a local host-key file. Host keys read with this
|
Load host keys from a local host-key file. Host keys read with this
|
||||||
|
@ -159,11 +159,11 @@ class SSHClient (object):
|
||||||
but will be saved back by L{save_host_keys} (so they can be modified).
|
but will be saved back by L{save_host_keys} (so they can be modified).
|
||||||
The missing host key policy L{AutoAddPolicy} adds keys to this set and
|
The missing host key policy L{AutoAddPolicy} adds keys to this set and
|
||||||
saves them, when connecting to a previously-unknown server.
|
saves them, when connecting to a previously-unknown server.
|
||||||
|
|
||||||
This method can be called multiple times. Each new set of host keys
|
This method can be called multiple times. Each new set of host keys
|
||||||
will be merged with the existing set (new replacing old if there are
|
will be merged with the existing set (new replacing old if there are
|
||||||
conflicts). When automatically saving, the last hostname is used.
|
conflicts). When automatically saving, the last hostname is used.
|
||||||
|
|
||||||
@param filename: the filename to read
|
@param filename: the filename to read
|
||||||
@type filename: str
|
@type filename: str
|
||||||
|
|
||||||
|
@ -171,16 +171,16 @@ class SSHClient (object):
|
||||||
"""
|
"""
|
||||||
self._host_keys_filename = filename
|
self._host_keys_filename = filename
|
||||||
self._host_keys.load(filename)
|
self._host_keys.load(filename)
|
||||||
|
|
||||||
def save_host_keys(self, filename):
|
def save_host_keys(self, filename):
|
||||||
"""
|
"""
|
||||||
Save the host keys back to a file. Only the host keys loaded with
|
Save the host keys back to a file. Only the host keys loaded with
|
||||||
L{load_host_keys} (plus any added directly) will be saved -- not any
|
L{load_host_keys} (plus any added directly) will be saved -- not any
|
||||||
host keys loaded with L{load_system_host_keys}.
|
host keys loaded with L{load_system_host_keys}.
|
||||||
|
|
||||||
@param filename: the filename to save to
|
@param filename: the filename to save to
|
||||||
@type filename: str
|
@type filename: str
|
||||||
|
|
||||||
@raise IOError: if the file could not be written
|
@raise IOError: if the file could not be written
|
||||||
"""
|
"""
|
||||||
f = open(filename, 'w')
|
f = open(filename, 'w')
|
||||||
|
@ -189,17 +189,17 @@ class SSHClient (object):
|
||||||
for keytype, key in keys.iteritems():
|
for keytype, key in keys.iteritems():
|
||||||
f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))
|
f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
def get_host_keys(self):
|
def get_host_keys(self):
|
||||||
"""
|
"""
|
||||||
Get the local L{HostKeys} object. This can be used to examine the
|
Get the local L{HostKeys} object. This can be used to examine the
|
||||||
local host keys or change them.
|
local host keys or change them.
|
||||||
|
|
||||||
@return: the local host keys
|
@return: the local host keys
|
||||||
@rtype: L{HostKeys}
|
@rtype: L{HostKeys}
|
||||||
"""
|
"""
|
||||||
return self._host_keys
|
return self._host_keys
|
||||||
|
|
||||||
def set_log_channel(self, name):
|
def set_log_channel(self, name):
|
||||||
"""
|
"""
|
||||||
Set the channel for logging. The default is C{"paramiko.transport"}
|
Set the channel for logging. The default is C{"paramiko.transport"}
|
||||||
|
@ -209,14 +209,14 @@ class SSHClient (object):
|
||||||
@type name: str
|
@type name: str
|
||||||
"""
|
"""
|
||||||
self._log_channel = name
|
self._log_channel = name
|
||||||
|
|
||||||
def set_missing_host_key_policy(self, policy):
|
def set_missing_host_key_policy(self, policy):
|
||||||
"""
|
"""
|
||||||
Set the policy to use when connecting to a server that doesn't have a
|
Set the policy to use when connecting to a server that doesn't have a
|
||||||
host key in either the system or local L{HostKeys} objects. The
|
host key in either the system or local L{HostKeys} objects. The
|
||||||
default policy is to reject all unknown servers (using L{RejectPolicy}).
|
default policy is to reject all unknown servers (using L{RejectPolicy}).
|
||||||
You may substitute L{AutoAddPolicy} or write your own policy class.
|
You may substitute L{AutoAddPolicy} or write your own policy class.
|
||||||
|
|
||||||
@param policy: the policy to use when receiving a host key from a
|
@param policy: the policy to use when receiving a host key from a
|
||||||
previously-unknown server
|
previously-unknown server
|
||||||
@type policy: L{MissingHostKeyPolicy}
|
@type policy: L{MissingHostKeyPolicy}
|
||||||
|
@ -232,14 +232,14 @@ class SSHClient (object):
|
||||||
is not found in either set of host keys, the missing host key policy
|
is not found in either set of host keys, the missing host key policy
|
||||||
is used (see L{set_missing_host_key_policy}). The default policy is
|
is used (see L{set_missing_host_key_policy}). The default policy is
|
||||||
to reject the key and raise an L{SSHException}.
|
to reject the key and raise an L{SSHException}.
|
||||||
|
|
||||||
Authentication is attempted in the following order of priority:
|
Authentication is attempted in the following order of priority:
|
||||||
|
|
||||||
- The C{pkey} or C{key_filename} passed in (if any)
|
- The C{pkey} or C{key_filename} passed in (if any)
|
||||||
- Any key we can find through an SSH agent
|
- Any key we can find through an SSH agent
|
||||||
- Any "id_rsa" or "id_dsa" key discoverable in C{~/.ssh/}
|
- Any "id_rsa" or "id_dsa" key discoverable in C{~/.ssh/}
|
||||||
- Plain username/password auth, if a password was given
|
- Plain username/password auth, if a password was given
|
||||||
|
|
||||||
If a private key requires a password to unlock it, and a password is
|
If a private key requires a password to unlock it, and a password is
|
||||||
passed in, that password will be used to attempt to unlock the key.
|
passed in, that password will be used to attempt to unlock the key.
|
||||||
|
|
||||||
|
@ -273,9 +273,8 @@ class SSHClient (object):
|
||||||
establishing an SSH session
|
establishing an SSH session
|
||||||
@raise socket.error: if a socket error occurred while connecting
|
@raise socket.error: if a socket error occurred while connecting
|
||||||
"""
|
"""
|
||||||
for (family, socktype, proto, canonname, sockaddr) in \
|
for (family, socktype, proto, canonname, sockaddr) in socket.getaddrinfo(hostname, port):
|
||||||
socket.getaddrinfo(hostname, port):
|
if socktype == socket.SOCK_STREAM:
|
||||||
if socktype==socket.SOCK_STREAM:
|
|
||||||
af = family
|
af = family
|
||||||
addr = sockaddr
|
addr = sockaddr
|
||||||
break
|
break
|
||||||
|
@ -294,10 +293,10 @@ class SSHClient (object):
|
||||||
t.set_log_channel(self._log_channel)
|
t.set_log_channel(self._log_channel)
|
||||||
t.start_client()
|
t.start_client()
|
||||||
ResourceManager.register(self, t)
|
ResourceManager.register(self, t)
|
||||||
|
|
||||||
server_key = t.get_remote_server_key()
|
server_key = t.get_remote_server_key()
|
||||||
keytype = server_key.get_name()
|
keytype = server_key.get_name()
|
||||||
|
|
||||||
our_server_key = self._system_host_keys.get(hostname, {}).get(keytype, None)
|
our_server_key = self._system_host_keys.get(hostname, {}).get(keytype, None)
|
||||||
if our_server_key is None:
|
if our_server_key is None:
|
||||||
our_server_key = self._host_keys.get(hostname, {}).get(keytype, None)
|
our_server_key = self._host_keys.get(hostname, {}).get(keytype, None)
|
||||||
|
@ -306,13 +305,13 @@ class SSHClient (object):
|
||||||
self._policy.missing_host_key(self, hostname, server_key)
|
self._policy.missing_host_key(self, hostname, server_key)
|
||||||
# if the callback returns, assume the key is ok
|
# if the callback returns, assume the key is ok
|
||||||
our_server_key = server_key
|
our_server_key = server_key
|
||||||
|
|
||||||
if server_key != our_server_key:
|
if server_key != our_server_key:
|
||||||
raise BadHostKeyException(hostname, server_key, our_server_key)
|
raise BadHostKeyException(hostname, server_key, our_server_key)
|
||||||
|
|
||||||
if username is None:
|
if username is None:
|
||||||
username = getpass.getuser()
|
username = getpass.getuser()
|
||||||
|
|
||||||
if key_filename is None:
|
if key_filename is None:
|
||||||
key_filenames = []
|
key_filenames = []
|
||||||
elif isinstance(key_filename, (str, unicode)):
|
elif isinstance(key_filename, (str, unicode)):
|
||||||
|
@ -320,7 +319,7 @@ class SSHClient (object):
|
||||||
else:
|
else:
|
||||||
key_filenames = key_filename
|
key_filenames = key_filename
|
||||||
self._auth(username, password, pkey, key_filenames, allow_agent, look_for_keys)
|
self._auth(username, password, pkey, key_filenames, allow_agent, look_for_keys)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""
|
"""
|
||||||
Close this SSHClient and its underlying L{Transport}.
|
Close this SSHClient and its underlying L{Transport}.
|
||||||
|
@ -336,7 +335,7 @@ class SSHClient (object):
|
||||||
the requested command is executed. The command's input and output
|
the requested command is executed. The command's input and output
|
||||||
streams are returned as python C{file}-like objects representing
|
streams are returned as python C{file}-like objects representing
|
||||||
stdin, stdout, and stderr.
|
stdin, stdout, and stderr.
|
||||||
|
|
||||||
@param command: the command to execute
|
@param command: the command to execute
|
||||||
@type command: str
|
@type command: str
|
||||||
@param bufsize: interpreted the same way as by the built-in C{file()} function in python
|
@param bufsize: interpreted the same way as by the built-in C{file()} function in python
|
||||||
|
@ -358,7 +357,7 @@ class SSHClient (object):
|
||||||
Start an interactive shell session on the SSH server. A new L{Channel}
|
Start an interactive shell session on the SSH server. A new L{Channel}
|
||||||
is opened and connected to a pseudo-terminal using the requested
|
is opened and connected to a pseudo-terminal using the requested
|
||||||
terminal type and size.
|
terminal type and size.
|
||||||
|
|
||||||
@param term: the terminal type to emulate (for example, C{"vt100"})
|
@param term: the terminal type to emulate (for example, C{"vt100"})
|
||||||
@type term: str
|
@type term: str
|
||||||
@param width: the width (in characters) of the terminal window
|
@param width: the width (in characters) of the terminal window
|
||||||
|
@ -367,47 +366,47 @@ class SSHClient (object):
|
||||||
@type height: int
|
@type height: int
|
||||||
@return: a new channel connected to the remote shell
|
@return: a new channel connected to the remote shell
|
||||||
@rtype: L{Channel}
|
@rtype: L{Channel}
|
||||||
|
|
||||||
@raise SSHException: if the server fails to invoke a shell
|
@raise SSHException: if the server fails to invoke a shell
|
||||||
"""
|
"""
|
||||||
chan = self._transport.open_session()
|
chan = self._transport.open_session()
|
||||||
chan.get_pty(term, width, height)
|
chan.get_pty(term, width, height)
|
||||||
chan.invoke_shell()
|
chan.invoke_shell()
|
||||||
return chan
|
return chan
|
||||||
|
|
||||||
def open_sftp(self):
|
def open_sftp(self):
|
||||||
"""
|
"""
|
||||||
Open an SFTP session on the SSH server.
|
Open an SFTP session on the SSH server.
|
||||||
|
|
||||||
@return: a new SFTP session object
|
@return: a new SFTP session object
|
||||||
@rtype: L{SFTPClient}
|
@rtype: L{SFTPClient}
|
||||||
"""
|
"""
|
||||||
return self._transport.open_sftp_client()
|
return self._transport.open_sftp_client()
|
||||||
|
|
||||||
def get_transport(self):
|
def get_transport(self):
|
||||||
"""
|
"""
|
||||||
Return the underlying L{Transport} object for this SSH connection.
|
Return the underlying L{Transport} object for this SSH connection.
|
||||||
This can be used to perform lower-level tasks, like opening specific
|
This can be used to perform lower-level tasks, like opening specific
|
||||||
kinds of channels.
|
kinds of channels.
|
||||||
|
|
||||||
@return: the Transport for this connection
|
@return: the Transport for this connection
|
||||||
@rtype: L{Transport}
|
@rtype: L{Transport}
|
||||||
"""
|
"""
|
||||||
return self._transport
|
return self._transport
|
||||||
|
|
||||||
def _auth(self, username, password, pkey, key_filenames, allow_agent, look_for_keys):
|
def _auth(self, username, password, pkey, key_filenames, allow_agent, look_for_keys):
|
||||||
"""
|
"""
|
||||||
Try, in order:
|
Try, in order:
|
||||||
|
|
||||||
- The key passed in, if one was passed in.
|
- The key passed in, if one was passed in.
|
||||||
- Any key we can find through an SSH agent (if allowed).
|
- Any key we can find through an SSH agent (if allowed).
|
||||||
- Any "id_rsa" or "id_dsa" key discoverable in ~/.ssh/ (if allowed).
|
- Any "id_rsa" or "id_dsa" key discoverable in ~/.ssh/ (if allowed).
|
||||||
- Plain username/password auth, if a password was given.
|
- Plain username/password auth, if a password was given.
|
||||||
|
|
||||||
(The password might be needed to unlock a private key.)
|
(The password might be needed to unlock a private key.)
|
||||||
"""
|
"""
|
||||||
saved_exception = None
|
saved_exception = None
|
||||||
|
|
||||||
if pkey is not None:
|
if pkey is not None:
|
||||||
try:
|
try:
|
||||||
self._log(DEBUG, 'Trying SSH key %s' % hexlify(pkey.get_fingerprint()))
|
self._log(DEBUG, 'Trying SSH key %s' % hexlify(pkey.get_fingerprint()))
|
||||||
|
@ -425,7 +424,7 @@ class SSHClient (object):
|
||||||
return
|
return
|
||||||
except SSHException, e:
|
except SSHException, e:
|
||||||
saved_exception = e
|
saved_exception = e
|
||||||
|
|
||||||
if allow_agent:
|
if allow_agent:
|
||||||
for key in Agent().get_keys():
|
for key in Agent().get_keys():
|
||||||
try:
|
try:
|
||||||
|
@ -449,7 +448,7 @@ class SSHClient (object):
|
||||||
keyfiles.append((RSAKey, rsa_key))
|
keyfiles.append((RSAKey, rsa_key))
|
||||||
if os.path.isfile(dsa_key):
|
if os.path.isfile(dsa_key):
|
||||||
keyfiles.append((DSSKey, dsa_key))
|
keyfiles.append((DSSKey, dsa_key))
|
||||||
|
|
||||||
if not look_for_keys:
|
if not look_for_keys:
|
||||||
keyfiles = []
|
keyfiles = []
|
||||||
|
|
||||||
|
@ -463,7 +462,7 @@ class SSHClient (object):
|
||||||
saved_exception = e
|
saved_exception = e
|
||||||
except IOError, e:
|
except IOError, e:
|
||||||
saved_exception = e
|
saved_exception = e
|
||||||
|
|
||||||
if password is not None:
|
if password is not None:
|
||||||
try:
|
try:
|
||||||
self._transport.auth_password(username, password)
|
self._transport.auth_password(username, password)
|
||||||
|
@ -478,4 +477,4 @@ class SSHClient (object):
|
||||||
|
|
||||||
def _log(self, level, msg):
|
def _log(self, level, msg):
|
||||||
self._transport._log(level, msg)
|
self._transport._log(level, msg)
|
||||||
|
|
||||||
|
|
|
@ -285,7 +285,14 @@ class Transport (threading.Thread):
|
||||||
if type(sock) is tuple:
|
if type(sock) is tuple:
|
||||||
# connect to the given (host, port)
|
# connect to the given (host, port)
|
||||||
hostname, port = sock
|
hostname, port = sock
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
for (family, socktype, proto, canonname, sockaddr) in socket.getaddrinfo(hostname, port):
|
||||||
|
if socktype == socket.SOCK_STREAM:
|
||||||
|
af = family
|
||||||
|
addr = sockaddr
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise SSHException('No suitable address family for %s' % hostname)
|
||||||
|
sock = socket.socket(af, socket.SOCK_STREAM)
|
||||||
sock.connect((hostname, port))
|
sock.connect((hostname, port))
|
||||||
# okay, normal socket-ish flow here...
|
# okay, normal socket-ish flow here...
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
|
|
Loading…
Reference in New Issue