Exemplo n.º 1
0
    def socket_recv(self):
        """
        Called by TelnetServer.poll() when recv data is ready.  Read any
        data on socket, processing telnet commands, and buffering all
        other bytestrings to self.recv_buffer.  If data is not received,
        or the connection is closed, x84.bbs.exception.Disconnected is
        raised.
        """
        try:
            data = self.sock.recv(self.BLOCKSIZE_RECV)
            recv = len(data)
            if recv == 0:
                raise Disconnected('Closed by client (EOF)')

        except socket.error as err:
            if err.errno == errno.EWOULDBLOCK:
                return 0
            raise Disconnected('socket_recv error: {0}'.format(err))

        self.bytes_received += recv
        self.last_input_time = time.time()

        # Test for telnet commands, non-telnet bytes
        # are pushed to self.recv_buffer (side-effect),
        for byte in data:
            self._iac_sniffer(byte)
        return recv
Exemplo n.º 2
0
    def socket_recv(self):
        """
        Called by TelnetServer.poll() when recv data is ready.  Read any
        data on socket, processing telnet commands, and buffering all
        other bytestrings to self.recv_buffer.  If data is not received,
        or the connection is closed, x84.bbs.exception.Disconnected is
        raised.
        """
        recv = 0
        try:
            data = self.sock.recv(self.BLOCKSIZE_RECV)
            recv = len(data)
            if 0 == recv:
                raise Disconnected('Closed by client')
        except socket.error as err:
            raise Disconnected('socket errno %d: %s' % (
                err[0],
                err[1],
            ))
        self.bytes_received += recv
        self.last_input_time = time.time()

        ## Test for telnet commands, non-telnet bytes
        ## are pushed to self.recv_buffer (side-effect),
        for byte in data:
            self._iac_sniffer(byte)
        return recv
Exemplo n.º 3
0
    def _send(self, send_bytes):
        """
        Sends ``send_bytes`` to ssh channel, returning number of bytes sent.

        Caller must re-buffer bytes not sent.
        :raises Disconnected: on socket send error (client disconnect).
        """
        try:
            return self.channel.send(send_bytes)
        except EOFError:
            raise Disconnected('EOFError')
        except socket.error as err:
            if err[0] == errno.EDEADLK:
                self.log.debug('{self.addrport}: {err} (bandwidth exceed)'
                               .format(self=self, err=err))
                return 0
            raise Disconnected('socket error: {err}'.format(err=err))
Exemplo n.º 4
0
    def socket_recv(self):
        """
        Receive data from the client socket and returns num bytes received.
        """
        try:
            data = self.sock.recv(self.BLOCKSIZE_RECV)
            recv = len(data)
            if recv == 0:
                raise Disconnected('Closed by client (EOF)')

        except socket.error as err:
            if err.errno == errno.EWOULDBLOCK:
                return 0
            raise Disconnected('socket_recv error: {0}'.format(err))

        self.bytes_received += recv
        self.last_input_time = time.time()
        self.recv_buffer.fromstring(data)
        return recv
Exemplo n.º 5
0
    def socket_recv(self):
        """
        Receive data from socket, returns number of bytes received.

        :raises Disconnect: client has disconnected.
        :rtype: int
        """
        recv = 0
        try:
            data = self.channel.recv(self.BLOCKSIZE_RECV)
            recv = len(data)
            if 0 == recv:
                raise Disconnected('Closed by client (EOF)')
        except socket.error as err:
            raise Disconnected('socket error: {err}'.format(err=err))
        self.bytes_received += recv
        self.last_input_time = time.time()
        self.recv_buffer.fromstring(data)
        return recv
