def allow_tag(session, idx): """ Whether user is allowed to tag a message. :rtype: bool A user can tag a message if the given session's user is: * the message author or recipient. * a member of sysop or moderator group. * a member of any existing tag-matching user group. """ moderated = get_ini('msg', 'moderated_tags', getter='getboolean') tag_moderators = set(get_ini('msg', 'tag_moderators', split=True)) if not moderated and 'sysop' in session.user.groups: return True elif moderated and (tag_moderators | session.user.groups): # tags are moderated, but user is one of the moderator groups return True msg = get_msg(idx) if session.user.handle in (msg.recipient, msg.author): return True for tag in msg.tags: if tag in session.user.groups: return True return False
def server(urls, funcs): """ Main server thread for running the web server """ from x84.bbs import get_ini from web.wsgiserver import CherryPyWSGIServer from web.wsgiserver.ssl_pyopenssl import pyOpenSSLAdapter log = logging.getLogger(__name__) addr = get_ini(section='web', key='addr' ) or '0.0.0.0' port = get_ini(section='web', key='port', getter='getint' ) or 8443 app = web.application(urls, funcs) web.config.debug = False log.info('https listening on {addr}:{port}/tcp' .format(addr=addr, port=port)) # Runs CherryPy WSGI server hosting WSGI app.wsgifunc(). web.httpserver.runsimple(app.wsgifunc(), (addr, port)) # blocking
def view_leaf_msgnet(server_tag=None, board_id=None): if server_tag is None: server_tags = get_ini(section="msg", key="server_tags", split=True) if not server_tags: raise ValueError("no `server_tags' defined in ini file, " "section [msg].") # RECURSION for _st in server_tags: view_leaf_msgnet(server_tag=_st, board_id=None) return if board_id is None: board_ids = DBProxy("{0}keys".format(server_tag)).keys() for _bid in board_ids: # RECURSION view_leaf_msgnet(server_tag=server_tag, board_id=_bid) return with DBProxy("{0}keys".format(server_tag)) as key_db: echo(u"\r\n[msgnet_{0}]".format(server_tag)) echo(u"\r\nurl_base = https://{addr}:{port}/".format(addr=get_ini("web", "addr"), port=get_ini("web", "port"))) echo(u"\r\nboard_id = {0}".format(board_id)) echo(u"\r\ntoken = {0}".format(key_db[board_id])) echo(u"\r\npoll_interval = 300") echo(u"\r\n") echo(u"\r\n[msg]") echo(u"\r\nnetwork_tags = {0}".format(server_tag)) echo(u"\r\n") echo(u"-" * 40)
def get_sesame_menu_items(session): # there doesn't exist any documentation on how this works, # only the given examples in the generated default.ini file menu_items = [] if ini.CFG.has_section('sesame'): for name in filter(lambda _name: '_' not in _name, ini.CFG.options('sesame')): sesame_kwargs = {'name': name} door_cmd = ini.CFG.get('sesame', name).split(None, 1)[0] if door_cmd.lower() == 'no' or not os.path.exists(door_cmd): # skip entry if path does not resolve, or set to 'no' continue inp_key = get_ini(section='sesame', key='{0}_key'.format(name)) if not inp_key: raise ValueError('sesame configuration for "{0}" requires ' 'complimenting value "{0}_key" for menu ' 'input key.'.format(name)) if get_ini(section='sesame', key='{0}_sysop_only'.format(name), getter='getboolean') and not session.user.is_sysop: continue text = get_ini( section='sesame', key='{0}_text'.format(name) ) or name menu_items.append( MenuItem(inp_key=inp_key, text=text, script='sesame', args=(), kwargs=sesame_kwargs)) return menu_items
def translate_ttype(ttype): """ Return preferred terminal type given the session-negotiation ttype. This provides a kind of coercion; we know some terminals, such as SyncTerm report a terminal type of 'ansi' -- however, the author publishes a termcap database for 'ansi-bbs' which he instructs should be used! So an ``[system]`` configuration item of ``termcap-ansi`` may be set to ``'ansi-bbs'`` to coerce such terminals for Syncterm-centric telnet servers -- though I would not recommend it. Furthermore, if the ttype is (literally) 'unknown', then a system-wide default terminal type may be returned, also by ``[system]`` configuration option ``termcap-unknown``. """ from x84.bbs import get_ini log = logging.getLogger(__name__) termcap_unknown = get_ini('system', 'termcap-unknown') or 'ansi' termcap_ansi = get_ini('system', 'termcap-ansi') or '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') and ttype != termcap_ansi): log.debug("terminal-type {0!r} => {1!r}" .format(ttype, termcap_ansi)) return termcap_ansi return ttype
def get_network_tag_description(term, colors): """ Return description text of message networks, if any. """ server_tags = get_ini("msg", "server_tags", split=True) network_tags = get_ini("msg", "network_tags", split=True) if not (network_tags or server_tags): return u"" return u"".join( ( u"\r\n\r\n", colors["text"](u"This board participates in intra-bbs " "messaging, "), u"".join( ( colors["text"](u"hosting network messages by tag "), u", ".join(quote(tag, colors) for tag in server_tags), ) ) if server_tags else u"", (colors["text"](u" and ") if (server_tags and network_tags) else u""), u"".join( ( colors["text"](u"participating in network messages by tag "), u", ".join(quote(tag, colors) for tag in network_tags), ) ) if network_tags else u"", u".", ) )
def view_leaf_msgnet(server_tag=None, board_id=None): if server_tag is None: server_tags = get_ini(section='msg', key='server_tags', split=True) if not server_tags: raise ValueError("no `server_tags' defined in ini file, " "section [msg].") # RECURSION for _st in server_tags: view_leaf_msgnet(server_tag=_st, board_id=None) return if board_id is None: board_ids = DBProxy('{0}keys'.format(server_tag)).keys() for _bid in board_ids: # RECURSION view_leaf_msgnet(server_tag=server_tag, board_id=_bid) return with DBProxy('{0}keys'.format(server_tag)) as key_db: echo(u'\r\n[msgnet_{0}]'.format(server_tag)) echo(u'\r\nurl_base = https://{addr}:{port}/'.format( addr=get_ini('web', 'addr'), port=get_ini('web', 'port'))) echo(u'\r\nboard_id = {0}'.format(board_id)) echo(u'\r\ntoken = {0}'.format(key_db[board_id])) echo(u'\r\npoll_interval = 300') echo(u'\r\n') echo(u'\r\n[msg]') echo(u'\r\nnetwork_tags = {0}'.format(server_tag)) echo(u'\r\n') echo(u'-' * 40)
def prompt_tags(session, term, msg, colors, public=True): xpos = max(0, (term.width // 2) - (80 // 2)) # conditionally enforce tag moderation moderated = get_ini("msg", "moderated_tags", getter="getboolean") tag_moderators = set(get_ini("msg", "tag_moderators", split=True)) # enforce 'public' tag if public and "public" not in msg.tags: msg.tags.add("public") elif not public and "public" in msg.tags: msg.tags.remove("public") # describe all available tags, as we oft want to do. do_describe_available_tags(term, colors) # and remind ourselves of the available network tags, description = get_network_tag_description(term, colors) if description: show_description(term=term, color=None, description=description) echo( u"".join( (term.move_x(xpos), term.clear_eos, u"Enter tags, separated by commas.\r\n", term.move_x(xpos), u":: ") ) ) all_tags = list_tags() while True: inp = LineEditor( subject_max_length, u", ".join(sorted(msg.tags)), colors={"highlight": colors["backlight"]} ).read() if inp is None: echo(u"".join((term.move_x(xpos), colors["highlight"]("Message canceled."), term.clear_eol))) term.inkey(1) return False msg.tags = set(filter(None, set(map(unicode.strip, inp.split(","))))) if moderated and not (tag_moderators | session.user.groups): cannot_tag = [_tag for _tag in msg.tags if _tag not in all_tags] if cannot_tag: echo( u"".join( ( u"\r\n", term.move_x(xpos), u", ".join((quote(tag, colors) for tag in cannot_tag)), u": not allowed; this system is moderated.", ) ) ) term.inkey(2) echo(term.move_up) map(msg.tags.remove, cannot_tag) continue return True
def check_anonymous_user(username): """ Boolean return when user is anonymous and is allowed. """ from x84.bbs import get_ini matching = get_ini(section='matrix', key='anoncmds', split=True) allowed = get_ini(section='matrix', key='enable_anonymous', getter='getboolean', split=False) return allowed and username in matching
def check_new_user(username): """ Boolean return when username matches `newcmds' ini cfg. """ from x84.bbs import get_ini matching = get_ini(section='matrix', key='newcmds', split=True) allowed = get_ini(section='nua', key='allow_apply', getter='getboolean') return allowed and username in matching
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]
def prompt_tags(session, term, msg, colors, public=True): xpos = max(0, (term.width // 2) - (80 // 2)) # conditionally enforce tag moderation moderated = get_ini('msg', 'moderated_tags', getter='getboolean') tag_moderators = set(get_ini('msg', 'tag_moderators', split=True)) # enforce 'public' tag if public and 'public' not in msg.tags: msg.tags.add('public') elif not public and 'public' in msg.tags: msg.tags.remove('public') # describe all available tags, as we oft want to do. do_describe_available_tags(term, colors) # and remind ourselves of the available network tags, description = get_network_tag_description(term, colors) if description: show_description(term=term, color=None, description=description) echo(u''.join( (term.move_x(xpos), term.clear_eos, u'Enter tags, separated by commas.\r\n', term.move_x(xpos), u':: '))) all_tags = list_tags() while True: inp = LineEditor(subject_max_length, u', '.join(sorted(msg.tags)), colors={ 'highlight': colors['backlight'] }).read() if inp is None: echo(u''.join( (term.move_x(xpos), colors['highlight']('Message canceled.'), term.clear_eol))) term.inkey(1) return False msg.tags = set(filter(None, set(map(unicode.strip, inp.split(','))))) if moderated and not (tag_moderators | session.user.groups): cannot_tag = [_tag for _tag in msg.tags if _tag not in all_tags] if cannot_tag: echo(u''.join((u'\r\n', term.move_x(xpos), u', '.join( (quote(tag, colors) for tag in cannot_tag)), u': not allowed; this system is moderated.'))) term.inkey(2) echo(term.move_up) map(msg.tags.remove, cannot_tag) continue return True
def get_networks(): " Get configured message networks. " from x84.bbs import get_ini log = logging.getLogger(__name__) # pull list of network-associated tags network_list = get_ini(section='msg', key='network_tags', split=True, splitsep=',') # expected configuration options, net_options = ('url_base token board_id'.split()) networks = list() for net_name in network_list: net = {'name': net_name} section = 'msgnet_{0}'.format(net_name) configured = True for option in net_options: if not get_ini(section=section, key=option): log.error('[{net_name}] Missing configuration, ' 'section=[{section}], option={option}.'.format( net_name=net_name, section=section, option=option)) configured = False net[option] = get_ini(section=section, key=option) if not configured: continue # make last_file an absolute path, relative to `datapath` net['last_file'] = os.path.join( os.path.expanduser(get_ini(section='system', key='datapath')), '{net[name]}_last'.format(net=net)) net['verify'] = True ca_path = get_ini(section=section, key='ca_path') if ca_path: ca_path = os.path.expanduser(ca_path) if not os.path.isfile(ca_path): log.warn("File not found for Config section [{section}], " "option {key}, value={ca_path}. default ca_verify " "will be used. ".format(section=section, key='ca_path', value=ca_path)) else: net['verify'] = ca_path networks.append(net) return networks
def get_networks(): " Get configured message networks. " from x84.bbs import get_ini log = logging.getLogger(__name__) # pull list of network-associated tags network_list = get_ini(section='msg', key='network_tags', split=True, splitsep=',') # expected configuration options, net_options = ('url_base token board_id'.split()) networks = list() for net_name in network_list: net = {'name': net_name} section = 'msgnet_{0}'.format(net_name) configured = True for option in net_options: if not get_ini(section=section, key=option): log.error('[{net_name}] Missing configuration, ' 'section=[{section}], option={option}.' .format(net_name=net_name, section=section, option=option)) configured = False net[option] = get_ini(section=section, key=option) if not configured: continue # make last_file an absolute path, relative to `datapath` net['last_file'] = os.path.join( os.path.expanduser(get_ini(section='system', key='datapath')), '{net[name]}_last'.format(net=net)) net['verify'] = True ca_path = get_ini(section=section, key='ca_path') if ca_path: ca_path = os.path.expanduser(ca_path) if not os.path.isfile(ca_path): log.warn("File not found for Config section [{section}], " "option {key}, value={ca_path}. default ca_verify " "will be used. ".format(section=section, key='ca_path', value=ca_path)) else: net['verify'] = ca_path networks.append(net) return networks
def do_dropfile(name, node): dropfile_path = get_ini('sesame', '{0}_droppath'.format(name)) dropfile_type = get_ini('sesame', '{0}_droptype'.format(name)) or 'doorsys' if not dropfile_path: return dropfile_type = dropfile_type.upper() if not hasattr(Dropfile, dropfile_type): raise ValueError('sesame configuration declares dropfile ' 'format of {0!r} but value is not supported ' 'by class Dropfile.'.format(dropfile_type)) _dropfile_type = getattr(Dropfile, dropfile_type) Dropfile(_dropfile_type, node).save(dropfile_path)
def add_leaf_msgnet(): import cryptography.fernet server_tags = get_ini(section="msg", key="server_tags", split=True) if not server_tags: raise ValueError(MSG_NO_SERVER_TAGS) if len(server_tags) == 1: server_tag = server_tags[0] else: while True: echo("chose a server tag: ") idx = 0 for idx, tag in server_tags: echo(u"\r\n{0}. {1}".format(idx, tag)) echo(u"\r\n: ") inp = LineEditor(width=len(str(idx)).read()) if inp is None: return try: server_tag = server_tags[int(inp)] break except ValueError: pass with DBProxy("{0}keys".format(server_tag)) as key_db: board_id = max(map(int, key_db.keys()) or [-1]) + 1 client_key = cryptography.fernet.Fernet.generate_key() key_db[board_id] = client_key echo(u"\r\n") echo(u"-" * 40) view_leaf_msgnet(server_tag, board_id)
def do_dropfile(name, node): dropfile_path = get_ini('sesame', '{0}_droppath'.format(name)) dropfile_type = get_ini('sesame', '{0}_droptype'.format(name) ) or 'doorsys' if not dropfile_path: return dropfile_type = dropfile_type.upper() if not hasattr(Dropfile, dropfile_type): raise ValueError('sesame configuration declares dropfile ' 'format of {0!r} but value is not supported ' 'by class Dropfile.'.format(dropfile_type)) _dropfile_type = getattr(Dropfile, dropfile_type) Dropfile(_dropfile_type, node).save(dropfile_path)
def __init__(self, client, sid, master_pipes): """ Class constructor. """ from x84.bbs import get_ini self.client = client self.sid = sid (self.master_write, self.master_read) = master_pipes self.timeout = get_ini('system', 'timeout') or 0
def add_leaf_msgnet(): import cryptography.fernet server_tags = get_ini(section='msg', key='server_tags', split=True) if not server_tags: raise ValueError(MSG_NO_SERVER_TAGS) if len(server_tags) == 1: server_tag = server_tags[0] else: while True: echo('chose a server tag: ') idx = 0 for idx, tag in server_tags: echo(u'\r\n{0}. {1}'.format(idx, tag)) echo(u'\r\n: ') inp = LineEditor(width=len(str(idx)).read()) if inp is None: return try: server_tag = server_tags[int(inp)] break except ValueError: pass with DBProxy('{0}keys'.format(server_tag)) as key_db: board_id = max(map(int, key_db.keys()) or [-1]) + 1 client_key = cryptography.fernet.Fernet.generate_key() key_db[board_id] = client_key echo(u'\r\n') echo(u'-' * 40) view_leaf_msgnet(server_tag, board_id)
def main(background_daemon=True): """ Entry point to configure and begin network message polling. Called by x84/engine.py, function main() as unmanaged thread. :param background_daemon: When True (default), this function returns and web modules are served in an unmanaged, background (daemon) thread. Otherwise, function call to ``main()`` is blocking. :type background_daemon: bool :rtype: None """ from threading import Thread from x84.bbs import get_ini log = logging.getLogger(__name__) poll_interval = get_ini( section='msg', key='poll_interval', getter='getint') or 1984 if background_daemon: t = Thread(target=poller, args=(poll_interval, )) t.daemon = True log.info('msgpoll at {0}s intervals.'.format(poll_interval)) t.start() else: poller(poll_interval)
def init_term(writer, env): """ Determine the final TERM and encoding and return a Terminal. curses is initialized using the value of 'TERM' of dictionary env, as well as a starting window size of 'LINES' and 'COLUMNS'. If the terminal-type is of 'ansi' or 'ansi-bbs', then the cp437 encoding is assumed; otherwise 'utf8'. A blessed-abstracted curses terminal is returned. """ from x84.bbs.ipc import IPCStream from x84.bbs import get_ini log = logging.getLogger(__name__) env['TERM'] = translate_ttype(env.get('TERM', 'unknown')) env['encoding'] = determine_encoding(env) term = Terminal(kind=env['TERM'], stream=IPCStream(writer=writer), rows=int(env.get('LINES', '24')), columns=int(env.get('COLUMNS', '80'))) if term.kind is None: # the given environment's TERM failed curses initialization # because, more than likely, the TERM type was not found. termcap_unknown = get_ini('system', 'termcap-unknown') or 'ansi' log.debug('terminal-type {0} failed, using {1} instead.' .format(env['TERM'], termcap_unknown)) term = Terminal(kind=termcap_unknown, stream=IPCStream(writer=writer), rows=int(env.get('LINES', '24')), columns=int(env.get('COLUMNS', '80'))) log.info("terminal type is {0!r}".format(term.kind)) return term
def main(background_daemon=True): """ Entry point to configure and begin network message polling. Called by x84/engine.py, function main() as unmanaged thread. :param background_daemon: When True (default), this function returns and web modules are served in an unmanaged, background (daemon) thread. Otherwise, function call to ``main()`` is blocking. :type background_daemon: bool :rtype: None """ from threading import Thread from x84.bbs import get_ini log = logging.getLogger(__name__) poll_interval = get_ini(section='msg', key='poll_interval', getter='getint' ) or 1984 if background_daemon: t = Thread(target=poller, args=(poll_interval,)) t.daemon = True log.info('msgpoll at {0}s intervals.'.format(poll_interval)) t.start() else: poller(poll_interval)
def main(name): """ Sesame runs a named door. """ session, term = getsession(), getterminal() # clear screen, echo(term.normal + u'\r\n' * term.height + term.move(0, 0)) # set font, if term.kind.startswith('ansi'): syncterm_font = get_ini('sesame', '{0}_syncterm_font'.format(name)) or 'cp437' echo(syncterm_setfont(syncterm_font)) echo(term.move_x(0) + term.clear_eol) # pylint: disable=W0212 # Access to a protected member {_columns, _rows} of a client class store_columns, store_rows = term._columns, term._rows if not prompt_resize_term(session, term, name): return with acquire_node(session, name) as node: if node == -1: echo( term.bold_red('This door has reached the maximum number ' 'of nodes and may not be played.\r\n\r\n' 'press any key.')) term.inkey() restore_screen(term, store_columns, store_rows) return do_dropfile(name, node) session.activity = u'Playing {}'.format(name) cmd, args = parse_command_args(session, name, node) env = get_env(session, name) cp437 = get_ini('sesame', '{0}_cp437'.format(name), getter='getboolean') _Door = DOSDoor if cmd.endswith('dosemu') else Door _Door(cmd=cmd, args=args, env=env, cp437=cp437).run() echo(term.bold_red + 'Press a key...') term.inkey() restore_screen(term, store_columns, store_rows)
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
def display_prompt(term, colors): """ Return string for displaying a system-wide command prompt. """ colors['lowlight'] = colors.get('lowlight', lambda txt: txt) bbsname = get_ini(section='system', key='bbsname') or 'Unnamed' return (u'{user}{at}{bbsname}{colon} '.format( user=term.session.user.handle, at=colors['lowlight'](u'@'), bbsname=bbsname, colon=colors['lowlight'](u'::')))
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
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))
def translate_ttype(ttype): from x84.bbs import get_ini log = logging.getLogger(__name__) termcap_unknown = get_ini('system', 'termcap-unknown') or 'ansi' termcap_ansi = get_ini('system', 'termcap-ansi') or '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') and ttype != termcap_ansi): log.debug("terminal-type {0!r} => {1!r}" .format(ttype, termcap_ansi)) return termcap_ansi return ttype
def check_channel_subsystem_request(self, channel, name): from x84.bbs import get_ini if name == 'sftp': if get_ini(section='sftp', key='enabled', getter='getboolean'): self.client.kind = 'sftp' self.sftp_requested.set() self.sftp = True return paramiko.ServerInterface.check_channel_subsystem_request( self, channel, name)
def main(name): """ Sesame runs a named door. """ session, term = getsession(), getterminal() # clear screen, echo(term.normal + u'\r\n' * term.height + term.move(0, 0)) # set font, if term.kind.startswith('ansi'): syncterm_font = get_ini( 'sesame', '{0}_syncterm_font'.format(name)) or 'cp437' echo(syncterm_setfont(syncterm_font)) echo(term.move_x(0) + term.clear_eol) # pylint: disable=W0212 # Access to a protected member {_columns, _rows} of a client class store_columns, store_rows = term._columns, term._rows if not prompt_resize_term(session, term, name): return with acquire_node(session, name) as node: if node == -1: echo(term.bold_red('This door has reached the maximum number ' 'of nodes and may not be played.\r\n\r\n' 'press any key.')) term.inkey() restore_screen(term, store_columns, store_rows) return do_dropfile(name, node) session.activity = u'Playing {}'.format(name) cmd, args = parse_command_args(session, name, node) env = get_env(session, name) cp437 = get_ini('sesame', '{0}_cp437'.format(name), getter='getboolean') _Door = DOSDoor if cmd.endswith('dosemu') else Door _Door(cmd=cmd, args=args, env=env, cp437=cp437).run() restore_screen(term, store_columns, store_rows)
def main(background_daemon=True): """ Entry point to configure and begin web server. Called by x84/engine.py, function main() as unmanaged thread. :param background_daemon: When True (default), this function returns and web modules are served in an unmanaged, background (daemon) thread. Otherwise, function call to ``main()`` is blocking. :type background_daemon: bool :rtype: None """ from x84.bbs import get_ini log = logging.getLogger(__name__) SCRIPT_PATH = get_ini(section='system', key='scriptpath') # ensure the SCRIPT_PATH is in os environment PATH for module lookup. sys.path.insert(0, os.path.expanduser(SCRIPT_PATH)) web_modules = get_ini(section='web', key='modules', split=True, splitsep=',') if not web_modules: log.debug('No `modules` defined in section [web]') return log.info(u'Ready web modules: {0}'.format(web_modules)) urls, funcs = get_urls_funcs(web_modules) if background_daemon: t = threading.Thread(target=server, args=( urls, funcs, )) t.daemon = True t.start() else: server(urls=urls, funcs=funcs)
def check_channel_subsystem_request(self, channel, name): from x84.bbs import get_ini if name == 'sftp': if get_ini(section='sftp', key='enabled', getter='getboolean'): self.client.kind = 'sftp' self.sftp_requested.set() self.sftp = True # XXX not returning True ?! return (super(SshSessionServer, self) .check_channel_subsystem_request(channel, name))
def prompt_resize_term(session, term, name): want_cols = get_ini('sesame', '{0}_cols'.format(name), getter='getint') want_rows = get_ini('sesame', '{0}_rows'.format(name), getter='getint') if want_cols and want_rows and not term.kind.startswith('ansi'): while not (term.width == want_cols and term.height == want_rows): resize_msg = (u'Please resize your window to {0} x {1} ' u'current size is {term.width} x {term.height} ' u'or press return. Press escape to cancel.'.format( want_cols, want_rows, term=term)) echo(term.normal + term.home + term.clear_eos + term.home) pager = Pager(yloc=0, xloc=0, width=want_cols, height=want_rows, colors={'border': term.bold_red}) echo(term.move(term.height - 1, term.width)) echo(pager.refresh() + pager.border()) width = min(70, term.width - 5) for yoff, line in enumerate(term.wrap(resize_msg, width)): echo(u''.join((term.move(yoff + 1, 5), line.rstrip()))) event, data = session.read_events(('input', 'refresh')) if event == 'refresh': continue if event == 'input': session.buffer_input(data, pushback=True) inp = term.inkey(0) while inp: if inp.code == term.KEY_ENTER: echo(term.normal + term.home + term.clear_eos) return True if inp.code == term.KEY_ESCAPE: return False inp = term.inkey(0) return True # if terminal type is 'ansi', just pass-through return True
def determine_encoding(env): """ Determine and return preferred encoding given session env. """ from x84.bbs import get_ini default_encoding = get_ini(section='session', key='default_encoding') or 'utf8' fallback_encoding = { 'ansi': 'cp437', 'ansi-bbs': 'cp437', }.get(env['TERM'], default_encoding) return env.get('encoding', fallback_encoding)
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: # message is for a network we host if tag in my_networks: transdb = DBProxy('{0}trans'.format(tag)) 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 = DBProxy('{0}queues'.format(tag)) with queuedb: queuedb[self.idx] = tag log.info('[{tag}] Message (msgid {self.idx}) queued ' 'for delivery'.format(tag=tag, self=self))
def prompt_resize_term(session, term, name): want_cols = get_ini('sesame', '{0}_cols'.format(name), getter='getint') want_rows = get_ini('sesame', '{0}_rows'.format(name), getter='getint') if want_cols and want_rows and not term.kind.startswith('ansi'): while not (term.width == want_cols and term.height == want_rows): resize_msg = (u'Please resize your window to {0} x {1} ' u'current size is {term.width} x {term.height} ' u'or press return. Press escape to cancel.' .format(want_cols, want_rows, term=term)) echo(term.normal + term.home + term.clear_eos + term.home) pager = Pager(yloc=0, xloc=0, width=want_cols, height=want_rows, colors={'border': term.bold_red}) echo(term.move(term.height - 1, term.width)) echo(pager.refresh() + pager.border()) width = min(70, term.width - 5) for yoff, line in enumerate(term.wrap(resize_msg, width)): echo(u''.join((term.move(yoff + 1, 5), line.rstrip()))) event, data = session.read_events(('input', 'refresh')) if event == 'refresh': continue if event == 'input': session.buffer_input(data, pushback=True) inp = term.inkey(0) while inp: if inp.code == term.KEY_ENTER: echo(term.normal + term.home + term.clear_eos) return True if inp.code == term.KEY_ESCAPE: return False inp = term.inkey(0) return True # if terminal type is 'ansi', just pass-through return True
def display_prompt(term, colors): """ Return string for displaying a system-wide command prompt. """ colors['lowlight'] = colors.get('lowlight', lambda txt: txt) bbsname = get_ini(section='system', key='bbsname') or 'Unnamed' xpos = 0 if term.width > 30: xpos = max(5, int((term.width / 2) - (80 / 2))) return (u'{xpos}{user}{at}{bbsname}{colon} '.format( xpos=term.move_x(xpos), user=term.session.user.handle, at=colors['lowlight'](u'@'), bbsname=bbsname, colon=colors['lowlight'](u'::')))
def __init__(self, *args, **kwargs): """ Class initializer. """ from x84.bbs import get_ini self.log = logging.getLogger(__name__) # root file folder, self.root = get_ini(section='sftp', key='root') # default file mode for uploaded files, _base = 8 # (value is octal!) self.mode = int( get_ini(section='sftp', key='uploads_filemode') or '644', _base) # allow anonymous login where enabled, otherwise use the # given `username' authenticated by ssh from x84.bbs.userbase import get_user, User _ssh_session = kwargs.pop('ssh_session') self.user = (User(u'anonymous') if _ssh_session.anonymous else get_user(_ssh_session.username)) self.flagged = set() SFTPServerInterface.__init__(self, *args, **kwargs)
def determine_encoding(env): """ Determine and return preferred encoding given session env. """ from x84.bbs import get_ini default_encoding = get_ini( section='session', key='default_encoding' ) or 'utf8' fallback_encoding = { 'ansi': 'cp437', 'ansi-bbs': 'cp437', }.get(env['TERM'], default_encoding) return env.get('encoding', fallback_encoding)
def __init__(self, *args, **kwargs): """ Class initializer. """ from x84.bbs import get_ini self.log = logging.getLogger(__name__) # root file folder, self.root = get_ini(section='sftp', key='root') # default file mode for uploaded files, _base = 8 # (value is octal!) self.mode = int(get_ini( section='sftp', key='uploads_filemode') or '644', _base) # allow anonymous login where enabled, otherwise use the # given `username' authenticated by ssh from x84.bbs.userbase import get_user, User _ssh_session = kwargs.pop('ssh_session') self.user = (User(u'anonymous') if _ssh_session.anonymous else get_user(_ssh_session.username)) self.flagged = set() SFTPServerInterface.__init__(self, *args, **kwargs)
def main(background_daemon=True): """ Entry point to configure and begin web server. Called by x84/engine.py, function main() as unmanaged thread. :param background_daemon: When True (default), this function returns and web modules are served in an unmanaged, background (daemon) thread. Otherwise, function call to ``main()`` is blocking. :type background_daemon: bool :rtype: None """ from x84.bbs import get_ini log = logging.getLogger(__name__) SCRIPT_PATH = get_ini(section='system', key='scriptpath') # ensure the SCRIPT_PATH is in os environment PATH for module lookup. sys.path.insert(0, os.path.expanduser(SCRIPT_PATH)) web_modules = get_ini(section='web', key='modules', split=True, splitsep=',') if not web_modules: log.debug('No `modules` defined in section [web]') return log.info(u'Ready web modules: {0}'.format(web_modules)) urls, funcs = get_urls_funcs(web_modules) if background_daemon: t = threading.Thread(target=server, args=(urls, funcs,)) t.daemon = True t.start() else: server(urls=urls, funcs=funcs)
def get_network_tag_description(term, colors): """ Return description text of message networks, if any. """ server_tags = get_ini('msg', 'server_tags', split=True) network_tags = get_ini('msg', 'network_tags', split=True) if not (network_tags or server_tags): return u'' return u''.join(( u'\r\n\r\n', colors['text'](u'This board participates in intra-bbs ' 'messaging, '), u''.join(( colors['text'](u'hosting network messages by tag '), u', '.join(quote(tag, colors) for tag in server_tags), )) if server_tags else u'', (colors['text'](u' and ') if (server_tags and network_tags) else u''), u''.join(( colors['text'](u'participating in network messages by tag '), u', '.join(quote(tag, colors) for tag in network_tags), )) if network_tags else u'', u'.', ))
def get_network_tag_description(term, colors): """ Return description text of message networks, if any. """ server_tags = get_ini('msg', 'server_tags', split=True) network_tags = get_ini('msg', 'network_tags', split=True) if not (network_tags or server_tags): return u'' return u''.join(( u'\r\n\r\n', colors['text'](u'This board participates in intra-bbs ' 'messaging, '), u''.join(( colors['text'](u'hosting network messages by tag '), u', '.join(quote(tag, colors) for tag in server_tags), )) if server_tags else u'', (colors['text']( u' and ') if (server_tags and network_tags) else u''), u''.join(( colors['text'](u'participating in network messages by tag '), u', '.join(quote(tag, colors) for tag in network_tags), )) if network_tags else u'', u'.', ))
def __init__(self, *args, **kwargs): from x84.bbs import get_ini self.log = logging.getLogger(__name__) # root file folder, self.root = get_ini(section='sftp', key='root') # default file mode for uploaded files, _base = 8 # (value is octal!) self.mode = int(get_ini( section='sftp', key='uploads_filemode') or '644', _base) # allow anonymous login where enabled, otherwise use the # given `username' authenticated by ssh from x84.bbs.userbase import get_user, User _ssh_session = kwargs.pop('ssh_session') self.user = (User(u'anonymous') if _ssh_session.anonymous else get_user(_ssh_session.username)) # XXX this means if a user interactively flags files, they have to # re-sftp login to see them, we should fix this self.flagged = self.user.get('flaggedfiles', set()) super(X84SFTPServer, self).__init__(*args, **kwargs)
def add_oneline(session, message): """ Add a oneliner to the local database. """ udb = DBProxy('oneliner') with udb: key = max([int(key) for key in udb.keys()] or [0]) + 1 udb[key] = { 'oneliner': message, 'alias': getsession().user.handle, 'bbsname': get_ini('system', 'bbsname'), 'timestamp': time.strftime('%Y-%m-%d %H:%M:%S'), } maybe_expunge_records() # tell everybody a new oneliner was posted, including our # -- allows it to work something like a chatroom. session.send_event('global', ('oneliner', True))
def acquire_node(session, name): nodes = get_ini('sesame', '{0}_nodes'.format(name), getter='getint') if not nodes: yield None return for node in range(1, nodes + 1): event = 'lock-{name}/{node}'.format(name=name, node=node) session.send_event(event, ('acquire', None)) if session.read_event(event): yield node session.send_event(event, ('release', None)) return # node could not be acquired yield -1
def _get_fp(section_key, optional=False): """ Return filepath of [web] option by ``section_key``. """ from x84.bbs import get_ini value = get_ini(section='web', key=section_key) or None if value: value = os.path.expanduser(value) elif optional: return None assert value is not None and os.path.isfile(value), ( 'Configuration section [web], key `{section_key}`, ' 'must {optional_exist}identify a path to an ' 'SSL {section_key} file. ' '(value is {value})'.format( section_key=section_key, optional_exist=not optional and 'exist and ' or '', value=value)) return value
def parse_command_args(session, name, node): # Parse command path and arguments command = get_ini('sesame', name) if ' ' in command: command, args = command.split(' ', 1) args = args.format( session=session.to_dict(), system=dict(ini.CFG.items('system')), node=node, ) args = shlex.split(args) else: args = [] if not os.path.exists(command): raise RuntimeError("The door {0} specified a command path of " "{1!r}, but no such file exists." .format(name, command)) return command, args
def get_response(request_data): """ Serve one API server request and return. """ # todo: The caller runs a while loop .. this should be a script # that does a while loop and imports x84.webserve. from x84.bbs import DBProxy, get_ini log = logging.getLogger(__name__) # validate primary json request keys for key in (_key for _key in VALIDATE_FIELDS if _key not in request_data): return server_error(log_func=log.warn, log_msg='Missing field, {key!r}'.format(key=key)) # validate this server offers such message network server_tags = get_ini(section='msg', key='server_tags', split=True, splitsep=',') if not request_data['network'] in server_tags: return server_error(log_func=log.warn, log_msg=('[{data[network]}] not in server_tags ' '({server_tags})'.format( data=request_data, server_tags=server_tags)), http_msg=u'Server error') tag = request_data['network'] # validate authentication token try: board_id, token, auth_tmval = parse_auth(request_data) except ValueError, err: return server_error( log_func=log.warn, log_msg=('[{data[network]}] Bad token: {err}'.format( data=request_data, err=err)), http_msg=u'Invalid token')
import collections import urlparse import textwrap import math import sys # local from x84.bbs import getsession, getterminal, echo, LineEditor, get_ini # 3rd-party import feedparser import html2text import requests #: fontset for SyncTerm emulator SYNCTERM_FONT = get_ini(section='hackernews', key='syncterm_font') or 'topaz' #: color of titlebars when viewing article summary COLOR_MAIN = get_ini(section='hackernews', key='color_main') or 'black_on_red' #: color of titlebars when viewing article COLOR_VIEW = get_ini(section='hackernews', key='color_view') or "black_on_magenta" USER_AGENT = 'Lynx/2.8.7rel.2 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/1.0.0a' RSS_URL = 'https://news.ycombinator.com/rss' RSS_TITLE = 'Hacker News' ARTICLE_LIMIT = 100 REQUEST_TIMEOUT = 10 #: structure defines an article