Exemple #1
0
def get_servers(CFG):
    """
    Given a configuration file, instantiate and return a list of enabled
    servers.
    """
    servers = []

    if (CFG.has_section('telnet') and
            (not CFG.has_option('telnet', 'enabled')
             or CFG.getboolean('telnet', 'enabled'))):
        # start telnet server instance
        from x84.telnet import TelnetServer
        servers.append(TelnetServer(config=CFG))

    if (CFG.has_section('ssh') and
            not CFG.has_option('ssh', 'enabled')
            or CFG.getboolean('ssh', 'enabled')):
        # start ssh server instance
        #
        # may raise an ImportError for systems where pyOpenSSL and etc. could
        # not be installed (due to any issues with missing python-dev, libffi,
        # cc, etc.).  Allow it to raise naturally, the curious user should
        # either discover and resolve the root issue, or disable ssh if it
        # cannot be resolved.
        from x84.ssh import SshServer
        servers.append(SshServer(config=CFG))

    if (CFG.has_section('rlogin') and
            (not CFG.has_option('rlogin', 'enabled')
             or CFG.getboolean('rlogin', 'enabled'))):
        # start rlogin server instance
        from x84.rlogin import RLoginServer
        servers.append(RLoginServer(config=CFG))

    return servers
Exemple #2
0
def get_servers(CFG):
    """ Instantiate and return enabled servers by configuration ``CFG``. """
    servers = []

    if (CFG.has_section('telnet') and
            (not CFG.has_option('telnet', 'enabled')
             or CFG.getboolean('telnet', 'enabled'))):
        # start telnet server instance
        from x84.telnet import TelnetServer
        servers.append(TelnetServer(config=CFG))

    if (CFG.has_section('ssh') and
            not CFG.has_option('ssh', 'enabled')
            or CFG.getboolean('ssh', 'enabled')):
        # start ssh server instance
        #
        # may raise an ImportError for systems where pyOpenSSL and etc. could
        # not be installed (due to any issues with missing python-dev, libffi,
        # cc, etc.).  Allow it to raise naturally, the curious user should
        # either discover and resolve the root issue, or disable ssh if it
        # cannot be resolved.
        from x84.ssh import SshServer
        servers.append(SshServer(config=CFG))

    if (CFG.has_section('rlogin') and
            (not CFG.has_option('rlogin', 'enabled')
             or CFG.getboolean('rlogin', 'enabled'))):
        # start rlogin server instance
        from x84.rlogin import RLoginServer
        servers.append(RLoginServer(config=CFG))

    return servers
Exemple #3
0
def get_ini(section=None, key=None, getter='get', split=False, splitsep=None):
    """
    Get an ini configuration of ``section`` and ``key``.

    If the option does not exist, an empty list, string, or False
    is returned -- return type decided by the given arguments.

    The ``getter`` method is 'get' by default, returning a string.
    For booleans, use ``getter='get_boolean'``.

    To return a list, use ``split=True``.
    """
    from x84.bbs.ini import CFG
    assert section is not None, section
    assert key is not None, key
    if CFG.has_option(section, key):
        getter = getattr(CFG, getter)
        value = getter(section, key)
        if split and hasattr(value, 'split'):
            return map(str.strip, value.split(splitsep))
        return value
    if getter == 'getboolean':
        return False
    if split:
        return []
    return u''
Exemple #4
0
def describe_ssh_availability(term, session):
    from x84.bbs.ini import CFG
    if session.kind == 'ssh':
        # what a good citizen!
        return

    if not (CFG.has_section('ssh') and not CFG.has_option('ssh', 'enabled')
            or CFG.getboolean('ssh', 'enabled')):
        # ssh not enabled
        return

    about_key = (u"You may even use an ssh key, which you can configure from "
                 u"your user profile, "
                 if not session.user.get('pubkey') else u'')
    big_msg = term.bold_blue("Big Brother is Watching You")
    description = (
        u"\r\n\r\n"
        u"    {term.red}You are using {session.kind}, but ssh is available "
        u"on port {ssh_port} of this server.  If you want a secure connection "
        u"with shorter latency, we recommend instead to use ssh!  {about_key}"
        u"Remember: {big_msg}!\r\n\r\n".format(term=term,
                                               session=session,
                                               ssh_port=ssh_port,
                                               about_key=about_key,
                                               big_msg=big_msg))

    show_description(term, description, color=None)
    waitprompt(term)