Exemplo n.º 6
0
Arquivo: ssh.py Projeto: hick/x84
    def socket_recv(self):
        """
        Receive any data ready on socket.

        All bytes buffered to :py:attr`SshClient.recv_buffer`.

        Throws Disconnected on EOF.
        """
        recv = 0
        try:
            data = self.channel.recv(self.BLOCKSIZE_RECV)
            recv = len(data)
            if 0 == recv:
                raise Disconnected('Closed by client (EOF)')
        except socket.error as err:
            raise Disconnected('socket error: {err}'.format(err))
        self.bytes_received += recv
        self.last_input_time = time.time()
        self.recv_buffer.fromstring(data)
        return recv
Exemplo n.º 7
0
 def _send(send_bytes):
     """
     throws x84.bbs.exception.Disconnected on sock.send err
     """
     try:
         return self.sock.send(send_bytes)
     except socket.error as err:
         if err.errno == errno.EDEADLK:
             self.log.debug(
                 '{self.addrport}: {err} (bandwidth exceed)'.format(
                     self=self, err=err))
             return 0
         raise Disconnected('send: {0}'.format(err))
Exemplo n.º 8
0
        def _send(send_bytes):
            """
            Inner low-level function for socket send.

            :raises Disconnected: on sock.send error.
            """
            try:
                return self.sock.send(send_bytes)
            except socket.error as err:
                if err.errno in (errno.EDEADLK, errno.EAGAIN):
                    self.log.debug(
                        '{self.addrport}: {err} (bandwidth exceed)'.format(
                            self=self, err=err))
                    return 0
                raise Disconnected('send: {0}'.format(err))
Exemplo n.º 9
0
def kill_session(client, reason='killed'):
    """ Given a client, shutdown its socket and signal subprocess exit. """
    from x84.bbs.exception import Disconnected
    client.shutdown()

    log = logging.getLogger(__name__)
    tty = find_tty(client)
    if tty is not None:
        try:
            tty.master_write.send(('exception', Disconnected(reason),))
        except (EOFError, IOError):
            pass
        log.info('[{tty.sid}] goodbye: {reason}'
                 .format(tty=tty, reason=reason))
        unregister_tty(tty)
Exemplo n.º 10
0
 def send(send_bytes):
     """
     throws x84.bbs.exception.Disconnected on sock.send err
     """
     try:
         return self.sock.send(send_bytes)
     except socket.error as err:
         if err[0] == 11:
             warnings.warn(
                 '%s: %s (bandwidth exceed)' % (
                     self.addrport(),
                     err[1],
                 ), RuntimeWarning, 2)
         else:
             raise Disconnected('socket send %d: %s' % (
                 err[0],
                 err[1],
             ))
Exemplo n.º 11
0
def kill_session(client, reason='killed'):
    """
    Given a client, shutdown its socket and signal subprocess exit.
    """
    from x84.bbs.exception import Disconnected
    from x84.terminal import unregister_tty
    client.shutdown()

    tty = find_tty(client)
    if tty is not None:
        try:
            tty.master_write.send((
                'exception',
                Disconnected(reason),
            ))
        except (EOFError, IOError) as err:
            pass
        unregister_tty(tty)
Exemplo n.º 12
0
 def session_send():
     """
     Test all sessions for idle timeout, and signal exit to subprocess,
     unregister the session.  Also test for data received by telnet
     client, and send to subprocess as 'input' event.
     """
     # accept session event i/o, such as output
     for sid, tty in terminals():
         # poll about and kick off idle users
         if tty.timeout and tty.client.idle() > tty.timeout:
             send_event = 'exception'
             send_data = Disconnected('timeout: %ds' % (tty.client.idle()))
             tty.iqueue.send((send_event, send_data))
             continue
         elif tty.client.input_ready():
             # input buffered on tcp socket, attempt to send to client
             # with a signal alarm timeout; raising a warning if exceeded.
             if not send_input(tty.client, tty.iqueue):
                 logger.warn('%s input buffer exceeded', sid)
                 tty.client.deactivate()
