Пример #1
0
    def _file_transport_command(self, in_path, out_path, sftp_action):
        # scp and sftp require square brackets for IPv6 addresses, but
        # accept them for hostnames and IPv4 addresses too.
        host = '[%s]' % self.host

        # Transfer methods to try
        methods = []

        # Use the transfer_method option if set, otherwise use scp_if_ssh
        ssh_transfer_method = self._play_context.ssh_transfer_method
        if ssh_transfer_method is not None:
            if not (ssh_transfer_method in ('smart', 'sftp', 'scp', 'piped')):
                raise AnsibleOptionsError(
                    'transfer_method needs to be one of [smart|sftp|scp|piped]'
                )
            if ssh_transfer_method == 'smart':
                methods = ['sftp', 'scp', 'piped']
            else:
                methods = [ssh_transfer_method]
        else:
            # since this can be a non-bool now, we need to handle it correctly
            scp_if_ssh = C.DEFAULT_SCP_IF_SSH
            if not isinstance(scp_if_ssh, bool):
                scp_if_ssh = scp_if_ssh.lower()
                if scp_if_ssh in BOOLEANS:
                    scp_if_ssh = boolean(scp_if_ssh, strict=False)
                elif scp_if_ssh != 'smart':
                    raise AnsibleOptionsError(
                        'scp_if_ssh needs to be one of [smart|True|False]')
            if scp_if_ssh == 'smart':
                methods = ['sftp', 'scp', 'piped']
            elif scp_if_ssh is True:
                methods = ['scp']
            else:
                methods = ['sftp']

        for method in methods:
            returncode = stdout = stderr = None
            if method == 'sftp':
                cmd = self._build_command(self.get_option('sftp_executable'),
                                          to_bytes(host))
                in_data = u"{0} {1} {2}\n".format(sftp_action,
                                                  shlex_quote(in_path),
                                                  shlex_quote(out_path))
                in_data = to_bytes(in_data, nonstring='passthru')
                (returncode, stdout, stderr) = self._bare_run(cmd,
                                                              in_data,
                                                              checkrc=False)
            elif method == 'scp':
                scp = self.get_option('scp_executable')
                if sftp_action == 'get':
                    cmd = self._build_command(
                        scp, u'{0}:{1}'.format(host, shlex_quote(in_path)),
                        out_path)
                else:
                    cmd = self._build_command(
                        scp, in_path,
                        u'{0}:{1}'.format(host, shlex_quote(out_path)))
                in_data = None
                (returncode, stdout, stderr) = self._bare_run(cmd,
                                                              in_data,
                                                              checkrc=False)
            elif method == 'piped':
                if sftp_action == 'get':
                    # we pass sudoable=False to disable pty allocation, which
                    # would end up mixing stdout/stderr and screwing with newlines
                    (returncode, stdout, stderr) = self.exec_command(
                        'dd if=%s bs=%s' % (in_path, BUFSIZE), sudoable=False)
                    out_file = open(
                        to_bytes(out_path, errors='surrogate_or_strict'),
                        'wb+')
                    out_file.write(stdout)
                    out_file.close()
                else:
                    in_data = open(
                        to_bytes(in_path, errors='surrogate_or_strict'),
                        'rb').read()
                    in_data = to_bytes(in_data, nonstring='passthru')
                    (returncode, stdout, stderr) = self.exec_command(
                        'dd of=%s bs=%s' % (out_path, BUFSIZE),
                        in_data=in_data,
                        sudoable=False)

            # Check the return code and rollover to next method if failed
            if returncode == 0:
                return (returncode, stdout, stderr)
            else:
                # If not in smart mode, the data will be printed by the raise below
                if len(methods) > 1:
                    display.warning(
                        msg=
                        '%s transfer mechanism failed on %s. Use ANSIBLE_DEBUG=1 to see detailed information'
                        % (method, host))
                    display.debug(msg='%s' % to_native(stdout))
                    display.debug(msg='%s' % to_native(stderr))

        if returncode == 255:
            raise AnsibleConnectionFailure(
                "Failed to connect to the host via %s: %s" %
                (method, to_native(stderr)))
        else:
            raise AnsibleError("failed to transfer file to %s %s:\n%s\n%s" %
                               (to_native(in_path), to_native(out_path),
                                to_native(stdout), to_native(stderr)))
Пример #2
0
    def _make_tmp_path(self, remote_user=None):
        '''
        Create and return a temporary path on a remote box.
        '''

        if remote_user is None:
            remote_user = self._play_context.remote_user

        basefile = 'ansible-tmp-%s-%s' % (time.time(), random.randint(0, 2**48))
        use_system_tmp = False

        if self._play_context.become and self._play_context.become_user not in ('root', remote_user):
            use_system_tmp = True

        tmp_mode = 0o700

        if use_system_tmp:
            tmpdir = None
        else:
            tmpdir = self._remote_expand_user(C.DEFAULT_REMOTE_TMP, sudoable=False)

        cmd = self._connection._shell.mkdtemp(basefile, use_system_tmp, tmp_mode, tmpdir)
        result = self._low_level_execute_command(cmd, sudoable=False)

        # error handling on this seems a little aggressive?
        if result['rc'] != 0:
            if result['rc'] == 5:
                output = 'Authentication failure.'
            elif result['rc'] == 255 and self._connection.transport in ('ssh',):

                if self._play_context.verbosity > 3:
                    output = u'SSH encountered an unknown error. The output was:\n%s%s' % (result['stdout'], result['stderr'])
                else:
                    output = (u'SSH encountered an unknown error during the connection. '
                              'We recommend you re-run the command using -vvvv, which will enable SSH debugging output to help diagnose the issue')

            elif u'No space left on device' in result['stderr']:
                output = result['stderr']
            else:
                output = ('Authentication or permission failure. '
                          'In some cases, you may have been able to authenticate and did not have permissions on the target directory. '
                          'Consider changing the remote temp path in ansible.cfg to a path rooted in "/tmp". '
                          'Failed command was: %s, exited with result %d' % (cmd, result['rc']))
            if 'stdout' in result and result['stdout'] != u'':
                output = output + u": %s" % result['stdout']
            raise AnsibleConnectionFailure(output)
        else:
            self._cleanup_remote_tmp = True

        try:
            stdout_parts = result['stdout'].strip().split('%s=' % basefile, 1)
            rc = self._connection._shell.join_path(stdout_parts[-1], u'').splitlines()[-1]
        except IndexError:
            # stdout was empty or just space, set to / to trigger error in next if
            rc = '/'

        # Catch failure conditions, files should never be
        # written to locations in /.
        if rc == '/':
            raise AnsibleError('failed to resolve remote temporary directory from %s: `%s` returned empty string' % (basefile, cmd))

        return rc
Пример #3
0
    def edit_config(self,
                    candidate=None,
                    commit=True,
                    replace=None,
                    comment=None):

        operations = self.get_device_operations()
        self.check_edit_config_capabiltiy(operations, candidate, commit,
                                          replace, comment)

        if (commit is False) and (not self.supports_sessions):
            raise ValueError(
                'check mode is not supported without configuration session')

        resp = {}
        session = None
        if self.supports_sessions:
            session = 'ansible_%s' % int(time.time())
            resp.update({'session': session})
            self.send_command('configure session %s' % session)
            if replace:
                self.send_command('rollback clean-config')
        else:
            self.send_command('configure')

        results = []
        requests = []
        multiline = False
        for line in to_list(candidate):
            if not isinstance(line, collections.Mapping):
                line = {'command': line}

            cmd = line['command']
            if cmd == 'end':
                continue
            elif cmd.startswith('banner') or multiline:
                multiline = True
            elif cmd == 'EOF' and multiline:
                multiline = False

            if multiline:
                line['sendonly'] = True

            if cmd != 'end' and cmd[0] != '!':
                try:
                    results.append(self.send_command(**line))
                    requests.append(cmd)
                except AnsibleConnectionFailure as e:
                    self.discard_changes(session)
                    raise AnsibleConnectionFailure(e.message)

        resp['request'] = requests
        resp['response'] = results
        if self.supports_sessions:
            out = self.send_command('show session-config diffs')
            if out:
                resp['diff'] = out.strip()

            if commit:
                self.commit()
            else:
                self.discard_changes(session)
        else:
            self.send_command('end')
        return resp
Пример #4
0
 def _handle_command_timeout(self, signum, frame):
     msg = 'command timeout triggered, timeout value is %s secs.\nSee the timeout setting options in the Network Debug and Troubleshooting Guide.'\
           % self.get_option('persistent_command_timeout')
     self.queue_message('log', msg)
     raise AnsibleConnectionFailure(msg)
