-- -- Firewolf Server -- Made by 1lann and GravityScore -- -- Networking API -- RC4 -- RC4 -- Implementation by AgentE382 local cryptWrapper = function(plaintext, salt) local key = type(salt) == "table" and {unpack(salt)} or {string.byte(salt, 1, #salt)} local S = {} for i = 0, 255 do S[i] = i end local j, keylength = 0, #key for i = 0, 255 do j = (j + S[i] + key[i % keylength + 1]) % 256 S[i], S[j] = S[j], S[i] end local i = 0 j = 0 local chars, astable = type(plaintext) == "table" and {unpack(plaintext)} or {string.byte(plaintext, 1, #plaintext)}, false for n = 1, #chars do i = (i + 1) % 256 j = (j + S[i]) % 256 S[i], S[j] = S[j], S[i] chars[n] = bit.bxor(S[(S[i] + S[j]) % 256], chars[n]) if chars[n] > 127 or chars[n] == 13 then astable = true end end return astable and chars or string.char(unpack(chars)) end local crypt = function(text, key) local resp, msg = pcall(cryptWrapper, text, key) if resp then return msg else return nil end end -- Base64 -- -- Base64 Encryption/Decryption -- By KillaVanilla -- http://www.computercraft.info/forums2/index.php?/topic/12450-killavanillas-various-apis/ -- http://pastebin.com/rCYDnCxn -- local alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" local function sixBitToBase64(input) return string.sub(alphabet, input+1, input+1) end local function base64ToSixBit(input) for i=1, 64 do if input == string.sub(alphabet, i, i) then return i-1 end end end local function octetToBase64(o1, o2, o3) local shifted = bit.brshift(bit.band(o1, 0xFC), 2) local i1 = sixBitToBase64(shifted) local i2 = "A" local i3 = "=" local i4 = "=" if o2 then i2 = sixBitToBase64(bit.bor( bit.blshift(bit.band(o1, 3), 4), bit.brshift(bit.band(o2, 0xF0), 4) )) if not o3 then i3 = sixBitToBase64(bit.blshift(bit.band(o2, 0x0F), 2)) else i3 = sixBitToBase64(bit.bor( bit.blshift(bit.band(o2, 0x0F), 2), bit.brshift(bit.band(o3, 0xC0), 6) )) end else i2 = sixBitToBase64(bit.blshift(bit.band(o1, 3), 4)) end if o3 then i4 = sixBitToBase64(bit.band(o3, 0x3F)) end return i1..i2..i3..i4 end local function base64ToThreeOctet(s1) local c1 = base64ToSixBit(string.sub(s1, 1, 1)) local c2 = base64ToSixBit(string.sub(s1, 2, 2)) local c3 = 0 local c4 = 0 local o1 = 0 local o2 = 0 local o3 = 0 if string.sub(s1, 3, 3) == "=" then c3 = nil c4 = nil elseif string.sub(s1, 4, 4) == "=" then c3 = base64ToSixBit(string.sub(s1, 3, 3)) c4 = nil else c3 = base64ToSixBit(string.sub(s1, 3, 3)) c4 = base64ToSixBit(string.sub(s1, 4, 4)) end o1 = bit.bor( bit.blshift(c1, 2), bit.brshift(bit.band( c2, 0x30 ), 4) ) if c3 then o2 = bit.bor( bit.blshift(bit.band(c2, 0x0F), 4), bit.brshift(bit.band( c3, 0x3C ), 2) ) else o2 = nil end if c4 then o3 = bit.bor( bit.blshift(bit.band(c3, 3), 6), c4 ) else o3 = nil end return o1, o2, o3 end local function splitIntoBlocks(bytes) local blockNum = 1 local blocks = {} for i=1, #bytes, 3 do blocks[blockNum] = {bytes[i], bytes[i+1], bytes[i+2]} blockNum = blockNum+1 end return blocks end function base64Encode(bytes) local blocks = splitIntoBlocks(bytes) local output = "" for i=1, #blocks do output = output..octetToBase64( unpack(blocks[i]) ) end return output end function base64Decode(str) local bytes = {} local blocks = {} local blockNum = 1 for i=1, #str, 4 do blocks[blockNum] = string.sub(str, i, i+3) blockNum = blockNum+1 end for i=1, #blocks do local o1, o2, o3 = base64ToThreeOctet(blocks[i]) table.insert(bytes, o1) table.insert(bytes, o2) table.insert(bytes, o3) end return bytes end -- SHA-256 -- -- Adaptation of the Secure Hashing Algorithm (SHA-244/256) -- Found Here: http://lua-users.org/wiki/SecureHashAlgorithm -- -- Using an adapted version of the bit library -- Found Here: https://bitbucket.org/Boolsheet/bslf/src/1ee664885805/bit.lua -- local MOD = 2^32 local MODM = MOD-1 local function memoize(f) local mt = {} local t = setmetatable({}, mt) function mt:__index(k) local v = f(k) t[k] = v return v end return t end local function make_bitop_uncached(t, m) local function bitop(a, b) local res,p = 0,1 while a ~= 0 and b ~= 0 do local am, bm = a % m, b % m res = res + t[am][bm] * p a = (a - am) / m b = (b - bm) / m p = p * m end res = res + (a + b) * p return res end return bitop end local function make_bitop(t) local op1 = make_bitop_uncached(t,2^1) local op2 = memoize(function(a) return memoize(function(b) return op1(a, b) end) end) return make_bitop_uncached(op2, 2 ^ (t.n or 1)) end local customBxor1 = make_bitop({[0] = {[0] = 0,[1] = 1}, [1] = {[0] = 1, [1] = 0}, n = 4}) local function customBxor(a, b, c, ...) local z = nil if b then a = a % MOD b = b % MOD z = customBxor1(a, b) if c then z = customBxor(z, c, ...) end return z elseif a then return a % MOD else return 0 end end local function customBand(a, b, c, ...) local z if b then a = a % MOD b = b % MOD z = ((a + b) - customBxor1(a,b)) / 2 if c then z = customBand(z, c, ...) end return z elseif a then return a % MOD else return MODM end end local function bnot(x) return (-1 - x) % MOD end local function rshift1(a, disp) if disp < 0 then return lshift(a, -disp) end return math.floor(a % 2 ^ 32 / 2 ^ disp) end local function rshift(x, disp) if disp > 31 or disp < -31 then return 0 end return rshift1(x % MOD, disp) end local function lshift(a, disp) if disp < 0 then return rshift(a, -disp) end return (a * 2 ^ disp) % 2 ^ 32 end local function rrotate(x, disp) x = x % MOD disp = disp % 32 local low = customBand(x, 2 ^ disp - 1) return rshift(x, disp) + lshift(low, 32 - disp) end local k = { 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, } local function str2hexa(s) return (string.gsub(s, ".", function(c) return string.format("%02x", string.byte(c)) end)) end local function num2s(l, n) local s = "" for i = 1, n do local rem = l % 256 s = string.char(rem) .. s l = (l - rem) / 256 end return s end local function s232num(s, i) local n = 0 for i = i, i + 3 do n = n*256 + string.byte(s, i) end return n end local function preproc(msg, len) local extra = 64 - ((len + 9) % 64) len = num2s(8 * len, 8) msg = msg .. "\128" .. string.rep("\0", extra) .. len assert(#msg % 64 == 0) return msg end local function initH256(H) H[1] = 0x6a09e667 H[2] = 0xbb67ae85 H[3] = 0x3c6ef372 H[4] = 0xa54ff53a H[5] = 0x510e527f H[6] = 0x9b05688c H[7] = 0x1f83d9ab H[8] = 0x5be0cd19 return H end local function digestblock(msg, i, H) local w = {} for j = 1, 16 do w[j] = s232num(msg, i + (j - 1)*4) end for j = 17, 64 do local v = w[j - 15] local s0 = customBxor(rrotate(v, 7), rrotate(v, 18), rshift(v, 3)) v = w[j - 2] w[j] = w[j - 16] + s0 + w[j - 7] + customBxor(rrotate(v, 17), rrotate(v, 19), rshift(v, 10)) end local a, b, c, d, e, f, g, h = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] for i = 1, 64 do local s0 = customBxor(rrotate(a, 2), rrotate(a, 13), rrotate(a, 22)) local maj = customBxor(customBand(a, b), customBand(a, c), customBand(b, c)) local t2 = s0 + maj local s1 = customBxor(rrotate(e, 6), rrotate(e, 11), rrotate(e, 25)) local ch = customBxor (customBand(e, f), customBand(bnot(e), g)) local t1 = h + s1 + ch + k[i] + w[i] h, g, f, e, d, c, b, a = g, f, e, d + t1, c, b, a, t1 + t2 end H[1] = customBand(H[1] + a) H[2] = customBand(H[2] + b) H[3] = customBand(H[3] + c) H[4] = customBand(H[4] + d) H[5] = customBand(H[5] + e) H[6] = customBand(H[6] + f) H[7] = customBand(H[7] + g) H[8] = customBand(H[8] + h) end local function sha256(msg) msg = preproc(msg, #msg) local H = initH256({}) for i = 1, #msg, 64 do digestblock(msg, i, H) end return str2hexa(num2s(H[1], 4) .. num2s(H[2], 4) .. num2s(H[3], 4) .. num2s(H[4], 4) .. num2s(H[5], 4) .. num2s(H[6], 4) .. num2s(H[7], 4) .. num2s(H[8], 4)) end local protocolName = "Firewolf" -- Cryptography local Cryptography = {} Cryptography.sha = {} Cryptography.base64 = {} Cryptography.aes = {} function Cryptography.bytesFromMessage(msg) local bytes = {} for i = 1, msg:len() do local letter = string.byte(msg:sub(i, i)) table.insert(bytes, letter) end return bytes end function Cryptography.messageFromBytes(bytes) local msg = "" for i = 1, #bytes do local letter = string.char(bytes[i]) msg = msg .. letter end return msg end function Cryptography.bytesFromKey(key) local bytes = {} for i = 1, key:len() / 2 do local group = key:sub((i - 1) * 2 + 1, (i - 1) * 2 + 1) local num = tonumber(group, 16) table.insert(bytes, num) end return bytes end function Cryptography.sha.sha256(msg) return sha256(msg) end function Cryptography.aes.encrypt(msg, key) return base64Encode(crypt(msg, key)) end function Cryptography.aes.decrypt(msg, key) return crypt(base64Decode(msg), key) end function Cryptography.base64.encode(msg) return base64Encode(Cryptography.bytesFromMessage(msg)) end function Cryptography.base64.decode(msg) return Cryptography.messageFromBytes(base64Decode(msg)) end function Cryptography.channel(text) local hashed = Cryptography.sha.sha256(text) local total = 0 for i = 1, hashed:len() do total = total + string.byte(hashed:sub(i, i)) end return (total % 55530) + 10000 end function Cryptography.sanatize(text) local sanatizeChars = {"%", "(", ")", "[", "]", ".", "+", "-", "*", "?", "^", "$"} for _, char in pairs(sanatizeChars) do text = text:gsub("%"..char, "%%%"..char) end return text end -- Modem local Modem = {} Modem.modems = {} local side = {"back"} function Modem.exists() Modem.exists = false -- for _, side in pairs(rs.getSides()) do -- All-sides invalid when also hooked to OC relays for _, side in pairs(side) do print(side," scan") if peripheral.isPresent(side) and peripheral.getType(side) == "modem" then Modem.exists = true if not Modem.modems[side] then Modem.modems[side] = peripheral.wrap(side) end end end return Modem.exists end function Modem.open(channel) if not Modem.exists then return false end for side, modem in pairs(Modem.modems) do modem.open(channel) rednet.open(side) end return true end function Modem.close(channel) if not Modem.exists then return false end for side, modem in pairs(Modem.modems) do modem.close(channel) end return true end function Modem.closeAll() if not Modem.exists then return false end for side, modem in pairs(Modem.modems) do modem.closeAll() end return true end function Modem.isOpen(channel) if not Modem.exists then return false end local isOpen = false for side, modem in pairs(Modem.modems) do if modem.isOpen(channel) then isOpen = true break end end return isOpen end function Modem.transmit(channel, msg) if not Modem.exists then return false end if not Modem.isOpen(channel) then Modem.open(channel) end for side, modem in pairs(Modem.modems) do modem.transmit(channel, channel, msg) end return true end -- Handshake local Handshake = {} Handshake.prime = 625210769 Handshake.channel = 54569 Handshake.base = -1 Handshake.secret = -1 Handshake.sharedSecret = -1 Handshake.packetHeader = "["..protocolName.."-Handshake-Packet-Header]" Handshake.packetMatch = "%["..protocolName.."%-Handshake%-Packet%-Header%](.+)" function Handshake.exponentWithModulo(base, exponent, modulo) local remainder = base for i = 1, exponent-1 do remainder = remainder * remainder if remainder >= modulo then remainder = remainder % modulo end end return remainder end function Handshake.clear() Handshake.base = -1 Handshake.secret = -1 Handshake.sharedSecret = -1 end function Handshake.generateInitiatorData() Handshake.base = math.random(10,99999) Handshake.secret = math.random(10,99999) return { type = "initiate", prime = Handshake.prime, base = Handshake.base, moddedSecret = Handshake.exponentWithModulo(Handshake.base, Handshake.secret, Handshake.prime) } end function Handshake.generateResponseData(initiatorData) local isPrimeANumber = type(initiatorData.prime) == "number" local isPrimeMatching = initiatorData.prime == Handshake.prime local isBaseANumber = type(initiatorData.base) == "number" local isInitiator = initiatorData.type == "initiate" local isModdedSecretANumber = type(initiatorData.moddedSecret) == "number" local areAllNumbersNumbers = isPrimeANumber and isBaseANumber and isModdedSecretANumber if areAllNumbersNumbers and isPrimeMatching then if isInitiator then Handshake.base = initiatorData.base Handshake.secret = math.random(10,99999) Handshake.sharedSecret = Handshake.exponentWithModulo(initiatorData.moddedSecret, Handshake.secret, Handshake.prime) return { type = "response", prime = Handshake.prime, base = Handshake.base, moddedSecret = Handshake.exponentWithModulo(Handshake.base, Handshake.secret, Handshake.prime) }, Handshake.sharedSecret elseif initiatorData.type == "response" and Handshake.base > 0 and Handshake.secret > 0 then Handshake.sharedSecret = Handshake.exponentWithModulo(initiatorData.moddedSecret, Handshake.secret, Handshake.prime) return Handshake.sharedSecret else return false end else return false end end -- Secure Connection local SecureConnection = {} SecureConnection.__index = SecureConnection SecureConnection.packetHeaderA = "["..protocolName.."-" SecureConnection.packetHeaderB = "-SecureConnection-Packet-Header]" SecureConnection.packetMatchA = "%["..protocolName.."%-" SecureConnection.packetMatchB = "%-SecureConnection%-Packet%-Header%](.+)" SecureConnection.connectionTimeout = 0.1 SecureConnection.successPacketTimeout = 0.1 function SecureConnection.new(secret, key, identifier, distance, isRednet) local self = setmetatable({}, SecureConnection) self:setup(secret, key, identifier, distance, isRednet) return self end function SecureConnection:setup(secret, key, identifier, distance, isRednet) local rawSecret if isRednet then self.isRednet = true self.distance = -1 self.rednet_id = distance rawSecret = protocolName .. "|" .. tostring(secret) .. "|" .. tostring(identifier) .. "|" .. tostring(key) .. "|rednet" else self.isRednet = false self.distance = distance rawSecret = protocolName .. "|" .. tostring(secret) .. "|" .. tostring(identifier) .. "|" .. tostring(key) .. "|" .. tostring(distance) end self.identifier = identifier self.packetMatch = SecureConnection.packetMatchA .. Cryptography.sanatize(identifier) .. SecureConnection.packetMatchB self.packetHeader = SecureConnection.packetHeaderA .. identifier .. SecureConnection.packetHeaderB self.secret = Cryptography.sha.sha256(rawSecret) self.channel = Cryptography.channel(self.secret) if not self.isRednet then Modem.open(self.channel) end end function SecureConnection:verifyHeader(msg) if type(msg) ~= "string" then return false end if msg:match(self.packetMatch) then return true else return false end end function SecureConnection:sendMessage(msg, rednetProtocol) local rawEncryptedMsg = Cryptography.aes.encrypt(self.packetHeader .. msg, self.secret) local encryptedMsg = self.packetHeader .. rawEncryptedMsg if self.isRednet then rednet.send(self.rednet_id, encryptedMsg, rednetProtocol) return true else return Modem.transmit(self.channel, encryptedMsg) end end function SecureConnection:decryptMessage(msg) if self:verifyHeader(msg) then local encrypted = msg:match(self.packetMatch) local unencryptedMsg = nil pcall(function() unencryptedMsg = Cryptography.aes.decrypt(encrypted, self.secret) end) if not unencryptedMsg then return false, "Could not decrypt" end if self:verifyHeader(unencryptedMsg) then return true, unencryptedMsg:match(self.packetMatch) else return false, "Could not verify" end else return false, "Could not stage 1 verify" end end -- END OF NETWORKING dnsListenChannel = 9999 dnsResponseChannel = 9998 local config = {} config.visibleLoggingLevel = 1 config.writtenLoggingLevel = 1 config.loggingLocation = "/fwserver-logs" config.enableLogging = false config.password = null config.allowRednetConnections = true config.repeatRednetMessages = false config.lastDomain = nil --config.actAsGPS = false --config.gpsLocation = {} local configLocation = "/.fwserver-config" local serverLocation = "/fw_servers" local serverAPILocation = "server_api" local locked = false local domain = ... local responseStack = {} local repeatStack = {} local terminalLog = {} local repeatedMessages = {} local lastLogNum = 0 local servedRequests = 0 local serverChannel = 0 local requestMatch = "" local maliciousMatch = "" local responseHeader = "" local pageRequestMatch = "" local pageResposneHeader = "" local renableRednet = nil local handles = {} local customCoroutines = {} local globalHandler = nil local closeMatch = "" local connections = {} local updateURL = "https://raw.githubusercontent.com/1lann/Firewolf/master/src/server.lua" local version = "3.5.3" local header = {} header.dnsPacket = "[Firewolf-DNS-Packet]" header.dnsHeader = "[Firewolf-DNS-Response]" header.dnsHeaderMatch = "^%[Firewolf%-DNS%-Response%](.+)$" header.rednetHeader = "[Firewolf-Rednet-Channel-Simulation]" header.rednetMatch = "^%[Firewolf%-Rednet%-Channel%-Simulation%](%d+)$" header.requestMatchA = "^%[Firewolf%-" header.requestMatchB = "%-Handshake%-Request%](.+)$" header.maliciousMatchA = "^%[Firewolf%-" header.maliciousMatchB = "%-.+%-Handshake%-Response%](.+)$" header.responseHeaderA = "[Firewolf-" header.responseHeaderB = "-" header.responseHeaderC = "-Handshake-Response]" header.pageRequestMatchA = "^%[Firewolf%-" header.pageRequestMatchB = "%-Page%-Request%](.+)$" header.pageResponseHeaderA = "[Firewolf-" header.pageResponseHeaderB = "-Page-Response][HEADER]" header.pageResponseHeaderC = "[BODY]" header.closeMatchA = "[Firewolf-" header.closeMatchB = "-Connection-Close]" local resetServerEvent = "firewolf-server-reset-event" local theme = {} theme.error = colors.red theme.background = colors.gray theme.text = colors.white theme.notice = colors.white theme.barColor = colors.red theme.barText = colors.white theme.inputColor = colors.white theme.clock = colors.white theme.lock = colors.orange theme.userResponse = colors.orange if not term.isColor() then theme.error = colors.white theme.background = colors.black theme.text = colors.white theme.notice = colors.white theme.barColor = colors.white theme.barText = colors.black theme.inputColor = colors.white theme.clock = colors.white theme.lock = colors.white theme.userResponse = colors.white end local w, h = term.getSize() -- Utilities local makeDirectory = function(path) fs.makeDir(path) local function createIndex(path) if not (fs.exists(path.."/index") or fs.exists(path.."/index.fwml")) then f = io.open(path.."/index", "w") f:write("print('')\ncenter('Welcome to "..domain.."!')") f:close() end end pcall(function() createIndex(path) end) end local checkDomain = function(domain) if domain:find("/") or domain:find(":") or domain:find("%?") then return "symbols" end if #domain < 4 then return "short" else Modem.open(dnsListenChannel) Modem.open(dnsResponseChannel) Modem.transmit(dnsListenChannel, header.dnsPacket) Modem.close(dnsListenChannel) rednet.broadcast(header.dnsPacket, header.rednetHeader .. dnsListenChannel) local timer = os.startTimer(2) while true do local event, id, channel, protocol, message, dist = os.pullEventRaw() if event == "modem_message" and channel == dnsResponseChannel and type(message) == string and message:match(header.dnsHeaderMatch) == domain then return "taken" elseif event == "rednet_message" and protocol and tonumber(protocol:match(header.rednetMatch)) == dnsResponseChannel and channel:match(header.dnsHeaderMatch) == domain then return "taken" elseif event == "timer" and id == timer then break end end end return "ok" end local checkConfig = function() local errorReport = {} if not config then table.insert(errorReport, "Corrupted configuration file!") return errorReport end if config.enableLogging then if fs.isReadOnly(config.loggingLocation) or fs.isDir(config.loggingLocation) then table.insert(errorReport, "Invalid logging location!") end end if #errorReport > 0 then return errorReport else return false end end local loadConfig = function() if fs.exists(configLocation) and not fs.isDir(configLocation) then local f = io.open(configLocation, "r") config = textutils.unserialize(f:read("*a")) if config then if type(config.actAsRednetRepeater) == "boolean" then config.actAsRednetRepeater = nil end end f:close() else config = nil end end local saveConfig = function() local f = io.open(configLocation, "w") f:write(textutils.serialize(config)) f:close() end local center = function(text) local x, y = term.getCursorPos() term.setCursorPos(math.floor(w / 2 - text:len() / 2) + (text:len() % 2 == 0 and 1 or 0), y) term.write(text) term.setCursorPos(1, y + 1) end local writeLog = function(text, color, level) if not level then level = 0 end if not color then color = theme.text end if level >= config.visibleLoggingLevel then local time = textutils.formatTime(os.time(), true) if #time <= 4 then time = "0"..time end table.insert(terminalLog, {text, color, "["..time.."] "}) end if config.enableLogging and level >= config.writtenLoggingLevel then if fs.isDir(config.loggingLocation) then fs.delete(config.loggingLocation) end if not fs.exists(config.loggingLocation) then local f = io.open(config.loggingLocation, "w") f:write("\n") f:close() end local time = textutils.formatTime(os.time(), true) if #time <= 4 then time = "0"..time end local f = io.open(config.loggingLocation, "a") f:write("["..time.."] "..text.."\n") f:close() end end local writeError = function(text) for i = 1, math.ceil(#text / (w - 8)) do writeLog(text:sub((i - 1) * (w - 8), i * (w - 8)), theme.error, math.huge) end end -- Message handling local receiveDaemon = function() while true do local event, id, channel, protocol, message, dist = os.pullEventRaw() if event == "modem_message" then if channel == rednet.CHANNEL_REPEAT and config.repeatRednetMessages and type(message) == "table" and message.sProtocol and message.sProtocol:match(header.rednetMatch) then table.insert(repeatStack, {message = message, reply = protocol}) elseif channel ~= rednet.CHANNEL_BROADCAST then table.insert(responseStack, {type = "direct", channel = channel, message = message, dist = dist, reply = protocol}) end elseif event == "rednet_message" and protocol and protocol:match(header.rednetMatch) and config.allowRednetConnections then table.insert(responseStack, {type = "rednet", channel = tonumber(protocol:match(header.rednetMatch)), rednet_id = id, message = channel, dist = null}) elseif event == "timer" and id == renableRednet then Modem.open(rednet.CHANNEL_REPEAT) end end end local getActiveConnection = function(channel, distance) for k, connection in pairs(connections) do if connection.channel == channel then if (not distance) then if connection.isRednet then return connection else return false end else if connection.distance == distance then return connection else return false end end end end end local urlEncode = function(url) local result = url result = result:gsub("%%", "%%a") result = result:gsub(":", "%%c") result = result:gsub("/", "%%s") result = result:gsub("\n", "%%n") result = result:gsub(" ", "%%w") result = result:gsub("&", "%%m") result = result:gsub("%?", "%%q") result = result:gsub("=", "%%e") result = result:gsub("%.", "%%d") return result end local urlDecode = function(url) local result = url result = result:gsub("%%c", ":") result = result:gsub("%%s", "/") result = result:gsub("%%n", "\n") result = result:gsub("%%w", " ") result = result:gsub("%%&", "&") result = result:gsub("%%q", "%?") result = result:gsub("%%e", "=") result = result:gsub("%%d", "%.") result = result:gsub("%%m", "%%") return result end local getURLVars = function(url) local vars = {} if url then local firstVarIndex, firstVarVal = url:match("%?([^=]+)=([^&]+)") if not firstVarIndex then return else vars[urlDecode(firstVarIndex)] = urlDecode(firstVarVal) for index, val in url:gmatch("&([^=]+)=([^&]+)") do vars[urlDecode(index)] = urlDecode(val) end return vars end else return end end local fetchPage = function(page) local pageRequest = fs.combine("", page:match("^[^%?]+")) local varRequest = page:match("%?[^%?]+$") if (pageRequest:match("(.+)%.fwml$")) then pageRequest = pageRequest:match("(.+)%.fwml$") end pageRequest = pageRequest:gsub("%.%.", "") local handleResponse, respHeader if globalHandler then err, msg = pcall(function() handleResponse, respHeader = globalHandler(page, getURLVars(varRequest)) end) if not err then writeLog("Error when executing server API function", theme.error, math.huge) writeError("/: " .. tostring(msg)) end end if handleResponse then if not respHeader then respHeader = "lua" end return handleResponse, respHeader, true end if pageRequest == serverAPILocation then -- Forbid accessing server api files return nil end for k,v in pairs(handles) do local startSearch, endSearch = pageRequest:find(k) if startSearch == 1 and ((endSearch == #pageRequest) or (pageRequest:sub(endSearch + 1, endSearch + 1) == "/")) then err, msg = pcall(function() handleResponse, respHeader = v(page, getURLVars(varRequest)) end) if not err then writeLog("Error when executing server API function", theme.error, math.huge) writeError(k .. ": " .. tostring(msg)) end end end if handleResponse then if not respHeader then respHeader = "lua" end return handleResponse, respHeader, true end local path = serverLocation .. "/" .. domain .. "/" .. pageRequest if fs.exists(path) and not fs.isDir(path) then local f = io.open(path, "r") local contents = f:read("*a") f:close() return contents, "lua", false else if fs.exists(path..".fwml") and not fs.isDir(path..".fwml") then local f = io.open(path..".fwml", "r") local contents = f:read("*a") f:close() return contents, "fwml", false end end return nil end local sendPage = function(connection, body, head) if head == "fwml" then connection:sendMessage(pageResposneHeader..'{["language"]="Firewolf Markup"}'..header.pageResponseHeaderC..body, header.rednetHeader .. connection.channel) else connection:sendMessage(pageResposneHeader..'{["language"]="Lua"}'..header.pageResponseHeaderC..body, header.rednetHeader .. connection.channel) end end local defaultNotFound = [[ [br] [br] [=] The page you requested could not be found! [br] Make sure you typed the URL correctly. ]] local handlePageRequest = function(handler, index) local connection = getActiveConnection(handler.channel, handler.dist) if connection:verifyHeader(handler.message) then local resp, data = connection:decryptMessage(handler.message) if not resp then -- Decryption Error writeLog("Decryption error!", theme.notice, 0) else -- Process Request if data:match(pageRequestMatch) then local page = data:match(pageRequestMatch) if page == "" or page == "/" then page = "index" end local body, head, isAPI = fetchPage(page) if body then sendPage(connection, tostring(body), head) if isAPI then writeLog("API request: "..page:sub(1,25), theme.text, 0) else writeLog("Successful request: "..page:sub(1,20), theme.text, 1) end else body, head, isAPI = fetchPage("not-found") if body then sendPage(connection, body, head) else sendPage(connection, defaultNotFound, "fwml") end writeLog("Unsuccessful request: "..page:sub(1,20), theme.text, 1) end elseif data == closeMatch then writeLog("Secure connection closed", theme.text, 0) Modem.close(connection.channel) table.remove(connections, index) end end end end local handleHandshakeRequest = function(handler) local requestData = handler.message:match(requestMatch) if requestData and type(textutils.unserialize(requestData)) == "table" then local receivedHandshake = textutils.unserialize(requestData) local data, key = Handshake.generateResponseData(receivedHandshake) if type(data) == "table" then local connection if handler.type == "direct" then connection = SecureConnection.new(key, domain, domain, handler.dist) Modem.transmit(serverChannel, responseHeader .. tostring(handler.dist) .. header.responseHeaderC .. textutils.serialize(data)) else connection = SecureConnection.new(key, domain, domain, handler.rednet_id, true) rednet.send(handler.rednet_id, responseHeader .. tostring(handler.rednet_id) .. header.responseHeaderC .. textutils.serialize(data), header.rednetHeader .. serverChannel) end writeLog("Secure connection opened", theme.text, 0) table.insert(connections, connection) if #connections >= 200 then Modem.close(connections[1].channel) table.remove(connections, 1) end end elseif handler.message:match(maliciousMatch) then -- Hijacking Detected writeLog("Warning: Connection Hijacking Detected", theme.error, 2) end end local responseDaemon = function() while true do os.pullEventRaw() for k, v in pairs(responseStack) do if v.channel then if v.channel == dnsListenChannel and v.message == header.dnsPacket then -- DNS Request if v.type == "rednet" then rednet.send(v.rednet_id, header.dnsHeader .. domain, header.rednetHeader .. dnsResponseChannel) else Modem.open(dnsResponseChannel) Modem.transmit(dnsResponseChannel, header.dnsHeader .. domain) Modem.close(dnsResponseChannel) end elseif v.channel == serverChannel and v.message then handleHandshakeRequest(v) elseif getActiveConnection(v.channel, v.dist) then handlePageRequest(v, k) end end end responseStack = {} if #repeatStack > 10 then Modem.close(rednet.CHANNEL_REPEAT) renableRednet = os.startTimer(2) repeatStack = {} writeLog("Thorttling Rednet Connections", theme.notice, 2) end for k, v in pairs(repeatStack) do if v.message.nMessageID and v.message.nRecipient then if (not repeatedMessages[v.message.nMessageID]) or (os.clock() - repeatedMessages[v.message.nMessageID]) > 10 then repeatedMessages[v.message.nMessageID] = os.clock() for side, modem in pairs(Modem.modems) do modem.transmit(rednet.CHANNEL_REPEAT, v.reply, v.message) modem.transmit(v.message.nRecipient, v.reply, v.message) end end end end repeatStack = {} end end -- Commands and Help local commands = {} local helpDocs = {} commands["password"] = function(newPassword) if not newPassword or newPassword == "" then writeLog("Usage: password ", theme.userResponse, math.huge) else config.password = newPassword saveConfig() writeLog("New password set!", theme.userResponse, math.huge) end end commands["lock"] = function() if config.password then locked = true writeLog("Server locked", theme.userResponse, math.huge) os.queueEvent("firewolf-lock-state-update") else writeLog("No password has been set! Set a", theme.userResponse, math.huge) writeLog("password with: password ", theme.userResponse, math.huge) end end commands["exit"] = function() error("firewolf-exit") end commands["stop"] = commands["exit"] commands["quit"] = commands["exit"] commands["startup"] = function() if fs.exists("/startup") then if fs.exists("/old-startup") then fs.delete("/old-startup") end fs.move("/startup", "/old-startup") end local f = io.open("/startup", "w") f:write([[ sleep(0.1) shell.run("]]..shell.getRunningProgram().." "..domain.."\")") f:close() writeLog("Server will now run on startup!", theme.userResponse, math.huge) end commands["rednet"] = function(set) if set == "on" then config.allowRednetConnections = true saveConfig() writeLog("Now allowing rednet connections", theme.userResponse, math.huge) elseif set == "off" then config.allowRednetConnections = false saveConfig() writeLog("Now dis-allowing rednet connections", theme.userResponse, math.huge) else if config.allowRednetConnections then writeLog("Rednet conn. are currently allowed", theme.userResponse, math.huge) else writeLog("Rednet conn. are currently dis-allowed", theme.userResponse, math.huge) end end end commands["restart"] = function() error("firewolf-restart") end commands["reboot"] = commands["restart"] commands["reload"] = function() os.queueEvent(resetServerEvent) end commands["refresh"] = commands["reload"] commands["repeat"] = function(set) if set == "on" then config.repeatRednetMessages = true saveConfig() writeLog("Rednet repeating is now on", theme.userResponse, math.huge) elseif set == "off" then config.repeatRednetMessages = false saveConfig() writeLog("Rednet repeating is now off", theme.userResponse, math.huge) else if config.repeatRednetMessages then writeLog("Rednet repeating is currently turned on", theme.userResponse, math.huge) else writeLog("Rednet repeating is currently turned off", theme.userResponse, math.huge) end end end commands["update"] = function() term.setCursorPos(1, h) term.clearLine() term.setTextColor(theme.userResponse) term.setCursorBlink(false) term.write("Updating...") local handle = http.get(updateURL) if not handle then writeLog("Failed to connect to update server!", theme.error, math.huge) else data = handle.readAll() if #data < 1000 then writeLog("Failed to update server!", theme.error, math.huge) else local f = io.open("/"..shell.getRunningProgram(), "w") f:write(data) f:close() error("firewolf-restart") end end end commands["edit"] = function() writeLog("Editing server files", theme.userResponse, math.huge) term.setBackgroundColor(colors.black) if term.isColor() then term.setTextColor(colors.yellow) else term.setTextColor(colors.white) end term.clear() term.setCursorPos(1, 1) print("Use exit to finish editing") shell.setDir(serverLocation .. "/" .. domain) shell.run("/rom/programs/shell") os.queueEvent(resetServerEvent) end commands["clear"] = function() terminalLog = {} term.clear() os.queueEvent("firewolf-lock-state-update") end helpDocs["password"] = {"Change the lock password", "Usage: password "} helpDocs["lock"] = {"Lock the server with a password"} helpDocs["exit"] = {"Exits and stops Firewolf Server"} helpDocs["quit"] = helpDocs["exit"] helpDocs["stop"] = helpDocs["exit"] helpDocs["restart"] = {"Fully restarts Firewolf Server"} helpDocs["reboot"] = helpDocs["restart"] helpDocs["reload"] = {"Reloads the server and Server API"} helpDocs["refresh"] = helpDocs["reload"] helpDocs["clear"] = {"Clears the displayed log"} helpDocs["rednet"] = {"Whether to allow rednet connections", "Usage: rednet "} helpDocs["startup"] = {"Runs the server for the current domain", "on startup"} helpDocs["repeat"] = {"Whether to repeat rednet messages", "Usage: repeat "} helpDocs["update"] = {"Updates Firewolf Server"} helpDocs["edit"] = {"Opens shell in server directory"} commands["help"] = function(command) if command then if helpDocs[command] then for _, v in pairs(helpDocs[command]) do writeLog(v, theme.userResponse, math.huge) end else writeLog("Command does not exist!", theme.userResponse, math.huge) end else writeLog("Use \"help \" for more info", theme.userResponse, math.huge) writeLog("Wiki: http://bit.ly/firewolf-wiki", theme.userResponse, math.huge) writeLog("Commands: password, lock, exit, update,", theme.userResponse, math.huge) writeLog("restart, clear, rednet, repeat, startup,", theme.userResponse, math.huge) writeLog("edit, reload", theme.userResponse, math.huge) end end -- Display manager local enteredText = "" local history = {} local scrollingHistory = false local cursorPosition = 1 local offsetPosition = 1 local inputName = "> " local lockTimer = 0 local lockedInputState = false local lockArt = [[ #### # # # # ######## -------- ######## ######## [LOCKED] ]] local drawBar = function() term.setTextColor(theme.barText) term.setBackgroundColor(theme.barColor) term.setCursorPos(1,1) term.clearLine() term.write(" "..version) center("["..domain.."]") local time = textutils.formatTime(os.time(), true) term.setCursorPos(w - #time, 1) term.setTextColor(theme.clock) term.write(time) end local drawLogs = function() if locked and lastLogNum >= 0 then term.setBackgroundColor(theme.background) term.clear() lastLogNum = -1 local lockX = math.ceil((w/2) - 4) local lineNum = 4 term.setTextColor(theme.lock) for line in lockArt:gmatch("[^\n]+") do term.setCursorPos(lockX, lineNum) term.write(line) lineNum = lineNum + 1 end inputName = "Password: " elseif not locked and lastLogNum ~= #terminalLog then term.setBackgroundColor(theme.background) term.clear() lastLogNum = #terminalLog lockedInputState = false if #terminalLog < h - 1 then term.setCursorPos(1, 2) for i = 1, #terminalLog do term.setTextColor(theme.clock) term.write(terminalLog[i][3]) term.setTextColor(terminalLog[i][2]) term.write(terminalLog[i][1]) term.setCursorPos(1, i + 2) end else term.setCursorPos(1, 2) for i = 1, h - 1 do term.setTextColor(theme.clock) term.write(terminalLog[#terminalLog - (h - 1) + i][3]) term.setTextColor(terminalLog[#terminalLog - (h - 1) + i][2]) term.write(terminalLog[#terminalLog - (h - 1) + i][1]) term.setCursorPos(1, i + 1) end end end end local drawInputBar = function() term.setCursorPos(1, h) term.setBackgroundColor(theme.background) term.clearLine() if lockedInputState then term.setCursorBlink(false) term.setTextColor(theme.error) term.write("Incorrect Password!") else term.setCursorBlink(true) term.setTextColor(theme.inputColor) term.write(inputName) width = w - #inputName if locked then term.write(string.rep("*", (#enteredText:sub(offsetPosition, offsetPosition + width)))) else term.write(enteredText:sub(offsetPosition, offsetPosition + width)) end end end local handleKeyEvents = function(event, key) if key == keys.backspace then if enteredText ~= "" then enteredText = enteredText:sub(1, cursorPosition - 2) .. enteredText:sub(cursorPosition, -1) cursorPosition = cursorPosition-1 if cursorPosition >= width then offsetPosition = offsetPosition - 1 end end elseif key == keys.enter and enteredText ~= "" and locked then if enteredText == config.password then writeLog("Successful login", theme.userResponse, math.huge) inputName = "> " locked = false os.queueEvent("firewolf-lock-state-update") else writeLog("Failed login attempt", theme.userResponse, math.huge) lockedInputState = true lockTimer = os.startTimer(2) os.queueEvent("firewolf-lock-state-update") end enteredText = "" cursorPosition = 1 offsetPosition = 1 elseif key == keys.enter and enteredText ~= "" and not locked then local commandWord = false local arguments = {} for word in enteredText:gmatch("%S+") do if not commandWord then commandWord = word else table.insert(arguments, word) end end if commands[commandWord] then local err, msg = pcall(commands[commandWord], unpack(arguments)) if not err and msg:find("firewolf-exit", nil, true) then error("firewolf-exit") elseif not err and msg:find("firewolf-restart", nil, true) then error("firewolf-restart") elseif not err then writeLog("An error occured when executing command", theme.error, math.huge) writeError(tostring(msg)) end else writeLog("No such command!", theme.error, math.huge) end table.insert(history, enteredText) enteredText = "" offsetPosition = 1 cursorPosition = 1 elseif key == keys.left then cursorPosition = cursorPosition - 1 if cursorPosition < 1 then cursorPosition = 1 end if cursorPosition >= width then offsetPosition = offsetPosition - 1 end elseif key == keys.right then cursorPosition = cursorPosition + 1 if cursorPosition > #enteredText + 1 then cursorPosition = #enteredText + 1 end if cursorPosition - offsetPosition >= width then offsetPosition = offsetPosition + 1 end elseif key == keys.down and #history > 0 then if type(scrollingHistory) == "number" then scrollingHistory = scrollingHistory - 1 if scrollingHistory > 0 then enteredText = history[#history - scrollingHistory + 1] cursorPosition = #enteredText + 1 else scrollingHistory = false enteredText = "" cursorPosition = 1 end end elseif key == keys.up and #history > 0 then if type(scrollingHistory) == "number" then scrollingHistory = scrollingHistory + 1 if scrollingHistory > #history then scrollingHistory = #history end enteredText = history[#history - scrollingHistory + 1] cursorPosition = #enteredText + 1 if cursorPosition > width then cursorPosition = 1 end else scrollingHistory = 1 enteredText = history[#history - scrollingHistory + 1] cursorPosition = #enteredText + 1 if cursorPosition > width then cursorPosition = 1 end end end end local terminalDaemon = function() local timer = os.startTimer(1) local lastTime = os.clock() while true do drawLogs() drawBar() drawInputBar() term.setCursorPos(cursorPosition - offsetPosition + #inputName + 1, h) local event, key = os.pullEventRaw() if event == "char" and not lockedInputState then enteredText = enteredText:sub(1, cursorPosition-1) .. key .. enteredText:sub(cursorPosition, -1) cursorPosition = cursorPosition + 1 if cursorPosition - offsetPosition >= width then offsetPosition = offsetPosition + 1 end elseif event == "key" and not lockedInputState then handleKeyEvents(event, key) elseif event == "timer" and key == timer then timer = os.startTimer(1) lastTime = os.clock() elseif event == "timer" and key == lockTimer then lockedInputState = false elseif event == "terminate" and not locked then error("firewolf-exit") end if (os.clock() - lastTime) > 1 then timer = os.startTimer(1) lastTime = os.clock() end end end -- Coroutine manager local receiveThread, responseThread, terminalThread local loadServerAPI = function() if fs.exists(serverLocation .. "/" .. domain .. "/" .. serverAPILocation) and not fs.isDir(serverLocation .. "/" .. domain .. "/" .. serverAPILocation) then local f = io.open(serverLocation .. "/" .. domain .. "/" .. serverAPILocation, "r") local apiData = f:read("*a") f:close() customCoroutines = {} local apiFunction, err = loadstring(apiData) if not apiFunction then writeLog("Error while loading server API", theme.error, math.huge) if err:match("%[string \"string\"%](.+)") then writeLog("server_api" .. err:match("%[string \"string\"%](.+)"), theme.error, math.huge) else writeLog(err, theme.error, math.huge) end else local global = getfenv(0) local env = {} for k,v in pairs(global) do env[k] = v end env["server"] = {} env["server"]["domain"] = domain env["server"]["modem"] = Modem env["server"]["handleRequest"] = function(index, func) if not(index and func and type(index) == "string" and type(func) == "function") then return error("index (string) and handler (function) expected") elseif #index:gsub("/", "") == 0 then return error("invalid index") end if index == "/" then globalHandler = func return end if index:sub(1, 1) == "/" then index = index:sub(2, -1) end if index:sub(-1, -1) == "/" then index = index:sub(1, -2) end if index:find(":") then return error("Handle index cannot contain \":\"s") end handles[index] = func end env["server"]["runCoroutine"] = function(func) local newThread = coroutine.create(func) local err, msg = coroutine.resume(newThread) if not err then return error(msg) end table.insert(customCoroutines, newThread) end env["server"]["applyTemplate"] = function(variables, template) local result if fs.exists(template) and not fs.isDir(template) then local f = io.open(template, "r") result = f:read("*a") f:close() else writeLog("Template file \"" .. template .. "\" does not exist!", theme.error, math.huge) writeLog("Template locations are relative to / (root)", theme.error, math.huge) return false end for k,v in pairs(variables) do result = result:gsub("{{"..Cryptography.sanatize(k).."}}", Cryptography.sanatize(v)) end return result end env["server"]["log"] = function(text) writeLog(text, theme.notice, math.huge) end setfenv(apiFunction, env) local err, msg = pcall(apiFunction) if not err then writeLog("Error while executing server API", theme.error, math.huge) writeError(tostring(msg)) else writeLog("Server API loaded", theme.notice, math.huge) end end end end local function resetServer() connections = {} repeatStack = {} responseStack = {} Modem.closeAll() Modem.open(serverChannel) Modem.open(dnsListenChannel) if config.repeatRednetMessages then Modem.open(rednet.CHANNEL_REPEAT) end loadServerAPI() responseThread = coroutine.create(function() responseDaemon() end) receiveThread = coroutine.create(receiveDaemon) coroutine.resume(responseThread) coroutine.resume(receiveThread) writeLog("The server has been reloaded", theme.userResponse, math.huge) os.queueEvent("firewolf-lock-state-update") end local function runThreads() while true do local shouldResetServer = false local events = {os.pullEventRaw()} err, msg = coroutine.resume(receiveThread, unpack(events)) if not err then writeLog("Internal error!", theme.error, math.huge) writeError(tostring(msg)) shouldResetServer = true end err, msg = coroutine.resume(responseThread) if not err then writeLog("Internal error!", theme.error, math.huge) writeError(tostring(msg)) shouldResetServer = true end err, msg = coroutine.resume(terminalThread, unpack(events)) if not err and msg:find("firewolf-exit", nil, true) then writeLog("Normal exit", theme.text, math.huge) Modem.closeAll() shell.setDir("") term.setBackgroundColor(colors.black) term.clear() term.setCursorPos(1, 1) term.setTextColor(colors.white) center("Thank you for using Firewolf Server") center("Made by 1lann and GravityScore") return elseif not err and msg:find("firewolf-restart", nil, true) then writeLog("Firewolf server restarting...", theme.text, math.huge) term.clear() error("firewolf-restart") elseif not err then term.clear() term.setCursorPos(1, 1) error("Restart required error: "..msg) end for k,v in pairs(customCoroutines) do if coroutine.status(v) ~= "dead" then local err, msg = coroutine.resume(v, unpack(events)) if not err then writeLog("Server API coroutine error!", theme.error, math.huge) writeError(tostring(msg)) shouldResetServer = true end end end if events[1] == resetServerEvent or shouldResetServer then resetServer() end end end local runOnDomain = function() serverChannel = Cryptography.channel(domain) requestMatch = header.requestMatchA .. Cryptography.sanatize(domain) .. header.requestMatchB responseHeader = header.responseHeaderA .. domain .. header.responseHeaderB pageRequestMatch = header.pageRequestMatchA .. Cryptography.sanatize(domain) .. header.pageRequestMatchB pageResposneHeader = header.pageResponseHeaderA .. domain .. header.pageResponseHeaderB closeMatch = header.closeMatchA .. domain .. header.closeMatchB maliciousMatch = header.maliciousMatchA .. Cryptography.sanatize(domain) .. header.maliciousMatchB Modem.open(serverChannel) Modem.open(dnsListenChannel) if config.repeatRednetMessages then Modem.open(rednet.CHANNEL_REPEAT) end writeLog("Firewolf Server "..version.." running" , theme.notice, math.huge) loadServerAPI() receiveThread = coroutine.create(receiveDaemon) responseThread = coroutine.create(responseDaemon) terminalThread = coroutine.create(terminalDaemon) coroutine.resume(receiveThread) coroutine.resume(responseThread) coroutine.resume(terminalThread) runThreads() end -- Server initialisation local init = function() Modem.closeAll() term.setBackgroundColor(theme.background) term.setTextColor(theme.notice) term.clear() term.setCursorPos(1,1) term.setCursorBlink(false) term.setTextColor(theme.text) if not fs.exists(serverLocation .. "/" .. domain) then makeDirectory(serverLocation .. "/" .. domain) else if not fs.isDir(serverLocation .. "/" .. domain) then fs.delete(serverLocation .. "/" .. domain) end makeDirectory(serverLocation .. "/" .. domain) end local report = checkConfig() if report then term.setBackgroundColor(colors.black) term.setTextColor(theme.error) term.clear() term.setCursorPos(1, 1) print("There was an error loading your config file") print("The config file is located at: "..configLocation) print("-------------------------------------------------") for k,v in pairs(report) do print(v) end return end if config.password then locked = true end local err, msg = pcall(function()runOnDomain()end) if not err and msg:find("firewolf-restart", nil, true) then term.clear() term.setCursorPos(1, 1) return shell.run("/"..shell.getRunningProgram(), domain) elseif not err then term.setBackgroundColor(colors.black) term.clear() term.setCursorPos(1, 1) term.setTextColor(colors.red) print("Sorry, Firewolf Server has crashed! Error:") print(msg) print("Firewolf Server will reboot in 3 seconds...") sleep(3) return shell.run("/"..shell.getRunningProgram(), domain) end end if pocket or turtle then term.setTextColor(theme.error) print("Sorry, Firewolf Server can") print("only be ran on computers.") return end if not Modem.exists() then term.setTextColor(theme.error) print("Error: No modems found!") return end if fs.isDir(configLocation) then fs.delete(configLocation) end if not fs.exists(configLocation) then saveConfig() end loadConfig() if not domain and config.lastDomain then domain = config.lastDomain end if domain then if term.isColor() then term.setTextColor(colors.yellow) else term.setTextColor(colors.white) end term.setCursorBlink(false) print("Initializing Firewolf Server...") local report = checkDomain(domain) term.setTextColor(theme.error) if report == "symbols" then print("Domain cannot contain \":\", \"/\" and \"?\"s") elseif report == "short" then print("Domain name too short!") elseif report == "taken" then print("Domain already taken!") else config.lastDomain = domain saveConfig() init() end else term.setTextColor(colors.white) print("Usage: "..shell.getRunningProgram().." ") end