Imported from archive.
[mudpy.git] / lib / muff / muffuser.py
1 """User objects for the MUFF Engine"""
2
3 # Copyright (c) 2005 mudpy, Jeremy Stanley <fungi@yuggoth.org>, all rights reserved.
4 # Licensed per terms in the LICENSE file distributed with this software.
5
6 # user accounts are stored in ini-style files supported by ConfigParser
7 import ConfigParser
8
9 # test for existence of the account dir with os.listdir and os.mkdir to make it
10 import os
11
12 # string.replace is used to perform substitutions for color codes and the like
13 import string
14
15 # hack to load all modules in the muff package
16 import muff
17 for module in muff.__all__:
18         exec("import " + module)
19
20 class User:
21         """This is a connected user."""
22
23         def __init__(self):
24                 """Default values for the in-memory user variables."""
25
26                 # the account name
27                 self.name = ""
28
29                 # the password hash
30                 self.passhash = ""
31
32                 # the current client ip address
33                 self.address = ""
34
35                 # the previous client ip address
36                 self.last_address = ""
37
38                 # the current socket connection object
39                 self.connection = None
40
41                 # a flag to denote whether the user is authenticated
42                 self.authenticated = False
43
44                 # number of times password entry has failed during this session
45                 self.password_tries = 1
46
47                 # the current state of the user
48                 self.state = "entering account name"
49
50                 # flag to indicate whether a menu has been displayed
51                 self.menu_seen = False
52
53                 # current error condition, if any
54                 self.error = ""
55
56                 # fifo-style queue for lines of user input
57                 self.input_queue = []
58
59                 # fifo-style queue for blocks of user output
60                 self.output_queue = []
61
62                 # holding pen for unterminated user input
63                 self.partial_input = ""
64
65                 # flag to indicate the current echo status of the client
66                 self.echoing = True
67
68                 # an object containing persistent account data
69                 self.record = ConfigParser.SafeConfigParser()
70
71         def load(self):
72                 """Retrieve account data from cold storage."""
73
74                 # what the filename for the user account should be
75                 filename = muffconf.config_data.get("files", "accounts") + "/" + self.name
76
77                 # try to load the password hash and last connection ipa
78                 try:
79                         self.record.read(filename)
80                         self.passhash = self.record.get("account", "passhash")
81                         self.last_address = self.record.get("account", "last_address", self.address)
82
83                 # if we can't, that's okay too
84                 except:
85                         pass
86
87         def get_passhash(self):
88                 """Retrieve the user's account password hash from storage."""
89
90                 # what the filename for the user account could be
91                 filename = muffconf.config_data.get("files", "accounts") + "/" + self.proposed_name
92
93                 # create a temporary account record object
94                 temporary_record = ConfigParser.SafeConfigParser()
95
96                 # try to load the indicated account and get a password hash
97                 try:
98                         temporary_record.read(filename)
99                         self.passhash = temporary_record.get("account", "passhash")
100
101                 # otherwise, the password hash is empty
102                 except:
103                         self.passhash = ""
104
105         def save(self):
106                 """Record account data to cold storage."""
107
108                 # the user account must be authenticated to save
109                 if self.authenticated:
110
111                         # create an account section if it doesn't exist
112                         if not self.record.has_section("account"):
113                                 self.record.add_section("account")
114
115                         # write some in-memory data to the record
116                         self.record.set("account", "name", self.name)
117                         self.record.set("account", "passhash", self.passhash)
118                         self.record.set("account", "last_address", self.address)
119
120                         # the account files live here
121                         account_path = muffconf.config_data.get("files", "accounts")
122                         # the filename to which we'll write
123                         filename = account_path + "/" + self.name.lower()
124
125                         # if the directory doesn't exist, create it
126                         # TODO: create account_path with 0700 perms
127                         try:
128                                 if os.listdir(account_path): pass
129                         except:
130                                 os.mkdir(account_path, )
131
132                         # open the user account file for writing
133                         # TODO: create filename with 0600 perms
134                         record_file = file(filename, "w")
135
136                         # dump the account data to it
137                         self.record.write(record_file)
138
139                         # close the user account file
140                         record_file.close()
141
142         def show_menu(self):
143                 """Send the user their current menu."""
144                 self.send(muffmenu.get_menu(self))
145
146         def remove(self):
147                 """Remove a user from the list of connected users."""
148                 muffvars.userlist.remove(self)
149
150         def send(self, output, eol="$(eol)"):
151                 """Send arbitrary text to a connected user."""
152
153                 # only when there is actual output
154                 if output:
155
156                         # start with a newline, append the message, then end
157                         # with the optional eol string passed to this function
158                         output = "$(eol)" + output + eol
159
160                         # replace eol markers with a crlf
161                         # TODO: search for markers and replace from a dict
162                         output = string.replace(output, "$(eol)", "\r\n")
163
164                         # replace display markers with ansi escapse sequences
165                         output = string.replace(output, "$(bld)", chr(27)+"[1m")
166                         output = string.replace(output, "$(nrm)", chr(27)+"[0m")
167                         output = string.replace(output, "$(blk)", chr(27)+"[30m")
168                         output = string.replace(output, "$(grn)", chr(27)+"[32m")
169                         output = string.replace(output, "$(red)", chr(27)+"[31m")
170
171                         # the user's account name
172                         output = string.replace(output, "$(account)", self.name)
173
174                         # wrap the text at 80 characters
175                         # TODO: prompt user for preferred wrap width
176                         output = muffmisc.wrap_ansi_text(output, 80)
177
178                         # drop the formatted output into the output queue
179                         self.output_queue.append(output)
180
181                         # try to send the last item in the queue, remove it and
182                         # flag that menu display is not needed
183                         try:
184                                 self.connection.send(self.output_queue[0])
185                                 self.output_queue.remove(self.output_queue[0])
186                                 self.menu_seen = False
187
188                         # but if we can't, that's okay too
189                         except:
190                                 pass
191