Ejemplo n.º 1
0
class RemoteRunner(Runner):
    def __init__(self, *args, **kwargs):
        super(RemoteRunner, self).__init__(*args, **kwargs)
        self.context

    def start(self, command):
        self.ssh_client = SSHClient()
        self.ssh_client.load_system_host_keys()
        self.ssh_client.set_missing_host_key_policy(AutoAddPolicy())
        self.ssh_client.connect(self.context.remote_runner.hostname, username=self.context.remote_runner.username)
        self.ssh_channel = self.ssh_client.get_transport().open_session()
        if self.using_pty:
            self.ssh_channel.get_pty()
        self.ssh_channel.exec_command(command)

    def stdout_reader(self):
        return self.ssh_channel.recv

    def stderr_reader(self):
        return self.ssh_channel.recv_stderr

    def default_encoding(self):
        return locale.getpreferredencoding(True)

    def wait(self):
        return self.ssh_channel.recv_exit_status()

    def returncode(self):
        return self.ssh_channel.recv_exit_status()
Ejemplo n.º 2
0
    def _get_ssh_connection(self):
        """Returns an ssh connection to the specified host"""
        _timeout = True
        ssh = SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        _start_time = time.time()
        saved_exception = exceptions.StandardError()
        #doing this because the log file fills up with these messages
        #this way it only logs it once
        log_attempted = False
        socket_error_logged = False
        auth_error_logged = False
        ssh_error_logged = False
        while not self._is_timed_out(self.timeout, _start_time):
            try:
                if not log_attempted:
                    self._log.debug('Attempting to SSH connect to: ')
                    self._log.debug('host: %s, username: %s, password: %s' %
                                    (self.host, self.username, self.password))
                    log_attempted = True
                ssh.connect(hostname=self.host,
                            username=self.username,
                            password=self.password,
                            timeout=20,
                            key_filename=[],
                            look_for_keys=False,
                            allow_agent=False)
                _timeout = False
                break
            except socket.error as e:
                if not socket_error_logged:
                    self._log.error('Socket Error: %s' % str(e))
                    socket_error_logged = True
                saved_exception = e
                continue
            except paramiko.AuthenticationException as e:
                if not auth_error_logged:
                    self._log.error('Auth Exception: %s' % str(e))
                    auth_error_logged = True
                saved_exception = e
                time.sleep(2)
                continue
            except paramiko.SSHException as e:
                if not ssh_error_logged:
                    self._log.error('SSH Exception: %s' % str(e))
                    ssh_error_logged = True
                saved_exception = e
                time.sleep(2)
                continue
                #Wait 2 seconds otherwise
            time.sleep(2)
        if _timeout:
            self._log.error('SSHConnector timed out while trying to establish a connection')
            raise saved_exception

        #This MUST be done because the transport gets garbage collected if it
        #is not done here, which causes the connection to close on invoke_shell
        #which is needed for exec_shell_command
        ResourceManager.register(self, ssh.get_transport())
        return ssh
Ejemplo n.º 3
0
def run_remote_command(args, hostgroup_name, hostgroup, command):
    """Run the appropriate command on hosts in a given host group based on
the action being taken"""
    client = SSHClient()
    client.load_system_host_keys()
    client.set_missing_host_key_policy(AutoAddPolicy())
    ssh_key = None
    host_results = {}
    if args.ssh_key:
        ssh_key = "%s/.ssh/%s" % (os.environ['HOME'], args.ssh_key)
    for host in hostgroup:
        try:
            client.connect(host.address,
                           allow_agent=True,
                           username=os.getenv('USER'))
        except Exception, e:
            print "Error running remote command on (%s:%s) (%s)" % (
                host.name, host.address, e)
            continue
        print "(%s:%s) => (%s)" % (host.name, host.address, command)
        chan = client.get_transport().open_session()
        chan.set_combine_stderr(True)
        chan.exec_command(command)
        dump_channel(chan)
        rv = chan.recv_exit_status()
        host_results[host.name] = rv
        chan.close()
        client.close()
        _summarize_exit_code(rv)
def place_code_on_workers():
    client = SSHClient()
    client.load_system_host_keys()
    for worker in worker_list.keys():
        client.connect(worker, username=USERNAME)
        scp = SCPClient(client.get_transport())
        scp.put(['worker.py'], remote_path='/home/{}/'.format(USERNAME))
        print("{color}[{}] {}{end_color}".format(
            worker, "Copied worker.py",
            color=worker_list[worker]['text_color'],
            end_color=Colors.ENDC))
        client.close()
Ejemplo n.º 5
0
Archivo: lmo.py Proyecto: mubix/lmo
def check_ssh(domain, port):
    client = SSHClient()
    vprint("Trying SSH to " + domain + " Port: " + str(port))
    try:
        client.connect(domain, port, timeout=1)
    except paramiko.ssh_exception.SSHException:
        pass
    except socket.timeout:
        print_closed("Failed! SSH to " + domain + " Port: " + str(port))
        return
    key = client.get_transport().get_remote_server_key()
    if key.get_base64(
    ) == "AAAAC3NzaC1lZDI1NTE5AAAAIIrfkWLMzwGKRliVsJOjm5OJRJo6AZt7NsqAH8bk9tYc":
        print_open("Success! SSH to " + domain + " Port: " + str(port))
def start_training():
    client_list = []
    for worker_name in worker_list.keys():
        client = SSHClient()
        client.load_system_host_keys()
        client.connect(worker_name, username=USERNAME)
        channel = client.get_transport().open_session()
        client_list.append((worker_name, client, channel, {'stdout': "", 'stderr': ""}))
    for worker_name, client, channel, outs in client_list:
        channel.exec_command('python3 worker.py')
    at_least_one_worker_still_running = True
    while at_least_one_worker_still_running:
        at_least_one_worker_still_running = False
        for worker_name, client, channel, outs in client_list:
            rl, wl, xl = select([channel], [], [], 0.0)
            if not channel.exit_status_ready():
                at_least_one_worker_still_running = True
            if len(rl) > 0:
                outs['stdout'] += channel.recv(RECV_BUFFER_SIZE).decode('utf-8')
                outs['stderr'] += channel.recv_stderr(RECV_BUFFER_SIZE).decode('utf-8')
                if channel.exit_status_ready():
                    if len(outs['stdout']) > 0:
                        print("{color}[{}] {}{end_color}".format(
                            worker_name, outs['stdout'],
                            color=worker_list[worker_name]['text_color'],
                            end_color=Colors.ENDC))
                        outs['stdout'] = ""
                    if len(outs['stderr']) > 0:
                        print("{color}[{}] {}{end_color}".format(
                            worker_name, outs['stderr'],
                            color=worker_list[worker_name]['text_color'],
                            end_color=Colors.ENDC))
                        outs['stderr'] = ""
                lines_stdout = outs['stdout'].split("\n")
                outs['stdout'] = lines_stdout[-1]
                lines_stderr = outs['stderr'].split("\n")
                outs['stderr'] = lines_stderr[-1]
                for line in lines_stdout[:-1]:
                    print("{color}[{}] {}{end_color}".format(
                        worker_name, line,
                        color=worker_list[worker_name]['text_color'],
                        end_color=Colors.ENDC))
                for line in lines_stderr[:-1]:
                    print("{color}[{}] {}{end_color}".format(
                        worker_name, line,
                        color=worker_list[worker_name]['warn_color'],
                        end_color=Colors.ENDC))
    for worker_name, client, channel, outs in client_list:
        client.close()
Ejemplo n.º 7
0
class AdvSSHClient():
    def __init__(self):
        self.channel = None  #ssh channel
        self.client = None
        self.CR = "\r\n"

    def open(self, host, port=22, username=None, password=None):
        self.client = SSHClient()
        self.client.set_missing_host_key_policy(AutoAddPolicy())
        self.client.connect(hostname=host,
                            port=port,
                            username=username,
                            password=password,
                            look_for_keys=False)
        t = self.client.get_transport()
        self.channel = t.open_session()
        self.channel.get_pty()
        self.channel.invoke_shell()

    def enable(self, enpassword):
        print self.doexec("en")
        sleep(WTIME)
        print self.doexec(enpassword)

    def read_all(self, tn, rbytes=RBYTES, waittime=WTIME):
        t = ""
        while select.select([tn], [], [], WTIME) == ([tn], [], []):
            t += tn.recv(rbytes)

        return t

    def doexec(self, cmdstring, eof=EOF, rbytes=RBYTES, waittime=WTIME):
        m = ""
        tn = self.channel
        tn.send(cmdstring + "\n")
        m = self.read_all(tn)

        if matchsg(m):
            tn.send(self.CR)
            logging.debug("____sending CR")
            while 1:
                t = self.read_all(tn)
                m += t
                if not matchsg(t):
                    logging.debug("_____get msg:" + t)
                    break
                tn.send(self.CR)
        return m
Ejemplo n.º 8
0
def run_ssh_command(self, command):
    key = task_key(self.request.id)
    redis.set(key, "")
    client = SSHClient()
    client.set_missing_host_key_policy(AutoAddPolicy)
    known_hosts = os.path.expanduser('~/.ssh/known_hosts')
    try:
        # So that we also save back the new host
        client.load_host_keys(known_hosts)
    except FileNotFoundError:
        if not os.path.exists(os.path.dirname(known_hosts)):
            os.mkdir(os.path.dirname(known_hosts))
        # so connect doesn't barf when trying to save
        open(known_hosts, "w").write("")
    if type(command) == list:
        commands = command
    else:
        commands = [command]
    for c in commands:
        if os.path.exists(keyfile):
            pkey = RSAKey.from_private_key_file(keyfile)
        else:
            pkey = None
        client.connect(settings.DOKKU_HOST,
                       port=settings.DOKKU_SSH_PORT,
                       username="******",
                       pkey=pkey,
                       allow_agent=False,
                       look_for_keys=False)
        transport = client.get_transport()
        channel = transport.open_session()
        channel.exec_command(c)
        while True:
            anything = False
            while channel.recv_ready():
                data = channel.recv(1024)
                handle_data(key, data)
                anything = True
            while channel.recv_stderr_ready():
                data = channel.recv_stderr(1024)
                handle_data(key, data)
                anything = True
            if not anything:
                if channel.exit_status_ready():
                    break
                time.sleep(0.1)
    return redis.get(key).decode("utf-8")
Ejemplo n.º 9
0
 def _inner(ssh_env, no_cache=False, *args, **kwargs):
     _rename_kwargs(ssh_env)
     key = (ssh_env['hostname'],
            ssh_env.get('port', 22),
            ssh_env['username'])
     with connect_lock:
         if key not in cache or no_cache:
             client = SSHClient()
             client.set_missing_host_key_policy(WarningPolicy())
             client.connect(**ssh_env)
             if no_cache:
                 return f(*args, **kwargs)
             proxy = NetstringMultiCtxProxy()
             transport = client.get_transport()
             port = transport.request_port_forward('127.0.0.1', 0,
                                                   proxy.handle)
             proxy.proxy_url = 'netstring://127.0.0.1:{0}'.format(port)
             cache[key] = (client, proxy)
         kwargs['client'], kwargs['proxy'] = cache[key]
     try:
         return f(*args, **kwargs)
     finally:
         if no_cache:
             client.close()
Ejemplo n.º 10
0
class SshClusterConnection(AbstractConnection):
    def __init__(self, girder_token, cluster):
        self._girder_token = girder_token
        self._cluster = cluster

    def _load_rsa_key(self, path, passphrase):
        return RSAKey.from_private_key_file(path, password=passphrase)

    def __enter__(self):
        self._client = SSHClient()
        self._client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

        username = parse('config.ssh.user').find(self._cluster)[0].value
        hostname = parse('config.host').find(self._cluster)[0].value
        passphrase \
            = parse('config.ssh.passphrase').find(self._cluster)
        if passphrase:
            passphrase = passphrase[0].value
        else:
            passphrase = None

        key_name = parse('config.ssh.key').find(self._cluster)[0].value
        key_path = os.path.join(cumulus.config.ssh.keyStore,
                                key_name)

        private_key = self._load_rsa_key(key_path, passphrase)

        self._client.connect(hostname=hostname, username=username,
                             pkey=private_key)

        return self

    def __exit__(self, type, value, traceback):
        self._client.close()

    def execute(self, command, ignore_exit_status=False, source_profile=True):
        if source_profile:
            command = 'source /etc/profile && %s' % command

        chan = self._client.get_transport().open_session()
        chan.exec_command(command)
        stdout = chan.makefile('r', -1)
        stderr = chan.makefile_stderr('r', -1)

        output = stdout.readlines() + stderr.readlines()
        exit_code = chan.recv_exit_status()
        if ignore_exit_status and exit_code != 0:
            raise SshCommandException(command, exit_code, output)

        return output

    @contextmanager
    def get(self, remote_path):
        sftp = None
        file = None
        try:
            sftp = self._client.get_transport().open_sftp_client()
            file = sftp.open(remote_path)
            yield file
        finally:
            if file:
                file.close()
                sftp.close()

    def isfile(self, remote_path):

        with self._client.get_transport().open_sftp_client() as sftp:
            try:
                s = sftp.stat(remote_path)
            except IOError:
                return False

            return stat.S_ISDIR(s.st_mode)

    def mkdir(self, remote_path, ignore_failure=False):
        with self._client.get_transport().open_sftp_client() as sftp:
            try:
                sftp.mkdir(remote_path)
            except IOError:
                if not ignore_failure:
                    raise

    def makedirs(self, remote_path):
        with self._client.get_transport().open_sftp_client() as sftp:
            current_path = ''
            if remote_path[0] == '/':
                current_path = '/'

            for path in remote_path.split('/'):
                if not path:
                    continue
                current_path = os.path.join(current_path, path)
                try:
                    sftp.listdir(current_path)
                except IOError:
                    sftp.mkdir(current_path)

    def put(self, stream, remote_path):
        with self._client.get_transport().open_sftp_client() as sftp:
            sftp.putfo(stream, remote_path)

    def stat(self, remote_path):
        with self._client.get_transport().open_sftp_client() as sftp:
            return sftp.stat(remote_path)

    def remove(self, remote_path):
        with self._client.get_transport().open_sftp_client() as sftp:
            return sftp.remove(remote_path)

    def list(self, remote_path):
        with self._client.get_transport().open_sftp_client() as sftp:
            for path in sftp.listdir_iter(remote_path):
                yield {
                    'name': path.filename,
                    'user': path.st_uid,
                    'group': path.st_gid,
                    'mode': path.st_mode,
                    # For now just pass mtime through
                    'date': path.st_mtime,
                    'size': path.st_size
                }
Ejemplo n.º 11
0
class USIEngine:
    def __init__(self,
                 name,
                 host,
                 engine_path,
                 nodes=None,
                 multiPV=1,
                 threads=1,
                 delay=0,
                 delay2=0):
        self.name = name
        self.nodes = nodes
        self.multiPV = multiPV
        self.quit_event = threading.Event()

        self.client = SSHClient()
        self.client.set_missing_host_key_policy(paramiko.client.WarningPolicy)
        #self.client.load_system_host_keys()
        keys = self.client.get_host_keys()
        keys.clear()
        self.client.connect(host)
        dirname = os.path.dirname(engine_path)
        command = f'cd {dirname} && {engine_path}'
        self.stdin, self.stdout, self.stderr = \
                self.client.exec_command(command, bufsize=0)

        self.queue = queue.Queue()
        self.watcher_thread = threading.Thread(target=self.stream_watcher,
                                               name='engine_watcher',
                                               args=(self.stdout, ))
        self.watcher_thread.start()
        self.pvs = [[]] * multiPV
        self.status = 'wait'
        self.position = 'startpos'

        self.send('usi')
        self.wait_for('usiok')
        self.set_option('Threads', threads)
        self.set_option('USI_Ponder', 'false')
        self.set_option('NetworkDelay', delay)
        self.set_option('NetworkDelay2', delay2)
        self.set_option('MultiPV', multiPV)
        if nodes:
            self.set_option('NodesLimit', nodes)
        #self.send('isready')
        #self.wait_for('readyok')

    def stream_watcher(self, stream):
        # for line in iter(stream.readline, b''):
        prog = re.compile('.*score cp (-?\d+) (?:multipv (\d+))? .*pv (.+)$')
        #for line in iter(stream.readline, b''):
        while (not self.quit_event.isSet()) and (not stream.closed):
            line = stream.readline().strip()
            if len(line):
                logging.debug(f'{self.name} > {line}')
                print(f'info string {self.name} > {line}', flush=True)
                match = prog.match(line)
                if match:
                    logging.debug(f'match: {match.group(1, 2, 3)}')
                    if match.group(2):
                        # multi PV
                        num = int(match.group(2)) - 1
                    else:
                        # single PV
                        num = 0
                    logging.debug(f'{self.name}: Found score of pv {num}')
                    self.pvs[num] = [int(match.group(1)), match.group(3)]

                # bestmove
                if line.startswith('bestmove'):
                    self.status = 'wait'

                self.queue.put(line)
        logging.debug(f'{self.name}: terminating the engine watcher thread')

    def set_option(self, name, value):
        self.send(f'setoption name {name} value {value}')

    def __del__(self):
        pass
        #self.terminate()

    def terminate(self):
        self.stop()
        self.quit_event.set()
        self.send('usi')
        self.watcher_thread.join(1)
        self.send('quit')
        self.status = 'quit'
        #self.client.close()

    def send(self, command):
        logging.debug(f'sending {command} to {self.name}')
        print(f'info string sending {command} to {self.name}', flush=True)
        self.stdin.write((command + '\n').encode('utf-8'))
        self.stdin.flush()

    def wait_for(self, command):
        logging.debug(f'{self.name}: waiting for {command}')
        lines = ""
        while self.client.get_transport().is_active():
            line = self.queue.get()
            lines += f'{line}\n'
            if (line == command):
                logging.debug(f'{self.name}: found {command}')
                self.status = 'wait'
                return lines

    def wait_for_bestmove(self):
        logging.debug(f'{self.name}: waiting for bestmove...')
        infostr(f'{self.name}: waiting for bestmove...')
        while self.client.get_transport().is_active():
            line = self.queue.get()
            if (line.startswith('bestmove')):
                logging.debug(f'{self.name}: found bestmove')
                infostr(f'{self.name}: found bestmove')
                bestmove = line[9:].split()[0].strip()
                self.status = 'wait'
                return bestmove

    def set_position(self, pos):
        self.position = pos
        self.send(f'position {pos}')

    def clear_queue(self):
        while True:
            try:
                line = self.queue.get_nowait()
                print(f'info string {self.name}: clearing queue: {line}',
                      flush=True)
            except queue.Empty:
                break

    def ponder(self, command):
        infostr(f'{self.name}: in ponder()')
        self.go_command = command
        if 'ponder' not in command:
            command = command.replace('go', 'go ponder')
        self.send(command)
        self.status = 'ponder'
        infostr(f'{self.name}: end of ponder()')

    def stop(self):
        infostr(f'{self.name}: in stop()')
        if self.status in ['go', 'ponder']:
            self.send('stop')
            self.wait_for_bestmove()
            self.status = 'wait'