Пример #5
0
    def _connect(self):
        if not HAS_NCCLIENT:
            raise AnsibleError("%s: %s" % (missing_required_lib("ncclient"), to_native(NCCLIENT_IMP_ERR)))

        self.queue_message('log', 'ssh connection done, starting ncclient')

        allow_agent = True
        if self._play_context.password is not None:
            allow_agent = False
        setattr(self._play_context, 'allow_agent', allow_agent)

        self.key_filename = self._play_context.private_key_file or self.get_option('private_key_file')
        if self.key_filename:
            self.key_filename = str(os.path.expanduser(self.key_filename))

        self._ssh_config = self.get_option('netconf_ssh_config')
        if self._ssh_config in BOOLEANS_TRUE:
            self._ssh_config = True
        elif self._ssh_config in BOOLEANS_FALSE:
            self._ssh_config = None

        # Try to guess the network_os if the network_os is set to auto
        if self._network_os == 'auto':
            for cls in netconf_loader.all(class_only=True):
                network_os = cls.guess_network_os(self)
                if network_os:
                    self.queue_message('vvv', 'discovered network_os %s' % network_os)
                    self._network_os = network_os

        # If we have tried to detect the network_os but were unable to i.e. network_os is still 'auto'
        # then use default as the network_os

        if self._network_os == 'auto':
            # Network os not discovered. Set it to default
            self.queue_message('vvv', 'Unable to discover network_os. Falling back to default.')
            self._network_os = 'default'
        try:
            ncclient_device_handler = self.netconf.get_option('ncclient_device_handler')
        except KeyError:
            ncclient_device_handler = 'default'
        self.queue_message('vvv', 'identified ncclient device handler: %s.' % ncclient_device_handler)
        device_params = {'name': ncclient_device_handler}

        try:
            port = self._play_context.port or 830
            self.queue_message('vvv', "ESTABLISH NETCONF SSH CONNECTION FOR USER: %s on PORT %s TO %s WITH SSH_CONFIG = %s" %
                               (self._play_context.remote_user, port, self._play_context.remote_addr, self._ssh_config))
            self._manager = manager.connect(
                host=self._play_context.remote_addr,
                port=port,
                username=self._play_context.remote_user,
                password=self._play_context.password,
                key_filename=self.key_filename,
                hostkey_verify=self.get_option('host_key_checking'),
                look_for_keys=self.get_option('look_for_keys'),
                device_params=device_params,
                allow_agent=self._play_context.allow_agent,
                timeout=self.get_option('persistent_connect_timeout'),
                ssh_config=self._ssh_config
            )

            self._manager._timeout = self.get_option('persistent_command_timeout')
        except SSHUnknownHostError as exc:
            raise AnsibleConnectionFailure(to_native(exc))
        except ImportError:
            raise AnsibleError("connection=netconf is not supported on {0}".format(self._network_os))

        if not self._manager.connected:
            return 1, b'', b'not connected'

        self.queue_message('log', 'ncclient manager object created successfully')

        self._connected = True

        super(Connection, self)._connect()

        return 0, to_bytes(self._manager.session_id, errors='surrogate_or_strict'), b''
Пример #6
0
    def _kerb_auth(self, principal, password):
        if password is None:
            password = ""

        self._kerb_ccache = tempfile.NamedTemporaryFile()
        display.vvvvv("creating Kerberos CC at %s" % self._kerb_ccache.name)
        krb5ccname = "FILE:%s" % self._kerb_ccache.name
        os.environ["KRB5CCNAME"] = krb5ccname
        krb5env = dict(KRB5CCNAME=krb5ccname)

        # stores various flags to call with kinit, we currently only use this
        # to set -f so we can get a forward-able ticket (cred delegation)
        kinit_flags = []
        if boolean(
                self.get_option('_extras').get(
                    'ansible_winrm_kerberos_delegation', False)):
            kinit_flags.append('-f')

        kinit_cmdline = [self._kinit_cmd]
        kinit_cmdline.extend(kinit_flags)
        kinit_cmdline.append(principal)

        # pexpect runs the process in its own pty so it can correctly send
        # the password as input even on MacOS which blocks subprocess from
        # doing so. Unfortunately it is not available on the built in Python
        # so we can only use it if someone has installed it
        if HAS_PEXPECT:
            proc_mechanism = "pexpect"
            command = kinit_cmdline.pop(0)
            password = to_text(password,
                               encoding='utf-8',
                               errors='surrogate_or_strict')

            display.vvvv("calling kinit with pexpect for principal %s" %
                         principal)
            try:
                child = pexpect.spawn(command,
                                      kinit_cmdline,
                                      timeout=60,
                                      env=krb5env,
                                      echo=False)
            except pexpect.ExceptionPexpect as err:
                err_msg = "Kerberos auth failure when calling kinit cmd " \
                          "'%s': %s" % (command, to_native(err))
                raise AnsibleConnectionFailure(err_msg)

            try:
                child.expect(".*:")
                child.sendline(password)
            except OSError as err:
                # child exited before the pass was sent, Ansible will raise
                # error based on the rc below, just display the error here
                display.vvvv("kinit with pexpect raised OSError: %s" %
                             to_native(err))

            # technically this is the stdout + stderr but to match the
            # subprocess error checking behaviour, we will call it stderr
            stderr = child.read()
            child.wait()
            rc = child.exitstatus
        else:
            proc_mechanism = "subprocess"
            password = to_bytes(password,
                                encoding='utf-8',
                                errors='surrogate_or_strict')

            display.vvvv("calling kinit with subprocess for principal %s" %
                         principal)
            try:
                p = subprocess.Popen(kinit_cmdline,
                                     stdin=subprocess.PIPE,
                                     stdout=subprocess.PIPE,
                                     stderr=subprocess.PIPE,
                                     env=krb5env)

            except OSError as err:
                err_msg = "Kerberos auth failure when calling kinit cmd " \
                          "'%s': %s" % (self._kinit_cmd, to_native(err))
                raise AnsibleConnectionFailure(err_msg)

            stdout, stderr = p.communicate(password + b'\n')
            rc = p.returncode != 0

        if rc != 0:
            # one last attempt at making sure the password does not exist
            # in the output
            exp_msg = to_native(stderr.strip())
            exp_msg = exp_msg.replace(to_native(password), "<redacted>")

            err_msg = "Kerberos auth failure for principal %s with %s: %s" \
                      % (principal, proc_mechanism, exp_msg)
            raise AnsibleConnectionFailure(err_msg)

        display.vvvvv("kinit succeeded for principal %s" % principal)
Пример #7
0
    def _connect(self):
        '''
        Connects to the remote device and starts the terminal
        '''
        if not self.connected:
            self.paramiko_conn._set_log_channel(self._get_log_channel())
            self.paramiko_conn.force_persistence = self.force_persistence

            command_timeout = self.get_option('persistent_command_timeout')
            max_pause = min([
                self.get_option('persistent_connect_timeout'), command_timeout
            ])
            retries = self.get_option('network_cli_retries')
            total_pause = 0

            for attempt in range(retries + 1):
                try:
                    ssh = self.paramiko_conn._connect()
                    break
                except Exception as e:
                    pause = 2**(attempt + 1)
                    if attempt == retries or total_pause >= max_pause:
                        raise AnsibleConnectionFailure(
                            to_text(e, errors='surrogate_or_strict'))
                    else:
                        msg = (
                            u"network_cli_retry: attempt: %d, caught exception(%s), "
                            u"pausing for %d seconds" %
                            (attempt + 1,
                             to_text(e, errors='surrogate_or_strict'), pause))

                        self.queue_message('vv', msg)
                        time.sleep(pause)
                        total_pause += pause
                        continue

            self.queue_message('vvvv', 'ssh connection done, setting terminal')
            self._connected = True

            self._ssh_shell = ssh.ssh.invoke_shell()
            self._ssh_shell.settimeout(command_timeout)

            self.queue_message(
                'vvvv',
                'loaded terminal plugin for network_os %s' % self._network_os)

            terminal_initial_prompt = self.get_option(
                'terminal_initial_prompt'
            ) or self._terminal.terminal_initial_prompt
            terminal_initial_answer = self.get_option(
                'terminal_initial_answer'
            ) or self._terminal.terminal_initial_answer
            newline = self.get_option(
                'terminal_inital_prompt_newline'
            ) or self._terminal.terminal_inital_prompt_newline
            check_all = self.get_option(
                'terminal_initial_prompt_checkall') or False

            self.receive(prompts=terminal_initial_prompt,
                         answer=terminal_initial_answer,
                         newline=newline,
                         check_all=check_all)

            if self._play_context.become and self._play_context.become_method == 'enable':
                self.queue_message('vvvv', 'firing event: on_become')
                auth_pass = self._play_context.become_pass
                self._terminal.on_become(passwd=auth_pass)

            self.queue_message('vvvv', 'firing event: on_open_shell()')
            self._terminal.on_open_shell()

            self.queue_message('vvvv',
                               'ssh connection has completed successfully')

        return self
Пример #8
0
    def receive_libssh(
        self,
        command=None,
        prompts=None,
        answer=None,
        newline=True,
        prompt_retry_check=False,
        check_all=False,
    ):
        self._command_response = resp = b""
        command_prompt_matched = False
        handled = False
        errored_response = None

        while True:

            if command_prompt_matched:
                data = self._read_post_command_prompt_match()
                if data:
                    command_prompt_matched = False
                else:
                    return self._command_response
            else:
                try:
                    data = self._ssh_shell.read_bulk_response()
                # TODO: Should be ConnectionError when pylibssh drops Python 2 support
                except OSError:
                    # Socket has closed
                    break

            if not data:
                continue
            self._last_recv_window = self._strip(data)
            resp += self._last_recv_window
            self._window_count += 1

            self._log_messages("response-%s: %s" % (self._window_count, data))

            if prompts and not handled:
                handled = self._handle_prompt(resp, prompts, answer, newline,
                                              False, check_all)
                self._matched_prompt_window = self._window_count
            elif (prompts and handled and prompt_retry_check
                  and self._matched_prompt_window + 1 == self._window_count):
                # check again even when handled, if same prompt repeats in next window
                # (like in the case of a wrong enable password, etc) indicates
                # value of answer is wrong, report this as error.
                if self._handle_prompt(
                        resp,
                        prompts,
                        answer,
                        newline,
                        prompt_retry_check,
                        check_all,
                ):
                    raise AnsibleConnectionFailure(
                        "For matched prompt '%s', answer is not valid" %
                        self._matched_cmd_prompt)

            if self._find_error(resp):
                # We can't exit here, as we need to drain the buffer in case
                # the error isn't fatal, and will be using the buffer again
                errored_response = resp

            if self._find_prompt(resp):
                if errored_response:
                    raise AnsibleConnectionFailure(errored_response)
                self._last_response = data
                self._command_response += self._sanitize(resp, command)
                command_prompt_matched = True
