· 7 years ago · Dec 11, 2018, 02:50 AM
1local channels = {
2 atm = 1,
3 db = 2,
4}
5
6os.loadAPI("/common.lua")
7os.loadAPI("/sha256.lua")
8
9local database = {}
10local dbpath = "/bank.db"
11local logpath = "/bank.log"
12local maintenance_pass = "test"
13local modem = peripheral.find("modem")
14
15local function loadDB(path)
16 local ret = {}
17 if fs.exists(path) then
18 local f = fs.open(path, "r")
19 local c = f.readAll()
20 ret = textutils.unserialise(c)
21 if not ret then return false end
22 end
23 return ret
24end
25local function saveDB(path, db)
26 local f = fs.open(path, "w")
27 local s = textutils.serialise(db)
28 f.write(s)
29 f.close()
30 return true
31end
32local function printHeader(str, ny)
33 local w, h = term.getSize()
34 local x, y = term.getCursorPos()
35 term.setCursorPos(math.ceil(w/2) - math.floor(#str/2), ny or 1)
36 term.clearLine()
37 term.write(str)
38 term.setCursorPos(x, y)
39end
40local logfile = fs.open(logpath, "a")
41local function log(top, str, nosave)
42 local form = (top and ("["..top.."] ") or "")..(str)
43 print(form)
44 printHeader("Database running with "..dbpath)
45 if not nosave then
46 logfile.writeLine(form)
47 logfile.flush()
48 end
49end
50
51database = loadDB(dbpath)
52saveDB(dbpath, database)
53
54modem.open(channels.db)
55term.clear()
56term.setCursorPos(1,2)
57logfile.writeLine()
58log("Init", "Database started")
59
60while true do
61 local event, side, chan, reply, msg = os.pullEvent()
62 if event == "modem_message" and chan == channels.db and reply == channels.atm and type(msg) == "table" then
63
64 if msg[1] == "auth" then
65 local user = msg[2]:lower()
66 local userdata = database[user]
67 if userdata then
68 modem.transmit(reply, channels.db, {true, userdata.salt})
69 local resp = common.waitForMsg(reply, 2)
70 if resp and resp[1] then
71 local ok = resp[2] == userdata.passwd
72 modem.transmit(reply, channels.db, {ok, not ok and "Wrong password" or nil})
73 log("Auth", (ok and "Valid" or "Invalid").." auth: "..user)
74 else log("Auth", user.." timed out")
75 end
76 else
77 log("Auth", "User not found: "..user)
78 modem.transmit(reply, channels.db, {false, "User not found"})
79 end
80
81 elseif msg[1] == "register" then
82 local user = msg[2]:lower()
83 local ok = true
84 if user:find("%s") then ok = false log("Register", "Tried registering with a space: "..user) modem.transmit(reply, channels.db, {false, "Username cannot contain spaces"}) end
85 if database[user] then ok = false log("Register", "User already exists: "..user) modem.transmit(reply, channels.db, {false, "User already exists"}) end
86 if ok then
87 local salt = common.genSalt(16)
88 modem.transmit(reply, channels.db, {true, salt})
89 local r = common.waitForMsg(reply, 2)
90 if r and r[1] and type(r[2]) == "string" and #r[2] == 64 then
91 database[user] = {}
92 database[user].balance = 0
93 database[user].passwd = r[2]
94 database[user].salt = salt
95 saveDB(dbpath, database)
96 log("Register", user.." created")
97 modem.transmit(reply, channels.db, {true})
98 else
99 log("Register", user.." timed out")
100 end
101 end
102
103 elseif msg[1] == "balance" then
104 local user = msg[2]:lower()
105 local userdata = database[user]
106 if msg[3] == userdata.passwd then
107 modem.transmit(reply, channels.db, {true, userdata.balance})
108 log("Balance", "Sent balance: "..user)
109 else
110 modem.transmit(reply, channels.db, {false, "Invalid hash"})
111 log("Balance", "Invalid hash from: "..user)
112 end
113
114 elseif msg[1] == "transfer" then
115 local user = msg[2]:lower()
116 local recip = msg[4]:lower()
117 local hash = msg[3]
118 local amt = tonumber(msg[5]) or math.huge
119 amt = math.abs(amt)
120 local userdat = database[user]
121 local recipdat = database[recip]
122 local ok = true
123
124 if not recipdat then ok = false log("Transfer", user.." tried to send to invalid: "..recip) modem.transmit(reply, channels.db, {false, "Recipient doesn't exist"}) end
125 if userdat.passwd ~= hash then ok = false log("Transfer", "Invalid hash from: "..user) modem.transmit(reply, channels.db, {false, "Invalid hash"}) end
126 if userdat.balance < amt then ok = false log("Transfer", user.." tried to send "..amt.." but has "..userdat.balance.." to "..recip) modem.transmit(reply, channels.db, {false, "Insufficent funds"}) end
127 if ok then
128 database[user].balance = userdat.balance - amt
129 database[recip].balance = recipdat.balance + amt
130 saveDB(dbpath, database)
131 modem.transmit(reply, channels.db, {true, userdat.balance})
132 log("Transfer", user.." transfered "..amt.." to "..recip)
133 end
134 end
135
136 elseif event == "key" and side == keys.enter then
137 log("Admin", "Enter password")
138 write("> ")
139 local pswd = read("*")
140 if pswd == maintenance_pass then
141 log("Admin", "Entering maintenance mode")
142 while true do
143 write("> ")
144 local cmd = read()
145 cmd = common.split(cmd, "%s")
146 if cmd[1] == "set" then
147 local user = cmd[2]
148 local amt = tonumber(cmd[3]) or 0
149 if type(user) ~= "string" or type(amt) ~= "number" then
150 log("Error", "Usage: set <user> <balance>")
151 else
152 user = user:lower()
153 if database[user] then
154 database[user].balance = amt
155 saveDB(dbpath, database)
156 log("Admin", "Set "..user.." balance to "..amt)
157 else
158 log("Admin", "Cannot set balance of "..user)
159 end
160 end
161
162 elseif cmd[1] == "add" then
163 local user = cmd[2]
164 local amt = tonumber(cmd[3]) or 0
165 if type(user) ~= "string" or type(amt) ~= "number" then
166 log("Error", "Usage: add <user> <amount>")
167 else
168 user = user:lower()
169 if database[user] then
170 database[user].balance = database[user].balance + amt
171 saveDB(dbpath, database)
172 log("Admin", (amt >= 0 and "Added " or "Removed ")..amt.." to "..user.."'s balance")
173 else
174 log("Admin", "Cannot add to balance of "..user)
175 end
176 end
177
178 elseif cmd[1] == "create" then
179 local user = cmd[2]
180 if type(user) ~= "string" then
181 log("Error", "Usage: create <user>")
182 else
183 user = user:lower()
184 local ok = true
185 if user:find("%s") then ok = false log("Admin", "Tried registering with a space: "..user) end
186 if database[user] then ok = false log("Admin", "User already exists: "..user) end
187 if ok then
188 log("Admin", "Enter password: ")
189 write("> ")
190 local pass = read()
191 local salt = common.genSalt(16)
192 local hash = sha256.pbkdf2(pass, salt, 64):toHex()
193 database[user] = {}
194 database[user].balance = 0
195 database[user].passwd = hash
196 database[user].salt = salt
197 saveDB(dbpath, database)
198 log("Admin", user.." created")
199 end
200 end
201
202 elseif cmd[1] == "delete" then
203 local user = cmd[2]
204 if type(user) ~= "string" then
205 log("Error", "Usage: delete <user>")
206 else
207 user = user:lower()
208 if database[user] then
209 database[user] = nil
210 saveDB(dbpath, database)
211 log("Admin", user.." deleted from database")
212 else
213 log("Admin", "Tried to delete invalid user: "..user)
214 end
215 end
216
217 elseif cmd[1] == "recover" then
218 local user = cmd[2]
219 if type(user) ~= "string" then
220 log("Error", "Usage: recover <user>")
221 else
222 user = user:lower()
223 if database[user] then
224 log("Admin", "Enter new password: ")
225 write("> ")
226 local pass = read()
227 local salt = common.genSalt(16)
228 local hash = sha256.pbkdf2(pass, salt, 64):toHex()
229 database[user].passwd = hash
230 database[user].salt = salt
231 saveDB(dbpath, database)
232 log("Admin", user.." password changed")
233 else
234 log("Admin", user.." does not exists")
235 end
236 end
237
238 elseif cmd[1] == "list" then
239 local users = {}
240 for user, userdat in pairs(database) do
241 log(nil, user..": "..userdat.balance, true)
242 end
243 elseif cmd[1] == "help" then
244 log(nil, "set <user> <balance> \16 Sets a user balance", true)
245 log(nil, "add <user> <amount> \16 Add/Remove from user balance", true)
246 log(nil, "create <user> \16 Adds account to database", true)
247 log(nil, "delete <user> \16 Removes account from database", true)
248 log(nil, "recover <user> \16 Changes account's password", true)
249 log(nil, "list \16 Lists registered accounts", true)
250 log(nil, "quit \16 Quits maintenance mode", true)
251 log(nil, "stop \16 Stops the database", true)
252
253 elseif cmd[1] == "quit" then break
254 elseif cmd[1] == "stop" then return
255
256 else log("Command", "Invalid command: "..(cmd[1] or ""), true)
257 end
258 repeat event, side = os.pullEvent("key") until side == keys.enter
259 end
260 log("Admin", "Exiting maintenance")
261 else
262 log("Admin", "Wrong password")
263 end
264 end
265end
266log("Admin", "Database shutting down")