Ejemplo n.º 12
0
class Connection(Context):
    """
    A connection to an SSH daemon, with methods for commands and file transfer.

    **Basics**

    This class inherits from Invoke's `~invoke.context.Context`, as it is a
    context within which commands, tasks etc can operate. It also encapsulates
    a Paramiko `~paramiko.client.SSHClient` instance, performing useful high
    level operations with that `~paramiko.client.SSHClient` and
    `~paramiko.channel.Channel` instances generated from it.

    .. _connect_kwargs:

    .. note::
        Many SSH specific options -- such as specifying private keys and
        passphrases, timeouts, disabling SSH agents, etc -- are handled
        directly by Paramiko and should be specified via the
        :ref:`connect_kwargs argument <connect_kwargs-arg>` of the constructor.

    **Lifecycle**

    `.Connection` has a basic "`create <__init__>`, `connect/open <open>`, `do
    work <run>`, `disconnect/close <close>`" lifecycle:

    - `Instantiation <__init__>` imprints the object with its connection
      parameters (but does **not** actually initiate the network connection).

        - An alternate constructor exists for users :ref:`upgrading piecemeal
          from Fabric 1 <from-v1>`: `from_v1`

    - Methods like `run`, `get` etc automatically trigger a call to
      `open` if the connection is not active; users may of course call `open`
      manually if desired.
    - Connections do not always need to be explicitly closed; much of the
      time, Paramiko's garbage collection hooks or Python's own shutdown
      sequence will take care of things. **However**, should you encounter edge
      cases (for example, sessions hanging on exit) it's helpful to explicitly
      close connections when you're done with them.

      This can be accomplished by manually calling `close`, or by using the
      object as a contextmanager::

        with Connection('host') as c:
            c.run('command')
            c.put('file')

    .. note::
        This class rebinds `invoke.context.Context.run` to `.local` so both
        remote and local command execution can coexist.

    **Configuration**

    Most `.Connection` parameters honor :doc:`Invoke-style configuration
    </concepts/configuration>` as well as any applicable :ref:`SSH config file
    directives <connection-ssh-config>`. For example, to end up with a
    connection to ``admin@myhost``, one could:

    - Use any built-in config mechanism, such as ``/etc/fabric.yml``,
      ``~/.fabric.json``, collection-driven configuration, env vars, etc,
      stating ``user: admin`` (or ``{"user": "******"}``, depending on config
      format.) Then ``Connection('myhost')`` would implicitly have a ``user``
      of ``admin``.
    - Use an SSH config file containing ``User admin`` within any applicable
      ``Host`` header (``Host myhost``, ``Host *``, etc.) Again,
      ``Connection('myhost')`` will default to an ``admin`` user.
    - Leverage host-parameter shorthand (described in `.Config.__init__`), i.e.
      ``Connection('admin@myhost')``.
    - Give the parameter directly: ``Connection('myhost', user='******')``.

    The same applies to agent forwarding, gateways, and so forth.

    .. versionadded:: 2.0
    """

    # NOTE: these are initialized here to hint to invoke.Config.__setattr__
    # that they should be treated as real attributes instead of config proxies.
    # (Additionally, we're doing this instead of using invoke.Config._set() so
    # we can take advantage of Sphinx's attribute-doc-comment static analysis.)
    # Once an instance is created, these values will usually be non-None
    # because they default to the default config values.
    host = None
    original_host = None
    user = None
    port = None
    ssh_config = None
    gateway = None
    forward_agent = None
    connect_timeout = None
    connect_kwargs = None
    client = None
    transport = None
    _sftp = None
    _agent_handler = None
    default_host_key_policy = AutoAddPolicy

    @classmethod
    def from_v1(cls, env, **kwargs):
        """
        Alternate constructor which uses Fabric 1's ``env`` dict for settings.

        All keyword arguments besides ``env`` are passed unmolested into the
        primary constructor.

        .. warning::
            Because your own config overrides will win over data from ``env``,
            make sure you only set values you *intend* to change from your v1
            environment!

        For details on exactly which ``env`` vars are imported and what they
        become in the new API, please see :ref:`v1-env-var-imports`.

        :param env:
            An explicit Fabric 1 ``env`` dict (technically, any
            ``fabric.utils._AttributeDict`` instance should work) to pull
            configuration from.

        .. versionadded:: 2.4
        """
        # TODO: import fabric.state.env (need good way to test it first...)
        # TODO: how to handle somebody accidentally calling this in a process
        # where 'fabric' is fabric 2, and there's no fabric 1? Probably just a
        # re-raise of ImportError??
        # Our only requirement is a non-empty host_string
        if not env.host_string:
            raise InvalidV1Env(
                "Supplied v1 env has an empty `host_string` value! Please make sure you're calling Connection.from_v1 within a connected Fabric 1 session."  # noqa
            )
        # TODO: detect collisions with kwargs & except instead of overwriting?
        # (More Zen of Python compliant, but also, effort, and also, makes it
        # harder for users to intentionally overwrite!)
        connect_kwargs = kwargs.setdefault("connect_kwargs", {})
        kwargs.setdefault("host", env.host_string)
        shorthand = derive_shorthand(env.host_string)
        # TODO: don't we need to do the below skipping for user too?
        kwargs.setdefault("user", env.user)
        # Skip port if host string seemed to have it; otherwise we hit our own
        # ambiguity clause in __init__. v1 would also have been doing this
        # anyways (host string wins over other settings).
        if not shorthand["port"]:
            # Run port through int(); v1 inexplicably has a string default...
            kwargs.setdefault("port", int(env.port))
        # key_filename defaults to None in v1, but in v2, we expect it to be
        # either unset, or set to a list. Thus, we only pull it over if it is
        # not None.
        if env.key_filename is not None:
            connect_kwargs.setdefault("key_filename", env.key_filename)
        # Obtain config values, if not given, from its own from_v1
        # NOTE: not using setdefault as we truly only want to call
        # Config.from_v1 when necessary.
        if "config" not in kwargs:
            kwargs["config"] = Config.from_v1(env)
        return cls(**kwargs)

    # TODO: should "reopening" an existing Connection object that has been
    # closed, be allowed? (See e.g. how v1 detects closed/semi-closed
    # connections & nukes them before creating a new client to the same host.)
    # TODO: push some of this into paramiko.client.Client? e.g. expand what
    # Client.exec_command does, it already allows configuring a subset of what
    # we do / will eventually do / did in 1.x. It's silly to have to do
    # .get_transport().open_session().
    def __init__(
        self,
        host,
        user=None,
        port=None,
        config=None,
        gateway=None,
        forward_agent=None,
        connect_timeout=None,
        connect_kwargs=None,
        inline_ssh_env=None,
    ):
        """
        Set up a new object representing a server connection.

        :param str host:
            the hostname (or IP address) of this connection.

            May include shorthand for the ``user`` and/or ``port`` parameters,
            of the form ``user@host``, ``host:port``, or ``user@host:port``.

            .. note::
                Due to ambiguity, IPv6 host addresses are incompatible with the
                ``host:port`` shorthand (though ``user@host`` will still work
                OK). In other words, the presence of >1 ``:`` character will
                prevent any attempt to derive a shorthand port number; use the
                explicit ``port`` parameter instead.

            .. note::
                If ``host`` matches a ``Host`` clause in loaded SSH config
                data, and that ``Host`` clause contains a ``Hostname``
                directive, the resulting `.Connection` object will behave as if
                ``host`` is equal to that ``Hostname`` value.

                In all cases, the original value of ``host`` is preserved as
                the ``original_host`` attribute.

                Thus, given SSH config like so::

                    Host myalias
                        Hostname realhostname

                a call like ``Connection(host='myalias')`` will result in an
                object whose ``host`` attribute is ``realhostname``, and whose
                ``original_host`` attribute is ``myalias``.

        :param str user:
            the login user for the remote connection. Defaults to
            ``config.user``.

        :param int port:
            the remote port. Defaults to ``config.port``.

        :param config:
            configuration settings to use when executing methods on this
            `.Connection` (e.g. default SSH port and so forth).

            Should be a `.Config` or an `invoke.config.Config`
            (which will be turned into a `.Config`).

            Default is an anonymous `.Config` object.

        :param gateway:
            An object to use as a proxy or gateway for this connection.

            This parameter accepts one of the following:

            - another `.Connection` (for a ``ProxyJump`` style gateway);
            - a shell command string (for a ``ProxyCommand`` style style
              gateway).

            Default: ``None``, meaning no gatewaying will occur (unless
            otherwise configured; if one wants to override a configured gateway
            at runtime, specify ``gateway=False``.)

            .. seealso:: :ref:`ssh-gateways`

        :param bool forward_agent:
            Whether to enable SSH agent forwarding.

            Default: ``config.forward_agent``.

        :param int connect_timeout:
            Connection timeout, in seconds.

            Default: ``config.timeouts.connect``.

        .. _connect_kwargs-arg:

        :param dict connect_kwargs:
            Keyword arguments handed verbatim to
            `SSHClient.connect <paramiko.client.SSHClient.connect>` (when
            `.open` is called).

            `.Connection` tries not to grow additional settings/kwargs of its
            own unless it is adding value of some kind; thus,
            ``connect_kwargs`` is currently the right place to hand in paramiko
            connection parameters such as ``pkey`` or ``key_filename``. For
            example::

                c = Connection(
                    host="hostname",
                    user="******",
                    connect_kwargs={
                        "key_filename": "/home/myuser/.ssh/private.key",
                    },
                )

            Default: ``config.connect_kwargs``.

        :param bool inline_ssh_env:
            Whether to send environment variables "inline" as prefixes in front
            of command strings (``export VARNAME=value && mycommand here``),
            instead of trying to submit them through the SSH protocol itself
            (which is the default behavior). This is necessary if the remote
            server has a restricted ``AcceptEnv`` setting (which is the common
            default).

            The default value is the value of the ``inline_ssh_env``
            :ref:`configuration value <default-values>` (which itself defaults
            to ``False``).

            .. warning::
                This functionality does **not** currently perform any shell
                escaping on your behalf! Be careful when using nontrivial
                values, and note that you can put in your own quoting,
                backslashing etc if desired.

                Consider using a different approach (such as actual
                remote shell scripts) if you run into too many issues here.

            .. note::
                When serializing into prefixed ``FOO=bar`` format, we apply the
                builtin `sorted` function to the env dictionary's keys, to
                remove what would otherwise be ambiguous/arbitrary ordering.

            .. note::
                This setting has no bearing on *local* shell commands; it only
                affects remote commands, and thus, methods like `.run` and
                `.sudo`.

        :raises ValueError:
            if user or port values are given via both ``host`` shorthand *and*
            their own arguments. (We `refuse the temptation to guess`_).

        .. _refuse the temptation to guess:
            http://zen-of-python.info/
            in-the-face-of-ambiguity-refuse-the-temptation-to-guess.html#12

        .. versionchanged:: 2.3
            Added the ``inline_ssh_env`` parameter.
        """
        # NOTE: parent __init__ sets self._config; for now we simply overwrite
        # that below. If it's somehow problematic we would want to break parent
        # __init__ up in a manner that is more cleanly overrideable.
        super(Connection, self).__init__(config=config)

        #: The .Config object referenced when handling default values (for e.g.
        #: user or port, when not explicitly given) or deciding how to behave.
        if config is None:
            config = Config()
        # Handle 'vanilla' Invoke config objects, which need cloning 'into' one
        # of our own Configs (which grants the new defaults, etc, while not
        # squashing them if the Invoke-level config already accounted for them)
        elif not isinstance(config, Config):
            config = config.clone(into=Config)
        self._set(_config=config)
        # TODO: when/how to run load_files, merge, load_shell_env, etc?
        # TODO: i.e. what is the lib use case here (and honestly in invoke too)

        shorthand = self.derive_shorthand(host)
        host = shorthand["host"]
        err = "You supplied the {} via both shorthand and kwarg! Please pick one."  # noqa
        if shorthand["user"] is not None:
            if user is not None:
                raise ValueError(err.format("user"))
            user = shorthand["user"]
        if shorthand["port"] is not None:
            if port is not None:
                raise ValueError(err.format("port"))
            port = shorthand["port"]

        # NOTE: we load SSH config data as early as possible as it has
        # potential to affect nearly every other attribute.
        #: The per-host SSH config data, if any. (See :ref:`ssh-config`.)
        self.ssh_config = self.config.base_ssh_config.lookup(host)

        self.original_host = host
        #: The hostname of the target server.
        self.host = host
        if "hostname" in self.ssh_config:
            # TODO: log that this occurred?
            self.host = self.ssh_config["hostname"]

        #: The username this connection will use to connect to the remote end.
        self.user = user or self.ssh_config.get("user", self.config.user)
        # TODO: is it _ever_ possible to give an empty user value (e.g.
        # user='')? E.g. do some SSH server specs allow for that?

        #: The network port to connect on.
        self.port = port or int(self.ssh_config.get("port", self.config.port))

        # Gateway/proxy/bastion/jump setting: non-None values - string,
        # Connection, even eg False - get set directly; None triggers seek in
        # config/ssh_config
        #: The gateway `.Connection` or ``ProxyCommand`` string to be used,
        #: if any.
        self.gateway = gateway if gateway is not None else self.get_gateway()
        # NOTE: we use string above, vs ProxyCommand obj, to avoid spinning up
        # the ProxyCommand subprocess at init time, vs open() time.
        # TODO: make paramiko.proxy.ProxyCommand lazy instead?

        if forward_agent is None:
            # Default to config...
            forward_agent = self.config.forward_agent
            # But if ssh_config is present, it wins
            if "forwardagent" in self.ssh_config:
                # TODO: SSHConfig really, seriously needs some love here, god
                map_ = {"yes": True, "no": False}
                forward_agent = map_[self.ssh_config["forwardagent"]]
        #: Whether agent forwarding is enabled.
        self.forward_agent = forward_agent

        if connect_timeout is None:
            connect_timeout = self.ssh_config.get("connecttimeout",
                                                  self.config.timeouts.connect)
        if connect_timeout is not None:
            connect_timeout = int(connect_timeout)
        #: Connection timeout
        self.connect_timeout = connect_timeout

        #: Keyword arguments given to `paramiko.client.SSHClient.connect` when
        #: `open` is called.
        self.connect_kwargs = self.resolve_connect_kwargs(connect_kwargs)

        #: The `paramiko.client.SSHClient` instance this connection wraps.
        self.client = SSHClient()
        self.setup_ssh_client()

        #: A convenience handle onto the return value of
        #: ``self.client.get_transport()``.
        self.transport = None

        if inline_ssh_env is None:
            inline_ssh_env = self.config.inline_ssh_env
        #: Whether to construct remote command lines with env vars prefixed
        #: inline.
        self.inline_ssh_env = inline_ssh_env

    def setup_ssh_client(self):
        if self.default_host_key_policy is not None:
            logging.debug("host key policy: %s", self.default_host_key_policy)
            self.client.set_missing_host_key_policy(
                self.default_host_key_policy())
        known_hosts = self.ssh_config.get("UserKnownHostsFile".lower(),
                                          "~/.ssh/known_hosts")
        logging.debug("loading host keys from %s", known_hosts)
        # multiple keys, seperated by whitespace, can be provided
        for filename in [os.path.expanduser(f) for f in known_hosts.split()]:
            if os.path.exists(filename):
                self.client.load_host_keys(filename)

    def resolve_connect_kwargs(self, connect_kwargs):
        # Grab connect_kwargs from config if not explicitly given.
        if connect_kwargs is None:
            # TODO: is it better to pre-empt conflicts w/ manually-handled
            # connect() kwargs (hostname, username, etc) here or in open()?
            # We're doing open() for now in case e.g. someone manually modifies
            # .connect_kwargs attributewise, but otherwise it feels better to
            # do it early instead of late.
            connect_kwargs = self.config.connect_kwargs
        # Special case: key_filename gets merged instead of overridden.
        # TODO: probably want some sorta smart merging generally, special cases
        # are bad.
        elif "key_filename" in self.config.connect_kwargs:
            kwarg_val = connect_kwargs.get("key_filename", [])
            conf_val = self.config.connect_kwargs["key_filename"]
            # Config value comes before kwarg value (because it may contain
            # CLI flag value.)
            connect_kwargs["key_filename"] = conf_val + kwarg_val

        # SSH config identityfile values come last in the key_filename
        # 'hierarchy'.
        if "identityfile" in self.ssh_config:
            connect_kwargs.setdefault("key_filename", [])
            connect_kwargs["key_filename"].extend(
                self.ssh_config["identityfile"])

        return connect_kwargs

    def get_gateway(self):
        # SSH config wins over Invoke-style config
        if "proxyjump" in self.ssh_config:
            # Reverse hop1,hop2,hop3 style ProxyJump directive so we start
            # with the final (itself non-gatewayed) hop and work up to
            # the front (actual, supplied as our own gateway) hop
            hops = reversed(self.ssh_config["proxyjump"].split(","))
            prev_gw = None
            for hop in hops:
                # Short-circuit if we appear to be our own proxy, which would
                # be a RecursionError. Implies SSH config wildcards.
                # TODO: in an ideal world we'd check user/port too in case they
                # differ, but...seriously? They can file a PR with those extra
                # half dozen test cases in play, E_NOTIME
                if self.derive_shorthand(hop)["host"] == self.host:
                    return None
                # Happily, ProxyJump uses identical format to our host
                # shorthand...
                kwargs = dict(config=self.config.clone())
                if prev_gw is not None:
                    kwargs["gateway"] = prev_gw
                cxn = Connection(hop, **kwargs)
                prev_gw = cxn
            return prev_gw
        elif "proxycommand" in self.ssh_config:
            # Just a string, which we interpret as a proxy command..
            return self.ssh_config["proxycommand"]
        # Fallback: config value (may be None).
        return self.config.gateway

    def __repr__(self):
        # Host comes first as it's the most common differentiator by far
        bits = [("host", self.host)]
        # TODO: maybe always show user regardless? Explicit is good...
        if self.user != self.config.user:
            bits.append(("user", self.user))
        # TODO: harder to make case for 'always show port'; maybe if it's
        # non-22 (even if config has overridden the local default)?
        if self.port != self.config.port:
            bits.append(("port", self.port))
        # NOTE: sometimes self.gateway may be eg False if someone wants to
        # explicitly override a configured non-None value (as otherwise it's
        # impossible for __init__ to tell if a None means "nothing given" or
        # "seriously please no gatewaying". So, this must always be a vanilla
        # truth test and not eg "is not None".
        if self.gateway:
            # Displaying type because gw params would probs be too verbose
            val = "proxyjump"
            if isinstance(self.gateway, string_types):
                val = "proxycommand"
            bits.append(("gw", val))
        return "<Connection {}>".format(" ".join("{}={}".format(*x)
                                                 for x in bits))

    def _identity(self):
        # TODO: consider including gateway and maybe even other init kwargs?
        # Whether two cxns w/ same user/host/port but different
        # gateway/keys/etc, should be considered "the same", is unclear.
        return (self.host, self.user, self.port)

    def __eq__(self, other):
        if not isinstance(other, Connection):
            return False
        return self._identity() == other._identity()

    def __lt__(self, other):
        return self._identity() < other._identity()

    def __hash__(self):
        # NOTE: this departs from Context/DataProxy, which is not usefully
        # hashable.
        return hash(self._identity())

    def derive_shorthand(self, host_string):
        # NOTE: used to be defined inline; preserving API call for both
        # backwards compatibility and because it seems plausible we may want to
        # modify behavior later, using eg config or other attributes.
        return derive_shorthand(host_string)

    @property
    def is_connected(self):
        """
        Whether or not this connection is actually open.

        .. versionadded:: 2.0
        """
        return self.transport.active if self.transport else False

    def open(self):
        """
        Initiate an SSH connection to the host/port this object is bound to.

        This may include activating the configured gateway connection, if one
        is set.

        Also saves a handle to the now-set Transport object for easier access.

        Various connect-time settings (and/or their corresponding :ref:`SSH
        config options <ssh-config>`) are utilized here in the call to
        `SSHClient.connect <paramiko.client.SSHClient.connect>`. (For details,
        see :doc:`the configuration docs </concepts/configuration>`.)

        .. versionadded:: 2.0
        """
        # Short-circuit
        if self.is_connected:
            return
        err = "Refusing to be ambiguous: connect() kwarg '{}' was given both via regular arg and via connect_kwargs!"  # noqa
        # These may not be given, period
        for key in """
            hostname
            port
            username
        """.split():
            if key in self.connect_kwargs:
                raise ValueError(err.format(key))
        # These may be given one way or the other, but not both
        if ("timeout" in self.connect_kwargs
                and self.connect_timeout is not None):
            raise ValueError(err.format("timeout"))
        # No conflicts -> merge 'em together
        kwargs = dict(
            self.connect_kwargs,
            username=self.user,
            hostname=self.host,
            port=self.port,
        )
        if self.gateway:
            kwargs["sock"] = self.open_gateway()
        if self.connect_timeout:
            kwargs["timeout"] = self.connect_timeout
        # Strip out empty defaults for less noisy debugging
        if "key_filename" in kwargs and not kwargs["key_filename"]:
            del kwargs["key_filename"]
        # Actually connect!
        self.client.connect(**kwargs)
        self.transport = self.client.get_transport()

    def open_gateway(self):
        """
        Obtain a socket-like object from `gateway`.

        :returns:
            A ``direct-tcpip`` `paramiko.channel.Channel`, if `gateway` was a
            `.Connection`; or a `~paramiko.proxy.ProxyCommand`, if `gateway`
            was a string.

        .. versionadded:: 2.0
        """
        # ProxyCommand is faster to set up, so do it first.
        if isinstance(self.gateway, string_types):
            # Leverage a dummy SSHConfig to ensure %h/%p/etc are parsed.
            # TODO: use real SSH config once loading one properly is
            # implemented.
            ssh_conf = SSHConfig()
            dummy = "Host {}\n    ProxyCommand {}"
            ssh_conf.parse(StringIO(dummy.format(self.host, self.gateway)))
            return ProxyCommand(ssh_conf.lookup(self.host)["proxycommand"])
        # Handle inner-Connection gateway type here.
        # TODO: logging
        self.gateway.open()
        # TODO: expose the opened channel itself as an attribute? (another
        # possible argument for separating the two gateway types...) e.g. if
        # someone wanted to piggyback on it for other same-interpreter socket
        # needs...
        # TODO: and the inverse? allow users to supply their own socket/like
        # object they got via $WHEREEVER?
        # TODO: how best to expose timeout param? reuse general connection
        # timeout from config?
        return self.gateway.transport.open_channel(
            kind="direct-tcpip",
            dest_addr=(self.host, int(self.port)),
            # NOTE: src_addr needs to be 'empty but not None' values to
            # correctly encode into a network message. Theoretically Paramiko
            # could auto-interpret None sometime & save us the trouble.
            src_addr=("", 0),
        )

    def close(self):
        """
        Terminate the network connection to the remote end, if open.

        If no connection is open, this method does nothing.

        .. versionadded:: 2.0
        """
        if self.is_connected:
            self.client.close()
            if self.forward_agent and self._agent_handler is not None:
                self._agent_handler.close()

    def __enter__(self):
        return self

    def __exit__(self, *exc):
        self.close()

    @opens
    def create_session(self):
        channel = self.transport.open_session()
        if self.forward_agent:
            self._agent_handler = AgentRequestHandler(channel)
        return channel

    def _remote_runner(self):
        return self.config.runners.remote(self, inline_env=self.inline_ssh_env)

    @opens
    def run(self, command, **kwargs):
        """
        Execute a shell command on the remote end of this connection.

        This method wraps an SSH-capable implementation of
        `invoke.runners.Runner.run`; see its documentation for details.

        .. warning::
            There are a few spots where Fabric departs from Invoke's default
            settings/behaviors; they are documented under
            `.Config.global_defaults`.

        .. versionadded:: 2.0
        """
        return self._run(self._remote_runner(), command, **kwargs)

    @opens
    def sudo(self, command, **kwargs):
        """
        Execute a shell command, via ``sudo``, on the remote end.

        This method is identical to `invoke.context.Context.sudo` in every way,
        except in that -- like `run` -- it honors per-host/per-connection
        configuration overrides in addition to the generic/global ones. Thus,
        for example, per-host sudo passwords may be configured.

        .. versionadded:: 2.0
        """
        return self._sudo(self._remote_runner(), command, **kwargs)

    def local(self, *args, **kwargs):
        """
        Execute a shell command on the local system.

        This method is effectively a wrapper of `invoke.run`; see its docs for
        details and call signature.

        .. versionadded:: 2.0
        """
        # Superclass run() uses runners.local, so we can literally just call it
        # straight.
        return super(Connection, self).run(*args, **kwargs)

    @opens
    def sftp(self):
        """
        Return a `~paramiko.sftp_client.SFTPClient` object.

        If called more than one time, memoizes the first result; thus, any
        given `.Connection` instance will only ever have a single SFTP client,
        and state (such as that managed by
        `~paramiko.sftp_client.SFTPClient.chdir`) will be preserved.

        .. versionadded:: 2.0
        """
        if self._sftp is None:
            self._sftp = self.client.open_sftp()
        return self._sftp

    def get(self, *args, **kwargs):
        """
        Get a remote file to the local filesystem or file-like object.

        Simply a wrapper for `.Transfer.get`. Please see its documentation for
        all details.

        .. versionadded:: 2.0
        """
        return Transfer(self).get(*args, **kwargs)

    def put(self, *args, **kwargs):
        """
        Put a remote file (or file-like object) to the remote filesystem.

        Simply a wrapper for `.Transfer.put`. Please see its documentation for
        all details.

        .. versionadded:: 2.0
        """
        return Transfer(self).put(*args, **kwargs)

    # TODO: yield the socket for advanced users? Other advanced use cases
    # (perhaps factor out socket creation itself)?
    # TODO: probably push some of this down into Paramiko
    @contextmanager
    @opens
    def forward_local(
        self,
        local_port,
        remote_port=None,
        remote_host="localhost",
        local_host="localhost",
    ):
        """
        Open a tunnel connecting ``local_port`` to the server's environment.

        For example, say you want to connect to a remote PostgreSQL database
        which is locked down and only accessible via the system it's running
        on. You have SSH access to this server, so you can temporarily make
        port 5432 on your local system act like port 5432 on the server::

            import psycopg2
            from fabric import Connection

            with Connection('my-db-server').forward_local(5432):
                db = psycopg2.connect(
                    host='localhost', port=5432, database='mydb'
                )
                # Do things with 'db' here

        This method is analogous to using the ``-L`` option of OpenSSH's
        ``ssh`` program.

        :param int local_port: The local port number on which to listen.

        :param int remote_port:
            The remote port number. Defaults to the same value as
            ``local_port``.

        :param str local_host:
            The local hostname/interface on which to listen. Default:
            ``localhost``.

        :param str remote_host:
            The remote hostname serving the forwarded remote port. Default:
            ``localhost`` (i.e., the host this `.Connection` is connected to.)

        :returns:
            Nothing; this method is only useful as a context manager affecting
            local operating system state.

        .. versionadded:: 2.0
        """
        if not remote_port:
            remote_port = local_port

        # TunnelManager does all of the work, sitting in the background (so we
        # can yield) and spawning threads every time somebody connects to our
        # local port.
        finished = Event()
        manager = TunnelManager(
            local_port=local_port,
            local_host=local_host,
            remote_port=remote_port,
            remote_host=remote_host,
            # TODO: not a huge fan of handing in our transport, but...?
            transport=self.transport,
            finished=finished,
        )
        manager.start()

        # Return control to caller now that things ought to be operational
        try:
            yield
        # Teardown once user exits block
        finally:
            # Signal to manager that it should close all open tunnels
            finished.set()
            # Then wait for it to do so
            manager.join()
            # Raise threading errors from within the manager, which would be
            # one of:
            # - an inner ThreadException, which was created by the manager on
            # behalf of its Tunnels; this gets directly raised.
            # - some other exception, which would thus have occurred in the
            # manager itself; we wrap this in a new ThreadException.
            # NOTE: in these cases, some of the metadata tracking in
            # ExceptionHandlingThread/ExceptionWrapper/ThreadException (which
            # is useful when dealing with multiple nearly-identical sibling IO
            # threads) is superfluous, but it doesn't feel worth breaking
            # things up further; we just ignore it for now.
            wrapper = manager.exception()
            if wrapper is not None:
                if wrapper.type is ThreadException:
                    raise wrapper.value
                else:
                    raise ThreadException([wrapper])

            # TODO: cancel port forward on transport? Does that even make sense
            # here (where we used direct-tcpip) vs the opposite method (which
            # is what uses forward-tcpip)?

    # TODO: probably push some of this down into Paramiko
    @contextmanager
    @opens
    def forward_remote(
        self,
        remote_port,
        local_port=None,
        remote_host="127.0.0.1",
        local_host="localhost",
    ):
        """
        Open a tunnel connecting ``remote_port`` to the local environment.

        For example, say you're running a daemon in development mode on your
        workstation at port 8080, and want to funnel traffic to it from a
        production or staging environment.

        In most situations this isn't possible as your office/home network
        probably blocks inbound traffic. But you have SSH access to this
        server, so you can temporarily make port 8080 on that server act like
        port 8080 on your workstation::

            from fabric import Connection

            c = Connection('my-remote-server')
            with c.forward_remote(8080):
                c.run("remote-data-writer --port 8080")
                # Assuming remote-data-writer runs until interrupted, this will
                # stay open until you Ctrl-C...

        This method is analogous to using the ``-R`` option of OpenSSH's
        ``ssh`` program.

        :param int remote_port: The remote port number on which to listen.

        :param int local_port:
            The local port number. Defaults to the same value as
            ``remote_port``.

        :param str local_host:
            The local hostname/interface the forwarded connection talks to.
            Default: ``localhost``.

        :param str remote_host:
            The remote interface address to listen on when forwarding
            connections. Default: ``127.0.0.1`` (i.e. only listen on the remote
            localhost).

        :returns:
            Nothing; this method is only useful as a context manager affecting
            local operating system state.

        .. versionadded:: 2.0
        """
        if not local_port:
            local_port = remote_port
        # Callback executes on each connection to the remote port and is given
        # a Channel hooked up to said port. (We don't actually care about the
        # source/dest host/port pairs at all; only whether the channel has data
        # to read and suchlike.)
        # We then pair that channel with a new 'outbound' socket connection to
        # the local host/port being forwarded, in a new Tunnel.
        # That Tunnel is then added to a shared data structure so we can track
        # & close them during shutdown.
        #
        # TODO: this approach is less than ideal because we have to share state
        # between ourselves & the callback handed into the transport's own
        # thread handling (which is roughly analogous to our self-controlled
        # TunnelManager for local forwarding). See if we can use more of
        # Paramiko's API (or improve it and then do so) so that isn't
        # necessary.
        tunnels = []

        def callback(channel, src_addr_tup, dst_addr_tup):
            sock = socket.socket()
            # TODO: handle connection failure such that channel, etc get closed
            sock.connect((local_host, local_port))
            # TODO: we don't actually need to generate the Events at our level,
            # do we? Just let Tunnel.__init__ do it; all we do is "press its
            # button" on shutdown...
            tunnel = Tunnel(channel=channel, sock=sock, finished=Event())
            tunnel.start()
            # Communication between ourselves & the Paramiko handling subthread
            tunnels.append(tunnel)

        # Ask Paramiko (really, the remote sshd) to call our callback whenever
        # connections are established on the remote iface/port.
        # transport.request_port_forward(remote_host, remote_port, callback)
        try:
            self.transport.request_port_forward(address=remote_host,
                                                port=remote_port,
                                                handler=callback)
            yield
        finally:
            # TODO: see above re: lack of a TunnelManager
            # TODO: and/or also refactor with TunnelManager re: shutdown logic.
            # E.g. maybe have a non-thread TunnelManager-alike with a method
            # that acts as the callback? At least then there's a tiny bit more
            # encapsulation...meh.
            for tunnel in tunnels:
                tunnel.finished.set()
                tunnel.join()
            self.transport.cancel_port_forward(address=remote_host,
                                               port=remote_port)
