示例#1
0
文件: engine.py 项目: ztaylor/x84
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.ini import CFG

    if sys.maxunicode == 65535:
        import warnings
        warnings.warn('Python not built with wide unicode support!')

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

    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]
示例#2
0
文件: engine.py 项目: tehmaze/x84
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
示例#3
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
示例#4
0
文件: engine.py 项目: hick/x84
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:
        import warnings
        warnings.warn('Python not built with wide unicode support!')

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

    # begin unmanaged servers
    if get_ini(section='web', key='modules'):
        # start https server for one or more web modules.
        #
        # 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 web modules if
        # it cannot be resolved.
        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]
示例#5
0
文件: engine.py 项目: tehmaze/x84
def client_recv(servers, ready_fds, log):
    """
    Test all clients for recv_ready(). If any data is available, then
    socket_recv() is called, buffering the data for the session which
    is exhausted in session_send().
    """
    from x84.bbs.exception import Disconnected
    from x84.terminal import kill_session
    for server in servers:
        for client in server.clients_ready(ready_fds):
            try:
                client.socket_recv()
            except Disconnected as err:
                log.debug('{client.addrport}: disconnect on recv: {err}'
                          .format(client=client, err=err))
                kill_session(client, 'disconnected: {err}'.format(err=err))
示例#6
0
文件: engine.py 项目: hick/x84
def client_send(terminals, log):
    """
    Test all clients for send_ready(). If any data is available, then
    tty.client.send() is called. This is data sent from the session to
    the tcp client.
    """
    from x84.bbs.exception import Disconnected
    from x84.terminal import kill_session
    # nothing to send until tty is registered.
    for sid, tty in terminals:
        if tty.client.send_ready():
            try:
                tty.client.send()
            except Disconnected as err:
                log.debug('{client.addrport}: disconnect on send: {err}'
                          .format(client=tty.client, err=err))
                kill_session(tty.client, 'disconnected: {err}'.format(err=err))
示例#7
0
文件: engine.py 项目: hick/x84
def client_recv(servers, log):
    """
    Test all clients for recv_ready(). If any data is available, then
    socket_recv() is called, buffering the data for the session which
    is exhausted in session_send().
    """
    from x84.bbs.exception import Disconnected
    from x84.terminal import kill_session
    for server in servers:
        for client in server.clients.values():
            if client.recv_ready():
                try:
                    client.socket_recv()
                except Disconnected as err:
                    log.debug('{client.addrport}: disconnect on recv: {err}'
                              .format(client=client, err=err))
                    kill_session(client, 'disconnected: {err}'.format(err=err))
示例#8
0
def client_recv(servers, ready_fds, log):
    """
    Test all clients for recv_ready().

    If any data is available, then ``client.socket_recv()`` is called,
    buffering the data for the session which is exhausted by
    :func:`session_send`.
    """
    from x84.bbs.exception import Disconnected
    for server in servers:
        for client in server.clients_ready(ready_fds):
            try:
                client.socket_recv()
            except Disconnected as err:
                log.debug('{client.addrport}: disconnect on recv: {err}'
                          .format(client=client, err=err))
                kill_session(client, 'disconnected: {err}'.format(err=err))
示例#9
0
def session_send(terminals):
    """
    Test all tty clients for input_ready().

    Meaning, tcp data has been buffered to be received by the tty session,
    and send it to the tty input queue (tty.master_write).  Also, test all
    sessions for idle timeout, signaling exit to subprocess when reached.
    """
    for _, tty in terminals:
        if tty.client.input_ready():
            try:
                tty.master_write.send(('input', tty.client.get_input()))
            except IOError:
                # this may happen if a sub-process crashes, or more often,
                # because the subprocess has logged off, but the user kept
                # banging the keyboard before we have had the opportunity
                # to close their telnet socket.
                kill_session(tty.client, 'no tty for socket data')

        # poll about and kick off idle users
        elif tty.timeout and tty.client.idle() > tty.timeout:
            kill_session(tty.client, 'timeout')
示例#10
0
文件: engine.py 项目: hick/x84
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()

    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)
