Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
sshtunnel.py1927 linesDownload Raw Back to site-packages
1#!/usr/bin/env python2# -*- coding: utf-8 -*-3"""4*sshtunnel* - Initiate SSH tunnels via a remote gateway.5 6``sshtunnel`` works by opening a port forwarding SSH connection in the7background, using threads.8 9The connection(s) are closed when explicitly calling the10:meth:`SSHTunnelForwarder.stop` method or using it as a context.11 12"""13 14import os15import sys16import socket17import getpass18import logging19import argparse20import warnings21import threading22from select import select23from binascii import hexlify24 25import paramiko26 27if sys.version_info[0] < 3:  # pragma: no cover28    import Queue as queue29    import SocketServer as socketserver30    string_types = basestring,  # noqa31    input_ = raw_input  # noqa32else:  # pragma: no cover33    import queue34    import socketserver35    string_types = str36    input_ = input37 38 39__version__ = '0.4.0'40__author__ = 'pahaz'41 42 43#: Timeout (seconds) for transport socket (``socket.settimeout``)44SSH_TIMEOUT = 0.1  # ``None`` may cause a block of transport thread45#: Timeout (seconds) for tunnel connection (open_channel timeout)46TUNNEL_TIMEOUT = 10.047 48_DAEMON = True  #: Use daemon threads in connections49_CONNECTION_COUNTER = 150_LOCK = threading.Lock()51_DEPRECATIONS = {52    'ssh_address': 'ssh_address_or_host',53    'ssh_host': 'ssh_address_or_host',54    'ssh_private_key': 'ssh_pkey',55    'raise_exception_if_any_forwarder_have_a_problem': 'mute_exceptions'56}57 58# logging59DEFAULT_LOGLEVEL = logging.ERROR  #: default level if no logger passed (ERROR)60TRACE_LEVEL = 161logging.addLevelName(TRACE_LEVEL, 'TRACE')62DEFAULT_SSH_DIRECTORY = '~/.ssh'63 64_StreamServer = socketserver.UnixStreamServer if os.name == 'posix' \65    else socketserver.TCPServer66 67#: Path of optional ssh configuration file68DEFAULT_SSH_DIRECTORY = '~/.ssh'69SSH_CONFIG_FILE = os.path.join(DEFAULT_SSH_DIRECTORY, 'config')70 71########################72#                      #73#       Utils          #74#                      #75########################76 77 78def check_host(host):79    assert isinstance(host, string_types), 'IP is not a string ({0})'.format(80        type(host).__name__81    )82 83 84def check_port(port):85    assert isinstance(port, int), 'PORT is not a number'86    assert port >= 0, 'PORT < 0 ({0})'.format(port)87 88 89def check_address(address):90    """91    Check if the format of the address is correct92 93    Arguments:94        address (tuple):95            (``str``, ``int``) representing an IP address and port,96            respectively97 98            .. note::99                alternatively a local ``address`` can be a ``str`` when working100                with UNIX domain sockets, if supported by the platform101    Raises:102        ValueError:103            raised when address has an incorrect format104 105    Example:106        >>> check_address(('127.0.0.1', 22))107    """108    if isinstance(address, tuple):109        check_host(address[0])110        check_port(address[1])111    elif isinstance(address, string_types):112        if os.name != 'posix':113            raise ValueError('Platform does not support UNIX domain sockets')114        if not (os.path.exists(address) or115                os.access(os.path.dirname(address), os.W_OK)):116            raise ValueError('ADDRESS not a valid socket domain socket ({0})'117                             .format(address))118    else:119        raise ValueError('ADDRESS is not a tuple, string, or character buffer '120                         '({0})'.format(type(address).__name__))121 122 123def check_addresses(address_list, is_remote=False):124    """125    Check if the format of the addresses is correct126 127    Arguments:128        address_list (list[tuple]):129            Sequence of (``str``, ``int``) pairs, each representing an IP130            address and port respectively131 132            .. note::133                when supported by the platform, one or more of the elements in134                the list can be of type ``str``, representing a valid UNIX135                domain socket136 137        is_remote (boolean):138            Whether or not the address list139    Raises:140        AssertionError:141            raised when ``address_list`` contains an invalid element142        ValueError:143            raised when any address in the list has an incorrect format144 145    Example:146 147        >>> check_addresses([('127.0.0.1', 22), ('127.0.0.1', 2222)])148    """149    assert all(isinstance(x, (tuple, string_types)) for x in address_list)150    if (is_remote and any(isinstance(x, string_types) for x in address_list)):151        raise AssertionError('UNIX domain sockets not allowed for remote'152                             'addresses')153 154    for address in address_list:155        check_address(address)156 157 158def create_logger(logger=None,159                  loglevel=None,160                  capture_warnings=True,161                  add_paramiko_handler=True):162    """163    Attach or create a new logger and add a console handler if not present164 165    Arguments:166 167        logger (Optional[logging.Logger]):168            :class:`logging.Logger` instance; a new one is created if this169            argument is empty170 171        loglevel (Optional[str or int]):172            :class:`logging.Logger`'s level, either as a string (i.e.173            ``ERROR``) or in numeric format (10 == ``DEBUG``)174 175            .. note:: a value of 1 == ``TRACE`` enables Tracing mode176 177        capture_warnings (boolean):178            Enable/disable capturing the events logged by the warnings module179            into ``logger``'s handlers180 181            Default: True182 183            .. note:: ignored in python 2.6184 185        add_paramiko_handler (boolean):186            Whether or not add a console handler for ``paramiko.transport``'s187            logger if no handler present188 189            Default: True190    Return:191        :class:`logging.Logger`192    """193    logger = logger or logging.getLogger(194        'sshtunnel.SSHTunnelForwarder'195    )196    if not any(isinstance(x, logging.Handler) for x in logger.handlers):197        logger.setLevel(loglevel or DEFAULT_LOGLEVEL)198        console_handler = logging.StreamHandler()199        _add_handler(logger,200                     handler=console_handler,201                     loglevel=loglevel or DEFAULT_LOGLEVEL)202    if loglevel:  # override if loglevel was set203        logger.setLevel(loglevel)204        for handler in logger.handlers:205            handler.setLevel(loglevel)206 207    if add_paramiko_handler:208        _check_paramiko_handlers(logger=logger)209 210    if capture_warnings and sys.version_info >= (2, 7):211        logging.captureWarnings(True)212        pywarnings = logging.getLogger('py.warnings')213        pywarnings.handlers.extend(logger.handlers)214    return logger215 216 217def _add_handler(logger, handler=None, loglevel=None):218    """219    Add a handler to an existing logging.Logger object220    """221    handler.setLevel(loglevel or DEFAULT_LOGLEVEL)222    if handler.level <= logging.DEBUG:223        _fmt = '%(asctime)s| %(levelname)-4.3s|%(threadName)10.9s/' \224               '%(lineno)04d@%(module)-10.9s| %(message)s'225        handler.setFormatter(logging.Formatter(_fmt))226    else:227        handler.setFormatter(logging.Formatter(228            '%(asctime)s| %(levelname)-8s| %(message)s'229        ))230    logger.addHandler(handler)231 232 233def _check_paramiko_handlers(logger=None):234    """235    Add a console handler for paramiko.transport's logger if not present236    """237    paramiko_logger = logging.getLogger('paramiko.transport')238    if not paramiko_logger.handlers:239        if logger:240            paramiko_logger.handlers = logger.handlers241        else:242            console_handler = logging.StreamHandler()243            console_handler.setFormatter(244                logging.Formatter('%(asctime)s | %(levelname)-8s| PARAMIKO: '245                                  '%(lineno)03d@%(module)-10s| %(message)s')246            )247            paramiko_logger.addHandler(console_handler)248 249 250def address_to_str(address):251    if isinstance(address, tuple):252        return '{0[0]}:{0[1]}'.format(address)253    return str(address)254 255 256def get_connection_id():257    global _CONNECTION_COUNTER258    with _LOCK:259        uid = _CONNECTION_COUNTER260        _CONNECTION_COUNTER += 1261    return uid262 263 264def _remove_none_values(dictionary):265    """ Remove dictionary keys whose value is None """266    return list(map(dictionary.pop,267                    [i for i in dictionary if dictionary[i] is None]))268 269########################270#                      #271#       Errors         #272#                      #273########################274 275 276class BaseSSHTunnelForwarderError(Exception):277    """ Exception raised by :class:`SSHTunnelForwarder` errors """278 279    def __init__(self, *args, **kwargs):280        self.value = kwargs.pop('value', args[0] if args else '')281 282    def __str__(self):283        return self.value284 285 286class HandlerSSHTunnelForwarderError(BaseSSHTunnelForwarderError):287    """ Exception for Tunnel forwarder errors """288    pass289 290 291########################292#                      #293#       Handlers       #294#                      #295########################296 297 298class _ForwardHandler(socketserver.BaseRequestHandler):299    """ Base handler for tunnel connections """300    remote_address = None301    ssh_transport = None302    logger = None303    info = None304 305    def _redirect(self, chan):306        while chan.active:307            rqst, _, _ = select([self.request, chan], [], [], 5)308            if self.request in rqst:309                data = self.request.recv(1024)310                if not data:311                    self.logger.log(312                        TRACE_LEVEL,313                        '>>> OUT {0} recv empty data >>>'.format(self.info)314                    )315                    break316                self.logger.log(317                    TRACE_LEVEL,318                    '>>> OUT {0} send to {1}: {2} >>>'.format(319                        self.info,320                        self.remote_address,321                        hexlify(data)322                    )323                )324                chan.sendall(data)325            if chan in rqst:  # else326                if not chan.recv_ready():327                    self.logger.log(328                        TRACE_LEVEL,329                        '<<< IN {0} recv is not ready <<<'.format(self.info)330                    )331                    break332                data = chan.recv(1024)333                self.logger.log(334                    TRACE_LEVEL,335                    '<<< IN {0} recv: {1} <<<'.format(self.info, hexlify(data))336                )337                self.request.sendall(data)338 339    def handle(self):340        uid = get_connection_id()341        self.info = '#{0} <-- {1}'.format(uid, self.client_address or342                                          self.server.local_address)343        src_address = self.request.getpeername()344        if not isinstance(src_address, tuple):345            src_address = ('dummy', 12345)346        try:347            chan = self.ssh_transport.open_channel(348                kind='direct-tcpip',349                dest_addr=self.remote_address,350                src_addr=src_address,351                timeout=TUNNEL_TIMEOUT352            )353        except Exception as e:  # pragma: no cover354            msg_tupe = 'ssh ' if isinstance(e, paramiko.SSHException) else ''355            exc_msg = 'open new channel {0}error: {1}'.format(msg_tupe, e)356            log_msg = '{0} {1}'.format(self.info, exc_msg)357            self.logger.log(TRACE_LEVEL, log_msg)358            raise HandlerSSHTunnelForwarderError(exc_msg)359 360        self.logger.log(TRACE_LEVEL, '{0} connected'.format(self.info))361        try:362            self._redirect(chan)363        except socket.error:364            # Sometimes a RST is sent and a socket error is raised, treat this365            # exception. It was seen that a 3way FIN is processed later on, so366            # no need to make an ordered close of the connection here or raise367            # the exception beyond this point...368            self.logger.log(TRACE_LEVEL, '{0} sending RST'.format(self.info))369        except Exception as e:370            self.logger.log(TRACE_LEVEL,371                            '{0} error: {1}'.format(self.info, repr(e)))372        finally:373            chan.close()374            self.request.close()375            self.logger.log(TRACE_LEVEL,376                            '{0} connection closed.'.format(self.info))377 378 379class _ForwardServer(socketserver.TCPServer):  # Not Threading380    """381    Non-threading version of the forward server382    """383    allow_reuse_address = True  # faster rebinding384 385    def __init__(self, *args, **kwargs):386        self.logger = create_logger(kwargs.pop('logger', None))387        self.tunnel_ok = queue.Queue(1)388        socketserver.TCPServer.__init__(self, *args, **kwargs)389 390    def handle_error(self, request, client_address):391        (exc_class, exc, tb) = sys.exc_info()392        local_side = request.getsockname()393        remote_side = self.remote_address394        self.logger.error('Could not establish connection from local {0} '395                          'to remote {1} side of the tunnel: {2}'396                          .format(local_side, remote_side, exc))397        try:398            self.tunnel_ok.put(False, block=False, timeout=0.1)399        except queue.Full:400            # wait untill tunnel_ok.get is called401            pass402        except exc:403            self.logger.error('unexpected internal error: {0}'.format(exc))404 405    @property406    def local_address(self):407        return self.server_address408 409    @property410    def local_host(self):411        return self.server_address[0]412 413    @property414    def local_port(self):415        return self.server_address[1]416 417    @property418    def remote_address(self):419        return self.RequestHandlerClass.remote_address420 421    @property422    def remote_host(self):423        return self.RequestHandlerClass.remote_address[0]424 425    @property426    def remote_port(self):427        return self.RequestHandlerClass.remote_address[1]428 429 430class _ThreadingForwardServer(socketserver.ThreadingMixIn, _ForwardServer):431    """432    Allow concurrent connections to each tunnel433    """434    # If True, cleanly stop threads created by ThreadingMixIn when quitting435    # This value is overrides by SSHTunnelForwarder.daemon_forward_servers436    daemon_threads = _DAEMON437 438 439class _StreamForwardServer(_StreamServer):440    """441    Serve over domain sockets (does not work on Windows)442    """443 444    def __init__(self, *args, **kwargs):445        self.logger = create_logger(kwargs.pop('logger', None))446        self.tunnel_ok = queue.Queue(1)447        _StreamServer.__init__(self, *args, **kwargs)448 449    @property450    def local_address(self):451        return self.server_address452 453    @property454    def local_host(self):455        return None456 457    @property458    def local_port(self):459        return None460 461    @property462    def remote_address(self):463        return self.RequestHandlerClass.remote_address464 465    @property466    def remote_host(self):467        return self.RequestHandlerClass.remote_address[0]468 469    @property470    def remote_port(self):471        return self.RequestHandlerClass.remote_address[1]472 473 474class _ThreadingStreamForwardServer(socketserver.ThreadingMixIn,475                                    _StreamForwardServer):476    """477    Allow concurrent connections to each tunnel478    """479    # If True, cleanly stop threads created by ThreadingMixIn when quitting480    # This value is overrides by SSHTunnelForwarder.daemon_forward_servers481    daemon_threads = _DAEMON482 483 484class SSHTunnelForwarder(object):485    """486    **SSH tunnel class**487 488        - Initialize a SSH tunnel to a remote host according to the input489          arguments490 491        - Optionally:492            + Read an SSH configuration file (typically ``~/.ssh/config``)493            + Load keys from a running SSH agent (i.e. Pageant, GNOME Keyring)494 495    Raises:496 497        :class:`.BaseSSHTunnelForwarderError`:498            raised by SSHTunnelForwarder class methods499 500        :class:`.HandlerSSHTunnelForwarderError`:501            raised by tunnel forwarder threads502 503            .. note::504                    Attributes ``mute_exceptions`` and505                    ``raise_exception_if_any_forwarder_have_a_problem``506                    (deprecated) may be used to silence most exceptions raised507                    from this class508 509    Keyword Arguments:510 511        ssh_address_or_host (tuple or str):512            IP or hostname of ``REMOTE GATEWAY``. It may be a two-element513            tuple (``str``, ``int``) representing IP and port respectively,514            or a ``str`` representing the IP address only515 516            .. versionadded:: 0.0.4517 518        ssh_config_file (str):519            SSH configuration file that will be read. If explicitly set to520            ``None``, parsing of this configuration is omitted521 522            Default: :const:`SSH_CONFIG_FILE`523 524            .. versionadded:: 0.0.4525 526        ssh_host_key (str):527            Representation of a line in an OpenSSH-style "known hosts"528            file.529 530            ``REMOTE GATEWAY``'s key fingerprint will be compared to this531            host key in order to prevent against SSH server spoofing.532            Important when using passwords in order not to accidentally533            do a login attempt to a wrong (perhaps an attacker's) machine534 535        ssh_username (str):536            Username to authenticate as in ``REMOTE SERVER``537 538            Default: current local user name539 540        ssh_password (str):541            Text representing the password used to connect to ``REMOTE542            SERVER`` or for unlocking a private key.543 544            .. note::545                Avoid coding secret password directly in the code, since this546                may be visible and make your service vulnerable to attacks547 548        ssh_port (int):549            Optional port number of the SSH service on ``REMOTE GATEWAY``,550            when `ssh_address_or_host`` is a ``str`` representing the551            IP part of ``REMOTE GATEWAY``'s address552 553            Default: 22554 555        ssh_pkey (str or paramiko.PKey):556            **Private** key file name (``str``) to obtain the public key557            from or a **public** key (:class:`paramiko.pkey.PKey`)558 559        ssh_private_key_password (str):560            Password for an encrypted ``ssh_pkey``561 562            .. note::563                Avoid coding secret password directly in the code, since this564                may be visible and make your service vulnerable to attacks565 566        ssh_proxy (socket-like object or tuple):567            Proxy where all SSH traffic will be passed through.568            It might be for example a :class:`paramiko.proxy.ProxyCommand`569            instance.570            See either the :class:`paramiko.transport.Transport`'s sock571            parameter documentation or ``ProxyCommand`` in ``ssh_config(5)``572            for more information.573 574            It is also possible to specify the proxy address as a tuple of575            type (``str``, ``int``) representing proxy's IP and port576 577            .. note::578                Ignored if ``ssh_proxy_enabled`` is False579 580            .. versionadded:: 0.0.5581 582        ssh_proxy_enabled (boolean):583            Enable/disable SSH proxy. If True and user's584            ``ssh_config_file`` contains a ``ProxyCommand`` directive585            that matches the specified ``ssh_address_or_host``,586            a :class:`paramiko.proxy.ProxyCommand` object will be created where587            all SSH traffic will be passed through588 589            Default: ``True``590 591            .. versionadded:: 0.0.4592 593        local_bind_address (tuple):594            Local tuple in the format (``str``, ``int``) representing the595            IP and port of the local side of the tunnel. Both elements in596            the tuple are optional so both ``('', 8000)`` and597            ``('10.0.0.1', )`` are valid values598 599            Default: ``('0.0.0.0', RANDOM_PORT)``600 601            .. versionchanged:: 0.0.8602                Added the ability to use a UNIX domain socket as local bind603                address604 605        local_bind_addresses (list[tuple]):606            In case more than one tunnel is established at once, a list607            of tuples (in the same format as ``local_bind_address``)608            can be specified, such as [(ip1, port_1), (ip_2, port2), ...]609 610            Default: ``[local_bind_address]``611 612            .. versionadded:: 0.0.4613 614        remote_bind_address (tuple):615            Remote tuple in the format (``str``, ``int``) representing the616            IP and port of the remote side of the tunnel.617 618        remote_bind_addresses (list[tuple]):619            In case more than one tunnel is established at once, a list620            of tuples (in the same format as ``remote_bind_address``)621            can be specified, such as [(ip1, port_1), (ip_2, port2), ...]622 623            Default: ``[remote_bind_address]``624 625            .. versionadded:: 0.0.4626 627        allow_agent (boolean):628            Enable/disable load of keys from an SSH agent629 630            Default: ``True``631 632            .. versionadded:: 0.0.8633 634        host_pkey_directories (list):635            Look for pkeys in folders on this list, for example ['~/.ssh'].636 637            Default: ``None`` (disabled)638 639            .. versionadded:: 0.1.4640 641        compression (boolean):642            Turn on/off transport compression. By default compression is643            disabled since it may negatively affect interactive sessions644 645            Default: ``False``646 647            .. versionadded:: 0.0.8648 649        logger (logging.Logger):650            logging instance for sshtunnel and paramiko651 652            Default: :class:`logging.Logger` instance with a single653            :class:`logging.StreamHandler` handler and654            :const:`DEFAULT_LOGLEVEL` level655 656            .. versionadded:: 0.0.3657 658        mute_exceptions (boolean):659            Allow silencing :class:`BaseSSHTunnelForwarderError` or660            :class:`HandlerSSHTunnelForwarderError` exceptions when enabled661 662            Default: ``False``663 664            .. versionadded:: 0.0.8665 666        set_keepalive (float):667            Interval in seconds defining the period in which, if no data668            was sent over the connection, a *'keepalive'* packet will be669            sent (and ignored by the remote host). This can be useful to670            keep connections alive over a NAT. You can set to 0.0 for671            disable keepalive.672 673            Default: 5.0 (no keepalive packets are sent)674 675            .. versionadded:: 0.0.7676 677        threaded (boolean):678            Allow concurrent connections over a single tunnel679 680            Default: ``True``681 682            .. versionadded:: 0.0.3683 684        ssh_address (str):685            Superseded by ``ssh_address_or_host``, tuple of type (str, int)686            representing the IP and port of ``REMOTE SERVER``687 688            .. deprecated:: 0.0.4689 690        ssh_host (str):691            Superseded by ``ssh_address_or_host``, tuple of type692            (str, int) representing the IP and port of ``REMOTE SERVER``693 694            .. deprecated:: 0.0.4695 696        ssh_private_key (str or paramiko.PKey):697            Superseded by ``ssh_pkey``, which can represent either a698            **private** key file name (``str``) or a **public** key699            (:class:`paramiko.pkey.PKey`)700 701            .. deprecated:: 0.0.8702 703        raise_exception_if_any_forwarder_have_a_problem (boolean):704            Allow silencing :class:`BaseSSHTunnelForwarderError` or705            :class:`HandlerSSHTunnelForwarderError` exceptions when set to706            False707 708            Default: ``True``709 710            .. versionadded:: 0.0.4711 712            .. deprecated:: 0.0.8 (use ``mute_exceptions`` instead)713 714    Attributes:715 716        tunnel_is_up (dict):717            Describe whether or not the other side of the tunnel was reported718            to be up (and we must close it) or not (skip shutting down that719            tunnel)720 721            .. note::722                This attribute should not be modified723 724            .. note::725                When :attr:`.skip_tunnel_checkup` is disabled or the local bind726                is a UNIX socket, the value will always be ``True``727 728            **Example**::729 730                {('127.0.0.1', 55550): True,   # this tunnel is up731                 ('127.0.0.1', 55551): False}  # this one isn't732 733            where 55550 and 55551 are the local bind ports734 735        skip_tunnel_checkup (boolean):736            Disable tunnel checkup (default for backwards compatibility).737 738            .. versionadded:: 0.1.0739 740    """741    skip_tunnel_checkup = True742    # This option affects the `ForwardServer` and all his threads743    daemon_forward_servers = _DAEMON  #: flag tunnel threads in daemon mode744    # This option affect only `Transport` thread745    daemon_transport = _DAEMON  #: flag SSH transport thread in daemon mode746 747    def local_is_up(self, target):748        """749        Check if a tunnel is up (remote target's host is reachable on TCP750        target's port)751 752        Arguments:753            target (tuple):754                tuple of type (``str``, ``int``) indicating the listen IP755                address and port756        Return:757            boolean758 759        .. deprecated:: 0.1.0760            Replaced by :meth:`.check_tunnels()` and :attr:`.tunnel_is_up`761        """762        try:763            check_address(target)764        except ValueError:765            self.logger.warning('Target must be a tuple (IP, port), where IP '766                                'is a string (i.e. "192.168.0.1") and port is '767                                'an integer (i.e. 40000). Alternatively '768                                'target can be a valid UNIX domain socket.')769            return False770 771        self.check_tunnels()772        return self.tunnel_is_up.get(target, True)773 774    def check_tunnels(self):775        """776        Check that if all tunnels are established and populates777        :attr:`.tunnel_is_up`778        """779        skip_tunnel_checkup = self.skip_tunnel_checkup780        try:781            # force tunnel check at this point782            self.skip_tunnel_checkup = False783            for _srv in self._server_list:784                self._check_tunnel(_srv)785        finally:786            self.skip_tunnel_checkup = skip_tunnel_checkup  # roll it back787 788    def _check_tunnel(self, _srv):789        """ Check if tunnel is already established """790        if self.skip_tunnel_checkup:791            self.tunnel_is_up[_srv.local_address] = True792            return793        self.logger.info('Checking tunnel to: {0}'.format(_srv.remote_address))794        if isinstance(_srv.local_address, string_types):  # UNIX stream795            s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)796        else:797            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)798        s.settimeout(TUNNEL_TIMEOUT)799        try:800            # Windows raises WinError 10049 if trying to connect to 0.0.0.0801            connect_to = ('127.0.0.1', _srv.local_port) \802                if _srv.local_host == '0.0.0.0' else _srv.local_address803            s.connect(connect_to)804            self.tunnel_is_up[_srv.local_address] = _srv.tunnel_ok.get(805                timeout=TUNNEL_TIMEOUT * 1.1806            )807            self.logger.debug(808                'Tunnel to {0} is DOWN'.format(_srv.remote_address)809            )810        except socket.error:811            self.logger.debug(812                'Tunnel to {0} is DOWN'.format(_srv.remote_address)813            )814            self.tunnel_is_up[_srv.local_address] = False815 816        except queue.Empty:817            self.logger.debug(818                'Tunnel to {0} is UP'.format(_srv.remote_address)819            )820            self.tunnel_is_up[_srv.local_address] = True821        finally:822            s.close()823 824    def _make_ssh_forward_handler_class(self, remote_address_):825        """826        Make SSH Handler class827        """828        class Handler(_ForwardHandler):829            remote_address = remote_address_830            ssh_transport = self._transport831            logger = self.logger832        return Handler833 834    def _make_ssh_forward_server_class(self, remote_address_):835        return _ThreadingForwardServer if self._threaded else _ForwardServer836 837    def _make_stream_ssh_forward_server_class(self, remote_address_):838        return _ThreadingStreamForwardServer if self._threaded \839            else _StreamForwardServer840 841    def _make_ssh_forward_server(self, remote_address, local_bind_address):842        """843        Make SSH forward proxy Server class844        """845        _Handler = self._make_ssh_forward_handler_class(remote_address)846        try:847            forward_maker_class = self._make_stream_ssh_forward_server_class \848                if isinstance(local_bind_address, string_types) \849                else self._make_ssh_forward_server_class850            _Server = forward_maker_class(remote_address)851            ssh_forward_server = _Server(852                local_bind_address,853                _Handler,854                logger=self.logger,855            )856 857            if ssh_forward_server:858                ssh_forward_server.daemon_threads = self.daemon_forward_servers859                self._server_list.append(ssh_forward_server)860                self.tunnel_is_up[ssh_forward_server.server_address] = False861            else:862                self._raise(863                    BaseSSHTunnelForwarderError,864                    'Problem setting up ssh {0} <> {1} forwarder. You can '865                    'suppress this exception by using the `mute_exceptions`'866                    'argument'.format(address_to_str(local_bind_address),867                                      address_to_str(remote_address))868                )869        except IOError:870            self._raise(871                BaseSSHTunnelForwarderError,872                "Couldn't open tunnel {0} <> {1} might be in use or "873                "destination not reachable".format(874                    address_to_str(local_bind_address),875                    address_to_str(remote_address)876                )877            )878 879    def __init__(880            self,881            ssh_address_or_host=None,882            ssh_config_file=SSH_CONFIG_FILE,883            ssh_host_key=None,884            ssh_password=None,885            ssh_pkey=None,886            ssh_private_key_password=None,887            ssh_proxy=None,888            ssh_proxy_enabled=True,889            ssh_username=None,890            local_bind_address=None,891            local_bind_addresses=None,892            logger=None,893            mute_exceptions=False,894            remote_bind_address=None,895            remote_bind_addresses=None,896            set_keepalive=5.0,897            threaded=True,  # old version False898            compression=None,899            allow_agent=True,  # look for keys from an SSH agent900            host_pkey_directories=None,  # look for keys in ~/.ssh901            *args,902            **kwargs  # for backwards compatibility903    ):904        self.logger = logger or create_logger()905 906        # Ensure paramiko.transport has a console handler907        _check_paramiko_handlers(logger=logger)908 909        self.ssh_host_key = ssh_host_key910        self.set_keepalive = set_keepalive911        self._server_list = []  # reset server list912        self.tunnel_is_up = {}  # handle tunnel status913        self._threaded = threaded914        self.is_alive = False915        # Check if deprecated arguments ssh_address or ssh_host were used916        for deprecated_argument in ['ssh_address', 'ssh_host']:917            ssh_address_or_host = self._process_deprecated(ssh_address_or_host,918                                                           deprecated_argument,919                                                           kwargs)920        # other deprecated arguments921        ssh_pkey = self._process_deprecated(ssh_pkey,922                                            'ssh_private_key',923                                            kwargs)924 925        self._raise_fwd_exc = self._process_deprecated(926            None,927            'raise_exception_if_any_forwarder_have_a_problem',928            kwargs) or not mute_exceptions929 930        if isinstance(ssh_address_or_host, tuple):931            check_address(ssh_address_or_host)932            (ssh_host, ssh_port) = ssh_address_or_host933        else:934            ssh_host = ssh_address_or_host935            ssh_port = kwargs.pop('ssh_port', None)936 937        if kwargs:938            raise ValueError('Unknown arguments: {0}'.format(kwargs))939 940        # remote binds941        self._remote_binds = self._get_binds(remote_bind_address,942                                             remote_bind_addresses,943                                             is_remote=True)944        # local binds945        self._local_binds = self._get_binds(local_bind_address,946                                            local_bind_addresses)947        self._local_binds = self._consolidate_binds(self._local_binds,948                                                    self._remote_binds)949 950        (self.ssh_host,951         self.ssh_username,952         ssh_pkey,  # still needs to go through _consolidate_auth953         self.ssh_port,954         self.ssh_proxy,955         self.compression) = self._read_ssh_config(956             ssh_host,957             ssh_config_file,958             ssh_username,959             ssh_pkey,960             ssh_port,961             ssh_proxy if ssh_proxy_enabled else None,962             compression,963             self.logger964        )965 966        (self.ssh_password, self.ssh_pkeys) = self._consolidate_auth(967            ssh_password=ssh_password,968            ssh_pkey=ssh_pkey,969            ssh_pkey_password=ssh_private_key_password,970            allow_agent=allow_agent,971            host_pkey_directories=host_pkey_directories,972            logger=self.logger973        )974 975        check_host(self.ssh_host)976        check_port(self.ssh_port)977 978        self.logger.info("Connecting to gateway: {0}:{1} as user '{2}'"979                         .format(self.ssh_host,980                                 self.ssh_port,981                                 self.ssh_username))982 983        self.logger.debug('Concurrent connections allowed: {0}'984                          .format(self._threaded))985 986    @staticmethod987    def _read_ssh_config(ssh_host,988                         ssh_config_file,989                         ssh_username=None,990                         ssh_pkey=None,991                         ssh_port=None,992                         ssh_proxy=None,993                         compression=None,994                         logger=None):995        """996        Read ssh_config_file and tries to look for user (ssh_username),997        identityfile (ssh_pkey), port (ssh_port) and proxycommand998        (ssh_proxy) entries for ssh_host999        """1000        ssh_config = paramiko.SSHConfig()1001        if not ssh_config_file:  # handle case where it's an empty string1002            ssh_config_file = None1003 1004        # Try to read SSH_CONFIG_FILE1005        try:1006            # open the ssh config file1007            with open(os.path.expanduser(ssh_config_file), 'r') as f:1008                ssh_config.parse(f)1009            # looks for information for the destination system1010            hostname_info = ssh_config.lookup(ssh_host)1011            # gather settings for user, port and identity file1012            # last resort: use the 'login name' of the user1013            ssh_username = (1014                ssh_username or1015                hostname_info.get('user')1016            )1017            ssh_pkey = (1018                ssh_pkey or1019                hostname_info.get('identityfile', [None])[0]1020            )1021            ssh_host = hostname_info.get('hostname')1022            ssh_port = ssh_port or hostname_info.get('port')1023 1024            proxycommand = hostname_info.get('proxycommand')1025            ssh_proxy = ssh_proxy or (paramiko.ProxyCommand(proxycommand) if1026                                      proxycommand else None)1027            if compression is None:1028                compression = hostname_info.get('compression', '')1029                compression = True if compression.upper() == 'YES' else False1030        except IOError:1031            if logger:1032                logger.warning(1033                    'Could not read SSH configuration file: {0}'1034                    .format(ssh_config_file)1035                )1036        except (AttributeError, TypeError):  # ssh_config_file is None1037            if logger:1038                logger.info('Skipping loading of ssh configuration file')1039        finally:1040            return (ssh_host,1041                    ssh_username or getpass.getuser(),1042                    ssh_pkey,1043                    int(ssh_port) if ssh_port else 22,  # fallback value1044                    ssh_proxy,1045                    compression)1046 1047    @staticmethod1048    def get_agent_keys(logger=None):1049        """ Load public keys from any available SSH agent1050 1051        Arguments:1052            logger (Optional[logging.Logger])1053 1054        Return:1055            list1056        """1057        paramiko_agent = paramiko.Agent()1058        agent_keys = paramiko_agent.get_keys()1059        if logger:1060            logger.info('{0} keys loaded from agent'.format(len(agent_keys)))1061        return list(agent_keys)1062 1063    @staticmethod1064    def get_keys(logger=None, host_pkey_directories=None, allow_agent=False):1065        """1066        Load public keys from any available SSH agent or local1067        .ssh directory.1068 1069        Arguments:1070            logger (Optional[logging.Logger])1071 1072            host_pkey_directories (Optional[list[str]]):1073                List of local directories where host SSH pkeys in the format1074                "id_*" are searched. For example, ['~/.ssh']1075 1076                .. versionadded:: 0.1.01077 1078            allow_agent (Optional[boolean]):1079                Whether or not load keys from agent1080 1081                Default: False1082 1083        Return:1084            list1085        """1086        keys = SSHTunnelForwarder.get_agent_keys(logger=logger) \1087            if allow_agent else []1088 1089        if host_pkey_directories is None:1090            host_pkey_directories = [DEFAULT_SSH_DIRECTORY]1091 1092        paramiko_key_types = {'rsa': paramiko.RSAKey,1093                              'dsa': paramiko.DSSKey,1094                              'ecdsa': paramiko.ECDSAKey}1095        if hasattr(paramiko, 'Ed25519Key'):1096            # NOQA: new in paramiko>=2.2: http://docs.paramiko.org/en/stable/api/keys.html#module-paramiko.ed25519key1097            paramiko_key_types['ed25519'] = paramiko.Ed25519Key1098        for directory in host_pkey_directories:1099            for keytype in paramiko_key_types.keys():1100                ssh_pkey_expanded = os.path.expanduser(1101                    os.path.join(directory, 'id_{}'.format(keytype))1102                )1103                try:1104                    if os.path.isfile(ssh_pkey_expanded):1105                        ssh_pkey = SSHTunnelForwarder.read_private_key_file(1106                            pkey_file=ssh_pkey_expanded,1107                            logger=logger,1108                            key_type=paramiko_key_types[keytype]1109                        )1110                        if ssh_pkey:1111                            keys.append(ssh_pkey)1112                except OSError as exc:1113                    if logger:1114                        logger.warning('Private key file {0} check error: {1}'1115                                       .format(ssh_pkey_expanded, exc))1116        if logger:1117            logger.info('{0} key(s) loaded'.format(len(keys)))1118        return keys1119 1120    @staticmethod1121    def _consolidate_binds(local_binds, remote_binds):1122        """1123        Fill local_binds with defaults when no value/s were specified,1124        leaving paramiko to decide in which local port the tunnel will be open1125        """1126        count = len(remote_binds) - len(local_binds)1127        if count < 0:1128            raise ValueError('Too many local bind addresses '1129                             '(local_bind_addresses > remote_bind_addresses)')1130        local_binds.extend([('0.0.0.0', 0) for x in range(count)])1131        return local_binds1132 1133    @staticmethod1134    def _consolidate_auth(ssh_password=None,1135                          ssh_pkey=None,1136                          ssh_pkey_password=None,1137                          allow_agent=True,1138                          host_pkey_directories=None,1139                          logger=None):1140        """1141        Get sure authentication information is in place.1142        ``ssh_pkey`` may be of classes:1143            - ``str`` - in this case it represents a private key file; public1144            key will be obtained from it1145            - ``paramiko.Pkey`` - it will be transparently added to loaded keys1146 1147        """1148        ssh_loaded_pkeys = SSHTunnelForwarder.get_keys(1149            logger=logger,1150            host_pkey_directories=host_pkey_directories,1151            allow_agent=allow_agent1152        )1153 1154        if isinstance(ssh_pkey, string_types):1155            ssh_pkey_expanded = os.path.expanduser(ssh_pkey)1156            if os.path.exists(ssh_pkey_expanded):1157                ssh_pkey = SSHTunnelForwarder.read_private_key_file(1158                    pkey_file=ssh_pkey_expanded,1159                    pkey_password=ssh_pkey_password or ssh_password,1160                    logger=logger1161                )1162            elif logger:1163                logger.warning('Private key file not found: {0}'1164                               .format(ssh_pkey))1165        if isinstance(ssh_pkey, paramiko.pkey.PKey):1166            ssh_loaded_pkeys.insert(0, ssh_pkey)1167 1168        if not ssh_password and not ssh_loaded_pkeys:1169            raise ValueError('No password or public key available!')1170        return (ssh_password, ssh_loaded_pkeys)1171 1172    def _raise(self, exception=BaseSSHTunnelForwarderError, reason=None):1173        if self._raise_fwd_exc:1174            raise exception(reason)1175        else:1176            self.logger.error(repr(exception(reason)))1177 1178    def _get_transport(self):1179        """ Return the SSH transport to the remote gateway """1180        if self.ssh_proxy:1181            if isinstance(self.ssh_proxy, paramiko.proxy.ProxyCommand):1182                proxy_repr = repr(self.ssh_proxy.cmd[1])1183            else:1184                proxy_repr = repr(self.ssh_proxy)1185            self.logger.debug('Connecting via proxy: {0}'.format(proxy_repr))1186            _socket = self.ssh_proxy1187        else:1188            _socket = (self.ssh_host, self.ssh_port)1189        if isinstance(_socket, socket.socket):1190            _socket.settimeout(SSH_TIMEOUT)1191            _socket.connect((self.ssh_host, self.ssh_port))1192        transport = paramiko.Transport(_socket)1193        sock = transport.sock1194        if isinstance(sock, socket.socket):1195            sock.settimeout(SSH_TIMEOUT)1196        transport.set_keepalive(self.set_keepalive)1197        transport.use_compression(compress=self.compression)1198        transport.daemon = self.daemon_transport1199        # try to solve https://github.com/paramiko/paramiko/issues/11811200        # transport.banner_timeout = 200

Showing the first 1,200 of 1927 lines. Download the file for the rest.

codekingpro/portable-devtools · Team Ai