Пример #9
0
 def _exec_cli_command(self, cmd, check_rc=True):
     """Executes a CLI command on the device"""
     rc, out, err = self._connection.exec_command(cmd)
     if check_rc and rc != 0:
         raise AnsibleConnectionFailure(err)
     return rc, out, err
Пример #10
0
 def _handle_command_timeout(self, signum, frame):
     msg = 'command timeout triggered, timeout value is %s secs.\nSee the timeout setting options in the Network Debug and Troubleshooting Guide.'\
           % self.get_option('persistent_command_timeout')
     display.display(msg, log_only=True)
     raise AnsibleConnectionFailure(msg)
Пример #11
0
    def receive_paramiko(
        self,
        command=None,
        prompts=None,
        answer=None,
        newline=True,
        prompt_retry_check=False,
        check_all=False,
    ):

        recv = BytesIO()
        cache_socket_timeout = self._ssh_shell.gettimeout()
        command_prompt_matched = False
        handled = False
        errored_response = None

        while True:
            if command_prompt_matched:
                try:
                    signal.signal(signal.SIGALRM,
                                  self._handle_buffer_read_timeout)
                    signal.setitimer(signal.ITIMER_REAL,
                                     self._buffer_read_timeout)
                    data = self._ssh_shell.recv(256)
                    signal.alarm(0)
                    self._log_messages("response-%s: %s" %
                                       (self._window_count + 1, data))
                    # if data is still received on channel it indicates the prompt string
                    # is wrongly matched in between response chunks, continue to read
                    # remaining response.
                    command_prompt_matched = False

                    # restart command_timeout timer
                    signal.signal(signal.SIGALRM, self._handle_command_timeout)
                    signal.alarm(self._command_timeout)

                except AnsibleCmdRespRecv:
                    # reset socket timeout to global timeout
                    self._ssh_shell.settimeout(cache_socket_timeout)
                    return self._command_response
            else:
                data = self._ssh_shell.recv(256)
                self._log_messages("response-%s: %s" %
                                   (self._window_count + 1, data))
            # when a channel stream is closed, received data will be empty
            if not data:
                break

            recv.write(data)
            offset = recv.tell() - 256 if recv.tell() > 256 else 0
            recv.seek(offset)

            window = self._strip(recv.read())
            self._last_recv_window = window
            self._window_count += 1

            if prompts and not handled:
                handled = self._handle_prompt(window, prompts, answer, newline,
                                              False, check_all)
                self._matched_prompt_window = self._window_count
            elif (prompts and handled and prompt_retry_check
                  and self._matched_prompt_window + 1 == self._window_count):
                # check again even when handled, if same prompt repeats in next window
                # (like in the case of a wrong enable password, etc) indicates
                # value of answer is wrong, report this as error.
                if self._handle_prompt(
                        window,
                        prompts,
                        answer,
                        newline,
                        prompt_retry_check,
                        check_all,
                ):
                    raise AnsibleConnectionFailure(
                        "For matched prompt '%s', answer is not valid" %
                        self._matched_cmd_prompt)

            if self._find_error(window):
                # We can't exit here, as we need to drain the buffer in case
                # the error isn't fatal, and will be using the buffer again
                errored_response = window

            if self._find_prompt(window):
                if errored_response:
                    raise AnsibleConnectionFailure(errored_response)
                self._last_response = recv.getvalue()
                resp = self._strip(self._last_response)
                self._command_response = self._sanitize(resp, command)
                if self._buffer_read_timeout == 0.0:
                    # reset socket timeout to global timeout
                    self._ssh_shell.settimeout(cache_socket_timeout)
                    return self._command_response
                else:
                    command_prompt_matched = True
Пример #12
0
    def _connect(self):
        if not HAS_NCCLIENT:
            raise AnsibleError("%s: %s" % (
                missing_required_lib("ncclient"),
                to_native(NCCLIENT_IMP_ERR),
            ))

        self.queue_message("log", "ssh connection done, starting ncclient")

        allow_agent = True
        if self._play_context.password is not None:
            allow_agent = False
        setattr(self._play_context, "allow_agent", allow_agent)

        self.key_filename = (self._play_context.private_key_file
                             or self.get_option("private_key_file"))
        if self.key_filename:
            self.key_filename = str(os.path.expanduser(self.key_filename))

        self._ssh_config = self.get_option("netconf_ssh_config")
        if self._ssh_config in BOOLEANS_TRUE:
            self._ssh_config = True
        elif self._ssh_config in BOOLEANS_FALSE:
            self._ssh_config = None

        # Try to guess the network_os if the network_os is set to auto
        if self._network_os == "auto":
            for cls in netconf_loader.all(class_only=True):
                network_os = cls.guess_network_os(self)
                if network_os:
                    self.queue_message("vvv",
                                       "discovered network_os %s" % network_os)
                    self._network_os = network_os

        # If we have tried to detect the network_os but were unable to i.e. network_os is still 'auto'
        # then use default as the network_os

        if self._network_os == "auto":
            # Network os not discovered. Set it to default
            self.queue_message(
                "vvv",
                "Unable to discover network_os. Falling back to default.",
            )
            self._network_os = "default"
        try:
            ncclient_device_handler = self.netconf.get_option(
                "ncclient_device_handler")
        except KeyError:
            ncclient_device_handler = "default"
        self.queue_message(
            "vvv",
            "identified ncclient device handler: %s." %
            ncclient_device_handler,
        )
        device_params = {"name": ncclient_device_handler}

        try:
            port = self._play_context.port or 830
            self.queue_message(
                "vvv",
                "ESTABLISH NETCONF SSH CONNECTION FOR USER: %s on PORT %s TO %s WITH SSH_CONFIG = %s"
                % (
                    self._play_context.remote_user,
                    port,
                    self._play_context.remote_addr,
                    self._ssh_config,
                ),
            )

            params = dict(
                host=self._play_context.remote_addr,
                port=port,
                username=self._play_context.remote_user,
                password=self._play_context.password,
                key_filename=self.key_filename,
                hostkey_verify=self.get_option("host_key_checking"),
                look_for_keys=self.get_option("look_for_keys"),
                device_params=device_params,
                allow_agent=self._play_context.allow_agent,
                timeout=self.get_option("persistent_connect_timeout"),
                ssh_config=self._ssh_config,
            )
            # sock is only supported by ncclient >= 0.6.10, and will error if
            # included on older versions. We check the version in
            # _get_proxy_command, so if this returns a value, the version is
            # fine and we have something to send. Otherwise, don't even send
            # the option to support older versions of ncclient
            sock = self._get_proxy_command(port)
            if sock:
                params["sock"] = sock

            self._manager = manager.connect(**params)

            self._manager._timeout = self.get_option(
                "persistent_command_timeout")
        except SSHUnknownHostError as exc:
            raise AnsibleConnectionFailure(to_native(exc))
        except AuthenticationError as exc:
            if str(exc).startswith("FileNotFoundError"):
                raise AnsibleError(
                    "Encountered FileNotFoundError in ncclient connect. Does {0} exist?"
                    .format(self.key_filename))
            raise
        except ImportError:
            raise AnsibleError(
                "connection=netconf is not supported on {0}".format(
                    self._network_os))

        if not self._manager.connected:
            return 1, b"", b"not connected"

        self.queue_message("log",
                           "ncclient manager object created successfully")

        self._connected = True

        super(Connection, self)._connect()

        return (
            0,
            to_bytes(self._manager.session_id, errors="surrogate_or_strict"),
            b"",
        )
Пример #13
0
 def on_open_shell(self):
     try:
         for cmd in ('terminal length 0', 'terminal width 511'):
             self._exec_cli_command(cmd)
     except AnsibleConnectionFailure:
         raise AnsibleConnectionFailure('unable to set terminal parameters')
