Пример #1
0
def test_overflow():
    handler = argcheck()
    screen = Screen(80, 24)
    screen.cursor_position = handler

    stream = Stream(screen)
    stream.feed(ctrl.CSI + b"999999999999999;99999999999999" + esc.HVP)
    assert handler.count == 1
    assert handler.args == (9999, 9999)
Пример #2
0
def test_missing_params():
    handler = argcheck()
    screen = Screen(80, 24)
    screen.cursor_position = handler

    stream = Stream(screen)
    stream.feed(ctrl.CSI + b";" + esc.HVP)
    assert handler.count == 1
    assert handler.args == (0, 0)
Пример #3
0
def test_unknown_sequences():
    handler = argcheck()
    screen = Screen(80, 24)
    screen.debug = handler

    stream = Stream(screen)
    stream.feed(ctrl.CSI + b"6;Z")
    assert handler.count == 1
    assert handler.args == (6, 0)
    assert handler.kwargs == {}
Пример #4
0
def test_linefeed():
    # ``linefeed`` is somewhat an exception, there's three ways to
    # trigger it.
    handler = counter()
    screen = Screen(80, 24)
    screen.linefeed = handler

    stream = Stream(screen)
    stream.feed(ctrl.LF + ctrl.VT + ctrl.FF)
    assert handler.count == 3
Пример #5
0
def test_control_characters():
    handler = argcheck()
    screen = Screen(80, 24)
    screen.cursor_position = handler

    stream = Stream(screen)
    stream.feed(ctrl.CSI + b"10;\t\t\n\r\n10" + esc.HVP)

    assert handler.count == 1
    assert handler.args == (10, 10)
Пример #6
0
    def __init__(self, username, host, port=22, sshConfigFile=None):
        """
        :type host: str
        :type port: int
        :type sshConfigFile: str|None
        """
        self._log = logging.getLogger(__name__)
        """:type: logging.Logger"""

        self._username = str(username)
        """:type: str"""
        self._host = str(host)
        """:type: str"""
        self._port = int(port)
        """:type: int"""
        self._sshConfigFile = str(sshConfigFile) if sshConfigFile else None
        """:type: str|None"""

        self._promptRegex = r'^[^\s]+[>#]\s?$'
        """:type: str"""
        self._moreRegex = r'^.*-+\s*more\s*-+.*$'
        """:type: str"""

        self._authenticated = False
        """:type: bool"""
        self._readSinceWrite = False
        """:type: bool"""

        sshConfigSpec = ['-F', self._sshConfigFile
                         ] if self._sshConfigFile else []
        portSpec = ['-p', self._port
                    ] if self._port and self._port != 22 else []
        optionsSpec = ['-oStrictHostKeyChecking=no', '-oConnectTimeout=5'
                       ] if not self._sshConfigFile else []
        userHostSpec = [(username + '@' if username else '') + self._host]

        args = ['ssh']
        args.extend(sshConfigSpec)
        args.extend(portSpec)
        args.extend(optionsSpec)
        args.extend(userHostSpec)

        self._log.info(' '.join(args))

        self._pty = PtyProcess.spawn(args,
                                     dimensions=(SSH.SCREEN_HEIGHT,
                                                 SSH.SCREEN_WIDTH),
                                     env={'TERM': 'vt100'})
        """:type: ptyprocess.PtyProcess"""
        self._vt = Screen(SSH.SCREEN_WIDTH, SSH.SCREEN_HEIGHT)
        """:type: pyte.Screen"""
        self._stream = ByteStream()
        """:type: pyte.ByteStream"""

        self._stream.attach(self._vt)
Пример #7
0
def test_reset_mode():
    bugger = counter()
    screen = Screen(80, 24)
    handler = argcheck()
    screen.debug = bugger
    screen.reset_mode = handler

    stream = Stream(screen)
    stream.feed(ctrl.CSI + b"?9;2l")
    assert not bugger.count
    assert handler.count == 1
    assert handler.args == (9, 2)
Пример #8
0
def test_set_mode():
    bugger = counter()
    screen = Screen(80, 24)
    handler = argcheck()
    screen.debug = bugger
    screen.set_mode = handler

    stream = Stream(screen)
    stream.feed(ctrl.CSI + b"?9;2h")
    assert not bugger.count
    assert handler.count == 1
    assert handler.args == (9, 2)
    assert handler.kwargs == {"private": True}
