def _read_until_prompt(self, output: bytes = b"", prompt: str = "") -> bytes: """ Read until expected prompt is seen. Args: output: bytes from previous reads if needed prompt: prompt to look for if not looking for base prompt (self.comms_prompt_pattern) Returns: bytes: output read from channel Raises: N/A """ prompt_pattern = get_prompt_pattern( prompt=prompt, class_prompt=self.comms_prompt_pattern) while True: output += self._read_chunk() if self.comms_ansi: output = strip_ansi(output=output) channel_match = re.search(pattern=prompt_pattern, string=output) if channel_match: self.logger.info(f"Read: {repr(output)}") return output
def _pty_authenticate(self, pty_session: PtyProcess) -> None: """ Private method to check initial authentication when using pty_session Args: pty_session: PtyProcess session object Returns: N/A # noqa: DAR202 Raises: ScrapliAuthenticationFailed: if we receive an EOFError -- this usually indicates that host key checking is enabled and failed. """ self.session_lock.acquire() output = b"" while True: try: output += pty_session.read() except EOFError: msg = f"Failed to open connection to host {self.host}" if b"Host key verification failed" in output: msg = f"Host key verification failed for host {self.host}" elif b"Operation timed out" in output: msg = f"Timed out connecting to host {self.host}" raise ScrapliAuthenticationFailed(msg) if self._comms_ansi: output = strip_ansi(output) if b"password" in output.lower(): LOG.debug("Found password prompt, sending password") pty_session.write(self.auth_password.encode()) pty_session.write(self._comms_return_char.encode()) self.session_lock.release() break
def get_prompt(self) -> str: """ Get current channel prompt Args: N/A Returns: N/A # noqa: DAR202 Raises: N/A """ prompt_pattern = get_prompt_pattern( prompt="", class_prompt=self.comms_prompt_pattern) self.transport.set_timeout(timeout=10) self._send_return() output = b"" while True: output += self._read_chunk() if self.comms_ansi: output = strip_ansi(output=output) channel_match = re.search(pattern=prompt_pattern, string=output) if channel_match: self.transport.set_timeout() current_prompt = channel_match.group(0) return current_prompt.decode().strip()
async def _read_until_prompt_or_time( self, output: bytes = b"", channel_outputs: Optional[List[bytes]] = None, read_duration: Optional[float] = None, ) -> bytes: """ Read until expected prompt is seen, outputs are seen, or for duration, whichever comes first As transport reading may block, transport timeout is temporarily set to the read_duration and any `ScrapliTimeout` that is raised while reading is ignored. Args: output: bytes from previous reads if needed channel_outputs: List of bytes to search for in channel output, if any are seen, return read output read_duration: duration to read from channel for Returns: bytes: output read from channel Raises: N/A """ prompt_pattern = get_prompt_pattern( prompt="", class_prompt=self.comms_prompt_pattern) if channel_outputs is None: channel_outputs = [] if read_duration is None: read_duration = 2.5 previous_timeout_transport = self.transport.timeout_transport self.transport.timeout_transport = int(read_duration) start = time.time() while True: try: output += await self._read_chunk() except ScrapliTimeout: pass if self.comms_ansi: output = strip_ansi(output=output) if (time.time() - start) > read_duration: break if any([ channel_output in output for channel_output in channel_outputs ]): break if re.search(pattern=prompt_pattern, string=output): break self.transport.timeout_transport = previous_timeout_transport self.logger.info(f"Read: {repr(output)}") return output
def _pty_authenticate(self, pty_session: PtyProcess) -> None: """ Private method to check initial authentication when using pty_session Args: pty_session: PtyProcess session object Returns: N/A # noqa: DAR202 Raises: ScrapliAuthenticationFailed: if we receive an EOFError -- this usually indicates that host key checking is enabled and failed. """ self.session_lock.acquire() output = b"" while True: try: new_output = pty_session.read() output += new_output LOG.debug(f"Attempting to authenticate. Read: {repr(new_output)}") except EOFError: msg = self._pty_authentication_eof_handler(output) LOG.critical(msg) raise ScrapliAuthenticationFailed(msg) if self._comms_ansi: output = strip_ansi(output) if b"password" in output.lower(): LOG.info("Found password prompt, sending password") pty_session.write(self.auth_password.encode()) pty_session.write(self._comms_return_char.encode()) self.session_lock.release() break
def _authenticate(self) -> None: """ Private method to check initial authentication when using pty_session Args: N/A Returns: N/A # noqa: DAR202 Raises: ScrapliAuthenticationFailed: if we get EOF and _ssh_message_handler does not raise an explicit exception/message """ output = b"" prompt_pattern = get_prompt_pattern( prompt="", class_prompt=self._comms_prompt_pattern) while True: try: new_output = self.session.read() output += new_output self.logger.debug( f"Attempting to authenticate. Read: {repr(new_output)}") except EOFError as exc: self._ssh_message_handler(output=output) # if _ssh_message_handler didn't raise any exception, we can raise the standard # "did you disable strict key message/exception" msg = ( f"Failed to open connection to host {self.host}. Do you need to disable " "`auth_strict_key`?") self.logger.critical(msg) raise ScrapliAuthenticationFailed(msg) from exc # even if we dont hit EOF, we may still have hit a message we need to process such # as insecure key permissions self._ssh_message_handler(output=output) if self._comms_ansi or b"\x1B" in output: output = strip_ansi(output=output) if b"password" in output.lower(): self.logger.info("Found password prompt, sending password") self.session.write(self.auth_password.encode()) self.session.write(self._comms_return_char.encode()) break if b"enter passphrase for key" in output.lower(): self.logger.info( "Found key passphrase prompt, sending passphrase") self.session.write(self.auth_private_key_passphrase.encode()) self.session.write(self._comms_return_char.encode()) break channel_match = re.search(pattern=prompt_pattern, string=output) if channel_match: self.logger.info( "Found channel prompt, assuming that key based authentication has succeeded" ) # set _isauthenticated to true, we've already authenticated, don't re-check self._isauthenticated = True break
def _system_isauthenticated(self) -> bool: """ Check if session is authenticated This is very naive -- it only knows if the sub process is alive and has not received an EOF. Beyond that we send the return character and re-read the channel. Args: N/A Returns: bool: True if authenticated, else False Raises: N/A """ self.logger.debug("Attempting to determine if authentication was successful") if self.session.isalive() and not self.session.eof(): prompt_pattern = get_prompt_pattern(prompt="", class_prompt=self._comms_prompt_pattern) self.session.write(self._comms_return_char.encode()) self._wait_for_session_fd_ready(fd=self.session.fd) output = b"" while True: new_output = self.session.read() output += new_output self.logger.debug( f"Attempting to validate authentication. Read: {repr(new_output)}" ) # we do not need to deal w/ line replacement for the actual output, only for # parsing if a prompt-like thing is at the end of the output output = output.replace(b"\r", b"") # always check to see if we should strip ansi here; if we don't handle this we # may raise auth failures for the wrong reason which would be confusing for # users if self._comms_ansi or b"\x1B" in output: output = strip_ansi(output=output) channel_match = re.search(pattern=prompt_pattern, string=output) if channel_match: self._isauthenticated = True break if b"password:"******"password" we know auth failed (hopefully in all scenarios!) self.logger.critical( "Found `password:` in output, assuming password authentication failed" ) break if output: self.logger.debug( f"Cannot determine if authenticated, \n\tRead: {repr(output)}" ) if self._isauthenticated: return True return False
def _pty_isauthenticated(self, pty_session: PtyProcess) -> bool: """ Check if session is authenticated This is very naive -- it only knows if the sub process is alive and has not received an EOF. Beyond that we lock the session and send the return character and re-read the channel. Args: pty_session: PtyProcess session object Returns: bool: True if authenticated, else False Raises: N/A """ LOG.debug("Attempting to determine if PTY authentication was successful") if pty_session.isalive() and not pty_session.eof(): prompt_pattern = get_prompt_pattern(prompt="", class_prompt=self._comms_prompt_pattern) self.session_lock.acquire() pty_session.write(self._comms_return_char.encode()) while True: # almost all of the time we don't need a while loop here, but every once in a while # fd won't be ready which causes a failure without an obvious root cause, # loop/logging to hopefully help with that fd_ready, _, _ = select([pty_session.fd], [], [], 0) if pty_session.fd in fd_ready: break LOG.debug("PTY fd not ready yet...") output = b"" while True: new_output = pty_session.read() output += new_output LOG.debug(f"Attempting validate authentication. Read: {repr(new_output)}") # we do not need to deal w/ line replacement for the actual output, only for # parsing if a prompt-like thing is at the end of the output output = output.replace(b"\r", b"") # always check to see if we should strip ansi here; if we don't handle this we # may raise auth failures for the wrong reason which would be confusing for # users if b"\x1B" in output: output = strip_ansi(output=output) channel_match = re.search(pattern=prompt_pattern, string=output) if channel_match: self.session_lock.release() self._isauthenticated = True return True if b"password:"******"password" we know auth failed (hopefully in all scenarios!) LOG.critical( "Found `password:` in output, assuming password authentication failed" ) return False if output: LOG.debug(f"Cannot determine if authenticated, \n\tRead: {repr(output)}") self.session_lock.release() return False
def _telnet_isauthenticated(self, telnet_session: ScrapliTelnet) -> bool: """ Check if session is authenticated This is very naive -- it only knows if the sub process is alive and has not received an EOF. Beyond that we lock the session and send the return character and re-read the channel. Args: telnet_session: Telnet session object Returns: bool: True if authenticated, else False Raises: N/A """ self.logger.debug( "Attempting to determine if telnet authentication was successful") if not telnet_session.eof: prompt_pattern = get_prompt_pattern( prompt="", class_prompt=self._comms_prompt_pattern) telnet_session_fd = telnet_session.fileno() self.session_lock.acquire() telnet_session.write(self._comms_return_char.encode()) while True: # almost all of the time we don't need a while loop here, but every once in a while # fd won't be ready which causes a failure without an obvious root cause, # loop/logging to hopefully help with that fd_ready, _, _ = select([telnet_session_fd], [], [], 0) if telnet_session_fd in fd_ready: break self.logger.debug("PTY fd not ready yet...") output = b"" while True: output += telnet_session.read_eager() # we do not need to deal w/ line replacement for the actual output, only for # parsing if a prompt-like thing is at the end of the output output = output.replace(b"\r", b"") if self._comms_ansi: output = strip_ansi(output=output) channel_match = re.search(pattern=prompt_pattern, string=output) if channel_match: self.session_lock.release() self._isauthenticated = True return True if b"password:"******"password" auth failed... hopefully true in all scenarios! self.session_lock.release() return False self.session_lock.release() return False
async def _read_until_input(self, channel_input: bytes, auto_expand: Optional[bool] = None) -> bytes: """ Async read until all input has been entered. Args: channel_input: bytes to write to channel auto_expand: bool to indicate if a device auto-expands commands, for example juniper devices without `cli complete-on-space` disabled will convert `config` to `configuration` after entering a space character after `config`; because scrapli reads the channel until each command is entered, the command changing from `config` to `configuration` will cause scrapli (by default) to never think the command has been entered. Returns: bytes: output read from channel Raises: N/A """ output = b"" if not channel_input: self.logger.info(f"Read: {repr(output)}") return output if auto_expand is None: auto_expand = self.comms_auto_expand # squish all channel input words together and cast to lower to make comparison easier processed_channel_input = b"".join(channel_input.lower().split()) while True: output += await self._read_chunk() if self.comms_ansi: output = strip_ansi(output=output) # replace any backspace chars (particular problem w/ junos), and remove any added spaces # this is just for comparison of the inputs to what was read from channel if not auto_expand and processed_channel_input in b"".join( output.lower().replace(b"\x08", b"").split()): break if auto_expand and self._process_auto_expand( output=output, channel_input=channel_input): break self.logger.info(f"Read: {repr(output)}") return output
def _pty_isauthenticated(self, pty_session: PtyProcess) -> bool: """ Check if session is authenticated This is very naive -- it only knows if the sub process is alive and has not received an EOF. Beyond that we lock the session and send the return character and re-read the channel. Args: pty_session: PtyProcess session object Returns: bool: True if authenticated, else False Raises: N/A """ LOG.debug( "Attempting to determine if PTY authentication was successful") if pty_session.isalive() and not pty_session.eof(): prompt_pattern = get_prompt_pattern("", self._comms_prompt_pattern) self.session_lock.acquire() pty_session.write(self._comms_return_char.encode()) fd_ready, _, _ = select([pty_session.fd], [], [], 0) if pty_session.fd in fd_ready: output = b"" while True: output += pty_session.read() # we do not need to deal w/ line replacement for the actual output, only for # parsing if a prompt-like thing is at the end of the output output = re.sub(b"\r", b"", output) # always check to see if we should strip ansi here; if we don't handle this we # may raise auth failures for the wrong reason which would be confusing for # users if b"\x1B" in output: output = strip_ansi(output) channel_match = re.search(prompt_pattern, output) if channel_match: self.session_lock.release() self._isauthenticated = True return True if b"password" in output.lower(): # if we see "password" we know auth failed (hopefully in all scenarios!) return False if output: LOG.debug( f"Cannot determine if authenticated, \n\tRead: {repr(output)}" ) self.session_lock.release() return False
def _telnet_isauthenticated(self, telnet_session: ScrapliTelnet) -> bool: """ Check if session is authenticated This is very naive -- it only knows if the sub process is alive and has not received an EOF. Beyond that we lock the session and send the return character and re-read the channel. Args: telnet_session: Telnet session object Returns: bool: True if authenticated, else False Raises: N/A """ LOG.debug( "Attempting to determine if telnet authentication was successful") if not telnet_session.eof: prompt_pattern = get_prompt_pattern("", self._comms_prompt_pattern) telnet_session_fd = telnet_session.fileno() self.session_lock.acquire() telnet_session.write(self._comms_return_char.encode()) time.sleep(0.25) fd_ready, _, _ = select([telnet_session_fd], [], [], 0) if telnet_session_fd in fd_ready: output = b"" while True: output += telnet_session.read_eager() # we do not need to deal w/ line replacement for the actual output, only for # parsing if a prompt-like thing is at the end of the output output = re.sub(b"\r", b"", output) if self._comms_ansi: output = strip_ansi(output) channel_match = re.search(prompt_pattern, output) if channel_match: self.session_lock.release() self._isauthenticated = True return True if b"password" in output.lower(): # if we see "password" auth failed... hopefully true in all scenarios! return False self.session_lock.release() return False
def _read_chunk(self) -> bytes: """ Private method to read chunk and strip comms_ansi if needed Args: N/A Returns: bytes: output read from channel Raises: N/A """ new_output = self.transport.read() new_output = new_output.replace(b"\r", b"") if self.comms_ansi: new_output = strip_ansi(output=new_output) LOG.debug(f"Read: {repr(new_output)}") return new_output
def _pty_authenticate(self, pty_session: PtyProcess) -> None: """ Private method to check initial authentication when using pty_session Args: pty_session: PtyProcess session object Returns: N/A # noqa: DAR202 Raises: ScrapliAuthenticationFailed: if we get EOF and _ssh_message_handler does not raise an explicit exception/message """ self.session_lock.acquire() output = b"" while True: try: new_output = pty_session.read() output += new_output self.logger.debug( f"Attempting to authenticate. Read: {repr(new_output)}") except EOFError: self._ssh_message_handler(output=output) # if _ssh_message_handler didn't raise any exception, we can raise the standard -- # did you disable strict key message/exception msg = ( f"Failed to open connection to host {self.host}. Do you need to disable " "`auth_strict_key`?") self.logger.critical(msg) raise ScrapliAuthenticationFailed(msg) if self._comms_ansi: output = strip_ansi(output) if b"password" in output.lower(): self.logger.info("Found password prompt, sending password") pty_session.write(self.auth_password.encode()) pty_session.write(self._comms_return_char.encode()) self.session_lock.release() break
def test_diff_xml_text(): xml_one = """<data> <ssh> <server> <v2/> <netconf-vrf-table> <vrf> <vrf-name>default</vrf-name> <blah>someblah</blah> <enable/> </vrf> </netconf-vrf-table> </server> </ssh> <netconf-yang> <agent> <ssh> <enable/> </ssh> </agent> </netconf-yang> </data>""" xml_two = """<data> <ssh> <server> <v2/> <netconf-vrf-table> <vrf> <vrf-name>default</vrf-name> <enable/> </vrf> </netconf-vrf-table> </server> </ssh> <netconf-yang> <agent> <ssh> <disable/> </ssh> </agent> </netconf-yang> </data>""" expected_diff = """@@ -5,7 +5,6 @@ <netconf-vrf-table> <vrf> <vrf-name>default</vrf-name> - <blah>someblah</blah> <enable/> </vrf> </netconf-vrf-table> @@ -14,7 +13,7 @@ <netconf-yang> <agent> <ssh> - <enable/> + <disable/> </ssh> </agent> </netconf-yang>""" actual_diff = diff_xml_text(document_one=xml_one, document_two=xml_two) assert strip_ansi(actual_diff.encode()).decode() == expected_diff
def test__strip_ansi(): output = b"[[email protected]: \x1b[1m/\x1b[0;0m]$" output = strip_ansi(output) assert output == b"[[email protected]: /]$"
async def _telnet_isauthenticated(self) -> bool: """ Check if session is authenticated This is very naive -- it only knows if the telnet session has not received an EOF. Beyond that we send the return character and re-read the channel. Args: N/A Returns: bool: True if authenticated, else False Raises: N/A """ self.logger.debug( "Attempting to determine if telnet authentication was successful") if not self.stdout.at_eof(): prompt_pattern = get_prompt_pattern( prompt="", class_prompt=self._comms_prompt_pattern) self.stdin.write(self._comms_return_char.encode()) output = b"" while True: new_output = await self.stdout.read(65535) output += new_output self.logger.debug( f"Attempting to validate authentication. Read: {repr(new_output)}" ) # we do not need to deal w/ line replacement for the actual output, only for # parsing if a prompt-like thing is at the end of the output output = output.replace(b"\r", b"") # always check to see if we should strip ansi here; if we don't handle this we # may raise auth failures for the wrong reason which would be confusing for # users if self._comms_ansi or b"\x1B" in output: output = strip_ansi(output=output) if b"\x00" in output: # at least nxos likes to send \x00 before output, we can check if the server # does this here, and set the transport attribute to True so we can strip it out # in the read method self._stdout_binary_transmission = True output = output.replace(b"\x00", b"") channel_match = re.search(pattern=prompt_pattern, string=output) if channel_match: self._isauthenticated = True break if b"username:"******"username" prompt we can assume (because telnet) that we failed # to authenticate self.logger.critical( "Found `username:` in output, assuming password authentication failed" ) break if b"password:"******"password" we know auth failed (hopefully in all scenarios!) self.logger.critical( "Found `password:` in output, assuming password authentication failed" ) break if output: self.logger.debug( f"Cannot determine if authenticated, \n\tRead: {repr(output)}" ) if self._isauthenticated: return True return False