Пример #14
0
    def edit_config(
        self, candidate=None, commit=True, replace=None, comment=None
    ):

        operations = self.get_device_operations()
        self.check_edit_config_capability(
            operations, candidate, commit, replace, comment
        )

        if (commit is False) and (not self.supports_sessions()):
            raise ValueError(
                "check mode is not supported without configuration session"
            )

        resp = {}
        session = None
        if self.supports_sessions():
            session = "ansible_%s" % int(time.time())
            resp.update({"session": session})
            self.send_command("configure session %s" % session)
            if replace:
                self.send_command("rollback clean-config")
        else:
            self.send_command("configure")

        results = []
        requests = []
        multiline = False
        for line in to_list(candidate):
            if not isinstance(line, Mapping):
                line = {"command": line}

            cmd = line["command"]
            if cmd == "end":
                continue
            if cmd.startswith("banner") or multiline:
                multiline = True
            elif cmd == "EOF" and multiline:
                multiline = False

            if multiline:
                line["sendonly"] = True

            if cmd != "end" and not cmd.startswith("!"):
                try:
                    results.append(self.send_command(**line))
                    requests.append(cmd)
                except AnsibleConnectionFailure as e:
                    self.discard_changes(session)
                    raise AnsibleConnectionFailure(e.message)

        resp["request"] = requests
        resp["response"] = results
        if self.supports_sessions():
            out = self.send_command("show session-config diffs")
            if out:
                resp["diff"] = out.strip()

            if commit:
                self.commit()
            else:
                self.discard_changes(session)
        else:
            self.send_command("end")
        return resp
Пример #15
0
    def receive_libssh(
        self,
        command=None,
        prompts=None,
        answer=None,
        newline=True,
        prompt_retry_check=False,
        check_all=False,
    ):
        self._command_response = resp = b""
        command_prompt_matched = False
        handled = False

        while True:

            if command_prompt_matched:
                data = self._read_post_command_prompt_match()
                if data:
                    command_prompt_matched = False
                else:
                    return self._command_response
            else:
                data = self._ssh_shell.read_bulk_response()

            if not data:
                continue
            self._last_recv_window = self._strip(data)
            resp += self._last_recv_window
            self._window_count += 1

            self._log_messages("response-%s: %s" % (self._window_count, data))

            if prompts and not handled:
                handled = self._handle_prompt(
                    resp, prompts, answer, newline, False, check_all
                )
                self._matched_prompt_window = self._window_count
            elif (
                prompts
                and handled
                and prompt_retry_check
                and self._matched_prompt_window + 1 == self._window_count
            ):
                # check again even when handled, if same prompt repeats in next window
                # (like in the case of a wrong enable password, etc) indicates
                # value of answer is wrong, report this as error.
                if self._handle_prompt(
                    resp,
                    prompts,
                    answer,
                    newline,
                    prompt_retry_check,
                    check_all,
                ):
                    raise AnsibleConnectionFailure(
                        "For matched prompt '%s', answer is not valid"
                        % self._matched_cmd_prompt
                    )

            if self._find_prompt(resp):
                self._last_response = data
                self._command_response += self._sanitize(resp, command)
                command_prompt_matched = True
Пример #16
0
    def _connect(self):
        '''
        Connects to the remote device and starts the terminal
        '''
        if self.connected:
            return

        p = connection_loader.get('paramiko', self._play_context, '/dev/null')
        p.set_options(
            direct={
                'look_for_keys':
                not bool(self._play_context.password
                         and not self._play_context.private_key_file)
            })
        p.force_persistence = self.force_persistence
        ssh = p._connect()

        display.vvvv('ssh connection done, setting terminal',
                     host=self._play_context.remote_addr)

        self._ssh_shell = ssh.ssh.invoke_shell()
        self._ssh_shell.settimeout(self._play_context.timeout)

        network_os = self._play_context.network_os
        if not network_os:
            raise AnsibleConnectionFailure(
                'Unable to automatically determine host network os. Please '
                'manually configure ansible_network_os value for this host')

        self._terminal = terminal_loader.get(network_os, self)
        if not self._terminal:
            raise AnsibleConnectionFailure('network os %s is not supported' %
                                           network_os)

        display.vvvv('loaded terminal plugin for network_os %s' % network_os,
                     host=self._play_context.remote_addr)

        self._cliconf = cliconf_loader.get(network_os, self)
        if self._cliconf:
            display.vvvv('loaded cliconf plugin for network_os %s' %
                         network_os,
                         host=self._play_context.remote_addr)
        else:
            display.vvvv('unable to load cliconf for network_os %s' %
                         network_os)

        self.receive()

        display.vvvv('firing event: on_open_shell()',
                     host=self._play_context.remote_addr)
        self._terminal.on_open_shell()

        if self._play_context.become and self._play_context.become_method == 'enable':
            display.vvvv('firing event: on_become',
                         host=self._play_context.remote_addr)
            auth_pass = self._play_context.become_pass
            self._terminal.on_become(passwd=auth_pass)

        display.vvvv('ssh connection has completed successfully',
                     host=self._play_context.remote_addr)
        self._connected = True

        return self
Пример #17
0
 def disable_pager(self):
     cmd = {u'command': u'terminal length 0'}
     try:
         self._exec_cli_command(u'terminal length 0')
     except AnsibleConnectionFailure:
         raise AnsibleConnectionFailure('unable to disable terminal pager')
Пример #18
0
    def _connect(self):
        if not HAS_NCCLIENT:
            raise AnsibleError(
                'The required "ncclient" python library is required to use the netconf connection type: %s.\n'
                'Please run pip install ncclient' %
                to_native(NCCLIENT_IMP_ERR))

        self.queue_message('log', 'ssh connection done, starting ncclient')

        allow_agent = True
        if self._play_context.password is not None:
            allow_agent = False
        setattr(self._play_context, 'allow_agent', allow_agent)

        self.key_filename = self._play_context.private_key_file or self.get_option(
            'private_key_file')
        if self.key_filename:
            self.key_filename = str(os.path.expanduser(self.key_filename))

        if self._network_os == 'default':
            for cls in netconf_loader.all(class_only=True):
                network_os = cls.guess_network_os(self)
                if network_os:
                    self.queue_message('log',
                                       'discovered network_os %s' % network_os)
                    self._network_os = network_os

        device_params = {
            'name':
            NETWORK_OS_DEVICE_PARAM_MAP.get(self._network_os)
            or self._network_os
        }

        ssh_config = self.get_option('netconf_ssh_config')
        if ssh_config in BOOLEANS_TRUE:
            ssh_config = True
        elif ssh_config in BOOLEANS_FALSE:
            ssh_config = None

        try:
            port = self._play_context.port or 830
            self.queue_message(
                'vvv',
                "ESTABLISH NETCONF SSH CONNECTION FOR USER: %s on PORT %s TO %s"
                % (self._play_context.remote_user, port,
                   self._play_context.remote_addr))
            self._manager = manager.connect(
                host=self._play_context.remote_addr,
                port=port,
                username=self._play_context.remote_user,
                password=self._play_context.password,
                key_filename=self.key_filename,
                hostkey_verify=self.get_option('host_key_checking'),
                look_for_keys=self.get_option('look_for_keys'),
                device_params=device_params,
                allow_agent=self._play_context.allow_agent,
                timeout=self.get_option('persistent_connect_timeout'),
                ssh_config=ssh_config)
        except SSHUnknownHostError as exc:
            raise AnsibleConnectionFailure(to_native(exc))
        except ImportError as exc:
            raise AnsibleError(
                "connection=netconf is not supported on {0}".format(
                    self._network_os))

        if not self._manager.connected:
            return 1, b'', b'not connected'

        self.queue_message('log',
                           'ncclient manager object created successfully')

        self._connected = True

        super(Connection, self)._connect()

        return 0, to_bytes(self._manager.session_id,
                           errors='surrogate_or_strict'), b''
Пример #19
0
    def _winrm_connect(self):
        '''
        Establish a WinRM connection over HTTP/HTTPS.
        '''
        display.vvv(
            "ESTABLISH WINRM CONNECTION FOR USER: %s on PORT %s TO %s" %
            (self._winrm_user, self._winrm_port, self._winrm_host),
            host=self._winrm_host)

        winrm_host = self._winrm_host
        if HAS_IPADDRESS:
            display.vvvv("checking if winrm_host %s is an IPv6 address" %
                         winrm_host)
            try:
                ipaddress.IPv6Address(winrm_host)
            except ipaddress.AddressValueError:
                pass
            else:
                winrm_host = "[%s]" % winrm_host

        netloc = '%s:%d' % (winrm_host, self._winrm_port)
        endpoint = urlunsplit(
            (self._winrm_scheme, netloc, self._winrm_path, '', ''))
        errors = []
        for transport in self._winrm_transport:
            if transport == 'kerberos':
                if not HAVE_KERBEROS:
                    errors.append(
                        'kerberos: the python kerberos library is not installed'
                    )
                    continue
                if self._kerb_managed:
                    self._kerb_auth(self._winrm_user, self._winrm_pass)
            display.vvvvv('WINRM CONNECT: transport=%s endpoint=%s' %
                          (transport, endpoint),
                          host=self._winrm_host)
            try:
                winrm_kwargs = self._winrm_kwargs.copy()
                if self._winrm_connection_timeout:
                    winrm_kwargs[
                        'operation_timeout_sec'] = self._winrm_connection_timeout
                    winrm_kwargs[
                        'read_timeout_sec'] = self._winrm_connection_timeout + 1
                protocol = Protocol(endpoint,
                                    transport=transport,
                                    **winrm_kwargs)

                # open the shell from connect so we know we're able to talk to the server
                if not self.shell_id:
                    self.shell_id = protocol.open_shell(
                        codepage=65001)  # UTF-8
                    display.vvvvv('WINRM OPEN SHELL: %s' % self.shell_id,
                                  host=self._winrm_host)

                return protocol
            except Exception as e:
                err_msg = to_text(e).strip()
                if re.search(to_text(r'Operation\s+?timed\s+?out'), err_msg,
                             re.I):
                    raise AnsibleError('the connection attempt timed out')
                m = re.search(to_text(r'Code\s+?(\d{3})'), err_msg)
                if m:
                    code = int(m.groups()[0])
                    if code == 401:
                        err_msg = 'the specified credentials were rejected by the server'
                    elif code == 411:
                        return protocol
                errors.append(u'%s: %s' % (transport, err_msg))
                display.vvvvv(u'WINRM CONNECTION ERROR: %s\n%s' %
                              (err_msg, to_text(traceback.format_exc())),
                              host=self._winrm_host)
        if errors:
            raise AnsibleConnectionFailure(', '.join(map(to_native, errors)))
        else:
            raise AnsibleError('No transport found for WinRM connection')
