/code/python/paramiko-1.6.1/paramiko/transport.py
http://sb-scripts.googlecode.com/ · Python · 1584 lines · 1336 code · 44 blank · 204 comment · 129 complexity · d53c3b9ea0b2e113eea621783d530946 MD5 · raw file
- # Copyright (C) 2003-2006 Robey Pointer <robey@lag.net>
- #
- # This file is part of paramiko.
- #
- # Paramiko is free software; you can redistribute it and/or modify it under the
- # terms of the GNU Lesser General Public License as published by the Free
- # Software Foundation; either version 2.1 of the License, or (at your option)
- # any later version.
- #
- # Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
- # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
- # details.
- #
- # You should have received a copy of the GNU Lesser General Public License
- # along with Paramiko; if not, write to the Free Software Foundation, Inc.,
- # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
- """
- L{Transport} handles the core SSH2 protocol.
- """
- import os
- import socket
- import string
- import struct
- import sys
- import threading
- import time
- import weakref
- from paramiko import util
- from paramiko.auth_handler import AuthHandler
- from paramiko.channel import Channel
- from paramiko.common import *
- 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.message import Message
- from paramiko.packet import Packetizer, NeedRekeyException
- from paramiko.primes import ModulusPack
- from paramiko.rsakey import RSAKey
- from paramiko.server import ServerInterface
- from paramiko.sftp_client import SFTPClient
- from paramiko.ssh_exception import SSHException, BadAuthenticationType, ChannelException
- # these come from PyCrypt
- # http://www.amk.ca/python/writing/pycrypt/
- # i believe this on the standards track.
- # PyCrypt compiled for Win32 can be downloaded from the HashTar homepage:
- # http://nitace.bsd.uchicago.edu:8080/hashtar
- from Crypto.Cipher import Blowfish, AES, DES3
- from Crypto.Hash import SHA, MD5
- # for thread cleanup
- _active_threads = []
- def _join_lingering_threads():
- for thr in _active_threads:
- thr.stop_thread()
- import atexit
- atexit.register(_join_lingering_threads)
- class SecurityOptions (object):
- """
- Simple object containing the security preferences of an ssh transport.
- These are tuples of acceptable ciphers, digests, key types, and key
- exchange algorithms, listed in order of preference.
- Changing the contents and/or order of these fields affects the underlying
- L{Transport} (but only if you change them before starting the session).
- If you try to add an algorithm that paramiko doesn't recognize,
- C{ValueError} will be raised. If you try to assign something besides a
- tuple to one of the fields, C{TypeError} will be raised.
- """
- __slots__ = [ 'ciphers', 'digests', 'key_types', 'kex', 'compression', '_transport' ]
- def __init__(self, transport):
- self._transport = transport
- def __repr__(self):
- """
- Returns a string representation of this object, for debugging.
- @rtype: str
- """
- 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)
- if type(x) is not tuple:
- raise TypeError('expected tuple or list')
- possible = getattr(self._transport, orig).keys()
- forbidden = filter(lambda n: n not in possible, x)
- if len(forbidden) > 0:
- raise ValueError('unknown cipher')
- setattr(self._transport, name, x)
- def _set_ciphers(self, x):
- self._set('_preferred_ciphers', '_cipher_info', x)
- def _set_digests(self, x):
- self._set('_preferred_macs', '_mac_info', x)
- def _set_key_types(self, x):
- self._set('_preferred_keys', '_key_info', x)
- def _set_kex(self, x):
- self._set('_preferred_kex', '_kex_info', x)
-
- def _set_compression(self, x):
- self._set('_preferred_compression', '_compression_info', 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")
- compression = property(_get_compression, _set_compression, None,
- "Compression algorithms")
- class Transport (threading.Thread):
- """
- An SSH Transport attaches to a stream (usually a socket), negotiates an
- encrypted session, authenticates, and then creates stream tunnels, called
- L{Channel}s, across the session. Multiple channels can be multiplexed
- across a single session (and often are, in the case of port forwardings).
- """
- _PROTO_ID = '2.0'
- _CLIENT_ID = 'paramiko_1.6.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_compression = ( 'none', )
-
- _cipher_info = {
- 'blowfish-cbc': { 'class': Blowfish, 'mode': Blowfish.MODE_CBC, 'block-size': 8, 'key-size': 16 },
- 'aes128-cbc': { 'class': AES, 'mode': AES.MODE_CBC, 'block-size': 16, 'key-size': 16 },
- 'aes256-cbc': { 'class': AES, 'mode': AES.MODE_CBC, 'block-size': 16, 'key-size': 32 },
- '3des-cbc': { 'class': DES3, 'mode': DES3.MODE_CBC, 'block-size': 8, 'key-size': 24 },
- }
- _mac_info = {
- 'hmac-sha1': { 'class': SHA, 'size': 20 },
- 'hmac-sha1-96': { 'class': SHA, 'size': 12 },
- 'hmac-md5': { 'class': MD5, 'size': 16 },
- 'hmac-md5-96': { 'class': MD5, 'size': 12 },
- }
- _key_info = {
- 'ssh-rsa': RSAKey,
- 'ssh-dss': DSSKey,
- }
- _kex_info = {
- 'diffie-hellman-group1-sha1': KexGroup1,
- 'diffie-hellman-group-exchange-sha1': KexGex,
- }
-
- _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 ),
- }
- _modulus_pack = None
- def __init__(self, sock):
- """
- Create a new SSH session over an existing socket, or socket-like
- object. This only creates the Transport object; it doesn't begin the
- SSH session yet. Use L{connect} or L{start_client} to begin a client
- session, or L{start_server} to begin a server session.
- If the object is not actually a socket, it must have the following
- methods:
- - C{send(str)}: Writes from 1 to C{len(str)} bytes, and
- returns an int representing the number of bytes written. Returns
- 0 or raises C{EOFError} if the stream has been closed.
- - C{recv(int)}: Reads from 1 to C{int} bytes and returns them as a
- string. Returns 0 or raises C{EOFError} if the stream has been
- closed.
- - C{close()}: Closes the socket.
- - C{settimeout(n)}: Sets a (float) timeout on I/O operations.
- For ease of use, you may also pass in an address (as a tuple) or a host
- string as the C{sock} argument. (A host string is a hostname with an
- optional port (separated by C{":"}) which will be converted into a
- tuple of C{(hostname, port)}.) A socket will be connected to this
- address and used for communication. Exceptions from the C{socket} call
- may be thrown in this case.
- @param sock: a socket or socket-like object to create the session over.
- @type sock: socket
- """
- if type(sock) is str:
- # convert "host:port" into (host, port)
- hl = sock.split(':', 1)
- if len(hl) == 1:
- sock = (hl[0], 22)
- else:
- sock = (hl[0], int(hl[1]))
- if type(sock) is tuple:
- # connect to the given (host, port)
- hostname, port = sock
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- sock.connect((hostname, port))
- # okay, normal socket-ish flow here...
- threading.Thread.__init__(self)
- self.randpool = randpool
- self.sock = sock
- # Python < 2.3 doesn't have the settimeout method - RogerB
- try:
- # we set the timeout so we can check self.active periodically to
- # see if we should bail. socket.timeout exception is never
- # propagated.
- self.sock.settimeout(0.1)
- except AttributeError:
- pass
- # 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_kex_init = self.remote_kex_init = None
- self.local_mac = self.remote_mac = None
- self.local_compression = self.remote_compression = None
- self.session_id = None
- self.host_key_type = None
- self.host_key = None
-
- # state used during negotiation
- self.kex_engine = None
- self.H = None
- self.K = None
- self.active = False
- self.initial_kex_done = False
- self.in_kex = False
- self.authenticated = False
- self.expected_packet = 0
- self.lock = threading.Lock() # synchronization (always higher level than write_lock)
- # tracking open channels
- self.channels = weakref.WeakValueDictionary() # (id -> Channel)
- self.channel_events = { } # (id -> Event)
- self.channels_seen = { } # (id -> True)
- self.channel_counter = 1
- self.window_size = 65536
- self.max_packet_size = 34816
- self.saved_exception = None
- self.clear_to_send = threading.Event()
- self.clear_to_send_lock = threading.Lock()
- self.log_name = 'paramiko.transport'
- self.logger = util.get_logger(self.log_name)
- self.packetizer.set_log(self.logger)
- self.auth_handler = None
- 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
- # server mode:
- self.server_mode = False
- self.server_object = None
- self.server_key_dict = { }
- self.server_accepts = [ ]
- self.server_accept_cv = threading.Condition(self.lock)
- self.subsystem_table = { }
- def __repr__(self):
- """
- Returns a string representation of this object, for debugging.
- @rtype: str
- """
- out = '<paramiko.Transport at %s' % hex(long(id(self)) & 0xffffffffL)
- if not self.active:
- out += ' (unconnected)'
- else:
- if self.local_cipher != '':
- out += ' (cipher %s, %d bits)' % (self.local_cipher,
- self._cipher_info[self.local_cipher]['key-size'] * 8)
- if self.is_authenticated():
- if len(self.channels) == 1:
- out += ' (active; 1 open channel)'
- else:
- out += ' (active; %d open channels)' % len(self.channels)
- elif self.initial_kex_done:
- out += ' (connected; awaiting auth)'
- else:
- out += ' (connecting)'
- out += '>'
- return out
-
- def atfork(self):
- """
- Terminate this Transport without closing the session. On posix
- systems, if a Transport is open during process forking, both parent
- and child will share the underlying socket, but only one process can
- use the connection (without corrupting the session). Use this method
- to clean up a Transport object without disrupting the other process.
-
- @since: 1.5.3
- """
- self.sock.close()
- self.close()
- 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
- creating a new L{Transport}. A separate thread is created for protocol
- negotiation.
-
- If an event is passed in, this method returns immediately. When
- negotiation is done (successful or not), the given C{Event} will
- be triggered. On failure, L{is_active} will return C{False}.
-
- (Since 1.4) If C{event} is C{None}, this method will not return until
- negotation is done. On success, the method returns normally.
- Otherwise an SSHException is raised.
- After a successful negotiation, you will usually want to authenticate,
- calling L{auth_password <Transport.auth_password>} or
- L{auth_publickey <Transport.auth_publickey>}.
- @note: L{connect} is a simpler method for connecting as a client.
-
- @note: After calling this method (or L{start_server} or L{connect}),
- you should no longer directly read from or write to the original
- socket object.
- @param event: an event to trigger when negotiation is complete
- (optional)
- @type event: threading.Event
- @raise SSHException: if negotiation fails (and no C{event} was passed
- in)
- """
- self.active = True
- if event is not None:
- # async, return immediately and let the app poll for completion
- self.completion_event = event
- self.start()
- return
- # synchronous, wait for a result
- self.completion_event = event = threading.Event()
- self.start()
- while True:
- event.wait(0.1)
- if not self.active:
- e = self.get_exception()
- if e is not None:
- raise e
- raise SSHException('Negotiation failed.')
- if event.isSet():
- break
- def start_server(self, event=None, server=None):
- """
- Negotiate a new SSH2 session as a server. This is the first step after
- creating a new L{Transport} and setting up your server host key(s). A
- separate thread is created for protocol negotiation.
-
- If an event is passed in, this method returns immediately. When
- negotiation is done (successful or not), the given C{Event} will
- be triggered. On failure, L{is_active} will return C{False}.
-
- (Since 1.4) If C{event} is C{None}, this method will not return until
- negotation is done. On success, the method returns normally.
- Otherwise an SSHException is raised.
- After a successful negotiation, the client will need to authenticate.
- Override the methods
- L{get_allowed_auths <ServerInterface.get_allowed_auths>},
- L{check_auth_none <ServerInterface.check_auth_none>},
- L{check_auth_password <ServerInterface.check_auth_password>}, and
- L{check_auth_publickey <ServerInterface.check_auth_publickey>} in the
- given C{server} object to control the authentication process.
- After a successful authentication, the client should request to open
- a channel. Override
- L{check_channel_request <ServerInterface.check_channel_request>} in the
- given C{server} object to allow channels to be opened.
- @note: After calling this method (or L{start_client} or L{connect}),
- you should no longer directly read from or write to the original
- socket object.
- @param event: an event to trigger when negotiation is complete.
- @type event: threading.Event
- @param server: an object used to perform authentication and create
- L{Channel}s.
- @type server: L{server.ServerInterface}
- @raise SSHException: if negotiation fails (and no C{event} was passed
- in)
- """
- if server is None:
- server = ServerInterface()
- self.server_mode = True
- self.server_object = server
- self.active = True
- if event is not None:
- # async, return immediately and let the app poll for completion
- self.completion_event = event
- self.start()
- return
- # synchronous, wait for a result
- self.completion_event = event = threading.Event()
- self.start()
- while True:
- event.wait(0.1)
- if not self.active:
- e = self.get_exception()
- if e is not None:
- raise e
- raise SSHException('Negotiation failed.')
- if event.isSet():
- break
- def add_server_key(self, key):
- """
- Add a host key to the list of keys used for server mode. When behaving
- as a server, the host key is used to sign certain packets during the
- SSH2 negotiation, so that the client can trust that we are who we say
- we are. Because this is used for signing, the key must contain private
- key info, not just the public half. Only one key of each type (RSA or
- DSS) is kept.
-
- @param key: the host key to add, usually an L{RSAKey <rsakey.RSAKey>} or
- L{DSSKey <dsskey.DSSKey>}.
- @type key: L{PKey <pkey.PKey>}
- """
- self.server_key_dict[key.get_name()] = key
- def get_server_key(self):
- """
- Return the active host key, in server mode. After negotiating with the
- client, this method will return the negotiated host key. If only one
- type of host key was set with L{add_server_key}, that's the only key
- that will ever be returned. But in cases where you have set more than
- one type of host key (for example, an RSA key and a DSS key), the key
- type will be negotiated by the client, and this method will return the
- key of the type agreed on. If the host key has not been negotiated
- yet, C{None} is returned. In client mode, the behavior is undefined.
- @return: host key of the type negotiated by the client, or C{None}.
- @rtype: L{PKey <pkey.PKey>}
- """
- try:
- return self.server_key_dict[self.host_key_type]
- except KeyError:
- return None
- def load_server_moduli(filename=None):
- """
- I{(optional)}
- Load a file of prime moduli for use in doing group-exchange key
- negotiation in server mode. It's a rather obscure option and can be
- safely ignored.
- In server mode, the remote client may request "group-exchange" key
- negotiation, which asks the server to send a random prime number that
- fits certain criteria. These primes are pretty difficult to compute,
- so they can't be generated on demand. But many systems contain a file
- of suitable primes (usually named something like C{/etc/ssh/moduli}).
- If you call C{load_server_moduli} and it returns C{True}, then this
- file of primes has been loaded and we will support "group-exchange" in
- server mode. Otherwise server mode will just claim that it doesn't
- support that method of key negotiation.
- @param filename: optional path to the moduli file, if you happen to
- know that it's not in a standard location.
- @type filename: str
- @return: True if a moduli file was successfully loaded; False
- otherwise.
- @rtype: bool
-
- @note: This has no effect when used in client mode.
- """
- Transport._modulus_pack = ModulusPack(randpool)
- # places to look for the openssh "moduli" file
- file_list = [ '/etc/ssh/moduli', '/usr/local/etc/moduli' ]
- if filename is not None:
- file_list.insert(0, filename)
- for fn in file_list:
- try:
- Transport._modulus_pack.read_file(fn)
- return True
- except IOError:
- pass
- # none succeeded
- Transport._modulus_pack = None
- return False
- load_server_moduli = staticmethod(load_server_moduli)
- def close(self):
- """
- Close this session, and any open channels that are tied to it.
- """
- self.active = False
- self.packetizer.close()
- for chan in self.channels.values():
- chan._unlink()
- def get_remote_server_key(self):
- """
- Return the host key of the server (in client mode).
- @note: Previously this call returned a tuple of (key type, key string).
- You can get the same effect by calling
- L{PKey.get_name <pkey.PKey.get_name>} for the key type, and
- C{str(key)} for the key string.
- @raise SSHException: if no session is currently active.
-
- @return: public key of the remote server
- @rtype: L{PKey <pkey.PKey>}
- """
- if (not self.active) or (not self.initial_kex_done):
- raise SSHException('No existing session')
- return self.host_key
- def is_active(self):
- """
- Return true if this session is active (open).
- @return: True if the session is still active (open); False if the
- session is closed
- @rtype: bool
- """
- return self.active
- def open_session(self):
- """
- Request a new channel to the server, of type C{"session"}. This
- is just an alias for C{open_channel('session')}.
- @return: a new L{Channel}
- @rtype: L{Channel}
-
- @raise SSHException: if the request is rejected or the session ends
- prematurely
- """
- return self.open_channel('session')
- def open_channel(self, kind, dest_addr=None, src_addr=None):
- """
- Request a new channel to the server. L{Channel}s are socket-like
- objects used for the actual transfer of data across the session.
- You may only request a channel after negotiating encryption (using
- L{connect} or L{start_client}) and authenticating.
- @param kind: the kind of channel requested (usually C{"session"},
- C{"forwarded-tcpip"} or C{"direct-tcpip"})
- @type kind: str
- @param dest_addr: the destination address of this port forwarding,
- if C{kind} is C{"forwarded-tcpip"} or C{"direct-tcpip"} (ignored
- for other channel types)
- @type dest_addr: (str, int)
- @param src_addr: the source address of this port forwarding, if
- C{kind} is C{"forwarded-tcpip"} or C{"direct-tcpip"}
- @type src_addr: (str, int)
- @return: a new L{Channel} on success
- @rtype: L{Channel}
- @raise SSHException: if the request is rejected or the session ends
- prematurely
- """
- chan = None
- if not self.active:
- # don't bother trying to allocate a channel
- return None
- self.lock.acquire()
- try:
- chanid = self.channel_counter
- while self.channels.has_key(chanid):
- self.channel_counter = (self.channel_counter + 1) & 0xffffff
- chanid = self.channel_counter
- self.channel_counter = (self.channel_counter + 1) & 0xffffff
- m = Message()
- m.add_byte(chr(MSG_CHANNEL_OPEN))
- m.add_string(kind)
- m.add_int(chanid)
- m.add_int(self.window_size)
- m.add_int(self.max_packet_size)
- 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])
- self.channels[chanid] = chan = Channel(chanid)
- 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)
- finally:
- self.lock.release()
- self._send_user_message(m)
- while True:
- event.wait(0.1);
- if not self.active:
- e = self.get_exception()
- if e is None:
- e = SSHException('Unable to open channel.')
- raise e
- if event.isSet():
- break
- self.lock.acquire()
- try:
- if self.channels.has_key(chanid):
- return chan
- finally:
- self.lock.release()
- e = self.get_exception()
- if e is None:
- e = SSHException('Unable to open channel.')
- raise e
- def open_sftp_client(self):
- """
- Create an SFTP client channel from an open transport. On success,
- an SFTP session will be opened with the remote host, and a new
- SFTPClient object will be returned.
- @return: a new L{SFTPClient} object, referring to an sftp session
- (channel) across this transport
- @rtype: L{SFTPClient}
- """
- return SFTPClient.from_transport(self)
- def send_ignore(self, bytes=None):
- """
- Send a junk packet across the encrypted link. This is sometimes used
- to add "noise" to a connection to confuse would-be attackers. It can
- also be used as a keep-alive for long lived connections traversing
- firewalls.
- @param bytes: the number of random bytes to send in the payload of the
- ignored packet -- defaults to a random number from 10 to 41.
- @type bytes: int
- """
- m = Message()
- m.add_byte(chr(MSG_IGNORE))
- randpool.stir()
- if bytes is None:
- bytes = (ord(randpool.get_bytes(1)) % 32) + 10
- m.add_bytes(randpool.get_bytes(bytes))
- self._send_user_message(m)
- def renegotiate_keys(self):
- """
- Force this session to switch to new keys. Normally this is done
- automatically after the session hits a certain number of packets or
- bytes sent or received, but this method gives you the option of forcing
- new keys whenever you want. Negotiating new keys causes a pause in
- traffic both ways as the two sides swap keys and do computations. This
- method returns when the session has switched to new keys.
- @raise SSHException: if the key renegotiation failed (which causes the
- session to end)
- """
- self.completion_event = threading.Event()
- self._send_kex_init()
- while True:
- self.completion_event.wait(0.1)
- if not self.active:
- e = self.get_exception()
- if e is not None:
- raise e
- raise SSHException('Negotiation failed.')
- if self.completion_event.isSet():
- break
- return
- def set_keepalive(self, interval):
- """
- Turn on/off keepalive packets (default is off). If this is set, after
- C{interval} seconds without sending any data over the connection, a
- "keepalive" packet will be sent (and ignored by the remote host). This
- can be useful to keep connections alive over a NAT, for example.
-
- @param interval: seconds to wait before sending a keepalive packet (or
- 0 to disable keepalives).
- @type interval: int
- """
- self.packetizer.set_keepalive(interval,
- lambda x=weakref.proxy(self): x.global_request('keepalive@lag.net', wait=False))
- def global_request(self, kind, data=None, wait=True):
- """
- Make a global request to the remote host. These are normally
- extensions to the SSH2 protocol.
- @param kind: name of the request.
- @type kind: str
- @param data: an optional tuple containing additional data to attach
- to the request.
- @type data: tuple
- @param wait: C{True} if this method should not return until a response
- is received; C{False} otherwise.
- @type wait: bool
- @return: a L{Message} containing possible additional data if the
- request was successful (or an empty L{Message} if C{wait} was
- C{False}); C{None} if the request was denied.
- @rtype: L{Message}
- """
- if wait:
- self.completion_event = threading.Event()
- m = Message()
- m.add_byte(chr(MSG_GLOBAL_REQUEST))
- m.add_string(kind)
- m.add_boolean(wait)
- if data is not None:
- m.add(*data)
- self._log(DEBUG, 'Sending global request "%s"' % kind)
- self._send_user_message(m)
- if not wait:
- return None
- while True:
- self.completion_event.wait(0.1)
- if not self.active:
- return None
- if self.completion_event.isSet():
- break
- return self.global_response
- def accept(self, timeout=None):
- """
- Return the next channel opened by the client over this transport, in
- server mode. If no channel is opened before the given timeout, C{None}
- is returned.
-
- @param timeout: seconds to wait for a channel, or C{None} to wait
- forever
- @type timeout: int
- @return: a new Channel opened by the client
- @rtype: L{Channel}
- """
- self.lock.acquire()
- try:
- if len(self.server_accepts) > 0:
- chan = self.server_accepts.pop(0)
- else:
- self.server_accept_cv.wait(timeout)
- if len(self.server_accepts) > 0:
- chan = self.server_accepts.pop(0)
- else:
- # timeout
- chan = None
- finally:
- self.lock.release()
- return chan
- def connect(self, hostkey=None, username='', password=None, pkey=None):
- """
- Negotiate an SSH2 session, and optionally verify the server's host key
- and authenticate using a password or private key. This is a shortcut
- for L{start_client}, L{get_remote_server_key}, and
- L{Transport.auth_password} or L{Transport.auth_publickey}. Use those
- methods if you want more control.
- You can use this method immediately after creating a Transport to
- negotiate encryption with a server. If it fails, an exception will be
- thrown. On success, the method will return cleanly, and an encrypted
- session exists. You may immediately call L{open_channel} or
- L{open_session} to get a L{Channel} object, which is used for data
- transfer.
- @note: If you fail to supply a password or private key, this method may
- succeed, but a subsequent L{open_channel} or L{open_session} call may
- fail because you haven't authenticated yet.
- @param hostkey: the host key expected from the server, or C{None} if
- you don't want to do host key verification.
- @type hostkey: L{PKey<pkey.PKey>}
- @param username: the username to authenticate as.
- @type username: str
- @param password: a password to use for authentication, if you want to
- use password authentication; otherwise C{None}.
- @type password: str
- @param pkey: a private key to use for authentication, if you want to
- use private key authentication; otherwise C{None}.
- @type pkey: L{PKey<pkey.PKey>}
-
- @raise SSHException: if the SSH2 negotiation fails, the host key
- supplied by the server is incorrect, or authentication fails.
- """
- if hostkey is not None:
- self._preferred_keys = [ hostkey.get_name() ]
- self.start_client()
- # check host key if we were given one
- if (hostkey is not None):
- key = self.get_remote_server_key()
- if (key.get_name() != hostkey.get_name()) or (str(key) != str(hostkey)):
- self._log(DEBUG, 'Bad host key from server')
- self._log(DEBUG, 'Expected: %s: %s' % (hostkey.get_name(), repr(str(hostkey))))
- self._log(DEBUG, 'Got : %s: %s' % (key.get_name(), repr(str(key))))
- 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:
- self._log(DEBUG, 'Attempting public-key auth...')
- self.auth_publickey(username, pkey)
- return
-
- def get_exception(self):
- """
- Return any exception that happened during the last server request.
- This can be used to fetch more specific error information after using
- calls like L{start_client}. The exception (if any) is cleared after
- this call.
-
- @return: an exception, or C{None} if there is no stored exception.
- @rtype: Exception
-
- @since: 1.1
- """
- self.lock.acquire()
- try:
- e = self.saved_exception
- self.saved_exception = None
- return e
- finally:
- self.lock.release()
- def set_subsystem_handler(self, name, handler, *larg, **kwarg):
- """
- Set the handler class for a subsystem in server mode. If a request
- for this subsystem is made on an open ssh channel later, this handler
- will be constructed and called -- see L{SubsystemHandler} for more
- detailed documentation.
- Any extra parameters (including keyword arguments) are saved and
- passed to the L{SubsystemHandler} constructor later.
- @param name: name of the subsystem.
- @type name: str
- @param handler: subclass of L{SubsystemHandler} that handles this
- subsystem.
- @type handler: class
- """
- try:
- self.lock.acquire()
- self.subsystem_table[name] = (handler, larg, kwarg)
- finally:
- self.lock.release()
-
- def is_authenticated(self):
- """
- Return true if this session is active and authenticated.
- @return: True if the session is still open and has been authenticated
- successfully; False if authentication failed and/or the session is
- closed.
- @rtype: bool
- """
- return self.active and (self.auth_handler is not None) and self.auth_handler.is_authenticated()
-
- def get_username(self):
- """
- Return the username this connection is authenticated for. If the
- session is not authenticated (or authentication failed), this method
- returns C{None}.
- @return: username that was authenticated, or C{None}.
- @rtype: string
- """
- if not self.active or (self.auth_handler is None):
- return None
- return self.auth_handler.get_username()
- def auth_none(self, username):
- """
- Try to authenticate to the server using no authentication at all.
- This will almost always fail. It may be useful for determining the
- list of authentication types supported by the server, by catching the
- L{BadAuthenticationType} exception raised.
-
- @param username: the username to authenticate as
- @type username: string
- @return: list of auth types permissible for the next stage of
- authentication (normally empty)
- @rtype: list
- @raise BadAuthenticationType: if "none" authentication isn't allowed
- by the server for this user
- @raise SSHException: if the authentication failed due to a network
- error
-
- @since: 1.5
- """
- if (not self.active) or (not self.initial_kex_done):
- raise SSHException('No existing session')
- my_event = threading.Event()
- self.auth_handler = AuthHandler(self)
- self.auth_handler.auth_none(username, my_event)
- return self.auth_handler.wait_for_response(my_event)
- def auth_password(self, username, password, event=None, fallback=True):
- """
- Authenticate to the server using a password. The username and password
- are sent over an encrypted link.
-
- If an C{event} is passed in, this method will return immediately, and
- the event will be triggered once authentication succeeds or fails. On
- success, L{is_authenticated} will return C{True}. On failure, you may
- use L{get_exception} to get more detailed error information.
- Since 1.1, if no event is passed, this method will block until the
- authentication succeeds or fails. On failure, an exception is raised.
- Otherwise, the method simply returns.
-
- Since 1.5, if no event is passed and C{fallback} is C{True} (the
- default), if the server doesn't support plain password authentication
- but does support so-called "keyboard-interactive" mode, an attempt
- will be made to authenticate using this interactive mode. If it fails,
- the normal exception will be thrown as if the attempt had never been
- made. This is useful for some recent Gentoo and Debian distributions,
- which turn off plain password authentication in a misguided belief
- that interactive authentication is "more secure". (It's not.)
-
- If the server requires multi-step authentication (which is very rare),
- this method will return a list of auth types permissible for the next
- step. Otherwise, in the normal case, an empty list is returned.
-
- @param username: the username to authenticate as
- @type username: string
- @param password: the password to authenticate with
- @type password: string
- @param event: an event to trigger when the authentication attempt is
- complete (whether it was successful or not)
- @type event: threading.Event
- @param fallback: C{True} if an attempt at an automated "interactive"
- password auth should be made if the server doesn't support normal
- password auth
- @type fallback: bool
- @return: list of auth types permissible for the next stage of
- authentication (normally empty)
- @rtype: list
-
- @raise BadAuthenticationType: if password authentication isn't
- allowed by the server for this user (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 send the password unless we're on a secure link
- raise SSHException('No existing session')
- if event is None:
- my_event = threading.Event()
- else:
- my_event = event
- self.auth_handler = AuthHandler(self)
- self.auth_handler.auth_password(username, password, my_event)
- if event is not None:
- # caller wants to wait for event themselves
- return []
- try:
- return self.auth_handler.wait_for_response(my_event)
- except BadAuthenticationType, x:
- # if password auth isn't allowed, but keyboard-interactive *is*, try to fudge it
- if not fallback or ('keyboard-interactive' not in x.allowed_types):
- raise
- try:
- def handler(title, instructions, fields):
- if len(fields) > 1:
- 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
- # to try to fake out automated scripting of the exact
- # type we're doing here. *shrug* :)
- return []
- return [ password ]
- return self.auth_interactive(username, handler)
- except SSHException, ignored:
- # attempt failed; just raise the original exception
- raise x
- return None
- def auth_publickey(self, username, key, event=None):
- """
- Authenticate to the server using a private key. The key is used to
- sign data from the server, so it must include the private part.
-
- If an C{event} is passed in, this method will return immediately, and
- the event will be triggered once authentication succeeds or fails. On
- success, L{is_authenticated} will return C{True}. On failure, you may
- use L{get_exception} to get more detailed error information.
-
- Since 1.1, if no event is passed, this method will block until the
- authentication succeeds or fails. On failure, an exception is raised.
- Otherwise, the method simply returns.
- If the server requires multi-step authentication (which is very rare),
- this method will return a list of auth types permissible for the next
- step. Otherwise, in the normal case, an empty list is returned.
- @param username: the username to authenticate as
- @type username: string
- @param key: the private key to authenticate with
- @type key: L{PKey <pkey.PKey>}
- @param event: an event to trigger when the authentication attempt is
- complete (whether it was successful or not)
- @type event: threading.Event
- @return: list of auth types permissible for the next stage of
- authentication (normally empty)
- @rtype: list
-
- @raise BadAuthenticationType: if public-key authentication isn't
- allowed by the server for this user (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')
- if event is None:
- my_event = threading.Event()
- else:
- my_event = event
- self.auth_handler = AuthHandler(self)
- self.auth_handler.auth_publickey(username, key, my_event)
- if event is not None:
- # caller wants to wait for event themselves
- return []
- return self.auth_handler.wait_for_response(my_event)
-
- 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
- dumb wrapper around PAM.
-
- This method will block until the authentication succeeds or fails,
- peroidically calling the handler asynchronously to get answers to
- authentication questions. The handler may be called more than once
- if the server continues to ask questions.
-
- The handler is expected to be a callable that will handle calls of the
- form: C{handler(title, instructions, prompt_list)}. The C{title} is
- meant to be a dialog-window title, and the C{instructions} are user
- instructions (both are strings). C{prompt_list} will be a list of
- prompts, each prompt being a tuple of C{(str, bool)}. The string is
- the prompt and the boolean indicates whether the user text should be
- echoed.
-
- A sample call would thus be:
- C{handler('title', 'instructions', [('Password:', False)])}.
-
- The handler should return a list or tuple of answers to the server's
- questions.
-
- If the server requires multi-step authentication (which is very rare),
- this method will return a list of auth types permissible for the next
- step. Otherwise, in the normal case, an empty list is returned.
- @param username: the username to authenticate as
- @type username: string
- @param handler: a handler for responding to server questions
- @type handler: callable
- @param submethods: a string list of desired submethods (optional)
- @type submethods: str
- @return: list of auth types permissible for the next stage of
- authentication (normally empty).
- @rtype: list
-
- @raise BadAuthenticationType: if public-key authentication isn't
- allowed by the server for this user
- @raise AuthenticationException: if the authentication failed
- @raise SSHException: if there was a network error
-
- @since: 1.5
- """
- 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_interactive(username, handler, my_event, submethods)
- 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
- C{"paramiko.transport"} but it can be set to anything you want.
- (See the C{logging} module for more info.) SSH Channels will log
- to a sub-channel of the one specified.
- @param name: new channel name for logging
- @type name: str
- @since: 1.1
- """
- self.log_name = name
- self.logger = util.get_logger(name)
- self.packetizer.set_log(self.logger)
- def get_log_channel(self):
- """
- Return the channel name used for this transport's logging.
- @return: channel name.
- @rtype: str
- @since: 1.2
- """
- return self.log_name
- def set_hexdump(self, hexdump):
- """
- Turn on/off logging a hex dump of protocol traffic at DEBUG level in
- the logs. Normally you would want this off (which is the default),
- but if you are debugging something, it may be useful.
- @param hexdump: C{True} to log protocol traffix (in hex) to the log;
- C{False} otherwise.
- @type hexdump: bool
- """
- self.packetizer.set_hexdump(hexdump)
-
- def get_hexdump(self):
- """
- Return C{True} if the transport is currently logging hex dumps of
- protocol traffic.
-
- @return: C{True} if hex dumps are being logged
- @rtype: bool
-
- @since: 1.4
- """
- return self.packetizer.get_hexdump()
-
- def use_compression(self, compress=True):
- """
- Turn on/off compression. This will only have an affect before starting
- the transport (ie before calling L{connect}, etc). By default,
- compression is off since it negatively affects interactive sessions
- and is not fully tested.
-
- @param compress: C{True} to ask the remote client/server to compress
- traffic; C{False} to refuse compression
- @type compress: bool
-
- @since: 1.5.2
- """
- if compress:
- self._preferred_compression = ( 'zlib@openssh.com', 'zlib', 'none' )
- else:
- self._preferred_compression = ( 'none', )
-
- def getpeername(self):
- """
- Return the address of the remote side of this Transport, if possible.
- This is effectively a wrapper around C{'getpeername'} on the underlying
- socket. If the socket-like object has no C{'getpeername'} method,
- then C{("unknown", 0)} is returned.
-
- @return: the address if the remote host, if known
- @rtype: tuple(str, int)
- """
- gp = getattr(self.sock, 'getpeername', None)
- if gp is None:
- return ('unknown', 0)
- return gp()
- def stop_thread(self):
- self.active = False
- self.packetizer.close()
- ### internals...
-
- def _log(self, level, msg):
- if issubclass(type(msg), list):
- for m in msg:
- self.logger.log(level, m)
- else:
- self.logger.log(level, msg)
- def _get_modulus_pack(self):
- "used by KexGex to find primes for group exchange"
- return self._modulus_pack
- def _unlink_channel(self, chanid):
- "used by a Channel to remove itself from the active channel list"
- try:
- self.lock.acquire()
- if self.channels.has_key(chanid):
- del self.channels[chanid]
- finally:
- self.lock.release()
- def _send_message(self, data):
- self.packetizer.send_message(data)
- def _send_user_message(self, data):
- """
- send a message, but block if we're in key negotiation. this is used
- for user-initiated requests.
- """
- while True:
- self.clear_to_send.wait(0.1)
- if not self.active:
- self._log(DEBUG, 'Dropping user packet because connection is dead.')
- return
- self.clear_to_send_lock.acquire()
- if self.clear_to_send.isSet():
- break
- self.clear_to_send_lock.release()
- try:
- self._send_message(data)
- finally:
- self.clear_to_send_lock.release()
- def _set_K_H(self, k, h):
- "used by a kex object to set the K (root key) and H (exchange hash)"
- self.K = k
- self.H = h
- if self.session_id == None:
- self.session_id = h
- def _expect_packet(self, type):
- "used by a kex object to register the next packet type it expects to see"
- self.expected_packet = type
- 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')
- if not key.verify_ssh_sig(self.H, Message(sig)):
- raise SSHException('Signature verification (%s) failed. Boo. Robey should debug this.' % self.host_key_type)
- self.host_key = key
- def _compute_key(self, id, nbytes):
- "id is 'A' - 'F' for the various keys used by ssh"
- m = Message()
- m.add_mpint(self.K)
- m.add_bytes(self.H)
- m.add_byte(id)
- m.add_bytes(self.session_id)
- out = sofar = SHA.new(str(m)).digest()
- while len(out) < nbytes:
- m = Message()
- m.add_mpint(self.K)
- m.add_bytes(self.H)
- m.add_bytes(sofar)
- digest = SHA.new(str(m)).digest()
- out += digest
- sofar += digest
- return out[:nbytes]
- def _get_cipher(self, name, key, iv):
- if not self._cipher_info.has_key(name):
- raise SSHException('Unknown client cipher ' + name)
- return self._cipher_info[name]['class'].new(key, self._cipher_info[name]['mode'], iv)
- 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
- # indefinitely, creating a GC cycle and not letting Transport ever be
- # GC'd. it's a bug in Thread.)
-
- # active=True occurs before the thread is launched, to avoid a race
- _active_threads.append(self)
- if self.server_mode:
- self._log(DEBUG, 'starting thread (server mode): %s' % hex(long(id(self)) & 0xffffffffL))
- else:
- self._log(DEBUG, 'starting thread (client mode): %s' % hex(long(id(self)) & 0xffffffffL))
- try:
- self.packetizer.write_all(self.local_version + '\r\n')
- self._check_banner()
- self._send_kex_init()
- self.expected_packet = MSG_KEXINIT
- while self.active:
- if self.packetizer.need_rekey() and not self.in_kex:
- self._send_kex_init()
- try:
- ptype, m = self.packetizer.read_message()
- except NeedRekeyException:
- continue
- if ptype == MSG_IGNORE:
- continue
- elif ptype == MSG_DISCONNECT:
- self._parse_disconnect(m)
- self.active = False
- self.packetizer.close()
- break
- elif ptype == MSG_DEBUG:
- self._parse_debug(m)
- continue
- if self.expected_packet != 0:
- if ptype != self.expected_packet:
- raise SSHException('Expecting packet %d, got %d' % (self.expected_packet, ptype))
- self.expected_packet = 0
- if (ptype >= 30) and (ptype <= 39):
- self.kex_engine.parse_next(ptype, m)
- continue
- if self._handler_table.has_key(ptype):
- self._handler_table[ptype](self, m)
- elif self._channel_handler_table.has_key(ptype):
- chanid = m.get_int()
- if self.channels.has_key(chanid):
- self._channel_handler_table[ptype](self.channels[chanid], m)
- elif self.channels_seen.has_key(chanid):
- self._log(DEBUG, 'Ignoring message for dead channel %d' % chanid)
- else:
- self._log(ERROR, 'Channel request for unknown channel %d' % chanid)
- self.active = False
- self.packetizer.close()
- elif (self.auth_handler is not None) and self.auth_handler._handler_table.has_key(ptype):
- self.auth_handler._handler_table[ptype](self.auth_handler, m)
- else:
- self._log(WARNING, 'Oops, unhandled type %d' % ptype)
- msg = Message()
- msg.add_byte(chr(MSG_UNIMPLEMENTED))
- msg.add_int(m.seqno)
- self._send_message(msg)
- except SSHException, e:
- self._log(ERROR, 'Exception: ' + str(e))
- self._log(ERROR, util.tb_strings())
- self.saved_exception = e
- except EOFError, e:
- self._log(DEBUG, 'EOF in transport thread')
- #self._log(DEBUG, util.tb_strings())
- self.saved_exception = e
- except socket.error, e:
- if type(e.args) is tuple:
- emsg = '%s (%d)' % (e.args[1], e.args[0])
- else:
- emsg = e.args
- self._log(ERROR, 'Socket exception: ' + emsg)
- self.saved_exception = e
- except Exception, e:
- self._log(ERROR, 'Unknown exception: ' + str(e))
- self._log(ERROR, util.tb_strings())
- self.saved_exception = e
- _active_threads.remove(self)
- for chan in self.channels.values():
- chan._unlink()
- if self.active:
- self.active = False
- self.packetizer.close()
- if self.completion_event != None:
- self.completion_event.set()
- if self.auth_handler is not None:
- self.auth_handler.abort()
- for event in self.channel_events.values():
- event.set()
- self.sock.close()
- ### protocol stages
- def _negotiate_keys(self, m):
- # throws SSHException on anything unusual
- self.clear_to_send_lock.acquire()
- try:
- self.clear_to_send.clear()
- finally:
- self.clear_to_send_lock.release()
- if self.local_kex_init == None:
- # remote side wants to renegotiate
- self._send_kex_init()
- self._parse_kex_init(m)
- self.kex_engine.start_kex()
- def _check_banner(self):
- # this is slow, but we only have to do it once
- for i in range(5):
- # give them 15 seconds for the first line, then just 2 seconds
- # each additional line. (some sites have very high latency.)
- if i == 0:
- timeout = self.banner_timeout
- else:
- timeout = 2
- try:
- buf = self.packetizer.readline(timeout)
- except Exception, x:
- raise SSHException('Error reading SSH protocol banner' + str(x))
- if buf[:4] == 'SSH-':
- break
- 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
- # pull off any attached comment
- comment = ''
- i = string.find(buf, ' ')
- if i >= 0:
- comment = buf[i+1:]
- buf = buf[:i]
- # parse out version string and make sure it matches
- segs = buf.split('-', 2)
- if len(segs) < 3:
- raise SSHException('Invalid SSH banner')
- version = segs[1]
- client = segs[2]
- if version != '1.99' and version != '2.0':
- raise SSHException('Incompatible version (%s instead of 2.0)' % (version,))
- self._log(INFO, 'Connected (version %s, client %s)' % (version, client))
- def _send_kex_init(self):
- """
- announce to the other side that we'd like to negotiate keys, and what
- kind of key negotiation we support.
- """
- self.clear_to_send_lock.acquire()
- try:
- self.clear_to_send.clear()
- finally:
- self.clear_to_send_lock.release()
- self.in_kex = True
- if self.server_mode:
- 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
- pkex = list(self.get_security_options().kex)
- pkex.remove('diffie-hellman-group-exchange-sha1')
- self.get_security_options().kex = pkex
- available_server_keys = filter(self.server_key_dict.keys().__contains__,
- self._preferred_keys)
- else:
- available_server_keys = self._preferred_keys
- randpool.stir()
- m = Message()
- m.add_byte(chr(MSG_KEXINIT))
- m.add_bytes(randpool.get_bytes(16))
- m.add_list(self._preferred_kex)
- m.add_list(available_server_keys)
- m.add_list(self._preferred_ciphers)
- m.add_list(self._preferred_ciphers)
- m.add_list(self._preferred_macs)
- m.add_list(self._preferred_macs)
- m.add_list(self._preferred_compression)
- m.add_list(self._preferred_compression)
- m.add_string('')
- m.add_string('')
- m.add_boolean(False)
- m.add_int(0)
- # save a copy for later (needed to compute a hash)
- self.local_kex_init = str(m)
- self._send_message(m)
- def _parse_kex_init(self, m):
- cookie = m.get_bytes(16)
- kex_algo_list = m.get_list()
- server_key_algo_list = m.get_list()
- client_encrypt_algo_list = m.get_list()
- server_encrypt_algo_list = m.get_list()
- client_mac_algo_list = m.get_list()
- server_mac_algo_list = m.get_list()
- client_compress_algo_list = m.get_list()
- server_compress_algo_list = m.get_list()
- client_lang_list = m.get_list()
- server_lang_list = m.get_list()
- kex_follows = m.get_boolean()
- unused = m.get_int()
- 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 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)
- else:
- 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)
- agreed_keys = filter(available_server_keys.__contains__, server_key_algo_list)
- else:
- 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]
- if self.server_mode and (self.get_server_key() is None):
- raise SSHException('Incompatible ssh peer (can\'t match requested host key type)')
- if self.server_mode:
- agreed_local_ciphers = filter(self._preferred_ciphers.__contains__,
- server_encrypt_algo_list)
- 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)
- agreed_remote_ciphers = 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)')
- self.local_cipher = agreed_local_ciphers[0]
- self.remote_cipher = agreed_remote_ciphers[0]
- 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)
- 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)
- 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]
- self.remote_mac = agreed_remote_macs[0]
- if self.server_mode:
- agreed_remote_compression = filter(self._preferred_compression.__contains__, client_compress_algo_list)
- agreed_local_compression = filter(self._preferred_compression.__contains__, server_compress_algo_list)
- else:
- agreed_local_compression = filter(client_compress_algo_list.__contains__, self._preferred_compression)
- agreed_remote_comp