Exemplo n.º 13
0
    def _iac_sniffer(self, byte):
        """
        Watches incomming data for Telnet IAC sequences.
        Passes the data, if any, with the IAC commands stripped to
        _recv_byte().
        """
        # Are we not currently in an IAC sequence coming from the DE?
        if self.telnet_got_iac is False:
            if byte == IAC:
                self.telnet_got_iac = True
            # Are we currently in a sub-negotiation?
            elif self.telnet_got_sb is True:
                self.telnet_sb_buffer.fromstring(byte)
                # Sanity check on length
                if len(self.telnet_sb_buffer) >= self.SB_MAXLEN:
                    raise Disconnected('sub-negotiation buffer filled')
            else:
                # Just a normal NVT character
                self._recv_byte(byte)
            return

        # Did we get sent a second IAC?
        if byte == IAC and self.telnet_got_sb is True:
            # Must be an escaped 255 (IAC + IAC)
            self.telnet_sb_buffer.fromstring(byte)
            self.telnet_got_iac = False
        # Do we already have an IAC + CMD?
        elif self.telnet_got_cmd is not None:
            # Yes, so handle the option
            self._three_byte_cmd(byte)
        # We have IAC but no CMD
        else:
            # Is this the middle byte of a three-byte command?
            if byte in (DO, DONT, WILL, WONT):
                self.telnet_got_cmd = byte
            else:
                # Nope, must be a two-byte command
                self._two_byte_cmd(byte)
Exemplo n.º 14
0
 def telnet_recv(fds):
     """
     test all telnet clients with file descriptors in list fds for
     recv(). If any are disconnected, signal exit to subprocess,
     unregister the session (if any).
     """
     # read in any telnet input
     for client in (clt for (fno, clt) in telnetd.clients.iteritems()
                    if fno in fds):
         try:
             client.socket_recv()
         except Disconnected as err:
             logger.info('%s Connection Closed: %s.',
                         client.addrport(), err)
             tty = lookup(client)
             if tty is None:
                 # no session found, just de-activate this client
                 client.deactivate()
             else:
                 # signal exit to sub-process and shutdown
                 send_event, send_data = 'exception', Disconnected(err)
                 tty.iqueue.send((send_event, send_data))
                 unregister(tty)
Exemplo n.º 15
0
 def telnet_send(recv_list):
     """
     test all telnet clients for send(). If any are disconnected,
     signal exit to subprocess, unregister the session, and return
     recv_list pruned of their file descriptors.
     """
     for sid, tty in terminals():
         if tty.client.send_ready():
             try:
                 tty.client.socket_send()
             except Disconnected as err:
                 logger.debug('%s Disconnected: %s.', sid, err)
                 # client.sock.fileno() can raised 'bad file descriptor',
                 # so, to remove it from the recv_list, reverse match by
                 # instance saved with its FD as a key for telnetd.clients!
                 for _fd, _client in telnetd.clients.items():
                     if tty.client == _client:
                         if _fd in recv_list:
                             recv_list.remove(_fd)
                         break
                 send_event, send_data = 'exception', Disconnected(err)
                 tty.iqueue.send((send_event, send_data,))
                 unregister(tty)
     return recv_list
