main = 1; punctuation = [[!;':@?,`.]] _oldError = _ERRORMESSAGE; lastInput = "<none>"; lastUser = "<none>"; ErrorTime = getSecs(); Errors = 0; function HandleError(s) if (secs > (ErrorTime + 5)) then ErrorTime = secs; Errors = 0; end; Errors = Errors + 1; if (Errors > 15) then print("ERK! Too many errors, giving up and quitting."); exit(1); end; local r, i; r = s .. "\ncaused by <"; r = r .. lastInput; r = r .. "> by "; r = r .. lastUser; r = r .. ". stack backtrace:\n"; local f; if not colloquy.noFork then f = writeto('|mail -s "[colloquy] stack backtrace" ' .. colloquy.email); else f = _STDERR; end print(strsub(r, 1, -2)); if (f) then write(f, r); end; for i = 2, 1000 do local a = getinfo(i) if a==nil then break end r = i - 1 .. ":" if (a.name) then r =r .. " function '" .. a.name .. "'"; end; if (a.currentline > -1) then r = r .. " at line " .. a.currentline; end; r = r .. " [" .. a.short_src .. "]\n"; print(strsub(r, 1, -2)); if (f) then write(f, r); end; end write("\n"); if (f) then closefile(f); end; print(""); sendToAll("WARNING: Caught an error that shouldn't have happened. Blame " .. lastUser .. ".", S_ALERT); end function db(v) print(v); end; function handleWrites(sockets) local i, v, err; for i, v in sockets do if (v == colloquy.resolver.socket) then colloquy.resolver:readySend(); elseif (colloquy.connections[v].socket:readySend() ~= nil and not colloquy.connections[v].socket.toClose) then disconnectUser(v, "- Connection closed."); end; local conn = colloquy.connections[v]; if (v ~= colloquy.resolver.socket and conn.socket.toClose == 2) then conn.socket.socket:close(); colloquy.connections[v] = nil; end; end; end; function printString(a) local i, s; for i=1,strlen(a) do write(strbyte(a, i) .. " "); end write("\n"); for i=1,strlen(a) do if (strbyte(a, i) > 31 and strbyte(a, i) < 100) then write(strsub(a, i, i) .. " "); elseif (strbyte(a, i) > 100 and strbyte(a, i) < 127) then write(strsub(a, i, i) .. " "); else write(" "); end; end; write("\n\n"); end; function handleReads(sockets) local i, v, err, string; for i, v in sockets do if (v == colloquy.server) then connectUser(); elseif (v == colloquy.botServer) then connectUser(1); elseif (v == colloquy.metaServer) then connectUser(2); elseif (v == colloquy.resolver.socket) then string = colloquy.resolver:readyRead("\n"); if (string == nil) then log("Connection to resolver lost."); return nil; elseif (string ~= "") then resolverResult(string); end; else if (colloquy.connections[v] == nil or colloquy.connections[v].socket == nil) then return nil; end; local conn = colloquy.connections[v]; local opts = conn.socket:readyPeek("\255"); if (opts ~= nil and opts ~= "") then opts = conn.socket:readyRead("\255"); string = parseTelnetOpts(opts, conn); end; if (string == nil) then string = conn.socket:readyRead("[\n\r]"); else string = string .. conn.socket:readyRead("[\n\r]"); end; if (string == nil) then disconnectUser(v, "- Connection closed."); conn.socket:close(); colloquy.connections[v] = nil; return nil; elseif (string ~= "") then -- wahey! We've a line of text. Let us bounce. -- remove delete characters first... while (strfind(string, "\127")) do string = gsub(string, "^\127", ""); string = gsub(string, "[^\127]\127", ""); end; while (strfind(string, "\8")) do string = gsub(string, "^\8", ""); string = gsub(string, "[^\8]\8", ""); end; lastUser = conn.username; lastConnection = conn; -- work though thr string, getting each chunk ended by \r\n, and pass it to parseInput... local tmp = gsub(string, "\r\n", "\n"); tmp = gsub(string, "\n\r", "\n"); if (not strfind(tmp, "\n", 1, 1)) then tmp = tmp .. "\n"; end; local l; repeat l = strsub(tmp, 1, strfind(tmp, "\n", 1, 1) - 1); call(parseInput, {v, l}, "x", HandleError); tmp = strsub(tmp, strfind(tmp, "\n", 1, 1) + 1, -1); until not strfind(tmp, "\n", 1, 1) end; end; end; end; function log(...) tinsert(arg, 1, date() .. ": "); tinsert(arg, 1, colloquy.logfile); tinsert(arg, arg.n + 1, "\n"); call(write, arg); flush(colloquy.logfile); end; lastLogRotation = date("%Y%m%d"); function expireLists() local toDie = {} for i, v in lists do if ( not v.used ) then -- this has never been used! give it the benefit of the doubt, and set it to now. v.used = secs; end; if ( (secs - v.used) > (60*60*24*(colloquy.listExpirey)) and not strfind(v.flags, "P", 1, 1) ) then -- Awwww! I bet it feels unwanted. Let's kill it, and put it out of it's misery. tinsert(toDie, i); end; end; for i, v in toDie do if (type(v) == "string") then lists[v] = nil; end; end; end; function doHousekeeping() local i, v; local toDelete = {} for i, v in colloquy.connections do if (v.status == 0) then -- they've not yet logged on... if ((secs - v.idle) > 30) then send("Your logon has timed out.", v, S_DISCONNECT); disconnectUser(i, ""); end; end; if (colloquy.guestTimeout and v.status == 1) then -- timeout guests... if ((secs - v.conTime) > colloquy.guestTimeout) then sendGM(v, S_DISCONNECT, "gGuestTimeout"); disconnectUser(i, "- Guest for too long."); elseif (not v.warned and (secs - v.conTime) > (colloquy.guestTimeout - 60)) then sendGM(v, S_WARN, "gGuestTimeout1") v.warned = 1; end end if ((secs - v.idle) > colloquy.maxIdle * 60) then if (colloquy.kickIdle) then if ((v.privs and strfind(v.privs, "Z", 1, 1)) or (v.restrict and strfind(v.restrict, "B", 1, 1))) then v.idle = secs; else sendGM(v, S_DISCONNECT, "gIdledOut"); disconnectUser(i, "- Idled out"); end; elseif (not v.veryIdle and not (v.restrict and strfind(v.restrict, "B", 1, 1))) then sendGMAll(S_IDLE, "gAutoIdle", v.username); v.idleReason = "Became automatically idle: " .. date("%a %b %e %H:%M:%S %Y"); v.veryIdle = colloquy.maxIdle * 60 ; end; end; if (v.timeWarn ~= 0 and v.timeWarn > 0 and (secs > v.timeTick)) then v.timeTick = secs + (v.timeWarn * 60); commandMark(v.socket.socket, ".-"); end; if (v.group == "" and v.c) then -- an empty connection - must fix this, this shouldn't happen. tinsert(toDelete, {v.socket.socket, i}); end; end; if (getn(toDelete) > 0) then for i, v in toDelete do if (i ~= "n") then close(v[1]); colloquy.connections[v[2]] = nil; end end buildSocketReaders(); end if (colloquy.logRotate and date("%H%M") == "0000" and lastLogRotation ~= date("%Y%m%d")) then log("Rotating log file"); lastLogRotation = date("%Y%m%d"); closefile(colloquy.logfile); execute(date(colloquy.logRotate)); remove(colloquy.logName) colloquy.logfile = openfile(colloquy.logName, "a") log("Rotated log file"); if (not colloquy.kickIdle) then -- save all the users to disc. local i, v; for i, v in colloquy.connections do if (type(v) == "table") then saveUser(i); end; end; saveUsers(colloquy.users); log("Saved connected users to disc"); local oldUsage = gcinfo(); collectgarbage(); log("Collected " .. tostring(oldUsage - gcinfo()) .. "kB of garbage"); end; -- now expire lists that havn't been used in 28 days... expireLists(); end; if date("%H%M") == "0000" and lastNewDate ~= date("%Y%m%d") then -- now tell everybody about the date change sendToAll(format("Date changed to %s.", date("%A %b %d %Y")), S_DONE); lastNewDate = date("%Y%m%d"); end; end; function selectery() local sockets, readyRead, readySend, error; getSecs(1); if not colloquy.readingSockets then buildSocketReaders() end; readyRead, readyWrite, error = select(colloquy.readingSockets, clientSocket.writers, 2); secs = getSecs(); if (error ~= "timeout") then handleWrites(readyWrite); handleReads(readyRead); end; if (secs - lastHousekeep >= 2) then doHousekeeping(); lastHousekeep = secs; end; end; function empty(t) local i, v, k; k = 0; for i, v in t do k = k + 1; end; return k == 0; end;