Beispiel #1
0
class MyTSFTPRequestHandler(socketserver.BaseRequestHandler):
    timeout = 60
    auth_timeout = 60

    def setup(self):
        self.transport = Transport(self.request)
        self.transport.load_server_moduli()
        so = self.transport.get_security_options()
        so.digests = ('hmac-sha1', )
        so.compression = ('*****@*****.**', 'none')
        self.transport.add_server_key(self.server.host_key)
        self.transport.set_subsystem_handler('sftp', MyTSFTPServer,
                                             MyTSFTPServerInterface)

    def handle(self):
        try:
            self.transport.start_server(server=MyTServerInterface())
        except SSHException as e:
            logger.error("SSH error: %s" % str(e))
            self.transport.close()
        except EOFError as e:
            logger.error("Socket error: %s" % str(e))
        except Exception as e:
            logger.error("Error: %s" % str(e))

    def handle_timeout(self):
        self.transport.close()
Beispiel #2
0
def sftp_server():
    """
    Set up an in-memory SFTP server thread. Yields the client Transport/socket.

    The resulting client Transport (along with all the server components) will
    be the same object throughout the test session; the `sftp` fixture then
    creates new higher level client objects wrapped around the client
    Transport, as necessary.
    """
    # Sockets & transports
    socks = LoopSocket()
    sockc = LoopSocket()
    sockc.link(socks)
    tc = Transport(sockc)
    ts = Transport(socks)
    # Auth
    host_key = RSAKey.from_private_key_file(_support('test_rsa.key'))
    ts.add_server_key(host_key)
    # Server setup
    event = threading.Event()
    server = StubServer()
    ts.set_subsystem_handler('sftp', SFTPServer, StubSFTPServer)
    ts.start_server(event, server)
    # Wait (so client has time to connect? Not sure. Old.)
    event.wait(1.0)
    # Make & yield connection.
    tc.connect(username='******', password='******')
    yield tc
Beispiel #3
0
def sftp_server():
    """
    Set up an in-memory SFTP server thread. Yields the client Transport/socket.

    The resulting client Transport (along with all the server components) will
    be the same object throughout the test session; the `sftp` fixture then
    creates new higher level client objects wrapped around the client
    Transport, as necessary.
    """
    # Sockets & transports
    socks = LoopSocket()
    sockc = LoopSocket()
    sockc.link(socks)
    tc = Transport(sockc)
    ts = Transport(socks)
    # Auth
    host_key = RSAKey.from_private_key_file(_support("test_rsa.key"))
    ts.add_server_key(host_key)
    # Server setup
    event = threading.Event()
    server = StubServer()
    ts.set_subsystem_handler("sftp", SFTPServer, StubSFTPServer)
    ts.start_server(event, server)
    # Wait (so client has time to connect? Not sure. Old.)
    event.wait(1.0)
    # Make & yield connection.
    tc.connect(username="******", password="******")
    yield tc
Beispiel #4
0
class MyTSFTPRequestHandler(SocketServer.BaseRequestHandler):
    timeout = 60
    auth_timeout = 60

    def setup(self):
        self.transport = Transport(self.request)
        self.transport.load_server_moduli()
        so = self.transport.get_security_options()
        so.digests = ('hmac-sha1', )
        so.compression = ('*****@*****.**', 'none')
        self.transport.add_server_key(self.server.host_key)
        self.transport.set_subsystem_handler(
            'sftp', MyTSFTPServer, MyTSFTPServerInterface)

    def handle(self):
        self.transport.start_server(server=MyTServerInterface())

    def handle_timeout(self):
        self.transport.close()
