Skip to content
Permalink
master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
--
-- 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