Ejemplo n.º 13
0
def set_client(ip,
               port,
               numb_conn,
               Tunnel,
               hostname=None,
               username=None,
               Key_path=None,
               passphrase=None):
    """
    this method is responsible for establishing a connection to a server (Ip and port of the server needed to be specified)
    In a direct LAN enviroment if Tunnel was Fale (hostname,username,keypath,passphrase) aren't needed;
    connection thought SSH tunneling if tunnel was True 
    Hostname and username needed to be specified 
    keypath and pass phrase needed to be specified if there isn't an ssh agent otherwise it isn't needed.
    """
    client = []
    transport = None
    if Tunnel:
        server_address = (ip, port)
        sshclient = SSHClient()
        sshclient.load_system_host_keys()
        sshclient.set_missing_host_key_policy(AutoAddPolicy())
        try:
            sshclient.connect(hostname=hostname,
                              username=username,
                              passphrase=passphrase,
                              key_filename=Key_path)
            transport = sshclient.get_transport()
        except (BadHostKeyException, SSHException,
                AuthenticationException) as e:
            print("A problem trying to connection to the Host {}".format(
                hostname))
            raise e
            transport.close()
            return None, None
        try:
            for i in range(numb_conn):
                client.append(
                    transport.open_channel(kind='direct-tcpip',
                                           src_addr=server_address,
                                           dest_addr=server_address))
        except SSHException as e:
            print(
                "problem Has been faced trying to make direct-tcpip conneciton"
            )
            raise e
            for i in client:
                i.close()
            sshclient.get_transport().close()
            raise (KeyboardInterrupt)
        except (KeyboardInterrupt):
            for i in client:
                i.close()
            sshclient.get_transport().close()
            return None, None
    else:
        server_address = (ip, port)
        try:
            for i in range(numb_conn):
                client.append(socket.socket(socket.AF_INET,
                                            socket.SOCK_STREAM))
                client[i].connect(server_address)
        except (OSError):
            print("The connection #{} failed".format(i + 1))
            print("The process is stopping")
            raise (KeyboardInterrupt)
        except (KeyboardInterrupt):
            for i in client:
                i.close()
            return None, None

    return client, transport  #returning the connection object for further use