Beispiel #5
0
class MyTSFTPRequestHandler(SocketServer.BaseRequestHandler):
    timeout = 60
    auth_timeout = 60

    def setup(self):
        self.transport = Transport(self.request)
        self.transport.load_server_moduli()
        so = self.transport.get_security_options()
        so.digests = ('hmac-sha1', )
        so.compression = ('*****@*****.**', 'none')
        self.transport.add_server_key(self.server.host_key)
        self.transport.set_subsystem_handler(
            'sftp', MyTSFTPServer, MyTSFTPServerInterface)

    def handle(self):
        self.transport.start_server(server=MyTServerInterface())

    def handle_timeout(self):
        try:
            self.transport.close()
        finally:
            super(MyTSFTPRequestHandler, self).handle_timeout()
  def handle_connection(self, host_key):
    try:
      DoGSSAPIKeyExchange = False
      t = Transport(self.sock, gss_kex=DoGSSAPIKeyExchange)
      t.set_subsystem_handler('netconf', self.subsys)
      t.set_gss_host(socket.getfqdn(""))
      try:
        t.load_server_moduli()
      except:
        Logger.warning('Failed to load moduli -- gex will be unsupported.')
      t.add_server_key(host_key)
      server = Server()
      t.start_server(server=server)

      # wait for auth
      self.channel = t.accept(20)
      if self.channel is None:
        Logger.error('No SSH channel')
        return

      Logger.info('Waiting for message')
      server.event.wait(10)
      Logger.info('Closing')
      ##self.channel.close()
      Logger.info('Client connection closed')
    except ConnectionResetError as e:
      Logger.debug(5,'Connection reset by peer')
    except SSHException:
      Logger.error('SSH negotiation failed, client connection dropped')
    except Exception as e:
      Logger.error('Caught exception: ' + str(e.__class__) + ': ' + str(e))
      traceback.print_exc()
      try:
        t.close()
      except:
        pass