示例#11
0
文件: engine.py 项目: hick/x84
def session_recv(locks, terminals, log, tap_events):
    """
    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, the number of seconds elapsed since lock was
    first held is consider stale after that period of time, and is acquire
    anyway.
    """
    # No actual Lock instances are held or released, just a simple dictionary
    # state/time tracking system.
    from x84.terminal import kill_session
    from x84.db import DBHandler

    for sid, tty in terminals:
        while tty.master_read.poll():
            try:
                event, data = tty.master_read.recv()
            except (EOFError, IOError) as err:
                # sub-process unexpectedly closed
                msg_err = 'master_read pipe: {err}'.format(err=err)
                log.exception(msg_err)
                kill_session(tty.client, msg_err)
                break
            except TypeError:
                msg_err = 'unpickling error'
                log.exception(msg_err)
                break

            # 'exit' event, unregisters client
            if event == 'exit':
                kill_session(tty.client, 'client exit')
                break

            # 'logger' event, prefix log message with handle and IP address
            elif event == 'logger':
                data.msg = ('{data.handle}[{tty.sid}] {data.msg}'
                            .format(data=data, tty=tty))
                log.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]
                reason = 'remote-disconnect by {sid.tty}'.format(sid=sid)
                for _sid, _tty in terminals:
                    if send_to == _sid:
                        kill_session(tty.client, reason)
                        break

            # 'route': message passing directly from one session to another
            elif event == 'route':
                if tap_events:
                    log.debug('route {0!r}'.format(data))
                tgt_sid, send_event, send_val = data[0], data[1], data[2:]
                for _sid, _tty in terminals:
                    if tgt_sid == _sid:
                        _tty.master_write.send((send_event, send_val))
                        break

            # 'global': message broadcasting to all sessions
            elif event == 'global':
                if tap_events:
                    log.debug('broadcast: {data!r}'.format(data=data))
                for _sid, _tty in terminals:
                    if sid != _sid:
                        _tty.master_write.send((event, data,))

            # 'set-timeout': set user-preferred timeout
            elif event == 'set-timeout':
                if tap_events:
                    log.debug('[{tty.sid}] set-timeout {data}'
                              .format(tty=tty, data=data))
                tty.timeout = data

            # 'db*': access DBProxy API for shared sqlitedict
            elif event.startswith('db'):
                thread = DBHandler(tty.master_write, event, data)
                thread.start()

            # 'lock': access fine-grained bbs-global locking
            elif event.startswith('lock'):
                handle_lock(locks, tty, event, data, tap_events, log)

            else:
                log.error('[{tty.sid}] unhandled event, data: '
                          '({event}, {data})'
                          .format(tty=tty, event=event, data=data))
示例#12
0
def session_recv(locks, terminals, log, tap_events):
    """
    Receive data waiting for terminal sessions.

    All data received from subprocess is handled here.
    """
    for sid, tty in terminals:
        while tty.master_read.poll():
            try:
                event, data = tty.master_read.recv()
            except (EOFError, IOError) as err:
                # sub-process unexpectedly closed
                log.exception('master_read pipe: {0}'.format(err))
                kill_session(tty.client, 'master_read pipe: {0}'.format(err))
                break
            except TypeError as err:
                log.exception('unpickling error: {0}'.format(err))
                break

            # 'exit' event, unregisters client
            if event == 'exit':
                kill_session(tty.client, 'client exit')
                break

            # 'logger' event, prefix log message with handle and IP address
            elif event == 'logger':
                data.msg = ('{data.handle}[{tty.sid}] {data.msg}'
                            .format(data=data, tty=tty))
                log.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':
                for _sid, _tty in terminals:
                    # data[0] is 'send-to' address.
                    if data[0] == _sid:
                        kill_session(
                            tty.client, 'remote-disconnect by {0}'.format(sid))
                        break

            # 'route': message passing directly from one session to another
            elif event == 'route':
                if tap_events:
                    log.debug('route {0!r}'.format(data))
                tgt_sid, send_event, send_val = data[0], data[1], data[2:]
                for _sid, _tty in terminals:
                    if tgt_sid == _sid:
                        _tty.master_write.send((send_event, send_val))
                        break

            # 'global': message broadcasting to all sessions
            elif event == 'global':
                if tap_events:
                    log.debug('broadcast: {data!r}'.format(data=data))
                for _sid, _tty in terminals:
                    if sid != _sid:
                        _tty.master_write.send((event, data,))

            # 'set-timeout': set user-preferred timeout
            elif event == 'set-timeout':
                if tap_events:
                    log.debug('[{tty.sid}] set-timeout {data}'
                              .format(tty=tty, data=data))
                tty.timeout = data

            # 'db*': access DBProxy API for shared sqlitedict
            elif event.startswith('db'):
                DBHandler(tty.master_write, event, data).start()

            # 'lock': access fine-grained bbs-global locking
            elif event.startswith('lock'):
                handle_lock(locks, tty, event, data, tap_events, log)

            else:
                log.error('[{tty.sid}] unhandled event, data: '
                          '({event}, {data})'
                          .format(tty=tty, event=event, data=data))