Ejemplo n.º 14
0
def set_server(ip, port, n_conn, Tunnel, hostname=None, username=None):
    """
    this method is responsible for establishing a server(listnening for connection) 
    In a direct LAN enviroment if Tunnel was False; hostname isn't needed
    n_conn is the awaited number of connections to be established
    Hostname is needed it Tunnel was activated  
    """
    connection = []
    transport = None
    if Tunnel:

        sshclient = SSHClient()

        sshclient.load_system_host_keys()

        sshclient.set_missing_host_key_policy(AutoAddPolicy())

        try:
            sshclient.connect(hostname=hostname, username=username)
        except (BadHostKeyException, SSHException,
                AuthenticationException) as e:
            print("problem hapened ssh into {}".format(hostname))
            raise e
            sshclient.get_transport().close()
            return None, None
        try:
            try:
                transport = sshclient.get_transport()
                port = transport.request_port_forward(address=ip, port=port)
            except (SSHException):
                print("the Node refused the Given port {}".format(port))
                port = 0
                port = transport.request_port_forward(address=ip, port=port)
            print("port {}".format(port))
            print('starting up on {} : {}'.format(ip, port))
            for i in range(n_conn):
                connection.append(transport.accept(None))
                print('client_address is {}:{} '.format(
                    *connection[i].getpeername()))
        except (BadHostKeyException, SSHException,
                AuthenticationException) as e:
            print(
                "an unexpected problem has been faced in request_port_forward")
            for i in connection:
                i.close()
            transport.close()
            return None, None

        #port,s=tunneling_cmd_hpc_server(user=user,path=path,local_port=port)
    else:
        #s = None
        sock = socket.socket(
            socket.AF_INET, socket.SOCK_STREAM
        )  #specifying socket type is IPV4"socket.AF_INET" and TCP "SOCK_STREAM"
        server_address = (ip, port)  # saving ip and port as tuple
        try:
            sock.bind(server_address)  # attaching the socket to the pc (code)
            print('starting up on', server_address[0], ':', server_address[1])
            sock.listen(n_conn)  # start waiting for 1 client to connection
            for i in range(n_conn):
                connection_, client_address = sock.accept(
                )  #accepting that client and hosting a connection with him
                print('client_address is {}:{} '.format(*client_address))
                connection.append(connection_)
        except (OSError) as e:
            print("Making a server Failed")
            raise e
            for i in connection:
                i.close()
            return None, None

    print('The number of connections started successfully = {}'.format(
        n_conn))  # printing when the connection is successful
    return connection, transport  #returning the connection object for further use
Ejemplo n.º 15
0
from paramiko.client import SSHClient
import time

# Credentials here are for a always-on Sandbox from Cisco DevNet
SSH_USER = "******"
SSH_PASSWORD = "******"
SSH_HOST = "<insert host>"
SSH_PORT = 22  # Change this if your SSH port is differente

client = SSHClient()
client.load_system_host_keys()

client.connect(SSH_HOST,
               port=SSH_PORT,
               username=SSH_USER,
               password=SSH_PASSWORD,
               look_for_keys=False)

channel = client.get_transport().open_session()
shell = client.invoke_shell()

commands = ["configure terminal", "hostname test"]

for cmd in commands:
    shell.send(cmd + "\n")
    out = shell.recv(1024)
    print(out)
    time.sleep(5)
Ejemplo n.º 16
0
class SSH(AbstractFs):

    policy = AutoAddPolicy
    recv_size = 1024

    _ssh = None
    _basepath = "/"

    def __init__(self, host, username="******", basepath=None):
        logger.debug("Creating SSH client")
        self._ssh = SSHClient()
        self._ssh.load_system_host_keys()

        logger.debug("Setting %s for missing host keys", self.policy.__name__)
        self._ssh.set_missing_host_key_policy(self.policy)

        logger.info(
            "Connecting to %s%s%s",
            "{}@".format(username) or "",
            host,
            ":{}".format(basepath) or "",
        )
        self._ssh.connect(host, username=username, compress="true")

        if basepath:
            logger.info("Use basepath %s", basepath)
            self._basepath = basepath

    def _collect_stream(self, channel, recv_fn):
        content = ""
        buffer = None

        while True:
            buffer = recv_fn(self.recv_size)
            content += buffer.decode("utf-8")

            if len(buffer) == 0:
                break

        return content

    def _exec(self, cmd, log_error=True):
        channel = self._ssh.get_transport().open_session()
        channel.exec_command(cmd)

        stdout = self._collect_stream(channel, channel.recv)
        stderr = self._collect_stream(channel, channel.recv_stderr)

        exit_code = channel.recv_exit_status()

        logger.debug(stdout)

        if exit_code != 0 and log_error:
            logger.error(stderr)
            logger.error("Command %s failed with exit code %s", cmd, exit_code)

        return exit_code, stdout, stderr

    def touch(self, filename):
        logger.debug("Touching file %s", filename)
        self._exec('touch "{}"'.format(filename))

    def rename(self, remote_src, remote_dest):
        logger.debug("Renaming %s into %s", remote_src, remote_dest)

        exit_code, _, _ = self._exec('mv "{}" "{}"'.format(remote_src, remote_dest))

        if exit_code != 0:
            raise SSHError("Failed renaming {} into {}".format(remote_src, remote_dest))

    def exists(self, path):
        logger.debug("Checking if %s exists...", path)
        exit_code, _, _ = self._exec('test -f "{}"'.format(path), log_error=False)
        exists = exit_code == 0

        logger.debug("File %s does%s exists.", path, "" if exists else " not")
        return exists

    def copy(self, remote_src, local_dst):
        exit_code, remote_content, _ = self._exec('cat "{}"'.format(remote_src))

        if exit_code != 0:
            raise SSHError("Cannot read from %s", remote_src)

        with open(local_dst, "w") as f:
            f.write(remote_content)

    # def remove(self, path):
    #     return os.remove(path)

    def symlink(self, remote_src, remote_dest, relative_to=None):
        if relative_to:
            remote_src = remote_src.replace(relative_to, ".")

        logger.debug("Symlinking %s to %s", remote_src, remote_dest)

        exit_code, _, _ = self._exec('ln -sf "{}" "{}"'.format(remote_src, remote_dest))

        if exit_code != 0:
            raise SSHError("Failed symlinking {} to {}".format(remote_src, remote_dest))

    def glob(self, remote_path, regex_str):
        logger.debug("Globbing %s for %s", remote_path, regex_str)

        exit_code, remote_content, _ = self._exec('ls "{}"'.format(remote_path))

        if exit_code != 0:
            raise SSHError("Failed listing {}".format(remote_path))

        regex = re.compile(regex_str)

        entries = (e for e in remote_content.split() if regex.match(e))
        entries = (os.path.join(remote_path, e) for e in entries)

        return entries

    def rmtree(self, remote_path):
        logger.debug("Removing tree %s", remote_path)

        exit_code, _, _ = self._exec('rm -rf "{}"'.format(remote_path))

        if exit_code != 0:
            raise SSHError("Failed removing {}".format(remote_path))

    def close(self):
        logger.debug("Closing SSH connection...")
        self._ssh.close()

        logger.debug("Connection closed.")
Ejemplo n.º 17
0
#!/usr/bin/python
from task_constants import *

from paramiko.client import SSHClient

client = SSHClient()
client.load_system_host_keys()
client.connect(hostname = opt_server,
               username = opt_username,
               key_filename = opt_keyfile)
output = client.exec_command('ls')
print output

from scp import SCPClient
scp = SCPClient(client.get_transport())
scp.put("ssh.py", "ssh.py")
scp.put("scp.py", "scp.py")
print client.exec_command('md5sum ssh.py scp.py')
client.close()