Beispiel #7
0
class Session:

    CIPHERS = None

    def __init__(self, proxyserver, client_socket, client_address,
                 authenticator, remoteaddr):

        self._transport = None

        self.channel = None

        self.proxyserver = proxyserver
        self.client_socket = client_socket
        self.client_address = client_address
        self.name = "{fr}->{to}".format(fr=client_address, to=remoteaddr)

        self.agent_requested = False

        self.ssh = False
        self.ssh_channel = None
        self.ssh_client = None

        self.scp = False
        self.scp_channel = None
        self.scp_command = ''

        self.sftp = False
        self.sftp_channel = None
        self.sftp_client = None
        self.sftp_client_ready = threading.Event()

        self.username = ''
        self.socket_remote_address = remoteaddr
        self.remote_address = (None, None)
        self.key = None
        self.agent = None
        self.authenticator = authenticator(self)

    @property
    def running(self):
        # Using status of main channels to determine session status (-> releasability of resources)
        # - often calculated, cpu heavy (?)
        ch_active = all([
            not ch.closed for ch in filter(
                None, [self.ssh_channel, self.scp_channel, self.sftp_channel])
        ])
        return self.proxyserver.running and ch_active

    @property
    def transport(self):
        if not self._transport:
            self._transport = Transport(self.client_socket)
            cve202014145.hookup_cve_2020_14145(self)
            if self.CIPHERS:
                if not isinstance(self.CIPHERS, tuple):
                    raise ValueError('ciphers must be a tuple')
                self._transport.get_security_options().ciphers = self.CIPHERS
            self._transport.add_server_key(self.proxyserver.host_key)
            self._transport.set_subsystem_handler(
                'sftp', ProxySFTPServer, self.proxyserver.sftp_interface)

        return self._transport

    def _wait_for_agent(self, limit, force_agent=False):
        max_retries = 0
        while not self.agent_requested:
            max_retries += 1
            time.sleep(0.1)
            if max_retries > 10:
                return force_agent
        return True

    def _start_channels(self):
        # create client or master channel
        if self.ssh_client:
            self.sftp_client_ready.set()
            return True

        if not self.agent and (self.authenticator.REQUEST_AGENT
                               or self.authenticator.REQUEST_AGENT_BREAKIN):
            try:
                if self._wait_for_agent(
                        10, self.authenticator.REQUEST_AGENT_BREAKIN):
                    self.agent = AgentServerProxy(self.transport)
                    self.agent.connect()
            except ChannelException:
                logging.error(
                    "Breakin not successful! Closing ssh connection to client")
                self.agent = None
                self.close()
                return False
        # Connect method start
        if not self.agent:
            logging.error('no ssh agent forwarded')
            return False

        if self.authenticator.authenticate() != AUTH_SUCCESSFUL:
            logging.error('Permission denied (publickey)')
            return False

        # Connect method end
        if not self.scp and not self.ssh and not self.sftp:
            if self.transport.is_active():
                self.transport.close()
                return False

        self.sftp_client_ready.set()
        return True

    def start(self):
        event = threading.Event()
        self.transport.start_server(
            event=event,
            server=self.proxyserver.authentication_interface(self))

        while not self.channel:
            self.channel = self.transport.accept(0.5)
            if not self.running:
                self.transport.close()
                return False

        if not self.channel:
            logging.error('(%s) session error opening channel!', self)
            self.transport.close()
            return False

        # wait for authentication
        event.wait()

        if not self.transport.is_active():
            return False

        if not self._start_channels():
            return False

        logging.debug("(%s) session started", self)
        return True

    def close(self):
        if self.agent:
            logging.error(
                "(%s) session cleaning up agent ... (because paramiko IO blocks, in a new Thread)",
                self)
            self.agent._close()
            # INFO: Agent closing sequence takes 15 minutes, due to blocking IO in paramiko
            # Paramiko agent.py tries to connect to a UNIX_SOCKET; it should be created as well (prob) BUT never is
            # Agents starts Thread -> leads to the socket.connect blocking; only returns after .join(1000) timeout
            threading.Thread(target=self.agent.close).start()
            # Can throw FileNotFoundError due to no verification (agent.py)
            logging.debug("(%s) session agent cleaned up", self)
        if self.ssh_client:
            logging.debug("(%s) closing ssh client to remote", self)
            self.ssh_client.transport.close()
            # With graceful exit the completion_event can be polled to wait, well ..., for completion
            # it can also only be a graceful exit if the ssh client has already been established
            if self.transport.completion_event.is_set(
            ) and self.transport.is_active():
                self.transport.completion_event.clear()
                self.transport.completion_event.wait()
        self.transport.close()
        logging.debug("(%s) session closed", self)

    def __str__(self):
        return self.name

    def __enter__(self):
        return self

    def __exit__(self, value_type, value, traceback):
        self.close()