Пример #20
0
    def _connect(self):
        super(Connection, self)._connect()

        display.display('ssh connection done, starting ncclient',
                        log_only=True)

        allow_agent = True
        if self._play_context.password is not None:
            allow_agent = False
        setattr(self._play_context, 'allow_agent', allow_agent)

        key_filename = None
        if self._play_context.private_key_file:
            key_filename = os.path.expanduser(
                self._play_context.private_key_file)

        network_os = self._play_context.network_os

        if not network_os:
            for cls in netconf_loader.all(class_only=True):
                network_os = cls.guess_network_os(self)
                if network_os:
                    display.display('discovered network_os %s' % network_os,
                                    log_only=True)

        device_params = {
            'name': (NETWORK_OS_DEVICE_PARAM_MAP.get(network_os) or network_os
                     or 'default')
        }

        ssh_config = os.getenv('ANSIBLE_NETCONF_SSH_CONFIG', False)
        if ssh_config in BOOLEANS_TRUE:
            ssh_config = True
        else:
            ssh_config = None

        try:
            self._manager = manager.connect(
                host=self._play_context.remote_addr,
                port=self._play_context.port or 830,
                username=self._play_context.remote_user,
                password=self._play_context.password,
                key_filename=str(key_filename),
                hostkey_verify=C.HOST_KEY_CHECKING,
                look_for_keys=C.PARAMIKO_LOOK_FOR_KEYS,
                device_params=device_params,
                allow_agent=self._play_context.allow_agent,
                timeout=self._play_context.timeout,
                ssh_config=ssh_config)
        except SSHUnknownHostError as exc:
            raise AnsibleConnectionFailure(str(exc))
        except ImportError as exc:
            raise AnsibleError(
                "connection=netconf is not supported on {0}".format(
                    network_os))

        if not self._manager.connected:
            return 1, b'', b'not connected'

        display.display('ncclient manager object created successfully',
                        log_only=True)

        self._connected = True

        self._netconf = netconf_loader.get(network_os, self)
        if self._netconf:
            display.display('loaded netconf plugin for network_os %s' %
                            network_os,
                            log_only=True)
        else:
            self._netconf = netconf_loader.get("default", self)
            display.display(
                'unable to load netconf plugin for network_os %s, falling back to default plugin'
                % network_os)

        return 0, to_bytes(self._manager.session_id,
                           errors='surrogate_or_strict'), b''
Пример #21
0
    def receive(self,
                command=None,
                prompts=None,
                answer=None,
                newline=True,
                prompt_retry_check=False,
                check_all=False):
        '''
        Handles receiving of output from command
        '''
        self._matched_prompt = None
        self._matched_cmd_prompt = None
        recv = BytesIO()
        handled = False
        command_prompt_matched = False
        matched_prompt_window = window_count = 0

        # set terminal regex values for command prompt and errors in response
        self._terminal_stderr_re = self._get_terminal_std_re(
            'terminal_stderr_re')
        self._terminal_stdout_re = self._get_terminal_std_re(
            'terminal_stdout_re')

        cache_socket_timeout = self._ssh_shell.gettimeout()
        command_timeout = self.get_option('persistent_command_timeout')
        self._validate_timeout_value(command_timeout,
                                     "persistent_command_timeout")
        if cache_socket_timeout != command_timeout:
            self._ssh_shell.settimeout(command_timeout)

        buffer_read_timeout = self.get_option('persistent_buffer_read_timeout')
        self._validate_timeout_value(buffer_read_timeout,
                                     "persistent_buffer_read_timeout")

        self._log_messages("command: %s" % command)
        while True:
            if command_prompt_matched:
                try:
                    signal.signal(signal.SIGALRM,
                                  self._handle_buffer_read_timeout)
                    signal.setitimer(signal.ITIMER_REAL, buffer_read_timeout)
                    data = self._ssh_shell.recv(256)
                    signal.alarm(0)
                    self._log_messages("response-%s: %s" %
                                       (window_count + 1, data))
                    # if data is still received on channel it indicates the prompt string
                    # is wrongly matched in between response chunks, continue to read
                    # remaining response.
                    command_prompt_matched = False

                    # restart command_timeout timer
                    signal.signal(signal.SIGALRM, self._handle_command_timeout)
                    signal.alarm(command_timeout)

                except AnsibleCmdRespRecv:
                    # reset socket timeout to global timeout
                    self._ssh_shell.settimeout(cache_socket_timeout)
                    return self._command_response
            else:
                data = self._ssh_shell.recv(256)
                self._log_messages("response-%s: %s" %
                                   (window_count + 1, data))
            # when a channel stream is closed, received data will be empty
            if not data:
                break

            recv.write(data)
            offset = recv.tell() - 256 if recv.tell() > 256 else 0
            recv.seek(offset)

            window = self._strip(recv.read())
            self._last_recv_window = window
            window_count += 1

            if prompts and not handled:
                handled = self._handle_prompt(window, prompts, answer, newline,
                                              False, check_all)
                matched_prompt_window = window_count
            elif prompts and handled and prompt_retry_check and matched_prompt_window + 1 == window_count:
                # check again even when handled, if same prompt repeats in next window
                # (like in the case of a wrong enable password, etc) indicates
                # value of answer is wrong, report this as error.
                if self._handle_prompt(window, prompts, answer, newline,
                                       prompt_retry_check, check_all):
                    raise AnsibleConnectionFailure(
                        "For matched prompt '%s', answer is not valid" %
                        self._matched_cmd_prompt)

            if self._find_prompt(window):
                self._last_response = recv.getvalue()
                resp = self._strip(self._last_response)
                self._command_response = self._sanitize(resp, command)
                if buffer_read_timeout == 0.0:
                    # reset socket timeout to global timeout
                    self._ssh_shell.settimeout(cache_socket_timeout)
                    return self._command_response
                else:
                    command_prompt_matched = True
Пример #22
0
    def _connect(self):
        super(Connection, self)._connect()

        display.display('ssh connection done, starting ncclient', log_only=True)

        allow_agent = True
        if self._play_context.password is not None:
            allow_agent = False
        setattr(self._play_context, 'allow_agent', allow_agent)

        self.key_filename = self._play_context.private_key_file or self.get_option('private_key_file')
        if self.key_filename:
            self.key_filename = str(os.path.expanduser(self.key_filename))

        if self._network_os == 'default':
            for cls in netconf_loader.all(class_only=True):
                network_os = cls.guess_network_os(self)
                if network_os:
                    display.display('discovered network_os %s' % network_os, log_only=True)
                    self._network_os = network_os

        device_params = {'name': NETWORK_OS_DEVICE_PARAM_MAP.get(self._network_os) or self._network_os}

        ssh_config = self.get_option('netconf_ssh_config')
        if ssh_config in BOOLEANS_TRUE:
            ssh_config = True
        elif ssh_config in BOOLEANS_FALSE:
            ssh_config = None

        try:
            self._manager = manager.connect(
                host=self._play_context.remote_addr,
                port=self._play_context.port or 830,
                username=self._play_context.remote_user,
                password=self._play_context.password,
                key_filename=self.key_filename,
                hostkey_verify=self.get_option('host_key_checking'),
                look_for_keys=self.get_option('look_for_keys'),
                device_params=device_params,
                allow_agent=self._play_context.allow_agent,
                timeout=self._play_context.timeout,
                ssh_config=ssh_config
            )
        except SSHUnknownHostError as exc:
            raise AnsibleConnectionFailure(str(exc))
        except ImportError as exc:
            raise AnsibleError("connection=netconf is not supported on {0}".format(self._network_os))

        if not self._manager.connected:
            return 1, b'', b'not connected'

        display.display('ncclient manager object created successfully', log_only=True)

        self._connected = True

        netconf = netconf_loader.get(self._network_os, self)
        if netconf:
            display.display('loaded netconf plugin for network_os %s' % self._network_os, log_only=True)
        else:
            netconf = netconf_loader.get("default", self)
            display.display('unable to load netconf plugin for network_os %s, falling back to default plugin' % self._network_os)
        self._implementation_plugins.append(netconf)

        super(Connection, self)._connect()

        return 0, to_bytes(self._manager.session_id, errors='surrogate_or_strict'), b''
Пример #23
0
 def _validate_timeout_value(self, timeout, timer_name):
     if timeout < 0:
         raise AnsibleConnectionFailure(
             "'%s' timer value '%s' is invalid, value should be greater than or equal to zero."
             % (timer_name, timeout))