Exemplo n.º 16
0
    def session_recv(fds):
        """
        receive data waiting for session; all data received from
        subprocess is in form (event, data), and is handled by ipc_recv.

        if stale is not None, elapsed time lock was held to consider stale
        and acquire anyway. no actual locks are held or released, just a
        simple dictionary state/time tracking system.
        """

        disp_handle = lambda handle: ((handle + u' ')
                                      if handle is not None
                                      and 0 != len(handle) else u'')
        disp_origin = lambda client: client.addrport().split(':', 1)[0]

        for sid, tty in terminals():
            while tty.oqueue.fileno() in fds and tty.oqueue.poll():
                # receive data from pipe, unregister if any error,
                try:
                    event, data = tty.oqueue.recv()
                except (EOFError, IOError) as err:
                    # sub-process unexpectedly closed
                    logger.exception(err)
                    unregister(tty)
                    return
                # 'exit' event, unregisters client
                if event == 'exit':
                    unregister(tty)
                    return
                # 'logger' event, propogated upward
                elif event == 'logger':
                    # prefix message with 'ip:port/nick '
                    data.msg = '%s[%s] %s' % (
                        disp_handle(data.handle),
                        disp_origin(tty.client),
                        data.msg)
                    logger.handle(data)
                # 'output' event, buffer for tcp socket
                elif event == 'output':
                    tty.client.send_unicode(ucs=data[0], encoding=data[1])
                # 'remote-disconnect' event, hunt and destroy
                elif event == 'remote-disconnect':
                    send_to = data[0]
                    for _sid, _tty in terminals():
                        if send_to == _sid:
                            send_event = 'exception'
                            send_val = Disconnected(
                                'remote-disconnect by %s' % (sid,))
                            tty.iqueue.send((send_event, send_val))
                            unregister(tty)
                            break
                # 'route': message passing directly from one session to another
                elif event == 'route':
                    logger.debug('route %r', (event, data))
                    tgt_sid, send_event, send_val = data[0], data[1], data[2:]
                    for _sid, _tty in terminals():
                        if tgt_sid == _sid:
                            _tty.iqueue.send((send_event, send_val))
                            break
                # 'global': message broadcasting to all sessions
                elif event == 'global':
                    logger.debug('broadcast %r', (event, data))
                    for _sid, _tty in terminals():
                        if sid != _sid:
                            _tty.iqueue.send((event, data,))
                # 'set-timeout': set user-preferred timeout
                elif event == 'set-timeout':
                    logger.debug('set-timeout %d', data)
                    tty.timeout = data
                # 'db*': access DBProxy API for shared sqlitedict
                elif event.startswith('db'):
                    thread = DBHandler(tty.iqueue, event, data)
                    thread.start()
                # 'lock': access fine-grained bbs-global locking
                elif event.startswith('lock'):
                    handle_lock(tty, event, data)
                else:
                    assert False, 'unhandled %r' % ((event, data),)
Exemplo n.º 17
0
 def _handle_will(self, option):
     """
     Process a WILL command option received by DE.
     """
     # pylint: disable=R0912
     #        Too many branches (19/12)
     self._note_reply_pending(option, False)
     if option == ECHO:
         raise Disconnected(
             'Refuse WILL ECHO by client, closing connection.')
     elif option == BINARY:
         if self.check_remote_option(BINARY) is not True:
             self._note_remote_option(BINARY, True)
             # agree to use BINARY
             self._iac_do(BINARY)
     elif option == NAWS:
         if self.check_remote_option(NAWS) is not True:
             self._note_remote_option(NAWS, True)
             self._note_local_option(NAWS, True)
             # agree to use NAWS, / go ahead ?
             self._iac_do(NAWS)
     elif option == STATUS:
         if self.check_remote_option(STATUS) is not True:
             self._note_remote_option(STATUS, True)
             self.send_str(bytes(''.join(
                 (IAC, SB, STATUS, SEND, IAC, SE))))  # go ahead
     elif option in UNSUPPORTED_WILL:
         if self.check_remote_option(option) is not False:
             # let DE know we refuse to do linemode, encryption, etc.
             self._iac_dont(option)
     elif option == SGA:
         #  IAC WILL SUPPRESS-GO-AHEAD
         #
         # The sender of this command requests permission to begin
         # suppressing transmission of the TELNET GO AHEAD (GA)
         # character when transmitting data characters, or the
         # sender of this command confirms it will now begin suppressing
         # transmission of GAs with transmitted data characters.
         if self.check_remote_option(SGA) is not True:
             # sender of this command confirms that the sender of data
             # is expected to suppress transmission of GAs.
             self._iac_do(SGA)
             self._note_remote_option(SGA, True)
     elif option == NEW_ENVIRON:
         if self.check_remote_option(NEW_ENVIRON) in (False, UNKNOWN):
             self._note_remote_option(NEW_ENVIRON, True)
             self.request_env()
         self._note_local_option(NEW_ENVIRON, True)
     elif option == XDISPLOC:
         # if they want to send it, go ahead.
         if self.check_remote_option(XDISPLOC):
             self._note_remote_option(XDISPLOC, True)
             self._iac_do(XDISPLOC)
             self.send_str(
                 bytes(''.join((IAC, SB, XDISPLOC, SEND, IAC, SE))))
     elif option == TTYPE:
         if self.check_remote_option(TTYPE) in (False, UNKNOWN):
             self._note_remote_option(TTYPE, True)
             self.request_ttype()
     else:
         self.log.debug(
             '{self.addrport}: unhandled will: {opt} (ignored).'.format(
                 self=self, opt=name_option(option)))
