summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--paramiko/__init__.py2
-rw-r--r--paramiko/channel.py11
-rw-r--r--paramiko/server.py4
-rw-r--r--paramiko/sftp.py18
-rw-r--r--paramiko/transport.py108
5 files changed, 114 insertions, 29 deletions
diff --git a/paramiko/__init__.py b/paramiko/__init__.py
index c987af24..3af2125d 100644
--- a/paramiko/__init__.py
+++ b/paramiko/__init__.py
@@ -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',
diff --git a/paramiko/channel.py b/paramiko/channel.py
index 6aaed9eb..67e9ea4a 100644
--- a/paramiko/channel.py
+++ b/paramiko/channel.py
@@ -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')
diff --git a/paramiko/server.py b/paramiko/server.py
index 6abcffbf..2a9b0153 100644
--- a/paramiko/server.py
+++ b/paramiko/server.py
@@ -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):
diff --git a/paramiko/sftp.py b/paramiko/sftp.py
index fc8793cf..fb977d74 100644
--- a/paramiko/sftp.py
+++ b/paramiko/sftp.py
@@ -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)
diff --git a/paramiko/transport.py b/paramiko/transport.py
index 7931a11f..11531c53 100644
--- a/paramiko/transport.py
+++ b/paramiko/transport.py
@@ -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]