Пример #9
0
def test_interrupt():
    bugger = argstore()
    handler = argcheck()

    screen = Screen(80, 24)
    screen.draw = bugger
    screen.cursor_position = handler

    stream = Stream(screen)
    stream.feed(ctrl.CSI + b"10;" + ctrl.SUB + b"10" + esc.HVP)

    assert not handler.count
    assert bugger.seen == [
        ctrl.SUB, b"10" + esc.HVP
    ]
Пример #10
0
    def __init__(self, username, host, port=22, sshConfigFile=None):
        """
        :type username: str
        :type host: str
        :type port: int
        :type sshConfigFile: str|None
        """
        self._log = logging.getLogger(__name__)
        """:type: logging.Logger"""

        self._username = str(username)
        """:type: str"""
        self._host = str(host)
        """:type: str"""
        self._port = int(port)
        """:type: int"""
        self._sshConfigFile = str(sshConfigFile) if sshConfigFile else None
        """:type: str|None"""

        self._promptRegex = r'^[^\s]+[>#]\s?$'
        """:type: str"""
        self._moreRegex = r'^.*-+\s*more\s*-+.*$'
        """:type: str"""

        self._authenticated = False
        """:type: bool"""
        self._readSinceWrite = False
        """:type: bool"""

        sshConfigSpec = ['-F', self._sshConfigFile] if self._sshConfigFile else []
        portSpec = ['-p', self._port] if self._port and self._port != 22 else []
        optionsSpec = ['-oStrictHostKeyChecking=no', '-oConnectTimeout=5'] if not self._sshConfigFile else []
        userHostSpec = [(username + '@' if username else '') + self._host]

        args = ['ssh']
        args.extend(sshConfigSpec)
        args.extend(portSpec)
        args.extend(optionsSpec)
        args.extend(userHostSpec)

        self._log.info(' '.join(args))

        self._pty = PtyProcess.spawn(args, dimensions=(SSH.SCREEN_HEIGHT, SSH.SCREEN_WIDTH), env={'TERM': 'vt100'})
        """:type: ptyprocess.PtyProcess"""
        self._vt = Screen(SSH.SCREEN_WIDTH, SSH.SCREEN_HEIGHT)
        """:type: pyte.Screen"""
        self._stream = ByteStream()
        """:type: pyte.ByteStream"""

        self._stream.attach(self._vt)
