--
--  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 = {}

		function Modem.exists()
			Modem.exists = false
			for _, side in pairs(rs.getSides()) do
				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 <new-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 <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 <new-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 <on or off>"}
helpDocs["startup"] = {"Runs the server for the current domain", "on startup"}
helpDocs["repeat"] = {"Whether to repeat rednet messages", "Usage: repeat <on or off>"}
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 <command>\" 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().." <domain>")
end
