Exemplo n.º 1
0
    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
Exemplo n.º 2
0
    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
Exemplo n.º 3
0
    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()
Exemplo n.º 4
0
    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
Exemplo n.º 5
0
    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
Exemplo n.º 6
0
    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
Exemplo n.º 7
0
    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
Exemplo n.º 8
0
    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
Exemplo n.º 9
0
    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
Exemplo n.º 10
0
    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
Exemplo n.º 11
0
    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
Exemplo n.º 12
0
    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
Exemplo n.º 13
0
    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
Exemplo n.º 14
0
    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
Exemplo n.º 15
0
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
Exemplo n.º 16
0
def test__strip_ansi():
    output = b"[[email protected]: \x1b[1m/\x1b[0;0m]$"
    output = strip_ansi(output)
    assert output == b"[[email protected]: /]$"
Exemplo n.º 17
0
    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