Exemplo n.º 18
0
Arquivo: __init__.py Projeto: hick/x84
def disconnect(reason=u''):
    """
    Disconnect session. Does not return.
    """
    raise Disconnected(reason,)
Exemplo n.º 19
0
def _loop(telnetd):
    """
    Main event loop. Never returns.
    """
    # pylint: disable=R0912,R0914,R0915
    #         Too many local variables (24/15)
    import logging
    import select
    import socket
    import time
    import sys

    from x84.bbs.exception import Disconnected
    from x84.terminal import terminals, ConnectTelnet, unregister
    from x84.bbs.ini import CFG
    from x84.telnet import TelnetClient
    from x84.db import DBHandler

    logger = logging.getLogger()

    if sys.maxunicode == 65535:
        logger.warn('Python not built with wide unicode support!')
    logger.info('listening %s/tcp', telnetd.port)
    timeout_ipc = CFG.getint('system', 'timeout_ipc')
    fileno_telnetd = telnetd.server_socket.fileno()
    locks = dict()

    def lookup(client):
        """
        Given a telnet client, return a matching session
        of tuple (client, inp_queue, out_queue, lock).
        If no session matches a telnet client,
            (None, None, None, None) is returned.
        """
        for _sid, tty in terminals():
            if client == tty.client:
                return tty
        return None

    def telnet_recv(fds):
        """
        test all telnet clients with file descriptors in list fds for
        recv(). If any are disconnected, signal exit to subprocess,
        unregister the session (if any).
        """
        # read in any telnet input
        for client in (clt for (fno, clt) in telnetd.clients.iteritems()
                       if fno in fds):
            try:
                client.socket_recv()
            except Disconnected as err:
                logger.info('%s Connection Closed: %s.',
                            client.addrport(), err)
                tty = lookup(client)
                if tty is None:
                    # no session found, just de-activate this client
                    client.deactivate()
                else:
                    # signal exit to sub-process and shutdown
                    send_event, send_data = 'exception', Disconnected(err)
                    tty.iqueue.send((send_event, send_data))
                    unregister(tty)

    def telnet_send(recv_list):
        """
        test all telnet clients for send(). If any are disconnected,
        signal exit to subprocess, unregister the session, and return
        recv_list pruned of their file descriptors.
        """
        for sid, tty in terminals():
            if tty.client.send_ready():
                try:
                    tty.client.socket_send()
                except Disconnected as err:
                    logger.debug('%s Disconnected: %s.', sid, err)
                    # client.sock.fileno() can raised 'bad file descriptor',
                    # so, to remove it from the recv_list, reverse match by
                    # instance saved with its FD as a key for telnetd.clients!
                    for _fd, _client in telnetd.clients.items():
                        if tty.client == _client:
                            if _fd in recv_list:
                                recv_list.remove(_fd)
                            break
                    send_event, send_data = 'exception', Disconnected(err)
                    tty.iqueue.send((send_event, send_data,))
                    unregister(tty)
        return recv_list

    def accept():
        """
        accept new connection from telnetd.server_socket, and
        instantiate a new TelnetClient, registering it with
        dictionary telnetd.clients, and spawning an unmanaged
        thread for negotiating TERM.
        """
        try:
            sock, address_pair = telnetd.server_socket.accept()
            # busy signal
            if telnetd.client_count() > telnetd.MAX_CONNECTIONS:
                try:
                    sock.shutdown(socket.SHUT_RDWR)
                except socket.error:
                    pass
                sock.close()
                logger.error('refused new connect; maximum reached.')
                return
            client = TelnetClient(sock, address_pair, telnetd.on_naws)
            telnetd.clients[client.sock.fileno()] = client
            # spawn negotiation and process registration thread
            ConnectTelnet(client).start()
            logger.info('%s Connected.', client.addrport())
        except socket.error as err:
            logger.error('accept error %d:%s', err[0], err[1],)

    @timeout_alarm(timeout_ipc, False)   # REMOVEME
    def f_send_event(iqueue, event, data):
        """
        Send event to subprocess, signaling an alarm timeout if blocked.
        """
        iqueue.send((event, data))
        return True

    def send_input(client, iqueue):
        """
        Send tcp input to subprocess as 'input' event,
        signaling an alarm timeout and re-buffering input if blocked.

        The reasons for the input buffer to block are vaugue
        """
        inp = client.get_input()
        retval = f_send_event(iqueue, 'input', inp)
        # if timeout occured, re-buffer input
        if not retval:
            client.recv_buffer.fromstring(inp)
        return retval

    def session_send():
        """
        Test all sessions for idle timeout, and signal exit to subprocess,
        unregister the session.  Also test for data received by telnet
        client, and send to subprocess as 'input' event.
        """
        # accept session event i/o, such as output
        for sid, tty in terminals():
            # poll about and kick off idle users
            if tty.timeout and tty.client.idle() > tty.timeout:
                send_event = 'exception'
                send_data = Disconnected('timeout: %ds' % (tty.client.idle()))
                tty.iqueue.send((send_event, send_data))
                continue
            elif tty.client.input_ready():
                # input buffered on tcp socket, attempt to send to client
                # with a signal alarm timeout; raising a warning if exceeded.
                if not send_input(tty.client, tty.iqueue):
                    logger.warn('%s input buffer exceeded', sid)
                    tty.client.deactivate()

    def handle_lock(tty, event, data):
        """
        handle locking event of (lock-key, (method, stale))
        """
        method, stale = data
        if method == 'acquire':
            if event in locks:
                # lock already held; check for and display owner, or
                # acquire a lock from a now-deceased session.
                held=False
                for _sid, _tty in terminals():
                    if _sid == locks[event][1] and _sid != tty.sid:
                        logger.debug('[%s] %r not acquired, held by %s.',
                                tty.sid, (event, data), _sid)
                        held=_sid
                        break
                if held is not False:
                    logger.debug('[%s] %r discovered stale lock, previously '
                            'held by %s.', tty.sid, (event, data), held)
                    del locks[event]
            if not event in locks:
                locks[event] = (time.time(), tty.sid)
                tty.iqueue.send((event, True,))
                logger.debug('[%s] %r granted.',
                             tty.sid, (event, data))
            else:
                # caller signals this kind of thread is short-lived, and any
                # existing lock older than 'stale' should be released.
                if (stale is not None
                        and time.time() - locks[event][0] > stale):
                    tty.iqueue.send((event, True,))
                    locks[event] = (time.time(), tty.sid)
                    logger.warn('[%s] %r stale %fs.',
                                tty.sid, (event, data),
                                time.time() - locks[event][0])
                # signal busy with matching event, data=False
                else:
                    tty.iqueue.send((event, False,))
                    logger.debug('[%s] %r not acquired.',
                            tty.sid, (event, data))
        elif method == 'release':
            if not event in locks:
                logger.error('[%s] %r failed: no match',
                             tty.sid, (event, data))
            else:
                del locks[event]
                logger.debug('[%s] %r removed.',
                             tty.sid, (event, data))

    def session_recv(fds):
        """
        receive data waiting for session; all data received from
        subprocess is in form (event, data), and is handled by ipc_recv.

        if stale is not None, elapsed time lock was held to consider stale
        and acquire anyway. no actual locks are held or released, just a
        simple dictionary state/time tracking system.
        """

        disp_handle = lambda handle: ((handle + u' ')
                                      if handle is not None
                                      and 0 != len(handle) else u'')
        disp_origin = lambda client: client.addrport().split(':', 1)[0]

        for sid, tty in terminals():
            while tty.oqueue.fileno() in fds and tty.oqueue.poll():
                # receive data from pipe, unregister if any error,
                try:
                    event, data = tty.oqueue.recv()
                except (EOFError, IOError) as err:
                    # sub-process unexpectedly closed
                    logger.exception(err)
                    unregister(tty)
                    return
                # 'exit' event, unregisters client
                if event == 'exit':
                    unregister(tty)
                    return
                # 'logger' event, propogated upward
                elif event == 'logger':
                    # prefix message with 'ip:port/nick '
                    data.msg = '%s[%s] %s' % (
                        disp_handle(data.handle),
                        disp_origin(tty.client),
                        data.msg)
                    logger.handle(data)
                # 'output' event, buffer for tcp socket
                elif event == 'output':
                    tty.client.send_unicode(ucs=data[0], encoding=data[1])
                # 'remote-disconnect' event, hunt and destroy
                elif event == 'remote-disconnect':
                    send_to = data[0]
                    for _sid, _tty in terminals():
                        if send_to == _sid:
                            send_event = 'exception'
                            send_val = Disconnected(
                                'remote-disconnect by %s' % (sid,))
                            tty.iqueue.send((send_event, send_val))
                            unregister(tty)
                            break
                # 'route': message passing directly from one session to another
                elif event == 'route':
                    logger.debug('route %r', (event, data))
                    tgt_sid, send_event, send_val = data[0], data[1], data[2:]
                    for _sid, _tty in terminals():
                        if tgt_sid == _sid:
                            _tty.iqueue.send((send_event, send_val))
                            break
                # 'global': message broadcasting to all sessions
                elif event == 'global':
                    logger.debug('broadcast %r', (event, data))
                    for _sid, _tty in terminals():
                        if sid != _sid:
                            _tty.iqueue.send((event, data,))
                # 'set-timeout': set user-preferred timeout
                elif event == 'set-timeout':
                    logger.debug('set-timeout %d', data)
                    tty.timeout = data
                # 'db*': access DBProxy API for shared sqlitedict
                elif event.startswith('db'):
                    thread = DBHandler(tty.iqueue, event, data)
                    thread.start()
                # 'lock': access fine-grained bbs-global locking
                elif event.startswith('lock'):
                    handle_lock(tty, event, data)
                else:
                    assert False, 'unhandled %r' % ((event, data),)

    #
    # main event loop
    #
    while True:

        # shutdown, close & delete inactive sockets,
        for fileno, client in [(_fno, _client)
                               for (_fno, _client) in telnetd.clients.items()
                               if not _client.active]:
            logger.info('close %s', telnetd.clients[fileno].addrport(),)
            try:
                client.sock.shutdown(socket.SHUT_RDWR)
            except socket.error:
                pass
            client.sock.close()

            # signal exit to any matching session
            for _sid, tty in terminals():
                if client == tty.client:
                    send_event = 'exception'
                    send_data = Disconnected('deactivated')
                    tty.iqueue.send((send_event, send_data,))
                    break

            # unregister
            del telnetd.clients[fileno]

        # send tcp data, pruning list of any clients d/c during activity
        fds = telnet_send([fileno_telnetd] + telnetd.clients.keys())

        # extend fd list with all session Pipes
        fds.extend([tty.oqueue.fileno() for _sid, tty in terminals()])

        try:
            fds = select.select(fds, [], [], 0.1)[0]
        except select.error as err:
            logger.exception(err)
            fds = list()

        if fileno_telnetd in fds:
            accept()

        # recv telnet data,
        telnet_recv(fds)

        # recv and handle session events,
        session_recv(fds)

        # send any session input data, poll timeout
        session_send()