"""
  The UUID of a task will serve as the filename for all the files associated with the task.
  As well as the handle for marking/handling the task.

  A task has these states
    - TODO (we should verify that the file has been generated, if not, alert)
        copy the file on WEB01 to OPT01
        mark the task as GOING_TO_RUN
        submit the job as qsub (a shell script with param)
            (qsub should be able to mark something as READY_TO_MAIL
Ejemplo n.º 18
0
class BufferedRemoteCommand():
    def __init__(self, ip, command, username=REMOTE_USER):
        self.ip = ip
        self.command = command
        self.username = username

        self.client = SSHClient()
        self.client.set_missing_host_key_policy(IgnoreHostKeyPolicy)

        self.lines = []
        self.current_line = None
        return

    def open(self):
        self.client.connect(self.ip, username=self.username)

        self.transport = self.client.get_transport()
        self.transport.set_keepalive(30)

        self.channel = self.transport.open_session()
        self.channel.set_combine_stderr(True)

        self.channel.exec_command(self.command)

        return

    def read(self, nbytes=256):
        if self.channel.recv_ready():
            b = self.channel.recv(nbytes)

            if len(b) == 0:
                self.closed = True
                return 0, None
            else:
                s = b.decode('utf-8')
                return len(b), s

        # try again later
        return -1, None

    def readlines(self):
        nbytes, out = self.read()

        if nbytes == -1:
            # no luck this time, come later
            while nbytes == -1:
                time.sleep(1)
                nbytes, out = self.read()

        if nbytes == 0:
            # we're closed
            lines = []

        else:
            lines = out.splitlines()

            if self.current_line:
                lines[0] = self.current_line + lines[0]
                self.current_line = None

            if out[-1] != '\n':
                self.current_line = lines[-1]
                lines = lines[:-1]

            if not lines:
                # we got a chunk of a line, not a whole one, keep buffering
                return self.readlines()

        return nbytes, lines

    def __iter__(self):
        return self

    def __next__(self):
        if self.channel.exit_status_ready():
            self.rc = self.channel.recv_exit_status()
            raise StopIteration

        if not self.lines:
            # fetch another round on the remote connection
            nbytes, self.lines = self.readlines()

            if nbytes == 0:
                # we're done
                raise StopIteration

        line = self.lines.pop(0)
        return line

    def close(self):
        return self.client.close()
Ejemplo n.º 19
0

	client=SSHClient()
	client.set_missing_host_key_policy(policy=MissingHostKeyPolicy2())
	client.connect(host,username=client_username,port=port,key_filename=client_certificate)
	
	f=open(run_file,"r")
	while True:
		line=f.readline()
		if line=="":
			break
		line=line.strip()
		line=line.split(" ")
		if(line[0]=="RUN"):
			writeLog("=============================================")
			cs=client.get_transport().open_session()
			stdout=cs.makefile()
			stderr=cs.makefile_stderr()
			command=" ".join(line[1:])
			writeLog("Running Command "+command)
			cs.exec_command(command)
			out=stdout.read()
			err=stderr.read()
			rc=cs.recv_exit_status()
			writeLog("Exit code "+str(rc))
			writeLog("====== STDOUT =====\n"+out)
			writeLog("====== STDERR =====\n"+err)
			cs.close()
			writeLog("=============================================")
		elif(line[0]=="COPY"):
			writeLog("=============================================")
Ejemplo n.º 20
0
class SshClusterConnection(AbstractConnection):
    def __init__(self, girder_token, cluster):
        self._girder_token = girder_token
        self._cluster = cluster

    def _load_rsa_key(self, path, passphrase):
        return RSAKey.from_private_key_file(path, password=passphrase)

    def __enter__(self):
        self._client = SSHClient()
        self._client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

        username = parse('config.ssh.user').find(self._cluster)[0].value
        hostname = parse('config.host').find(self._cluster)[0].value

        port = parse('config.port').find(self._cluster)
        if port:
            port = port[0].value
        else:
            port = 22

        passphrase \
            = parse('config.ssh.passphrase').find(self._cluster)
        if passphrase:
            passphrase = passphrase[0].value
        else:
            passphrase = None

        key_name = parse('config.ssh.key').find(self._cluster)[0].value
        key_path = os.path.join(cumulus.config.ssh.keyStore, key_name)

        private_key = self._load_rsa_key(key_path, passphrase)

        self._client.connect(hostname=hostname,
                             port=port,
                             username=username,
                             pkey=private_key)

        return self

    def __exit__(self, type, value, traceback):
        self._client.close()

    def execute(self, command, ignore_exit_status=False, source_profile=True):
        if source_profile:
            command = 'source /etc/profile && %s' % command

        chan = self._client.get_transport().open_session()
        chan.exec_command(command)
        stdout = chan.makefile('r', -1)
        stderr = chan.makefile_stderr('r', -1)

        output = stdout.readlines() + stderr.readlines()
        exit_code = chan.recv_exit_status()
        if ignore_exit_status and exit_code != 0:
            raise SshCommandException(command, exit_code, output)

        return output

    @contextmanager
    def get(self, remote_path):
        sftp = None
        file = None
        try:
            sftp = self._client.get_transport().open_sftp_client()
            file = sftp.open(remote_path)
            yield file
        finally:
            if file:
                file.close()
                sftp.close()

    def isfile(self, remote_path):

        with self._client.get_transport().open_sftp_client() as sftp:
            try:
                s = sftp.stat(remote_path)
            except IOError:
                return False

            return stat.S_ISDIR(s.st_mode)

    def mkdir(self, remote_path, ignore_failure=False):
        with self._client.get_transport().open_sftp_client() as sftp:
            try:
                sftp.mkdir(remote_path)
            except IOError:
                if not ignore_failure:
                    raise

    def makedirs(self, remote_path):
        with self._client.get_transport().open_sftp_client() as sftp:
            current_path = ''
            if remote_path[0] == '/':
                current_path = '/'

            for path in remote_path.split('/'):
                if not path:
                    continue
                current_path = os.path.join(current_path, path)
                try:
                    sftp.listdir(current_path)
                except IOError:
                    sftp.mkdir(current_path)

    def put(self, stream, remote_path):
        with self._client.get_transport().open_sftp_client() as sftp:
            sftp.putfo(stream, remote_path)

    def stat(self, remote_path):
        with self._client.get_transport().open_sftp_client() as sftp:
            return sftp.stat(remote_path)

    def remove(self, remote_path):
        with self._client.get_transport().open_sftp_client() as sftp:
            return sftp.remove(remote_path)

    def list(self, remote_path):
        with self._client.get_transport().open_sftp_client() as sftp:
            for path in sftp.listdir_iter(remote_path):
                yield {
                    'name': path.filename,
                    'user': path.st_uid,
                    'group': path.st_gid,
                    'mode': path.st_mode,
                    # For now just pass mtime through
                    'date': path.st_mtime,
                    'size': path.st_size
                }
Ejemplo n.º 21
0
    def start_server_over_ssh(self):
        try:
            client = SSHClient()
            client.load_system_host_keys()
            client.set_missing_host_key_policy(WarningPolicy())
            client.connect(
                self.ssh_host,
                port=self.ssh_port,
                username=self.ssh_user,
                password=self.text,
            )
            transport = client.get_transport()
            ip, _ = transport.getpeername()
            if ip:
                self.update_ip_linedt_signal.emit(ip)
                logger.info(f"IP for {self.ssh_host} detected as {ip}.")
            ws_name = self.run_config["workspace_name"]
            server_port = self.run_config["server_port"]
            # TODO Check if the server port is already in use
            logger.info(
                f"Checking if server port: {server_port} at ip: {ip} is already in use."
            )
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            result = sock.connect_ex((ip, int(server_port)))
            if result == 0:
                logger.error(f"Port {server_port} is already open.")
                self.button_message_signal.emit([
                    f"Port: {server_port} at ip: {ip} is already in use!",
                    "maroon", 3
                ])
                self.error_signal.emit()
                sock.close()
                client.close()
                return
            else:
                logger.info(f"Port {server_port} is not open.")
            sock.close()
            cuda_command = "module load cuda/10.1\n"
            command = (
                "/dls_sw/apps/SuRVoS2/s2_conda/bin/python -u "
                "/dls/science/groups/das/SuRVoS/s2/s2_dec/SuRVoS2/survos.py "
                f"start_server {ws_name} {server_port} > {date.today()}_survos2.log &\n"
            )
            logger.info(f"Running command on remote machine: {command}")

            session = transport.open_session()
            session.setblocking(0)  # Set to non-blocking mode
            session.get_pty()
            session.invoke_shell()
            # Send commands
            session.send(cuda_command)
            session.send(command)
            # Loop for 15 seconds
            self.button_message_signal.emit([
                f"Starting server on {self.ssh_host}. Please Wait!", "navy", 14
            ])
            start = time.time()
            while time.time() - start < 15:
                if session.recv_ready():
                    data = session.recv(512)
                    print(data.decode(), flush=True)
                time.sleep(1)  # Yield CPU so we don't take up 100% usage...
            self.finished.emit()

        except AuthenticationException:
            logger.error("SSH Authentication failed!")
            self.button_message_signal.emit(
                ["Incorrect Password!", "maroon", 3])
            self.error_signal.emit()
Ejemplo n.º 22
0
class ParamikoConnection(BaseConnection):
    def __init__(self, **kwargs):
        self._kwargs = kwargs
        self._ssh: Union[SSHClient, None] = None

        self.host = None
        self.user = '******'

        host = kwargs.get('host', None)
        if host:
            splitted = host.split('@')
            self.host = splitted.pop(-1)
            if splitted:
                self.user = splitted[-1]
        self._port = SSH_PORT

    def connect(self):
        logging.getLogger('paramiko').setLevel(logging.ERROR)
        self._ssh = SSHClient()
        self._ssh.set_missing_host_key_policy(AutoAddPolicy())
        self._ssh.load_system_host_keys()
        timeout = self._kwargs.get('timeout', None)
        self._ssh.connect(self.host,
                          self._port,
                          self.user,
                          allow_agent=True,
                          timeout=timeout)

    def close(self):
        pass

    def connected(self):
        pass

    def exec_command(self, cmd):
        transport = self._ssh.get_transport()
        ch = transport.open_session()
        AgentRequestHandler(ch)
        ch.exec_command(cmd)

        stdin = ''
        stdout = ''
        stderr = ''
        size = 1024 * 1024
        # TODO: write stdin to channel
        while True:

            r, w, x = select.select([ch], [], [], 1)

            if len(r):
                if ch in r:
                    while True:
                        data = ch.recv_stderr(size)
                        if not data:
                            break
                        stderr += data.decode(errors='replace')
                        # for line in data.decode(errors='replace').splitlines():
                        #     print(line.strip())
                    while True:
                        data = ch.recv(size)
                        if not data:
                            break
                        stdout += data.decode(errors='replace')
                        # for line in data.decode(errors='replace').splitlines():
                        #     print(line.strip())

            if ch.exit_status_ready():
                break

        return stdin, stdout, stderr, ch.recv_exit_status()

    def put_file(self, local, remote):
        is_file_object = hasattr(local, 'seek') and callable(local.seek)
        sftp = self._ssh.open_sftp()
        if is_file_object:
            sftp.putfo(local, remote)
        else:
            sftp.put(local, remote)
        return remote

    def fetch_file(self, remote, local):
        is_file_object = hasattr(local, 'seek') and callable(local.seek)
        sftp = self._ssh.open_sftp()
        if is_file_object:
            sftp.getfo(remote, local)
        else:
            sftp.get(remote, local)
        return remote

    def put_dir(self, local_path, remote_path):
        assert os.path.isdir(local_path)
        sftp = self._ssh.open_sftp()
        if os.path.basename(local_path):
            strip = os.path.dirname(local_path)
        else:
            strip = os.path.dirname(os.path.dirname(local_path))

        remote_paths = []

        for context, dirs, files in os.walk(local_path):
            rcontext = context.replace(strip, '', 1)
            rcontext = rcontext.replace(os.sep, '/')
            rcontext = rcontext.lstrip('/')
            rcontext = os.path.join(remote_path, rcontext)

            exists = False
            try:
                s = sftp.lstat(rcontext)
                exists = True
            except FileNotFoundError:
                pass

            if not exists:
                sftp.mkdir(rcontext)

            for d in dirs:
                n = os.path.join(rcontext, d)
                exists = False
                try:
                    s = sftp.lstat(rcontext)
                    exists = True
                except FileNotFoundError:
                    pass
                if not exists:
                    sftp.mkdir(n)
            for f in files:
                local_path = os.path.join(context, f)
                n = os.path.join(rcontext, f)
                p = sftp.put(local_path, n)
                remote_paths.append(p)

        return remote_paths

    # def fetch_dir(self):
    #     pass

    def stat(self, remote):
        sftp = self._ssh.open_sftp()
        return sftp.lstat(remote)
Ejemplo n.º 23
0
class MasterNodeSSHClient():
    def __init__(self, host, username=None, ssh_key=None):
        self.ssh_client = SSHClient()
        self.ssh_client.load_system_host_keys()
        self.ssh_client.set_missing_host_key_policy(AutoAddPolicy())
        self.ssh_client.connect(host, username=username, key_filename=os.path.expanduser(ssh_key))
        self.ssh_client.get_transport().set_keepalive(30)

    def _send_command_and_wait(self, cmd):
        ssh = self.ssh_client

        chan = ssh.get_transport().open_session()

        try:
            # Execute the given command
            chan.exec_command(cmd)

            # To capture Data. Need to read the entire buffer to caputure output

            contents = StringIO.StringIO()
            error = StringIO.StringIO()

            while not chan.exit_status_ready():

                if chan.recv_ready():
                    data = chan.recv(1024)
                    # print "Indside stdout"
                    while data:
                        contents.write(data)
                        data = chan.recv(1024)

                if chan.recv_stderr_ready():

                    error_buff = chan.recv_stderr(1024)
                    while error_buff:
                        error.write(error_buff)
                        error_buff = chan.recv_stderr(1024)

            exit_status = chan.recv_exit_status()

        except socket.timeout:
            raise socket.timeout

        out = contents.getvalue()
        err = error.getvalue()

        return out, err, exit_status

    def test(self):
        output = self._send_command_and_wait('echo "ssh connection successful"')
        self.print_output(output)

    def copy(self, src, dst):
        scp = SFTPClient.from_transport(self.ssh_client.get_transport())
        scp.put(src, dst)

    def create_tmp_dir(self, prefix=''):
        scp = SFTPClient.from_transport(self.ssh_client.get_transport())
        dirpath = path_join('/tmp', prefix + str(int(time.time() * 1000)))
        scp.mkdir(dirpath)
        return dirpath

    def dist_copy(self, src, dest):
        cmd = 'sudo -u hdfs hadoop distcp {} {}'.format(src, dest)
        return self._send_command_and_wait(cmd)

    def run_pig_script(self, pig_script, support_scripts=[]):
        tmp_dir = self.create_tmp_dir('pig_scripts')

        def copy_script(full_path):
            filename = os.path.basename(full_path)
            dest = path_join(tmp_dir, filename)
            self.copy(full_path, dest)

        copy_script(pig_script)
        for script in support_scripts:
            copy_script(script)
        cmd = 'cd {} && pig {}'.format(tmp_dir, os.path.basename(pig_script))
        return self._send_command_and_wait(cmd)

    def run_impala_script(self, impala_script):
        filename = os.path.basename(impala_script)
        dest = path_join(self.create_tmp_dir('impala_scripts'), filename)
        self.copy(impala_script, dest)
        cmd = 'impala-shell -i {} -f {}'.format(get_dns(slave=True), dest)
        return self._send_command_and_wait(cmd)

    def fix_hdfs_permissions(self, hdfs_dir):
        return self._send_command_and_wait('sudo -u hdfs hdfs dfs -chmod -R 777 {}'.format(hdfs_dir))

    @staticmethod
    def print_output(output_tuple):
        for line in output_tuple[0].split('\n'):
            print line
        for line in output_tuple[1].split('\n'):
            print line

    def close(self):
        self.ssh_client.close()
Ejemplo n.º 24
0
class SshBackend(UploadDownloadBackend):
    def __init__(self, host, user, password, interpreter, cwd):
        UploadDownloadBackend.__init__(self)
        try:
            import paramiko
            from paramiko.client import SSHClient
        except ImportError:
            raise RuntimeError("paramiko is required")

        self._host = host
        self._user = user
        self._password = password
        self._remote_interpreter = interpreter
        self._cwd = cwd
        self._proc = None  # type: Optional[RemoteProcess]
        self._sftp = None  # type: Optional[paramiko.SFTPClient]
        self._client = SSHClient()
        self._client.load_system_host_keys()
        # TODO: does it get closed properly after process gets killed?
        self._client.connect(hostname=host, username=user, password=password)

    def _create_remote_process(self, cmd_items: List[str], cwd: str,
                               env: Dict) -> RemoteProcess:
        # Before running the main thing:
        # * print process id (so that we can kill it later)
        #   http://redes-privadas-virtuales.blogspot.com/2013/03/getting-hold-of-remote-pid-through.html
        # * change to desired directory

        cmd_line_str = (
            "echo $$ ; stty -echo ; " +
            (" cd %s  2> /dev/null ;" % shlex.quote(cwd) if cwd else "") +
            (" exec " + " ".join(map(shlex.quote, cmd_items))))
        stdin, stdout, _ = self._client.exec_command(cmd_line_str,
                                                     bufsize=0,
                                                     get_pty=True,
                                                     environment=env)

        # stderr gets directed to stdout because of pty
        pid = stdout.readline().strip()
        channel = stdout.channel

        return RemoteProcess(self._client, channel, stdin, stdout, pid)

    def _handle_immediate_command(self, cmd: ImmediateCommand) -> None:
        if cmd.name == "kill":
            self._kill()
        elif cmd.name == "interrupt":
            self._interrupt()
        else:
            raise RuntimeError("Unknown immediateCommand %s" % cmd.name)

    def _kill(self):
        if self._proc is None or self._proc.poll() is not None:
            return

        self._proc.kill()

    def _interrupt(self):
        pass

    def _get_sftp(self, fresh: bool):

        if fresh and self._sftp is not None:
            self._sftp.close()
            self._sftp = None

        if self._sftp is None:
            import paramiko

            # TODO: does it get closed properly after process gets killed?
            self._sftp = paramiko.SFTPClient.from_transport(
                self._client.get_transport())

        return self._sftp

    def _read_file(self, source_path: str, target_fp: BinaryIO,
                   callback: Callable[[int, int], None]) -> None:
        self._perform_sftp_operation_with_retry(
            lambda sftp: sftp.getfo(source_path, target_fp, callback))

    def _write_file(
        self,
        source_fp: BinaryIO,
        target_path: str,
        file_size: int,
        callback: Callable[[int, int], None],
    ) -> None:
        self._perform_sftp_operation_with_retry(
            lambda sftp: sftp.putfo(source_fp, target_path, callback))

    def _perform_sftp_operation_with_retry(self, operation) -> Any:
        try:
            return operation(self._get_sftp(fresh=False))
        except OSError:
            # It looks like SFTPClient gets stale after a while.
            # Try again with fresh SFTPClient
            return operation(self._get_sftp(fresh=True))

    def _get_stat_mode_for_upload(self, path: str) -> Optional[int]:
        try:
            return self._perform_sftp_operation_with_retry(
                lambda sftp: sftp.stat(path).st_mode)
        except OSError as e:
            return None

    def _mkdir_for_upload(self, path: str) -> None:
        self._perform_sftp_operation_with_retry(
            lambda sftp: sftp.mkdir(path, NEW_DIR_MODE))
Ejemplo n.º 25
0
class _SSHConnection:
    """
    Helper class handling SFTP communication.

    If provided with a hostname, automatically initiates the SFTP session.

    :param hostname: The SSH host, defaults to :const:`None`.
    :type hostname: str, optional
    :param port: The port where the SSH host is listening, defaults to :const:`None`.
    :type port: int, optional
    :param username: The username on the target SSH host, defaults to :const:`None`.
    :type username: str, optional

    :ivar client: The SSH session handler.
    :vartype client: paramiko.client.SSHClient
    :ivar sftp_session: The SFTP session handler.
    :vartype sftp_session: paramiko.sftp_client.SFTPClient
    """

    client = None
    sftp_session = None

    def __init__(self,
                 hostname: str = None,
                 port: int = None,
                 username: str = None):
        """Initialize object."""
        if hostname:
            self.set_session(hostname, port, username)

    def set_session(self,
                    hostname: str,
                    port: int = None,
                    username: str = None):
        """
        Set up a SFTP session.

        :param str hostname: The SSH host.
        :param port: The port where the SSH host is listening, defaults to
          :const:`None`.
        :type port: int, optional
        :param username: The username on the target SSH host, defaults to :const:`None`.
        :type username: str, optional
        """
        self.client = SSHClient()
        self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

        if port and username:
            self.client.connect(hostname, port, username)
        elif port:
            self.client.connect(hostname, port)
        elif username:
            self.client.connect(hostname, username=username)
        else:
            self.client.connect(hostname)

        self.sftp_session = self.client.open_sftp()

    def close_session(self):
        """Close the SFTP session."""
        if self.is_active():
            self.client.close()

    def mkdir_p(self, path: str):
        """
        Simulate the :command:`mkdir -p <path>` Unix command.

        It creates the directory and all its non-existing parents.

        :param str path: The path to a directory.
        """
        dirs = []

        while len(path) > 1:
            dirs.append(path)
            path = os.path.dirname(path)

        if len(path) == 1 and not path.startswith("/"):
            dirs.append(path)

        while dirs:
            path = dirs.pop()
            try:
                self.sftp_session.stat(path)
            except IOError:
                self.sftp_session.mkdir(path, mode=0o755)

    # def write_file(self, content: str, path: str):
    #     """
    #     Create or overwrite a file under `path` and store `content` in it.
    #
    #     It creates the parent directories if required.
    #
    #     :param str content: The file content.
    #     :param str path: Path to the new file.
    #     """
    #     self.mkdir_p(os.path.normpath(os.path.dirname(path)))
    #
    #     with self.sftp_session.open(path, "w") as sftp_file:
    #         sftp_file.write(content)

    def put_file(self, local_path, remote_path):
        """
        Copy the local file at `local_path` into `remote_path`.

        It creates the parent directories if required.

        :param str local_path: Path to the local file.
        :param str remote_path: Path to the remote file.
        """
        self.mkdir_p(os.path.normpath(os.path.dirname(remote_path)))
        self.sftp_session.put(local_path, remote_path)

    def is_active(self) -> bool:
        """Check whether the SSH session is active or not.

        :return: `True` if the SSH session is active, `False` otherwise.
        :rtype: bool
        """
        if self.client:
            transport = self.client.get_transport()

            if transport is not None and transport.is_active():
                try:
                    transport.send_ignore()
                    return True
                except EOFError:
                    return False

        return False
