Пример #1
0
def translate_ttype(ttype):
    from x84.bbs.ini import CFG
    log = logging.getLogger(__name__)

    termcap_unknown = CFG.get('system', 'termcap-unknown')
    termcap_ansi = CFG.get('system', 'termcap-ansi')

    if termcap_unknown != 'no' and ttype == 'unknown':
        log.debug("terminal-type {0!r} => {1!r}".format(
            ttype, termcap_unknown))
        return termcap_unknown

    elif termcap_ansi != 'no' and ttype.lower().startswith('ansi'):
        log.debug("terminal-type {0!r} => {1!r}".format(ttype, termcap_ansi))
        return termcap_ansi

    log.info("terminal type is {0!r}".format(ttype))
    return ttype
Пример #2
0
    def queue_for_network(self):
        " Queue message for networks, hosting or sending. "
        from x84.bbs import get_ini

        log = logging.getLogger(__name__)

        # server networks this server is a member of,
        member_networks = get_ini(section='msg',
                                  key='network_tags',
                                  split=True,
                                  splitsep=',')

        # server networks offered by this server,
        my_networks = get_ini(section='msg',
                              key='server_tags',
                              split=True,
                              splitsep=',')

        # check all tags of message; if they match a message network,
        # either record for hosting servers, or schedule for delivery.
        for tag in self.tags:
            section = 'msgnet_{tag}'.format(tag=tag)

            # message is for a network we host
            if tag in my_networks:
                section = 'msgnet_{tag}'.format(tag=tag)
                transdb_name = CFG.get(section, 'trans_db_name')
                transdb = DBProxy(transdb_name)
                with transdb:
                    self.body = u''.join((self.body, format_origin_line()))
                    self.save()
                    transdb[self.idx] = self.idx
                log.info('[{tag}] Stored for network (msgid {self.idx}).'
                         .format(tag=tag, self=self))

            # message is for a another network, queue for delivery
            elif tag in member_networks:
                queuedb_name = CFG.get(section, 'queue_db_name')
                queuedb = DBProxy(queuedb_name)
                with queuedb:
                    queuedb[self.idx] = tag
                log.info('[{tag}] Message (msgid {self.idx}) queued '
                         'for delivery'.format(tag=tag, self=self))
Пример #3
0
def translate_ttype(ttype):
    from x84.bbs.ini import CFG
    log = logging.getLogger(__name__)

    termcap_unknown = CFG.get('system', 'termcap-unknown')
    termcap_ansi = CFG.get('system', 'termcap-ansi')

    if termcap_unknown != 'no' and ttype == 'unknown':
        log.debug("terminal-type {0!r} => {1!r}"
                  .format(ttype, termcap_unknown))
        return termcap_unknown

    elif termcap_ansi != 'no' and ttype.lower().startswith('ansi'):
        log.debug("terminal-type {0!r} => {1!r}"
                  .format(ttype, termcap_ansi))
        return termcap_ansi

    log.info("terminal type is {0!r}".format(ttype))
    return ttype
Пример #4
0
def get_digestpw():
    """
    Returns singleton to password digest routine.
    """
    global FN_PASSWORD_DIGEST
    if FN_PASSWORD_DIGEST is not None:
        return FN_PASSWORD_DIGEST

    from x84.bbs.ini import CFG
    FN_PASSWORD_DIGEST = {
        'bcrypt': _digestpw_bcrypt,
        'internal': _digestpw_internal,
        'plaintext': _digestpw_plaintext,
    }.get(CFG.get('system', 'password_digest'))
    return FN_PASSWORD_DIGEST
Пример #5
0
def get_digestpw():
    """
    Returns singleton to password digest routine.
    """
    global FN_PASSWORD_DIGEST
    if FN_PASSWORD_DIGEST is not None:
        return FN_PASSWORD_DIGEST

    from x84.bbs.ini import CFG
    FN_PASSWORD_DIGEST = {
        'bcrypt': _digestpw_bcrypt,
        'internal': _digestpw_internal,
        'plaintext': _digestpw_plaintext,
    }.get(CFG.get('system', 'password_digest'))
    return FN_PASSWORD_DIGEST
Пример #6
0
def connect_bot(botname):
    """ Make a zombie telnet connection to the board as the given bot. """
    def read_forever(client):
        client.read_all()

    import telnetlib
    from x84.bbs.ini import CFG
    from x84.bbs.session import BOTLOCK, BOTQUEUE
    telnet_addr = CFG.get('telnet', 'addr')
    telnet_port = CFG.getint('telnet', 'port')
    with BOTLOCK:
        client = telnetlib.Telnet()
        client.set_option_negotiation_callback(callback_cmdopt)
        client.open(telnet_addr, telnet_port)
        BOTQUEUE.put(botname)
        t = threading.Thread(target=read_forever, args=(client,))
        t.daemon = True
        t.start()
Пример #7
0
    def GET(self, num=10):
        """ Return last x callers """

        num = int(num)
        callers = DBProxy('lastcalls', use_session=False).items()
        last = sorted(callers[-num:], reverse=True,
                      key=lambda caller: caller[1][0])

        # output JSON instead?
        if 'json' in web.input(_method='get'):
            return json.dumps(last)

        callers_html = ''
        board = CFG.get('system', 'bbsname', 'x/84')
        page_title = 'Last {num} Callers to {board}'.format(
            num=num, board=board)

        for caller in last:
            callers_html += ''.join(('<li><b>{who}</b> {affil} ',
                                     '<small>at {when}</small></li>')).format(
                who=caller[0],
                affil='(%s)' % caller[1][2] if caller[1][2] else '',
                when=datetime.fromtimestamp(caller[1][0]))

        web.header('Content-Type', 'text/html; charset=utf-8', unique=True)
        output = """
            <!DOCTYPE html>
            <html lang="en-US">
            <head>
                <meta charset="utf-8" />
                <title>{page_title}</title>
            </head>
            <body>
                <h1>{page_title}</h1>
                <ul>
                    {callers_html}
                </ul>
            </body>
            </html>
            """.format(page_title=page_title, callers_html=callers_html)
        return output