Пример #24
0
    def exec_command(self, cmd, in_data=None, sudoable=True):
        """ run a command on the remote host """

        super(Connection, self).exec_command(
            cmd, in_data=in_data, sudoable=sudoable
        )

        if in_data:
            raise AnsibleError(
                "Internal Error: this module does not support optimized module pipelining"
            )

        bufsize = 4096

        try:
            self.chan = self.ssh.new_channel()
        except Exception as e:
            text_e = to_text(e)
            msg = u"Failed to open session"
            if text_e:
                msg += u": %s" % text_e
            raise AnsibleConnectionFailure(to_native(msg))

        # sudo usually requires a PTY (cf. requiretty option), therefore
        # we give it one by default (pty=True in ansible.cfg), and we try
        # to initialise from the calling environment when sudoable is enabled
        if self.get_option("pty") and sudoable:
            self.chan.request_shell()

        display.vvv("EXEC %s" % cmd, host=self._play_context.remote_addr)

        cmd = to_bytes(cmd, errors="surrogate_or_strict")

        result = None
        no_prompt_out = b""
        no_prompt_err = b""
        become_output = b""
        out = b""
        err = b""

        try:
            if self.become and self.become.expect_prompt():
                passprompt = False
                become_sucess = False
                self.chan.sendall(cmd)

                while not (become_sucess or passprompt):
                    display.debug("Waiting for Privilege Escalation input")
                    self.chan.poll(timeout=self._play_context.timeout)
                    chunk = self.chan.recv(bufsize)
                    display.debug("chunk is: %s" % chunk)

                    if not chunk:
                        if b"unknown user" in become_output:
                            n_become_user = to_native(
                                self.become.get_option(
                                    "become_user",
                                    playcontext=self._play_context,
                                )
                            )
                            raise AnsibleError(
                                "user %s does not exist" % n_become_user
                            )
                        else:
                            break
                            # raise AnsibleError('ssh connection closed waiting for password prompt')
                    become_output += chunk
                    # need to check every line because we might get lectured
                    # and we might get the middle of a line in a chunk
                    for line in become_output.splitlines(True):
                        if self.become.check_success(line):
                            become_sucess = True
                            break
                        if self.become.check_password_prompt(line):
                            passprompt = True
                            break
                if passprompt:
                    if self.become:
                        become_pass = self.become.get_option(
                            "become_pass", playcontext=self._play_context
                        )
                        self.chan.sendall(
                            to_bytes(become_pass, errors="surrogate_or_strict")
                            + b"\n"
                        )
                    else:
                        raise AnsibleError(
                            "A password is required but none was supplied"
                        )
                else:
                    no_prompt_out += become_output
                    no_prompt_err += become_output
            else:
                result = self.chan.exec_command(
                    to_text(cmd, errors="surrogate_or_strict")
                )
        except socket.timeout:
            raise AnsibleError(
                "ssh timed out waiting for privilege escalation.\n"
                + become_output
            )

        if result:
            rc = result.returncode
            out = result.stdout
            err = result.stderr
        else:
            rc = self.chan.get_channel_exit_status()
        return rc, out, err
Пример #25
0
    def _run(self, cmd, in_data, sudoable=True, checkrc=True):
        '''
        Starts the command and communicates with it until it ends.
        '''

        display_cmd = list(map(shlex_quote, map(to_text, cmd)))
        display.vvv(u'SSH: EXEC {0}'.format(u' '.join(display_cmd)),
                    host=self.host)

        # Start the given command. If we don't need to pipeline data, we can try
        # to use a pseudo-tty (ssh will have been invoked with -tt). If we are
        # pipelining data, or can't create a pty, we fall back to using plain
        # old pipes.

        p = None

        if isinstance(cmd, (text_type, binary_type)):
            cmd = to_bytes(cmd)
        else:
            cmd = list(map(to_bytes, cmd))

        if not in_data:
            try:
                # Make sure stdin is a proper pty to avoid tcgetattr errors
                master, slave = pty.openpty()
                if PY3 and self._play_context.password:
                    p = subprocess.Popen(cmd,
                                         stdin=slave,
                                         stdout=subprocess.PIPE,
                                         stderr=subprocess.PIPE,
                                         pass_fds=self.sshpass_pipe)
                else:
                    p = subprocess.Popen(cmd,
                                         stdin=slave,
                                         stdout=subprocess.PIPE,
                                         stderr=subprocess.PIPE)
                stdin = os.fdopen(master, 'wb', 0)
                os.close(slave)
            except (OSError, IOError):
                p = None

        if not p:
            if PY3 and self._play_context.password:
                p = subprocess.Popen(cmd,
                                     stdin=subprocess.PIPE,
                                     stdout=subprocess.PIPE,
                                     stderr=subprocess.PIPE,
                                     pass_fds=self.sshpass_pipe)
            else:
                p = subprocess.Popen(cmd,
                                     stdin=subprocess.PIPE,
                                     stdout=subprocess.PIPE,
                                     stderr=subprocess.PIPE)
            stdin = p.stdin

        # If we are using SSH password authentication, write the password into
        # the pipe we opened in _build_command.

        if self._play_context.password:
            os.close(self.sshpass_pipe[0])
            try:
                os.write(self.sshpass_pipe[1],
                         to_bytes(self._play_context.password) + b'\n')
            except OSError as e:
                # Ignore broken pipe errors if the sshpass process has exited.
                if e.errno != errno.EPIPE or p.poll() is None:
                    raise
            os.close(self.sshpass_pipe[1])

        #
        # SSH state machine
        #

        # Now we read and accumulate output from the running process until it
        # exits. Depending on the circumstances, we may also need to write an
        # escalation password and/or pipelined input to the process.

        states = [
            'awaiting_prompt', 'awaiting_escalation', 'ready_to_send',
            'awaiting_exit'
        ]

        # Are we requesting privilege escalation? Right now, we may be invoked
        # to execute sftp/scp with sudoable=True, but we can request escalation
        # only when using ssh. Otherwise we can send initial data straightaway.

        state = states.index('ready_to_send')
        if b'ssh' in cmd:
            if self._play_context.prompt:
                # We're requesting escalation with a password, so we have to
                # wait for a password prompt.
                state = states.index('awaiting_prompt')
                display.debug(u'Initial state: %s: %s' %
                              (states[state], self._play_context.prompt))
            elif self._play_context.become and self._play_context.success_key:
                # We're requesting escalation without a password, so we have to
                # detect success/failure before sending any initial data.
                state = states.index('awaiting_escalation')
                display.debug(u'Initial state: %s: %s' %
                              (states[state], self._play_context.success_key))

        # We store accumulated stdout and stderr output from the process here,
        # but strip any privilege escalation prompt/confirmation lines first.
        # Output is accumulated into tmp_*, complete lines are extracted into
        # an array, then checked and removed or copied to stdout or stderr. We
        # set any flags based on examining the output in self._flags.

        b_stdout = b_stderr = b''
        b_tmp_stdout = b_tmp_stderr = b''

        self._flags = dict(become_prompt=False,
                           become_success=False,
                           become_error=False,
                           become_nopasswd_error=False)

        # select timeout should be longer than the connect timeout, otherwise
        # they will race each other when we can't connect, and the connect
        # timeout usually fails
        timeout = 2 + self._play_context.timeout
        rpipes = [p.stdout, p.stderr]
        for fd in rpipes:
            fcntl.fcntl(fd, fcntl.F_SETFL,
                        fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)

        # If we can send initial data without waiting for anything, we do so
        # before we call select.

        if states[state] == 'ready_to_send' and in_data:
            self._send_initial_data(stdin, in_data)
            state += 1

        while True:
            rfd, wfd, efd = select.select(rpipes, [], [], timeout)

            # We pay attention to timeouts only while negotiating a prompt.

            if not rfd:
                if state <= states.index('awaiting_escalation'):
                    # If the process has already exited, then it's not really a
                    # timeout; we'll let the normal error handling deal with it.
                    if p.poll() is not None:
                        break
                    self._terminate_process(p)
                    raise AnsibleError(
                        'Timeout (%ds) waiting for privilege escalation prompt: %s'
                        % (timeout, to_native(b_stdout)))

            # Read whatever output is available on stdout and stderr, and stop
            # listening to the pipe if it's been closed.

            if p.stdout in rfd:
                b_chunk = p.stdout.read()
                if b_chunk == b'':
                    rpipes.remove(p.stdout)
                    # When ssh has ControlMaster (+ControlPath/Persist) enabled, the
                    # first connection goes into the background and we never see EOF
                    # on stderr. If we see EOF on stdout, lower the select timeout
                    # to reduce the time wasted selecting on stderr if we observe
                    # that the process has not yet existed after this EOF. Otherwise
                    # we may spend a long timeout period waiting for an EOF that is
                    # not going to arrive until the persisted connection closes.
                    timeout = 1
                b_tmp_stdout += b_chunk
                display.debug("stdout chunk (state=%s):\n>>>%s<<<\n" %
                              (state, to_text(b_chunk)))

            if p.stderr in rfd:
                b_chunk = p.stderr.read()
                if b_chunk == b'':
                    rpipes.remove(p.stderr)
                b_tmp_stderr += b_chunk
                display.debug("stderr chunk (state=%s):\n>>>%s<<<\n" %
                              (state, to_text(b_chunk)))

            # We examine the output line-by-line until we have negotiated any
            # privilege escalation prompt and subsequent success/error message.
            # Afterwards, we can accumulate output without looking at it.

            if state < states.index('ready_to_send'):
                if b_tmp_stdout:
                    b_output, b_unprocessed = self._examine_output(
                        'stdout', states[state], b_tmp_stdout, sudoable)
                    b_stdout += b_output
                    b_tmp_stdout = b_unprocessed

                if b_tmp_stderr:
                    b_output, b_unprocessed = self._examine_output(
                        'stderr', states[state], b_tmp_stderr, sudoable)
                    b_stderr += b_output
                    b_tmp_stderr = b_unprocessed
            else:
                b_stdout += b_tmp_stdout
                b_stderr += b_tmp_stderr
                b_tmp_stdout = b_tmp_stderr = b''

            # If we see a privilege escalation prompt, we send the password.
            # (If we're expecting a prompt but the escalation succeeds, we
            # didn't need the password and can carry on regardless.)

            if states[state] == 'awaiting_prompt':
                if self._flags['become_prompt']:
                    display.debug('Sending become_pass in response to prompt')
                    stdin.write(
                        to_bytes(self._play_context.become_pass) + b'\n')
                    self._flags['become_prompt'] = False
                    state += 1
                elif self._flags['become_success']:
                    state += 1

            # We've requested escalation (with or without a password), now we
            # wait for an error message or a successful escalation.

            if states[state] == 'awaiting_escalation':
                if self._flags['become_success']:
                    display.debug('Escalation succeeded')
                    self._flags['become_success'] = False
                    state += 1
                elif self._flags['become_error']:
                    display.debug('Escalation failed')
                    self._terminate_process(p)
                    self._flags['become_error'] = False
                    raise AnsibleError('Incorrect %s password' %
                                       self._play_context.become_method)
                elif self._flags['become_nopasswd_error']:
                    display.debug('Escalation requires password')
                    self._terminate_process(p)
                    self._flags['become_nopasswd_error'] = False
                    raise AnsibleError('Missing %s password' %
                                       self._play_context.become_method)
                elif self._flags['become_prompt']:
                    # This shouldn't happen, because we should see the "Sorry,
                    # try again" message first.
                    display.debug('Escalation prompt repeated')
                    self._terminate_process(p)
                    self._flags['become_prompt'] = False
                    raise AnsibleError('Incorrect %s password' %
                                       self._play_context.become_method)

            # Once we're sure that the privilege escalation prompt, if any, has
            # been dealt with, we can send any initial data and start waiting
            # for output.

            if states[state] == 'ready_to_send':
                if in_data:
                    self._send_initial_data(stdin, in_data)
                state += 1

            # Now we're awaiting_exit: has the child process exited? If it has,
            # and we've read all available output from it, we're done.

            if p.poll() is not None:
                if not rpipes or not rfd:
                    break
                # We should not see further writes to the stdout/stderr file
                # descriptors after the process has closed, set the select
                # timeout to gather any last writes we may have missed.
                timeout = 0
                continue

            # If the process has not yet exited, but we've already read EOF from
            # its stdout and stderr (and thus removed both from rpipes), we can
            # just wait for it to exit.

            elif not rpipes:
                p.wait()
                break

            # Otherwise there may still be outstanding data to read.

        # close stdin after process is terminated and stdout/stderr are read
        # completely (see also issue #848)
        stdin.close()

        if C.HOST_KEY_CHECKING:
            if cmd[0] == b"sshpass" and p.returncode == 6:
                raise AnsibleError(
                    'Using a SSH password instead of a key is not possible because Host Key checking is enabled and sshpass does not support this.  Please add this host\'s fingerprint to your known_hosts file to manage this host.'
                )

        controlpersisterror = b'Bad configuration option: ControlPersist' in b_stderr or b'unknown configuration option: ControlPersist' in b_stderr
        if p.returncode != 0 and controlpersisterror:
            raise AnsibleError(
                'using -c ssh on certain older ssh versions may not support ControlPersist, set ANSIBLE_SSH_ARGS="" (or ssh_args in [ssh_connection] section of the config file) before running again'
            )

        if p.returncode == 255 and in_data and checkrc:
            raise AnsibleConnectionFailure(
                'SSH Error: data could not be sent to the remote host. Make sure this host can be reached over ssh'
            )

        return (p.returncode, b_stdout, b_stderr)
