summaryrefslogtreecommitdiffhomepage
path: root/paramiko/transport.py
diff options
context:
space:
mode:
Diffstat (limited to 'paramiko/transport.py')
-rw-r--r--paramiko/transport.py1055
1 files changed, 590 insertions, 465 deletions
diff --git a/paramiko/transport.py b/paramiko/transport.py
index 4af29c95..9a5d33dd 100644
--- a/paramiko/transport.py
+++ b/paramiko/transport.py
@@ -39,18 +39,49 @@ 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, MSG_DEBUG, ERROR, WARNING,
- cMSG_UNIMPLEMENTED, INFO, cMSG_KEXINIT, cMSG_NEWKEYS, MSG_NEWKEYS,
- cMSG_REQUEST_SUCCESS, cMSG_REQUEST_FAILURE, CONNECTION_FAILED_CODE,
- OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED, OPEN_SUCCEEDED,
- cMSG_CHANNEL_OPEN_FAILURE, cMSG_CHANNEL_OPEN_SUCCESS, MSG_GLOBAL_REQUEST,
- MSG_REQUEST_SUCCESS, MSG_REQUEST_FAILURE, 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, MIN_WINDOW_SIZE, MIN_PACKET_SIZE, MAX_WINDOW_SIZE,
- DEFAULT_WINDOW_SIZE, DEFAULT_MAX_PACKET_SIZE, HIGHEST_USERAUTH_MESSAGE_ID,
+ xffffffff,
+ cMSG_CHANNEL_OPEN,
+ cMSG_IGNORE,
+ cMSG_GLOBAL_REQUEST,
+ DEBUG,
+ MSG_KEXINIT,
+ MSG_IGNORE,
+ MSG_DISCONNECT,
+ MSG_DEBUG,
+ ERROR,
+ WARNING,
+ cMSG_UNIMPLEMENTED,
+ INFO,
+ cMSG_KEXINIT,
+ cMSG_NEWKEYS,
+ MSG_NEWKEYS,
+ cMSG_REQUEST_SUCCESS,
+ cMSG_REQUEST_FAILURE,
+ CONNECTION_FAILED_CODE,
+ OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED,
+ OPEN_SUCCEEDED,
+ cMSG_CHANNEL_OPEN_FAILURE,
+ cMSG_CHANNEL_OPEN_SUCCESS,
+ MSG_GLOBAL_REQUEST,
+ MSG_REQUEST_SUCCESS,
+ MSG_REQUEST_FAILURE,
+ 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,
+ MIN_WINDOW_SIZE,
+ MIN_PACKET_SIZE,
+ MAX_WINDOW_SIZE,
+ DEFAULT_WINDOW_SIZE,
+ DEFAULT_MAX_PACKET_SIZE,
+ HIGHEST_USERAUTH_MESSAGE_ID,
)
from paramiko.compress import ZlibCompressor, ZlibDecompressor
from paramiko.dsskey import DSSKey
@@ -69,7 +100,10 @@ from paramiko.ecdsakey import ECDSAKey
from paramiko.server import ServerInterface
from paramiko.sftp_client import SFTPClient
from paramiko.ssh_exception import (
- SSHException, BadAuthenticationType, ChannelException, ProxyCommandFailure,
+ SSHException,
+ BadAuthenticationType,
+ ChannelException,
+ ProxyCommandFailure,
)
from paramiko.util import retry_on_signal, ClosingContextManager, clamp_value
@@ -77,12 +111,14 @@ from paramiko.util import retry_on_signal, ClosingContextManager, clamp_value
# for thread cleanup
_active_threads = []
+
def _join_lingering_threads():
for thr in _active_threads:
thr.stop_thread()
import atexit
+
atexit.register(_join_lingering_threads)
@@ -99,162 +135,163 @@ class Transport(threading.Thread, ClosingContextManager):
_ENCRYPT = object()
_DECRYPT = object()
- _PROTO_ID = '2.0'
- _CLIENT_ID = 'paramiko_{}'.format(paramiko.__version__)
+ _PROTO_ID = "2.0"
+ _CLIENT_ID = "paramiko_{}".format(paramiko.__version__)
# These tuples of algorithm identifiers are in preference order; do not
# reorder without reason!
_preferred_ciphers = (
- 'aes128-ctr',
- 'aes192-ctr',
- 'aes256-ctr',
- 'aes128-cbc',
- 'aes192-cbc',
- 'aes256-cbc',
- 'blowfish-cbc',
- '3des-cbc',
+ "aes128-ctr",
+ "aes192-ctr",
+ "aes256-ctr",
+ "aes128-cbc",
+ "aes192-cbc",
+ "aes256-cbc",
+ "blowfish-cbc",
+ "3des-cbc",
)
_preferred_macs = (
- 'hmac-sha2-256',
- 'hmac-sha2-512',
- 'hmac-sha1',
- 'hmac-md5',
- 'hmac-sha1-96',
- 'hmac-md5-96',
+ "hmac-sha2-256",
+ "hmac-sha2-512",
+ "hmac-sha1",
+ "hmac-md5",
+ "hmac-sha1-96",
+ "hmac-md5-96",
)
_preferred_keys = (
- 'ssh-ed25519',
- 'ecdsa-sha2-nistp256',
- 'ecdsa-sha2-nistp384',
- 'ecdsa-sha2-nistp521',
- 'ssh-rsa',
- 'ssh-dss',
+ "ssh-ed25519",
+ "ecdsa-sha2-nistp256",
+ "ecdsa-sha2-nistp384",
+ "ecdsa-sha2-nistp521",
+ "ssh-rsa",
+ "ssh-dss",
)
_preferred_kex = (
- 'ecdh-sha2-nistp256',
- 'ecdh-sha2-nistp384',
- 'ecdh-sha2-nistp521',
- 'diffie-hellman-group-exchange-sha256',
- 'diffie-hellman-group14-sha256',
- 'diffie-hellman-group-exchange-sha1',
- 'diffie-hellman-group14-sha1',
- 'diffie-hellman-group1-sha1',
+ "ecdh-sha2-nistp256",
+ "ecdh-sha2-nistp384",
+ "ecdh-sha2-nistp521",
+ "diffie-hellman-group-exchange-sha256",
+ "diffie-hellman-group14-sha256",
+ "diffie-hellman-group-exchange-sha1",
+ "diffie-hellman-group14-sha1",
+ "diffie-hellman-group1-sha1",
)
_preferred_gsskex = (
- 'gss-gex-sha1-toWM5Slw5Ew8Mqkay+al2g==',
- 'gss-group14-sha1-toWM5Slw5Ew8Mqkay+al2g==',
- 'gss-group1-sha1-toWM5Slw5Ew8Mqkay+al2g==',
+ "gss-gex-sha1-toWM5Slw5Ew8Mqkay+al2g==",
+ "gss-group14-sha1-toWM5Slw5Ew8Mqkay+al2g==",
+ "gss-group1-sha1-toWM5Slw5Ew8Mqkay+al2g==",
)
- _preferred_compression = ('none',)
+ _preferred_compression = ("none",)
_cipher_info = {
- 'aes128-ctr': {
- 'class': algorithms.AES,
- 'mode': modes.CTR,
- 'block-size': 16,
- 'key-size': 16
+ "aes128-ctr": {
+ "class": algorithms.AES,
+ "mode": modes.CTR,
+ "block-size": 16,
+ "key-size": 16,
},
- 'aes192-ctr': {
- 'class': algorithms.AES,
- 'mode': modes.CTR,
- 'block-size': 16,
- 'key-size': 24
+ "aes192-ctr": {
+ "class": algorithms.AES,
+ "mode": modes.CTR,
+ "block-size": 16,
+ "key-size": 24,
},
- 'aes256-ctr': {
- 'class': algorithms.AES,
- 'mode': modes.CTR,
- 'block-size': 16,
- 'key-size': 32
+ "aes256-ctr": {
+ "class": algorithms.AES,
+ "mode": modes.CTR,
+ "block-size": 16,
+ "key-size": 32,
},
- 'blowfish-cbc': {
- 'class': algorithms.Blowfish,
- 'mode': modes.CBC,
- 'block-size': 8,
- 'key-size': 16
+ "blowfish-cbc": {
+ "class": algorithms.Blowfish,
+ "mode": modes.CBC,
+ "block-size": 8,
+ "key-size": 16,
},
- 'aes128-cbc': {
- 'class': algorithms.AES,
- 'mode': modes.CBC,
- 'block-size': 16,
- 'key-size': 16
+ "aes128-cbc": {
+ "class": algorithms.AES,
+ "mode": modes.CBC,
+ "block-size": 16,
+ "key-size": 16,
},
- 'aes192-cbc': {
- 'class': algorithms.AES,
- 'mode': modes.CBC,
- 'block-size': 16,
- 'key-size': 24
+ "aes192-cbc": {
+ "class": algorithms.AES,
+ "mode": modes.CBC,
+ "block-size": 16,
+ "key-size": 24,
},
- 'aes256-cbc': {
- 'class': algorithms.AES,
- 'mode': modes.CBC,
- 'block-size': 16,
- 'key-size': 32
+ "aes256-cbc": {
+ "class": algorithms.AES,
+ "mode": modes.CBC,
+ "block-size": 16,
+ "key-size": 32,
},
- '3des-cbc': {
- 'class': algorithms.TripleDES,
- 'mode': modes.CBC,
- 'block-size': 8,
- 'key-size': 24
+ "3des-cbc": {
+ "class": algorithms.TripleDES,
+ "mode": modes.CBC,
+ "block-size": 8,
+ "key-size": 24,
},
}
-
_mac_info = {
- 'hmac-sha1': {'class': sha1, 'size': 20},
- 'hmac-sha1-96': {'class': sha1, 'size': 12},
- 'hmac-sha2-256': {'class': sha256, 'size': 32},
- 'hmac-sha2-512': {'class': sha512, 'size': 64},
- 'hmac-md5': {'class': md5, 'size': 16},
- 'hmac-md5-96': {'class': md5, 'size': 12},
+ "hmac-sha1": {"class": sha1, "size": 20},
+ "hmac-sha1-96": {"class": sha1, "size": 12},
+ "hmac-sha2-256": {"class": sha256, "size": 32},
+ "hmac-sha2-512": {"class": sha512, "size": 64},
+ "hmac-md5": {"class": md5, "size": 16},
+ "hmac-md5-96": {"class": md5, "size": 12},
}
_key_info = {
- 'ssh-rsa': RSAKey,
- 'ssh-rsa-cert-v01@openssh.com': RSAKey,
- 'ssh-dss': DSSKey,
- 'ssh-dss-cert-v01@openssh.com': DSSKey,
- 'ecdsa-sha2-nistp256': ECDSAKey,
- 'ecdsa-sha2-nistp256-cert-v01@openssh.com': ECDSAKey,
- 'ecdsa-sha2-nistp384': ECDSAKey,
- 'ecdsa-sha2-nistp384-cert-v01@openssh.com': ECDSAKey,
- 'ecdsa-sha2-nistp521': ECDSAKey,
- 'ecdsa-sha2-nistp521-cert-v01@openssh.com': ECDSAKey,
- 'ssh-ed25519': Ed25519Key,
- 'ssh-ed25519-cert-v01@openssh.com': Ed25519Key,
+ "ssh-rsa": RSAKey,
+ "ssh-rsa-cert-v01@openssh.com": RSAKey,
+ "ssh-dss": DSSKey,
+ "ssh-dss-cert-v01@openssh.com": DSSKey,
+ "ecdsa-sha2-nistp256": ECDSAKey,
+ "ecdsa-sha2-nistp256-cert-v01@openssh.com": ECDSAKey,
+ "ecdsa-sha2-nistp384": ECDSAKey,
+ "ecdsa-sha2-nistp384-cert-v01@openssh.com": ECDSAKey,
+ "ecdsa-sha2-nistp521": ECDSAKey,
+ "ecdsa-sha2-nistp521-cert-v01@openssh.com": ECDSAKey,
+ "ssh-ed25519": Ed25519Key,
+ "ssh-ed25519-cert-v01@openssh.com": Ed25519Key,
}
_kex_info = {
- 'diffie-hellman-group1-sha1': KexGroup1,
- 'diffie-hellman-group14-sha1': KexGroup14,
- 'diffie-hellman-group-exchange-sha1': KexGex,
- 'diffie-hellman-group-exchange-sha256': KexGexSHA256,
- 'diffie-hellman-group14-sha256': KexGroup14SHA256,
- 'gss-group1-sha1-toWM5Slw5Ew8Mqkay+al2g==': KexGSSGroup1,
- 'gss-group14-sha1-toWM5Slw5Ew8Mqkay+al2g==': KexGSSGroup14,
- 'gss-gex-sha1-toWM5Slw5Ew8Mqkay+al2g==': KexGSSGex,
- 'ecdh-sha2-nistp256': KexNistp256,
- 'ecdh-sha2-nistp384': KexNistp384,
- 'ecdh-sha2-nistp521': KexNistp521,
+ "diffie-hellman-group1-sha1": KexGroup1,
+ "diffie-hellman-group14-sha1": KexGroup14,
+ "diffie-hellman-group-exchange-sha1": KexGex,
+ "diffie-hellman-group-exchange-sha256": KexGexSHA256,
+ "diffie-hellman-group14-sha256": KexGroup14SHA256,
+ "gss-group1-sha1-toWM5Slw5Ew8Mqkay+al2g==": KexGSSGroup1,
+ "gss-group14-sha1-toWM5Slw5Ew8Mqkay+al2g==": KexGSSGroup14,
+ "gss-gex-sha1-toWM5Slw5Ew8Mqkay+al2g==": KexGSSGex,
+ "ecdh-sha2-nistp256": KexNistp256,
+ "ecdh-sha2-nistp384": KexNistp384,
+ "ecdh-sha2-nistp521": KexNistp521,
}
_compression_info = {
# zlib@openssh.com is just zlib, but only turned on after a successful
# authentication. openssh servers may only offer this type because
# they've had troubles with security holes in zlib in the past.
- 'zlib@openssh.com': (ZlibCompressor, ZlibDecompressor),
- 'zlib': (ZlibCompressor, ZlibDecompressor),
- 'none': (None, None),
+ "zlib@openssh.com": (ZlibCompressor, ZlibDecompressor),
+ "zlib": (ZlibCompressor, ZlibDecompressor),
+ "none": (None, None),
}
_modulus_pack = None
_active_check_timeout = 0.1
- 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):
+ 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
@@ -304,7 +341,7 @@ class Transport(threading.Thread, ClosingContextManager):
if isinstance(sock, string_types):
# convert "host:port" into (host, port)
- hl = sock.split(':', 1)
+ hl = sock.split(":", 1)
self.hostname = hl[0]
if len(hl) == 1:
sock = (hl[0], 22)
@@ -314,7 +351,7 @@ class Transport(threading.Thread, ClosingContextManager):
# connect to the given (host, port)
hostname, port = sock
self.hostname = hostname
- reason = 'No suitable address family'
+ reason = "No suitable address family"
addrinfos = socket.getaddrinfo(
hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM
)
@@ -331,7 +368,8 @@ class Transport(threading.Thread, ClosingContextManager):
break
else:
raise SSHException(
- 'Unable to connect to {}: {}'.format(hostname, reason))
+ "Unable to connect to {}: {}".format(hostname, reason)
+ )
# okay, normal socket-ish flow here...
threading.Thread.__init__(self)
self.setDaemon(True)
@@ -342,9 +380,9 @@ class Transport(threading.Thread, ClosingContextManager):
# negotiated crypto parameters
self.packetizer = Packetizer(sock)
- self.local_version = 'SSH-' + self._PROTO_ID + '-' + self._CLIENT_ID
- self.remote_version = ''
- self.local_cipher = self.remote_cipher = ''
+ self.local_version = "SSH-" + self._PROTO_ID + "-" + self._CLIENT_ID
+ self.remote_version = ""
+ self.local_cipher = self.remote_cipher = ""
self.local_kex_init = self.remote_kex_init = None
self.local_mac = self.remote_mac = None
self.local_compression = self.remote_compression = None
@@ -376,8 +414,8 @@ class Transport(threading.Thread, ClosingContextManager):
# tracking open channels
self._channels = ChannelMap()
- self.channel_events = {} # (id -> Event)
- self.channels_seen = {} # (id -> True)
+ self.channel_events = {} # (id -> Event)
+ self.channels_seen = {} # (id -> True)
self._channel_counter = 0
self.default_max_packet_size = default_max_packet_size
self.default_window_size = default_window_size
@@ -389,7 +427,7 @@ class Transport(threading.Thread, ClosingContextManager):
self.clear_to_send = threading.Event()
self.clear_to_send_lock = threading.Lock()
self.clear_to_send_timeout = 30.0
- self.log_name = 'paramiko.transport'
+ self.log_name = "paramiko.transport"
self.logger = util.get_logger(self.log_name)
self.packetizer.set_log(self.logger)
self.auth_handler = None
@@ -418,23 +456,24 @@ class Transport(threading.Thread, ClosingContextManager):
Returns a string representation of this object, for debugging.
"""
id_ = hex(long(id(self)) & xffffffff)
- out = '<paramiko.Transport at {}'.format(id_)
+ out = "<paramiko.Transport at {}".format(id_)
if not self.active:
- out += ' (unconnected)'
+ out += " (unconnected)"
else:
- if self.local_cipher != '':
- out += ' (cipher {}, {:d} bits)'.format(
+ if self.local_cipher != "":
+ out += " (cipher {}, {:d} bits)".format(
self.local_cipher,
- self._cipher_info[self.local_cipher]['key-size'] * 8
+ self._cipher_info[self.local_cipher]["key-size"] * 8,
)
if self.is_authenticated():
- out += ' (active; {} open channel(s))'.format(
- len(self._channels))
+ out += " (active; {} open channel(s))".format(
+ len(self._channels)
+ )
elif self.initial_kex_done:
- out += ' (connected; awaiting auth)'
+ out += " (connected; awaiting auth)"
else:
- out += ' (connecting)'
- out += '>'
+ out += " (connecting)"
+ out += ">"
return out
def atfork(self):
@@ -545,10 +584,9 @@ class Transport(threading.Thread, ClosingContextManager):
e = self.get_exception()
if e is not None:
raise e
- raise SSHException('Negotiation failed.')
- if (
- event.is_set() or
- (timeout is not None and time.time() >= max_time)
+ raise SSHException("Negotiation failed.")
+ if event.is_set() or (
+ timeout is not None and time.time() >= max_time
):
break
@@ -614,7 +652,7 @@ class Transport(threading.Thread, ClosingContextManager):
e = self.get_exception()
if e is not None:
raise e
- raise SSHException('Negotiation failed.')
+ raise SSHException("Negotiation failed.")
if event.is_set():
break
@@ -681,7 +719,7 @@ class Transport(threading.Thread, ClosingContextManager):
"""
Transport._modulus_pack = ModulusPack()
# places to look for the openssh "moduli" file
- file_list = ['/etc/ssh/moduli', '/usr/local/etc/moduli']
+ file_list = ["/etc/ssh/moduli", "/usr/local/etc/moduli"]
if filename is not None:
file_list.insert(0, filename)
for fn in file_list:
@@ -719,7 +757,7 @@ class Transport(threading.Thread, ClosingContextManager):
:return: public key (`.PKey`) of the remote server
"""
if (not self.active) or (not self.initial_kex_done):
- raise SSHException('No existing session')
+ raise SSHException("No existing session")
return self.host_key
def is_active(self):
@@ -733,10 +771,7 @@ class Transport(threading.Thread, ClosingContextManager):
return self.active
def open_session(
- self,
- window_size=None,
- max_packet_size=None,
- timeout=None,
+ self, window_size=None, max_packet_size=None, timeout=None
):
"""
Request a new channel to the server, of type ``"session"``. This is
@@ -763,10 +798,12 @@ class Transport(threading.Thread, ClosingContextManager):
.. versionchanged:: 1.15
Added the ``window_size`` and ``max_packet_size`` arguments.
"""
- return self.open_channel('session',
- window_size=window_size,
- max_packet_size=max_packet_size,
- timeout=timeout)
+ 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):
"""
@@ -782,7 +819,7 @@ class Transport(threading.Thread, ClosingContextManager):
`.SSHException` -- if the request is rejected or the session ends
prematurely
"""
- return self.open_channel('x11', src_addr=src_addr)
+ return self.open_channel("x11", src_addr=src_addr)
def open_forward_agent_channel(self):
"""
@@ -796,7 +833,7 @@ class Transport(threading.Thread, ClosingContextManager):
:raises: `.SSHException` --
if the request is rejected or the session ends prematurely
"""
- return self.open_channel('auth-agent@openssh.com')
+ return self.open_channel("auth-agent@openssh.com")
def open_forwarded_tcpip_channel(self, src_addr, dest_addr):
"""
@@ -808,15 +845,17 @@ class Transport(threading.Thread, ClosingContextManager):
:param src_addr: originator's address
:param dest_addr: local (server) connected address
"""
- return self.open_channel('forwarded-tcpip', dest_addr, src_addr)
+ return self.open_channel("forwarded-tcpip", dest_addr, src_addr)
- def open_channel(self,
- kind,
- dest_addr=None,
- src_addr=None,
- window_size=None,
- max_packet_size=None,
- timeout=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
@@ -853,7 +892,7 @@ class Transport(threading.Thread, ClosingContextManager):
Added the ``window_size`` and ``max_packet_size`` arguments.
"""
if not self.active:
- raise SSHException('SSH session not active')
+ raise SSHException("SSH session not active")
timeout = 3600 if timeout is None else timeout
self.lock.acquire()
try:
@@ -866,12 +905,12 @@ class Transport(threading.Thread, ClosingContextManager):
m.add_int(chanid)
m.add_int(window_size)
m.add_int(max_packet_size)
- if (kind == 'forwarded-tcpip') or (kind == 'direct-tcpip'):
+ if (kind == "forwarded-tcpip") or (kind == "direct-tcpip"):
m.add_string(dest_addr[0])
m.add_int(dest_addr[1])
m.add_string(src_addr[0])
m.add_int(src_addr[1])
- elif kind == 'x11':
+ elif kind == "x11":
m.add_string(src_addr[0])
m.add_int(src_addr[1])
chan = Channel(chanid)
@@ -889,18 +928,18 @@ class Transport(threading.Thread, ClosingContextManager):
if not self.active:
e = self.get_exception()
if e is None:
- e = SSHException('Unable to open channel.')
+ e = SSHException("Unable to open channel.")
raise e
if event.is_set():
break
elif start_ts + timeout < time.time():
- raise SSHException('Timeout opening channel.')
+ raise SSHException("Timeout opening channel.")
chan = self._channels.get(chanid)
if chan is not None:
return chan
e = self.get_exception()
if e is None:
- e = SSHException('Unable to open channel.')
+ e = SSHException("Unable to open channel.")
raise e
def request_port_forward(self, address, port, handler=None):
@@ -937,20 +976,22 @@ class Transport(threading.Thread, ClosingContextManager):
`.SSHException` -- if the server refused the TCP forward request
"""
if not self.active:
- raise SSHException('SSH session not active')
+ raise SSHException("SSH session not active")
port = int(port)
response = self.global_request(
- 'tcpip-forward', (address, port), wait=True
+ "tcpip-forward", (address, port), wait=True
)
if response is None:
- raise SSHException('TCP forwarding request denied')
+ raise SSHException("TCP forwarding request denied")
if port == 0:
port = response.get_int()
if handler is None:
+
def default_handler(channel, src_addr, dest_addr_port):
# src_addr, src_port = src_addr_port
# dest_addr, dest_port = dest_addr_port
self._queue_incoming_channel(channel)
+
handler = default_handler
self._tcp_handler = handler
return port
@@ -967,7 +1008,7 @@ class Transport(threading.Thread, ClosingContextManager):
if not self.active:
return
self._tcp_handler = None
- self.global_request('cancel-tcpip-forward', (address, port), wait=True)
+ self.global_request("cancel-tcpip-forward", (address, port), wait=True)
def open_sftp_client(self):
"""
@@ -1020,7 +1061,7 @@ class Transport(threading.Thread, ClosingContextManager):
e = self.get_exception()
if e is not None:
raise e
- raise SSHException('Negotiation failed.')
+ raise SSHException("Negotiation failed.")
if self.completion_event.is_set():
break
return
@@ -1036,8 +1077,10 @@ class Transport(threading.Thread, ClosingContextManager):
seconds to wait before sending a keepalive packet (or
0 to disable keepalives).
"""
+
def _request(x=weakref.proxy(self)):
- return x.global_request('keepalive@lag.net', wait=False)
+ return x.global_request("keepalive@lag.net", wait=False)
+
self.packetizer.set_keepalive(interval, _request)
def global_request(self, kind, data=None, wait=True):
@@ -1105,7 +1148,7 @@ class Transport(threading.Thread, ClosingContextManager):
def connect(
self,
hostkey=None,
- username='',
+ username="",
password=None,
pkey=None,
gss_host=None,
@@ -1179,34 +1222,43 @@ class Transport(threading.Thread, ClosingContextManager):
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()
+ key.get_name() != hostkey.get_name()
+ or key.asbytes() != hostkey.asbytes()
):
- self._log(DEBUG, 'Bad host key from server')
- self._log(DEBUG, 'Expected: {}: {}'.format(
- hostkey.get_name(), repr(hostkey.asbytes()),
- ))
- self._log(DEBUG, 'Got : {}: {}'.format(
- key.get_name(), repr(key.asbytes()),
- ))
- raise SSHException('Bad host key from server')
- self._log(DEBUG, 'Host key verified ({})'.format(
- hostkey.get_name()))
+ self._log(DEBUG, "Bad host key from server")
+ self._log(
+ DEBUG,
+ "Expected: {}: {}".format(
+ hostkey.get_name(), repr(hostkey.asbytes())
+ ),
+ )
+ self._log(
+ DEBUG,
+ "Got : {}: {}".format(
+ key.get_name(), repr(key.asbytes())
+ ),
+ )
+ raise SSHException("Bad host key from server")
+ self._log(
+ DEBUG, "Host key verified ({})".format(hostkey.get_name())
+ )
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)') # noqa
+ self._log(
+ DEBUG, "Attempting GSS-API auth... (gssapi-with-mic)"
+ ) # noqa
self.auth_gssapi_with_mic(
- username, self.gss_host, gss_deleg_creds,
+ username, self.gss_host, gss_deleg_creds
)
elif gss_kex:
- self._log(DEBUG, 'Attempting GSS-API auth... (gssapi-keyex)')
+ 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._log(DEBUG, "Attempting public-key auth...")
self.auth_publickey(username, pkey)
else:
- self._log(DEBUG, 'Attempting password auth...')
+ self._log(DEBUG, "Attempting password auth...")
self.auth_password(username, password)
return
@@ -1261,9 +1313,9 @@ class Transport(threading.Thread, ClosingContextManager):
closed.
"""
return (
- self.active and
- self.auth_handler is not None and
- self.auth_handler.is_authenticated()
+ self.active
+ and self.auth_handler is not None
+ and self.auth_handler.is_authenticated()
)
def get_username(self):
@@ -1313,7 +1365,7 @@ class Transport(threading.Thread, ClosingContextManager):
.. versionadded:: 1.5
"""
if (not self.active) or (not self.initial_kex_done):
- raise SSHException('No existing session')
+ raise SSHException("No existing session")
my_event = threading.Event()
self.auth_handler = AuthHandler(self)
self.auth_handler.auth_none(username, my_event)
@@ -1369,7 +1421,7 @@ class Transport(threading.Thread, ClosingContextManager):
if (not self.active) or (not self.initial_kex_done):
# we should never try to send the password unless we're on a secure
# link
- raise SSHException('No existing session')
+ raise SSHException("No existing session")
if event is None:
my_event = threading.Event()
else:
@@ -1384,12 +1436,13 @@ class Transport(threading.Thread, ClosingContextManager):
except BadAuthenticationType as e:
# if password auth isn't allowed, but keyboard-interactive *is*,
# try to fudge it
- if not fallback or ('keyboard-interactive' not in e.allowed_types):
+ if not fallback or ("keyboard-interactive" not in e.allowed_types):
raise
try:
+
def handler(title, instructions, fields):
if len(fields) > 1:
- raise SSHException('Fallback authentication failed.')
+ raise SSHException("Fallback authentication failed.")
if len(fields) == 0:
# for some reason, at least on os x, a 2nd request will
# be made with zero fields requested. maybe it's just
@@ -1397,6 +1450,7 @@ class Transport(threading.Thread, ClosingContextManager):
# type we're doing here. *shrug* :)
return []
return [password]
+
return self.auth_interactive(username, handler)
except SSHException:
# attempt failed; just raise the original exception
@@ -1439,7 +1493,7 @@ class Transport(threading.Thread, ClosingContextManager):
"""
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')
+ raise SSHException("No existing session")
if event is None:
my_event = threading.Event()
else:
@@ -1451,7 +1505,7 @@ class Transport(threading.Thread, ClosingContextManager):
return []
return self.auth_handler.wait_for_response(my_event)
- def auth_interactive(self, username, handler, submethods=''):
+ def auth_interactive(self, username, handler, submethods=""):
"""
Authenticate to the server interactively. A handler is used to answer
arbitrary questions from the server. On many servers, this is just a
@@ -1496,7 +1550,7 @@ class Transport(threading.Thread, ClosingContextManager):
"""
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')
+ raise SSHException("No existing session")
my_event = threading.Event()
self.auth_handler = AuthHandler(self)
self.auth_handler.auth_interactive(
@@ -1504,7 +1558,7 @@ class Transport(threading.Thread, ClosingContextManager):
)
return self.auth_handler.wait_for_response(my_event)
- def auth_interactive_dumb(self, username, handler=None, submethods=''):
+ def auth_interactive_dumb(self, username, handler=None, submethods=""):
"""
Autenticate to the server interactively but dumber.
Just print the prompt and / or instructions to stdout and send back
@@ -1513,6 +1567,7 @@ class Transport(threading.Thread, ClosingContextManager):
"""
if not handler:
+
def handler(title, instructions, prompt_list):
answers = []
if title:
@@ -1520,9 +1575,10 @@ class Transport(threading.Thread, ClosingContextManager):
if instructions:
print(instructions.strip())
for prompt, show_input in prompt_list:
- print(prompt.strip(), end=' ')
+ print(prompt.strip(), end=" ")
answers.append(input())
return answers
+
return self.auth_interactive(username, handler, submethods)
def auth_gssapi_with_mic(self, username, gss_host, gss_deleg_creds):
@@ -1543,7 +1599,7 @@ class Transport(threading.Thread, ClosingContextManager):
"""
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')
+ raise SSHException("No existing session")
my_event = threading.Event()
self.auth_handler = AuthHandler(self)
self.auth_handler.auth_gssapi_with_mic(
@@ -1568,7 +1624,7 @@ class Transport(threading.Thread, ClosingContextManager):
"""
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')
+ raise SSHException("No existing session")
my_event = threading.Event()
self.auth_handler = AuthHandler(self)
self.auth_handler.auth_gssapi_keyex(username, my_event)
@@ -1635,9 +1691,9 @@ class Transport(threading.Thread, ClosingContextManager):
.. versionadded:: 1.5.2
"""
if compress:
- self._preferred_compression = ('zlib@openssh.com', 'zlib', 'none')
+ self._preferred_compression = ("zlib@openssh.com", "zlib", "none")
else:
- self._preferred_compression = ('none',)
+ self._preferred_compression = ("none",)
def getpeername(self):
"""
@@ -1651,9 +1707,9 @@ class Transport(threading.Thread, ClosingContextManager):
the address of the remote host, if known, as a ``(str, int)``
tuple.
"""
- gp = getattr(self.sock, 'getpeername', None)
+ gp = getattr(self.sock, "getpeername", None)
if gp is None:
- return 'unknown', 0
+ return "unknown", 0
return gp()
def stop_thread(self):
@@ -1672,10 +1728,10 @@ class Transport(threading.Thread, ClosingContextManager):
# our socket and packetizer are both closed (but where we'd
# otherwise be sitting forever on that recv()).
while (
- self.is_alive() and
- self is not threading.current_thread() and
- not self.sock._closed and
- not self.packetizer.closed
+ self.is_alive()
+ and self is not threading.current_thread()
+ and not self.sock._closed
+ and not self.packetizer.closed
):
self.join(0.1)
@@ -1717,14 +1773,18 @@ class Transport(threading.Thread, ClosingContextManager):
while True:
self.clear_to_send.wait(0.1)
if not self.active:
- self._log(DEBUG, 'Dropping user packet because connection is dead.') # noqa
+ self._log(
+ DEBUG, "Dropping user packet because connection is dead."
+ ) # noqa
return
self.clear_to_send_lock.acquire()
if self.clear_to_send.is_set():
break
self.clear_to_send_lock.release()
if time.time() > start + self.clear_to_send_timeout:
- raise SSHException('Key-exchange timed out waiting for key negotiation') # noqa
+ raise SSHException(
+ "Key-exchange timed out waiting for key negotiation"
+ ) # noqa
try:
self._send_message(data)
finally:
@@ -1748,9 +1808,13 @@ class Transport(threading.Thread, ClosingContextManager):
def _verify_key(self, host_key, sig):
key = self._key_info[self.host_key_type](Message(host_key))
if key is None:
- raise SSHException('Unknown host key type')
+ raise SSHException("Unknown host key type")
if not key.verify_ssh_sig(self.H, Message(sig)):
- raise SSHException('Signature verification ({}) failed.'.format(self.host_key_type)) # noqa
+ raise SSHException(
+ "Signature verification ({}) failed.".format(
+ self.host_key_type
+ )
+ ) # noqa
self.host_key = key
def _compute_key(self, id, nbytes):
@@ -1762,16 +1826,16 @@ class Transport(threading.Thread, ClosingContextManager):
m.add_bytes(self.session_id)
# Fallback to SHA1 for kex engines that fail to specify a hex
# algorithm, or for e.g. transport tests that don't run kexinit.
- hash_algo = getattr(self.kex_engine, 'hash_algo', None)
+ hash_algo = getattr(self.kex_engine, "hash_algo", None)
hash_select_msg = "kex engine {} specified hash_algo {!r}".format(
- self.kex_engine.__class__.__name__, hash_algo,
+ self.kex_engine.__class__.__name__, hash_algo
)
if hash_algo is None:
hash_algo = sha1
hash_select_msg += ", falling back to sha1"
- if not hasattr(self, '_logged_hash_selection'):
+ if not hasattr(self, "_logged_hash_selection"):
self._log(DEBUG, hash_select_msg)
- setattr(self, '_logged_hash_selection', True)
+ setattr(self, "_logged_hash_selection", True)
out = sofar = hash_algo(m.asbytes()).digest()
while len(out) < nbytes:
m = Message()
@@ -1785,11 +1849,11 @@ class Transport(threading.Thread, ClosingContextManager):
def _get_cipher(self, name, key, iv, operation):
if name not in self._cipher_info:
- raise SSHException('Unknown client cipher ' + name)
+ raise SSHException("Unknown client cipher " + name)
else:
cipher = Cipher(
- self._cipher_info[name]['class'](key),
- self._cipher_info[name]['mode'](iv),
+ self._cipher_info[name]["class"](key),
+ self._cipher_info[name]["mode"](iv),
backend=default_backend(),
)
if operation is self._ENCRYPT:
@@ -1799,8 +1863,10 @@ class Transport(threading.Thread, ClosingContextManager):
def _set_forward_agent_handler(self, handler):
if handler is None:
+
def default_handler(channel):
self._queue_incoming_channel(channel)
+
self._forward_agent_handler = default_handler
else:
self._forward_agent_handler = handler
@@ -1811,6 +1877,7 @@ class Transport(threading.Thread, ClosingContextManager):
# by default, use the same mechanism as accept()
def default_handler(channel, src_addr_port):
self._queue_incoming_channel(channel)
+
self._x11_handler = default_handler
else:
self._x11_handler = handler
@@ -1844,9 +1911,9 @@ class Transport(threading.Thread, ClosingContextManager):
Otherwise (client mode, authed, or pre-auth message) returns None.
"""
if (
- not self.server_mode or
- ptype <= HIGHEST_USERAUTH_MESSAGE_ID or
- self.is_authenticated()
+ not self.server_mode
+ or ptype <= HIGHEST_USERAUTH_MESSAGE_ID
+ or self.is_authenticated()
):
return None
# WELP. We must be dealing with someone trying to do non-auth things
@@ -1857,13 +1924,13 @@ class Transport(threading.Thread, ClosingContextManager):
reply.add_byte(cMSG_REQUEST_FAILURE)
# Channel opens let us reject w/ a specific type + message.
elif ptype == MSG_CHANNEL_OPEN:
- kind = message.get_text() # noqa
+ kind = message.get_text() # noqa
chanid = message.get_int()
reply.add_byte(cMSG_CHANNEL_OPEN_FAILURE)
reply.add_int(chanid)
reply.add_int(OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED)
- reply.add_string('')
- reply.add_string('en')
+ reply.add_string("")
+ reply.add_string("en")
# NOTE: Post-open channel messages do not need checking; the above will
# reject attemps to open channels, meaning that even if a malicious
# user tries to send a MSG_CHANNEL_REQUEST, it will simply fall under
@@ -1885,13 +1952,16 @@ class Transport(threading.Thread, ClosingContextManager):
_active_threads.append(self)
tid = hex(long(id(self)) & xffffffff)
if self.server_mode:
- self._log(DEBUG, 'starting thread (server mode): {}'.format(tid))
+ self._log(DEBUG, "starting thread (server mode): {}".format(tid))
else:
- self._log(DEBUG, 'starting thread (client mode): {}'.format(tid))
+ self._log(DEBUG, "starting thread (client mode): {}".format(tid))
try:
try:
- self.packetizer.write_all(b(self.local_version + '\r\n'))
- self._log(DEBUG, 'Local version/idstring: {}'.format(self.local_version)) # noqa
+ self.packetizer.write_all(b(self.local_version + "\r\n"))
+ self._log(
+ DEBUG,
+ "Local version/idstring: {}".format(self.local_version),
+ ) # noqa
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
@@ -1921,7 +1991,11 @@ class Transport(threading.Thread, ClosingContextManager):
continue
if len(self._expected_packet) > 0:
if ptype not in self._expected_packet:
- raise SSHException('Expecting packet from {!r}, got {:d}'.format(self._expected_packet, ptype)) # noqa
+ raise SSHException(
+ "Expecting packet from {!r}, got {:d}".format(
+ self._expected_packet, ptype
+ )
+ ) # noqa
self._expected_packet = tuple()
if (ptype >= 30) and (ptype <= 41):
self.kex_engine.parse_next(ptype, m)
@@ -1939,20 +2013,30 @@ class Transport(threading.Thread, ClosingContextManager):
if chan is not None:
self._channel_handler_table[ptype](chan, m)
elif chanid in self.channels_seen:
- self._log(DEBUG, 'Ignoring message for dead channel {:d}'.format(chanid)) # noqa
+ self._log(
+ DEBUG,
+ "Ignoring message for dead channel {:d}".format( # noqa
+ chanid
+ ),
+ )
else:
- self._log(ERROR, 'Channel request for unknown channel {:d}'.format(chanid)) # noqa
+ self._log(
+ ERROR,
+ "Channel request for unknown channel {:d}".format( # noqa
+ chanid
+ ),
+ )
break
elif (
- self.auth_handler is not None and
- ptype in self.auth_handler._handler_table
+ self.auth_handler is not None
+ and ptype in self.auth_handler._handler_table
):
handler = self.auth_handler._handler_table[ptype]
handler(self.auth_handler, m)
if len(self._expected_packet) > 0:
continue
else:
- err = 'Oops, unhandled type {:d}'.format(ptype)
+ err = "Oops, unhandled type {:d}".format(ptype)
self._log(WARNING, err)
msg = Message()
msg.add_byte(cMSG_UNIMPLEMENTED)
@@ -1960,24 +2044,24 @@ class Transport(threading.Thread, ClosingContextManager):
self._send_message(msg)
self.packetizer.complete_handshake()
except SSHException as e:
- self._log(ERROR, 'Exception: ' + str(e))
+ self._log(ERROR, "Exception: " + str(e))
self._log(ERROR, util.tb_strings())
self.saved_exception = e
except EOFError as e:
- self._log(DEBUG, 'EOF in transport thread')
+ self._log(DEBUG, "EOF in transport thread")
self.saved_exception = e
except socket.error as e:
if type(e.args) is tuple:
if e.args:
- emsg = '{} ({:d})'.format(e.args[1], e.args[0])
+ emsg = "{} ({:d})".format(e.args[1], e.args[0])
else: # empty tuple, e.g. socket.timeout
emsg = str(e) or repr(e)
else:
emsg = e.args
- self._log(ERROR, 'Socket exception: ' + emsg)
+ self._log(ERROR, "Socket exception: " + emsg)
self.saved_exception = e
except Exception as e:
- self._log(ERROR, 'Unknown exception: ' + str(e))
+ self._log(ERROR, "Unknown exception: " + str(e))
self._log(ERROR, util.tb_strings())
self.saved_exception = e
_active_threads.remove(self)
@@ -2006,7 +2090,6 @@ class Transport(threading.Thread, ClosingContextManager):
if self.sys.modules is not None:
raise
-
def _log_agreement(self, which, local, remote):
# Log useful, non-duplicative line re: an agreed-upon algorithm.
# Old code implied algorithms could be asymmetrical (different for
@@ -2048,32 +2131,32 @@ class Transport(threading.Thread, ClosingContextManager):
raise
except Exception as e:
raise SSHException(
- 'Error reading SSH protocol banner' + str(e)
+ "Error reading SSH protocol banner" + str(e)
)
- if buf[:4] == 'SSH-':
+ if buf[:4] == "SSH-":
break
- self._log(DEBUG, 'Banner: ' + buf)
- if buf[:4] != 'SSH-':
+ self._log(DEBUG, "Banner: " + buf)
+ if buf[:4] != "SSH-":
raise SSHException('Indecipherable protocol version "' + buf + '"')
# save this server version string for later
self.remote_version = buf
- self._log(DEBUG, 'Remote version/idstring: {}'.format(buf))
+ self._log(DEBUG, "Remote version/idstring: {}".format(buf))
# pull off any attached comment
# NOTE: comment used to be stored in a variable and then...never used.
# since 2003. ca 877cd974b8182d26fa76d566072917ea67b64e67
- i = buf.find(' ')
+ i = buf.find(" ")
if i >= 0:
buf = buf[:i]
# parse out version string and make sure it matches
- segs = buf.split('-', 2)
+ segs = buf.split("-", 2)
if len(segs) < 3:
- raise SSHException('Invalid SSH banner')
+ raise SSHException("Invalid SSH banner")
version = segs[1]
client = segs[2]
- if version != '1.99' and version != '2.0':
- msg = 'Incompatible version ({} instead of 2.0)'
+ if version != "1.99" and version != "2.0":
+ msg = "Incompatible version ({} instead of 2.0)"
raise SSHException(msg.format(version))
- msg = 'Connected (version {}, client {})'.format(version, client)
+ msg = "Connected (version {}, client {})".format(version, client)
self._log(INFO, msg)
def _send_kex_init(self):
@@ -2089,25 +2172,27 @@ class Transport(threading.Thread, ClosingContextManager):
self.gss_kex_used = False
self.in_kex = True
if self.server_mode:
- mp_required_prefix = 'diffie-hellman-group-exchange-sha'
+ mp_required_prefix = "diffie-hellman-group-exchange-sha"
kex_mp = [
- k for k
- in self._preferred_kex
+ k
+ for k in self._preferred_kex
if k.startswith(mp_required_prefix)
]
if (self._modulus_pack is None) and (len(kex_mp) > 0):
# can't do group-exchange if we don't have a pack of potential
# primes
pkex = [
- k for k
- in self.get_security_options().kex
+ k
+ for k in self.get_security_options().kex
if not k.startswith(mp_required_prefix)
]
self.get_security_options().kex = pkex
- available_server_keys = list(filter(
- list(self.server_key_dict.keys()).__contains__,
- self._preferred_keys
- ))
+ available_server_keys = list(
+ filter(
+ list(self.server_key_dict.keys()).__contains__,
+ self._preferred_keys,
+ )
+ )
else:
available_server_keys = self._preferred_keys
@@ -2131,7 +2216,7 @@ class Transport(threading.Thread, ClosingContextManager):
self._send_message(m)
def _parse_kex_init(self, m):
- m.get_bytes(16) # cookie, discarded
+ m.get_bytes(16) # cookie, discarded
kex_algo_list = m.get_list()
server_key_algo_list = m.get_list()
client_encrypt_algo_list = m.get_list()
@@ -2143,20 +2228,32 @@ class Transport(threading.Thread, ClosingContextManager):
client_lang_list = m.get_list()
server_lang_list = m.get_list()
kex_follows = m.get_boolean()
- m.get_int() # unused
-
- self._log(DEBUG,
- 'kex algos:' + str(kex_algo_list) +
- ' server key:' + str(server_key_algo_list) +
- ' client encrypt:' + str(client_encrypt_algo_list) +
- ' server encrypt:' + str(server_encrypt_algo_list) +
- ' client mac:' + str(client_mac_algo_list) +
- ' server mac:' + str(server_mac_algo_list) +
- ' client compress:' + str(client_compress_algo_list) +
- ' server compress:' + str(server_compress_algo_list) +
- ' client lang:' + str(client_lang_list) +
- ' server lang:' + str(server_lang_list) +
- ' kex follows?' + str(kex_follows)
+ m.get_int() # unused
+
+ self._log(
+ DEBUG,
+ "kex algos:"
+ + str(kex_algo_list)
+ + " server key:"
+ + str(server_key_algo_list)
+ + " client encrypt:"
+ + str(client_encrypt_algo_list)
+ + " server encrypt:"
+ + str(server_encrypt_algo_list)
+ + " client mac:"
+ + str(client_mac_algo_list)
+ + " server mac:"
+ + str(server_mac_algo_list)
+ + " client compress:"
+ + str(client_compress_algo_list)
+ + " server compress:"
+ + str(server_compress_algo_list)
+ + " client lang:"
+ + str(client_lang_list)
+ + " server lang:"
+ + str(server_lang_list)
+ + " kex follows?"
+ + str(kex_follows),
)
# as a server, we pick the first item in the client's list that we
@@ -2164,122 +2261,150 @@ class Transport(threading.Thread, ClosingContextManager):
# as a client, we pick the first item in our list that the server
# supports.
if self.server_mode:
- agreed_kex = list(filter(
- self._preferred_kex.__contains__,
- kex_algo_list
- ))
+ agreed_kex = list(
+ filter(self._preferred_kex.__contains__, kex_algo_list)
+ )
else:
- agreed_kex = list(filter(
- kex_algo_list.__contains__,
- self._preferred_kex
- ))
+ agreed_kex = list(
+ filter(kex_algo_list.__contains__, self._preferred_kex)
+ )
if len(agreed_kex) == 0:
- raise SSHException('Incompatible ssh peer (no acceptable kex algorithm)') # noqa
+ raise SSHException(
+ "Incompatible ssh peer (no acceptable kex algorithm)"
+ ) # noqa
self.kex_engine = self._kex_info[agreed_kex[0]](self)
self._log(DEBUG, "Kex agreed: {}".format(agreed_kex[0]))
if self.server_mode:
- available_server_keys = list(filter(
- list(self.server_key_dict.keys()).__contains__,
- self._preferred_keys
- ))
- agreed_keys = list(filter(
- available_server_keys.__contains__, server_key_algo_list
- ))
+ available_server_keys = list(
+ filter(
+ list(self.server_key_dict.keys()).__contains__,
+ self._preferred_keys,
+ )
+ )
+ agreed_keys = list(
+ filter(
+ available_server_keys.__contains__, server_key_algo_list
+ )
+ )
else:
- agreed_keys = list(filter(
- server_key_algo_list.__contains__, self._preferred_keys
- ))
+ agreed_keys = list(
+ filter(server_key_algo_list.__contains__, self._preferred_keys)
+ )
if len(agreed_keys) == 0:
- raise SSHException('Incompatible ssh peer (no acceptable host key)') # noqa
+ raise SSHException(
+ "Incompatible ssh peer (no acceptable host key)"
+ ) # noqa
self.host_key_type = agreed_keys[0]
if self.server_mode and (self.get_server_key() is None):
- raise SSHException('Incompatible ssh peer (can\'t match requested host key type)') # noqa
- self._log_agreement(
- 'HostKey', agreed_keys[0], agreed_keys[0]
- )
+ raise SSHException(
+ "Incompatible ssh peer (can't match requested host key type)"
+ ) # noqa
+ self._log_agreement("HostKey", agreed_keys[0], agreed_keys[0])
if self.server_mode:
- agreed_local_ciphers = list(filter(
- self._preferred_ciphers.__contains__,
- server_encrypt_algo_list
- ))
- agreed_remote_ciphers = list(filter(
- self._preferred_ciphers.__contains__,
- client_encrypt_algo_list
- ))
+ agreed_local_ciphers = list(
+ filter(
+ self._preferred_ciphers.__contains__,
+ server_encrypt_algo_list,
+ )
+ )
+ agreed_remote_ciphers = list(
+ filter(
+ self._preferred_ciphers.__contains__,
+ client_encrypt_algo_list,
+ )
+ )
else:
- agreed_local_ciphers = list(filter(
- client_encrypt_algo_list.__contains__,
- self._preferred_ciphers
- ))
- agreed_remote_ciphers = list(filter(
- server_encrypt_algo_list.__contains__,
- self._preferred_ciphers
- ))
+ agreed_local_ciphers = list(
+ filter(
+ client_encrypt_algo_list.__contains__,
+ self._preferred_ciphers,
+ )
+ )
+ agreed_remote_ciphers = list(
+ filter(
+ server_encrypt_algo_list.__contains__,
+ self._preferred_ciphers,
+ )
+ )
if len(agreed_local_ciphers) == 0 or len(agreed_remote_ciphers) == 0:
- raise SSHException('Incompatible ssh server (no acceptable ciphers)') # noqa
+ raise SSHException(
+ "Incompatible ssh server (no acceptable ciphers)"
+ ) # noqa
self.local_cipher = agreed_local_ciphers[0]
self.remote_cipher = agreed_remote_ciphers[0]
self._log_agreement(
- 'Cipher', local=self.local_cipher, remote=self.remote_cipher
+ "Cipher", local=self.local_cipher, remote=self.remote_cipher
)
if self.server_mode:
- agreed_remote_macs = list(filter(
- self._preferred_macs.__contains__, client_mac_algo_list
- ))
- agreed_local_macs = list(filter(
- self._preferred_macs.__contains__, server_mac_algo_list
- ))
+ agreed_remote_macs = list(
+ filter(self._preferred_macs.__contains__, client_mac_algo_list)
+ )
+ agreed_local_macs = list(
+ filter(self._preferred_macs.__contains__, server_mac_algo_list)
+ )
else:
- agreed_local_macs = list(filter(
- client_mac_algo_list.__contains__, self._preferred_macs
- ))
- agreed_remote_macs = list(filter(
- server_mac_algo_list.__contains__, self._preferred_macs
- ))
+ agreed_local_macs = list(
+ filter(client_mac_algo_list.__contains__, self._preferred_macs)
+ )
+ agreed_remote_macs = list(
+ 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)')
+ raise SSHException("Incompatible ssh server (no acceptable macs)")
self.local_mac = agreed_local_macs[0]
self.remote_mac = agreed_remote_macs[0]
self._log_agreement(
- 'MAC', local=self.local_mac, remote=self.remote_mac
+ "MAC", local=self.local_mac, remote=self.remote_mac
)
if self.server_mode:
- agreed_remote_compression = list(filter(
- self._preferred_compression.__contains__,
- client_compress_algo_list
- ))
- agreed_local_compression = list(filter(
- self._preferred_compression.__contains__,
- server_compress_algo_list
- ))
+ agreed_remote_compression = list(
+ filter(
+ self._preferred_compression.__contains__,
+ client_compress_algo_list,
+ )
+ )
+ agreed_local_compression = list(
+ filter(
+ self._preferred_compression.__contains__,
+ server_compress_algo_list,
+ )
+ )
else:
- agreed_local_compression = list(filter(
- client_compress_algo_list.__contains__,
- self._preferred_compression
- ))
- agreed_remote_compression = list(filter(
- server_compress_algo_list.__contains__,
- self._preferred_compression
- ))
+ agreed_local_compression = list(
+ filter(
+ client_compress_algo_list.__contains__,
+ self._preferred_compression,
+ )
+ )
+ agreed_remote_compression = list(
+ filter(
+ server_compress_algo_list.__contains__,
+ self._preferred_compression,
+ )
+ )
if (
- len(agreed_local_compression) == 0 or
- len(agreed_remote_compression) == 0
+ len(agreed_local_compression) == 0
+ or len(agreed_remote_compression) == 0
):
- msg = 'Incompatible ssh server (no acceptable compression) {!r} {!r} {!r}' # noqa
- raise SSHException(msg.format(
- agreed_local_compression, agreed_remote_compression,
- self._preferred_compression,
- ))
+ msg = "Incompatible ssh server (no acceptable compression)"
+ msg += " {!r} {!r} {!r}"
+ raise SSHException(
+ msg.format(
+ agreed_local_compression,
+ agreed_remote_compression,
+ self._preferred_compression,
+ )
+ )
self.local_compression = agreed_local_compression[0]
self.remote_compression = agreed_remote_compression[0]
self._log_agreement(
- 'Compression',
+ "Compression",
local=self.local_compression,
- remote=self.remote_compression
+ remote=self.remote_compression,
)
# save for computing hash later...
@@ -2292,40 +2417,36 @@ class Transport(threading.Thread, ClosingContextManager):
def _activate_inbound(self):
"""switch on newly negotiated encryption parameters for
inbound traffic"""
- block_size = self._cipher_info[self.remote_cipher]['block-size']
+ block_size = self._cipher_info[self.remote_cipher]["block-size"]
if self.server_mode:
- IV_in = self._compute_key('A', block_size)
+ IV_in = self._compute_key("A", block_size)
key_in = self._compute_key(
- 'C', self._cipher_info[self.remote_cipher]['key-size']
+ "C", self._cipher_info[self.remote_cipher]["key-size"]
)
else:
- IV_in = self._compute_key('B', block_size)
+ IV_in = self._compute_key("B", block_size)
key_in = self._compute_key(
- 'D', self._cipher_info[self.remote_cipher]['key-size']
+ "D", self._cipher_info[self.remote_cipher]["key-size"]
)
engine = self._get_cipher(
self.remote_cipher, key_in, IV_in, self._DECRYPT
)
- mac_size = self._mac_info[self.remote_mac]['size']
- mac_engine = self._mac_info[self.remote_mac]['class']
+ mac_size = self._mac_info[self.remote_mac]["size"]
+ mac_engine = self._mac_info[self.remote_mac]["class"]
# initial mac keys are done in the hash's natural size (not the
# potentially truncated transmission size)
if self.server_mode:
- mac_key = self._compute_key('E', mac_engine().digest_size)
+ mac_key = self._compute_key("E", mac_engine().digest_size)
else:
- mac_key = self._compute_key('F', mac_engine().digest_size)
+ mac_key = self._compute_key("F", mac_engine().digest_size)
self.packetizer.set_inbound_cipher(
engine, block_size, mac_engine, mac_size, mac_key
)
compress_in = self._compression_info[self.remote_compression][1]
- if (
- compress_in is not None and
- (
- self.remote_compression != 'zlib@openssh.com' or
- self.authenticated
- )
+ if compress_in is not None and (
+ self.remote_compression != "zlib@openssh.com" or self.authenticated
):
- self._log(DEBUG, 'Switching on inbound compression ...')
+ self._log(DEBUG, "Switching on inbound compression ...")
self.packetizer.set_inbound_compressor(compress_in())
def _activate_outbound(self):
@@ -2334,37 +2455,37 @@ class Transport(threading.Thread, ClosingContextManager):
m = Message()
m.add_byte(cMSG_NEWKEYS)
self._send_message(m)
- block_size = self._cipher_info[self.local_cipher]['block-size']
+ block_size = self._cipher_info[self.local_cipher]["block-size"]
if self.server_mode:
- IV_out = self._compute_key('B', block_size)
+ IV_out = self._compute_key("B", block_size)
key_out = self._compute_key(
- 'D', self._cipher_info[self.local_cipher]['key-size'])
+ "D", self._cipher_info[self.local_cipher]["key-size"]
+ )
else:
- IV_out = self._compute_key('A', block_size)
+ IV_out = self._compute_key("A", block_size)
key_out = self._compute_key(
- 'C', self._cipher_info[self.local_cipher]['key-size'])
+ "C", self._cipher_info[self.local_cipher]["key-size"]
+ )
engine = self._get_cipher(
- self.local_cipher, key_out, IV_out, self._ENCRYPT)
- mac_size = self._mac_info[self.local_mac]['size']
- mac_engine = self._mac_info[self.local_mac]['class']
+ self.local_cipher, key_out, IV_out, self._ENCRYPT
+ )
+ mac_size = self._mac_info[self.local_mac]["size"]
+ mac_engine = self._mac_info[self.local_mac]["class"]
# initial mac keys are done in the hash's natural size (not the
# potentially truncated transmission size)
if self.server_mode:
- mac_key = self._compute_key('F', mac_engine().digest_size)
+ mac_key = self._compute_key("F", mac_engine().digest_size)
else:
- mac_key = self._compute_key('E', mac_engine().digest_size)
- sdctr = self.local_cipher.endswith('-ctr')
+ mac_key = self._compute_key("E", mac_engine().digest_size)
+ sdctr = self.local_cipher.endswith("-ctr")
self.packetizer.set_outbound_cipher(
- engine, block_size, mac_engine, mac_size, mac_key, sdctr)
+ engine, block_size, mac_engine, mac_size, mac_key, sdctr
+ )
compress_out = self._compression_info[self.local_compression][0]
- if (
- compress_out is not None and
- (
- self.local_compression != 'zlib@openssh.com' or
- self.authenticated
- )
+ if compress_out is not None and (
+ self.local_compression != "zlib@openssh.com" or self.authenticated
):
- self._log(DEBUG, 'Switching on outbound compression ...')
+ self._log(DEBUG, "Switching on outbound compression ...")
self.packetizer.set_outbound_compressor(compress_out())
if not self.packetizer.need_rekey():
self.in_kex = False
@@ -2374,17 +2495,17 @@ class Transport(threading.Thread, ClosingContextManager):
def _auth_trigger(self):
self.authenticated = True
# delayed initiation of compression
- if self.local_compression == 'zlib@openssh.com':
+ if self.local_compression == "zlib@openssh.com":
compress_out = self._compression_info[self.local_compression][0]
- self._log(DEBUG, 'Switching on outbound compression ...')
+ self._log(DEBUG, "Switching on outbound compression ...")
self.packetizer.set_outbound_compressor(compress_out())
- if self.remote_compression == 'zlib@openssh.com':
+ if self.remote_compression == "zlib@openssh.com":
compress_in = self._compression_info[self.remote_compression][1]
- self._log(DEBUG, 'Switching on inbound compression ...')
+ self._log(DEBUG, "Switching on inbound compression ...")
self.packetizer.set_inbound_compressor(compress_in())
def _parse_newkeys(self, m):
- self._log(DEBUG, 'Switch to new keys ...')
+ self._log(DEBUG, "Switch to new keys ...")
self._activate_inbound()
# can also free a bunch of stuff here
self.local_kex_init = self.remote_kex_init = None
@@ -2412,7 +2533,7 @@ class Transport(threading.Thread, ClosingContextManager):
def _parse_disconnect(self, m):
code = m.get_int()
desc = m.get_text()
- self._log(INFO, 'Disconnect (code {:d}): {}'.format(code, desc))
+ self._log(INFO, "Disconnect (code {:d}): {}".format(code, desc))
def _parse_global_request(self, m):
kind = m.get_text()
@@ -2421,16 +2542,16 @@ class Transport(threading.Thread, ClosingContextManager):
if not self.server_mode:
self._log(
DEBUG,
- 'Rejecting "{}" global request from server.'.format(kind)
+ 'Rejecting "{}" global request from server.'.format(kind),
)
ok = False
- elif kind == 'tcpip-forward':
+ elif kind == "tcpip-forward":
address = m.get_text()
port = m.get_int()
ok = self.server_object.check_port_forward_request(address, port)
if ok:
ok = (ok,)
- elif kind == 'cancel-tcpip-forward':
+ elif kind == "cancel-tcpip-forward":
address = m.get_text()
port = m.get_int()
self.server_object.cancel_port_forward_request(address, port)
@@ -2451,13 +2572,13 @@ class Transport(threading.Thread, ClosingContextManager):
self._send_message(msg)
def _parse_request_success(self, m):
- self._log(DEBUG, 'Global request successful.')
+ self._log(DEBUG, "Global request successful.")
self.global_response = m
if self.completion_event is not None:
self.completion_event.set()
def _parse_request_failure(self, m):
- self._log(DEBUG, 'Global request denied.')
+ self._log(DEBUG, "Global request denied.")
self.global_response = None
if self.completion_event is not None:
self.completion_event.set()
@@ -2469,13 +2590,14 @@ class Transport(threading.Thread, ClosingContextManager):
server_max_packet_size = m.get_int()
chan = self._channels.get(chanid)
if chan is None:
- self._log(WARNING, 'Success for unrequested channel! [??]')
+ self._log(WARNING, "Success for unrequested channel! [??]")
return
self.lock.acquire()
try:
chan._set_remote_channel(
- server_chanid, server_window_size, server_max_packet_size)
- self._log(DEBUG, 'Secsh channel {:d} opened.'.format(chanid))
+ server_chanid, server_window_size, server_max_packet_size
+ )
+ self._log(DEBUG, "Secsh channel {:d} opened.".format(chanid))
if chanid in self.channel_events:
self.channel_events[chanid].set()
del self.channel_events[chanid]
@@ -2488,12 +2610,12 @@ class Transport(threading.Thread, ClosingContextManager):
reason = m.get_int()
reason_str = m.get_text()
m.get_text() # ignored language
- reason_text = CONNECTION_FAILED_CODE.get(reason, '(unknown code)')
+ reason_text = CONNECTION_FAILED_CODE.get(reason, "(unknown code)")
self._log(
ERROR,
- 'Secsh channel {:d} open FAILED: {}: {}'.format(
- chanid, reason_str, reason_text,
- )
+ "Secsh channel {:d} open FAILED: {}: {}".format(
+ chanid, reason_str, reason_text
+ ),
)
self.lock.acquire()
try:
@@ -2514,39 +2636,39 @@ class Transport(threading.Thread, ClosingContextManager):
max_packet_size = m.get_int()
reject = False
if (
- kind == 'auth-agent@openssh.com' and
- self._forward_agent_handler is not None
+ kind == "auth-agent@openssh.com"
+ and self._forward_agent_handler is not None
):
- self._log(DEBUG, 'Incoming forward agent connection')
+ self._log(DEBUG, "Incoming forward agent connection")
self.lock.acquire()
try:
my_chanid = self._next_channel()
finally:
self.lock.release()
- elif (kind == 'x11') and (self._x11_handler is not None):
+ elif (kind == "x11") and (self._x11_handler is not None):
origin_addr = m.get_text()
origin_port = m.get_int()
self._log(
DEBUG,
- 'Incoming x11 connection from {}:{:d}'.format(
- origin_addr, origin_port,
- )
+ "Incoming x11 connection from {}:{:d}".format(
+ origin_addr, origin_port
+ ),
)
self.lock.acquire()
try:
my_chanid = self._next_channel()
finally:
self.lock.release()
- elif (kind == 'forwarded-tcpip') and (self._tcp_handler is not None):
+ elif (kind == "forwarded-tcpip") and (self._tcp_handler is not None):
server_addr = m.get_text()
server_port = m.get_int()
origin_addr = m.get_text()
origin_port = m.get_int()
self._log(
DEBUG,
- 'Incoming tcp forwarded connection from {}:{:d}'.format(
- origin_addr, origin_port,
- )
+ "Incoming tcp forwarded connection from {}:{:d}".format(
+ origin_addr, origin_port
+ ),
)
self.lock.acquire()
try:
@@ -2556,7 +2678,8 @@ class Transport(threading.Thread, ClosingContextManager):
elif not self.server_mode:
self._log(
DEBUG,
- 'Rejecting "{}" channel request from server.'.format(kind))
+ 'Rejecting "{}" channel request from server.'.format(kind),
+ )
reject = True
reason = OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
else:
@@ -2565,7 +2688,7 @@ class Transport(threading.Thread, ClosingContextManager):
my_chanid = self._next_channel()
finally:
self.lock.release()
- if kind == 'direct-tcpip':
+ if kind == "direct-tcpip":
# handle direct-tcpip requests coming from the client
dest_addr = m.get_text()
dest_port = m.get_int()
@@ -2574,23 +2697,25 @@ class Transport(threading.Thread, ClosingContextManager):
reason = self.server_object.check_channel_direct_tcpip_request(
my_chanid,
(origin_addr, origin_port),
- (dest_addr, dest_port)
+ (dest_addr, dest_port),
)
else:
reason = self.server_object.check_channel_request(
- kind, my_chanid)
+ kind, my_chanid
+ )
if reason != OPEN_SUCCEEDED:
self._log(
DEBUG,
- 'Rejecting "{}" channel request from client.'.format(kind))
+ 'Rejecting "{}" channel request from client.'.format(kind),
+ )
reject = True
if reject:
msg = Message()
msg.add_byte(cMSG_CHANNEL_OPEN_FAILURE)
msg.add_int(chanid)
msg.add_int(reason)
- msg.add_string('')
- msg.add_string('en')
+ msg.add_string("")
+ msg.add_string("en")
self._send_message(msg)
return
@@ -2601,9 +2726,11 @@ class Transport(threading.Thread, ClosingContextManager):
self.channels_seen[my_chanid] = True
chan._set_transport(self)
chan._set_window(
- self.default_window_size, self.default_max_packet_size)
+ self.default_window_size, self.default_max_packet_size
+ )
chan._set_remote_channel(
- chanid, initial_window_size, max_packet_size)
+ chanid, initial_window_size, max_packet_size
+ )
finally:
self.lock.release()
m = Message()
@@ -2613,19 +2740,17 @@ class Transport(threading.Thread, ClosingContextManager):
m.add_int(self.default_window_size)
m.add_int(self.default_max_packet_size)
self._send_message(m)
- self._log(DEBUG,
- 'Secsh channel {:d} ({}) opened.'.format(my_chanid, kind)
+ self._log(
+ DEBUG, "Secsh channel {:d} ({}) opened.".format(my_chanid, kind)
)
- if kind == 'auth-agent@openssh.com':
+ if kind == "auth-agent@openssh.com":
self._forward_agent_handler(chan)
- elif kind == 'x11':
+ elif kind == "x11":
self._x11_handler(chan, (origin_addr, origin_port))
- elif kind == 'forwarded-tcpip':
+ elif kind == "forwarded-tcpip":
chan.origin_addr = (origin_addr, origin_port)
self._tcp_handler(
- chan,
- (origin_addr, origin_port),
- (server_addr, server_port)
+ chan, (origin_addr, origin_port), (server_addr, server_port)
)
else:
self._queue_incoming_channel(chan)
@@ -2634,7 +2759,7 @@ class Transport(threading.Thread, ClosingContextManager):
m.get_boolean() # always_display
msg = m.get_string()
m.get_string() # language
- self._log(DEBUG, 'Debug msg: {}'.format(util.safe_string(msg)))
+ self._log(DEBUG, "Debug msg: {}".format(util.safe_string(msg)))
def _get_subsystem_handler(self, name):
try:
@@ -2668,7 +2793,7 @@ class Transport(threading.Thread, ClosingContextManager):
}
-class SecurityOptions (object):
+class SecurityOptions(object):
"""
Simple object containing the security preferences of an ssh transport.
These are tuples of acceptable ciphers, digests, key types, and key
@@ -2680,7 +2805,7 @@ class SecurityOptions (object):
``ValueError`` will be raised. If you try to assign something besides a
tuple to one of the fields, ``TypeError`` will be raised.
"""
- __slots__ = '_transport'
+ __slots__ = "_transport"
def __init__(self, transport):
self._transport = transport
@@ -2689,17 +2814,17 @@ class SecurityOptions (object):
"""
Returns a string representation of this object, for debugging.
"""
- return '<paramiko.SecurityOptions for {!r}>'.format(self._transport)
+ return "<paramiko.SecurityOptions for {!r}>".format(self._transport)
def _set(self, name, orig, x):
if type(x) is list:
x = tuple(x)
if type(x) is not tuple:
- raise TypeError('expected tuple or list')
+ raise TypeError("expected tuple or list")
possible = list(getattr(self._transport, orig).keys())
forbidden = [n for n in x if n not in possible]
if len(forbidden) > 0:
- raise ValueError('unknown cipher')
+ raise ValueError("unknown cipher")
setattr(self._transport, name, x)
@property
@@ -2709,7 +2834,7 @@ class SecurityOptions (object):
@ciphers.setter
def ciphers(self, x):
- self._set('_preferred_ciphers', '_cipher_info', x)
+ self._set("_preferred_ciphers", "_cipher_info", x)
@property
def digests(self):
@@ -2718,7 +2843,7 @@ class SecurityOptions (object):
@digests.setter
def digests(self, x):
- self._set('_preferred_macs', '_mac_info', x)
+ self._set("_preferred_macs", "_mac_info", x)
@property
def key_types(self):
@@ -2727,8 +2852,7 @@ class SecurityOptions (object):
@key_types.setter
def key_types(self, x):
- self._set('_preferred_keys', '_key_info', x)
-
+ self._set("_preferred_keys", "_key_info", x)
@property
def kex(self):
@@ -2737,7 +2861,7 @@ class SecurityOptions (object):
@kex.setter
def kex(self, x):
- self._set('_preferred_kex', '_kex_info', x)
+ self._set("_preferred_kex", "_kex_info", x)
@property
def compression(self):
@@ -2746,10 +2870,11 @@ class SecurityOptions (object):
@compression.setter
def compression(self, x):
- self._set('_preferred_compression', '_compression_info', x)
+ self._set("_preferred_compression", "_compression_info", x)
+
+class ChannelMap(object):
-class ChannelMap (object):
def __init__(self):
# (id -> Channel)
self._map = weakref.WeakValueDictionary()