X-Git-Url: https://mudpy.org/gitweb?p=mudpy.git;a=blobdiff_plain;f=lib%2Fmuff%2Fmuffuser.py;h=68818692f43b5cd223fb88e93f68593911483943;hp=4ecdebf156a0bf681bf8dac01e3da83e3146b115;hb=0f39af78818acbbee0b99145ff5ff303553027c6;hpb=d49d19d943672b3cea1b2e43802f4b5eca6c81b5 diff --git a/lib/muff/muffuser.py b/lib/muff/muffuser.py index 4ecdebf..6881869 100644 --- a/lib/muff/muffuser.py +++ b/lib/muff/muffuser.py @@ -1,103 +1,191 @@ """User objects for the MUFF Engine""" -# Copyright (c) 2005 mudpy, Jeremy Stanley -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# - Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# - Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following -# disclaimer in the documentation and/or other materials provided -# with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. +# Copyright (c) 2005 mudpy, Jeremy Stanley , all rights reserved. +# Licensed per terms in the LICENSE file distributed with this software. +# user accounts are stored in ini-style files supported by ConfigParser import ConfigParser + +# test for existence of the account dir with os.listdir and os.mkdir to make it +import os + +# string.replace is used to perform substitutions for color codes and the like import string +# hack to load all modules in the muff package import muff for module in muff.__all__: exec("import " + module) class User: + """This is a connected user.""" + def __init__(self): + """Default values for the in-memory user variables.""" + + # the account name self.name = "" + + # the password hash self.passhash = "" + + # the current client ip address self.address = "" + + # the previous client ip address + self.last_address = "" + + # the current socket connection object self.connection = None + + # a flag to denote whether the user is authenticated self.authenticated = False + + # number of times password entry has failed during this session self.password_tries = 1 + + # the current state of the user self.state = "entering account name" + + # flag to indicate whether a menu has been displayed self.menu_seen = False + + # current error condition, if any self.error = "" + + # fifo-style queue for lines of user input self.input_queue = [] + + # fifo-style queue for blocks of user output self.output_queue = [] + + # holding pen for unterminated user input self.partial_input = "" + + # flag to indicate the current echo status of the client self.echoing = True + + # an object containing persistent account data self.record = ConfigParser.SafeConfigParser() + def load(self): - filename = muffconf.config_data.get("general", "account_path") + "/" + self.name + """Retrieve account data from cold storage.""" + + # what the filename for the user account should be + filename = muffconf.config_data.get("files", "accounts") + "/" + self.name + + # try to load the password hash and last connection ipa try: self.record.read(filename) self.passhash = self.record.get("account", "passhash") self.last_address = self.record.get("account", "last_address", self.address) + + # if we can't, that's okay too except: pass + def get_passhash(self): - filename = muffconf.config_data.get("general", "account_path") + "/" + self.proposed_name + """Retrieve the user's account password hash from storage.""" + + # what the filename for the user account could be + filename = muffconf.config_data.get("files", "accounts") + "/" + self.proposed_name + + # create a temporary account record object temporary_record = ConfigParser.SafeConfigParser() + + # try to load the indicated account and get a password hash try: temporary_record.read(filename) self.passhash = temporary_record.get("account", "passhash") + + # otherwise, the password hash is empty except: self.passhash = "" + def save(self): + """Record account data to cold storage.""" + + # the user account must be authenticated to save if self.authenticated: - filename = muffconf.config_data.get("general", "account_path") + "/" + self.name.lower() + + # create an account section if it doesn't exist if not self.record.has_section("account"): self.record.add_section("account") + + # write some in-memory data to the record self.record.set("account", "name", self.name) self.record.set("account", "passhash", self.passhash) self.record.set("account", "last_address", self.address) + + # the account files live here + account_path = muffconf.config_data.get("files", "accounts") + # the filename to which we'll write + filename = account_path + "/" + self.name.lower() + + # if the directory doesn't exist, create it + # TODO: create account_path with 0700 perms + try: + if os.listdir(account_path): pass + except: + os.mkdir(account_path, ) + + # open the user account file for writing + # TODO: create filename with 0600 perms record_file = file(filename, "w") + + # dump the account data to it self.record.write(record_file) + + # close the user account file record_file.close() + def show_menu(self): + """Send the user their current menu.""" self.send(muffmenu.get_menu(self)) + def remove(self): + """Remove a user from the list of connected users.""" muffvars.userlist.remove(self) + def send(self, output, eol="$(eol)"): + """Send arbitrary text to a connected user.""" + + # only when there is actual output if output: + + # start with a newline, append the message, then end + # with the optional eol string passed to this function output = "$(eol)" + output + eol + + # replace eol markers with a crlf + # TODO: search for markers and replace from a dict output = string.replace(output, "$(eol)", "\r\n") - output = string.replace(output, "$(div)", "\r\n\r\n") + + # replace display markers with ansi escapse sequences output = string.replace(output, "$(bld)", chr(27)+"[1m") output = string.replace(output, "$(nrm)", chr(27)+"[0m") output = string.replace(output, "$(blk)", chr(27)+"[30m") output = string.replace(output, "$(grn)", chr(27)+"[32m") output = string.replace(output, "$(red)", chr(27)+"[31m") + + # the user's account name output = string.replace(output, "$(account)", self.name) + + # wrap the text at 80 characters + # TODO: prompt user for preferred wrap width output = muffmisc.wrap_ansi_text(output, 80) + + # drop the formatted output into the output queue self.output_queue.append(output) + + # try to send the last item in the queue, remove it and + # flag that menu display is not needed try: self.connection.send(self.output_queue[0]) self.output_queue.remove(self.output_queue[0]) self.menu_seen = False + + # but if we can't, that's okay too except: pass