Пример #26
0
    def _winrm_exec(self,
                    command,
                    args=(),
                    from_exec=False,
                    stdin_iterator=None):
        if not self.protocol:
            self.protocol = self._winrm_connect()
            self._connected = True
        if from_exec:
            display.vvvvv("WINRM EXEC %r %r" % (command, args),
                          host=self._winrm_host)
        else:
            display.vvvvvv("WINRM EXEC %r %r" % (command, args),
                           host=self._winrm_host)
        command_id = None
        try:
            stdin_push_failed = False
            command_id = self.protocol.run_command(
                self.shell_id,
                to_bytes(command),
                map(to_bytes, args),
                console_mode_stdin=(stdin_iterator is None))

            try:
                if stdin_iterator:
                    for (data, is_last) in stdin_iterator:
                        self._winrm_send_input(self.protocol,
                                               self.shell_id,
                                               command_id,
                                               data,
                                               eof=is_last)

            except Exception as ex:
                display.warning(
                    "ERROR DURING WINRM SEND INPUT - attempting to recover: %s %s"
                    % (type(ex).__name__, to_text(ex)))
                display.debug(traceback.format_exc())
                stdin_push_failed = True

            # NB: this can hang if the receiver is still running (eg, network failed a Send request but the server's still happy).
            # FUTURE: Consider adding pywinrm status check/abort operations to see if the target is still running after a failure.
            resptuple = self.protocol.get_command_output(
                self.shell_id, command_id)
            # ensure stdout/stderr are text for py3
            # FUTURE: this should probably be done internally by pywinrm
            response = Response(
                tuple(
                    to_text(v) if isinstance(v, binary_type) else v
                    for v in resptuple))

            # TODO: check result from response and set stdin_push_failed if we have nonzero
            if from_exec:
                display.vvvvv('WINRM RESULT %r' % to_text(response),
                              host=self._winrm_host)
            else:
                display.vvvvvv('WINRM RESULT %r' % to_text(response),
                               host=self._winrm_host)

            display.vvvvvv('WINRM STDOUT %s' % to_text(response.std_out),
                           host=self._winrm_host)
            display.vvvvvv('WINRM STDERR %s' % to_text(response.std_err),
                           host=self._winrm_host)

            if stdin_push_failed:
                # There are cases where the stdin input failed but the WinRM service still processed it. We attempt to
                # see if stdout contains a valid json return value so we can ignore this error
                try:
                    filtered_output, dummy = _filter_non_json_lines(
                        response.std_out)
                    json.loads(filtered_output)
                except ValueError:
                    # stdout does not contain a return response, stdin input was a fatal error
                    stderr = to_bytes(response.std_err, encoding='utf-8')
                    if stderr.startswith(b"#< CLIXML"):
                        stderr = _parse_clixml(stderr)

                    raise AnsibleError(
                        'winrm send_input failed; \nstdout: %s\nstderr %s' %
                        (to_native(response.std_out), to_native(stderr)))

            return response
        except requests.exceptions.Timeout as exc:
            raise AnsibleConnectionFailure('winrm connection error: %s' %
                                           to_native(exc))
        finally:
            if command_id:
                self.protocol.cleanup_command(self.shell_id, command_id)
Пример #27
0
    def exec_command(self, cmd, in_data=None, sudoable=True):
        ''' run a command on the remote host '''

        super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)

        if in_data:
            raise AnsibleError("Internal Error: this module does not support optimized module pipelining")

        bufsize = 4096

        try:
            self.ssh.get_transport().set_keepalive(5)
            chan = self.ssh.get_transport().open_session()
        except Exception as e:
            text_e = to_text(e)
            msg = u"Failed to open session"
            if text_e:
                msg += u": %s" % text_e
            raise AnsibleConnectionFailure(to_native(msg))

        # sudo usually requires a PTY (cf. requiretty option), therefore
        # we give it one by default (pty=True in ansible.cfg), and we try
        # to initialise from the calling environment when sudoable is enabled
        if self.get_option('pty') and sudoable:
            chan.get_pty(term=os.getenv('TERM', 'vt100'), width=int(os.getenv('COLUMNS', 0)), height=int(os.getenv('LINES', 0)))

        display.vvv("EXEC %s" % cmd, host=self._play_context.remote_addr)

        cmd = to_bytes(cmd, errors='surrogate_or_strict')

        no_prompt_out = b''
        no_prompt_err = b''
        become_output = b''

        try:
            chan.exec_command(cmd)
            if self.become and self.become.expect_prompt():
                passprompt = False
                become_sucess = False
                while not (become_sucess or passprompt):
                    display.debug('Waiting for Privilege Escalation input')

                    chunk = chan.recv(bufsize)
                    display.debug("chunk is: %s" % chunk)
                    if not chunk:
                        if b'unknown user' in become_output:
                            n_become_user = to_native(self.become.get_option('become_user',
                                                                             playcontext=self._play_context))
                            raise AnsibleError('user %s does not exist' % n_become_user)
                        else:
                            break
                            # raise AnsibleError('ssh connection closed waiting for password prompt')
                    become_output += chunk

                    # need to check every line because we might get lectured
                    # and we might get the middle of a line in a chunk
                    for l in become_output.splitlines(True):
                        if self.become.check_success(l):
                            become_sucess = True
                            break
                        elif self.become.check_password_prompt(l):
                            passprompt = True
                            break

                if passprompt:
                    if self.become:
                        become_pass = self.become.get_option('become_pass', playcontext=self._play_context)
                        chan.sendall(to_bytes(become_pass, errors='surrogate_or_strict') + b'\n')
                    else:
                        raise AnsibleError("A password is required but none was supplied")
                else:
                    no_prompt_out += become_output
                    no_prompt_err += become_output
        except socket.timeout:
            raise AnsibleError('ssh timed out waiting for privilege escalation.\n' + become_output)

        stdout = b''.join(chan.makefile('rb', bufsize))
        stderr = b''.join(chan.makefile_stderr('rb', bufsize))

        return (chan.recv_exit_status(), no_prompt_out + stdout, no_prompt_out + stderr)
