[project @ Arch-1:robey@lag.net--2003-public%secsh--dev--1.0--patch-68]
added Transport.get_security_options() just something i wanted to play with: added Transport.get_security_options() which returns a SecurityOptions object. this object is a kind of proxy for the 4 "preferred_*" fields in Transport, and lets me avoid exposing those fields directly in case i change my mind later about how they should be stored. added some docs to Channel explaining that the request methods now return True/False, and fixed up docs in a few other places.
This commit is contained in:
parent
5598a8f88f
commit
aebe186c3e
|
@ -77,9 +77,11 @@ Message = message.Message
|
||||||
PasswordRequiredException = ssh_exception.PasswordRequiredException
|
PasswordRequiredException = ssh_exception.PasswordRequiredException
|
||||||
SFTP = sftp.SFTP
|
SFTP = sftp.SFTP
|
||||||
ServerInterface = server.ServerInterface
|
ServerInterface = server.ServerInterface
|
||||||
|
SecurityOptions = transport.SecurityOptions
|
||||||
|
|
||||||
|
|
||||||
__all__ = [ 'Transport',
|
__all__ = [ 'Transport',
|
||||||
|
'SecurityOptions',
|
||||||
'Channel',
|
'Channel',
|
||||||
'RSAKey',
|
'RSAKey',
|
||||||
'DSSKey',
|
'DSSKey',
|
||||||
|
|
|
@ -116,6 +116,8 @@ class Channel (object):
|
||||||
@type width: int
|
@type width: int
|
||||||
@param height: height (in characters) of the terminal screen
|
@param height: height (in characters) of the terminal screen
|
||||||
@type height: int
|
@type height: int
|
||||||
|
@return: C{True} if the operation succeeded; C{False} if not.
|
||||||
|
@rtype: bool
|
||||||
"""
|
"""
|
||||||
if self.closed or self.eof_received or self.eof_sent or not self.active:
|
if self.closed or self.eof_received or self.eof_sent or not self.active:
|
||||||
raise SSHException('Channel is not open')
|
raise SSHException('Channel is not open')
|
||||||
|
@ -144,6 +146,9 @@ class Channel (object):
|
||||||
Request an interactive shell session on this channel. If the server
|
Request an interactive shell session on this channel. If the server
|
||||||
allows it, the channel will then be directly connected to the stdin
|
allows it, the channel will then be directly connected to the stdin
|
||||||
and stdout of the shell.
|
and stdout of the shell.
|
||||||
|
|
||||||
|
@return: C{True} if the operation succeeded; C{False} if not.
|
||||||
|
@rtype: bool
|
||||||
"""
|
"""
|
||||||
if self.closed or self.eof_received or self.eof_sent or not self.active:
|
if self.closed or self.eof_received or self.eof_sent or not self.active:
|
||||||
raise SSHException('Channel is not open')
|
raise SSHException('Channel is not open')
|
||||||
|
@ -169,6 +174,8 @@ class Channel (object):
|
||||||
|
|
||||||
@param command: a shell command to execute.
|
@param command: a shell command to execute.
|
||||||
@type command: string
|
@type command: string
|
||||||
|
@return: C{True} if the operation succeeded; C{False} if not.
|
||||||
|
@rtype: bool
|
||||||
"""
|
"""
|
||||||
if self.closed or self.eof_received or self.eof_sent or not self.active:
|
if self.closed or self.eof_received or self.eof_sent or not self.active:
|
||||||
raise SSHException('Channel is not open')
|
raise SSHException('Channel is not open')
|
||||||
|
@ -195,6 +202,8 @@ class Channel (object):
|
||||||
|
|
||||||
@param subsystem: name of the subsystem being requested.
|
@param subsystem: name of the subsystem being requested.
|
||||||
@type subsystem: string
|
@type subsystem: string
|
||||||
|
@return: C{True} if the operation succeeded; C{False} if not.
|
||||||
|
@rtype: bool
|
||||||
"""
|
"""
|
||||||
if self.closed or self.eof_received or self.eof_sent or not self.active:
|
if self.closed or self.eof_received or self.eof_sent or not self.active:
|
||||||
raise SSHException('Channel is not open')
|
raise SSHException('Channel is not open')
|
||||||
|
@ -222,6 +231,8 @@ class Channel (object):
|
||||||
@type width: int
|
@type width: int
|
||||||
@param height: new height (in characters) of the terminal screen
|
@param height: new height (in characters) of the terminal screen
|
||||||
@type height: int
|
@type height: int
|
||||||
|
@return: C{True} if the operation succeeded; C{False} if not.
|
||||||
|
@rtype: bool
|
||||||
"""
|
"""
|
||||||
if self.closed or self.eof_received or self.eof_sent or not self.active:
|
if self.closed or self.eof_received or self.eof_sent or not self.active:
|
||||||
raise SSHException('Channel is not open')
|
raise SSHException('Channel is not open')
|
||||||
|
|
|
@ -28,6 +28,10 @@ class ServerInterface (object):
|
||||||
"""
|
"""
|
||||||
This class defines an interface for controlling the behavior of paramiko
|
This class defines an interface for controlling the behavior of paramiko
|
||||||
in server mode.
|
in server mode.
|
||||||
|
|
||||||
|
Methods on this class are called from paramiko's primary thread, so you
|
||||||
|
shouldn't do too much work in them. (Certainly nothing that blocks or
|
||||||
|
sleeps.)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def check_channel_request(self, kind, chanid):
|
def check_channel_request(self, kind, chanid):
|
||||||
|
|
|
@ -123,8 +123,10 @@ class SFTPError (Exception):
|
||||||
|
|
||||||
class SFTPFile (BufferedFile):
|
class SFTPFile (BufferedFile):
|
||||||
|
|
||||||
# some sftp servers will choke if you send read/write requests larger than
|
"""
|
||||||
# this size.
|
Some sftp servers will choke if you send read/write requests larger than
|
||||||
|
this size.
|
||||||
|
"""
|
||||||
MAX_REQUEST_SIZE = 32768
|
MAX_REQUEST_SIZE = 32768
|
||||||
|
|
||||||
def __init__(self, sftp, handle, mode='r', bufsize=-1):
|
def __init__(self, sftp, handle, mode='r', bufsize=-1):
|
||||||
|
@ -245,10 +247,20 @@ class SFTP (object):
|
||||||
# raise SFTPError('Incompatible sftp protocol')
|
# raise SFTPError('Incompatible sftp protocol')
|
||||||
|
|
||||||
def from_transport(selfclass, t):
|
def from_transport(selfclass, t):
|
||||||
|
"""
|
||||||
|
Create an SFTP client channel from an open L{Transport}.
|
||||||
|
|
||||||
|
@param t: an open L{Transport} which is already authenticated.
|
||||||
|
@type t: L{Transport}
|
||||||
|
@return: a new L{SFTP} object, referring to an sftp session (channel)
|
||||||
|
across the transport.
|
||||||
|
@rtype: L{SFTP}
|
||||||
|
"""
|
||||||
chan = t.open_session()
|
chan = t.open_session()
|
||||||
if chan is None:
|
if chan is None:
|
||||||
return None
|
return None
|
||||||
chan.invoke_subsystem('sftp')
|
if not chan.invoke_subsystem('sftp'):
|
||||||
|
raise SFTPError('Failed to invoke sftp subsystem')
|
||||||
return selfclass(chan)
|
return selfclass(chan)
|
||||||
from_transport = classmethod(from_transport)
|
from_transport = classmethod(from_transport)
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,50 @@ import atexit
|
||||||
atexit.register(_join_lingering_threads)
|
atexit.register(_join_lingering_threads)
|
||||||
|
|
||||||
|
|
||||||
|
class SecurityOptions (object):
|
||||||
|
"""
|
||||||
|
Simple object containing the security preferences of an ssh transport.
|
||||||
|
These are lists of acceptable ciphers, digests, key types, and key
|
||||||
|
exchange algorithms, listed in order of preference.
|
||||||
|
"""
|
||||||
|
__slots__ = [ 'ciphers', 'digests', 'key_types', 'kex', '_transport' ]
|
||||||
|
|
||||||
|
def __init__(self, transport):
|
||||||
|
self._transport = transport
|
||||||
|
|
||||||
|
def _get_ciphers(self):
|
||||||
|
return self._transport._preferred_ciphers
|
||||||
|
|
||||||
|
def _set_ciphers(self, x):
|
||||||
|
self._transport._preferred_ciphers = x
|
||||||
|
|
||||||
|
def _get_digests(self):
|
||||||
|
return self._transport._preferred_macs
|
||||||
|
|
||||||
|
def _set_digests(self, x):
|
||||||
|
self._transport._preferred_macs = x
|
||||||
|
|
||||||
|
def _get_key_types(self):
|
||||||
|
return self._transport._preferred_keys
|
||||||
|
|
||||||
|
def _set_key_types(self, x):
|
||||||
|
self._transport._preferred_keys = x
|
||||||
|
|
||||||
|
def _get_kex(self):
|
||||||
|
return self._transport._preferred_kex
|
||||||
|
|
||||||
|
def _set_kex(self, x):
|
||||||
|
self._transport._preferred_kex = x
|
||||||
|
|
||||||
|
ciphers = property(_get_ciphers, _set_ciphers, None,
|
||||||
|
"Symmetric encryption ciphers")
|
||||||
|
digests = property(_get_digests, _set_digests, None,
|
||||||
|
"Digest (one-way hash) algorithms")
|
||||||
|
key_types = property(_get_key_types, _set_key_types, None,
|
||||||
|
"Public-key algorithms")
|
||||||
|
kex = property(_get_kex, _set_kex, None, "Key exchange algorithms")
|
||||||
|
|
||||||
|
|
||||||
class BaseTransport (threading.Thread):
|
class BaseTransport (threading.Thread):
|
||||||
"""
|
"""
|
||||||
Handles protocol negotiation, key exchange, encryption, and the creation
|
Handles protocol negotiation, key exchange, encryption, and the creation
|
||||||
|
@ -62,10 +106,10 @@ class BaseTransport (threading.Thread):
|
||||||
_PROTO_ID = '2.0'
|
_PROTO_ID = '2.0'
|
||||||
_CLIENT_ID = 'pyssh_1.1'
|
_CLIENT_ID = 'pyssh_1.1'
|
||||||
|
|
||||||
preferred_ciphers = [ 'aes128-cbc', 'blowfish-cbc', 'aes256-cbc', '3des-cbc' ]
|
_preferred_ciphers = [ 'aes128-cbc', 'blowfish-cbc', 'aes256-cbc', '3des-cbc' ]
|
||||||
preferred_macs = [ 'hmac-sha1', 'hmac-md5', 'hmac-sha1-96', 'hmac-md5-96' ]
|
_preferred_macs = [ 'hmac-sha1', 'hmac-md5', 'hmac-sha1-96', 'hmac-md5-96' ]
|
||||||
preferred_keys = [ 'ssh-rsa', 'ssh-dss' ]
|
_preferred_keys = [ 'ssh-rsa', 'ssh-dss' ]
|
||||||
preferred_kex = [ 'diffie-hellman-group1-sha1', 'diffie-hellman-group-exchange-sha1' ]
|
_preferred_kex = [ 'diffie-hellman-group1-sha1', 'diffie-hellman-group-exchange-sha1' ]
|
||||||
|
|
||||||
_cipher_info = {
|
_cipher_info = {
|
||||||
'blowfish-cbc': { 'class': Blowfish, 'mode': Blowfish.MODE_CBC, 'block-size': 8, 'key-size': 16 },
|
'blowfish-cbc': { 'class': Blowfish, 'mode': Blowfish.MODE_CBC, 'block-size': 8, 'key-size': 16 },
|
||||||
|
@ -205,6 +249,18 @@ class BaseTransport (threading.Thread):
|
||||||
out += '>'
|
out += '>'
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
def get_security_options(self):
|
||||||
|
"""
|
||||||
|
Return a L{SecurityOptions} object which can be used to tweak the
|
||||||
|
encryption algorithms this transport will permit, and the order of
|
||||||
|
preference for them.
|
||||||
|
|
||||||
|
@return: an object that can be used to change the preferred algorithms
|
||||||
|
for encryption, digest (hash), public key, and key exchange.
|
||||||
|
@rtype: L{SecurityOptions}
|
||||||
|
"""
|
||||||
|
return SecurityOptions(self)
|
||||||
|
|
||||||
def start_client(self, event=None):
|
def start_client(self, event=None):
|
||||||
"""
|
"""
|
||||||
Negotiate a new SSH2 session as a client. This is the first step after
|
Negotiate a new SSH2 session as a client. This is the first step after
|
||||||
|
@ -640,7 +696,7 @@ class BaseTransport (threading.Thread):
|
||||||
@since: doduo
|
@since: doduo
|
||||||
"""
|
"""
|
||||||
if hostkeytype is not None:
|
if hostkeytype is not None:
|
||||||
self.preferred_keys = [ hostkeytype ]
|
self._preferred_keys = [ hostkeytype ]
|
||||||
|
|
||||||
event = threading.Event()
|
event = threading.Event()
|
||||||
self.start_client(event)
|
self.start_client(event)
|
||||||
|
@ -1048,23 +1104,23 @@ class BaseTransport (threading.Thread):
|
||||||
"""
|
"""
|
||||||
self.clear_to_send.clear()
|
self.clear_to_send.clear()
|
||||||
if self.server_mode:
|
if self.server_mode:
|
||||||
if (self._modulus_pack is None) and ('diffie-hellman-group-exchange-sha1' in self.preferred_kex):
|
if (self._modulus_pack is None) and ('diffie-hellman-group-exchange-sha1' in self._preferred_kex):
|
||||||
# can't do group-exchange if we don't have a pack of potential primes
|
# can't do group-exchange if we don't have a pack of potential primes
|
||||||
self.preferred_kex.remove('diffie-hellman-group-exchange-sha1')
|
self._preferred_kex.remove('diffie-hellman-group-exchange-sha1')
|
||||||
available_server_keys = filter(self.server_key_dict.keys().__contains__,
|
available_server_keys = filter(self.server_key_dict.keys().__contains__,
|
||||||
self.preferred_keys)
|
self._preferred_keys)
|
||||||
else:
|
else:
|
||||||
available_server_keys = self.preferred_keys
|
available_server_keys = self._preferred_keys
|
||||||
|
|
||||||
m = Message()
|
m = Message()
|
||||||
m.add_byte(chr(MSG_KEXINIT))
|
m.add_byte(chr(MSG_KEXINIT))
|
||||||
m.add_bytes(randpool.get_bytes(16))
|
m.add_bytes(randpool.get_bytes(16))
|
||||||
m.add(','.join(self.preferred_kex))
|
m.add(','.join(self._preferred_kex))
|
||||||
m.add(','.join(available_server_keys))
|
m.add(','.join(available_server_keys))
|
||||||
m.add(','.join(self.preferred_ciphers))
|
m.add(','.join(self._preferred_ciphers))
|
||||||
m.add(','.join(self.preferred_ciphers))
|
m.add(','.join(self._preferred_ciphers))
|
||||||
m.add(','.join(self.preferred_macs))
|
m.add(','.join(self._preferred_macs))
|
||||||
m.add(','.join(self.preferred_macs))
|
m.add(','.join(self._preferred_macs))
|
||||||
m.add('none')
|
m.add('none')
|
||||||
m.add('none')
|
m.add('none')
|
||||||
m.add('')
|
m.add('')
|
||||||
|
@ -1105,19 +1161,19 @@ class BaseTransport (threading.Thread):
|
||||||
# as a server, we pick the first item in the client's list that we support.
|
# 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 client, we pick the first item in our list that the server supports.
|
||||||
if self.server_mode:
|
if self.server_mode:
|
||||||
agreed_kex = filter(self.preferred_kex.__contains__, kex_algo_list)
|
agreed_kex = filter(self._preferred_kex.__contains__, kex_algo_list)
|
||||||
else:
|
else:
|
||||||
agreed_kex = filter(kex_algo_list.__contains__, self.preferred_kex)
|
agreed_kex = filter(kex_algo_list.__contains__, self._preferred_kex)
|
||||||
if len(agreed_kex) == 0:
|
if len(agreed_kex) == 0:
|
||||||
raise SSHException('Incompatible ssh peer (no acceptable kex algorithm)')
|
raise SSHException('Incompatible ssh peer (no acceptable kex algorithm)')
|
||||||
self.kex_engine = self._kex_info[agreed_kex[0]](self)
|
self.kex_engine = self._kex_info[agreed_kex[0]](self)
|
||||||
|
|
||||||
if self.server_mode:
|
if self.server_mode:
|
||||||
available_server_keys = filter(self.server_key_dict.keys().__contains__,
|
available_server_keys = filter(self.server_key_dict.keys().__contains__,
|
||||||
self.preferred_keys)
|
self._preferred_keys)
|
||||||
agreed_keys = filter(available_server_keys.__contains__, server_key_algo_list)
|
agreed_keys = filter(available_server_keys.__contains__, server_key_algo_list)
|
||||||
else:
|
else:
|
||||||
agreed_keys = filter(server_key_algo_list.__contains__, self.preferred_keys)
|
agreed_keys = filter(server_key_algo_list.__contains__, self._preferred_keys)
|
||||||
if len(agreed_keys) == 0:
|
if len(agreed_keys) == 0:
|
||||||
raise SSHException('Incompatible ssh peer (no acceptable host key)')
|
raise SSHException('Incompatible ssh peer (no acceptable host key)')
|
||||||
self.host_key_type = agreed_keys[0]
|
self.host_key_type = agreed_keys[0]
|
||||||
|
@ -1125,15 +1181,15 @@ class BaseTransport (threading.Thread):
|
||||||
raise SSHException('Incompatible ssh peer (can\'t match requested host key type)')
|
raise SSHException('Incompatible ssh peer (can\'t match requested host key type)')
|
||||||
|
|
||||||
if self.server_mode:
|
if self.server_mode:
|
||||||
agreed_local_ciphers = filter(self.preferred_ciphers.__contains__,
|
agreed_local_ciphers = filter(self._preferred_ciphers.__contains__,
|
||||||
server_encrypt_algo_list)
|
server_encrypt_algo_list)
|
||||||
agreed_remote_ciphers = filter(self.preferred_ciphers.__contains__,
|
agreed_remote_ciphers = filter(self._preferred_ciphers.__contains__,
|
||||||
client_encrypt_algo_list)
|
client_encrypt_algo_list)
|
||||||
else:
|
else:
|
||||||
agreed_local_ciphers = filter(client_encrypt_algo_list.__contains__,
|
agreed_local_ciphers = filter(client_encrypt_algo_list.__contains__,
|
||||||
self.preferred_ciphers)
|
self._preferred_ciphers)
|
||||||
agreed_remote_ciphers = filter(server_encrypt_algo_list.__contains__,
|
agreed_remote_ciphers = filter(server_encrypt_algo_list.__contains__,
|
||||||
self.preferred_ciphers)
|
self._preferred_ciphers)
|
||||||
if (len(agreed_local_ciphers) == 0) or (len(agreed_remote_ciphers) == 0):
|
if (len(agreed_local_ciphers) == 0) or (len(agreed_remote_ciphers) == 0):
|
||||||
raise SSHException('Incompatible ssh server (no acceptable ciphers)')
|
raise SSHException('Incompatible ssh server (no acceptable ciphers)')
|
||||||
self.local_cipher = agreed_local_ciphers[0]
|
self.local_cipher = agreed_local_ciphers[0]
|
||||||
|
@ -1141,11 +1197,11 @@ class BaseTransport (threading.Thread):
|
||||||
self._log(DEBUG, 'Ciphers agreed: local=%s, remote=%s' % (self.local_cipher, self.remote_cipher))
|
self._log(DEBUG, 'Ciphers agreed: local=%s, remote=%s' % (self.local_cipher, self.remote_cipher))
|
||||||
|
|
||||||
if self.server_mode:
|
if self.server_mode:
|
||||||
agreed_remote_macs = filter(self.preferred_macs.__contains__, client_mac_algo_list)
|
agreed_remote_macs = filter(self._preferred_macs.__contains__, client_mac_algo_list)
|
||||||
agreed_local_macs = filter(self.preferred_macs.__contains__, server_mac_algo_list)
|
agreed_local_macs = filter(self._preferred_macs.__contains__, server_mac_algo_list)
|
||||||
else:
|
else:
|
||||||
agreed_local_macs = filter(client_mac_algo_list.__contains__, self.preferred_macs)
|
agreed_local_macs = filter(client_mac_algo_list.__contains__, self._preferred_macs)
|
||||||
agreed_remote_macs = filter(server_mac_algo_list.__contains__, self.preferred_macs)
|
agreed_remote_macs = filter(server_mac_algo_list.__contains__, self._preferred_macs)
|
||||||
if (len(agreed_local_macs) == 0) or (len(agreed_remote_macs) == 0):
|
if (len(agreed_local_macs) == 0) or (len(agreed_remote_macs) == 0):
|
||||||
raise SSHException('Incompatible ssh server (no acceptable macs)')
|
raise SSHException('Incompatible ssh server (no acceptable macs)')
|
||||||
self.local_mac = agreed_local_macs[0]
|
self.local_mac = agreed_local_macs[0]
|
||||||
|
|
Loading…
Reference in New Issue