Beispiel #8
0
class Session:
    CIPHERS = None

    def __init__(self, proxyserver, client_socket, client_address,
                 authenticator, remoteaddr):

        self._transport = None

        self.channel = None

        self.proxyserver = proxyserver
        self.client_socket = client_socket
        self.client_address = client_address

        self.ssh = False
        self.ssh_channel = None
        self.ssh_client = None

        self.scp = False
        self.scp_channel = None
        self.scp_command = ''

        self.sftp = False
        self.sftp_channel = None
        self.sftp_client = None
        self.sftp_client_ready = threading.Event()

        self.username = ''
        self.socket_remote_address = remoteaddr
        self.remote_address = (None, None)
        self.key = None
        self.agent = None
        self.authenticator = authenticator(self)

    @property
    def running(self):
        return self.proxyserver.running

    @property
    def transport(self):
        if not self._transport:
            self._transport = Transport(self.client_socket)
            if self.CIPHERS:
                if not isinstance(self.CIPHERS, tuple):
                    raise ValueError('ciphers must be a tuple')
                self._transport.get_security_options().ciphers = self.CIPHERS
            self._transport.add_server_key(self.proxyserver.host_key)
            self._transport.set_subsystem_handler(
                'sftp', ProxySFTPServer, self.proxyserver.sftp_interface)

        return self._transport

    def _start_channels(self):
        # create client or master channel
        if self.ssh_client:
            self.sftp_client_ready.set()
            return True

        if not self.agent and self.authenticator.AGENT_FORWARDING:
            try:
                self.agent = AgentServerProxy(self.transport)
                self.agent.connect()
            except Exception:
                self.close()
                return False
        # Connect method start
        if not self.agent:
            self.channel.send('Kein SSH Agent weitergeleitet\r\n')
            return False

        if self.authenticator.authenticate() != AUTH_SUCCESSFUL:
            self.channel.send('Permission denied (publickey).\r\n')
            return False
        logging.info('connection established')

        # Connect method end
        if not self.scp and not self.ssh and not self.sftp:
            if self.transport.is_active():
                self.transport.close()
                return False

        self.sftp_client_ready.set()
        return True

    def start(self):
        event = threading.Event()
        self.transport.start_server(
            event=event,
            server=self.proxyserver.authentication_interface(self))

        while not self.channel:
            self.channel = self.transport.accept(0.5)
            if not self.running:
                if self.transport.is_active():
                    self.transport.close()
                return False

        if not self.channel:
            logging.error('error opening channel!')
            if self.transport.is_active():
                self.transport.close()
            return False

        # wait for authentication
        event.wait()

        if not self.transport.is_active():
            return False

        if not self._start_channels():
            return False

        logging.info("session started")
        return True

    def close(self):
        if self.transport.is_active():
            self.transport.close()
        if self.agent:
            self.agent.close()

    def __enter__(self):
        return self

    def __exit__(self, value_type, value, traceback):
        self.close()
Beispiel #9
0
class Session:

    CIPHERS = None

    def __init__(self, proxyserver, client_socket, client_address, authenticator, remoteaddr):

        self._transport = None

        self.channel = None

        self.proxyserver = proxyserver
        self.client_socket = client_socket
        self.client_address = client_address
        self.name = "{fr}->{to}".format(fr=client_address, to=remoteaddr)

        self.agent_requested = threading.Event()

        self.ssh = False
        self.ssh_channel = None
        self.ssh_client = None
        self.ssh_pty_kwargs = None

        self.scp = False
        self.scp_channel = None
        self.scp_command = ''

        self.sftp = False
        self.sftp_channel = None
        self.sftp_client = None
        self.sftp_client_ready = threading.Event()

        self.username = ''
        self.socket_remote_address = remoteaddr
        self.remote_address = (None, None)
        self.key = None
        self.agent = None
        self.authenticator = authenticator(self)

    @property
    def running(self):
        # Using status of main channels to determine session status (-> releasability of resources)
        # - often calculated, cpu heavy (?)
        ch_active = all([not ch.closed for ch in filter(None, [self.ssh_channel, self.scp_channel, self.sftp_channel])])
        return self.proxyserver.running and ch_active

    @property
    def transport(self):
        if not self._transport:
            self._transport = Transport(self.client_socket)
            cve202014145.hookup_cve_2020_14145(self)
            if self.CIPHERS:
                if not isinstance(self.CIPHERS, tuple):
                    raise ValueError('ciphers must be a tuple')
                self._transport.get_security_options().ciphers = self.CIPHERS
            self._transport.add_server_key(self.proxyserver.host_key)
            self._transport.set_subsystem_handler('sftp', ProxySFTPServer, self.proxyserver.sftp_interface)

        return self._transport

    def _start_channels(self):
        # create client or master channel
        if self.ssh_client:
            self.sftp_client_ready.set()
            return True

        if not self.agent and (self.authenticator.REQUEST_AGENT or self.authenticator.REQUEST_AGENT_BREAKIN):
            try:
                if self.agent_requested.wait(1) or self.authenticator.REQUEST_AGENT_BREAKIN:
                    self.agent = AgentProxy(self.transport)
            except ChannelException:
                logging.error("Breakin not successful! Closing ssh connection to client")
                self.agent = None
                self.close()
                return False
        # Connect method start
        if not self.agent:
            logging.error('no ssh agent forwarded')
            return False

        if self.authenticator.authenticate() != AUTH_SUCCESSFUL:
            logging.error('Permission denied (publickey)')
            return False

        # Connect method end
        if not self.scp and not self.ssh and not self.sftp:
            if self.transport.is_active():
                self.transport.close()
                return False

        self.sftp_client_ready.set()
        return True

    def start(self):
        event = threading.Event()
        self.transport.start_server(
            event=event,
            server=self.proxyserver.authentication_interface(self)
        )

        while not self.channel:
            self.channel = self.transport.accept(0.5)
            if not self.running:
                self.transport.close()
                return False

        if not self.channel:
            logging.error('(%s) session error opening channel!', self)
            self.transport.close()
            return False

        # wait for authentication
        event.wait()

        if not self.transport.is_active():
            return False

        if not self._start_channels():
            return False

        logging.debug("(%s) session started", self)
        return True

    def close(self):
        if self.agent:
            self.agent.close()
            logging.debug("(%s) session agent cleaned up", self)
        if self.ssh_client:
            logging.debug("(%s) closing ssh client to remote", self)
            self.ssh_client.transport.close()
            # With graceful exit the completion_event can be polled to wait, well ..., for completion
            # it can also only be a graceful exit if the ssh client has already been established
            if self.transport.completion_event.is_set() and self.transport.is_active():
                self.transport.completion_event.clear()
                while self.transport.is_active():
                    if self.transport.completion_event.wait(0.1):
                        break
        self.transport.close()
        logging.debug("(%s) session closed", self)

    def __str__(self):
        return self.name

    def __enter__(self):
        return self

    def __exit__(self, value_type, value, traceback):
        self.close()