Пример #11
0
class SSH:

    SCREEN_WIDTH = 512
    SCREEN_HEIGHT = 256

    def __init__(self, username, host, port=22, sshConfigFile=None):
        """
        :type username: str
        :type host: str
        :type port: int
        :type sshConfigFile: str|None
        """
        self._log = logging.getLogger(__name__)
        """:type: logging.Logger"""

        self._username = str(username)
        """:type: str"""
        self._host = str(host)
        """:type: str"""
        self._port = int(port)
        """:type: int"""
        self._sshConfigFile = str(sshConfigFile) if sshConfigFile else None
        """:type: str|None"""

        self._promptRegex = r'^[^\s]+[>#]\s?$'
        """:type: str"""
        self._moreRegex = r'^.*-+\s*more\s*-+.*$'
        """:type: str"""

        self._authenticated = False
        """:type: bool"""
        self._readSinceWrite = False
        """:type: bool"""

        sshConfigSpec = ['-F', self._sshConfigFile] if self._sshConfigFile else []
        portSpec = ['-p', self._port] if self._port and self._port != 22 else []
        optionsSpec = ['-oStrictHostKeyChecking=no', '-oConnectTimeout=5'] if not self._sshConfigFile else []
        userHostSpec = [(username + '@' if username else '') + self._host]

        args = ['ssh']
        args.extend(sshConfigSpec)
        args.extend(portSpec)
        args.extend(optionsSpec)
        args.extend(userHostSpec)

        self._log.info(' '.join(args))

        self._pty = PtyProcess.spawn(args, dimensions=(SSH.SCREEN_HEIGHT, SSH.SCREEN_WIDTH), env={'TERM': 'vt100'})
        """:type: ptyprocess.PtyProcess"""
        self._vt = Screen(SSH.SCREEN_WIDTH, SSH.SCREEN_HEIGHT)
        """:type: pyte.Screen"""
        self._stream = ByteStream()
        """:type: pyte.ByteStream"""

        self._stream.attach(self._vt)

    def __repr__(self):
        """
        :rtype: str
        """
        return '<type SSH host:{} port:{}>'.format(self._host, self._port)

    @property
    def host(self):
        """
        :rtype: str
        """
        return self._host

    @property
    def port(self):
        """
        :rtype: int
        """
        return self._port

    @property
    def promptRegex(self):
        """
        :rtype: str
        """
        return self._promptRegex

    @promptRegex.setter
    def promptRegex(self, value):

        self._promptRegex = value

    @property
    def moreRegex(self):
        """
        :rtype: str
        """
        return self._moreRegex

    @moreRegex.setter
    def moreRegex(self, value):

        self._moreRegex = value

    def __enter__(self):

        return self

    def __exit__(self, exc_type, exc_val, exc_tb):

        self.disconnect()

    def __del__(self):

        self.disconnect()

    def disconnect(self):

        if self._pty:

            self._pty.terminate(force=True)

            self._authenticated = False

            self._pty = None
            self._stream = None
            self._vt = None

            self._readSinceWrite = False

            self._log.info('Disconnected from {}'.format(self.host))

    def _read(self, timeout=10, stripPrompt=True, promptRegex=None):
        """
        :type timeout: int
        :type stripPrompt: bool
        :type promptRegex: str|None
        :rtype: str
        """

        self._assertConnectionState(connected=True)

        if not promptRegex:

            promptRegex = self.promptRegex

        deadline = datetime.utcnow() + timedelta(seconds=timeout)

        eof = False
        self._vt.reset()

        while True:

            try:

                read = self._recv()

                if read is not None:

                    deadline = datetime.utcnow() + timedelta(seconds=timeout)

                    self._stream.feed(read)

                    if self._vt.cursor.y > self._vt.lines * 0.5:

                        self._vt.save_cursor()
                        self._vt.resize(self._vt.lines * 2, SSH.SCREEN_WIDTH)
                        self._vt.restore_cursor()

                    continue

                else:

                    time.sleep(0.01)

            except EOFError:

                self._log.info('EOF received')
                eof = True
                break

            line = self._vt.buffer[self._vt.cursor.y]
            line = ''.join(map(lambda l: l.data, line)).strip()

            if re.match(promptRegex, line, re.MULTILINE | re.IGNORECASE | re.UNICODE):

                break

            if re.match(self._moreRegex, line, re.MULTILINE | re.IGNORECASE | re.UNICODE):

                self._send(' ')
                continue

            if datetime.utcnow() > deadline:

                self._log.info('Read timeout; could not match prompt regex or more pagination regex. ' +
                               'Last regex match attempted: {}'.format(line))
                break

        lines = []

        rstart = time.time()

        for line in [l.rstrip() for l in self._vt.display[0:self._vt.cursor.y+1]]:

            if stripPrompt and re.match(promptRegex, line, re.MULTILINE | re.IGNORECASE | re.UNICODE):

                continue

            lines.append(line)

        result = '\n'.join(lines).strip('\n')

        rend = time.time()

        if rend-rstart >= 1:

            self._log.info('Rendered {} lines ({} chars) in {:.2f}s'.format(len(lines), len(result), rend-rstart))

        self._readSinceWrite = True

        if eof:

            self.disconnect()

        return result

    def read(self, timeout=10, stripPrompt=True, promptRegex=None):
        """
        :type timeout: int
        :type stripPrompt: bool
        :type promptRegex: str|None
        :rtype: str
        """

        self._assertConnectionState(connected=True, authenticated=True)

        return self._read(timeout=timeout, promptRegex=promptRegex, stripPrompt=stripPrompt)

    def _write(self, command, timeout=10, consumeEcho=True):
        """
        :type command: str
        :type timeout: int
        :type consumeEcho: bool
        """

        self._assertConnectionState(connected=True)

        command = command.replace('?', '\x16?')

        if not self._readSinceWrite:

            self._read(stripPrompt=False)

        self._readSinceWrite = False

        if not command.endswith('\n'):

            command += '\n'

        self._send(command)

        if not consumeEcho:

            return

        # consume what's echoed back

        readLen = len(command)

        deadline = datetime.utcnow() + timedelta(seconds=timeout)

        while readLen > 0:

            recvd = self._recv(readLen)

            if recvd is not None:

                deadline = datetime.utcnow() + timedelta(seconds=timeout)
                self._stream.feed(recvd)
                readLen -= len(recvd)

            elif datetime.utcnow() > deadline:

                break

            time.sleep(0.01)

    def write(self, command, timeout=10, consumeEcho=True):
        """
        :type command: str
        :type timeout: int
        :type consumeEcho: bool
        """

        self._assertConnectionState(connected=True, authenticated=True)

        self._write(command, timeout=timeout, consumeEcho=consumeEcho)

    def authenticate(self, password=None, passphrase=None, promptCallback=onConnectionPrompt, promptState=None):
        """
        :type password: str|None
        :type passphrase: str|None
        :type promptCallback: (str, dict[str, object], logging.Logger) => bool|None
        :type promptState: dict|None
        :rtype: bool
        """
        try:

            self._assertConnectionState(connected=True, authenticated=False)

        except Exception as ex:

            self._log.error(self._formatException(ex, 'Could not connect to the remote host'))
            return False

        state = {
            'password': password,
            'passphrase': passphrase
        }

        if isinstance(promptState, dict):

            state.update(promptState)

        while True:

            prompt = self._read(promptRegex=r'.+', stripPrompt=False)

            # if we appear to be authenticated...
            if re.findall(self._promptRegex, prompt, re.MULTILINE | re.IGNORECASE | re.UNICODE):

                self._authenticated = True

                break

            result = promptCallback(prompt, state, self._log)

            if result is None:

                break

            self._write(result, consumeEcho=False)

        return self._authenticated

    def enable(self, password):
        """
        :type password: str
        :rtype: bool
        """

        self._assertConnectionState(connected=True, authenticated=True)

        self.write('enable')

        prompt = self.read(promptRegex=r'^Password:.*$', stripPrompt=False).lower()

        if 'password:'******'Remote did not prompt for an enable password')
            return True

        self.write(password, consumeEcho=False)

        passwordResult = self.read(stripPrompt=False).lower()

        if 'password:'******'Enable failed: incorrect password')
            return False

        self._log.info('Enabled on {}'.format(self.host))

        return True

    def showRunningConfig(self):
        """
        :rtype: Configuration
        """
        self.write('show running-config')

        result = self.read()

        return Configuration(result)

    def showStartupConfig(self):
        """
        :rtype: Configuration
        """
        self.write('show startup-config')

        result = self.read()

        return Configuration(result)

    def _send(self, value):
        """
        :type value: str
        """
        self._log.debug('SEND: ' + repr(value))
        self._pty.write(value)

    def _recv(self, nr=1024):
        """
        :type nr: int
        :rtype: str
        """
        canRead = self._pty.fd in select([self._pty.fd], [], [], 0.1)[0]
        if not canRead: return None
        result = self._pty.read(nr)
        self._log.debug('RECV: ' + repr(result))
        return result

    def _formatException(self, exception, message):
        """
        :type exception: Exception
        :type message: str
        :rtype: str
        """
        stack = traceback.extract_stack()

        exceptionMessage = traceback.format_exception_only(type(exception), exception)[0].strip()
        file = stack[-2][0]
        line = stack[-2][1]
        function = stack[-2][2]

        return '%s: %s in %s at %s:%d' % (message, exceptionMessage, function, file, line)

    def _assertConnectionState(self, connected=None, authenticated=None):
        """
        :type connected: bool|None
        :type authenticated: bool|None
        """

        if connected == True:

            if not self._pty or not self._pty.isalive():

                raise NotConnectedError('No SSH connection is established')

        if authenticated == True:

            if not self._authenticated:

                raise NotAuthenticatedError('This SSH connection has not authenticated')

        elif authenticated == False:

            if self._authenticated:

                raise AlreadyAuthenticatedError('This SSH connection has already authenticated')
