summaryrefslogtreecommitdiffhomepage
path: root/paramiko/transport.py
diff options
context:
space:
mode:
Diffstat (limited to 'paramiko/transport.py')
-rw-r--r--paramiko/transport.py333
1 files changed, 263 insertions, 70 deletions
diff --git a/paramiko/transport.py b/paramiko/transport.py
index f7241bb7..31c27a2f 100644
--- a/paramiko/transport.py
+++ b/paramiko/transport.py
@@ -31,6 +31,7 @@ from hashlib import md5, sha1
import paramiko
from paramiko import util
from paramiko.auth_handler import AuthHandler
+from paramiko.ssh_gss import GSSAuth
from paramiko.channel import Channel
from paramiko.common import xffffffff, cMSG_CHANNEL_OPEN, cMSG_IGNORE, \
cMSG_GLOBAL_REQUEST, DEBUG, MSG_KEXINIT, MSG_IGNORE, MSG_DISCONNECT, \
@@ -42,11 +43,14 @@ from paramiko.common import xffffffff, cMSG_CHANNEL_OPEN, cMSG_IGNORE, \
MSG_CHANNEL_OPEN_SUCCESS, MSG_CHANNEL_OPEN_FAILURE, MSG_CHANNEL_OPEN, \
MSG_CHANNEL_SUCCESS, MSG_CHANNEL_FAILURE, MSG_CHANNEL_DATA, \
MSG_CHANNEL_EXTENDED_DATA, MSG_CHANNEL_WINDOW_ADJUST, MSG_CHANNEL_REQUEST, \
- MSG_CHANNEL_EOF, MSG_CHANNEL_CLOSE
+ MSG_CHANNEL_EOF, MSG_CHANNEL_CLOSE, MIN_WINDOW_SIZE, MIN_PACKET_SIZE, \
+ 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_group1 import KexGroup1
+from paramiko.kex_group14 import KexGroup14
+from paramiko.kex_gss import KexGSSGex, KexGSSGroup1, KexGSSGroup14, NullHostKey
from paramiko.message import Message
from paramiko.packet import Packetizer, NeedRekeyException
from paramiko.primes import ModulusPack
@@ -57,7 +61,7 @@ from paramiko.server import ServerInterface
from paramiko.sftp_client import SFTPClient
from paramiko.ssh_exception import (SSHException, BadAuthenticationType,
ChannelException, ProxyCommandFailure)
-from paramiko.util import retry_on_signal
+from paramiko.util import retry_on_signal, ClosingContextManager, clamp_value
from Crypto.Cipher import Blowfish, AES, DES3, ARC4
try:
@@ -77,13 +81,15 @@ import atexit
atexit.register(_join_lingering_threads)
-class Transport (threading.Thread):
+class Transport (threading.Thread, ClosingContextManager):
"""
An SSH Transport attaches to a stream (usually a socket), negotiates an
encrypted session, authenticates, and then creates stream tunnels, called
`channels <.Channel>`, across the session. Multiple channels can be
multiplexed across a single session (and often are, in the case of port
forwardings).
+
+ Instances of this class may be used as context managers.
"""
_PROTO_ID = '2.0'
_CLIENT_ID = 'paramiko_%s' % paramiko.__version__
@@ -92,7 +98,7 @@ class Transport (threading.Thread):
'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-group1-sha1', 'diffie-hellman-group-exchange-sha1')
+ _preferred_kex = ( 'diffie-hellman-group14-sha1', 'diffie-hellman-group-exchange-sha1' , 'diffie-hellman-group1-sha1')
_preferred_compression = ('none',)
_cipher_info = {
@@ -121,7 +127,11 @@ class Transport (threading.Thread):
_kex_info = {
'diffie-hellman-group1-sha1': KexGroup1,
+ 'diffie-hellman-group14-sha1': KexGroup14,
'diffie-hellman-group-exchange-sha1': KexGex,
+ 'gss-group1-sha1-toWM5Slw5Ew8Mqkay+al2g==': KexGSSGroup1,
+ 'gss-group14-sha1-toWM5Slw5Ew8Mqkay+al2g==': KexGSSGroup14,
+ 'gss-gex-sha1-toWM5Slw5Ew8Mqkay+al2g==': KexGSSGex
}
_compression_info = {
@@ -135,7 +145,12 @@ class Transport (threading.Thread):
_modulus_pack = None
- def __init__(self, sock):
+ def __init__(self,
+ sock,
+ default_window_size=DEFAULT_WINDOW_SIZE,
+ default_max_packet_size=DEFAULT_MAX_PACKET_SIZE,
+ gss_kex=False,
+ gss_deleg_creds=True):
"""
Create a new SSH session over an existing socket, or socket-like
object. This only creates the `.Transport` object; it doesn't begin the
@@ -161,8 +176,24 @@ class Transport (threading.Thread):
address and used for communication. Exceptions from the ``socket``
call may be thrown in this case.
+ .. note::
+ Modifying the the window and packet sizes might have adverse
+ effects on your channels created from this transport. The default
+ values are the same as in the OpenSSH code base and have been
+ battle tested.
+
:param socket sock:
a socket or socket-like object to create the session over.
+ :param int default_window_size:
+ sets the default window size on the transport. (defaults to
+ 2097152)
+ :param int default_max_packet_size:
+ sets the default max packet size on the transport. (defaults to
+ 32768)
+
+ .. versionchanged:: 1.15
+ Added the ``default_window_size`` and ``default_max_packet_size``
+ arguments.
"""
self.active = False
@@ -216,6 +247,21 @@ class Transport (threading.Thread):
self.host_key_type = None
self.host_key = None
+ # GSS-API / SSPI Key Exchange
+ self.use_gss_kex = gss_kex
+ # This will be set to True if GSS-API Key Exchange was performed
+ self.gss_kex_used = False
+ self.kexgss_ctxt = None
+ self.gss_host = None
+ if self.use_gss_kex:
+ self.kexgss_ctxt = GSSAuth("gssapi-keyex", gss_deleg_creds)
+ self._preferred_kex = ('gss-gex-sha1-toWM5Slw5Ew8Mqkay+al2g==',
+ 'gss-group14-sha1-toWM5Slw5Ew8Mqkay+al2g==',
+ 'gss-group1-sha1-toWM5Slw5Ew8Mqkay+al2g==',
+ 'diffie-hellman-group-exchange-sha1',
+ 'diffie-hellman-group14-sha1',
+ 'diffie-hellman-group1-sha1')
+
# state used during negotiation
self.kex_engine = None
self.H = None
@@ -232,8 +278,8 @@ class Transport (threading.Thread):
self.channel_events = {} # (id -> Event)
self.channels_seen = {} # (id -> True)
self._channel_counter = 0
- self.window_size = 65536
- self.max_packet_size = 34816
+ self.default_max_packet_size = default_max_packet_size
+ self.default_window_size = default_window_size
self._forward_agent_handler = None
self._x11_handler = None
self._tcp_handler = None
@@ -249,6 +295,8 @@ class Transport (threading.Thread):
self.global_response = None # response Message from an arbitrary global request
self.completion_event = None # user-defined event callbacks
self.banner_timeout = 15 # how long (seconds) to wait for the SSH banner
+ self.handshake_timeout = 15 # how long (seconds) to wait for the handshake to finish after SSH banner sent.
+
# server mode:
self.server_mode = False
@@ -288,6 +336,7 @@ class Transport (threading.Thread):
.. versionadded:: 1.5.3
"""
+ self.sock.close()
self.close()
def get_security_options(self):
@@ -299,6 +348,17 @@ class Transport (threading.Thread):
"""
return SecurityOptions(self)
+ def set_gss_host(self, gss_host):
+ """
+ Setter for C{gss_host} if GSS-API Key Exchange is performed.
+
+ :param str gss_host: The targets name in the kerberos database
+ Default: The name of the host to connect to
+ :rtype: Void
+ """
+ # We need the FQDN to get this working with SSPI
+ self.gss_host = socket.getfqdn(gss_host)
+
def start_client(self, event=None):
"""
Negotiate a new SSH2 session as a client. This is the first step after
@@ -319,9 +379,10 @@ class Transport (threading.Thread):
.. note:: `connect` is a simpler method for connecting as a client.
- .. note:: After calling this method (or `start_server` or `connect`),
- you should no longer directly read from or write to the original
- socket object.
+ .. note::
+ After calling this method (or `start_server` or `connect`), you
+ should no longer directly read from or write to the original socket
+ object.
:param .threading.Event event:
an event to trigger when negotiation is complete (optional)
@@ -346,7 +407,7 @@ class Transport (threading.Thread):
if e is not None:
raise e
raise SSHException('Negotiation failed.')
- if event.isSet():
+ if event.is_set():
break
def start_server(self, event=None, server=None):
@@ -411,7 +472,7 @@ class Transport (threading.Thread):
if e is not None:
raise e
raise SSHException('Negotiation failed.')
- if event.isSet():
+ if event.is_set():
break
def add_server_key(self, key):
@@ -449,6 +510,7 @@ class Transport (threading.Thread):
pass
return None
+ @staticmethod
def load_server_moduli(filename=None):
"""
(optional)
@@ -488,7 +550,6 @@ class Transport (threading.Thread):
# none succeeded
Transport._modulus_pack = None
return False
- load_server_moduli = staticmethod(load_server_moduli)
def close(self):
"""
@@ -528,18 +589,33 @@ class Transport (threading.Thread):
"""
return self.active
- def open_session(self):
+ def open_session(self, window_size=None, max_packet_size=None, timeout=None):
"""
Request a new channel to the server, of type ``"session"``. This is
just an alias for calling `open_channel` with an argument of
``"session"``.
+ .. note:: Modifying the the window and packet sizes might have adverse
+ effects on the session created. The default values are the same
+ as in the OpenSSH code base and have been battle tested.
+
+ :param int window_size:
+ optional window size for this session.
+ :param int max_packet_size:
+ optional max packet size for this session.
+
:return: a new `.Channel`
:raises SSHException: if the request is rejected or the session ends
prematurely
+
+ .. versionchanged:: 1.15
+ Added the ``window_size`` and ``max_packet_size`` arguments.
"""
- return self.open_channel('session')
+ return self.open_channel('session',
+ window_size=window_size,
+ max_packet_size=max_packet_size,
+ timeout=timeout)
def open_x11_channel(self, src_addr=None):
"""
@@ -581,13 +657,23 @@ class Transport (threading.Thread):
"""
return self.open_channel('forwarded-tcpip', dest_addr, src_addr)
- def open_channel(self, kind, dest_addr=None, src_addr=None):
+ def open_channel(self,
+ kind,
+ dest_addr=None,
+ src_addr=None,
+ window_size=None,
+ max_packet_size=None,
+ timeout=None):
"""
Request a new channel to the server. `Channels <.Channel>` are
socket-like objects used for the actual transfer of data across the
session. You may only request a channel after negotiating encryption
(using `connect` or `start_client`) and authenticating.
+ .. note:: Modifying the the window and packet sizes might have adverse
+ effects on the channel created. The default values are the same
+ as in the OpenSSH code base and have been battle tested.
+
:param str kind:
the kind of channel requested (usually ``"session"``,
``"forwarded-tcpip"``, ``"direct-tcpip"``, or ``"x11"``)
@@ -597,22 +683,35 @@ class Transport (threading.Thread):
``"direct-tcpip"`` (ignored for other channel types)
:param src_addr: the source address of this port forwarding, if
``kind`` is ``"forwarded-tcpip"``, ``"direct-tcpip"``, or ``"x11"``
+ :param int window_size:
+ optional window size for this session.
+ :param int max_packet_size:
+ optional max packet size for this session.
+ :param float timeout:
+ optional timeout opening a channel, default 3600s (1h)
+
:return: a new `.Channel` on success
- :raises SSHException: if the request is rejected or the session ends
- prematurely
+ :raises SSHException: if the request is rejected, the session ends
+ prematurely or there is a timeout openning a channel
+
+ .. versionchanged:: 1.15
+ Added the ``window_size`` and ``max_packet_size`` arguments.
"""
if not self.active:
raise SSHException('SSH session not active')
+ timeout = 3600 if timeout is None else timeout
self.lock.acquire()
try:
+ window_size = self._sanitize_window_size(window_size)
+ max_packet_size = self._sanitize_packet_size(max_packet_size)
chanid = self._next_channel()
m = Message()
m.add_byte(cMSG_CHANNEL_OPEN)
m.add_string(kind)
m.add_int(chanid)
- m.add_int(self.window_size)
- m.add_int(self.max_packet_size)
+ m.add_int(window_size)
+ m.add_int(max_packet_size)
if (kind == 'forwarded-tcpip') or (kind == 'direct-tcpip'):
m.add_string(dest_addr[0])
m.add_int(dest_addr[1])
@@ -626,10 +725,11 @@ class Transport (threading.Thread):
self.channel_events[chanid] = event = threading.Event()
self.channels_seen[chanid] = True
chan._set_transport(self)
- chan._set_window(self.window_size, self.max_packet_size)
+ chan._set_window(window_size, max_packet_size)
finally:
self.lock.release()
self._send_user_message(m)
+ start_ts = time.time()
while True:
event.wait(0.1)
if not self.active:
@@ -637,8 +737,10 @@ class Transport (threading.Thread):
if e is None:
e = SSHException('Unable to open channel.')
raise e
- if event.isSet():
+ if event.is_set():
break
+ elif start_ts + timeout < time.time():
+ raise SSHException('Timeout openning channel.')
chan = self._channels.get(chanid)
if chan is not None:
return chan
@@ -670,6 +772,7 @@ class Transport (threading.Thread):
:param callable handler:
optional handler for incoming forwarded connections, of the form
``func(Channel, (str, int), (str, int))``.
+
:return: the port number (`int`) allocated by the server
:raises SSHException: if the server refused the TCP forward request
@@ -756,7 +859,7 @@ class Transport (threading.Thread):
if e is not None:
raise e
raise SSHException('Negotiation failed.')
- if self.completion_event.isSet():
+ if self.completion_event.is_set():
break
return
@@ -807,7 +910,7 @@ class Transport (threading.Thread):
self.completion_event.wait(0.1)
if not self.active:
return None
- if self.completion_event.isSet():
+ if self.completion_event.is_set():
break
return self.global_response
@@ -836,7 +939,8 @@ class Transport (threading.Thread):
self.lock.release()
return chan
- def connect(self, hostkey=None, username='', password=None, pkey=None):
+ def connect(self, hostkey=None, username='', password=None, pkey=None,
+ gss_host=None, gss_auth=False, gss_kex=False, gss_deleg_creds=True):
"""
Negotiate an SSH2 session, and optionally verify the server's host key
and authenticate using a password or private key. This is a shortcut
@@ -866,6 +970,14 @@ class Transport (threading.Thread):
:param .PKey pkey:
a private key to use for authentication, if you want to use private
key authentication; otherwise ``None``.
+ :param str gss_host:
+ The target's name in the kerberos database. Default: hostname
+ :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:
+ Whether to delegate GSS-API client credentials.
:raises SSHException: if the SSH2 negotiation fails, the host key
supplied by the server is incorrect, or authentication fails.
@@ -876,7 +988,9 @@ class Transport (threading.Thread):
self.start_client()
# check host key if we were given one
- if hostkey is not None:
+ # If GSS-API Key Exchange was performed, we are not required to check
+ # the host key.
+ if (hostkey is not None) and not gss_kex:
key = self.get_remote_server_key()
if (key.get_name() != hostkey.get_name()) or (key.asbytes() != hostkey.asbytes()):
self._log(DEBUG, 'Bad host key from server')
@@ -885,13 +999,19 @@ class Transport (threading.Thread):
raise SSHException('Bad host key from server')
self._log(DEBUG, 'Host key verified (%s)' % hostkey.get_name())
- if (pkey is not None) or (password is not None):
- if password is not None:
- self._log(DEBUG, 'Attempting password auth...')
- self.auth_password(username, password)
- else:
+ if (pkey is not None) or (password is not None) or gss_auth or gss_kex:
+ if gss_auth:
+ self._log(DEBUG, 'Attempting GSS-API auth... (gssapi-with-mic)')
+ self.auth_gssapi_with_mic(username, gss_host, gss_deleg_creds)
+ elif gss_kex:
+ self._log(DEBUG, 'Attempting GSS-API auth... (gssapi-keyex)')
+ self.auth_gssapi_keyex(username)
+ elif pkey is not None:
self._log(DEBUG, 'Attempting public-key auth...')
self.auth_publickey(username, pkey)
+ else:
+ self._log(DEBUG, 'Attempting password auth...')
+ self.auth_password(username, password)
return
@@ -1174,6 +1294,55 @@ class Transport (threading.Thread):
self.auth_handler.auth_interactive(username, handler, my_event, submethods)
return self.auth_handler.wait_for_response(my_event)
+ def auth_gssapi_with_mic(self, username, gss_host, gss_deleg_creds):
+ """
+ Authenticate to the Server using GSS-API / SSPI.
+
+ :param str username: The username to authenticate as
+ :param str gss_host: The target host
+ :param bool gss_deleg_creds: Delegate credentials or not
+ :return: list of auth types permissible for the next stage of
+ authentication (normally empty)
+ :rtype: list
+ :raise BadAuthenticationType: if gssapi-with-mic isn't
+ allowed by the server (and no event was passed in)
+ :raise AuthenticationException: if the authentication failed (and no
+ event was passed in)
+ :raise SSHException: if there was a network error
+ """
+ if (not self.active) or (not self.initial_kex_done):
+ # we should never try to authenticate unless we're on a secure link
+ raise SSHException('No existing session')
+ my_event = threading.Event()
+ self.auth_handler = AuthHandler(self)
+ self.auth_handler.auth_gssapi_with_mic(username, gss_host, gss_deleg_creds, my_event)
+ return self.auth_handler.wait_for_response(my_event)
+
+ def auth_gssapi_keyex(self, username):
+ """
+ Authenticate to the Server with GSS-API / SSPI if GSS-API Key Exchange
+ was the used key exchange method.
+
+ :param str username: The username to authenticate as
+ :param str gss_host: The target host
+ :param bool gss_deleg_creds: Delegate credentials or not
+ :return: list of auth types permissible for the next stage of
+ authentication (normally empty)
+ :rtype: list
+ :raise BadAuthenticationType: if GSS-API Key Exchange was not performed
+ (and no event was passed in)
+ :raise AuthenticationException: if the authentication failed (and no
+ event was passed in)
+ :raise SSHException: if there was a network error
+ """
+ if (not self.active) or (not self.initial_kex_done):
+ # we should never try to authenticate unless we're on a secure link
+ raise SSHException('No existing session')
+ my_event = threading.Event()
+ self.auth_handler = AuthHandler(self)
+ self.auth_handler.auth_gssapi_keyex(username, my_event)
+ return self.auth_handler.wait_for_response(my_event)
+
def set_log_channel(self, name):
"""
Set the channel for this transport's logging. The default is
@@ -1258,7 +1427,7 @@ class Transport (threading.Thread):
def stop_thread(self):
self.active = False
self.packetizer.close()
- while self.isAlive():
+ while self.is_alive() and (self is not threading.current_thread()):
self.join(10)
### internals...
@@ -1302,7 +1471,7 @@ class Transport (threading.Thread):
self._log(DEBUG, 'Dropping user packet because connection is dead.')
return
self.clear_to_send_lock.acquire()
- if self.clear_to_send.isSet():
+ if self.clear_to_send.is_set():
break
self.clear_to_send_lock.release()
if time.time() > start + self.clear_to_send_timeout:
@@ -1392,6 +1561,17 @@ class Transport (threading.Thread):
finally:
self.lock.release()
+ def _sanitize_window_size(self, window_size):
+ if window_size is None:
+ window_size = self.default_window_size
+ return clamp_value(MIN_WINDOW_SIZE, window_size, MAX_WINDOW_SIZE)
+
+ def _sanitize_packet_size(self, max_packet_size):
+ if max_packet_size is None:
+ max_packet_size = self.default_max_packet_size
+ return clamp_value(MIN_PACKET_SIZE, max_packet_size, MAX_WINDOW_SIZE)
+
+
def run(self):
# (use the exposed "run" method, because if we specify a thread target
# of a private method, threading.Thread will keep a reference to it
@@ -1412,6 +1592,12 @@ class Transport (threading.Thread):
try:
self.packetizer.write_all(b(self.local_version + '\r\n'))
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.
+ # 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)
self._send_kex_init()
self._expect_packet(MSG_KEXINIT)
@@ -1436,7 +1622,7 @@ class Transport (threading.Thread):
if ptype not in self._expected_packet:
raise SSHException('Expecting packet from %r, got %d' % (self._expected_packet, ptype))
self._expected_packet = tuple()
- if (ptype >= 30) and (ptype <= 39):
+ if (ptype >= 30) and (ptype <= 41):
self.kex_engine.parse_next(ptype, m)
continue
@@ -1461,6 +1647,7 @@ class Transport (threading.Thread):
msg.add_byte(cMSG_UNIMPLEMENTED)
msg.add_int(m.seqno)
self._send_message(msg)
+ self.packetizer.complete_handshake()
except SSHException as e:
self._log(ERROR, 'Exception: ' + str(e))
self._log(ERROR, util.tb_strings())
@@ -1856,7 +2043,7 @@ class Transport (threading.Thread):
self.lock.acquire()
try:
chan._set_remote_channel(server_chanid, server_window_size, server_max_packet_size)
- self._log(INFO, 'Secsh channel %d opened.' % chanid)
+ self._log(DEBUG, 'Secsh channel %d opened.' % chanid)
if chanid in self.channel_events:
self.channel_events[chanid].set()
del self.channel_events[chanid]
@@ -1870,7 +2057,7 @@ class Transport (threading.Thread):
reason_str = m.get_text()
lang = m.get_text()
reason_text = CONNECTION_FAILED_CODE.get(reason, '(unknown code)')
- self._log(INFO, 'Secsh channel %d open FAILED: %s: %s' % (chanid, reason_str, reason_text))
+ self._log(ERROR, 'Secsh channel %d open FAILED: %s: %s' % (chanid, reason_str, reason_text))
self.lock.acquire()
try:
self.saved_exception = ChannelException(reason, reason_text)
@@ -1955,7 +2142,7 @@ class Transport (threading.Thread):
self._channels.put(my_chanid, chan)
self.channels_seen[my_chanid] = True
chan._set_transport(self)
- chan._set_window(self.window_size, self.max_packet_size)
+ chan._set_window(self.default_window_size, self.default_max_packet_size)
chan._set_remote_channel(chanid, initial_window_size, max_packet_size)
finally:
self.lock.release()
@@ -1963,10 +2150,10 @@ class Transport (threading.Thread):
m.add_byte(cMSG_CHANNEL_OPEN_SUCCESS)
m.add_int(chanid)
m.add_int(my_chanid)
- m.add_int(self.window_size)
- m.add_int(self.max_packet_size)
+ m.add_int(self.default_window_size)
+ m.add_int(self.default_max_packet_size)
self._send_message(m)
- self._log(INFO, 'Secsh channel %d (%s) opened.', my_chanid, kind)
+ self._log(DEBUG, 'Secsh channel %d (%s) opened.', my_chanid, kind)
if kind == 'auth-agent@openssh.com':
self._forward_agent_handler(chan)
elif kind == 'x11':
@@ -2039,21 +2226,6 @@ class SecurityOptions (object):
"""
return '<paramiko.SecurityOptions for %s>' % repr(self._transport)
- def _get_ciphers(self):
- return self._transport._preferred_ciphers
-
- def _get_digests(self):
- return self._transport._preferred_macs
-
- def _get_key_types(self):
- return self._transport._preferred_keys
-
- def _get_kex(self):
- return self._transport._preferred_kex
-
- def _get_compression(self):
- return self._transport._preferred_compression
-
def _set(self, name, orig, x):
if type(x) is list:
x = tuple(x)
@@ -2065,30 +2237,51 @@ class SecurityOptions (object):
raise ValueError('unknown cipher')
setattr(self._transport, name, x)
- def _set_ciphers(self, x):
+ @property
+ def ciphers(self):
+ """Symmetric encryption ciphers"""
+ return self._transport._preferred_ciphers
+
+ @ciphers.setter
+ def ciphers(self, x):
self._set('_preferred_ciphers', '_cipher_info', x)
- def _set_digests(self, x):
+ @property
+ def digests(self):
+ """Digest (one-way hash) algorithms"""
+ return self._transport._preferred_macs
+
+ @digests.setter
+ def digests(self, x):
self._set('_preferred_macs', '_mac_info', x)
- def _set_key_types(self, x):
+ @property
+ def key_types(self):
+ """Public-key algorithms"""
+ return self._transport._preferred_keys
+
+ @key_types.setter
+ def key_types(self, x):
self._set('_preferred_keys', '_key_info', x)
- def _set_kex(self, x):
+
+ @property
+ def kex(self):
+ """Key exchange algorithms"""
+ return self._transport._preferred_kex
+
+ @kex.setter
+ def kex(self, x):
self._set('_preferred_kex', '_kex_info', x)
- def _set_compression(self, x):
- self._set('_preferred_compression', '_compression_info', x)
+ @property
+ def compression(self):
+ """Compression algorithms"""
+ return self._transport._preferred_compression
- 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")
- compression = property(_get_compression, _set_compression, None,
- "Compression algorithms")
+ @compression.setter
+ def compression(self, x):
+ self._set('_preferred_compression', '_compression_info', x)
class ChannelMap (object):