[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
|
||||
SFTP = sftp.SFTP
|
||||
ServerInterface = server.ServerInterface
|
||||
SecurityOptions = transport.SecurityOptions
|
||||
|
||||
|
||||
__all__ = [ 'Transport',
|
||||
'SecurityOptions',
|
||||
'Channel',
|
||||
'RSAKey',
|
||||
'DSSKey',
|
||||
|
|
|
@ -116,6 +116,8 @@ class Channel (object):
|
|||
@type width: int
|
||||
@param height: height (in characters) of the terminal screen
|
||||
@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:
|
||||
raise SSHException('Channel is not open')
|
||||
|
@ -144,6 +146,9 @@ class Channel (object):
|
|||
Request an interactive shell session on this channel. If the server
|
||||
allows it, the channel will then be directly connected to the stdin
|
||||
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:
|
||||
raise SSHException('Channel is not open')
|
||||
|
@ -169,6 +174,8 @@ class Channel (object):
|
|||
|
||||
@param command: a shell command to execute.
|
||||
@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:
|
||||
raise SSHException('Channel is not open')
|
||||
|
@ -195,6 +202,8 @@ class Channel (object):
|
|||
|
||||
@param subsystem: name of the subsystem being requested.
|
||||
@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:
|
||||
raise SSHException('Channel is not open')
|
||||
|
@ -222,6 +231,8 @@ class Channel (object):
|
|||
@type width: int
|
||||
@param height: new height (in characters) of the terminal screen
|
||||
@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:
|
||||
raise SSHException('Channel is not open')
|
||||
|
|
|
@ -28,6 +28,10 @@ class ServerInterface (object):
|
|||
"""
|
||||
This class defines an interface for controlling the behavior of paramiko
|
||||
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):
|
||||
|
|
|
@ -123,8 +123,10 @@ class SFTPError (Exception):
|
|||
|
||||
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
|
||||
|
||||
def __init__(self, sftp, handle, mode='r', bufsize=-1):
|
||||
|
@ -245,10 +247,20 @@ class SFTP (object):
|
|||
# raise SFTPError('Incompatible sftp protocol')
|
||||
|
||||
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()
|
||||
if chan is None:
|
||||
return None
|
||||
chan.invoke_subsystem('sftp')
|
||||
if not chan.invoke_subsystem('sftp'):
|
||||
raise SFTPError('Failed to invoke sftp subsystem')
|
||||
return selfclass(chan)
|
||||
from_transport = classmethod(from_transport)
|
||||
|
||||
|
|
|
@ -53,6 +53,50 @@ import atexit
|
|||
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):
|
||||
"""
|
||||
Handles protocol negotiation, key exchange, encryption, and the creation
|
||||
|
@ -62,10 +106,10 @@ class BaseTransport (threading.Thread):
|
|||
_PROTO_ID = '2.0'
|
||||
_CLIENT_ID = 'pyssh_1.1'
|
||||
|
||||
preferred_ciphers = [ 'aes128-cbc', 'blowfish-cbc', 'aes256-cbc', '3des-cbc' ]
|
||||
preferred_macs = [ 'hmac-sha1', 'hmac-md5', 'hmac-sha1-96', 'hmac-md5-96' ]
|
||||
preferred_keys = [ 'ssh-rsa', 'ssh-dss' ]
|
||||
preferred_kex = [ 'diffie-hellman-group1-sha1', 'diffie-hellman-group-exchange-sha1' ]
|
||||
_preferred_ciphers = [ 'aes128-cbc', 'blowfish-cbc', 'aes256-cbc', '3des-cbc' ]
|
||||
_preferred_macs = [ 'hmac-sha1', 'hmac-md5', 'hmac-sha1-96', 'hmac-md5-96' ]
|
||||
_preferred_keys = [ 'ssh-rsa', 'ssh-dss' ]
|
||||
_preferred_kex = [ 'diffie-hellman-group1-sha1', 'diffie-hellman-group-exchange-sha1' ]
|
||||
|
||||
_cipher_info = {
|
||||
'blowfish-cbc': { 'class': Blowfish, 'mode': Blowfish.MODE_CBC, 'block-size': 8, 'key-size': 16 },
|
||||
|
@ -205,6 +249,18 @@ class BaseTransport (threading.Thread):
|
|||
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):
|
||||
"""
|
||||
Negotiate a new SSH2 session as a client. This is the first step after
|
||||
|
@ -640,7 +696,7 @@ class BaseTransport (threading.Thread):
|
|||
@since: doduo
|
||||
"""
|
||||
if hostkeytype is not None:
|
||||
self.preferred_keys = [ hostkeytype ]
|
||||
self._preferred_keys = [ hostkeytype ]
|
||||
|
||||
event = threading.Event()
|
||||
self.start_client(event)
|
||||
|
@ -1048,23 +1104,23 @@ class BaseTransport (threading.Thread):
|
|||
"""
|
||||
self.clear_to_send.clear()
|
||||
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
|
||||
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__,
|
||||
self.preferred_keys)
|
||||
self._preferred_keys)
|
||||
else:
|
||||
available_server_keys = self.preferred_keys
|
||||
available_server_keys = self._preferred_keys
|
||||
|
||||
m = Message()
|
||||
m.add_byte(chr(MSG_KEXINIT))
|
||||
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(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_ciphers))
|
||||
m.add(','.join(self._preferred_ciphers))
|
||||
m.add(','.join(self._preferred_macs))
|
||||
m.add(','.join(self._preferred_macs))
|
||||
m.add('none')
|
||||
m.add('none')
|
||||
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 client, we pick the first item in our list that the server supports.
|
||||
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:
|
||||
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:
|
||||
raise SSHException('Incompatible ssh peer (no acceptable kex algorithm)')
|
||||
self.kex_engine = self._kex_info[agreed_kex[0]](self)
|
||||
|
||||
if self.server_mode:
|
||||
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)
|
||||
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:
|
||||
raise SSHException('Incompatible ssh peer (no acceptable host key)')
|
||||
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)')
|
||||
|
||||
if self.server_mode:
|
||||
agreed_local_ciphers = filter(self.preferred_ciphers.__contains__,
|
||||
agreed_local_ciphers = filter(self._preferred_ciphers.__contains__,
|
||||
server_encrypt_algo_list)
|
||||
agreed_remote_ciphers = filter(self.preferred_ciphers.__contains__,
|
||||
agreed_remote_ciphers = filter(self._preferred_ciphers.__contains__,
|
||||
client_encrypt_algo_list)
|
||||
else:
|
||||
agreed_local_ciphers = filter(client_encrypt_algo_list.__contains__,
|
||||
self.preferred_ciphers)
|
||||
self._preferred_ciphers)
|
||||
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):
|
||||
raise SSHException('Incompatible ssh server (no acceptable ciphers)')
|
||||
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))
|
||||
|
||||
if self.server_mode:
|
||||
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_remote_macs = filter(self._preferred_macs.__contains__, client_mac_algo_list)
|
||||
agreed_local_macs = filter(self._preferred_macs.__contains__, server_mac_algo_list)
|
||||
else:
|
||||
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_local_macs = filter(client_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):
|
||||
raise SSHException('Incompatible ssh server (no acceptable macs)')
|
||||
self.local_mac = agreed_local_macs[0]
|
||||
|
|
Loading…
Reference in New Issue