def send( self, command, prompt=None, answer=None, newline=True, sendonly=False, prompt_retry_check=False, check_all=False, ): """ Sends the command to the device in the opened shell """ if check_all: prompt_len = len(to_list(prompt)) answer_len = len(to_list(answer)) if prompt_len != answer_len: raise AssibleConnectionFailure( "Number of prompts (%s) is not same as that of answers (%s)" % (prompt_len, answer_len)) try: cmd = b"%s\r" % command self._history.append(cmd) self._ssh_shell.sendall(cmd) self._log_messages("send command: %s" % cmd) if sendonly: return response = self.receive(command, prompt, answer, newline, prompt_retry_check, check_all) return to_text(response, errors="surrogate_then_replace") except (socket.timeout, AttributeError): self.queue_message("error", traceback.format_exc()) raise AssibleConnectionFailure( "timeout value %s seconds reached while trying to send command: %s" % (self._ssh_shell.gettimeout(), command.strip()))
def __init__(self, play_context, new_stdin, *args, **kwargs): super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) self._url = None self._auth = None if self._network_os: self.httpapi = httpapi_loader.get(self._network_os, self) if self.httpapi: self._sub_plugin = { "type": "httpapi", "name": self.httpapi._load_name, "obj": self.httpapi, } self.queue_message( "vvvv", "loaded API plugin %s from path %s for network_os %s" % ( self.httpapi._load_name, self.httpapi._original_path, self._network_os, ), ) else: raise AssibleConnectionFailure( "unable to load API plugin for network_os %s" % self._network_os) else: raise AssibleConnectionFailure( "Unable to automatically determine host network os. Please " "manually configure assible_network_os value for this host") self.queue_message("log", "network_os is set to %s" % self._network_os)
def on_become(self, passwd=None): if self._get_prompt().endswith(b"#"): return cmd = {u"command": u"enable"} if passwd: # Note: python-3.5 cannot combine u"" and r"" together. Thus make # an r string and use to_text to ensure it's text on both py2 and py3. cmd[u"prompt"] = to_text(r"[\r\n]?(?:.*)?[Pp]assword: ?$", errors="surrogate_or_strict") cmd[u"answer"] = passwd cmd[u"prompt_retry_check"] = True try: self._exec_cli_command( to_bytes(json.dumps(cmd), errors="surrogate_or_strict")) prompt = self._get_prompt() if prompt is None or not prompt.endswith(b"#"): raise AssibleConnectionFailure( "failed to elevate privilege to enable mode still at prompt [%s]" % prompt) except AssibleConnectionFailure as e: prompt = self._get_prompt() raise AssibleConnectionFailure( "unable to elevate privilege to enable mode, at prompt [%s] with error: %s" % (prompt, e.message))
def __init__(self, play_context, new_stdin, *args, **kwargs): super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) self._ssh_shell = None self._matched_prompt = None self._matched_cmd_prompt = None self._matched_pattern = None self._last_response = None self._history = list() self._command_response = None self._last_recv_window = None self._terminal = None self.cliconf = None self._paramiko_conn = None # Managing prompt context self._check_prompt = False self._task_uuid = to_text(kwargs.get("task_uuid", "")) if self._play_context.verbosity > 3: logging.getLogger("paramiko").setLevel(logging.DEBUG) if self._network_os: self._terminal = terminal_loader.get(self._network_os, self) if not self._terminal: raise AssibleConnectionFailure( "network os %s is not supported" % self._network_os) self.cliconf = cliconf_loader.get(self._network_os, self) if self.cliconf: self._sub_plugin = { "type": "cliconf", "name": self.cliconf._load_name, "obj": self.cliconf, } self.queue_message( "vvvv", "loaded cliconf plugin %s from path %s for network_os %s" % ( self.cliconf._load_name, self.cliconf._original_path, self._network_os, ), ) else: self.queue_message( "vvvv", "unable to load cliconf for network_os %s" % self._network_os, ) else: raise AssibleConnectionFailure( "Unable to automatically determine host network os. Please " "manually configure assible_network_os value for this host") self.queue_message("log", "network_os is set to %s" % self._network_os)
def on_open_shell(self): try: for cmd in (b"set terminal length 0", b"set terminal width 512"): self._exec_cli_command(cmd) self._exec_cli_command(b"set terminal length %d" % self.terminal_length) except AssibleConnectionFailure: raise AssibleConnectionFailure("unable to set terminal parameters")
def _connect(self): if not HAS_PYPSRP: raise AssibleError("pypsrp or dependencies are not installed: %s" % to_native(PYPSRP_IMP_ERR)) super(Connection, self)._connect() self._build_kwargs() display.vvv("ESTABLISH PSRP CONNECTION FOR USER: %s ON PORT %s TO %s" % (self._psrp_user, self._psrp_port, self._psrp_host), host=self._psrp_host) if not self.runspace: connection = WSMan(**self._psrp_conn_kwargs) # create our psuedo host to capture the exit code and host output host_ui = PSHostUserInterface() self.host = PSHost(None, None, False, "Assible PSRP Host", None, host_ui, None) self.runspace = RunspacePool( connection, host=self.host, configuration_name=self._psrp_configuration_name) display.vvvvv( "PSRP OPEN RUNSPACE: auth=%s configuration=%s endpoint=%s" % (self._psrp_auth, self._psrp_configuration_name, connection.transport.endpoint), host=self._psrp_host) try: self.runspace.open() except AuthenticationError as e: raise AssibleConnectionFailure("failed to authenticate with " "the server: %s" % to_native(e)) except WinRMError as e: raise AssibleConnectionFailure( "psrp connection failure during runspace open: %s" % to_native(e)) except (ConnectionError, ConnectTimeout) as e: raise AssibleConnectionFailure( "Failed to connect to the host via PSRP: %s" % to_native(e)) self._connected = True return self
def _connect(self): ''' verify ''' if self.get_option('remote_user') == 'invaliduser' and self.get_option( 'password') == 'badpassword': raise AssibleConnectionFailure('Got invaliduser and badpassword') if not self._connected: display.vvv(u"ESTABLISH FAKELOCAL CONNECTION FOR USER: {0}".format( self._play_context.remote_user), host=self._play_context.remote_addr) self._connected = True return self
def send(self, path, data, **kwargs): """ Sends the command to the device over api """ url_kwargs = dict( timeout=self.get_option("persistent_command_timeout"), validate_certs=self.get_option("validate_certs"), use_proxy=self.get_option("use_proxy"), headers={}, ) url_kwargs.update(kwargs) if self._auth: # Avoid modifying passed-in headers headers = dict(kwargs.get("headers", {})) headers.update(self._auth) url_kwargs["headers"] = headers else: url_kwargs["force_basic_auth"] = True url_kwargs["url_username"] = self.get_option("remote_user") url_kwargs["url_password"] = self.get_option("password") try: url = self._url + path self._log_messages("send url '%s' with data '%s' and kwargs '%s'" % (url, data, url_kwargs)) response = open_url(url, data=data, **url_kwargs) except HTTPError as exc: is_handled = self.handle_httperror(exc) if is_handled is True: return self.send(path, data, **kwargs) elif is_handled is False: raise else: response = is_handled except URLError as exc: raise AssibleConnectionFailure( "Could not connect to {0}: {1}".format(self._url + path, exc.reason)) response_buffer = BytesIO() resp_data = response.read() self._log_messages("received response: '%s'" % resp_data) response_buffer.write(resp_data) # Try to assign a new auth token if one is given self._auth = self.update_auth(response, response_buffer) or self._auth response_buffer.seek(0) return response, response_buffer
def on_open_shell(self): try: self._exec_cli_command(b"terminal length 0") except AssibleConnectionFailure: raise AssibleConnectionFailure("unable to set terminal parameters") try: self._exec_cli_command(b"terminal width 512") try: self._exec_cli_command(b"terminal width 0") except AssibleConnectionFailure: pass except AssibleConnectionFailure: display.display( "WARNING: Unable to set terminal width, command responses may be truncated" )
def edit_config(self, candidate=None, commit=True, replace=None, comment=None): resp = {} operations = self.get_device_operations() self.check_edit_config_capability(operations, candidate, commit, replace, comment) results = [] requests = [] self.send_command("configure") for cmd in to_list(candidate): if not isinstance(cmd, Mapping): cmd = {"command": cmd} results.append(self.send_command(**cmd)) requests.append(cmd["command"]) out = self.get("compare") out = to_text(out, errors="surrogate_or_strict") diff_config = out if not out.startswith("No changes") else None if diff_config: if commit: try: self.commit(comment) except AssibleConnectionFailure as e: msg = "commit failed: %s" % e.message self.discard_changes() raise AssibleConnectionFailure(msg) else: self.send_command("exit") else: self.discard_changes() else: self.send_command("exit") if (to_text(self._connection.get_prompt(), errors="surrogate_or_strict").strip().endswith("#")): self.discard_changes() if diff_config: resp["diff"] = diff_config resp["response"] = results resp["request"] = requests return resp
def _find_prompt(self, response): """Searches the buffered response for a matching command prompt """ errored_response = None is_error_message = False for regex in self._terminal_stderr_re: if regex.search(response): is_error_message = True # Check if error response ends with command prompt if not # receive it buffered prompt for regex in self._terminal_stdout_re: match = regex.search(response) if match: errored_response = response self._matched_pattern = regex.pattern self._matched_prompt = match.group() self._log_messages( "matched error regex '%s' from response '%s'" % (self._matched_pattern, errored_response)) break if not is_error_message: for regex in self._terminal_stdout_re: match = regex.search(response) if match: self._matched_pattern = regex.pattern self._matched_prompt = match.group() self._log_messages( "matched cli prompt '%s' with regex '%s' from response '%s'" % ( self._matched_prompt, self._matched_pattern, response, )) if not errored_response: return True if errored_response: raise AssibleConnectionFailure(errored_response) return False
def _get_terminal_std_re(self, option): terminal_std_option = self.get_option(option) terminal_std_re = [] if terminal_std_option: for item in terminal_std_option: if "pattern" not in item: raise AssibleConnectionFailure( "'pattern' is a required key for option '%s'," " received option value is %s" % (option, item)) pattern = br"%s" % to_bytes(item["pattern"]) flag = item.get("flags", 0) if flag: flag = getattr(re, flag.split(".")[1]) terminal_std_re.append(re.compile(pattern, flag)) else: # To maintain backward compatibility terminal_std_re = getattr(self._terminal, option) return terminal_std_re
def set_cli_prompt_context(self): """ Make sure we are in the operational cli mode :return: None """ if self._connection.connected: out = self._connection.get_prompt() if out is None: raise AssibleConnectionFailure( message= u"cli prompt is not identified from the last received" u" response window: %s" % self._connection._last_recv_window) if re.search( r"config.*\)#", to_text(out, errors="surrogate_then_replace").strip(), ): self._connection.queue_message( "vvvv", "wrong context, sending end to device") self._connection.send_command("end")
def _update_cli_prompt_context(self, config_context=None, exit_command='exit'): """ Update the cli prompt context to ensure it is in operational mode :param config_context: It is string value to identify if the current cli prompt ends with config mode prompt :param exit_command: Command to execute to exit the config mode :return: None """ out = self._connection.get_prompt() if out is None: raise AssibleConnectionFailure( message=u'cli prompt is not identified from the last received' u' response window: %s' % self._connection._last_recv_window) while True: out = to_text(out, errors='surrogate_then_replace').strip() if config_context and out.endswith(config_context): self._connection.queue_message( 'vvvv', 'wrong context, sending exit to device') self.send_command(exit_command) out = self._connection.get_prompt() else: break
def _connect(self): if not HAS_NCCLIENT: raise AssibleError("%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 AssibleConnectionFailure(to_native(exc)) except ImportError: raise AssibleError( "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 _connect_uncached(self): ''' activates the connection object ''' if paramiko is None: raise AssibleError("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 AssibleConnectionFailure('host key mismatch for %s' % e.hostname) except paramiko.ssh_exception.AuthenticationException as e: msg = 'Failed to authenticate: {0}'.format(to_text(e)) raise AssibleAuthenticationFailure(msg) except Exception as e: msg = to_text(e) if u"PID check failed" in msg: raise AssibleError( "paramiko version issue, please upgrade paramiko on the machine running assible" ) 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 AssibleConnectionFailure(msg) else: raise AssibleConnectionFailure(msg) return ssh
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 AssibleError( "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 AssibleConnectionFailure(to_native(msg)) # sudo usually requires a PTY (cf. requiretty option), therefore # we give it one by default (pty=True in assible.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 AssibleError('user %s does not exist' % n_become_user) else: break # raise AssibleError('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 AssibleError( "A password is required but none was supplied") else: no_prompt_out += become_output no_prompt_err += become_output except socket.timeout: raise AssibleError( '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 _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, these could be explicit args set by 'assible_winrm_kinit_args' OR # '-f' if kerberos delegation is requested (assible_winrm_kerberos_delegation). kinit_cmdline = [self._kinit_cmd] kinit_args = self.get_option('kinit_args') if kinit_args: kinit_args = [ to_text(a) for a in shlex.split(kinit_args) if a.strip() ] kinit_cmdline.extend(kinit_args) elif boolean( self.get_option('_extras').get( 'assible_winrm_kerberos_delegation', False)): kinit_cmdline.append('-f') 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 AssibleConnectionFailure(err_msg) try: child.expect(".*:") child.sendline(password) except OSError as err: # child exited before the pass was sent, Assible 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 AssibleConnectionFailure(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 AssibleConnectionFailure(err_msg) display.vvvvv("kinit succeeded for principal %s" % principal)
def exec_command(self, cmd, in_data=None, sudoable=True): display.debug('Intercepted call to exec remote command') raise AssibleConnectionFailure( 'BADLOCAL Error: this is supposed to fail')
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.debug("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 AssibleError('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 AssibleConnectionFailure(', '.join(map(to_native, errors))) else: raise AssibleError('No transport found for WinRM connection')
def _validate_timeout_value(self, timeout, timer_name): if timeout < 0: raise AssibleConnectionFailure( "'%s' timer value '%s' is invalid, value should be greater than or equal to zero." % (timer_name, timeout))
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 AssibleConnectionFailure(msg)
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 AssibleError( '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 AssibleConnectionFailure('winrm connection error: %s' % to_native(exc)) finally: if command_id: self.protocol.cleanup_command(self.shell_id, command_id)
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 AssibleCmdRespRecv: # 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 AssibleConnectionFailure( "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): """ 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 AssibleConnectionFailure( 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: 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 put_file(self, in_path, out_path): display.debug('Intercepted call to send data') raise AssibleConnectionFailure( 'BADLOCAL Error: this is supposed to fail')