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)))
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
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
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)
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''
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)
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
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
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
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)
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
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"", )
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')
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
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
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
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')
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''
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')
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''
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
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''
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))
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
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)
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)
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)
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
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
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