Пример #8
0
    def GET(self, num=10):
        """ Return last x callers """

        num = int(num)
        callers = DBProxy('lastcalls', use_session=False).items()
        last = sorted(callers[-num:], reverse=True,
                      key=lambda caller: caller[1][0])

        # output JSON instead?
        if 'json' in web.input(_method='get'):
            return json.dumps(last)

        callers_html = ''
        board = CFG.get('system', 'bbsname', 'x/84')
        page_title = 'Last {num} Callers to {board}'.format(
            num=num, board=board)

        for caller in last:
            callers_html += ''.join(('<li><b>{who}</b> {affil} ',
                                     '<small>at {when}</small></li>')).format(
                who=caller[0],
                affil='(%s)' % caller[1][2] if caller[1][2] else '',
                when=datetime.fromtimestamp(caller[1][0]))

        web.header('Content-Type', 'text/html; charset=utf-8', unique=True)
        output = """
            <!DOCTYPE html>
            <html lang="en-US">
            <head>
                <meta charset="utf-8" />
                <title>{page_title}</title>
            </head>
            <body>
                <h1>{page_title}</h1>
                <ul>
                    {callers_html}
                </ul>
            </body>
            </html>
            """.format(page_title=page_title, callers_html=callers_html)
        return output
Пример #9
0
    def GET(self, num=10):
        """ Return last x oneliners """

        num = int(num)
        oneliners = DBProxy('oneliner', use_session=False).items()
        oneliners = [(int(k), v)
                     for (k,
                          v) in DBProxy('oneliner', use_session=False).items()]
        last = oneliners[-num:]

        # output JSON instead?
        if 'json' in web.input(_method='get'):
            return json.dumps(last)

        board = CFG.get('system', 'bbsname', 'x/84')
        page_title = 'Last {num} Oneliners on {board}'.format(num=num,
                                                              board=board)
        oneliners_html = ''

        for line in last:
            val = line[1]
            oneliners_html += '<li><b>{alias}:</b> {oneliner}</li>'.format(
                alias=val['alias'], oneliner=val['oneliner'])

        web.header('Content-Type', 'text/html; charset=utf-8', unique=True)
        output = """
            <!DOCTYPE html>
            <html lang="en-US">
            <head>
                <meta charset="utf-8" />
                <title>{page_title}</title>
            </head>
            <body>
                <h1>{page_title}</h1>
                <ul>
                    {oneliners_html}
                </ul>
            </body>
            </html>
            """.format(page_title=page_title, oneliners_html=oneliners_html)
        return output
Пример #10
0
    def GET(self, num=10):
        """ Return last x oneliners """

        num = int(num)
        oneliners = DBProxy('oneliner', use_session=False).items()
        oneliners = [(int(k), v) for (k, v) in
                     DBProxy('oneliner', use_session=False).items()]
        last = oneliners[-num:]

        # output JSON instead?
        if 'json' in web.input(_method='get'):
            return json.dumps(last)

        board = CFG.get('system', 'bbsname', 'x/84')
        page_title = 'Last {num} Oneliners on {board}'.format(
            num=num, board=board)
        oneliners_html = ''

        for line in last:
            val = line[1]
            oneliners_html += '<li><b>{alias}:</b> {oneliner}</li>'.format(
                alias=val['alias'], oneliner=val['oneliner'])

        web.header('Content-Type', 'text/html; charset=utf-8', unique=True)
        output = """
            <!DOCTYPE html>
            <html lang="en-US">
            <head>
                <meta charset="utf-8" />
                <title>{page_title}</title>
            </head>
            <body>
                <h1>{page_title}</h1>
                <ul>
                    {oneliners_html}
                </ul>
            </body>
            </html>
            """.format(page_title=page_title, oneliners_html=oneliners_html)
        return output
Пример #11
0
def start(web_modules):
    """ fire up a web server with the given modules as endpoints """
    from threading import Thread
    import logging
    import sys
    import os
    from x84.bbs.ini import CFG

    logger = logging.getLogger()
    sys.path.insert(0, os.path.expanduser(CFG.get('system', 'scriptpath')))
    urls = ('/favicon.ico', 'favicon')
    funcs = globals()
    funcs['favicon'] = Favicon

    for mod in web_modules:
        module = None

        # first check for it in the scripttpath's webmodules dir
        try:
            module = __import__('webmodules.%s' % mod,
                                fromlist=('webmodules',))
        except ImportError:
            pass

        # fallback to the engine's webmodules dir
        if module is None:
            module = __import__('x84.webmodules.%s' % mod,
                                fromlist=('x84.webmodules',))

        api = module.web_module()
        urls += api['urls']

        for key in api['funcs']:
            funcs[key] = api['funcs'][key]

    t = Thread(target=server_thread, args=(urls, funcs,))
    t.daemon = True
    t.start()
    logger.info(u'Web modules: %s' % u', '.join(web_modules))
Пример #12
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
Пример #13
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
Пример #14
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
Пример #15
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)