Ejemplo n.º 26
0
class SSHperformer(Performer):
    provider_name = 'ssh'
    settings_class = SSHPerformerSettings

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.client = None
        self.logger = getLogger(__name__)

    def _connect(self):
        self.client = SSHClient()
        self.client.set_missing_host_key_policy(AutoAddPolicy())
        self.client.load_system_host_keys()

        connection_details = {}
        if self.settings.port:
            connection_details['port'] = self.settings.port
        if self.settings.username:
            connection_details['username'] = self.settings.username
        if self.settings.password:
            connection_details['password'] = self.settings.password
        try:
            self.client.connect(self.settings.hostname, **connection_details)
        except NoValidConnectionsError as e:
            raise PerformerError('Cant connect to %s' % self.settings.hostname)
        else:
            #ssh agent forwarding
            s = self.client.get_transport().open_session()
            AgentRequestHandler(s)

    def _paramiko_exec_command(self, command, bufsize=-1, timeout=None):
        # replacement paramiko.client.exec_command(command) for binary output
        # https://github.com/paramiko/paramiko/issues/291
        # inspired by workaround https://gist.github.com/smurn/4d45a51b3a571fa0d35d

        chan = self.client._transport.open_session(timeout=timeout)
        chan.settimeout(timeout)
        chan.exec_command(command)
        stdin = chan.makefile('wb', bufsize)
        stdout = chan.makefile('rb', bufsize)
        stderr = chan.makefile_stderr('rb', bufsize)
        return stdin, stdout, stderr

    def execute(self, command, logger=None, writein=None, max_lines=None):
        self.logger.debug("Execute command: '%s'" % command)
        if not self.client:
            self._connect()

        stdin, stdout, stderr = self._paramiko_exec_command(command)

        # read stdout asynchronously - in 'realtime'
        output_reader = OutputReader(stdout, logger=logger or self.output_logger, max_lines=max_lines)

        if writein:
            # write writein to stdin
            stdin.write(writein)
            stdin.flush()
            stdin.channel.shutdown_write()

        # wait for end of output
        output = output_reader.output()

        # wait for exit code
        exit_code = stdout.channel.recv_exit_status()

        if exit_code:
            err = stderr.read().decode('utf-8').strip()
            self.logger.debug('command error: %s' % err)
            raise CommandError(command, exit_code, err)

        return output

    def send_file(self, source, target):
        self.logger.debug("Send file: '%s' '%s'" % (source, target))
        source = expanduser(source)
        sftp = self.client.open_sftp()
        sftp.put(source, target)
        sftp.close()

    @contextmanager
    def get_fo(self, remote_path):
        from tempfile import SpooledTemporaryFile
        self.logger.debug('SSH Get fo: %s' % remote_path)
        sftp = self.client.open_sftp()
        try:
            with SpooledTemporaryFile(1024000) as fo:
                sftp.getfo(remote_path, fo)
                yield fo
        finally:
            sftp.close()
class FileHandler():
    ''' handle getting/putting/cleaning of local and remote hosts files '''
    def __init__(self, temp_file, **kwargs):
        self.params = SimpleNamespace(**kwargs)
        self.logger = get_logger(self.__class__.__name__,
                                 self.params.log_level)
        self.temp_file = temp_file
        self.ssh = SSHClient()
        self.ssh.set_missing_host_key_policy(AutoAddPolicy())
        self.delayed_put = ResettableTimer(self.params.delay,
                                           self.put_hostfile)
        self.key = False

        self.get_server_ip()

        if self.params.key != '':
            self.logger.debug('self.params.key: %s', self.params.key)
            self.verify_key()

        self.get_hostfile()

    def get_server_ip(self):
        '''
		check for a valid dnsmasq server IP to use

		we can't use a hostname for the server because we end up trying to do a DNS
		lookup immediately after instructing dnsmasq to restart, and it's generally
		unwise to attempt a DNS resolution when we've just shut down the DNS server
		'''
        try:
            ipaddress.ip_address(self.params.server)
            self.params.server_ip = self.params.server
        except ValueError:
            try:
                self.params.server_ip = socket.getaddrinfo(
                    self.params.server, None)[0][4][0]
            except (ValueError, socket.gaierror):
                self.logger.error('Server (%s) cannot be found.',
                                  self.params.server)
                sys.exit(1)

    def verify_key(self):
        ''' verify and open key file or error on failure '''

        self.check_key('RSA')
        if not self.key:
            self.check_key('DSA')
            if not self.key:
                self.logger.error('No usable RSA or DSA key found. Halting.')
                sys.exit(1)

    def check_key(self, algorithm):
        ''' sets self.key if self.params.key is valid for the algorithm '''

        if algorithm == 'RSA':
            algo_class = RSAKey
        elif algorithm == 'DSA':
            algo_class = DSSKey
        else:
            raise Exception('check_key() works with \'RSA\' or \'DSA\' only.')

        self.logger.debug('Testing if key is %s.', algorithm)
        try:
            key = algo_class.from_private_key_file(self.params.key)
        except PasswordRequiredException:
            if self.params.password != '':
                self.logger.debug('Decrypting %s key.', algorithm)
                try:
                    key = algo_class.from_private_key_file(
                        self.params.key, password=self.params.password)
                except SSHException:
                    self.logger.error('Password for key is not valid.')
                else:
                    self.logger.info('Found valid encrypted %s key.',
                                     algorithm)
                    self.key = key
            else:
                self.logger.error('Encrypted %s key, requires password.',
                                  algorithm)
        except SSHException:
            self.key = False
        else:
            self.logger.info('Found valid %s key.', algorithm)
            self.key = key

    def open_ssh(self):
        '''
		check if an SSH connection is already open,
		open a new connection if necessary
		'''

        try:
            transport = self.ssh.get_transport()
            transport.send_ignore()
        except (EOFError, AttributeError):
            self.logger.debug('Opening SSH connection.')

            pass_params = {}
            pass_params['username'] = self.params.login

            if self.key:
                pass_params['key_filename'] = self.params.key
            else:
                pass_params['password'] = self.params.password

            try:
                self.ssh.connect(self.params.server_ip, **pass_params)
            except AuthenticationException:
                self.logger.error('Could not authenticate with remote device.')
                sys.exit(1)

    def close_ssh(self):
        ''' close the SSH connection '''
        if self.ssh:
            self.logger.debug('Closing SSH connection.')
            self.ssh.close()

    def get_hostfile(self):
        ''' get the specified hosts file from the remote device '''

        self.logger.info('Reading remote hosts file: %s', self.params.file)

        self.open_ssh()
        exec_return = self.ssh.exec_command('cat ' + self.params.file)[1]

        remote_hosts = []
        if exec_return.channel.recv_exit_status():
            self.logger.info(
                'Remote hosts file does not exist, it will be created.')
        else:
            remote_hosts = exec_return.readlines()

        self.hosts = remote_hosts

    def queue_put(self):
        '''
		delayed putting of the local hosts file on the remote device
		the delay allows for any additional changes in the immediate future,
		such as expected when a container is restarting, for example.
		'''

        self.logger.info('Queued remote host file update.')
        self.delayed_put.reset()

    def put_hostfile(self):
        ''' put the local hosts file on the remote device '''
        self.open_ssh()
        self.logger.info('Writing remote hosts file: %s', self.params.file)

        with open(self.temp_file.name, 'r') as temp_file:
            exec_return = self.ssh.exec_command('echo -e "' +
                                                temp_file.read() + '" > ' +
                                                self.params.file)[1]
            if exec_return.channel.recv_exit_status():
                self.logger.error('Could not write remote file.')

        self.exec_remote_command()
        self.close_ssh()

    def exec_remote_command(self):
        ''' execute command to update dnsmasq on remote device '''

        self.open_ssh()
        remote_cmd = self.params.remote_cmd.strip('\'"')

        try:
            exec_return = self.ssh.exec_command(remote_cmd)[1]
        except SSHException:
            self.logger.error(
                'SSHException: Failed to execute remote command: %s',
                remote_cmd)

        if exec_return.channel.recv_exit_status() > 0:
            self.logger.error('Could not execute remote command: %s',
                              remote_cmd)
        else:
            self.logger.info('Executed remote command: %s', remote_cmd)
Ejemplo n.º 28
0
def ssh_paramiko_connect_to(display_desc):
    #plain socket attributes:
    dtype = display_desc["type"]
    host = display_desc["host"]
    port = display_desc.get("ssh-port", 22)
    #ssh and command attributes:
    username = display_desc.get("username") or get_username()
    if "proxy_host" in display_desc:
        display_desc.setdefault("proxy_username", get_username())
    password = display_desc.get("password")
    remote_xpra = display_desc["remote_xpra"]
    proxy_command = display_desc["proxy_command"]       #ie: "_proxy_start"
    socket_dir = display_desc.get("socket_dir")
    display = display_desc.get("display")
    display_as_args = display_desc["display_as_args"]   #ie: "--start=xterm :10"
    paramiko_config = display_desc.copy()
    paramiko_config.update(display_desc.get("paramiko-config", {}))
    socket_info = {
            "host"  : host,
            "port"  : port,
            }
    def get_keyfiles(host_config, config_name="key"):
        keyfiles = (host_config or {}).get("identityfile") or get_default_keyfiles()
        keyfile = paramiko_config.get(config_name)
        if keyfile:
            keyfiles.insert(0, keyfile)
        return keyfiles

    with nogssapi_context():
        from paramiko import SSHConfig, ProxyCommand
        ssh_config = SSHConfig()
        user_config_file = os.path.expanduser("~/.ssh/config")
        sock = None
        host_config = None
        if os.path.exists(user_config_file):
            with open(user_config_file) as f:
                ssh_config.parse(f)
            log("parsed user config '%s': %i hosts found", user_config_file, len(ssh_config.get_hostnames()))
            host_config = ssh_config.lookup(host)
            if host_config:
                log("got host config for '%s': %s", host, host_config)
                chost = host_config.get("hostname", host)
                cusername = host_config.get("user", username)
                cport = host_config.get("port", port)
                try:
                    port = int(cport)
                except (TypeError, ValueError):
                    raise InitExit(EXIT_SSH_FAILURE, "invalid ssh port specified: '%s'" % cport) from None
                proxycommand = host_config.get("proxycommand")
                if proxycommand:
                    log("found proxycommand='%s' for host '%s'", proxycommand, chost)
                    sock = ProxyCommand(proxycommand)
                    log("ProxyCommand(%s)=%s", proxycommand, sock)
                    from xpra.child_reaper import getChildReaper
                    cmd = getattr(sock, "cmd", [])
                    def proxycommand_ended(proc):
                        log("proxycommand_ended(%s) exit code=%s", proc, proc.poll())
                    getChildReaper().add_process(sock.process, "paramiko-ssh-client", cmd, True, True,
                                                 callback=proxycommand_ended)
                    proxy_keys = get_keyfiles(host_config, "proxy_key")
                    log("proxy keys=%s", proxy_keys)
                    from paramiko.client import SSHClient
                    ssh_client = SSHClient()
                    ssh_client.load_system_host_keys()
                    log("ssh proxy command connect to %s", (chost, cport, sock))
                    ssh_client.connect(chost, cport, sock=sock)
                    transport = ssh_client.get_transport()
                    do_ssh_paramiko_connect_to(transport, chost,
                                               cusername, password,
                                               host_config or ssh_config.lookup("*"),
                                               proxy_keys,
                                               paramiko_config)
                    chan = paramiko_run_remote_xpra(transport, proxy_command, remote_xpra, socket_dir, display_as_args)
                    peername = (chost, cport)
                    conn = SSHProxyCommandConnection(chan, peername, peername, socket_info)
                    conn.target = host_target_string("ssh", cusername, chost, port, display)
                    conn.timeout = SOCKET_TIMEOUT
                    conn.start_stderr_reader()
                    conn.process = (sock.process, "ssh", cmd)
                    from xpra.net import bytestreams
                    from paramiko.ssh_exception import ProxyCommandFailure
                    bytestreams.CLOSED_EXCEPTIONS = tuple(list(bytestreams.CLOSED_EXCEPTIONS)+[ProxyCommandFailure])
                    return conn

        keys = get_keyfiles(host_config)
        from xpra.scripts.main import socket_connect
        from paramiko.transport import Transport
        from paramiko import SSHException
        if "proxy_host" in display_desc:
            proxy_host = display_desc["proxy_host"]
            proxy_port = display_desc.get("proxy_port", 22)
            proxy_username = display_desc.get("proxy_username", username)
            proxy_password = display_desc.get("proxy_password", password)
            proxy_keys = get_keyfiles(host_config, "proxy_key")
            sock = socket_connect(dtype, proxy_host, proxy_port)
            middle_transport = Transport(sock)
            middle_transport.use_compression(False)
            try:
                middle_transport.start_client()
            except SSHException as e:
                log("start_client()", exc_info=True)
                raise InitExit(EXIT_SSH_FAILURE, "SSH negotiation failed: %s" % e) from None
            proxy_host_config = ssh_config.lookup(host)
            do_ssh_paramiko_connect_to(middle_transport, proxy_host,
                                       proxy_username, proxy_password,
                                       proxy_host_config or ssh_config.lookup("*"),
                                       proxy_keys,
                                       paramiko_config)
            log("Opening proxy channel")
            chan_to_middle = middle_transport.open_channel("direct-tcpip", (host, port), ('localhost', 0))

            transport = Transport(chan_to_middle)
            transport.use_compression(False)
            try:
                transport.start_client()
            except SSHException as e:
                log("start_client()", exc_info=True)
                raise InitExit(EXIT_SSH_FAILURE, "SSH negotiation failed: %s" % e)
            do_ssh_paramiko_connect_to(transport, host,
                                       username, password,
                                       host_config or ssh_config.lookup("*"),
                                       keys,
                                       paramiko_config)
            chan = paramiko_run_remote_xpra(transport, proxy_command, remote_xpra, socket_dir, display_as_args)
            peername = (host, port)
            conn = SSHProxyCommandConnection(chan, peername, peername, socket_info)
            conn.target = "%s via %s" % (
                host_target_string("ssh", username, host, port, display),
                host_target_string("ssh", proxy_username, proxy_host, proxy_port, None),
                )
            conn.timeout = SOCKET_TIMEOUT
            conn.start_stderr_reader()
            return conn

        #plain TCP connection to the server,
        #we open it then give the socket to paramiko:
        sock = socket_connect(dtype, host, port)
        sockname = sock.getsockname()
        peername = sock.getpeername()
        log("paramiko socket_connect: sockname=%s, peername=%s", sockname, peername)
        transport = Transport(sock)
        transport.use_compression(False)
        try:
            transport.start_client()
        except SSHException as e:
            log("start_client()", exc_info=True)
            raise InitExit(EXIT_SSH_FAILURE, "SSH negotiation failed: %s" % e) from None
        do_ssh_paramiko_connect_to(transport, host, username, password,
                                   host_config or ssh_config.lookup("*"),
                                   keys,
                                   paramiko_config)
        chan = paramiko_run_remote_xpra(transport, proxy_command, remote_xpra, socket_dir, display_as_args)
        conn = SSHSocketConnection(chan, sock, sockname, peername, (host, port), socket_info)
        conn.target = host_target_string("ssh", username, host, port, display)
        conn.timeout = SOCKET_TIMEOUT
        conn.start_stderr_reader()
        return conn
Ejemplo n.º 29
0
class MicroPythonSshBackend(MicroPythonOsBackend):
    def __init__(self, host, user, password, cwd, mp_executable,
                 api_stubs_path):
        from paramiko.client import SSHClient

        self._host = host
        self._user = user
        self._password = password
        self._sftp = None
        self._client = SSHClient()
        self._client.load_system_host_keys()
        # TODO: does it get closed properly after process gets killed?
        self._client.connect(hostname=host, username=user, password=password)

        self._cwd = cwd
        super().__init__(mp_executable, api_stubs_path, cwd=cwd)

    def _which(self, executable):
        cmd_str = " ".join(map(shlex.quote, ["which", executable]))
        _, stdout, _ = self._client.exec_command(cmd_str,
                                                 bufsize=0,
                                                 timeout=3,
                                                 get_pty=False)
        return stdout.readline().strip() or None

    def _create_connection(self, run_args=[]):
        # NB! It's connection to the micropython process, not to the host
        from thonny.plugins.micropython.ssh_connection import SshProcessConnection

        return SshProcessConnection(self._client, self._cwd,
                                    self._mp_executable, ["-i"] + run_args)

    def _tweak_welcome_text(self, original):
        return (super()._tweak_welcome_text(original).strip() + "\n" +
                self._user + "@" + self._host + "\n")

    def _get_sftp(self):
        if self._sftp is None:
            import paramiko

            # TODO: does it get closed properly after process gets killed?
            self._sftp = paramiko.SFTPClient.from_transport(
                self._client.get_transport())

        return self._sftp

    def _cmd_write_file(self, cmd):
        with io.BytesIO(cmd["content_bytes"]) as fp:
            self._get_sftp().putfo(fp, cmd["path"])

        return {"path": cmd["path"], "editor_id": cmd.get("editor_id")}

    def _cmd_read_file(self, cmd):
        try:
            with io.BytesIO() as fp:
                self._get_sftp().getfo(cmd["path"], fp)
                fp.seek(0)
                content_bytes = fp.read()
            error = None
        except Exception as e:
            # TODO: _report_internal_error()
            error = str(e)
            content_bytes = None

        return {
            "content_bytes": content_bytes,
            "path": cmd["path"],
            "error": error
        }

    def _upload_file(self, source, target, notifier=None):
        self._move_data_via_sftp(self._get_sftp().put, source, target,
                                 notifier)

    def _download_file(self, source, target, notifier=None):
        self._move_data_via_sftp(self._get_sftp().get, source, target,
                                 notifier)

    def _move_data_via_sftp(self, op, source, target, notifier):
        if notifier is None:
            callback = None
        else:

            def callback(sent, total):
                notifier(sent)

        op(source, target, callback=callback)
