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 |