SHOW:
|
|
- or go back to the newest paste.
| 1 | -- | |
| 2 | -- Firewolf Server | |
| 3 | -- Made by 1lann and GravityScore | |
| 4 | -- | |
| 5 | ||
| 6 | -- Networking API | |
| 7 | -- RC4 | |
| 8 | -- RC4 | |
| 9 | -- Implementation by AgentE382 | |
| 10 | ||
| 11 | ||
| 12 | local cryptWrapper = function(plaintext, salt) | |
| 13 | local key = type(salt) == "table" and {unpack(salt)} or {string.byte(salt, 1, #salt)}
| |
| 14 | local S = {}
| |
| 15 | for i = 0, 255 do | |
| 16 | S[i] = i | |
| 17 | end | |
| 18 | ||
| 19 | local j, keylength = 0, #key | |
| 20 | for i = 0, 255 do | |
| 21 | j = (j + S[i] + key[i % keylength + 1]) % 256 | |
| 22 | S[i], S[j] = S[j], S[i] | |
| 23 | end | |
| 24 | ||
| 25 | local i = 0 | |
| 26 | j = 0 | |
| 27 | local chars, astable = type(plaintext) == "table" and {unpack(plaintext)} or {string.byte(plaintext, 1, #plaintext)}, false
| |
| 28 | ||
| 29 | for n = 1, #chars do | |
| 30 | i = (i + 1) % 256 | |
| 31 | j = (j + S[i]) % 256 | |
| 32 | S[i], S[j] = S[j], S[i] | |
| 33 | chars[n] = bit.bxor(S[(S[i] + S[j]) % 256], chars[n]) | |
| 34 | if chars[n] > 127 or chars[n] == 13 then | |
| 35 | astable = true | |
| 36 | end | |
| 37 | end | |
| 38 | ||
| 39 | return astable and chars or string.char(unpack(chars)) | |
| 40 | end | |
| 41 | ||
| 42 | ||
| 43 | local crypt = function(text, key) | |
| 44 | local resp, msg = pcall(cryptWrapper, text, key) | |
| 45 | if resp then | |
| 46 | return msg | |
| 47 | else | |
| 48 | return nil | |
| 49 | end | |
| 50 | end | |
| 51 | ||
| 52 | ||
| 53 | -- Base64 | |
| 54 | -- | |
| 55 | -- Base64 Encryption/Decryption | |
| 56 | -- By KillaVanilla | |
| 57 | -- http://www.computercraft.info/forums2/index.php?/topic/12450-killavanillas-various-apis/ | |
| 58 | -- http://pastebin.com/rCYDnCxn | |
| 59 | -- | |
| 60 | ||
| 61 | ||
| 62 | ||
| 63 | local alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" | |
| 64 | ||
| 65 | ||
| 66 | local function sixBitToBase64(input) | |
| 67 | return string.sub(alphabet, input+1, input+1) | |
| 68 | end | |
| 69 | ||
| 70 | ||
| 71 | local function base64ToSixBit(input) | |
| 72 | for i=1, 64 do | |
| 73 | if input == string.sub(alphabet, i, i) then | |
| 74 | return i-1 | |
| 75 | end | |
| 76 | end | |
| 77 | end | |
| 78 | ||
| 79 | ||
| 80 | local function octetToBase64(o1, o2, o3) | |
| 81 | local shifted = bit.brshift(bit.band(o1, 0xFC), 2) | |
| 82 | local i1 = sixBitToBase64(shifted) | |
| 83 | local i2 = "A" | |
| 84 | local i3 = "=" | |
| 85 | local i4 = "=" | |
| 86 | if o2 then | |
| 87 | i2 = sixBitToBase64(bit.bor( bit.blshift(bit.band(o1, 3), 4), bit.brshift(bit.band(o2, 0xF0), 4) )) | |
| 88 | if not o3 then | |
| 89 | i3 = sixBitToBase64(bit.blshift(bit.band(o2, 0x0F), 2)) | |
| 90 | else | |
| 91 | i3 = sixBitToBase64(bit.bor( bit.blshift(bit.band(o2, 0x0F), 2), bit.brshift(bit.band(o3, 0xC0), 6) )) | |
| 92 | end | |
| 93 | else | |
| 94 | i2 = sixBitToBase64(bit.blshift(bit.band(o1, 3), 4)) | |
| 95 | end | |
| 96 | if o3 then | |
| 97 | i4 = sixBitToBase64(bit.band(o3, 0x3F)) | |
| 98 | end | |
| 99 | ||
| 100 | return i1..i2..i3..i4 | |
| 101 | end | |
| 102 | ||
| 103 | ||
| 104 | local function base64ToThreeOctet(s1) | |
| 105 | local c1 = base64ToSixBit(string.sub(s1, 1, 1)) | |
| 106 | local c2 = base64ToSixBit(string.sub(s1, 2, 2)) | |
| 107 | local c3 = 0 | |
| 108 | local c4 = 0 | |
| 109 | local o1 = 0 | |
| 110 | local o2 = 0 | |
| 111 | local o3 = 0 | |
| 112 | if string.sub(s1, 3, 3) == "=" then | |
| 113 | c3 = nil | |
| 114 | c4 = nil | |
| 115 | elseif string.sub(s1, 4, 4) == "=" then | |
| 116 | c3 = base64ToSixBit(string.sub(s1, 3, 3)) | |
| 117 | c4 = nil | |
| 118 | else | |
| 119 | c3 = base64ToSixBit(string.sub(s1, 3, 3)) | |
| 120 | c4 = base64ToSixBit(string.sub(s1, 4, 4)) | |
| 121 | end | |
| 122 | o1 = bit.bor( bit.blshift(c1, 2), bit.brshift(bit.band( c2, 0x30 ), 4) ) | |
| 123 | if c3 then | |
| 124 | o2 = bit.bor( bit.blshift(bit.band(c2, 0x0F), 4), bit.brshift(bit.band( c3, 0x3C ), 2) ) | |
| 125 | else | |
| 126 | o2 = nil | |
| 127 | end | |
| 128 | if c4 then | |
| 129 | o3 = bit.bor( bit.blshift(bit.band(c3, 3), 6), c4 ) | |
| 130 | else | |
| 131 | o3 = nil | |
| 132 | end | |
| 133 | return o1, o2, o3 | |
| 134 | end | |
| 135 | ||
| 136 | ||
| 137 | local function splitIntoBlocks(bytes) | |
| 138 | local blockNum = 1 | |
| 139 | local blocks = {}
| |
| 140 | for i=1, #bytes, 3 do | |
| 141 | blocks[blockNum] = {bytes[i], bytes[i+1], bytes[i+2]}
| |
| 142 | blockNum = blockNum+1 | |
| 143 | end | |
| 144 | return blocks | |
| 145 | end | |
| 146 | ||
| 147 | ||
| 148 | function base64Encode(bytes) | |
| 149 | local blocks = splitIntoBlocks(bytes) | |
| 150 | local output = "" | |
| 151 | for i=1, #blocks do | |
| 152 | output = output..octetToBase64( unpack(blocks[i]) ) | |
| 153 | end | |
| 154 | return output | |
| 155 | end | |
| 156 | ||
| 157 | ||
| 158 | function base64Decode(str) | |
| 159 | local bytes = {}
| |
| 160 | local blocks = {}
| |
| 161 | local blockNum = 1 | |
| 162 | ||
| 163 | for i=1, #str, 4 do | |
| 164 | blocks[blockNum] = string.sub(str, i, i+3) | |
| 165 | blockNum = blockNum+1 | |
| 166 | end | |
| 167 | ||
| 168 | for i=1, #blocks do | |
| 169 | local o1, o2, o3 = base64ToThreeOctet(blocks[i]) | |
| 170 | table.insert(bytes, o1) | |
| 171 | table.insert(bytes, o2) | |
| 172 | table.insert(bytes, o3) | |
| 173 | end | |
| 174 | ||
| 175 | return bytes | |
| 176 | end | |
| 177 | ||
| 178 | ||
| 179 | -- SHA-256 | |
| 180 | -- | |
| 181 | -- Adaptation of the Secure Hashing Algorithm (SHA-244/256) | |
| 182 | -- Found Here: http://lua-users.org/wiki/SecureHashAlgorithm | |
| 183 | -- | |
| 184 | -- Using an adapted version of the bit library | |
| 185 | -- Found Here: https://bitbucket.org/Boolsheet/bslf/src/1ee664885805/bit.lua | |
| 186 | -- | |
| 187 | ||
| 188 | ||
| 189 | ||
| 190 | local MOD = 2^32 | |
| 191 | local MODM = MOD-1 | |
| 192 | ||
| 193 | ||
| 194 | local function memoize(f) | |
| 195 | local mt = {}
| |
| 196 | local t = setmetatable({}, mt)
| |
| 197 | function mt:__index(k) | |
| 198 | local v = f(k) | |
| 199 | t[k] = v | |
| 200 | return v | |
| 201 | end | |
| 202 | return t | |
| 203 | end | |
| 204 | ||
| 205 | ||
| 206 | local function make_bitop_uncached(t, m) | |
| 207 | local function bitop(a, b) | |
| 208 | local res,p = 0,1 | |
| 209 | while a ~= 0 and b ~= 0 do | |
| 210 | local am, bm = a % m, b % m | |
| 211 | res = res + t[am][bm] * p | |
| 212 | a = (a - am) / m | |
| 213 | b = (b - bm) / m | |
| 214 | p = p * m | |
| 215 | end | |
| 216 | res = res + (a + b) * p | |
| 217 | return res | |
| 218 | end | |
| 219 | ||
| 220 | return bitop | |
| 221 | end | |
| 222 | ||
| 223 | ||
| 224 | local function make_bitop(t) | |
| 225 | local op1 = make_bitop_uncached(t,2^1) | |
| 226 | local op2 = memoize(function(a) | |
| 227 | return memoize(function(b) | |
| 228 | return op1(a, b) | |
| 229 | end) | |
| 230 | end) | |
| 231 | return make_bitop_uncached(op2, 2 ^ (t.n or 1)) | |
| 232 | end | |
| 233 | ||
| 234 | ||
| 235 | local customBxor1 = make_bitop({[0] = {[0] = 0,[1] = 1}, [1] = {[0] = 1, [1] = 0}, n = 4})
| |
| 236 | ||
| 237 | local function customBxor(a, b, c, ...) | |
| 238 | local z = nil | |
| 239 | if b then | |
| 240 | a = a % MOD | |
| 241 | b = b % MOD | |
| 242 | z = customBxor1(a, b) | |
| 243 | if c then | |
| 244 | z = customBxor(z, c, ...) | |
| 245 | end | |
| 246 | return z | |
| 247 | elseif a then | |
| 248 | return a % MOD | |
| 249 | else | |
| 250 | return 0 | |
| 251 | end | |
| 252 | end | |
| 253 | ||
| 254 | ||
| 255 | local function customBand(a, b, c, ...) | |
| 256 | local z | |
| 257 | if b then | |
| 258 | a = a % MOD | |
| 259 | b = b % MOD | |
| 260 | z = ((a + b) - customBxor1(a,b)) / 2 | |
| 261 | if c then | |
| 262 | z = customBand(z, c, ...) | |
| 263 | end | |
| 264 | return z | |
| 265 | elseif a then | |
| 266 | return a % MOD | |
| 267 | else | |
| 268 | return MODM | |
| 269 | end | |
| 270 | end | |
| 271 | ||
| 272 | ||
| 273 | local function bnot(x) | |
| 274 | return (-1 - x) % MOD | |
| 275 | end | |
| 276 | ||
| 277 | ||
| 278 | local function rshift1(a, disp) | |
| 279 | if disp < 0 then | |
| 280 | return lshift(a, -disp) | |
| 281 | end | |
| 282 | return math.floor(a % 2 ^ 32 / 2 ^ disp) | |
| 283 | end | |
| 284 | ||
| 285 | ||
| 286 | local function rshift(x, disp) | |
| 287 | if disp > 31 or disp < -31 then | |
| 288 | return 0 | |
| 289 | end | |
| 290 | return rshift1(x % MOD, disp) | |
| 291 | end | |
| 292 | ||
| 293 | ||
| 294 | local function lshift(a, disp) | |
| 295 | if disp < 0 then | |
| 296 | return rshift(a, -disp) | |
| 297 | end | |
| 298 | return (a * 2 ^ disp) % 2 ^ 32 | |
| 299 | end | |
| 300 | ||
| 301 | ||
| 302 | local function rrotate(x, disp) | |
| 303 | x = x % MOD | |
| 304 | disp = disp % 32 | |
| 305 | local low = customBand(x, 2 ^ disp - 1) | |
| 306 | return rshift(x, disp) + lshift(low, 32 - disp) | |
| 307 | end | |
| 308 | ||
| 309 | ||
| 310 | local k = {
| |
| 311 | 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, | |
| 312 | 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, | |
| 313 | 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, | |
| 314 | 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, | |
| 315 | 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, | |
| 316 | 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, | |
| 317 | 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, | |
| 318 | 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, | |
| 319 | 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, | |
| 320 | 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, | |
| 321 | 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, | |
| 322 | 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, | |
| 323 | 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, | |
| 324 | 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, | |
| 325 | 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, | |
| 326 | 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, | |
| 327 | } | |
| 328 | ||
| 329 | ||
| 330 | local function str2hexa(s) | |
| 331 | return (string.gsub(s, ".", function(c) | |
| 332 | return string.format("%02x", string.byte(c))
| |
| 333 | end)) | |
| 334 | end | |
| 335 | ||
| 336 | ||
| 337 | local function num2s(l, n) | |
| 338 | local s = "" | |
| 339 | for i = 1, n do | |
| 340 | local rem = l % 256 | |
| 341 | s = string.char(rem) .. s | |
| 342 | l = (l - rem) / 256 | |
| 343 | end | |
| 344 | return s | |
| 345 | end | |
| 346 | ||
| 347 | ||
| 348 | local function s232num(s, i) | |
| 349 | local n = 0 | |
| 350 | for i = i, i + 3 do | |
| 351 | n = n*256 + string.byte(s, i) | |
| 352 | end | |
| 353 | return n | |
| 354 | end | |
| 355 | ||
| 356 | ||
| 357 | local function preproc(msg, len) | |
| 358 | local extra = 64 - ((len + 9) % 64) | |
| 359 | len = num2s(8 * len, 8) | |
| 360 | msg = msg .. "\128" .. string.rep("\0", extra) .. len
| |
| 361 | assert(#msg % 64 == 0) | |
| 362 | return msg | |
| 363 | end | |
| 364 | ||
| 365 | ||
| 366 | local function initH256(H) | |
| 367 | H[1] = 0x6a09e667 | |
| 368 | H[2] = 0xbb67ae85 | |
| 369 | H[3] = 0x3c6ef372 | |
| 370 | H[4] = 0xa54ff53a | |
| 371 | H[5] = 0x510e527f | |
| 372 | H[6] = 0x9b05688c | |
| 373 | H[7] = 0x1f83d9ab | |
| 374 | H[8] = 0x5be0cd19 | |
| 375 | return H | |
| 376 | end | |
| 377 | ||
| 378 | ||
| 379 | local function digestblock(msg, i, H) | |
| 380 | local w = {}
| |
| 381 | for j = 1, 16 do | |
| 382 | w[j] = s232num(msg, i + (j - 1)*4) | |
| 383 | end | |
| 384 | for j = 17, 64 do | |
| 385 | local v = w[j - 15] | |
| 386 | local s0 = customBxor(rrotate(v, 7), rrotate(v, 18), rshift(v, 3)) | |
| 387 | v = w[j - 2] | |
| 388 | w[j] = w[j - 16] + s0 + w[j - 7] + customBxor(rrotate(v, 17), rrotate(v, 19), rshift(v, 10)) | |
| 389 | end | |
| 390 | ||
| 391 | 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] | |
| 392 | for i = 1, 64 do | |
| 393 | local s0 = customBxor(rrotate(a, 2), rrotate(a, 13), rrotate(a, 22)) | |
| 394 | local maj = customBxor(customBand(a, b), customBand(a, c), customBand(b, c)) | |
| 395 | local t2 = s0 + maj | |
| 396 | local s1 = customBxor(rrotate(e, 6), rrotate(e, 11), rrotate(e, 25)) | |
| 397 | local ch = customBxor (customBand(e, f), customBand(bnot(e), g)) | |
| 398 | local t1 = h + s1 + ch + k[i] + w[i] | |
| 399 | h, g, f, e, d, c, b, a = g, f, e, d + t1, c, b, a, t1 + t2 | |
| 400 | end | |
| 401 | ||
| 402 | H[1] = customBand(H[1] + a) | |
| 403 | H[2] = customBand(H[2] + b) | |
| 404 | H[3] = customBand(H[3] + c) | |
| 405 | H[4] = customBand(H[4] + d) | |
| 406 | H[5] = customBand(H[5] + e) | |
| 407 | H[6] = customBand(H[6] + f) | |
| 408 | H[7] = customBand(H[7] + g) | |
| 409 | H[8] = customBand(H[8] + h) | |
| 410 | end | |
| 411 | ||
| 412 | ||
| 413 | local function sha256(msg) | |
| 414 | msg = preproc(msg, #msg) | |
| 415 | local H = initH256({})
| |
| 416 | for i = 1, #msg, 64 do | |
| 417 | digestblock(msg, i, H) | |
| 418 | end | |
| 419 | return str2hexa(num2s(H[1], 4) .. num2s(H[2], 4) .. num2s(H[3], 4) .. num2s(H[4], 4) .. | |
| 420 | num2s(H[5], 4) .. num2s(H[6], 4) .. num2s(H[7], 4) .. num2s(H[8], 4)) | |
| 421 | end | |
| 422 | ||
| 423 | ||
| 424 | local protocolName = "Firewolf" | |
| 425 | ||
| 426 | ||
| 427 | -- Cryptography | |
| 428 | local Cryptography = {}
| |
| 429 | Cryptography.sha = {}
| |
| 430 | Cryptography.base64 = {}
| |
| 431 | Cryptography.aes = {}
| |
| 432 | ||
| 433 | ||
| 434 | function Cryptography.bytesFromMessage(msg) | |
| 435 | local bytes = {}
| |
| 436 | ||
| 437 | for i = 1, msg:len() do | |
| 438 | local letter = string.byte(msg:sub(i, i)) | |
| 439 | table.insert(bytes, letter) | |
| 440 | end | |
| 441 | ||
| 442 | return bytes | |
| 443 | end | |
| 444 | ||
| 445 | ||
| 446 | function Cryptography.messageFromBytes(bytes) | |
| 447 | local msg = "" | |
| 448 | ||
| 449 | for i = 1, #bytes do | |
| 450 | local letter = string.char(bytes[i]) | |
| 451 | msg = msg .. letter | |
| 452 | end | |
| 453 | ||
| 454 | return msg | |
| 455 | end | |
| 456 | ||
| 457 | ||
| 458 | function Cryptography.bytesFromKey(key) | |
| 459 | local bytes = {}
| |
| 460 | ||
| 461 | for i = 1, key:len() / 2 do | |
| 462 | local group = key:sub((i - 1) * 2 + 1, (i - 1) * 2 + 1) | |
| 463 | local num = tonumber(group, 16) | |
| 464 | table.insert(bytes, num) | |
| 465 | end | |
| 466 | ||
| 467 | return bytes | |
| 468 | end | |
| 469 | ||
| 470 | ||
| 471 | function Cryptography.sha.sha256(msg) | |
| 472 | return sha256(msg) | |
| 473 | end | |
| 474 | ||
| 475 | ||
| 476 | function Cryptography.aes.encrypt(msg, key) | |
| 477 | return base64Encode(crypt(msg, key)) | |
| 478 | end | |
| 479 | ||
| 480 | ||
| 481 | function Cryptography.aes.decrypt(msg, key) | |
| 482 | return crypt(base64Decode(msg), key) | |
| 483 | end | |
| 484 | ||
| 485 | ||
| 486 | function Cryptography.base64.encode(msg) | |
| 487 | return base64Encode(Cryptography.bytesFromMessage(msg)) | |
| 488 | end | |
| 489 | ||
| 490 | ||
| 491 | function Cryptography.base64.decode(msg) | |
| 492 | return Cryptography.messageFromBytes(base64Decode(msg)) | |
| 493 | end | |
| 494 | ||
| 495 | function Cryptography.channel(text) | |
| 496 | local hashed = Cryptography.sha.sha256(text) | |
| 497 | ||
| 498 | local total = 0 | |
| 499 | ||
| 500 | for i = 1, hashed:len() do | |
| 501 | total = total + string.byte(hashed:sub(i, i)) | |
| 502 | end | |
| 503 | ||
| 504 | return (total % 55530) + 10000 | |
| 505 | end | |
| 506 | ||
| 507 | function Cryptography.sanatize(text) | |
| 508 | local sanatizeChars = {"%", "(", ")", "[", "]", ".", "+", "-", "*", "?", "^", "$"}
| |
| 509 | ||
| 510 | for _, char in pairs(sanatizeChars) do | |
| 511 | text = text:gsub("%"..char, "%%%"..char)
| |
| 512 | end | |
| 513 | return text | |
| 514 | end | |
| 515 | ||
| 516 | ||
| 517 | -- Modem | |
| 518 | local Modem = {}
| |
| 519 | ||
| 520 | Modem.modems = {}
| |
| 521 | ||
| 522 | function Modem.exists() | |
| 523 | Modem.exists = false | |
| 524 | for _, side in pairs(rs.getSides()) do | |
| 525 | if peripheral.isPresent(side) and peripheral.getType(side) == "modem" then | |
| 526 | Modem.exists = true | |
| 527 | ||
| 528 | if not Modem.modems[side] then | |
| 529 | Modem.modems[side] = peripheral.wrap(side) | |
| 530 | end | |
| 531 | end | |
| 532 | end | |
| 533 | ||
| 534 | return Modem.exists | |
| 535 | end | |
| 536 | ||
| 537 | ||
| 538 | function Modem.open(channel) | |
| 539 | if not Modem.exists then | |
| 540 | return false | |
| 541 | end | |
| 542 | ||
| 543 | for side, modem in pairs(Modem.modems) do | |
| 544 | modem.open(channel) | |
| 545 | rednet.open(side) | |
| 546 | end | |
| 547 | ||
| 548 | return true | |
| 549 | end | |
| 550 | ||
| 551 | ||
| 552 | function Modem.close(channel) | |
| 553 | if not Modem.exists then | |
| 554 | return false | |
| 555 | end | |
| 556 | ||
| 557 | for side, modem in pairs(Modem.modems) do | |
| 558 | modem.close(channel) | |
| 559 | end | |
| 560 | ||
| 561 | return true | |
| 562 | end | |
| 563 | ||
| 564 | ||
| 565 | function Modem.closeAll() | |
| 566 | if not Modem.exists then | |
| 567 | return false | |
| 568 | end | |
| 569 | ||
| 570 | for side, modem in pairs(Modem.modems) do | |
| 571 | modem.closeAll() | |
| 572 | end | |
| 573 | ||
| 574 | return true | |
| 575 | end | |
| 576 | ||
| 577 | ||
| 578 | function Modem.isOpen(channel) | |
| 579 | if not Modem.exists then | |
| 580 | return false | |
| 581 | end | |
| 582 | ||
| 583 | local isOpen = false | |
| 584 | for side, modem in pairs(Modem.modems) do | |
| 585 | if modem.isOpen(channel) then | |
| 586 | isOpen = true | |
| 587 | break | |
| 588 | end | |
| 589 | end | |
| 590 | ||
| 591 | return isOpen | |
| 592 | end | |
| 593 | ||
| 594 | ||
| 595 | function Modem.transmit(channel, msg) | |
| 596 | if not Modem.exists then | |
| 597 | return false | |
| 598 | end | |
| 599 | ||
| 600 | if not Modem.isOpen(channel) then | |
| 601 | Modem.open(channel) | |
| 602 | end | |
| 603 | ||
| 604 | for side, modem in pairs(Modem.modems) do | |
| 605 | modem.transmit(channel, channel, msg) | |
| 606 | end | |
| 607 | ||
| 608 | return true | |
| 609 | end | |
| 610 | ||
| 611 | ||
| 612 | -- Handshake | |
| 613 | local Handshake = {}
| |
| 614 | ||
| 615 | Handshake.prime = 625210769 | |
| 616 | Handshake.channel = 54569 | |
| 617 | Handshake.base = -1 | |
| 618 | Handshake.secret = -1 | |
| 619 | Handshake.sharedSecret = -1 | |
| 620 | Handshake.packetHeader = "["..protocolName.."-Handshake-Packet-Header]" | |
| 621 | Handshake.packetMatch = "%["..protocolName.."%-Handshake%-Packet%-Header%](.+)" | |
| 622 | ||
| 623 | function Handshake.exponentWithModulo(base, exponent, modulo) | |
| 624 | local remainder = base | |
| 625 | ||
| 626 | for i = 1, exponent-1 do | |
| 627 | remainder = remainder * remainder | |
| 628 | if remainder >= modulo then | |
| 629 | remainder = remainder % modulo | |
| 630 | end | |
| 631 | end | |
| 632 | ||
| 633 | return remainder | |
| 634 | end | |
| 635 | ||
| 636 | ||
| 637 | function Handshake.clear() | |
| 638 | Handshake.base = -1 | |
| 639 | Handshake.secret = -1 | |
| 640 | Handshake.sharedSecret = -1 | |
| 641 | end | |
| 642 | ||
| 643 | function Handshake.generateInitiatorData() | |
| 644 | Handshake.base = math.random(10,99999) | |
| 645 | Handshake.secret = math.random(10,99999) | |
| 646 | return {
| |
| 647 | type = "initiate", | |
| 648 | prime = Handshake.prime, | |
| 649 | base = Handshake.base, | |
| 650 | moddedSecret = Handshake.exponentWithModulo(Handshake.base, Handshake.secret, Handshake.prime) | |
| 651 | } | |
| 652 | end | |
| 653 | ||
| 654 | function Handshake.generateResponseData(initiatorData) | |
| 655 | local isPrimeANumber = type(initiatorData.prime) == "number" | |
| 656 | local isPrimeMatching = initiatorData.prime == Handshake.prime | |
| 657 | local isBaseANumber = type(initiatorData.base) == "number" | |
| 658 | local isInitiator = initiatorData.type == "initiate" | |
| 659 | local isModdedSecretANumber = type(initiatorData.moddedSecret) == "number" | |
| 660 | local areAllNumbersNumbers = isPrimeANumber and isBaseANumber and isModdedSecretANumber | |
| 661 | ||
| 662 | if areAllNumbersNumbers and isPrimeMatching then | |
| 663 | if isInitiator then | |
| 664 | Handshake.base = initiatorData.base | |
| 665 | Handshake.secret = math.random(10,99999) | |
| 666 | Handshake.sharedSecret = Handshake.exponentWithModulo(initiatorData.moddedSecret, Handshake.secret, Handshake.prime) | |
| 667 | return {
| |
| 668 | type = "response", | |
| 669 | prime = Handshake.prime, | |
| 670 | base = Handshake.base, | |
| 671 | moddedSecret = Handshake.exponentWithModulo(Handshake.base, Handshake.secret, Handshake.prime) | |
| 672 | }, Handshake.sharedSecret | |
| 673 | elseif initiatorData.type == "response" and Handshake.base > 0 and Handshake.secret > 0 then | |
| 674 | Handshake.sharedSecret = Handshake.exponentWithModulo(initiatorData.moddedSecret, Handshake.secret, Handshake.prime) | |
| 675 | return Handshake.sharedSecret | |
| 676 | else | |
| 677 | return false | |
| 678 | end | |
| 679 | else | |
| 680 | return false | |
| 681 | end | |
| 682 | end | |
| 683 | ||
| 684 | -- Secure Connection | |
| 685 | local SecureConnection = {}
| |
| 686 | SecureConnection.__index = SecureConnection | |
| 687 | ||
| 688 | ||
| 689 | SecureConnection.packetHeaderA = "["..protocolName.."-" | |
| 690 | SecureConnection.packetHeaderB = "-SecureConnection-Packet-Header]" | |
| 691 | SecureConnection.packetMatchA = "%["..protocolName.."%-" | |
| 692 | SecureConnection.packetMatchB = "%-SecureConnection%-Packet%-Header%](.+)" | |
| 693 | SecureConnection.connectionTimeout = 0.1 | |
| 694 | SecureConnection.successPacketTimeout = 0.1 | |
| 695 | ||
| 696 | ||
| 697 | function SecureConnection.new(secret, key, identifier, distance, isRednet) | |
| 698 | local self = setmetatable({}, SecureConnection)
| |
| 699 | self:setup(secret, key, identifier, distance, isRednet) | |
| 700 | return self | |
| 701 | end | |
| 702 | ||
| 703 | ||
| 704 | function SecureConnection:setup(secret, key, identifier, distance, isRednet) | |
| 705 | local rawSecret | |
| 706 | ||
| 707 | if isRednet then | |
| 708 | self.isRednet = true | |
| 709 | self.distance = -1 | |
| 710 | self.rednet_id = distance | |
| 711 | rawSecret = protocolName .. "|" .. tostring(secret) .. "|" .. tostring(identifier) .. | |
| 712 | "|" .. tostring(key) .. "|rednet" | |
| 713 | else | |
| 714 | self.isRednet = false | |
| 715 | self.distance = distance | |
| 716 | rawSecret = protocolName .. "|" .. tostring(secret) .. "|" .. tostring(identifier) .. | |
| 717 | "|" .. tostring(key) .. "|" .. tostring(distance) | |
| 718 | end | |
| 719 | ||
| 720 | self.identifier = identifier | |
| 721 | self.packetMatch = SecureConnection.packetMatchA .. Cryptography.sanatize(identifier) .. SecureConnection.packetMatchB | |
| 722 | self.packetHeader = SecureConnection.packetHeaderA .. identifier .. SecureConnection.packetHeaderB | |
| 723 | self.secret = Cryptography.sha.sha256(rawSecret) | |
| 724 | self.channel = Cryptography.channel(self.secret) | |
| 725 | ||
| 726 | if not self.isRednet then | |
| 727 | Modem.open(self.channel) | |
| 728 | end | |
| 729 | end | |
| 730 | ||
| 731 | ||
| 732 | function SecureConnection:verifyHeader(msg) | |
| 733 | if type(msg) ~= "string" then return false end | |
| 734 | ||
| 735 | if msg:match(self.packetMatch) then | |
| 736 | return true | |
| 737 | else | |
| 738 | return false | |
| 739 | end | |
| 740 | end | |
| 741 | ||
| 742 | ||
| 743 | function SecureConnection:sendMessage(msg, rednetProtocol) | |
| 744 | local rawEncryptedMsg = Cryptography.aes.encrypt(self.packetHeader .. msg, self.secret) | |
| 745 | local encryptedMsg = self.packetHeader .. rawEncryptedMsg | |
| 746 | ||
| 747 | if self.isRednet then | |
| 748 | rednet.send(self.rednet_id, encryptedMsg, rednetProtocol) | |
| 749 | return true | |
| 750 | else | |
| 751 | return Modem.transmit(self.channel, encryptedMsg) | |
| 752 | end | |
| 753 | end | |
| 754 | ||
| 755 | ||
| 756 | function SecureConnection:decryptMessage(msg) | |
| 757 | if self:verifyHeader(msg) then | |
| 758 | local encrypted = msg:match(self.packetMatch) | |
| 759 | ||
| 760 | local unencryptedMsg = nil | |
| 761 | pcall(function() unencryptedMsg = Cryptography.aes.decrypt(encrypted, self.secret) end) | |
| 762 | if not unencryptedMsg then | |
| 763 | return false, "Could not decrypt" | |
| 764 | end | |
| 765 | ||
| 766 | if self:verifyHeader(unencryptedMsg) then | |
| 767 | return true, unencryptedMsg:match(self.packetMatch) | |
| 768 | else | |
| 769 | return false, "Could not verify" | |
| 770 | end | |
| 771 | else | |
| 772 | return false, "Could not stage 1 verify" | |
| 773 | end | |
| 774 | end | |
| 775 | -- END OF NETWORKING | |
| 776 | ||
| 777 | dnsListenChannel = 9999 | |
| 778 | dnsResponseChannel = 9998 | |
| 779 | ||
| 780 | local config = {}
| |
| 781 | config.visibleLoggingLevel = 1 | |
| 782 | config.writtenLoggingLevel = 1 | |
| 783 | config.loggingLocation = "/fwserver-logs" | |
| 784 | config.enableLogging = false | |
| 785 | config.password = null | |
| 786 | config.allowRednetConnections = true | |
| 787 | config.repeatRednetMessages = false | |
| 788 | config.lastDomain = nil | |
| 789 | --config.actAsGPS = false | |
| 790 | --config.gpsLocation = {}
| |
| 791 | ||
| 792 | local configLocation = "/.fwserver-config" | |
| 793 | local serverLocation = "/fw_servers" | |
| 794 | local serverAPILocation = "server_api" | |
| 795 | local locked = false | |
| 796 | local domain = ... | |
| 797 | local responseStack = {}
| |
| 798 | local repeatStack = {}
| |
| 799 | local terminalLog = {}
| |
| 800 | local repeatedMessages = {}
| |
| 801 | local lastLogNum = 0 | |
| 802 | local servedRequests = 0 | |
| 803 | local serverChannel = 0 | |
| 804 | local requestMatch = "" | |
| 805 | local maliciousMatch = "" | |
| 806 | local responseHeader = "" | |
| 807 | local pageRequestMatch = "" | |
| 808 | local pageResposneHeader = "" | |
| 809 | local renableRednet = nil | |
| 810 | local handles = {}
| |
| 811 | local customCoroutines = {}
| |
| 812 | local globalHandler = nil | |
| 813 | local closeMatch = "" | |
| 814 | local connections = {}
| |
| 815 | local updateURL = "https://raw.githubusercontent.com/1lann/Firewolf/master/src/server.lua" | |
| 816 | ||
| 817 | local version = "3.5.2" | |
| 818 | ||
| 819 | local header = {}
| |
| 820 | header.dnsPacket = "[Firewolf-DNS-Packet]" | |
| 821 | header.dnsHeader = "[Firewolf-DNS-Response]" | |
| 822 | header.dnsHeaderMatch = "^%[Firewolf%-DNS%-Response%](.+)$" | |
| 823 | header.rednetHeader = "[Firewolf-Rednet-Channel-Simulation]" | |
| 824 | header.rednetMatch = "^%[Firewolf%-Rednet%-Channel%-Simulation%](%d+)$" | |
| 825 | header.requestMatchA = "^%[Firewolf%-" | |
| 826 | header.requestMatchB = "%-Handshake%-Request%](.+)$" | |
| 827 | header.maliciousMatchA = "^%[Firewolf%-" | |
| 828 | header.maliciousMatchB = "%-.+%-Handshake%-Response%](.+)$" | |
| 829 | header.responseHeaderA = "[Firewolf-" | |
| 830 | header.responseHeaderB = "-" | |
| 831 | header.responseHeaderC = "-Handshake-Response]" | |
| 832 | header.pageRequestMatchA = "^%[Firewolf%-" | |
| 833 | header.pageRequestMatchB = "%-Page%-Request%](.+)$" | |
| 834 | header.pageResponseHeaderA = "[Firewolf-" | |
| 835 | header.pageResponseHeaderB = "-Page-Response][HEADER]" | |
| 836 | header.pageResponseHeaderC = "[BODY]" | |
| 837 | header.closeMatchA = "[Firewolf-" | |
| 838 | header.closeMatchB = "-Connection-Close]" | |
| 839 | ||
| 840 | local resetServerEvent = "firewolf-server-reset-event" | |
| 841 | ||
| 842 | local theme = {}
| |
| 843 | theme.error = colors.red | |
| 844 | theme.background = colors.gray | |
| 845 | theme.text = colors.white | |
| 846 | theme.notice = colors.white | |
| 847 | theme.barColor = colors.red | |
| 848 | theme.barText = colors.white | |
| 849 | theme.inputColor = colors.white | |
| 850 | theme.clock = colors.white | |
| 851 | theme.lock = colors.orange | |
| 852 | theme.userResponse = colors.orange | |
| 853 | ||
| 854 | if not term.isColor() then | |
| 855 | theme.error = colors.white | |
| 856 | theme.background = colors.black | |
| 857 | theme.text = colors.white | |
| 858 | theme.notice = colors.white | |
| 859 | theme.barColor = colors.white | |
| 860 | theme.barText = colors.black | |
| 861 | theme.inputColor = colors.white | |
| 862 | theme.clock = colors.white | |
| 863 | theme.lock = colors.white | |
| 864 | theme.userResponse = colors.white | |
| 865 | end | |
| 866 | ||
| 867 | local w, h = term.getSize() | |
| 868 | ||
| 869 | -- Utilities | |
| 870 | ||
| 871 | local makeDirectory = function(path) | |
| 872 | fs.makeDir(path) | |
| 873 | local function createIndex(path) | |
| 874 | if not (fs.exists(path.."/index") or fs.exists(path.."/index.fwml")) then | |
| 875 | f = io.open(path.."/index", "w") | |
| 876 | f:write("print('')\ncenter('Welcome to "..domain.."!')")
| |
| 877 | f:close() | |
| 878 | end | |
| 879 | end | |
| 880 | pcall(function() createIndex(path) end) | |
| 881 | end | |
| 882 | ||
| 883 | local checkDomain = function(domain) | |
| 884 | if domain:find("/") or domain:find(":") or domain:find("%?") then
| |
| 885 | return "symbols" | |
| 886 | end | |
| 887 | if #domain < 4 then | |
| 888 | return "short" | |
| 889 | else | |
| 890 | Modem.open(dnsListenChannel) | |
| 891 | Modem.open(dnsResponseChannel) | |
| 892 | Modem.transmit(dnsListenChannel, header.dnsPacket) | |
| 893 | Modem.close(dnsListenChannel) | |
| 894 | ||
| 895 | rednet.broadcast(header.dnsPacket, header.rednetHeader .. dnsListenChannel) | |
| 896 | local timer = os.startTimer(2) | |
| 897 | while true do | |
| 898 | local event, id, channel, protocol, message, dist = os.pullEventRaw() | |
| 899 | if event == "modem_message" and channel == dnsResponseChannel and type(message) == string and message:match(header.dnsHeaderMatch) == domain then | |
| 900 | return "taken" | |
| 901 | elseif event == "rednet_message" and protocol and tonumber(protocol:match(header.rednetMatch)) == dnsResponseChannel and channel:match(header.dnsHeaderMatch) == domain then | |
| 902 | return "taken" | |
| 903 | elseif event == "timer" and id == timer then | |
| 904 | break | |
| 905 | end | |
| 906 | end | |
| 907 | end | |
| 908 | return "ok" | |
| 909 | end | |
| 910 | ||
| 911 | local checkConfig = function() | |
| 912 | local errorReport = {}
| |
| 913 | if not config then | |
| 914 | table.insert(errorReport, "Corrupted configuration file!") | |
| 915 | return errorReport | |
| 916 | end | |
| 917 | if config.enableLogging then | |
| 918 | if fs.isReadOnly(config.loggingLocation) or fs.isDir(config.loggingLocation) then | |
| 919 | table.insert(errorReport, "Invalid logging location!") | |
| 920 | end | |
| 921 | end | |
| 922 | ||
| 923 | if #errorReport > 0 then | |
| 924 | return errorReport | |
| 925 | else | |
| 926 | return false | |
| 927 | end | |
| 928 | end | |
| 929 | ||
| 930 | local loadConfig = function() | |
| 931 | if fs.exists(configLocation) and not fs.isDir(configLocation) then | |
| 932 | local f = io.open(configLocation, "r") | |
| 933 | config = textutils.unserialize(f:read("*a"))
| |
| 934 | if config then | |
| 935 | if type(config.actAsRednetRepeater) == "boolean" then | |
| 936 | config.actAsRednetRepeater = nil | |
| 937 | end | |
| 938 | end | |
| 939 | f:close() | |
| 940 | else | |
| 941 | config = nil | |
| 942 | end | |
| 943 | end | |
| 944 | ||
| 945 | local saveConfig = function() | |
| 946 | local f = io.open(configLocation, "w") | |
| 947 | f:write(textutils.serialize(config)) | |
| 948 | f:close() | |
| 949 | end | |
| 950 | ||
| 951 | local center = function(text) | |
| 952 | local x, y = term.getCursorPos() | |
| 953 | term.setCursorPos(math.floor(w / 2 - text:len() / 2) + (text:len() % 2 == 0 and 1 or 0), y) | |
| 954 | term.write(text) | |
| 955 | term.setCursorPos(1, y + 1) | |
| 956 | end | |
| 957 | ||
| 958 | local writeLog = function(text, color, level) | |
| 959 | if not level then level = 0 end | |
| 960 | if not color then color = theme.text end | |
| 961 | if level >= config.visibleLoggingLevel then | |
| 962 | local time = textutils.formatTime(os.time(), true) | |
| 963 | if #time <= 4 then | |
| 964 | time = "0"..time | |
| 965 | end | |
| 966 | table.insert(terminalLog, {text, color, "["..time.."] "})
| |
| 967 | end | |
| 968 | ||
| 969 | if config.enableLogging and level >= config.writtenLoggingLevel then | |
| 970 | if fs.isDir(config.loggingLocation) then | |
| 971 | fs.delete(config.loggingLocation) | |
| 972 | end | |
| 973 | if not fs.exists(config.loggingLocation) then | |
| 974 | local f = io.open(config.loggingLocation, "w") | |
| 975 | f:write("\n")
| |
| 976 | f:close() | |
| 977 | end | |
| 978 | local time = textutils.formatTime(os.time(), true) | |
| 979 | if #time <= 4 then | |
| 980 | time = "0"..time | |
| 981 | end | |
| 982 | local f = io.open(config.loggingLocation, "a") | |
| 983 | f:write("["..time.."] "..text.."\n")
| |
| 984 | f:close() | |
| 985 | end | |
| 986 | end | |
| 987 | ||
| 988 | local writeError = function(text) | |
| 989 | for i = 1, math.ceil(#text / (w - 8)) do | |
| 990 | writeLog(text:sub((i - 1) * (w - 8), i * (w - 8)), theme.error, math.huge) | |
| 991 | end | |
| 992 | end | |
| 993 | ||
| 994 | -- Message handling | |
| 995 | ||
| 996 | local receiveDaemon = function() | |
| 997 | while true do | |
| 998 | local event, id, channel, protocol, message, dist = os.pullEventRaw() | |
| 999 | if event == "modem_message" then | |
| 1000 | if channel == rednet.CHANNEL_REPEAT and config.repeatRednetMessages and type(message) == "table" and | |
| 1001 | message.sProtocol and message.sProtocol:match(header.rednetMatch) then | |
| 1002 | table.insert(repeatStack, {message = message, reply = protocol})
| |
| 1003 | elseif channel ~= rednet.CHANNEL_BROADCAST then | |
| 1004 | table.insert(responseStack, {type = "direct", channel = channel, message = message, dist = dist, reply = protocol})
| |
| 1005 | end | |
| 1006 | elseif event == "rednet_message" and protocol and protocol:match(header.rednetMatch) and config.allowRednetConnections then | |
| 1007 | table.insert(responseStack, {type = "rednet", channel = tonumber(protocol:match(header.rednetMatch)), rednet_id = id, message = channel, dist = null})
| |
| 1008 | elseif event == "timer" and id == renableRednet then | |
| 1009 | Modem.open(rednet.CHANNEL_REPEAT) | |
| 1010 | end | |
| 1011 | end | |
| 1012 | end | |
| 1013 | ||
| 1014 | local getActiveConnection = function(channel, distance) | |
| 1015 | for k, connection in pairs(connections) do | |
| 1016 | if connection.channel == channel then | |
| 1017 | if (not distance) then | |
| 1018 | if connection.isRednet then | |
| 1019 | return connection | |
| 1020 | else | |
| 1021 | return false | |
| 1022 | end | |
| 1023 | else | |
| 1024 | if connection.distance == distance then | |
| 1025 | return connection | |
| 1026 | else | |
| 1027 | return false | |
| 1028 | end | |
| 1029 | end | |
| 1030 | end | |
| 1031 | end | |
| 1032 | end | |
| 1033 | ||
| 1034 | local urlEncode = function(url) | |
| 1035 | local result = url | |
| 1036 | ||
| 1037 | result = result:gsub("%%", "%%a")
| |
| 1038 | result = result:gsub(":", "%%c")
| |
| 1039 | result = result:gsub("/", "%%s")
| |
| 1040 | result = result:gsub("\n", "%%n")
| |
| 1041 | result = result:gsub(" ", "%%w")
| |
| 1042 | result = result:gsub("&", "%%m")
| |
| 1043 | result = result:gsub("%?", "%%q")
| |
| 1044 | result = result:gsub("=", "%%e")
| |
| 1045 | result = result:gsub("%.", "%%d")
| |
| 1046 | ||
| 1047 | return result | |
| 1048 | end | |
| 1049 | ||
| 1050 | local urlDecode = function(url) | |
| 1051 | local result = url | |
| 1052 | ||
| 1053 | result = result:gsub("%%c", ":")
| |
| 1054 | result = result:gsub("%%s", "/")
| |
| 1055 | result = result:gsub("%%n", "\n")
| |
| 1056 | result = result:gsub("%%w", " ")
| |
| 1057 | result = result:gsub("%%&", "&")
| |
| 1058 | result = result:gsub("%%q", "%?")
| |
| 1059 | result = result:gsub("%%e", "=")
| |
| 1060 | result = result:gsub("%%d", "%.")
| |
| 1061 | result = result:gsub("%%m", "%%")
| |
| 1062 | ||
| 1063 | return result | |
| 1064 | end | |
| 1065 | ||
| 1066 | local getURLVars = function(url) | |
| 1067 | local vars = {}
| |
| 1068 | if url then | |
| 1069 | local firstVarIndex, firstVarVal = url:match("%?([^=]+)=([^&]+)")
| |
| 1070 | if not firstVarIndex then | |
| 1071 | return | |
| 1072 | else | |
| 1073 | vars[urlDecode(firstVarIndex)] = urlDecode(firstVarVal) | |
| 1074 | for index, val in url:gmatch("&([^=]+)=([^&]+)") do
| |
| 1075 | vars[urlDecode(index)] = urlDecode(val) | |
| 1076 | end | |
| 1077 | return vars | |
| 1078 | end | |
| 1079 | else | |
| 1080 | return | |
| 1081 | end | |
| 1082 | end | |
| 1083 | ||
| 1084 | local fetchPage = function(page) | |
| 1085 | local pageRequest = fs.combine("", page:match("^[^%?]+"))
| |
| 1086 | local varRequest = page:match("%?[^%?]+$")
| |
| 1087 | ||
| 1088 | if (pageRequest:match("(.+)%.fwml$")) then
| |
| 1089 | pageRequest = pageRequest:match("(.+)%.fwml$")
| |
| 1090 | end | |
| 1091 | ||
| 1092 | pageRequest = pageRequest:gsub("%.%.", "")
| |
| 1093 | ||
| 1094 | local handleResponse, respHeader | |
| 1095 | ||
| 1096 | if globalHandler then | |
| 1097 | err, msg = pcall(function() handleResponse, respHeader = globalHandler(page, getURLVars(varRequest)) end) | |
| 1098 | if not err then | |
| 1099 | writeLog("Error when executing server API function", theme.error, math.huge)
| |
| 1100 | writeError("/: " .. tostring(msg))
| |
| 1101 | end | |
| 1102 | end | |
| 1103 | ||
| 1104 | if handleResponse then | |
| 1105 | if not respHeader then | |
| 1106 | respHeader = "lua" | |
| 1107 | end | |
| 1108 | return handleResponse, respHeader, true | |
| 1109 | end | |
| 1110 | ||
| 1111 | if pageRequest == serverAPILocation then | |
| 1112 | -- Forbid accessing server api files | |
| 1113 | return nil | |
| 1114 | end | |
| 1115 | ||
| 1116 | for k,v in pairs(handles) do | |
| 1117 | local startSearch, endSearch = pageRequest:find(k) | |
| 1118 | if startSearch == 1 and ((endSearch == #pageRequest) or (pageRequest:sub(endSearch + 1, endSearch + 1) == "/")) then | |
| 1119 | err, msg = pcall(function() handleResponse, respHeader = v(page, getURLVars(varRequest)) end) | |
| 1120 | if not err then | |
| 1121 | writeLog("Error when executing server API function", theme.error, math.huge)
| |
| 1122 | writeError(k .. ": " .. tostring(msg)) | |
| 1123 | end | |
| 1124 | end | |
| 1125 | end | |
| 1126 | ||
| 1127 | if handleResponse then | |
| 1128 | if not respHeader then | |
| 1129 | respHeader = "lua" | |
| 1130 | end | |
| 1131 | return handleResponse, respHeader, true | |
| 1132 | end | |
| 1133 | ||
| 1134 | - | local path = serverLocation .. "/" .. domain .. "/" .. pageRequest |
| 1134 | + | local path = serverLocation .. "/" .. pageRequest |
| 1135 | if fs.exists(path) and not fs.isDir(path) then | |
| 1136 | local f = io.open(path, "r") | |
| 1137 | local contents = f:read("*a")
| |
| 1138 | f:close() | |
| 1139 | ||
| 1140 | return contents, "lua", false | |
| 1141 | else | |
| 1142 | if fs.exists(path..".fwml") and not fs.isDir(path..".fwml") then | |
| 1143 | local f = io.open(path..".fwml", "r") | |
| 1144 | local contents = f:read("*a")
| |
| 1145 | f:close() | |
| 1146 | ||
| 1147 | return contents, "fwml", false | |
| 1148 | end | |
| 1149 | end | |
| 1150 | ||
| 1151 | return nil | |
| 1152 | end | |
| 1153 | ||
| 1154 | local sendPage = function(connection, body, head) | |
| 1155 | if head == "fwml" then | |
| 1156 | connection:sendMessage(pageResposneHeader..'{["language"]="Firewolf Markup"}'..header.pageResponseHeaderC..body, header.rednetHeader .. connection.channel)
| |
| 1157 | else | |
| 1158 | connection:sendMessage(pageResposneHeader..'{["language"]="Lua"}'..header.pageResponseHeaderC..body, header.rednetHeader .. connection.channel)
| |
| 1159 | end | |
| 1160 | end | |
| 1161 | ||
| 1162 | local defaultNotFound = [[ | |
| 1163 | [br] | |
| 1164 | [br] | |
| 1165 | [=] | |
| 1166 | The page you requested could not be found! | |
| 1167 | [br] | |
| 1168 | Make sure you typed the URL correctly. | |
| 1169 | ]] | |
| 1170 | ||
| 1171 | local handlePageRequest = function(handler, index) | |
| 1172 | local connection = getActiveConnection(handler.channel, handler.dist) | |
| 1173 | if connection:verifyHeader(handler.message) then | |
| 1174 | local resp, data = connection:decryptMessage(handler.message) | |
| 1175 | if not resp then | |
| 1176 | -- Decryption Error | |
| 1177 | writeLog("Decryption error!", theme.notice, 0)
| |
| 1178 | else | |
| 1179 | -- Process Request | |
| 1180 | if data:match(pageRequestMatch) then | |
| 1181 | local page = data:match(pageRequestMatch) | |
| 1182 | if page == "" or page == "/" then | |
| 1183 | page = "index" | |
| 1184 | end | |
| 1185 | local body, head, isAPI = fetchPage(page) | |
| 1186 | if body then | |
| 1187 | sendPage(connection, tostring(body), head) | |
| 1188 | if isAPI then | |
| 1189 | writeLog("API request: "..page:sub(1,25), theme.text, 0)
| |
| 1190 | else | |
| 1191 | writeLog("Successful request: "..page:sub(1,20), theme.text, 1)
| |
| 1192 | end | |
| 1193 | else | |
| 1194 | body, head, isAPI = fetchPage("not-found")
| |
| 1195 | if body then | |
| 1196 | sendPage(connection, body, head) | |
| 1197 | else | |
| 1198 | sendPage(connection, defaultNotFound, "fwml") | |
| 1199 | end | |
| 1200 | writeLog("Unsuccessful request: "..page:sub(1,20), theme.text, 1)
| |
| 1201 | end | |
| 1202 | elseif data == closeMatch then | |
| 1203 | writeLog("Secure connection closed", theme.text, 0)
| |
| 1204 | Modem.close(connection.channel) | |
| 1205 | table.remove(connections, index) | |
| 1206 | end | |
| 1207 | end | |
| 1208 | end | |
| 1209 | end | |
| 1210 | ||
| 1211 | local handleHandshakeRequest = function(handler) | |
| 1212 | local requestData = handler.message:match(requestMatch) | |
| 1213 | if requestData and type(textutils.unserialize(requestData)) == "table" then | |
| 1214 | local receivedHandshake = textutils.unserialize(requestData) | |
| 1215 | local data, key = Handshake.generateResponseData(receivedHandshake) | |
| 1216 | if type(data) == "table" then | |
| 1217 | local connection | |
| 1218 | ||
| 1219 | if handler.type == "direct" then | |
| 1220 | connection = SecureConnection.new(key, domain, domain, handler.dist) | |
| 1221 | ||
| 1222 | Modem.transmit(serverChannel, responseHeader .. tostring(handler.dist) .. header.responseHeaderC .. textutils.serialize(data)) | |
| 1223 | else | |
| 1224 | connection = SecureConnection.new(key, domain, domain, handler.rednet_id, true) | |
| 1225 | ||
| 1226 | rednet.send(handler.rednet_id, responseHeader .. tostring(handler.rednet_id) .. header.responseHeaderC .. textutils.serialize(data), header.rednetHeader .. serverChannel) | |
| 1227 | end | |
| 1228 | ||
| 1229 | writeLog("Secure connection opened", theme.text, 0)
| |
| 1230 | ||
| 1231 | table.insert(connections, connection) | |
| 1232 | ||
| 1233 | if #connections >= 200 then | |
| 1234 | Modem.close(connections[1].channel) | |
| 1235 | table.remove(connections, 1) | |
| 1236 | end | |
| 1237 | end | |
| 1238 | elseif handler.message:match(maliciousMatch) then | |
| 1239 | -- Hijacking Detected | |
| 1240 | writeLog("Warning: Connection Hijacking Detected", theme.error, 2)
| |
| 1241 | end | |
| 1242 | end | |
| 1243 | ||
| 1244 | local responseDaemon = function() | |
| 1245 | while true do | |
| 1246 | os.pullEventRaw() | |
| 1247 | ||
| 1248 | for k, v in pairs(responseStack) do | |
| 1249 | if v.channel then | |
| 1250 | if v.channel == dnsListenChannel and v.message == header.dnsPacket then | |
| 1251 | -- DNS Request | |
| 1252 | if v.type == "rednet" then | |
| 1253 | rednet.send(v.rednet_id, header.dnsHeader .. domain, header.rednetHeader .. dnsResponseChannel) | |
| 1254 | else | |
| 1255 | Modem.open(dnsResponseChannel) | |
| 1256 | Modem.transmit(dnsResponseChannel, header.dnsHeader .. domain) | |
| 1257 | Modem.close(dnsResponseChannel) | |
| 1258 | end | |
| 1259 | elseif v.channel == serverChannel and v.message then | |
| 1260 | handleHandshakeRequest(v) | |
| 1261 | elseif getActiveConnection(v.channel, v.dist) then | |
| 1262 | handlePageRequest(v, k) | |
| 1263 | end | |
| 1264 | end | |
| 1265 | end | |
| 1266 | ||
| 1267 | responseStack = {}
| |
| 1268 | ||
| 1269 | if #repeatStack > 10 then | |
| 1270 | Modem.close(rednet.CHANNEL_REPEAT) | |
| 1271 | renableRednet = os.startTimer(2) | |
| 1272 | repeatStack = {}
| |
| 1273 | writeLog("Thorttling Rednet Connections", theme.notice, 2)
| |
| 1274 | end | |
| 1275 | ||
| 1276 | for k, v in pairs(repeatStack) do | |
| 1277 | if v.message.nMessageID and v.message.nRecipient then | |
| 1278 | if (not repeatedMessages[v.message.nMessageID]) or (os.clock() - repeatedMessages[v.message.nMessageID]) > 10 then | |
| 1279 | repeatedMessages[v.message.nMessageID] = os.clock() | |
| 1280 | for side, modem in pairs(Modem.modems) do | |
| 1281 | modem.transmit(rednet.CHANNEL_REPEAT, v.reply, v.message) | |
| 1282 | modem.transmit(v.message.nRecipient, v.reply, v.message) | |
| 1283 | end | |
| 1284 | end | |
| 1285 | end | |
| 1286 | end | |
| 1287 | ||
| 1288 | repeatStack = {}
| |
| 1289 | end | |
| 1290 | end | |
| 1291 | ||
| 1292 | -- Commands and Help | |
| 1293 | ||
| 1294 | local commands = {}
| |
| 1295 | local helpDocs = {}
| |
| 1296 | ||
| 1297 | commands["password"] = function(newPassword) | |
| 1298 | if not newPassword or newPassword == "" then | |
| 1299 | writeLog("Usage: password <new-password>", theme.userResponse, math.huge)
| |
| 1300 | else | |
| 1301 | config.password = newPassword | |
| 1302 | saveConfig() | |
| 1303 | writeLog("New password set!", theme.userResponse, math.huge)
| |
| 1304 | end | |
| 1305 | end | |
| 1306 | ||
| 1307 | commands["lock"] = function() | |
| 1308 | if config.password then | |
| 1309 | locked = true | |
| 1310 | writeLog("Server locked", theme.userResponse, math.huge)
| |
| 1311 | os.queueEvent("firewolf-lock-state-update")
| |
| 1312 | else | |
| 1313 | writeLog("No password has been set! Set a", theme.userResponse, math.huge)
| |
| 1314 | writeLog("password with: password <password>", theme.userResponse, math.huge)
| |
| 1315 | end | |
| 1316 | end | |
| 1317 | ||
| 1318 | commands["exit"] = function() | |
| 1319 | error("firewolf-exit")
| |
| 1320 | end | |
| 1321 | ||
| 1322 | commands["stop"] = commands["exit"] | |
| 1323 | ||
| 1324 | commands["quit"] = commands["exit"] | |
| 1325 | ||
| 1326 | commands["startup"] = function() | |
| 1327 | if fs.exists("/startup") then
| |
| 1328 | if fs.exists("/old-startup") then
| |
| 1329 | fs.delete("/old-startup")
| |
| 1330 | end | |
| 1331 | fs.move("/startup", "/old-startup")
| |
| 1332 | end | |
| 1333 | local f = io.open("/startup", "w")
| |
| 1334 | f:write([[ | |
| 1335 | sleep(0.1) | |
| 1336 | shell.run("]]..shell.getRunningProgram().." "..domain.."\")")
| |
| 1337 | f:close() | |
| 1338 | writeLog("Server will now run on startup!", theme.userResponse, math.huge)
| |
| 1339 | end | |
| 1340 | ||
| 1341 | commands["rednet"] = function(set) | |
| 1342 | if set == "on" then | |
| 1343 | config.allowRednetConnections = true | |
| 1344 | saveConfig() | |
| 1345 | writeLog("Now allowing rednet connections", theme.userResponse, math.huge)
| |
| 1346 | elseif set == "off" then | |
| 1347 | config.allowRednetConnections = false | |
| 1348 | saveConfig() | |
| 1349 | writeLog("Now dis-allowing rednet connections", theme.userResponse, math.huge)
| |
| 1350 | else | |
| 1351 | if config.allowRednetConnections then | |
| 1352 | writeLog("Rednet conn. are currently allowed", theme.userResponse, math.huge)
| |
| 1353 | else | |
| 1354 | writeLog("Rednet conn. are currently dis-allowed", theme.userResponse, math.huge)
| |
| 1355 | end | |
| 1356 | end | |
| 1357 | end | |
| 1358 | ||
| 1359 | commands["restart"] = function() | |
| 1360 | error("firewolf-restart")
| |
| 1361 | end | |
| 1362 | ||
| 1363 | commands["reboot"] = commands["restart"] | |
| 1364 | ||
| 1365 | commands["reload"] = function() | |
| 1366 | os.queueEvent(resetServerEvent) | |
| 1367 | end | |
| 1368 | ||
| 1369 | commands["refresh"] = commands["reload"] | |
| 1370 | ||
| 1371 | commands["repeat"] = function(set) | |
| 1372 | if set == "on" then | |
| 1373 | config.repeatRednetMessages = true | |
| 1374 | saveConfig() | |
| 1375 | writeLog("Rednet repeating is now on", theme.userResponse, math.huge)
| |
| 1376 | elseif set == "off" then | |
| 1377 | config.repeatRednetMessages = false | |
| 1378 | saveConfig() | |
| 1379 | writeLog("Rednet repeating is now off", theme.userResponse, math.huge)
| |
| 1380 | else | |
| 1381 | if config.repeatRednetMessages then | |
| 1382 | writeLog("Rednet repeating is currently turned on", theme.userResponse, math.huge)
| |
| 1383 | else | |
| 1384 | writeLog("Rednet repeating is currently turned off", theme.userResponse, math.huge)
| |
| 1385 | end | |
| 1386 | end | |
| 1387 | end | |
| 1388 | ||
| 1389 | commands["update"] = function() | |
| 1390 | term.setCursorPos(1, h) | |
| 1391 | term.clearLine() | |
| 1392 | term.setTextColor(theme.userResponse) | |
| 1393 | term.setCursorBlink(false) | |
| 1394 | term.write("Updating...")
| |
| 1395 | local handle = http.get(updateURL) | |
| 1396 | if not handle then | |
| 1397 | writeLog("Failed to connect to update server!", theme.error, math.huge)
| |
| 1398 | else | |
| 1399 | data = handle.readAll() | |
| 1400 | if #data < 1000 then | |
| 1401 | writeLog("Failed to update server!", theme.error, math.huge)
| |
| 1402 | else | |
| 1403 | local f = io.open("/"..shell.getRunningProgram(), "w")
| |
| 1404 | f:write(data) | |
| 1405 | f:close() | |
| 1406 | error("firewolf-restart")
| |
| 1407 | end | |
| 1408 | end | |
| 1409 | end | |
| 1410 | ||
| 1411 | commands["edit"] = function() | |
| 1412 | writeLog("Editing server files", theme.userResponse, math.huge)
| |
| 1413 | term.setBackgroundColor(colors.black) | |
| 1414 | if term.isColor() then | |
| 1415 | term.setTextColor(colors.yellow) | |
| 1416 | else | |
| 1417 | term.setTextColor(colors.white) | |
| 1418 | end | |
| 1419 | term.clear() | |
| 1420 | term.setCursorPos(1, 1) | |
| 1421 | print("Use exit to finish editing")
| |
| 1422 | shell.setDir(serverLocation .. "/" .. domain) | |
| 1423 | shell.run("/rom/programs/shell")
| |
| 1424 | os.queueEvent(resetServerEvent) | |
| 1425 | end | |
| 1426 | ||
| 1427 | commands["clear"] = function() | |
| 1428 | terminalLog = {}
| |
| 1429 | term.clear() | |
| 1430 | os.queueEvent("firewolf-lock-state-update")
| |
| 1431 | end | |
| 1432 | ||
| 1433 | helpDocs["password"] = {"Change the lock password", "Usage: password <new-password>"}
| |
| 1434 | helpDocs["lock"] = {"Lock the server with a password"}
| |
| 1435 | helpDocs["exit"] = {"Exits and stops Firewolf Server"}
| |
| 1436 | helpDocs["quit"] = helpDocs["exit"] | |
| 1437 | helpDocs["stop"] = helpDocs["exit"] | |
| 1438 | helpDocs["restart"] = {"Fully restarts Firewolf Server"}
| |
| 1439 | helpDocs["reboot"] = helpDocs["restart"] | |
| 1440 | helpDocs["reload"] = {"Reloads the server and Server API"}
| |
| 1441 | helpDocs["refresh"] = helpDocs["reload"] | |
| 1442 | helpDocs["clear"] = {"Clears the displayed log"}
| |
| 1443 | helpDocs["rednet"] = {"Whether to allow rednet connections", "Usage: rednet <on or off>"}
| |
| 1444 | helpDocs["startup"] = {"Runs the server for the current domain", "on startup"}
| |
| 1445 | helpDocs["repeat"] = {"Whether to repeat rednet messages", "Usage: repeat <on or off>"}
| |
| 1446 | helpDocs["update"] = {"Updates Firewolf Server"}
| |
| 1447 | helpDocs["edit"] = {"Opens shell in server directory"}
| |
| 1448 | ||
| 1449 | commands["help"] = function(command) | |
| 1450 | if command then | |
| 1451 | if helpDocs[command] then | |
| 1452 | for _, v in pairs(helpDocs[command]) do | |
| 1453 | writeLog(v, theme.userResponse, math.huge) | |
| 1454 | end | |
| 1455 | else | |
| 1456 | writeLog("Command does not exist!", theme.userResponse, math.huge)
| |
| 1457 | end | |
| 1458 | else | |
| 1459 | writeLog("Use \"help <command>\" for more info", theme.userResponse, math.huge)
| |
| 1460 | writeLog("Wiki: http://bit.ly/firewolf-wiki", theme.userResponse, math.huge)
| |
| 1461 | writeLog("Commands: password, lock, exit, update,", theme.userResponse, math.huge)
| |
| 1462 | writeLog("restart, clear, rednet, repeat, startup,", theme.userResponse, math.huge)
| |
| 1463 | writeLog("edit, reload", theme.userResponse, math.huge)
| |
| 1464 | end | |
| 1465 | end | |
| 1466 | ||
| 1467 | -- Display manager | |
| 1468 | ||
| 1469 | local enteredText = "" | |
| 1470 | local history = {}
| |
| 1471 | local scrollingHistory = false | |
| 1472 | local cursorPosition = 1 | |
| 1473 | local offsetPosition = 1 | |
| 1474 | local inputName = "> " | |
| 1475 | local lockTimer = 0 | |
| 1476 | local lockedInputState = false | |
| 1477 | ||
| 1478 | local lockArt = [[ | |
| 1479 | #### | |
| 1480 | # # | |
| 1481 | # # | |
| 1482 | ######## | |
| 1483 | -------- | |
| 1484 | ######## | |
| 1485 | ######## | |
| 1486 | ||
| 1487 | [LOCKED] | |
| 1488 | ]] | |
| 1489 | ||
| 1490 | local drawBar = function() | |
| 1491 | term.setTextColor(theme.barText) | |
| 1492 | term.setBackgroundColor(theme.barColor) | |
| 1493 | term.setCursorPos(1,1) | |
| 1494 | term.clearLine() | |
| 1495 | term.write(" "..version)
| |
| 1496 | center("["..domain.."]")
| |
| 1497 | local time = textutils.formatTime(os.time(), true) | |
| 1498 | term.setCursorPos(w - #time, 1) | |
| 1499 | term.setTextColor(theme.clock) | |
| 1500 | term.write(time) | |
| 1501 | end | |
| 1502 | ||
| 1503 | local drawLogs = function() | |
| 1504 | if locked and lastLogNum >= 0 then | |
| 1505 | term.setBackgroundColor(theme.background) | |
| 1506 | term.clear() | |
| 1507 | lastLogNum = -1 | |
| 1508 | local lockX = math.ceil((w/2) - 4) | |
| 1509 | local lineNum = 4 | |
| 1510 | term.setTextColor(theme.lock) | |
| 1511 | for line in lockArt:gmatch("[^\n]+") do
| |
| 1512 | term.setCursorPos(lockX, lineNum) | |
| 1513 | term.write(line) | |
| 1514 | lineNum = lineNum + 1 | |
| 1515 | end | |
| 1516 | inputName = "Password: " | |
| 1517 | elseif not locked and lastLogNum ~= #terminalLog then | |
| 1518 | term.setBackgroundColor(theme.background) | |
| 1519 | term.clear() | |
| 1520 | lastLogNum = #terminalLog | |
| 1521 | lockedInputState = false | |
| 1522 | if #terminalLog < h - 1 then | |
| 1523 | term.setCursorPos(1, 2) | |
| 1524 | for i = 1, #terminalLog do | |
| 1525 | term.setTextColor(theme.clock) | |
| 1526 | term.write(terminalLog[i][3]) | |
| 1527 | term.setTextColor(terminalLog[i][2]) | |
| 1528 | term.write(terminalLog[i][1]) | |
| 1529 | term.setCursorPos(1, i + 2) | |
| 1530 | end | |
| 1531 | else | |
| 1532 | term.setCursorPos(1, 2) | |
| 1533 | for i = 1, h - 1 do | |
| 1534 | term.setTextColor(theme.clock) | |
| 1535 | term.write(terminalLog[#terminalLog - (h - 1) + i][3]) | |
| 1536 | term.setTextColor(terminalLog[#terminalLog - (h - 1) + i][2]) | |
| 1537 | term.write(terminalLog[#terminalLog - (h - 1) + i][1]) | |
| 1538 | term.setCursorPos(1, i + 1) | |
| 1539 | end | |
| 1540 | end | |
| 1541 | end | |
| 1542 | end | |
| 1543 | ||
| 1544 | local drawInputBar = function() | |
| 1545 | term.setCursorPos(1, h) | |
| 1546 | term.setBackgroundColor(theme.background) | |
| 1547 | term.clearLine() | |
| 1548 | ||
| 1549 | if lockedInputState then | |
| 1550 | term.setCursorBlink(false) | |
| 1551 | term.setTextColor(theme.error) | |
| 1552 | term.write("Incorrect Password!")
| |
| 1553 | else | |
| 1554 | term.setCursorBlink(true) | |
| 1555 | term.setTextColor(theme.inputColor) | |
| 1556 | term.write(inputName) | |
| 1557 | width = w - #inputName | |
| 1558 | if locked then | |
| 1559 | term.write(string.rep("*", (#enteredText:sub(offsetPosition, offsetPosition + width))))
| |
| 1560 | else | |
| 1561 | term.write(enteredText:sub(offsetPosition, offsetPosition + width)) | |
| 1562 | end | |
| 1563 | end | |
| 1564 | end | |
| 1565 | ||
| 1566 | local handleKeyEvents = function(event, key) | |
| 1567 | if key == 14 then | |
| 1568 | if enteredText ~= "" then | |
| 1569 | enteredText = enteredText:sub(1, cursorPosition - 2) .. enteredText:sub(cursorPosition, -1) | |
| 1570 | cursorPosition = cursorPosition-1 | |
| 1571 | if cursorPosition >= width then | |
| 1572 | offsetPosition = offsetPosition - 1 | |
| 1573 | end | |
| 1574 | end | |
| 1575 | elseif key == 28 and enteredText ~= "" and locked then | |
| 1576 | if enteredText == config.password then | |
| 1577 | writeLog("Successful login", theme.userResponse, math.huge)
| |
| 1578 | inputName = "> " | |
| 1579 | locked = false | |
| 1580 | os.queueEvent("firewolf-lock-state-update")
| |
| 1581 | else | |
| 1582 | writeLog("Failed login attempt", theme.userResponse, math.huge)
| |
| 1583 | lockedInputState = true | |
| 1584 | lockTimer = os.startTimer(2) | |
| 1585 | os.queueEvent("firewolf-lock-state-update")
| |
| 1586 | end | |
| 1587 | enteredText = "" | |
| 1588 | cursorPosition = 1 | |
| 1589 | offsetPosition = 1 | |
| 1590 | elseif key == 28 and enteredText ~= "" and not locked then | |
| 1591 | local commandWord = false | |
| 1592 | local arguments = {}
| |
| 1593 | for word in enteredText:gmatch("%S+") do
| |
| 1594 | if not commandWord then | |
| 1595 | commandWord = word | |
| 1596 | else | |
| 1597 | table.insert(arguments, word) | |
| 1598 | end | |
| 1599 | end | |
| 1600 | if commands[commandWord] then | |
| 1601 | local err, msg = pcall(commands[commandWord], unpack(arguments)) | |
| 1602 | if not err and msg:find("firewolf-exit", nil, true) then
| |
| 1603 | error("firewolf-exit")
| |
| 1604 | elseif not err and msg:find("firewolf-restart", nil, true) then
| |
| 1605 | error("firewolf-restart")
| |
| 1606 | elseif not err then | |
| 1607 | writeLog("An error occured when executing command", theme.error, math.huge)
| |
| 1608 | writeError(tostring(msg)) | |
| 1609 | end | |
| 1610 | else | |
| 1611 | writeLog("No such command!", theme.error, math.huge)
| |
| 1612 | end | |
| 1613 | table.insert(history, enteredText) | |
| 1614 | enteredText = "" | |
| 1615 | offsetPosition = 1 | |
| 1616 | cursorPosition = 1 | |
| 1617 | elseif key == 203 then | |
| 1618 | cursorPosition = cursorPosition - 1 | |
| 1619 | if cursorPosition < 1 then | |
| 1620 | cursorPosition = 1 | |
| 1621 | end | |
| 1622 | if cursorPosition >= width then | |
| 1623 | offsetPosition = offsetPosition - 1 | |
| 1624 | end | |
| 1625 | elseif key == 205 then | |
| 1626 | cursorPosition = cursorPosition + 1 | |
| 1627 | if cursorPosition > #enteredText + 1 then | |
| 1628 | cursorPosition = #enteredText + 1 | |
| 1629 | end | |
| 1630 | if cursorPosition - offsetPosition >= width then | |
| 1631 | offsetPosition = offsetPosition + 1 | |
| 1632 | end | |
| 1633 | elseif key == 208 and #history > 0 then | |
| 1634 | if type(scrollingHistory) == "number" then | |
| 1635 | scrollingHistory = scrollingHistory - 1 | |
| 1636 | if scrollingHistory > 0 then | |
| 1637 | enteredText = history[#history - scrollingHistory + 1] | |
| 1638 | cursorPosition = #enteredText + 1 | |
| 1639 | else | |
| 1640 | scrollingHistory = false | |
| 1641 | enteredText = "" | |
| 1642 | cursorPosition = 1 | |
| 1643 | end | |
| 1644 | end | |
| 1645 | elseif key == 200 and #history > 0 then | |
| 1646 | if type(scrollingHistory) == "number" then | |
| 1647 | scrollingHistory = scrollingHistory + 1 | |
| 1648 | if scrollingHistory > #history then | |
| 1649 | scrollingHistory = #history | |
| 1650 | end | |
| 1651 | enteredText = history[#history - scrollingHistory + 1] | |
| 1652 | cursorPosition = #enteredText + 1 | |
| 1653 | if cursorPosition > width then | |
| 1654 | cursorPosition = 1 | |
| 1655 | end | |
| 1656 | else | |
| 1657 | scrollingHistory = 1 | |
| 1658 | enteredText = history[#history - scrollingHistory + 1] | |
| 1659 | cursorPosition = #enteredText + 1 | |
| 1660 | if cursorPosition > width then | |
| 1661 | cursorPosition = 1 | |
| 1662 | end | |
| 1663 | end | |
| 1664 | end | |
| 1665 | end | |
| 1666 | ||
| 1667 | ||
| 1668 | local terminalDaemon = function() | |
| 1669 | local timer = os.startTimer(1) | |
| 1670 | local lastTime = os.clock() | |
| 1671 | ||
| 1672 | while true do | |
| 1673 | drawLogs() | |
| 1674 | drawBar() | |
| 1675 | drawInputBar() | |
| 1676 | ||
| 1677 | term.setCursorPos(cursorPosition - offsetPosition + #inputName + 1, h) | |
| 1678 | ||
| 1679 | local event, key = os.pullEventRaw() | |
| 1680 | if event == "char" and not lockedInputState then | |
| 1681 | enteredText = enteredText:sub(1, cursorPosition-1) .. key .. enteredText:sub(cursorPosition, -1) | |
| 1682 | cursorPosition = cursorPosition + 1 | |
| 1683 | if cursorPosition - offsetPosition >= width then | |
| 1684 | offsetPosition = offsetPosition + 1 | |
| 1685 | end | |
| 1686 | elseif event == "key" and not lockedInputState then | |
| 1687 | handleKeyEvents(event, key) | |
| 1688 | elseif event == "timer" and key == timer then | |
| 1689 | timer = os.startTimer(1) | |
| 1690 | lastTime = os.clock() | |
| 1691 | elseif event == "timer" and key == lockTimer then | |
| 1692 | lockedInputState = false | |
| 1693 | elseif event == "terminate" and not locked then | |
| 1694 | error("firewolf-exit")
| |
| 1695 | end | |
| 1696 | ||
| 1697 | if (os.clock() - lastTime) > 1 then | |
| 1698 | timer = os.startTimer(1) | |
| 1699 | lastTime = os.clock() | |
| 1700 | end | |
| 1701 | end | |
| 1702 | end | |
| 1703 | ||
| 1704 | -- Coroutine manager | |
| 1705 | ||
| 1706 | local receiveThread, responseThread, terminalThread | |
| 1707 | ||
| 1708 | local loadServerAPI = function() | |
| 1709 | if fs.exists(serverLocation .. "/" .. domain .. "/" .. serverAPILocation) and not fs.isDir(serverLocation .. "/" .. domain .. "/" .. serverAPILocation) then | |
| 1710 | local f = io.open(serverLocation .. "/" .. domain .. "/" .. serverAPILocation, "r") | |
| 1711 | local apiData = f:read("*a")
| |
| 1712 | f:close() | |
| 1713 | ||
| 1714 | customCoroutines = {}
| |
| 1715 | ||
| 1716 | local apiFunction, err = loadstring(apiData) | |
| 1717 | if not apiFunction then | |
| 1718 | writeLog("Error while loading server API", theme.error, math.huge)
| |
| 1719 | if err:match("%[string \"string\"%](.+)") then
| |
| 1720 | writeLog("server_api" .. err:match("%[string \"string\"%](.+)"), theme.error, math.huge)
| |
| 1721 | else | |
| 1722 | writeLog(err, theme.error, math.huge) | |
| 1723 | end | |
| 1724 | else | |
| 1725 | local global = getfenv(0) | |
| 1726 | local env = {}
| |
| 1727 | ||
| 1728 | for k,v in pairs(global) do | |
| 1729 | env[k] = v | |
| 1730 | end | |
| 1731 | ||
| 1732 | env["server"] = {}
| |
| 1733 | ||
| 1734 | env["server"]["domain"] = domain | |
| 1735 | ||
| 1736 | env["server"]["modem"] = Modem | |
| 1737 | ||
| 1738 | env["server"]["handleRequest"] = function(index, func) | |
| 1739 | if not(index and func and type(index) == "string" and type(func) == "function") then | |
| 1740 | return error("index (string) and handler (function) expected")
| |
| 1741 | elseif #index:gsub("/", "") == 0 then
| |
| 1742 | return error("invalid index")
| |
| 1743 | end | |
| 1744 | if index == "/" then | |
| 1745 | globalHandler = func | |
| 1746 | return | |
| 1747 | end | |
| 1748 | if index:sub(1, 1) == "/" then index = index:sub(2, -1) end | |
| 1749 | if index:sub(-1, -1) == "/" then index = index:sub(1, -2) end | |
| 1750 | if index:find(":") then
| |
| 1751 | return error("Handle index cannot contain \":\"s")
| |
| 1752 | end | |
| 1753 | handles[index] = func | |
| 1754 | end | |
| 1755 | ||
| 1756 | env["server"]["runCoroutine"] = function(func) | |
| 1757 | local newThread = coroutine.create(func) | |
| 1758 | local err, msg = coroutine.resume(newThread) | |
| 1759 | if not err then | |
| 1760 | return error(msg) | |
| 1761 | end | |
| 1762 | ||
| 1763 | table.insert(customCoroutines, newThread) | |
| 1764 | end | |
| 1765 | ||
| 1766 | env["server"]["applyTemplate"] = function(variables, template) | |
| 1767 | local result | |
| 1768 | if fs.exists(template) and not fs.isDir(template) then | |
| 1769 | local f = io.open(template, "r") | |
| 1770 | result = f:read("*a")
| |
| 1771 | f:close() | |
| 1772 | else | |
| 1773 | writeLog("Template file \"" .. template .. "\" does not exist!", theme.error, math.huge)
| |
| 1774 | writeLog("Template locations are relative to / (root)", theme.error, math.huge)
| |
| 1775 | return false | |
| 1776 | end | |
| 1777 | for k,v in pairs(variables) do | |
| 1778 | result = result:gsub("{{"..Cryptography.sanatize(k).."}}", Cryptography.sanatize(v))
| |
| 1779 | end | |
| 1780 | return result | |
| 1781 | end | |
| 1782 | ||
| 1783 | env["server"]["log"] = function(text) | |
| 1784 | writeLog(text, theme.notice, math.huge) | |
| 1785 | end | |
| 1786 | ||
| 1787 | setfenv(apiFunction, env) | |
| 1788 | local err, msg = pcall(apiFunction) | |
| 1789 | ||
| 1790 | if not err then | |
| 1791 | writeLog("Error while executing server API", theme.error, math.huge)
| |
| 1792 | writeError(tostring(msg)) | |
| 1793 | else | |
| 1794 | writeLog("Server API loaded", theme.notice, math.huge)
| |
| 1795 | end | |
| 1796 | end | |
| 1797 | end | |
| 1798 | end | |
| 1799 | ||
| 1800 | local function resetServer() | |
| 1801 | connections = {}
| |
| 1802 | repeatStack = {}
| |
| 1803 | responseStack = {}
| |
| 1804 | Modem.closeAll() | |
| 1805 | Modem.open(serverChannel) | |
| 1806 | Modem.open(dnsListenChannel) | |
| 1807 | if config.repeatRednetMessages then | |
| 1808 | Modem.open(rednet.CHANNEL_REPEAT) | |
| 1809 | end | |
| 1810 | ||
| 1811 | loadServerAPI() | |
| 1812 | ||
| 1813 | responseThread = coroutine.create(function() responseDaemon() end) | |
| 1814 | receiveThread = coroutine.create(receiveDaemon) | |
| 1815 | coroutine.resume(responseThread) | |
| 1816 | coroutine.resume(receiveThread) | |
| 1817 | writeLog("The server has been reloaded", theme.userResponse, math.huge)
| |
| 1818 | ||
| 1819 | os.queueEvent("firewolf-lock-state-update")
| |
| 1820 | end | |
| 1821 | ||
| 1822 | local function runThreads() | |
| 1823 | while true do | |
| 1824 | local shouldResetServer = false | |
| 1825 | local events = {os.pullEventRaw()}
| |
| 1826 | ||
| 1827 | err, msg = coroutine.resume(receiveThread, unpack(events)) | |
| 1828 | if not err then | |
| 1829 | writeLog("Internal error!", theme.error, math.huge)
| |
| 1830 | writeError(tostring(msg)) | |
| 1831 | shouldResetServer = true | |
| 1832 | end | |
| 1833 | ||
| 1834 | err, msg = coroutine.resume(responseThread) | |
| 1835 | if not err then | |
| 1836 | writeLog("Internal error!", theme.error, math.huge)
| |
| 1837 | writeError(tostring(msg)) | |
| 1838 | shouldResetServer = true | |
| 1839 | end | |
| 1840 | ||
| 1841 | err, msg = coroutine.resume(terminalThread, unpack(events)) | |
| 1842 | if not err and msg:find("firewolf-exit", nil, true) then
| |
| 1843 | writeLog("Normal exit", theme.text, math.huge)
| |
| 1844 | Modem.closeAll() | |
| 1845 | shell.setDir("")
| |
| 1846 | term.setBackgroundColor(colors.black) | |
| 1847 | term.clear() | |
| 1848 | term.setCursorPos(1, 1) | |
| 1849 | term.setTextColor(colors.white) | |
| 1850 | center("Thank you for using Firewolf Server")
| |
| 1851 | center("Made by 1lann and GravityScore")
| |
| 1852 | return | |
| 1853 | elseif not err and msg:find("firewolf-restart", nil, true) then
| |
| 1854 | writeLog("Firewolf server restarting...", theme.text, math.huge)
| |
| 1855 | term.clear() | |
| 1856 | error("firewolf-restart")
| |
| 1857 | elseif not err then | |
| 1858 | term.clear() | |
| 1859 | term.setCursorPos(1, 1) | |
| 1860 | error("Restart required error: "..msg)
| |
| 1861 | end | |
| 1862 | ||
| 1863 | for k,v in pairs(customCoroutines) do | |
| 1864 | if coroutine.status(v) ~= "dead" then | |
| 1865 | local err, msg = coroutine.resume(v, unpack(events)) | |
| 1866 | if not err then | |
| 1867 | writeLog("Server API coroutine error!", theme.error, math.huge)
| |
| 1868 | writeError(tostring(msg)) | |
| 1869 | shouldResetServer = true | |
| 1870 | end | |
| 1871 | end | |
| 1872 | end | |
| 1873 | ||
| 1874 | if events[1] == resetServerEvent or shouldResetServer then | |
| 1875 | resetServer() | |
| 1876 | end | |
| 1877 | end | |
| 1878 | end | |
| 1879 | ||
| 1880 | local runOnDomain = function() | |
| 1881 | serverChannel = Cryptography.channel(domain) | |
| 1882 | requestMatch = header.requestMatchA .. Cryptography.sanatize(domain) .. header.requestMatchB | |
| 1883 | responseHeader = header.responseHeaderA .. domain .. header.responseHeaderB | |
| 1884 | pageRequestMatch = header.pageRequestMatchA .. Cryptography.sanatize(domain) .. header.pageRequestMatchB | |
| 1885 | pageResposneHeader = header.pageResponseHeaderA .. domain .. header.pageResponseHeaderB | |
| 1886 | closeMatch = header.closeMatchA .. domain .. header.closeMatchB | |
| 1887 | maliciousMatch = header.maliciousMatchA .. Cryptography.sanatize(domain) .. header.maliciousMatchB | |
| 1888 | ||
| 1889 | Modem.open(serverChannel) | |
| 1890 | Modem.open(dnsListenChannel) | |
| 1891 | ||
| 1892 | if config.repeatRednetMessages then | |
| 1893 | Modem.open(rednet.CHANNEL_REPEAT) | |
| 1894 | end | |
| 1895 | ||
| 1896 | writeLog("Firewolf Server "..version.." running" , theme.notice, math.huge)
| |
| 1897 | ||
| 1898 | loadServerAPI() | |
| 1899 | ||
| 1900 | receiveThread = coroutine.create(receiveDaemon) | |
| 1901 | responseThread = coroutine.create(responseDaemon) | |
| 1902 | terminalThread = coroutine.create(terminalDaemon) | |
| 1903 | ||
| 1904 | coroutine.resume(receiveThread) | |
| 1905 | coroutine.resume(responseThread) | |
| 1906 | coroutine.resume(terminalThread) | |
| 1907 | ||
| 1908 | runThreads() | |
| 1909 | end | |
| 1910 | ||
| 1911 | -- Server initialisation | |
| 1912 | ||
| 1913 | local init = function() | |
| 1914 | Modem.closeAll() | |
| 1915 | term.setBackgroundColor(theme.background) | |
| 1916 | term.setTextColor(theme.notice) | |
| 1917 | term.clear() | |
| 1918 | term.setCursorPos(1,1) | |
| 1919 | term.setCursorBlink(false) | |
| 1920 | term.setTextColor(theme.text) | |
| 1921 | ||
| 1922 | if not fs.exists(serverLocation .. "/" .. domain) then | |
| 1923 | makeDirectory(serverLocation .. "/" .. domain) | |
| 1924 | else | |
| 1925 | if not fs.isDir(serverLocation .. "/" .. domain) then | |
| 1926 | fs.delete(serverLocation .. "/" .. domain) | |
| 1927 | end | |
| 1928 | makeDirectory(serverLocation .. "/" .. domain) | |
| 1929 | end | |
| 1930 | ||
| 1931 | local report = checkConfig() | |
| 1932 | if report then | |
| 1933 | term.setBackgroundColor(colors.black) | |
| 1934 | term.setTextColor(theme.error) | |
| 1935 | term.clear() | |
| 1936 | term.setCursorPos(1, 1) | |
| 1937 | print("There was an error loading your config file")
| |
| 1938 | print("The config file is located at: "..configLocation)
| |
| 1939 | print("-------------------------------------------------")
| |
| 1940 | for k,v in pairs(report) do | |
| 1941 | print(v) | |
| 1942 | end | |
| 1943 | return | |
| 1944 | end | |
| 1945 | ||
| 1946 | if config.password then | |
| 1947 | locked = true | |
| 1948 | end | |
| 1949 | ||
| 1950 | local err, msg = pcall(function()runOnDomain()end) | |
| 1951 | if not err and msg:find("firewolf-restart", nil, true) then
| |
| 1952 | term.clear() | |
| 1953 | term.setCursorPos(1, 1) | |
| 1954 | return shell.run("/"..shell.getRunningProgram(), domain)
| |
| 1955 | elseif not err then | |
| 1956 | term.setBackgroundColor(colors.black) | |
| 1957 | term.clear() | |
| 1958 | term.setCursorPos(1, 1) | |
| 1959 | term.setTextColor(colors.red) | |
| 1960 | print("Sorry, Firewolf Server has crashed! Error:")
| |
| 1961 | print(msg) | |
| 1962 | print("Firewolf Server will reboot in 3 seconds...")
| |
| 1963 | sleep(3) | |
| 1964 | return shell.run("/"..shell.getRunningProgram(), domain)
| |
| 1965 | end | |
| 1966 | end | |
| 1967 | ||
| 1968 | if pocket or turtle then | |
| 1969 | term.setTextColor(theme.error) | |
| 1970 | print("Sorry, Firewolf Server can")
| |
| 1971 | print("only be ran on computers.")
| |
| 1972 | return | |
| 1973 | end | |
| 1974 | ||
| 1975 | if not Modem.exists() then | |
| 1976 | term.setTextColor(theme.error) | |
| 1977 | print("Error: No modems found!")
| |
| 1978 | return | |
| 1979 | end | |
| 1980 | ||
| 1981 | if fs.isDir(configLocation) then | |
| 1982 | fs.delete(configLocation) | |
| 1983 | end | |
| 1984 | ||
| 1985 | if not fs.exists(configLocation) then | |
| 1986 | saveConfig() | |
| 1987 | end | |
| 1988 | ||
| 1989 | loadConfig() | |
| 1990 | ||
| 1991 | if not domain and config.lastDomain then | |
| 1992 | domain = config.lastDomain | |
| 1993 | end | |
| 1994 | ||
| 1995 | if domain then | |
| 1996 | if term.isColor() then | |
| 1997 | term.setTextColor(colors.yellow) | |
| 1998 | else | |
| 1999 | term.setTextColor(colors.white) | |
| 2000 | end | |
| 2001 | term.setCursorBlink(false) | |
| 2002 | print("Initializing Firewolf Server...")
| |
| 2003 | local report = checkDomain(domain) | |
| 2004 | term.setTextColor(theme.error) | |
| 2005 | if report == "symbols" then | |
| 2006 | print("Domain cannot contain \":\", \"/\" and \"?\"s")
| |
| 2007 | elseif report == "short" then | |
| 2008 | print("Domain name too short!")
| |
| 2009 | - | elseif report == "taken" then |
| 2009 | + | |
| 2010 | - | print("Domain already taken!")
|
| 2010 | + | |
| 2011 | saveConfig() | |
| 2012 | init() | |
| 2013 | end | |
| 2014 | else | |
| 2015 | term.setTextColor(colors.white) | |
| 2016 | print("Usage: "..shell.getRunningProgram().." <domain>")
| |
| 2017 | end |