Ejemplo n.º 30
0
Archivo: usine.py Proyecto: cbnva/usine
class Client:

    context = {}

    def __init__(self, hostname, configpath=None, dry_run=False):
        ssh_config = SSHConfig()
        if not hostname:
            print(red('"hostname" must be defined'))
            sys.exit(1)
        parsed = self.parse_host(hostname)
        hostname = parsed.get('hostname')
        username = parsed.get('username')
        if configpath:
            if not isinstance(configpath, (list, tuple)):
                configpath = [configpath]
            for path in configpath:
                self._load_config(path, hostname)
        with (Path.home() / '.ssh/config').open() as fd:
            ssh_config.parse(fd)
        ssh_config = ssh_config.lookup(hostname)
        self.dry_run = dry_run
        self.hostname = config.hostname or ssh_config['hostname']
        self.username = (username or config.username
                         or ssh_config.get('user', getuser()))
        self.formatter = Formatter()
        self.key_filenames = []
        if config.key_filename:
            self.key_filenames.append(config.key_filename)
        if 'identityfile' in ssh_config:
            self.key_filenames.extend(ssh_config['identityfile'])
        self.sudo = ''
        self.cd = None
        self.screen = None
        self.env = {}
        self._sftp = None
        self.proxy_command = ssh_config.get('proxycommand',
                                            config.proxy_command)
        self.open()

    def open(self):
        self._client = SSHClient()
        self._client.load_system_host_keys()
        self._client.set_missing_host_key_policy(WarningPolicy())
        print(f'Connecting to {self.username}@{self.hostname}')
        if self.proxy_command:
            print('ProxyCommand:', self.proxy_command)
        sock = (paramiko.ProxyCommand(self.proxy_command)
                if self.proxy_command else None)
        try:
            self._client.connect(hostname=self.hostname,
                                 username=self.username,
                                 sock=sock,
                                 key_filename=self.key_filenames)
        except paramiko.ssh_exception.BadHostKeyException:
            sys.exit('Connection error: bad host key')
        self._transport = self._client.get_transport()

    def close(self):
        print(f'\nDisconnecting from {self.username}@{self.hostname}')
        self._client.close()

    def _load_config(self, path, hostname):
        with Path(path).open() as fd:
            conf = yaml.load(fd)
            if hostname in conf:
                conf.update(conf[hostname])
            config.update(conf)

    def parse_host(self, host_string):
        user_hostport = host_string.rsplit('@', 1)
        hostport = user_hostport.pop()
        user = user_hostport[0] if user_hostport and user_hostport[0] else None

        # IPv6: can't reliably tell where addr ends and port begins, so don't
        # try (and don't bother adding special syntax either, user should avoid
        # this situation by using port=).
        if hostport.count(':') > 1:
            host = hostport
            port = None
        # IPv4: can split on ':' reliably.
        else:
            host_port = hostport.rsplit(':', 1)
            host = host_port.pop(0) or None
            port = host_port[0] if host_port and host_port[0] else None

        if port is not None:
            port = int(port)

        return {'username': user, 'hostname': host, 'port': port}

    def _build_command(self, cmd, **kwargs):
        prefix = ''
        if self.cd:
            cmd = f'cd {self.cd}; {cmd}'
        if self.env:
            prefix = ' '.join(f'{k}={v}' for k, v in self.env.items())
        if self.sudo:
            prefix = f'{self.sudo} {prefix}'
        cmd = self.format(f"{prefix} sh -c $'{cmd}'")
        if self.screen:
            cmd = f'screen -UD -RR -S {self.screen} {cmd}'
        return cmd.strip().replace('  ', ' ')

    def _call_command(self, cmd, **kwargs):
        channel = self._transport.open_session()
        try:
            size = os.get_terminal_size()
        except IOError:
            channel.get_pty()  # Fails when ran from pytest.
        else:
            channel.get_pty(width=size.columns, height=size.lines)
        channel.exec_command(cmd)
        channel.setblocking(False)  # Allow to read from empty buffer.
        stdout = channel.makefile('r', -1)
        stderr = channel.makefile_stderr('r', -1)
        proxy_stdout = b''
        buf = b''
        while True:
            while sys.stdin in select.select([sys.stdin], [], [], 0)[0]:
                # TODO compute bytes_to_read like in invoke?
                data = sys.stdin.read(1)
                if data:
                    channel.sendall(data)
                else:
                    break
            if not channel.recv_ready():
                if buf:  # We may have read some buffer yet, let's output it.
                    sys.stdout.write(buf.decode())
                    sys.stdout.flush()
                    buf = b''
                if channel.exit_status_ready():
                    break
                continue
            try:
                data = stdout.read(1)
            except Exception:  # Not sure how to catch socket.timeout properly.
                pass
            else:
                proxy_stdout += data
                buf += data
                if data == b'\n':
                    sys.stdout.write(buf.decode())
                    sys.stdout.flush()
                    buf = b''
                continue
            time.sleep(paramiko.io_sleep)
        channel.setblocking(True)  # Make sure we now wait for stderr.
        ret = Status(proxy_stdout.decode(),
                     stderr.read().decode().strip(),
                     channel.recv_exit_status())
        channel.close()
        if ret.code:
            self.exit(ret.stderr, ret.code)
        return ret

    def exit(self, msg, code=1):
        print(red(msg))
        sys.exit(code)

    def __call__(self, cmd, **kwargs):
        cmd = self._build_command(cmd, **kwargs)
        print(gray(cmd))
        if self.dry_run:
            return Status('¡DRY RUN!', '¡DRY RUN!', 0)
        with character_buffered():
            return self._call_command(cmd, **kwargs)

    def format(self, tpl):
        try:
            return self.formatter.vformat(tpl, None, self.context)
        except KeyError as e:
            print(red(f'Missing key {e}'))
            sys.exit(1)

    @property
    def sftp(self):
        if not self._sftp:
            self._sftp = self._client.open_sftp()
        return self._sftp
Ejemplo n.º 31
0
def ssh_paramiko_connect_to(display_desc):
    #plain socket attributes:
    dtype = display_desc["type"]
    host = display_desc["host"]
    port = display_desc.get("ssh-port", 22)
    #ssh and command attributes:
    username = display_desc.get("username") or get_username()
    if "proxy_host" in display_desc:
        display_desc.setdefault("proxy_username", get_username())
    password = display_desc.get("password")
    target = ssh_target_string(display_desc)
    remote_xpra = display_desc["remote_xpra"]
    proxy_command = display_desc["proxy_command"]  #ie: "_proxy_start"
    socket_dir = display_desc.get("socket_dir")
    display_as_args = display_desc["display_as_args"]  #ie: "--start=xterm :10"
    socket_info = {
        "host": host,
        "port": port,
    }
    with nogssapi_context():
        from paramiko import SSHConfig, ProxyCommand
        ssh_config = SSHConfig()
        user_config_file = os.path.expanduser("~/.ssh/config")
        sock = None
        host_config = None
        if os.path.exists(user_config_file):
            with open(user_config_file) as f:
                ssh_config.parse(f)
            host_config = ssh_config.lookup(host)
            if host_config:
                host = host_config.get("hostname", host)
                username = host_config.get("username", username)
                port = host_config.get("port", port)
                proxycommand = host_config.get("proxycommand")
                if proxycommand:
                    sock = ProxyCommand(proxycommand)
                    from xpra.child_reaper import getChildReaper
                    cmd = getattr(sock, "cmd", [])
                    getChildReaper().add_process(sock.process,
                                                 "paramiko-ssh-client", cmd,
                                                 True, True)
                    log("found proxycommand='%s' for host '%s'", proxycommand,
                        host)
                    from paramiko.client import SSHClient
                    ssh_client = SSHClient()
                    ssh_client.load_system_host_keys()
                    ssh_client.connect(host, port, sock=sock)
                    transport = ssh_client.get_transport()
                    do_ssh_paramiko_connect_to(
                        transport, host, username, password, host_config
                        or ssh_config.lookup("*"))
                    chan = paramiko_run_remote_xpra(transport, proxy_command,
                                                    remote_xpra, socket_dir,
                                                    display_as_args)
                    peername = (host, port)
                    conn = SSHProxyCommandConnection(chan, peername, target,
                                                     socket_info)
                    conn.timeout = SOCKET_TIMEOUT
                    conn.start_stderr_reader()
                    conn.process = (sock.process, "ssh", cmd)
                    from xpra.net import bytestreams
                    from paramiko.ssh_exception import ProxyCommandFailure
                    bytestreams.CLOSED_EXCEPTIONS = tuple(
                        list(bytestreams.CLOSED_EXCEPTIONS) +
                        [ProxyCommandFailure])
                    return conn
        from xpra.scripts.main import socket_connect
        from paramiko.transport import Transport
        from paramiko import SSHException
        if "proxy_host" in display_desc:
            proxy_host = display_desc["proxy_host"]
            proxy_port = display_desc.get("proxy_port", 22)
            proxy_username = display_desc.get("proxy_username", username)
            proxy_password = display_desc.get("proxy_password", password)
            sock = socket_connect(dtype, proxy_host, proxy_port)
            middle_transport = Transport(sock)
            middle_transport.use_compression(False)
            try:
                middle_transport.start_client()
            except SSHException as e:
                log("start_client()", exc_info=True)
                raise InitException("SSH negotiation failed: %s" % e)
            proxy_host_config = ssh_config.lookup(host)
            do_ssh_paramiko_connect_to(
                middle_transport, proxy_host, proxy_username, proxy_password,
                proxy_host_config or ssh_config.lookup("*"))
            log("Opening proxy channel")
            chan_to_middle = middle_transport.open_channel(
                "direct-tcpip", (host, port), ('localhost', 0))

            transport = Transport(chan_to_middle)
            transport.use_compression(False)
            try:
                transport.start_client()
            except SSHException as e:
                log("start_client()", exc_info=True)
                raise InitException("SSH negotiation failed: %s" % e)
            do_ssh_paramiko_connect_to(transport, host, username, password,
                                       host_config or ssh_config.lookup("*"))
            chan = paramiko_run_remote_xpra(transport, proxy_command,
                                            remote_xpra, socket_dir,
                                            display_as_args)

            peername = (host, port)
            conn = SSHProxyCommandConnection(chan, peername, target,
                                             socket_info)
            conn.timeout = SOCKET_TIMEOUT
            conn.start_stderr_reader()
            return conn

        #plain TCP connection to the server,
        #we open it then give the socket to paramiko:
        sock = socket_connect(dtype, host, port)
        sockname = sock.getsockname()
        peername = sock.getpeername()
        log("paramiko socket_connect: sockname=%s, peername=%s", sockname,
            peername)
        transport = Transport(sock)
        transport.use_compression(False)
        try:
            transport.start_client()
        except SSHException as e:
            log("start_client()", exc_info=True)
            raise InitException("SSH negotiation failed: %s" % e)
        do_ssh_paramiko_connect_to(transport, host, username, password,
                                   host_config or ssh_config.lookup("*"))
        chan = paramiko_run_remote_xpra(transport, proxy_command, remote_xpra,
                                        socket_dir, display_as_args)
        conn = SSHSocketConnection(chan, sock, sockname, peername, target,
                                   socket_info)
        conn.timeout = SOCKET_TIMEOUT
        conn.start_stderr_reader()
        return conn
Ejemplo n.º 32
0
class SshMixin(UploadDownloadMixin):
    def __init__(self, host, user, password, interpreter, cwd):
        # UploadDownloadMixin.__init__(self)
        try:
            import paramiko
            from paramiko.client import SSHClient, AutoAddPolicy
        except ImportError:
            print(
                "\nThis back-end requires an extra package named 'paramiko'."
                " Install it from 'Tools => Manage plug-ins' or via your system package manager.",
                file=sys.stderr,
            )
            sys.exit()

        self._host = host
        self._user = user
        self._password = password
        self._remote_interpreter = interpreter
        self._cwd = cwd
        self._proc = None  # type: Optional[RemoteProcess]
        self._sftp = None  # type: Optional[paramiko.SFTPClient]
        self._client = SSHClient()
        self._client.load_system_host_keys()
        self._client.set_missing_host_key_policy(paramiko.client.AutoAddPolicy())
        # TODO: does it get closed properly after process gets killed?
        self._connect()

    def _connect(self):
        from paramiko.ssh_exception import AuthenticationException
        import socket

        from paramiko import SSHException

        try:
            self._client.connect(
                hostname=self._host,
                username=self._user,
                password=self._password,
                passphrase=self._password,
            )
        except (SSHException, OSError) as e:
            print(
                "\nCan't connect to '%s' with user '%s': %s" % (self._host, self._user, str(e)),
                file=sys.stderr,
            )
            print("Re-check your host, authentication method, password or keys.", file=sys.stderr)
            delete_stored_ssh_password()

            sys.exit(1)

    def _create_remote_process(self, cmd_items: List[str], cwd: str, env: Dict) -> RemoteProcess:
        # Before running the main thing:
        # * print process id (so that we can kill it later)
        #   http://redes-privadas-virtuales.blogspot.com/2013/03/getting-hold-of-remote-pid-through.html
        # * change to desired directory
        #
        # About -onlcr: https://stackoverflow.com/q/35887380/261181
        cmd_line_str = (
            "echo $$ ; stty -echo ; stty -onlcr ; "
            + (" cd %s  2> /dev/null ;" % shlex.quote(cwd) if cwd else "")
            + (" exec " + " ".join(map(shlex.quote, cmd_items)))
        )
        stdin, stdout, _ = self._client.exec_command(
            cmd_line_str, bufsize=0, get_pty=True, environment=env
        )

        # stderr gets directed to stdout because of pty
        pid = stdout.readline().strip()
        channel = stdout.channel

        return RemoteProcess(self._client, channel, stdin, stdout, pid)

    def _handle_immediate_command(self, cmd: ImmediateCommand) -> None:
        if cmd.name == "kill":
            self._kill()
        elif cmd.name == "interrupt":
            self._interrupt()
        else:
            raise RuntimeError("Unknown immediateCommand %s" % cmd.name)

    def _kill(self):
        if self._proc is None or self._proc.poll() is not None:
            return

        self._proc.kill()

    def _interrupt(self):
        pass

    def _get_sftp(self, fresh: bool):

        if fresh and self._sftp is not None:
            self._sftp.close()
            self._sftp = None

        if self._sftp is None:
            import paramiko

            # TODO: does it get closed properly after process gets killed?
            self._sftp = paramiko.SFTPClient.from_transport(self._client.get_transport())

        return self._sftp

    def _read_file(
        self, source_path: str, target_fp: BinaryIO, callback: Callable[[int, int], None]
    ) -> None:
        self._perform_sftp_operation_with_retry(
            lambda sftp: sftp.getfo(source_path, target_fp, callback)
        )

    def _write_file(
        self,
        source_fp: BinaryIO,
        target_path: str,
        file_size: int,
        callback: Callable[[int, int], None],
    ) -> None:
        self._perform_sftp_operation_with_retry(
            lambda sftp: sftp.putfo(source_fp, target_path, callback)
        )

    def _perform_sftp_operation_with_retry(self, operation) -> Any:
        try:
            return operation(self._get_sftp(fresh=False))
        except OSError:
            # It looks like SFTPClient gets stale after a while.
            # Try again with fresh SFTPClient
            return operation(self._get_sftp(fresh=True))

    def _get_stat_mode_for_upload(self, path: str) -> Optional[int]:
        try:
            return self._perform_sftp_operation_with_retry(lambda sftp: sftp.stat(path).st_mode)
        except OSError as e:
            return None

    def _mkdir_for_upload(self, path: str) -> None:
        self._perform_sftp_operation_with_retry(lambda sftp: sftp.mkdir(path, NEW_DIR_MODE))
