Beispiel #1
0
    def _restructure_output(self,
                            output: bytes,
                            strip_prompt: bool = False) -> bytes:
        """
        Clean up preceding empty lines, and strip prompt if desired

        Args:
            output: bytes from channel
            strip_prompt: bool True/False whether to strip prompt or not

        Returns:
            bytes: output of joined output lines optionally with prompt removed

        Raises:
            N/A

        """
        output = normalize_lines(output=output)

        if not strip_prompt:
            return output

        # could be compiled elsewhere, but allow for users to modify the prompt whenever they want
        prompt_pattern = get_prompt_pattern(
            prompt="", class_prompt=self.comms_prompt_pattern)
        output = re.sub(pattern=prompt_pattern, repl=b"", string=output)
        return output
Beispiel #2
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("", self.comms_prompt_pattern)
        self.transport.set_timeout(1000)
        self.transport.write(self.comms_return_char)
        LOG.debug(
            f"Write (sending return character): {repr(self.comms_return_char)}"
        )
        output = b""
        while True:
            output += self._read_chunk()
            channel_match = re.search(prompt_pattern, output)
            if channel_match:
                self.transport.set_timeout()
                current_prompt = channel_match.group(0)
                return current_prompt.decode().strip()
Beispiel #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()
Beispiel #4
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
Beispiel #5
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
Beispiel #6
0
    def _restructure_output(self,
                            output: bytes,
                            strip_prompt: bool = False) -> bytes:
        """
        Clean up preceding empty lines, and strip prompt if desired

        Args:
            output: bytes from channel
            strip_prompt: bool True/False whether to strip prompt or not

        Returns:
            bytes: output of joined output lines optionally with prompt removed

        Raises:
            N/A

        """
        output = normalize_lines(output=output)

        if strip_prompt:
            # could be compiled elsewhere, but allow users to modify the prompt whenever they want
            prompt_pattern = get_prompt_pattern(
                prompt="", class_prompt=self.comms_prompt_pattern)
            output = re.sub(pattern=prompt_pattern, repl=b"", string=output)

        # lstrip the return character out of the final result before storing, also remove any extra
        # whitespace to the right if any
        output = output.lstrip(self.comms_return_char.encode()).rstrip()
        return output
Beispiel #7
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
Beispiel #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
Beispiel #9
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
Beispiel #10
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
Beispiel #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
Beispiel #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
Beispiel #13
0
    def _determine_current_priv(self, current_prompt: str) -> PrivilegeLevel:
        """
        Determine current privilege level from prompt string

        Args:
            current_prompt: string of current prompt

        Returns:
            PrivilegeLevel: NamedTuple of current privilege level

        Raises:
            UnknownPrivLevel: if privilege level cannot be determined

        """
        for priv_level in self.privs.values():
            prompt_pattern = get_prompt_pattern("", priv_level.pattern)
            if re.search(prompt_pattern, current_prompt.encode()):
                return priv_level
        raise UnknownPrivLevel
Beispiel #14
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, self.comms_prompt_pattern)

        while True:
            output += self._read_chunk()
            channel_match = re.search(prompt_pattern, output)
            if channel_match:
                return output
Beispiel #15
0
def test_get_prompt_pattern_arg_string():
    class_pattern = "averygoodpattern"
    result = get_prompt_pattern("awesomepattern", class_pattern)
    assert result == re.compile(b"awesomepattern")
Beispiel #16
0
def test_get_prompt_pattern_arg_pattern():
    class_pattern = "averygoodpattern"
    result = get_prompt_pattern("^awesomepattern$", class_pattern)
    assert result == re.compile(b"^awesomepattern$",
                                re.IGNORECASE | re.MULTILINE)
Beispiel #17
0
def test_get_prompt_pattern_class_pattern_no_line_start_end_markers():
    class_pattern = "averygoodpattern"
    result = get_prompt_pattern(class_pattern, "")
    assert result == re.compile(b"averygoodpattern")
Beispiel #18
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