diff options
Diffstat (limited to 'ryu/contrib/ncclient/transport/ssh.py')
-rw-r--r-- | ryu/contrib/ncclient/transport/ssh.py | 312 |
1 files changed, 0 insertions, 312 deletions
diff --git a/ryu/contrib/ncclient/transport/ssh.py b/ryu/contrib/ncclient/transport/ssh.py deleted file mode 100644 index ad3d549d..00000000 --- a/ryu/contrib/ncclient/transport/ssh.py +++ /dev/null @@ -1,312 +0,0 @@ -# Copyright 2009 Shikhar Bhushan -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import socket -import getpass -from binascii import hexlify -from cStringIO import StringIO -from select import select - -import paramiko - -from errors import AuthenticationError, SessionCloseError, SSHError, SSHUnknownHostError -from session import Session - -import logging -logger = logging.getLogger("ncclient.transport.ssh") - -BUF_SIZE = 4096 -MSG_DELIM = "]]>]]>" -TICK = 0.1 - -def default_unknown_host_cb(host, fingerprint): - """An unknown host callback returns `True` if it finds the key acceptable, and `False` if not. - - This default callback always returns `False`, which would lead to :meth:`connect` raising a :exc:`SSHUnknownHost` exception. - - Supply another valid callback if you need to verify the host key programatically. - - *host* is the hostname that needs to be verified - - *fingerprint* is a hex string representing the host key fingerprint, colon-delimited e.g. `"4b:69:6c:72:6f:79:20:77:61:73:20:68:65:72:65:21"` - """ - return False - -def _colonify(fp): - finga = fp[:2] - for idx in range(2, len(fp), 2): - finga += ":" + fp[idx:idx+2] - return finga - -class SSHSession(Session): - - "Implements a :rfc:`4742` NETCONF session over SSH." - - def __init__(self, capabilities): - Session.__init__(self, capabilities) - self._host_keys = paramiko.HostKeys() - self._transport = None - self._connected = False - self._channel = None - self._buffer = StringIO() # for incoming data - # parsing-related, see _parse() - self._parsing_state = 0 - self._parsing_pos = 0 - - def _parse(self): - "Messages ae delimited by MSG_DELIM. The buffer could have grown by a maximum of BUF_SIZE bytes everytime this method is called. Retains state across method calls and if a byte has been read it will not be considered again." - delim = MSG_DELIM - n = len(delim) - 1 - expect = self._parsing_state - buf = self._buffer - buf.seek(self._parsing_pos) - while True: - x = buf.read(1) - if not x: # done reading - break - elif x == delim[expect]: # what we expected - expect += 1 # expect the next delim char - else: - expect = 0 - continue - # loop till last delim char expected, break if other char encountered - for i in range(expect, n): - x = buf.read(1) - if not x: # done reading - break - if x == delim[expect]: # what we expected - expect += 1 # expect the next delim char - else: - expect = 0 # reset - break - else: # if we didn't break out of the loop, full delim was parsed - msg_till = buf.tell() - n - buf.seek(0) - logger.debug('parsed new message') - self._dispatch_message(buf.read(msg_till).strip()) - buf.seek(n+1, os.SEEK_CUR) - rest = buf.read() - buf = StringIO() - buf.write(rest) - buf.seek(0) - expect = 0 - self._buffer = buf - self._parsing_state = expect - self._parsing_pos = self._buffer.tell() - - def load_known_hosts(self, filename=None): - """Load host keys from an openssh :file:`known_hosts`-style file. Can be called multiple times. - - If *filename* is not specified, looks in the default locations i.e. :file:`~/.ssh/known_hosts` and :file:`~/ssh/known_hosts` for Windows. - """ - if filename is None: - filename = os.path.expanduser('~/.ssh/known_hosts') - try: - self._host_keys.load(filename) - except IOError: - # for windows - filename = os.path.expanduser('~/ssh/known_hosts') - try: - self._host_keys.load(filename) - except IOError: - pass - else: - self._host_keys.load(filename) - - def close(self): - if self._transport.is_active(): - self._transport.close() - self._connected = False - - # REMEMBER to update transport.rst if sig. changes, since it is hardcoded there - def connect(self, host, port=830, timeout=None, unknown_host_cb=default_unknown_host_cb, - username=None, password=None, key_filename=None, allow_agent=True, look_for_keys=True): - """Connect via SSH and initialize the NETCONF session. First attempts the publickey authentication method and then password authentication. - - To disable attempting publickey authentication altogether, call with *allow_agent* and *look_for_keys* as `False`. - - *host* is the hostname or IP address to connect to - - *port* is by default 830, but some devices use the default SSH port of 22 so this may need to be specified - - *timeout* is an optional timeout for socket connect - - *unknown_host_cb* is called when the server host key is not recognized. It takes two arguments, the hostname and the fingerprint (see the signature of :func:`default_unknown_host_cb`) - - *username* is the username to use for SSH authentication - - *password* is the password used if using password authentication, or the passphrase to use for unlocking keys that require it - - *key_filename* is a filename where a the private key to be used can be found - - *allow_agent* enables querying SSH agent (if found) for keys - - *look_for_keys* enables looking in the usual locations for ssh keys (e.g. :file:`~/.ssh/id_*`) - """ - if username is None: - username = getpass.getuser() - - sock = None - for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM): - af, socktype, proto, canonname, sa = res - try: - sock = socket.socket(af, socktype, proto) - sock.settimeout(timeout) - except socket.error: - continue - try: - sock.connect(sa) - except socket.error: - sock.close() - continue - break - else: - raise SSHError("Could not open socket to %s:%s" % (host, port)) - - t = self._transport = paramiko.Transport(sock) - t.set_log_channel(logger.name) - - try: - t.start_client() - except paramiko.SSHException: - raise SSHError('Negotiation failed') - - # host key verification - server_key = t.get_remote_server_key() - known_host = self._host_keys.check(host, server_key) - - fingerprint = _colonify(hexlify(server_key.get_fingerprint())) - - if not known_host and not unknown_host_cb(host, fingerprint): - raise SSHUnknownHostError(host, fingerprint) - - if key_filename is None: - key_filenames = [] - elif isinstance(key_filename, basestring): - key_filenames = [ key_filename ] - else: - key_filenames = key_filename - - self._auth(username, password, key_filenames, allow_agent, look_for_keys) - - self._connected = True # there was no error authenticating - - c = self._channel = self._transport.open_session() - c.set_name("netconf") - c.invoke_subsystem("netconf") - - self._post_connect() - - # on the lines of paramiko.SSHClient._auth() - def _auth(self, username, password, key_filenames, allow_agent, - look_for_keys): - saved_exception = None - - for key_filename in key_filenames: - for cls in (paramiko.RSAKey, paramiko.DSSKey): - try: - key = cls.from_private_key_file(key_filename, password) - logger.debug("Trying key %s from %s", - hexlify(key.get_fingerprint()), key_filename) - self._transport.auth_publickey(username, key) - return - except Exception as e: - saved_exception = e - logger.debug(e) - - if allow_agent: - for key in paramiko.Agent().get_keys(): - try: - logger.debug("Trying SSH agent key %s", - hexlify(key.get_fingerprint())) - self._transport.auth_publickey(username, key) - return - except Exception as e: - saved_exception = e - logger.debug(e) - - keyfiles = [] - if look_for_keys: - rsa_key = os.path.expanduser("~/.ssh/id_rsa") - dsa_key = os.path.expanduser("~/.ssh/id_dsa") - if os.path.isfile(rsa_key): - keyfiles.append((paramiko.RSAKey, rsa_key)) - if os.path.isfile(dsa_key): - keyfiles.append((paramiko.DSSKey, dsa_key)) - # look in ~/ssh/ for windows users: - rsa_key = os.path.expanduser("~/ssh/id_rsa") - dsa_key = os.path.expanduser("~/ssh/id_dsa") - if os.path.isfile(rsa_key): - keyfiles.append((paramiko.RSAKey, rsa_key)) - if os.path.isfile(dsa_key): - keyfiles.append((paramiko.DSSKey, dsa_key)) - - for cls, filename in keyfiles: - try: - key = cls.from_private_key_file(filename, password) - logger.debug("Trying discovered key %s in %s", - hexlify(key.get_fingerprint()), filename) - self._transport.auth_publickey(username, key) - return - except Exception as e: - saved_exception = e - logger.debug(e) - - if password is not None: - try: - self._transport.auth_password(username, password) - return - except Exception as e: - saved_exception = e - logger.debug(e) - - if saved_exception is not None: - # need pep-3134 to do this right - raise AuthenticationError(repr(saved_exception)) - - raise AuthenticationError("No authentication methods available") - - def run(self): - chan = self._channel - q = self._q - try: - while True: - # select on a paramiko ssh channel object does not ever return it in the writable list, so channels don't exactly emulate the socket api - r, w, e = select([chan], [], [], TICK) - # will wakeup evey TICK seconds to check if something to send, more if something to read (due to select returning chan in readable list) - if r: - data = chan.recv(BUF_SIZE) - if data: - self._buffer.write(data) - self._parse() - else: - raise SessionCloseError(self._buffer.getvalue()) - if not q.empty() and chan.send_ready(): - logger.debug("Sending message") - data = q.get() + MSG_DELIM - while data: - n = chan.send(data) - if n <= 0: - raise SessionCloseError(self._buffer.getvalue(), data) - data = data[n:] - except Exception as e: - logger.debug("Broke out of main loop, error=%r", e) - self.close() - self._dispatch_error(e) - - @property - def transport(self): - "Underlying `paramiko.Transport <http://www.lag.net/paramiko/docs/paramiko.Transport-class.html>`_ object. This makes it possible to call methods like :meth:`~paramiko.Transport.set_keepalive` on it." - return self._transport |