Beispiel #10
0
class Session(BaseSession):

    CIPHERS = None

    @classmethod
    @typechecked
    def parser_arguments(cls) -> None:
        plugin_group = cls.parser().add_argument_group(cls.__name__)
        plugin_group.add_argument('--session-log-dir',
                                  dest='session_log_dir',
                                  help='directory to store ssh session logs')

    @typechecked
    def __init__(
        self, proxyserver: 'ssh_proxy_server.server.SSHProxyServer',
        client_socket: socket.socket, client_address: Union[Tuple[Text, int],
                                                            Tuple[Text, int,
                                                                  int, int]],
        authenticator: Type['ssh_proxy_server.authentication.Authenticator'],
        remoteaddr: Union[Tuple[Text, int], Tuple[Text, int, int,
                                                  int]]) -> None:
        super().__init__()
        self.sessionid = uuid4()
        logging.info(
            f"{EMOJI['information']} session {stylize(self.sessionid, fg('light_blue') + attr('bold'))} created"
        )
        self._transport: Optional[paramiko.Transport] = None

        self.channel = None

        self.proxyserver: 'ssh_proxy_server.server.SSHProxyServer' = proxyserver
        self.client_socket = client_socket
        self.client_address = client_address
        self.name = f"{client_address}->{remoteaddr}"
        self.closed = False

        self.agent_requested: threading.Event = threading.Event()

        self.ssh_requested: bool = False
        self.ssh_channel: Optional[paramiko.Channel] = None
        self.ssh_client: Optional[
            ssh_proxy_server.clients.ssh.SSHClient] = None
        self.ssh_pty_kwargs = None

        self.scp_requested: bool = False
        self.scp_channel = None
        self.scp_command: bytes = b''

        self.sftp_requested: bool = False
        self.sftp_channel = None
        self.sftp_client: Optional[
            ssh_proxy_server.clients.sftp.SFTPClient] = None
        self.sftp_client_ready = threading.Event()

        self.username: str = ''
        self.username_provided: Optional[str] = None
        self.password: Optional[str] = None
        self.password_provided: Optional[str] = None
        self.socket_remote_address = remoteaddr
        self.remote_address: Tuple[Optional[Text],
                                   Optional[int]] = (None, None)
        self.remote_key: Optional[PKey] = None
        self.accepted_key: Optional[PKey] = None
        self.agent: Optional[AgentProxy] = None
        self.authenticator: 'ssh_proxy_server.authentication.Authenticator' = authenticator(
            self)

        self.env_requests: Dict[bytes, bytes] = {}
        self.session_log_dir: Optional[str] = self.get_session_log_dir()

    @typechecked
    def get_session_log_dir(self) -> Optional[str]:
        if not self.args.session_log_dir:
            return None
        session_log_dir = os.path.expanduser(self.args.session_log_dir)
        return os.path.join(session_log_dir, str(self.sessionid))

    @property
    def running(self) -> bool:
        session_channel_open: bool = True
        ssh_channel_open: bool = False
        scp_channel_open: bool = False

        if self.channel is not None:
            session_channel_open = not self.channel.closed
        if self.ssh_channel is not None:
            ssh_channel_open = not self.ssh_channel.closed
        if self.scp_channel is not None:
            scp_channel_open = not self.scp_channel.closed if self.scp_channel else False
        open_channel_exists = session_channel_open or ssh_channel_open or scp_channel_open

        return_value = self.proxyserver.running and open_channel_exists and not self.closed
        return return_value

    @property
    def transport(self) -> paramiko.Transport:
        if self._transport is None:
            self._transport = Transport(self.client_socket)
            key_negotiation.handle_key_negotiation(self)
            if self.CIPHERS:
                if not isinstance(self.CIPHERS, tuple):
                    raise ValueError('ciphers must be a tuple')
                self._transport.get_security_options().ciphers = self.CIPHERS
            host_key: Optional[PKey] = self.proxyserver.host_key
            if host_key is not None:
                self._transport.add_server_key(host_key)
            self._transport.set_subsystem_handler(
                'sftp', ProxySFTPServer, self.proxyserver.sftp_interface, self)

        return self._transport

    @typechecked
    def _start_channels(self) -> bool:
        # create client or master channel
        if self.ssh_client:
            self.sftp_client_ready.set()
            return True

        if not self.agent or self.authenticator.REQUEST_AGENT_BREAKIN:
            try:
                if self.agent_requested.wait(
                        1) or self.authenticator.REQUEST_AGENT_BREAKIN:
                    self.agent = AgentProxy(self.transport)
            except ChannelException:
                logging.error(
                    "Breakin not successful! Closing ssh connection to client")
                self.agent = None
                self.close()
                return False
        # Connect method start
        if not self.agent:
            if self.username_provided is None:
                logging.error("No username proviced during login!")
                return False
            return self.authenticator.auth_fallback(
                self.username_provided) == paramiko.common.AUTH_SUCCESSFUL

        if self.authenticator.authenticate(
                store_credentials=False) != paramiko.common.AUTH_SUCCESSFUL:
            if self.username_provided is None:
                logging.error("No username proviced during login!")
                return False
            if self.authenticator.auth_fallback(
                    self.username_provided) == paramiko.common.AUTH_SUCCESSFUL:
                return True
            else:
                self.transport.close()
                return False

        # Connect method end
        if not self.scp_requested and not self.ssh_requested and not self.sftp_requested:
            if self.transport.is_active():
                self.transport.close()
                return False

        self.sftp_client_ready.set()
        return True

    @typechecked
    def start(self) -> bool:
        event = threading.Event()
        self.transport.start_server(
            event=event,
            server=self.proxyserver.authentication_interface(self))

        while not self.channel:
            self.channel = self.transport.accept(0.5)
            if not self.running:
                self.transport.close()
                return False

        if not self.channel:
            logging.error('(%s) session error opening channel!', self)
            self.transport.close()
            return False

        # wait for authentication
        event.wait()

        if not self.transport.is_active():
            return False

        self.proxyserver.client_tunnel_interface.setup(self)

        if not self._start_channels():
            return False

        logging.info(
            f"{EMOJI['information']} {stylize(self.sessionid, fg('light_blue') + attr('bold'))} - session started"
        )
        return True

    @typechecked
    def close(self) -> None:
        if self.agent:
            self.agent.close()
            logging.debug("(%s) session agent cleaned up", self)
        if self.ssh_client:
            logging.debug("(%s) closing ssh client to remote", self)
            if self.ssh_client.transport:
                self.ssh_client.transport.close()
            # With graceful exit the completion_event can be polled to wait, well ..., for completion
            # it can also only be a graceful exit if the ssh client has already been established
            if self.transport.completion_event is not None:
                if self.transport.completion_event.is_set(
                ) and self.transport.is_active():
                    self.transport.completion_event.clear()
                    while self.transport.is_active():
                        if self.transport.completion_event.wait(0.1):
                            break
        if self.transport.server_object is not None:
            for f in cast(BaseServerInterface,
                          self.transport.server_object).forwarders:
                f.close()
                f.join()
        self.transport.close()
        logging.info(
            f"{EMOJI['information']} session {stylize(self.sessionid, fg('light_blue') + attr('bold'))} closed"
        )
        logging.debug(f"({self}) session closed")
        self.closed = True

    @typechecked
    def __str__(self) -> str:
        return self.name

    @typechecked
    def __enter__(self) -> 'Session':
        return self

    def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
        logging.debug("(%s) session exited", self)
        self.close()
