local clientAssignmentDirectory = "usr/" local userList = "user_list" local adminUser = "KillaVanilla" local adminPass = "something" local lastAccessTimes = {} local logfile = "server.log" local logServ = 117 function log(stuff) local lHandle = fs.open(logfile, "a") lHandle.writeLine(stuff) lHandle.close() end local function spec_print(...) local args2 = {...} local str = "" for i,v in ipairs(args2) do str = str..v end rednet.send(logServ, textutils.serialize({"LOGSERV", "GradeServ", str})) if http then local wHandle = http.get("http://timeapi.org/utc/now") if wHandle then str = "[ RL TIME: "..wHandle.readAll().." UTC] "..str else str = "[ MC TIME: Day "..os.day().." @ "..textutils.formatTime(os.time()).."} "..str end end print(str) log(str) end local successMsg = textutils.serialize({"assignment", "return_code", "success"}) assert(fs.exists(userList), userList.." does not exist!") for i,v in ipairs(rs.getSides()) do if peripheral.getType(v) == "modem" then rednet.open(v) end end if not fs.exists(clientAssignmentDirectory) then fs.makeDir(clientAssignmentDirectory) end local handle = io.open(userList, "r") local cred_table = {} for line in handle:lines() do string.gsub(line, "([%a%p%d]+)%s+([%a%p%d]+)", function(user, pass) cred_table[user] = pass if not fs.exists(fs.combine(clientAssignmentDirectory, user)) then fs.makeDir(fs.combine(clientAssignmentDirectory, user)) end end) end cred_table[adminUser] = adminPass handle:close() local function runTests(inputFile) -- run a simple pair of tests designed to catch (lazy) cheaters and weed out obviously-wrong answers. if not fs.exists(inputFile) then spec_print("Attempted to run test on nonexistent file: "..input) return "false", "File does not exist" end local iHandle = fs.open(inputFile, "r") local input = iHandle.readAll() iHandle.close() for i,v in ipairs(fs.list(clientAssignmentDirectory)) do if fs.isDir(fs.combine(clientAssignmentDirectory, v)) then for i,file in ipairs(fs.list(fs.combine(clientAssignmentDirectory, v))) do if file ~= "SanityTests.log" then local handle = fs.open(fs.combine(fs.combine(clientAssignmentDirectory, v), file), "r") if handle.readAll() == input then handle.close() return "false" -- cheater! end handle.close() end end end end local ok, err = pcall(loadstring(input)) return tostring(ok) end local function getLoginCredent(id) for i,v in pairs(lastAccessTimes) do if v[2] == id then return true, i end end end local function runSanityTests() for userID,user in ipairs(fs.list(clientAssignmentDirectory)) do if fs.isDir(fs.combine(clientAssignmentDirectory, user)) then local sanityLog = fs.open(fs.combine(fs.combine(clientAssignmentDirectory, user), "SanityTests.log"), "w") for fileID, fileName in ipairs(fs.list(fs.combine(clientAssignmentDirectory, user))) do if fileName ~= "SanityTests.log" then sanityLog.writeLine(fileName..": "..runTests(fs.combine(fs.combine(clientAssignmentDirectory, user), fileName))) end end sanityLog.close() end end end local function listenerThread() while true do local id, msg = rednet.receive() msg = textutils.unserialize(msg) if msg then local hasLoggedIn, user = getLoginCredent(id) local isAdmin = false if hasLoggedIn then isAdmin = (user == adminUser) end if msg[1] == "assignment" then if msg[2] == "login" then if cred_table[msg[3]] == msg[4] then if msg[3] == adminUser then spec_print("ID "..id.." logged in as "..msg[3]..". (admin account)") else spec_print("ID "..id.." logged in as "..msg[3]..". (student account)") end lastAccessTimes[msg[3]] = {os.clock(), id} rednet.send(id, successMsg) end elseif hasLoggedIn then if msg[2] == "logout" then spec_print("ID "..id.." ("..user..") logged out.") lastAccessTimes[user] = nil rednet.send(id, successMsg) elseif msg[2] == "submit" then if not fs.exists(fs.combine(clientAssignmentDirectory..user, msg[3])) then local file = fs.open(fs.combine(clientAssignmentDirectory..user, msg[3]), "w") file.write(msg[4]) file.close() spec_print("ID "..id.." ("..user..") submitted an assignment as file "..msg[3].." ("..fs.getSize(fs.combine(clientAssignmentDirectory..user, msg[3])).." bytes written).") rednet.send(id, successMsg) end lastAccessTimes[user][1] = os.clock() elseif msg[2] == "get_results" then spec_print("ID "..id.." ("..user..") downloaded his/her test results.") local file = fs.open(fs.combine(clientAssignmentDirectory..user, "SanityTests.log"), "r") rednet.send(id, textutils.serialize({"assignment", "results", file.readAll()})) file.close() lastAccessTimes[user][1] = os.clock() elseif isAdmin then local fullFileName = fs.combine(clientAssignmentDirectory, msg[3]) if msg[2] == "get" then if fs.exists(fullFileName) then spec_print("ID "..id.." ("..user..") downloaded file "..fullFileName..".") local handle = fs.open(fullFileName, "r") rednet.send(id, textutils.serialize({"assignment", "file", msg[3], handle.readAll()})) handle.close() end elseif msg[2] == "list" then spec_print("ID "..id.." ("..user..") requested directory listing for "..fullFileName..".") if fs.exists(fullFileName) and fs.isDir(fullFileName) then rednet.send(id, textutils.serialize({"assignment", "list", msg[3], fs.list(fullFileName)})) end elseif msg[2] == "put" then local handle = fs.open(fullFileName, "w") handle.write(msg[4]) handle.close() spec_print("ID "..id.." ("..user..") uploaded file "..fullFileName.." ("..fs.getSize(fullFileName).." bytes written).") rednet.send(id, successMsg) elseif msg[2] == "delete" then if fs.exists(fullfileName) then fs.delete(fullFileName) spec_print("ID "..id.." ("..user..") deleted file "..fullFileName..".") end end end end end end end end local function timedEventThread() local sanityTestItr = 0 while true do local lastAccessTimes_clone = {} for i,v in pairs(lastAccessTimes) do if os.clock() < v[1]+300 then lastAccessTimes_clone[i] = v else spec_print("Automatically logging out ID "..v[2].." ("..i..") due to inactivity.") end end lastAccessTimes = lastAccessTimes_clone sanityTestItr = sanityTestItr+1 if sanityTestItr >= 30 then runSanityTests() sanityTestItr = 0 end os.sleep(1) end end spec_print("GradeServ V1.2 is ONLINE!") parallel.waitForAll(listenerThread, timedEventThread)