Пример #12
0
class SSH:

    SCREEN_WIDTH = 512
    SCREEN_HEIGHT = 256

    def __init__(self, username, host, port=22, sshConfigFile=None):
        """
        :type host: str
        :type port: int
        :type sshConfigFile: str|None
        """
        self._log = logging.getLogger(__name__)
        """:type: logging.Logger"""

        self._username = str(username)
        """:type: str"""
        self._host = str(host)
        """:type: str"""
        self._port = int(port)
        """:type: int"""
        self._sshConfigFile = str(sshConfigFile) if sshConfigFile else None
        """:type: str|None"""

        self._promptRegex = r'^[^\s]+[>#]\s?$'
        """:type: str"""
        self._moreRegex = r'^.*-+\s*more\s*-+.*$'
        """:type: str"""

        self._authenticated = False
        """:type: bool"""
        self._readSinceWrite = False
        """:type: bool"""

        sshConfigSpec = ['-F', self._sshConfigFile
                         ] if self._sshConfigFile else []
        portSpec = ['-p', self._port
                    ] if self._port and self._port != 22 else []
        optionsSpec = ['-oStrictHostKeyChecking=no', '-oConnectTimeout=5'
                       ] if not self._sshConfigFile else []
        userHostSpec = [(username + '@' if username else '') + self._host]

        args = ['ssh']
        args.extend(sshConfigSpec)
        args.extend(portSpec)
        args.extend(optionsSpec)
        args.extend(userHostSpec)

        self._log.info(' '.join(args))

        self._pty = PtyProcess.spawn(args,
                                     dimensions=(SSH.SCREEN_HEIGHT,
                                                 SSH.SCREEN_WIDTH),
                                     env={'TERM': 'vt100'})
        """:type: ptyprocess.PtyProcess"""
        self._vt = Screen(SSH.SCREEN_WIDTH, SSH.SCREEN_HEIGHT)
        """:type: pyte.Screen"""
        self._stream = ByteStream()
        """:type: pyte.ByteStream"""

        self._stream.attach(self._vt)

    @property
    def host(self):
        """
        :rtype: str
        """
        return self._host

    @property
    def port(self):
        """
        :rtype: int
        """
        return self._port

    @property
    def promptRegex(self):

        return self._promptRegex

    @promptRegex.setter
    def promptRegex(self, value):

        self._promptRegex = value

    @property
    def moreRegex(self):

        return self._moreRegex

    @moreRegex.setter
    def moreRegex(self, value):

        self._moreRegex = value

    def __enter__(self):

        return self

    def __exit__(self, exc_type, exc_val, exc_tb):

        self.disconnect()

    def __del__(self):

        self.disconnect()

    def disconnect(self):

        if self._pty:

            self._pty.terminate(force=True)

            self._authenticated = False

            self._pty = None
            self._stream = None
            self._vt = None

            self._readSinceWrite = False

            self._log.info('Disconnected from {}'.format(self.host))

    def read(self, timeout=10, promptRegex=None, stripPrompt=True):
        """
        :type timeout: int
        :type promptRegex: str|None
        :type stripPrompt: bool
        :rtype: str
        """

        try:

            self._assertConnectionState(connected=True)

        except Exception as ex:

            self._log.error(
                self._formatException(ex,
                                      'Could not read from the remote host'))
            return ''

        if not promptRegex:

            promptRegex = self.promptRegex

        deadline = datetime.utcnow() + timedelta(seconds=timeout)

        eof = False
        self._vt.reset()

        while True:

            try:

                read = self._recv()

                if read is not None:
                    deadline = datetime.utcnow() + timedelta(seconds=timeout)
                    self._stream.feed(read)
                    if self._vt.cursor.y > self._vt.lines * 0.5:
                        self._vt.save_cursor()
                        self._vt.resize(self._vt.lines * 2, SSH.SCREEN_WIDTH)
                        self._vt.restore_cursor()
                    continue
                else:
                    time.sleep(0.01)

            except EOFError:

                eof = True
                break

            line = self._vt.buffer[self._vt.cursor.y]
            line = ''.join(map(lambda l: getattr(l, 'data'), line)).strip()

            if re.match(promptRegex, line,
                        re.MULTILINE | re.IGNORECASE | re.UNICODE):

                break

            if re.match(self._moreRegex, line,
                        re.MULTILINE | re.IGNORECASE | re.UNICODE):

                self._send(' ')
                continue

            if datetime.utcnow() > deadline:

                self._log.info(
                    'Read timeout; could not match prompt regex or more pagination regex.'
                    + 'Last regex match attempted: {}'.format(line))
                break

        lines = []

        rstart = time.time()

        for line in [
                l.rstrip() for l in self._vt.display[0:self._vt.cursor.y + 1]
        ]:

            if stripPrompt and re.match(
                    promptRegex, line,
                    re.MULTILINE | re.IGNORECASE | re.UNICODE):

                continue

            lines.append(line)

        result = '\n'.join(lines).strip('\n')

        rend = time.time()

        if rend - rstart >= 1:
            self._log.info('Rendered {} lines ({} chars) in {:.2f}s'.format(
                len(lines), len(result), rend - rstart))

        self._readSinceWrite = True

        if eof:

            self.disconnect()

        return result

    def write(self, command, consumeEcho=True):
        """
        :type command: str
        :type consumeEcho: bool
        """
        try:

            self._assertConnectionState(connected=True)

        except Exception as ex:

            self._log.error(
                self._formatException(ex,
                                      'Could not write to the remote host'))
            return

        command = command.replace('?', '\x16?')

        if not self._readSinceWrite:

            self.read(stripPrompt=False)

        self._readSinceWrite = False

        if not command.endswith('\n'):

            command += '\n'

        self._send(command)

        if not consumeEcho:

            return

        # consume what's echoed back

        readLen = len(command)

        while readLen > 0:

            recvd = self._recv(readLen)
            self._stream.feed(recvd)
            readLen -= len(recvd)
            time.sleep(0.01)

    def connect(self, password=None, privateKeyPassphrase=None):
        """
        :type password: str|None
        :type privateKeyPassphrase: str|None
        :rtype: bool
        """
        try:

            self._assertConnectionState(connected=True)

        except Exception as ex:

            self._log.error(
                self._formatException(ex,
                                      'Could not connect to the remote host'))
            return False

        triedPassphrase = False
        triedPassword = False

        while True:

            read = self.read(promptRegex=r'.+', stripPrompt=False)

            if not triedPassphrase and 'passphrase for key' in read.lower():

                if not privateKeyPassphrase:

                    self._log.warn(
                        'Prompted for private key passphrase, but none was provided'
                    )
                    return False

                self.write(privateKeyPassphrase, consumeEcho=False)

                triedPassphrase = True

                continue

            if not triedPassword and 'password:'******'Prompted for password, but none was provided')
                    return False

                self.write(password, consumeEcho=False)

                triedPassword = True

                continue

            if 'passphrase for key' in read.lower():

                self._log.warn('Provided private key passphrase was incorrect')
                return False

            if 'password:'******'Provided password was incorrect')
                return False

            if 'killed by signal' in read.lower():

                self._log.warn(read.lower())
                return False

            if read.lower().startswith('ssh: '):

                self._log.warn(read.lower())
                return False

            self._authenticated = True

            self._log.info('Connected to {}'.format(self.host))

            return True

    def enable(self, password):
        """
        :type password: str
        :rtype: bool
        """
        try:

            self._assertConnectionState(authenticated=True)

        except Exception as ex:

            self._log.error(
                self._formatException(ex,
                                      'Could not enable on the remote host'))
            return False

        self.write('enable')

        enableResult = self.read(promptRegex=r'^Password:.*$',
                                 stripPrompt=False).lower()

        if 'password:'******'Enable failed: remote did not prompt for a password')
            return False

        self.write(password, consumeEcho=False)

        passwordResult = self.read(stripPrompt=False).lower()

        if 'password:'******'Enable failed: incorrect password')
            return False

        self._log.info('Enabled on {}'.format(self.host))

        return True

    def showRunningConfig(self):
        """
        :rtype: Configuration
        """
        self.write('show running-config')

        result = self.read()

        return Configuration(result)

    def showStartupConfig(self):
        """
        :rtype: Configuration
        """
        self.write('show startup-config')

        result = self.read()

        return Configuration(result)

    def _send(self, value):
        """
        :type value: str
        """
        self._log.debug('SEND: ' + repr(value))
        self._pty.write(value)

    def _recv(self, nr=1024):
        """
        :type nr: int
        :rtype: str
        """
        canRead = self._pty.fd in select([self._pty.fd], [], [], 0.1)[0]
        if not canRead: return None
        result = self._pty.read(nr)
        self._log.debug('RECV: ' + repr(result))
        return result

    def _formatException(self, exception, message):
        """
        :type exception: Exception
        :type message: str
        :rtype: str
        """
        stack = traceback.extract_stack()

        exceptionMessage = traceback.format_exception_only(
            type(exception), exception)[0].strip()
        file = stack[-2][0]
        line = stack[-2][1]
        function = stack[-2][2]

        return '%s: %s in %s at %s:%d' % (message, exceptionMessage, function,
                                          file, line)

    def _assertConnectionState(self, connected=None, authenticated=None):
        """
        :type connected: bool|None
        :type authenticated: bool|None
        """
        if not self._pty: return

        if connected == True:

            if not self._pty.isalive():

                raise NotConnectedError(
                    'An SSH connection has not been established')

        elif connected == False:

            if self._pty.isalive():

                raise AlreadyConnectedError(
                    'This SSH connection has already been established')

        if authenticated == True:

            if not self._authenticated:

                raise NotAuthenticatedError(
                    'This SSH connection has not authenticated')

        elif authenticated == False:

            if self._authenticated:

                raise AlreadyAuthenticatedError(
                    'This SSH connection has already authenticated')