Exemple #5
0
def describe_ssh_availability(term, session):
    from x84.bbs.ini import CFG
    if session.kind == 'ssh':
        # what a good citizen!
        return

    if not (CFG.has_section('ssh') and
            not CFG.has_option('ssh', 'enabled')
            or CFG.getboolean('ssh', 'enabled')):
        # ssh not enabled
        return

    about_key = (u"You may even use an ssh key, which you can configure from "
                 u"your user profile, " if not session.user.get('pubkey')
                 else u'')
    big_msg = term.bold_blue("Big Brother is Watching You")
    description = (
        u"\r\n\r\n"
        u"    {term.red}You are using {session.kind}, but ssh is available "
        u"on port {ssh_port} of this server.  If you want a secure connection "
        u"with shorter latency, we recommend instead to use ssh!  {about_key}"
        u"Remember: {big_msg}!\r\n\r\n".format(
            term=term, session=session, ssh_port=ssh_port,
            about_key=about_key, big_msg=big_msg))

    show_description(term, description, color=None)
    waitprompt(term)
Exemple #6
0
def describe_ssh_availability(term, session):
    from x84.bbs.ini import CFG
    if session.kind == 'ssh':
        # what a good citizen!
        return

    if not (CFG.has_section('ssh') and
            not CFG.has_option('ssh', 'enabled')
            or CFG.getboolean('ssh', 'enabled')):
        # ssh not enabled
        return

    about_key = (u"You may even use an ssh key, which you can configure from "
                 u"your user profile, " if not session.user.get('pubkey')
                 else u'')
    big_msg = term.bold_blue("Big Brother is Watching You")
    description = (
        "    {term.red}You are using {session.kind}, but ssh is available "
        "on port {ssh_port} of this server.  If you want a secure connection "
        "with shorter latency, we recommend instead to use ssh!  {about_key}"
        "Remember: {big_msg}!"
        .format(term=term,
                session=session,
                ssh_port=ssh_port,
                about_key=about_key,
                big_msg=big_msg)
    )

    echo(u'\r\n\r\n')
    for txt in term.wrap(description, width=min(80, term.width)):
        echo(term.move_x(max(0, (term.width // 2) - 40)))
        echo(term.red(txt.rstrip() + '\r\n'))
    echo(u'\r\n\r\n')
    echo(term.center(term.bold_black('Press any key to continue: ')).rstrip())
    term.inkey()
Exemple #7
0
def main():
    """
    x84 main entry point. The system begins and ends here.

    Command line arguments to engine.py:

    - ``--config=`` location of alternate configuration file
    - ``--logger=`` location of alternate logging.ini file
    """
    import x84.bbs.ini

    # load existing .ini files or create default ones.
    x84.bbs.ini.init(*parse_args())
    from x84.bbs import get_ini
    from x84.bbs.ini import CFG

    if sys.maxunicode == 65535:
        # apple is the only known bastardized variant that does this;
        # presumably for memory/speed savings (UCS-2 strings are faster
        # than UCS-4).  Python 3 dynamically allocates string types by
        # their widest content, so such things aren't necessary ...
        import warnings
        warnings.warn('This python is built without wide unicode support. '
                      'some internationalized languages will not be possible.')

    # retrieve list of managed servers
    servers = get_servers(CFG)

    # begin unmanaged servers
    if (CFG.has_section('web') and
            (not CFG.has_option('web', 'enabled')
             or CFG.getboolean('web', 'enabled'))):
        # start https server for one or more web modules.
        from x84 import webserve
        webserve.main()

    if get_ini(section='msg', key='network_tags'):
        # start background timer to poll for new messages
        # of message networks we may be a member of.
        from x84 import msgpoll
        msgpoll.main()

    try:
        # begin main event loop
        _loop(servers)
    except KeyboardInterrupt:
        # exit on ^C, killing any client sessions.
        from x84.terminal import kill_session
        for server in servers:
            for idx, thread in enumerate(server.threads[:]):
                if not thread.stopped:
                    thread.stopped = True
                server.threads.remove(thread)
            for key, client in server.clients.items()[:]:
                kill_session(client, 'server shutdown')
                del server.clients[key]
    return 0
Exemple #8
0
def main():
    """
    x84 main entry point. The system begins and ends here.

    Command line arguments to engine.py:

    - ``--config=`` location of alternate configuration file
    - ``--logger=`` location of alternate logging.ini file
    """
    # load existing .ini files or create default ones.
    import x84.bbs.ini
    x84.bbs.ini.init(*cmdline.parse_args())

    from x84.bbs import get_ini
    from x84.bbs.ini import CFG

    if sys.maxunicode == 65535:
        # apple is the only known bastardized variant that does this;
        # presumably for memory/speed savings (UCS-2 strings are faster
        # than UCS-4).  Python 3 dynamically allocates string types by
        # their widest content, so such things aren't necessary, there.
        import warnings
        warnings.warn('This python is built without wide unicode support. '
                      'some internationalized languages will not be possible.')

    # retrieve list of managed servers
    servers = get_servers(CFG)

    # begin unmanaged servers
    if (CFG.has_section('web') and
            (not CFG.has_option('web', 'enabled')
             or CFG.getboolean('web', 'enabled'))):
        # start https server for one or more web modules.
        from x84 import webserve
        webserve.main()

    if get_ini(section='msg', key='network_tags'):
        # start background timer to poll for new messages
        # of message networks we may be a member of.
        from x84 import msgpoll
        msgpoll.main()

    try:
        # begin main event loop
        _loop(servers)
    except KeyboardInterrupt:
        # exit on ^C, killing any client sessions.
        for server in servers:
            for thread in server.threads[:]:
                if not thread.stopped:
                    thread.stopped = True
                server.threads.remove(thread)
            for key, client in server.clients.items()[:]:
                kill_session(client, 'server shutdown')
                del server.clients[key]
    return 0
Exemple #9
0
def showart(filepattern, encoding=None, auto_mode=True, center=False,
            poll_cancel=False, msg_cancel=None):
    """
    Yield unicode sequences for any given ANSI Art (of art_encoding). Effort
    is made to parse SAUCE data, translate input to unicode, and trim artwork
    too large to display.  If ``poll_cancel`` is not ``False``, represents
    time as float for each line to block for keypress -- if any is received,
    then iteration ends and ``msg_cancel`` is displayed as last line of art.

    If you provide no ``encoding``, the piece encoding will be based on either
    the encoding in the SAUCE record, the configured default or the default
    fallback ``CP437`` encoding.

    Alternate codecs are available if you provide the ``encoding`` argument.
    For example, if you want to show an Amiga style ASCII art file::

        >>> from x84.bbs import echo, showart
        >>> for line in showart('test.asc', 'topaz'):
        ...     echo(line)

    The ``auto_mode`` flag will, if set, only respect the selected encoding if
    the active session is UTF-8 capable.

    If ``center`` is set to ``True``, the piece will be centered respecting the
    current terminal's width.

    """
    import random
    import glob
    import os
    from sauce import SAUCE
    from x84.bbs.ini import CFG

    session, term = getsession(), getterminal()

    # Open the piece
    try:
        filename = os.path.relpath(random.choice(glob.glob(filepattern)))
    except IndexError:
        filename = None

    if filename is None:
        yield u''.join((
            term.bold_red(u'-- '),
            u'no files matching {0}'.format(filepattern),
            term.bold_red(u' --'),
        ))
        return

    # Parse the piece
    parsed = SAUCE(filename)

    # If no explicit encoding is given, we go through a couple of steps to
    # resolve the possible file encoding:
    if encoding is None:
        # 1. See if the SAUCE record has a font we know about, it's in the
        #    filler
        if parsed.record and parsed.filler_str in SAUCE_FONT_MAP:
            encoding = SAUCE_FONT_MAP[parsed.filler_str]

        # 2. Get the system default art encoding
        elif CFG.has_option('system', 'art_utf8_codec'):
            encoding = CFG.get('system', 'art_utf8_codec')

        # 3. Fall back to CP437
        else:
            encoding = 'cp437'

    # If auto_mode is enabled, we'll only use the input encoding on UTF-8
    # capable terminals, because our codecs do not know how to "transcode"
    # between the various encodings.
    if auto_mode:
        def _decode(what):
            session = getsession()
            if session.encoding == 'utf8':
                return what.decode(encoding)
            elif session.encoding == 'cp437':
                return what.decode('cp437')
            else:
                return what

    # If auto_mode is disabled, we'll just respect whatever input encoding was
    # selected before
    else:
        _decode = lambda what: what.decode(encoding)

    # For wide terminals, center the piece on the screen using cursor movement,
    # if requested
    padding = u''
    if center and term.width > 81:
        padding = term.move_x((term.width / 2) - 40)

    msg_cancel = msg_cancel or u''.join(
        (term.normal,
         term.bold_black(u'-- '),
         u'cancelled {0} by input'.format(filename),
         term.bold_black(u' --'),
         ))

    msg_too_wide = u''.join(
        (term.normal,
         term.bold_black(u'-- '),
         u'cancelled {0}, too wide:: {{0}}'.format(filename),
         term.bold_black(u' --'),
         ))

    lines = _decode(parsed.data).splitlines()
    for idx, line in enumerate(lines):
        # Allow slow terminals to cancel by pressing a keystroke
        if poll_cancel is not False and term.inkey(poll_cancel):
            yield u'\r\n' + term.center(msg_cancel) + u'\r\n'
            return
        line_length = term.length(line.rstrip())
        if not padding and term.width < line_length:
            yield (u'\r\n' +
                   term.center(msg_too_wide.format(line_length)) +
                   u'\r\n')
            return
        if idx == len(lines) - 1:
            # strip DOS end of file (^Z)
            line = line.rstrip('\x1a')
            if not line.strip():
                break
        yield padding + line + u'\r\n'
    yield term.normal
Exemple #10
0
def main(msg=None):
    """ Main procedure. """
    from x84.bbs import Msg, getsession, echo, getterminal
    from x84.bbs.ini import CFG
    session, term = getsession(), getterminal()
    new_message = True if msg is None else False
    network_tags = None
    is_network_msg = False

    if CFG.has_option('msg', 'network_tags'):
        network_tags = map(
            str.strip, CFG.get('msg', 'network_tags').split(','))

    if new_message:
        msg = Msg()
        msg.tags = ('public',)

    elif network_tags is not None:
        net_tags = [tag for tag in network_tags if tag in msg.tags]
        is_network_msg = True if len(net_tags) > 0 else False

    banner()
    while True:
        session.activity = 'Constructing a %s message' % (
            u'public' if u'public' in msg.tags else u'private',)
        if network_tags and new_message:
            is_network_msg = prompt_network(msg, network_tags)
        if not prompt_recipient(msg, is_network_msg):
            break
        if not prompt_subject(msg):
            break
        if not prompt_public(msg):
            break
        if not prompt_body(msg):
            break
        # XXX
        if not prompt_tags(msg):
            break
        if is_network_msg:
            # XX move to sub-func
            how_many = len([tag for tag in network_tags if tag in msg.tags])
            if how_many == 0:
                echo(u''.join((u'\r\n',
                               term.bold_yellow_on_red(
                                   u' YOU tOld ME thiS WAS A NEtWORk MESSAGE. '
                                   u'WhY did YOU liE?! '),
                               u'\r\n')))
                term.inkey(timeout=7)
                continue
            if how_many > 1:
                echo(u''.join((
                    u'\r\n', term.bold_yellow_on_red(
                        u' ONlY ONE NEtWORk CAN bE POStEd tO At A tiME, '
                        u'SORRY '),
                    u'\r\n')))
                continue
            if u'public' not in msg.tags:
                echo(u''.join((u'\r\n',
                               term.bold_yellow_on_red(
                                   u" YOU ShOUldN't SENd PRiVAtE "
                                   u"MESSAGES OVER tHE NEtWORk... "),
                               u'\r\n')))
                term.inkey(timeout=7)
                continue
        display_msg(msg)
        if not prompt_send():
            break

        msg.save()
        session.user['msgs_sent'] = session.user.get('msgs_sent', 0) + 1
        return True
    return False
Exemple #11
0
    def save(self, send_net=True, ctime=None):
        """
        Save message in 'Msgs' sqlite db, and record index in 'tags' db.
        """
        from x84.bbs.ini import CFG
        from x84.bbs import getsession

        session = getsession()
        use_session = bool(session is not None)
        log = logging.getLogger(__name__)
        new = self.idx is None or self._stime is None

        # persist message record to MSGDB
        db_msg = DBProxy(MSGDB, use_session=use_session)
        with db_msg:
            if new:
                self.idx = max([int(key) for key in db_msg.keys()] or [-1]) + 1
                if ctime is not None:
                    self._ctime = self._stime = ctime
                else:
                    self._stime = datetime.datetime.now()
                new = True
            db_msg['%d' % (self.idx,)] = self

        # persist message idx to TAGDB
        db_tag = DBProxy(TAGDB, use_session=use_session)
        with db_tag:
            for tag in db_tag.keys():
                msgs = db_tag[tag]
                if tag in self.tags and self.idx not in msgs:
                    msgs.add(self.idx)
                    db_tag[tag] = msgs
                    log.debug("msg {self.idx} tagged '{tag}'"
                              .format(self=self, tag=tag))
                elif tag not in self.tags and self.idx in msgs:
                    msgs.remove(self.idx)
                    db_tag[tag] = msgs
                    log.info("msg {self.idx} removed tag '{tag}'"
                             .format(self=self, tag=tag))
            for tag in [_tag for _tag in self.tags if _tag not in db_tag]:
                db_tag[tag] = set([self.idx])

        # persist message as child to parent;
        if not hasattr(self, 'parent'):
            self.parent = None
        assert self.parent not in self.children
        if self.parent is not None:
            parent_msg = get_msg(self.parent)
            if self.idx != parent_msg.idx:
                parent_msg.children.add(self.idx)
                parent_msg.save()
            else:
                log.error('Parent idx same as message idx; stripping')
                self.parent = None
                with db_msg:
                    db_msg['%d' % (self.idx)] = self

        if send_net and new and CFG.has_option('msg', 'network_tags'):
            self.queue_for_network()

        log.info(
            u"saved {new}{public}msg {post}, addressed to '{self.recipient}'."
            .format(new='new ' if new else '',
                    public='public ' if 'public' in self.tags else '',
                    post='post' if self.parent is None else 'reply',
                    self=self))
Exemple #12
0
def main(msg=None):
    """ Main procedure. """
    from x84.bbs import Msg, getsession, echo, getterminal
    from x84.bbs.ini import CFG
    session, term = getsession(), getterminal()
    new_message = True if msg is None else False
    network_tags = None
    is_network_msg = False

    if CFG.has_option('msg', 'network_tags'):
        network_tags = map(
            str.strip, CFG.get('msg', 'network_tags').split(','))

    if CFG.has_option('msg', 'server_tags'):
        if network_tags is None:
            network_tags = list()
        network_tags += map(
            str.strip, CFG.get('msg', 'server_tags').split(','))

    if new_message:
        msg = Msg()
        msg.tags = ('public',)

    elif network_tags is not None:
        net_tags = [tag for tag in network_tags if tag in msg.tags]
        is_network_msg = True if len(net_tags) > 0 else False

    banner()
    while True:
        session.activity = 'Constructing a %s message' % (
            u'public' if u'public' in msg.tags else u'private',)
        if network_tags and new_message:
            is_network_msg = prompt_network(msg, network_tags)
        if not prompt_recipient(msg, is_network_msg):
            break
        if not prompt_subject(msg):
            break
        if not prompt_public(msg):
            break
        if not prompt_body(msg):
            break
        # XXX
        if not prompt_tags(msg):
            break
        if is_network_msg:
            # XX move to sub-func
            how_many = len([tag for tag in network_tags if tag in msg.tags])
            if how_many == 0:
                echo(u''.join((u'\r\n',
                               term.bold_yellow_on_red(
                                   u' YOU tOld ME thiS WAS A NEtWORk MESSAGE. '
                                   u'WhY did YOU liE?! '),
                               u'\r\n')))
                term.inkey(timeout=7)
                continue
            if how_many > 1:
                echo(u''.join((
                    u'\r\n', term.bold_yellow_on_red(
                        u' ONlY ONE NEtWORk CAN bE POStEd tO At A tiME, '
                        u'SORRY '),
                    u'\r\n')))
                continue
            if u'public' not in msg.tags:
                echo(u''.join((u'\r\n',
                               term.bold_yellow_on_red(
                                   u" YOU ShOUldN't SENd PRiVAtE "
                                   u"MESSAGES OVER tHE NEtWORk... "),
                               u'\r\n')))
                term.inkey(timeout=7)
                continue
        display_msg(msg)
        if not prompt_send():
            break

        msg.save()
        session.user['msgs_sent'] = session.user.get('msgs_sent', 0) + 1
        return True
    return False
Exemple #13
0
    def save(self, send_net=True, ctime=None):
        """
        Save message in 'Msgs' sqlite db, and record index in 'tags' db.
        """
        from x84.bbs.ini import CFG
        from x84.bbs import getsession

        session = getsession()
        use_session = bool(session is not None)
        log = logging.getLogger(__name__)
        new = self.idx is None or self._stime is None

        # persist message record to MSGDB
        db_msg = DBProxy(MSGDB, use_session=use_session)
        with db_msg:
            if new:
                self.idx = max(map(int, db_msg.keys()) or [-1]) + 1
                if ctime is not None:
                    self._ctime = self._stime = ctime
                else:
                    self._stime = datetime.datetime.now()
                new = True
            db_msg['%d' % (self.idx, )] = self

        # persist message idx to TAGDB
        db_tag = DBProxy(TAGDB, use_session=use_session)
        with db_tag:
            for tag in db_tag.keys():
                msgs = db_tag[tag]
                if tag in self.tags and self.idx not in msgs:
                    msgs.add(self.idx)
                    db_tag[tag] = msgs
                    log.debug("msg {self.idx} tagged '{tag}'".format(self=self,
                                                                     tag=tag))
                elif tag not in self.tags and self.idx in msgs:
                    msgs.remove(self.idx)
                    db_tag[tag] = msgs
                    log.info("msg {self.idx} removed tag '{tag}'".format(
                        self=self, tag=tag))
            for tag in [_tag for _tag in self.tags if _tag not in db_tag]:
                db_tag[tag] = set([self.idx])

        # persist message as child to parent;
        if not hasattr(self, 'parent'):
            self.parent = None
        assert self.parent not in self.children
        if self.parent is not None:
            parent_msg = get_msg(self.parent)
            if self.idx != parent_msg.idx:
                parent_msg.children.add(self.idx)
                parent_msg.save()
            else:
                log.error('Parent idx same as message idx; stripping')
                self.parent = None
                with db_msg:
                    db_msg['%d' % (self.idx)] = self

        if send_net and new and CFG.has_option('msg', 'network_tags'):
            self.queue_for_network()

        log.info(
            u"saved {new}{public}msg {post}, addressed to '{self.recipient}'.".
            format(new='new ' if new else '',
                   public='public ' if 'public' in self.tags else '',
                   post='post' if self.parent is None else 'reply',
                   self=self))
Exemple #14
0
def _loop(servers):
    """
    Main event loop. Never returns.
    """
    # pylint: disable=R0912,R0914,R0915
    #         Too many local variables (24/15)
    import logging
    import select
    import sys
    from x84.terminal import get_terminals, kill_session
    from x84.bbs.ini import CFG
    from x84.fail2ban import get_fail2ban_function

    SELECT_POLL = 0.02 # polling time is 20ms

    # WIN32 has no session_fds (multiprocess queues are not polled using
    # select), use a persistently empty set; for WIN32, sessions are always
    # polled for data at every loop.
    WIN32 = sys.platform.lower().startswith('win32')
    session_fds = set()

    log = logging.getLogger('x84.engine')

    if not len(servers):
        raise ValueError("No servers configured for event loop! (ssh, telnet)")

    tap_events = CFG.getboolean('session', 'tap_events')
    check_ban = get_fail2ban_function()
    locks = dict()

    # message polling setup
    if CFG.has_option('msg', 'poll_interval'):
        from x84 import msgpoll
        msgpoll.start_polling()

    if CFG.has_section('web') and CFG.has_option('web', 'modules'):
        try:
            __import__("web")
            __import__("OpenSSL")
            import webserve
            module_names = CFG.get('web', 'modules', '').split(',')
            if module_names:
                web_modules = set(map(str.strip, module_names))
                log.info('starting webmodules: {0!r}'.format(web_modules))
                webserve.start(web_modules)
        except ImportError as err:
            log.error("section [web] enabled but not enabled: {0}".format(err))

    while True:
        # shutdown, close & delete inactive clients,
        for server in servers:
            # bbs sessions that are no longer active on the socket
            # level -- send them a 'kill signal'
            for key, client in server.clients.items()[:]:
                if not client.is_active():
                    kill_session(client, 'socket shutdown')
                    del server.clients[key]
            # on-connect negotiations that have completed or failed.
            # delete their thread instance from further evaluation
            for thread in [_thread for _thread in server.threads
                           if _thread.stopped][:]:
                server.threads.remove(thread)

        server_fds = [server.server_socket.fileno() for server in servers]
        client_fds = [fd for fd in server.client_fds() for server in servers]
        check_r = server_fds + client_fds
        if not WIN32:
            session_fds = get_session_output_fds(servers)
            check_r += session_fds

        # We'd like to use timeout 'None', but the registration of
        # a new client in terminal.start_process surprises us with new
        # file descriptors for the session i/o. unless we loop for
        # additional `session_fds', a connecting client would block.
        ready_r, _, _ = select.select(check_r, [], [], SELECT_POLL)

        for fd in ready_r:
            # see if any new tcp connections were made
            server = find_server(servers, fd)
            if server is not None:
                accept(log, server, check_ban)

        # receive new data from tcp clients.
        client_recv(servers, log)
        terms = get_terminals()

        # receive new data from session terminals
        if WIN32 or set(session_fds) & set(ready_r):
            try:
                session_recv(locks, terms, log, tap_events)
            except IOError, err:
                # if the ipc closes while we poll, warn and continue
                log.warn(err)

        # send tcp data to clients
        client_send(terms, log)

        # send session data, poll for user-timeout and disconnect them
        session_send(terms)