Beispiel #11
0
class Session:

    CIPHERS = None

    def __init__(self, proxyserver, client_socket, client_address, authenticator, remoteaddr):

        self._transport = None

        self.channel = None

        self.proxyserver = proxyserver
        self.client_socket = client_socket
        self.client_address = client_address
        self.name = "{fr}->{to}".format(fr=client_address, to=remoteaddr)

        self.agent_requested = False

        self.ssh = False
        self.ssh_channel = None
        self.ssh_client = None

        self.scp = False
        self.scp_channel = None
        self.scp_command = ''

        self.sftp = False
        self.sftp_channel = None
        self.sftp_client = None
        self.sftp_client_ready = threading.Event()

        self.username = ''
        self.socket_remote_address = remoteaddr
        self.remote_address = (None, None)
        self.key = None
        self.agent = None
        self.authenticator = authenticator(self)

    @property
    def running(self):
        # Using status of main channels to determine session status (-> releasability of resources)
        # - often calculated, cpu heavy (?)
        ch_active = all([not ch.closed for ch in filter(None, [self.ssh_channel, self.scp_channel, self.sftp_channel])])
        return self.proxyserver.running and ch_active

    @property
    def transport(self):
        if not self._transport:
            self._transport = Transport(self.client_socket)
            self.hookup_cve_14145()
            if self.CIPHERS:
                if not isinstance(self.CIPHERS, tuple):
                    raise ValueError('ciphers must be a tuple')
                self._transport.get_security_options().ciphers = self.CIPHERS
            self._transport.add_server_key(self.proxyserver.host_key)
            self._transport.set_subsystem_handler('sftp', ProxySFTPServer, self.proxyserver.sftp_interface)

        return self._transport

    def hookup_cve_14145(self):
        # When really trying to implement connection termination/forwarding based on CVE-14145
        # one should consider that clients who already accepted the fingerprint of the ssh-mitm server
        # will be connected through on their second connect and will get a changed keys error
        # (because they have a cached fingerprint and it looks like they need to be connected through)
        def intercept_key_negotiation(transport, m):
            # restore intercept, to not disturb re-keying if this significantly alters the connection
            transport._handler_table[common.MSG_KEXINIT] = Transport._negotiate_keys

            m.get_bytes(16)  # cookie, discarded
            m.get_list()  # key_algo_list, discarded
            server_key_algo_list = m.get_list()
            for host_key_algo in DEFAULT_ALGORITMS:
                if server_key_algo_list == host_key_algo:
                    logging.info("CVE-14145: Client connecting for the FIRST time!")
                    break
            else:
                logging.info("CVE-14145: Client has a locally cached remote fingerprint!")
            if "openssh" in self.transport.remote_version.lower():
                if isinstance(self.proxyserver.host_key, ECDSAKey):
                    logging.warning("CVE-14145: ECDSA-SHA2 Key is a bad choice; this will produce more false positives!")
                r = re.compile(r".*openssh_(\d\.\d).*", re.IGNORECASE)
                if int(r.match(self.transport.remote_version).group(1).replace(".", "")) > 83:
                    logging.warning("CVE-14145: Remote OpenSSH Version > 8.3; CVE-14145 might produce false positive!")

            m.rewind()
            # normal operation
            Transport._negotiate_keys(transport, m)

        self.transport._handler_table[common.MSG_KEXINIT] = intercept_key_negotiation

    def _start_channels(self):
        # create client or master channel
        if self.ssh_client:
            self.sftp_client_ready.set()
            return True

        if not self.agent and (self.authenticator.REQUEST_AGENT or self.authenticator.REQUEST_AGENT_BREAKIN):
            try:
                self.agent = AgentServerProxy(self.transport)
                self.agent.connect()
            except Exception:
                self.close()
                return False
        # Connect method start
        if not self.agent:
            self.channel.send('Kein SSH Agent weitergeleitet\r\n')
            return False

        if self.authenticator.authenticate() != AUTH_SUCCESSFUL:
            self.channel.send('Permission denied (publickey).\r\n')
            return False
        logging.info('connection established')

        # Connect method end
        if not self.scp and not self.ssh and not self.sftp:
            if self.transport.is_active():
                self.transport.close()
                return False

        self.sftp_client_ready.set()
        return True

    def start(self):
        event = threading.Event()
        self.transport.start_server(
            event=event,
            server=self.proxyserver.authentication_interface(self)
        )

        while not self.channel:
            self.channel = self.transport.accept(0.5)
            if not self.running:
                if self.transport.is_active():
                    self.transport.close()
                return False

        if not self.channel:
            logging.error('(%s) session error opening channel!', self)
            if self.transport.is_active():
                self.transport.close()
            return False

        # wait for authentication
        event.wait()

        if not self.transport.is_active():
            return False

        if not self._start_channels():
            return False

        logging.info("(%s) session started", self)
        return True

    def close(self):
        if self.agent:
            logging.debug("(%s) session cleaning up agent ... (because paramiko IO bocks, in a new Thread)", self)
            self.agent._close()
            # INFO: Agent closing sequence takes 15 minutes, due to blocking IO in paramiko
            # Paramiko agent.py tries to connect to a UNIX_SOCKET; it should be created as well (prob) BUT never is
            # Agents starts Thread -> leads to the socket.connect blocking; only returns after .join(1000) timeout
            threading.Thread(target=self.agent.close).start()
            # Can throw FileNotFoundError due to no verification (agent.py)
            logging.debug("(%s) session agent cleaned up", self)
        if self.ssh_client:
            logging.info("(%s) closing ssh client to remote", self)
            self.ssh_client.transport.close()
            # With graceful exit the completion_event can be polled to wait, well ..., for completion
            # it can also only be a graceful exit if the ssh client has already been established
            if self.transport.completion_event.is_set() and self.transport.is_active():
                self.transport.completion_event.clear()
                self.transport.completion_event.wait()
        self.transport.close()
        logging.info("(%s) session closed", self)

    def __str__(self):
        return self.name

    def __enter__(self):
        return self

    def __exit__(self, value_type, value, traceback):
        self.close()