Пример #28
0
    def _connect(self):
        """
        Connects to the remote device and starts the terminal
        """
        self._ssh_type = self.get_option("ssh_type")
        if self._play_context.verbosity > 3:
            logging.getLogger(self._ssh_type).setLevel(logging.DEBUG)

        self.queue_message(
            "vvvv", "invoked shell using ssh_type: %s" % self._ssh_type
        )

        if not self.connected:
            self.ssh_type_conn._set_log_channel(self._get_log_channel())
            self.ssh_type_conn.force_persistence = self.force_persistence

            command_timeout = self.get_option("persistent_command_timeout")
            max_pause = min(
                [
                    self.get_option("persistent_connect_timeout"),
                    command_timeout,
                ]
            )
            retries = self.get_option("network_cli_retries")
            total_pause = 0

            for attempt in range(retries + 1):
                try:
                    ssh = self.ssh_type_conn._connect()
                    break
                except AnsibleError:
                    raise
                except Exception as e:
                    pause = 2 ** (attempt + 1)
                    if attempt == retries or total_pause >= max_pause:
                        raise AnsibleConnectionFailure(
                            to_text(e, errors="surrogate_or_strict")
                        )
                    else:
                        msg = (
                            u"network_cli_retry: attempt: %d, caught exception(%s), "
                            u"pausing for %d seconds"
                            % (
                                attempt + 1,
                                to_text(e, errors="surrogate_or_strict"),
                                pause,
                            )
                        )

                        self.queue_message("vv", msg)
                        time.sleep(pause)
                        total_pause += pause
                        continue

            self.queue_message("vvvv", "ssh connection done, setting terminal")
            self._connected = True

            self._ssh_shell = ssh.ssh.invoke_shell()
            if self._ssh_type == "paramiko":
                self._ssh_shell.settimeout(command_timeout)

            self.queue_message(
                "vvvv",
                "loaded terminal plugin for network_os %s" % self._network_os,
            )

            terminal_initial_prompt = (
                self.get_option("terminal_initial_prompt")
                or self._terminal.terminal_initial_prompt
            )
            terminal_initial_answer = (
                self.get_option("terminal_initial_answer")
                or self._terminal.terminal_initial_answer
            )
            newline = (
                self.get_option("terminal_inital_prompt_newline")
                or self._terminal.terminal_inital_prompt_newline
            )
            check_all = (
                self.get_option("terminal_initial_prompt_checkall") or False
            )

            self.receive(
                prompts=terminal_initial_prompt,
                answer=terminal_initial_answer,
                newline=newline,
                check_all=check_all,
            )

            if self._play_context.become:
                self.queue_message("vvvv", "firing event: on_become")
                auth_pass = self._play_context.become_pass
                self._terminal.on_become(passwd=auth_pass)

            self.queue_message("vvvv", "firing event: on_open_shell()")
            self._terminal.on_open_shell()

            self.queue_message(
                "vvvv", "ssh connection has completed successfully"
            )

        return self
Пример #29
0
    def _connect_uncached(self):
        ''' activates the connection object '''

        if paramiko is None:
            raise AnsibleError("paramiko is not installed: %s" %
                               to_native(PARAMIKO_IMPORT_ERR))

        port = self._play_context.port or 22
        display.vvv(
            "ESTABLISH PARAMIKO SSH CONNECTION FOR USER: %s on PORT %s TO %s" %
            (self._play_context.remote_user, port,
             self._play_context.remote_addr),
            host=self._play_context.remote_addr)

        ssh = paramiko.SSHClient()

        # override paramiko's default logger name
        if self._log_channel is not None:
            ssh.set_log_channel(self._log_channel)

        self.keyfile = os.path.expanduser("~/.ssh/known_hosts")

        if self.get_option('host_key_checking'):
            for ssh_known_hosts in ("/etc/ssh/ssh_known_hosts",
                                    "/etc/openssh/ssh_known_hosts"):
                try:
                    # TODO: check if we need to look at several possible locations, possible for loop
                    ssh.load_system_host_keys(ssh_known_hosts)
                    break
                except IOError:
                    pass  # file was not found, but not required to function
            ssh.load_system_host_keys()

        ssh_connect_kwargs = self._parse_proxy_command(port)

        ssh.set_missing_host_key_policy(MyAddPolicy(self._new_stdin, self))

        conn_password = self.get_option(
            'password') or self._play_context.password

        allow_agent = True

        if conn_password is not None:
            allow_agent = False

        try:
            key_filename = None
            if self._play_context.private_key_file:
                key_filename = os.path.expanduser(
                    self._play_context.private_key_file)

            # paramiko 2.2 introduced auth_timeout parameter
            if LooseVersion(paramiko.__version__) >= LooseVersion('2.2.0'):
                ssh_connect_kwargs['auth_timeout'] = self._play_context.timeout

            ssh.connect(self._play_context.remote_addr.lower(),
                        username=self._play_context.remote_user,
                        allow_agent=allow_agent,
                        look_for_keys=self.get_option('look_for_keys'),
                        key_filename=key_filename,
                        password=conn_password,
                        timeout=self._play_context.timeout,
                        port=port,
                        **ssh_connect_kwargs)
        except paramiko.ssh_exception.BadHostKeyException as e:
            raise AnsibleConnectionFailure('host key mismatch for %s' %
                                           e.hostname)
        except paramiko.ssh_exception.AuthenticationException as e:
            msg = 'Failed to authenticate: {0}'.format(to_text(e))
            raise AnsibleAuthenticationFailure(msg)
        except Exception as e:
            msg = to_text(e)
            if u"PID check failed" in msg:
                raise AnsibleError(
                    "paramiko version issue, please upgrade paramiko on the machine running ansible"
                )
            elif u"Private key file is encrypted" in msg:
                msg = 'ssh %s@%s:%s : %s\nTo connect as a different user, use -u <username>.' % (
                    self._play_context.remote_user,
                    self._play_context.remote_addr, port, msg)
                raise AnsibleConnectionFailure(msg)
            else:
                raise AnsibleConnectionFailure(msg)

        return ssh
Пример #30
0
    def _connect_uncached(self):
        ''' activates the connection object '''

        if not HAVE_PARAMIKO:
            raise AnsibleError("paramiko is not installed")

        port = self._play_context.port or 22
        display.vvv("ESTABLISH CONNECTION FOR USER: %s on PORT %s TO %s" %
                    (self._play_context.remote_user, port,
                     self._play_context.remote_addr),
                    host=self._play_context.remote_addr)

        ssh = paramiko.SSHClient()

        self.keyfile = os.path.expanduser("~/.ssh/known_hosts")

        if C.HOST_KEY_CHECKING:
            for ssh_known_hosts in ("/etc/ssh/ssh_known_hosts",
                                    "/etc/openssh/ssh_known_hosts"):
                try:
                    #TODO: check if we need to look at several possible locations, possible for loop
                    ssh.load_system_host_keys(ssh_known_hosts)
                    break
                except IOError:
                    pass  # file was not found, but not required to function
            ssh.load_system_host_keys()

        sock_kwarg = self._parse_proxy_command(port)

        ssh.set_missing_host_key_policy(MyAddPolicy(self._new_stdin, self))

        allow_agent = True

        if self._play_context.password is not None:
            allow_agent = False

        try:
            key_filename = None
            if self._play_context.private_key_file:
                key_filename = os.path.expanduser(
                    self._play_context.private_key_file)

            ssh.connect(self._play_context.remote_addr,
                        username=self._play_context.remote_user,
                        allow_agent=allow_agent,
                        look_for_keys=C.PARAMIKO_LOOK_FOR_KEYS,
                        key_filename=key_filename,
                        password=self._play_context.password,
                        timeout=self._play_context.timeout,
                        port=port,
                        **sock_kwarg)
        except paramiko.ssh_exception.BadHostKeyException as e:
            raise AnsibleConnectionFailure('host key mismatch for %s' %
                                           e.hostname)
        except Exception as e:
            msg = str(e)
            if "PID check failed" in msg:
                raise AnsibleError(
                    "paramiko version issue, please upgrade paramiko on the machine running ansible"
                )
            elif "Private key file is encrypted" in msg:
                msg = 'ssh %s@%s:%s : %s\nTo connect as a different user, use -u <username>.' % (
                    self._play_context.remote_user,
                    self._play_context.remote_addr, port, msg)
                raise AnsibleConnectionFailure(msg)
            else:
                raise AnsibleConnectionFailure(msg)

        return ssh