Ejemplo n.º 33
0
Archivo: ssh.py Proyecto: jackerzz/spug
class SSH:
    def __init__(self,
                 hostname,
                 port=22,
                 username='******',
                 pkey=None,
                 password=None,
                 default_env=None,
                 connect_timeout=10):
        self.stdout = None
        self.client = None
        self.channel = None
        self.sftp = None
        self.eof = 'Spug EOF 2108111926'
        self.already_init = False
        self.default_env = self._make_env_command(default_env)
        self.regex = re.compile(r'Spug EOF 2108111926 (-?\d+)[\r\n]?')
        self.arguments = {
            'hostname':
            hostname,
            'port':
            port,
            'username':
            username,
            'password':
            password,
            'pkey':
            RSAKey.from_private_key(StringIO(pkey))
            if isinstance(pkey, str) else pkey,
            'timeout':
            connect_timeout,
            'banner_timeout':
            30
        }

    @staticmethod
    def generate_key():
        key_obj = StringIO()
        key = RSAKey.generate(2048)
        key.write_private_key(key_obj)
        return key_obj.getvalue(), 'ssh-rsa ' + key.get_base64()

    def get_client(self):
        if self.client is not None:
            return self.client
        self.client = SSHClient()
        self.client.set_missing_host_key_policy(AutoAddPolicy)
        self.client.connect(**self.arguments)
        return self.client

    def ping(self):
        return True

    def add_public_key(self, public_key):
        command = f'mkdir -p -m 700 ~/.ssh && \
        echo {public_key!r} >> ~/.ssh/authorized_keys && \
        chmod 600 ~/.ssh/authorized_keys'

        exit_code, out = self.exec_command_raw(command)
        if exit_code != 0:
            raise Exception(f'add public key error: {out}')

    def exec_command_raw(self, command, environment=None):
        channel = self.client.get_transport().open_session()
        if environment:
            channel.update_environment(environment)
        channel.set_combine_stderr(True)
        channel.exec_command(command)
        code, output = channel.recv_exit_status(), channel.recv(-1)
        return code, self._decode(output)

    def exec_command(self, command, environment=None):
        channel = self._get_channel()
        command = self._handle_command(command, environment)
        channel.send(command)
        out, exit_code = '', -1
        for line in self.stdout:
            match = self.regex.search(line)
            if match:
                exit_code = int(match.group(1))
                line = line[:match.start()]
                out += line
                break
            out += line
        return exit_code, out

    def _win_exec_command_with_stream(self, command, environment=None):
        channel = self.client.get_transport().open_session()
        if environment:
            channel.update_environment(environment)
        channel.set_combine_stderr(True)
        channel.get_pty(width=102)
        channel.exec_command(command)
        stdout = channel.makefile("rb", -1)
        out = stdout.readline()
        while out:
            yield channel.exit_status, self._decode(out)
            out = stdout.readline()
        yield channel.recv_exit_status(), self._decode(out)

    def exec_command_with_stream(self, command, environment=None):
        channel = self._get_channel()
        command = self._handle_command(command, environment)
        channel.send(command)
        exit_code, line = -1, ''
        while True:
            line = self._decode(channel.recv(8196))
            if not line:
                break
            match = self.regex.search(line)
            if match:
                exit_code = int(match.group(1))
                line = line[:match.start()]
                break
            yield exit_code, line
        yield exit_code, line

    def put_file(self, local_path, remote_path):
        sftp = self._get_sftp()
        sftp.put(local_path, remote_path)

    def put_file_by_fl(self, fl, remote_path, callback=None):
        sftp = self._get_sftp()
        sftp.putfo(fl, remote_path, callback=callback)

    def list_dir_attr(self, path):
        sftp = self._get_sftp()
        return sftp.listdir_attr(path)

    def sftp_stat(self, path):
        sftp = self._get_sftp()
        return sftp.stat(path)

    def remove_file(self, path):
        sftp = self._get_sftp()
        sftp.remove(path)

    def _get_channel(self):
        if self.channel:
            return self.channel

        counter = 0
        self.channel = self.client.invoke_shell()
        command = 'set +o zle\nset -o no_nomatch\nexport PS1= && stty -echo\n'
        if self.default_env:
            command += f'{self.default_env}\n'
        command += f'echo {self.eof} $?\n'
        self.channel.send(command.encode())
        while True:
            if self.channel.recv_ready():
                line = self._decode(self.channel.recv(8196))
                if self.regex.search(line):
                    self.stdout = self.channel.makefile('r')
                    break
            elif counter >= 100:
                self.client.close()
                raise Exception('Wait spug response timeout')
            else:
                counter += 1
                time.sleep(0.1)
        return self.channel

    def _get_sftp(self):
        if self.sftp:
            return self.sftp

        self.sftp = self.client.open_sftp()
        return self.sftp

    def _make_env_command(self, environment):
        if not environment:
            return None
        str_envs = []
        for k, v in environment.items():
            k = k.replace('-', '_')
            if isinstance(v, str):
                v = v.replace("'", "'\"'\"'")
            str_envs.append(f"{k}='{v}'")
        str_envs = ' '.join(str_envs)
        return f'export {str_envs}'

    def _handle_command(self, command, environment):
        new_command = commands = ''
        if not self.already_init:
            commands = 'export SPUG_EXEC_FILE=$(mktemp)\n'
            commands += 'trap \'rm -f $SPUG_EXEC_FILE\' EXIT\n'
            self.already_init = True

        env_command = self._make_env_command(environment)
        if env_command:
            new_command += f'{env_command}\n'
        new_command += command
        new_command += f'\necho {self.eof} $?\n'
        b64_command = base64.standard_b64encode(new_command.encode())
        commands += f'echo {b64_command.decode()} | base64 -di > $SPUG_EXEC_FILE\n'
        commands += 'source $SPUG_EXEC_FILE\n'
        return commands

    def _decode(self, content):
        try:
            content = content.decode()
        except UnicodeDecodeError:
            content = content.decode(encoding='GBK', errors='ignore')
        return content

    def __enter__(self):
        self.get_client()
        transport = self.client.get_transport()
        if 'windows' in transport.remote_version.lower():
            self.exec_command = self.exec_command_raw
            self.exec_command_with_stream = self._win_exec_command_with_stream
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.client.close()
        self.client = None
Ejemplo n.º 34
0
class SshProxy(SubprocessProxy):
    def __init__(self, clean):
        self._host = get_workbench().get_option("ssh.host")
        self._user = get_workbench().get_option("ssh.user")
        self._password = get_workbench().get_option("ssh.password")
        self._executable = get_workbench().get_option("ssh.executable")
        self._thread_commands = collections.deque()
        self._client = None
        self._sftp = None
        self._proc = None
        self._starting = True

        self._thread_commands.append(InlineCommand("connect"))
        super().__init__(clean, self._executable)
        Thread(target=self._process_thread_commands, daemon=True).start()

    def send_command(self, cmd: CommandToBackend) -> Optional[str]:
        if cmd.name in ["write_file", "read_file", "upload", "download"]:
            self._thread_commands.append(cmd)
        else:
            super().send_command(cmd)

    def _handle_local_commands(self):
        pass

    def _get_launcher_with_args(self):
        return [
            self._get_remote_program_directory() +
            "/thonny/backend_launcher.py"
        ]

    def _process_thread_commands(self):
        while True:
            cmd = self._thread_commands.popleft()
            if cmd.name == "quit":
                return
            elif cmd.name == "connect":
                self._connect()
            elif cmd.name == "launch":
                self._execute_backend

    def _connect(self):
        pass

    def _launch_backend(self):
        pass

    def _start_background_process(self, clean=None, extra_args=[]):
        self._response_queue = collections.deque()
        self._thread_commands = collections.deque()
        """
        # prepare environment
        env = get_environment_for_python_subprocess(self._executable)
        # variables controlling communication with the back-end process
        env["PYTHONIOENCODING"] = "utf-8"

        # because cmd line option -u won't reach child processes
        # see https://github.com/thonny/thonny/issues/808
        env["PYTHONUNBUFFERED"] = "1"

        # Let back-end know about plug-ins
        env["THONNY_USER_DIR"] = THONNY_USER_DIR
        env["THONNY_FRONTEND_SYS_PATH"] = repr(sys.path)

        env["THONNY_LANGUAGE"] = get_workbench().get_option("general.language")
        env["FRIENDLY_TRACEBACK_LEVEL"] = str(
            get_workbench().get_option("assistance.friendly_traceback_level")
        )

        if get_workbench().in_debug_mode():
            env["THONNY_DEBUG"] = "1"
        elif "THONNY_DEBUG" in env:
            del env["THONNY_DEBUG"]

        if not os.path.exists(self._executable):
            raise UserError(
                "Interpreter (%s) not found. Please recheck corresponding option!"
                % self._executable
            )
        """

        cmd_line = [
            self._executable,
            "-u",  # unbuffered IO
            "-B",  # don't write pyo/pyc files
            # (to avoid problems when using different Python versions without write permissions)
        ] + self._get_launcher_with_args()

        cmd_line_str = " ".join(map(shlex.quote, cmd_line))

        debug("Starting the backend: %s %s", cmd_line_str,
              get_workbench().get_local_cwd())

        # Don't remain waiting
        self._starting = True
        self._thread_commands.append(InlineCommand("launch"),
                                     cmd_line=cmd_line_str)

    def _connect_in_background(self, cmd_line_str):
        try:
            from paramiko.client import SSHClient
        except ImportError:
            self._show_error(
                "SSH connection requires an extra package -- 'paramiko'.\n" +
                "You can install it via 'Tools => Manage plug-ins' or via system package manager."
            )
            return

        self._client = SSHClient()
        self._client.load_system_host_keys()
        self._client.connect(hostname=self._host,
                             username=self._user,
                             password=self._password)

        self._check_install_thonny_backend()

        env = {
            "THONNY_USER_DIR": "~/.config/Thonny",
            "THONNY_FRONTEND_SYS_PATH": "[]",
        }

        stdin, stdout, stderr = self._client.exec_command(cmd_line_str,
                                                          bufsize=0,
                                                          timeout=None,
                                                          get_pty=False,
                                                          environment=env)
        self._proc = SshPopen(stdin, stdout, stderr)

        # setup asynchronous output listeners
        Thread(target=self._listen_stdout, args=(stdout, ),
               daemon=True).start()
        Thread(target=self._listen_stderr, args=(stderr, ),
               daemon=True).start()

        self._send_msg(ToplevelCommand("get_environment_info"))
        self._starting = False

    def _get_initial_cwd(self):
        return "~/"

    def _get_sftp(self):
        if self._sftp is None:
            import paramiko

            # TODO: does it get closed properly after process gets killed?
            self._sftp = paramiko.SFTPClient.from_transport(
                self._client.get_transport())

        return self._sftp

    def is_terminated(self):
        return not self._starting and not super().process_is_alive()

    def interrupt(self):
        # Don't interrupt local process, but direct it to device
        # self._send_msg(InterruptCommand())
        self._proc.stdin.write("\x03")

    def fetch_next_message(self):
        msg = super().fetch_next_message()
        if msg and "welcome_text" in msg:
            msg["welcome_text"] += " (" + self._executable + " on " + self._host + ")"
        return msg

    def supports_remote_files(self):
        return self._proc is not None

    def uses_local_filesystem(self):
        return False

    def ready_for_remote_file_operations(self):
        return self._proc is not None and get_runner(
        ).is_waiting_toplevel_command()

    def supports_remote_directories(self):
        return self._cwd is not None and self._cwd != ""

    def supports_trash(self):
        return False

    def is_connected(self):
        return self._proc is not None

    def _show_error(self, text):
        get_shell().print_error("\n" + text + "\n")

    def disconnect(self):
        self.destroy()

    def get_node_label(self):
        return self._host

    def get_exe_dirs(self):
        return []

    def destroy(self):
        super().destroy()
        self._client.close()

    def _get_remote_program_directory(self):
        return "/tmp/thonny-backend-" + thonny.get_version()

    def _check_install_thonny_backend(self):
        import paramiko

        sftp = paramiko.SFTPClient.from_transport(self._client.get_transport())

        launch_dir = self._get_remote_program_directory()
        try:
            sftp.stat(launch_dir)
            # dir is present
            if not launch_dir.endswith("-dev"):
                # don't overwrite unless in dev mode
                return

        except IOError:
            sftp.mkdir(launch_dir)

        # copy backend_launcher next to thonny module so that thonny module will be in path
        # import thonny.backend_launcher
        # sftp.put(thonny.backend_launcher.__file__, launch_dir + "/launch.py")

        # other files go to thonny directory
        module_dir = launch_dir + "/thonny"
        try:
            sftp.stat(module_dir)
        except IOError:
            sftp.mkdir(module_dir)

        import thonny.backend_launcher
        import thonny.backend
        import thonny.common
        import thonny.ast_utils

        # create empty __init__.py
        # sftp.open(module_dir + "/__init__.py", "w").close()

        for module in [
                thonny,
                thonny.backend_launcher,
                thonny.backend,
                thonny.common,
                thonny.ast_utils,
        ]:
            local_path = module.__file__
            remote_path = module_dir + "/" + os.path.basename(local_path)
            sftp.put(local_path, remote_path)

        sftp.close()
Ejemplo n.º 35
0
class RemoteHost:

    PATH_CONFIG = '~/.ssh/config'

    def __init__(self,
                 host,
                 credential=None,
                 stdout_queue=None,
                 stderr_queue=None):
        self._password = None
        self.host = host
        self.credential = credential
        self.stdout_queue = stdout_queue
        self.stderr_queue = stderr_queue
        self.client = SSHClient()
        self.client.set_missing_host_key_policy(AutoAddPolicy())
        self.config = SSHConfig()
        self.forward_agent = False
        self.parse_config_if_exists()

    def __enter__(self):
        self.client.load_system_host_keys()
        self.connect()
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.disconnect()

    def parse_config_if_exists(self):
        path = os.path.expanduser(self.PATH_CONFIG)
        if os.path.exists(path):
            with open(path) as f:
                self.config.parse(f)

    def connect(self):
        host_config = self.config.lookup(self.host)
        log.debug('ssh-config: %s' % host_config)

        forwardagent = host_config.get('forwardagent')
        if forwardagent is not None and forwardagent == 'yes':
            self.forward_agent = True
            log.debug('forwarding agent is enabled')

        param = {
            'sock': None,
            'timeout': 10.0,
        }

        proxy_command = host_config.get('proxycommand')
        if proxy_command is not None:
            param['sock'] = ProxyCommand(proxy_command)

        if self.credential is not None:
            param.update(self.credential.connect_param)

        self.client.connect(self.host, **param)
        log.info('connected: %s' % self.host)

    def disconnect(self):
        self.client.close()

    @property
    def password(self):
        if self._password is None:
            self._password = getpass('Password: '******'ascii') + b'\n'
        return self._password

    @password.setter
    def password(self, value):
        self._password = value

    def is_active(self):
        transport = self.client.get_transport()
        return transport is not None and transport.is_active()

    async def run_async(self,
                        command,
                        stdin_param=None,
                        get_pty=False,
                        interval=10,
                        callback=None,
                        buff_size=1024):
        if not self.is_active():
            log.error('ssh connection is not active')
            return

        transport = self.client.get_transport()
        channel = transport.open_session()

        if self.forward_agent:
            AgentRequestHandler(channel)

        if get_pty:
            channel.get_pty()
            channel.set_combine_stderr(True)

        channel.exec_command(command.encode(sys.stdout.encoding))

        if stdin_param is not None:
            stdin = channel.makefile('wb', -1)
            stdin.write(stdin_param)
            stdin.flush()

        while not channel.exit_status_ready():
            if callback is not None:
                callback(channel)
            await asyncio.sleep(interval)

        if channel.exit_status != 0:
            log.warn('%s exit status is not zero: %d' %
                     (self.host, channel.exit_status))

        stdout = stderr = b''
        while channel.recv_ready():
            stdout += channel.recv(buff_size)
            await asyncio.sleep(1)
        if stdout and self.stdout_queue is not None:
            s = stdout.decode(sys.stdout.encoding)
            self.stdout_queue.put_nowait(s)

        while channel.recv_stderr_ready():
            stderr += channel.recv_stderr(buff_size)
            await asyncio.sleep(1)
        if stderr and self.stderr_queue is not None:
            s = stderr.decode(sys.stderr.encoding)
            self.stderr_queue.put_nowait(s)

        return channel.exit_status, stdout, stderr

    async def sudo_async(self, command, password=None, **kwargs):
        cmd = 'sudo %s' % command
        if password is not None:
            self.password = password
        return await self.run_async(cmd,
                                    stdin_param=self.password,
                                    get_pty=True,
                                    **kwargs)

    def run(self, command, stdin_param=None, combine_stderr=False, **kwargs):
        if not self.is_active():
            log.error('ssh connection is not active')
            return

        cmd = command.encode(sys.stdout.encoding)
        stdin, stdout, stderr = self.client.exec_command(cmd, **kwargs)
        if stdin_param is not None:
            stdin.write(stdin_param)
            stdin.flush()

        stdout.channel.set_combine_stderr(combine_stderr)
        out = stdout.read().decode(sys.stdout.encoding)
        if out:
            log.info('%s stdout:\n%s' % (self.host, out.strip()))

        status_code = stdout.channel.recv_exit_status()
        if status_code != 0:
            err = stderr.read().decode(sys.stdout.encoding)
            if err:
                log.warn('%s stderr:\n%s' % (self.host, err.strip()))

        return status_code

    def sudo(self, command):
        cmd = 'sudo %s' % command
        return self.run(cmd, stdin_param=self.password, get_pty=True)