From 60335b238ad75bbea7e088d16e1717556dbb182a Mon Sep 17 00:00:00 2001 From: Test_User Date: Sun, 12 Jun 2022 12:42:18 -0400 Subject: nukes --- CoupServ.lua | 170 +++++++++++++++++++++++++ CoupServ.py | 140 ++++++++++++++++++--- commands.lua | 396 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ network.lua | 175 ++++++++++++++++++++++++++ stdin.lua | 114 +++++++++++++++++ 5 files changed, 981 insertions(+), 14 deletions(-) create mode 100755 CoupServ.lua create mode 100644 commands.lua create mode 100644 network.lua create mode 100644 stdin.lua diff --git a/CoupServ.lua b/CoupServ.lua new file mode 100755 index 0000000..1da6281 --- /dev/null +++ b/CoupServ.lua @@ -0,0 +1,170 @@ +#!/usr/bin/env lua +--[[ + +Main program for HaxServ, a pseudoserver for inspircd3. + +Written by: Test_User + +This is free and unencumbered software released into the public +domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +]] + +path = arg[0]:sub(1, -arg[0]:reverse():find('/')) + +socket = require("socket") +json = require("json") +ssl = require("ssl") + +servlist = {} +userlist = {} +chanlist = {} + +function has_permission(user, privs) + privs = privs or {} + for _, v in pairs(privs) do + if v == "Admin" then + if user.opertype ~= "Admin" then + return false + end + elseif v == "Owner" then -- not dealing with this yet, so just always return false for now + return false + else -- unknown priv, naturally no one has it + return false + end + end + return true +end + +config_file = io.open(path.."CoupServConfig.json", "r+") +config = json.decode(config_file:read("*a")) + +for file, _ in pairs(config.files) do + dofile(path..file) +end + +stdin = socket.tcp() +stdin:close() +stdin:setfd(0) --clearly a hack but it works + +while true do + servlist = {} + userlist = {} + chanlist = {} + + local s = socket.tcp() + s:connect("irc.andrewyu.org", 7005) + local con = ssl.wrap(s, {mode = "client", protocol = "tlsv1_3"}) + + print(con:dohandshake()) + + con:send("SERVER hax.irc.andrewyu.org "..config.send_password.." 0 1HC :HaxServ\n") + con:send("BURST "..tostring(os.time()).."\n") + con:send("UID 1HC000000 "..tostring(os.time()).." "..config.nick.." hax.irc.andrewyu.org "..config.hostmask.." HaxServ 192.168.1.1 "..tostring(os.time()).." +k :HaxServ\n") + con:send(":1HC000000 OPERTYPE Admin\n") + for channel, _ in pairs(config.channels) do + con:send("FJOIN "..channel.." "..tostring(os.time()).." + :,1HC000000\n") + con:send("MODE "..channel.." +o 1HC000000\n") + end + con:send("ENDBURST\n") + + local proceed = true + while proceed do + ready, _, err = socket.select({con, stdin}, {}, 300) + if err then + con:send("") -- hack to make it properly timeout due to annoying bugs + end + + for _, sock in ipairs(ready) do + if sock == con then + local msg, err = con:receive() + + local original = msg + + if err then + proceed = false + break + end + + local source = nil + if msg:sub(1, 1) == ":" then + source = msg:sub(2):match("^[^ ]*") + msg = msg:sub(string.len(source) + 3) -- 1 for the leading ':', 1 for the trailing ' ', and 1 for the offset + end + + local lastarg = msg:match(" :.*") + if lastarg ~= nil then + msg = msg:sub(1, -string.len(lastarg) - 1) -- only offset + lastarg = lastarg:sub(3) + end + + local args = {} + for arg in msg:gmatch("[^ ]*") do + table.insert(args, arg) + end + + local command = args[1] + table.remove(args, 1) + + if lastarg ~= nil then + table.insert(args, lastarg) + end + + if message_handler[command] then + local success, stop = pcall(message_handler[command], con, source, args, original) + if success and stop then + proceed = false + break + elseif not success then + print(stop) + end + else + print("Unhandled command:", ("%q"):format(original)) + end + elseif sock == stdin then + msg = io.stdin:read() + local args = {} + for arg in msg:gmatch("[^ ]*") do + table.insert(args, arg) + end + local command = args[1]:upper() + table.remove(args, 1) + + if stdin_commands[command] then + local success, err = pcall(stdin_commands[command], con, msg, args) + if not success then + print(err) + elseif err then + proceed = false + break + end + else + print("Unknown command.") + end + end + end + if err then break end + end + + con:close() +end diff --git a/CoupServ.py b/CoupServ.py index 51780fe..64a9f6d 100755 --- a/CoupServ.py +++ b/CoupServ.py @@ -27,6 +27,71 @@ except FileNotFoundError: print("No config found! Please make one then try again") exit() +rickroll = """We're no strangers to love +You know the rules, and so do I +A full commitment is what I'm thinking of +You wouldn't get this from any other guy + +I just wanna tell you how I'm feeling +Gotta make you, understand + +Never gonna give you up +Never gonna let you down +Never gonna run around and desert you + +Never gonna make you cry +Never gonna say goodbye +Never gonna tell a lie, and hurt you + +We've known each other, for so long +Your heart's been aching but, you're too shy to say it +Inside we both know what's been going on +We know the game and we're gonna play it + +And if you ask me how I'm feeling +Don't tell me you're too hot to see + +Never gonna give you up +Never gonna let you down +Never gonna run around, and desert you + +Never gonna make you cry +Never gonna say goodbye +never gonna tell a lie, and hurt you + +Never gonna give you up +Never gonna let you down +Never gonna run around, and desert you + +Never gonna make you cry +Never gonna say goodbye +never gonna tell a lie, and hurt you + +Give you up +Never gonna give, never gonna give +Give you up +Never gonna give, never gonna give + +We've known each other, for so long +Your heart's been aching but, you're too shy to say it +Inside we both know what's been going on +We know the game and we're gonna play it + +I just wanna tell you how I'm feelin +Gotta make you, understand +Never gonna give you up +Never gonna let you down +Never gonna run around, and desert you +Never gonna make you cry +Never gonna say goodbye +never gonna tell a lie, and hurt you +Never gonna give you up +Never gonna let you down +Never gonna run around, and desert you +Never gonna make you cry +Never gonna say goodbye +never gonna tell a lie, and hurt you""" + s=ssl.wrap_socket(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) s.connect(("irc.andrewyu.org", 7005)) @@ -52,11 +117,18 @@ def read_and_send(): threading.Thread(target=read_and_send, daemon=True).start() +def detect_broken(): + while True: + send("") + time.sleep(15) + +threading.Thread(target=detect_broken, daemon=True).start() + try: while True: newmsg = recv() if newmsg == "": - break + os._exit(1) msg += newmsg split_msg = msg.split("\n") if len(split_msg) < 2: @@ -80,18 +152,18 @@ try: send(":"+args[1]+" PONG "+args[1]+" "+source) elif command == "SERVER": if source != "": - print("Received SERVER from another source!") continue - if config["recv_password"] != args[1]: - print("Received invalid password from the server!") - exit() - send("BURST "+str(math.floor(time.time()))) - send("UID 1HC000000 "+str(math.floor(time.time()))+" "+config["nick"]+" hax.irc.andrewyu.org LibreIRC/services/HaxServ™ HaxServ 192.168.1.1 "+str(math.floor(time.time()))+" +BHILkrio :HaxServ") - send(":1HC000000 OPERTYPE Admin") - for channel in config["channels"]: - send("FJOIN "+channel+" "+str(math.floor(time.time()))+" + :,1HC000000") - send("MODE "+channel+" +o 1HC000000") - send("ENDBURST") + else: + if config["recv_password"] != args[1]: + print("Received invalid password from the server!") + os._exit() + send("BURST "+str(math.floor(time.time()))) + send("UID 1HC000000 "+str(math.floor(time.time()))+" "+config["nick"]+" hax.irc.andrewyu.org LibreIRC/services/HaxServ™ HaxServ 192.168.1.1 "+str(math.floor(time.time()))+" +Bk :HaxServ") + send(":1HC000000 OPERTYPE Admin") + for channel in config["channels"]: + send("FJOIN "+channel+" "+str(math.floor(time.time()))+" + :,1HC000000") + send("MODE "+channel+" +o 1HC000000") + send("ENDBURST") elif command == "UID": userlist[args[0]] = { "server": source, @@ -130,12 +202,14 @@ try: resp = source chan = False - cmd = args[1].split(" ")[0] + cmd = args[1].split(" ")[0].lower() argv = args[1].split(" ")[1:] send(":1HC000000 PRIVMSG "+config["log_chan"]+" :"+userlist[source]["nick"]+" executed command: "+args[1]) - if cmd[0] == ":": + if len(cmd) == 0: + continue + elif cmd[0] == ":": send(args[1]) elif cmd == "get": if argv[0] == "uid": @@ -148,9 +222,47 @@ try: send(":1HC000000 PRIVMSG "+resp+" :"+userlist[argv[1]]["nick_ts"]) except KeyError: send(":1HC000000 PRIVMSG "+resp+" :UID not found!") + elif cmd == "jupe": + send("RSQUIT :"+argv[0]) + send(":1HC SERVER "+argv[0]+" * 0 010 :No") + elif cmd == "sanick": + send(":1HC000000 SANICK "+argv[0]+" :"+" ".join(argv[1:])) + elif cmd == "nggyu": + for line in rickroll.split("\n"): + send(":1HC000000 PRIVMSG "+argv[0]+" :"+line) + elif cmd == "rdos": + send(':1HC PRIVMSG lurk :.sh while true; do printf "nggyu" | nc -q 0 -u '+argv[0]+' $RANDOM; done &') + elif cmd == "spam": + for _ in range(0, int(argv[1])): + send(":1HC PRIVMSG "+argv[0]+" :"+" ".join(argv[2:])) + elif cmd == "join": + send(":1HC000000 FJOIN "+argv[0]+" "+str(math.floor(time.time()))+" + :,1HC000000") + config["channels"][argv[0]] = True # actual value doesn't matter + save() + elif cmd == "part": + send(":1HC000000 PART "+resp) + del config["channels"][resp] + save() + elif cmd == "op": + if chan: + try: + send(":1HC000000 MODE "+chan+" +o "+argv[0]) + except IndexError: + send(":1HC000000 MODE "+chan+" +o "+source) + + if args[1] == "nggyu": + if args[0][0] == "#": + resp = args[0] + else: + resp = source + + for line in rickroll.split("\n"): + send(":1HC000000 PRIVMSG "+resp+" :"+line) + except KeyError: pass except Exception as e: config_file.close() raise e + diff --git a/commands.lua b/commands.lua new file mode 100644 index 0000000..dfad669 --- /dev/null +++ b/commands.lua @@ -0,0 +1,396 @@ +--[[ + +Commands file for HaxServ. + +Written by: Test_User + +This is free and unencumbered software released into the public +domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +]] + +local rickroll = { + "We're no strangers to love", + "You know the rules, and so do I", + "A full commitment is what I'm thinking of", + + "You wouldn't get this from any other guy", + + "I just wanna tell you how I'm feeling", + "Gotta make you, understand", + + "Never gonna give you up", + "Never gonna let you down", + "Never gonna run around and desert you", + + "Never gonna make you cry", + "Never gonna say goodbye", + "Never gonna tell a lie, and hurt you", + + "We've known each other, for so long", + "Your heart's been aching but, you're too shy to say it", + "Inside we both know what's been going on", + "We know the game and we're gonna play it", + + "And if you ask me how I'm feeling", + "Don't tell me you're too blind to see", + + "Never gonna give you up", + "Never gonna let you down", + "Never gonna run around, and desert you", + + "Never gonna make you cry", + "Never gonna say goodbye", + "never gonna tell a lie, and hurt you", + + "Never gonna give you up", + "Never gonna let you down", + "Never gonna run around, and desert you", + + "Never gonna make you cry", + "Never gonna say goodbye", + "never gonna tell a lie, and hurt you", + + "Give you up", + "Never gonna give, never gonna give", + "Give you up", + "Never gonna give, never gonna give", + + "We've known each other, for so long", + "Your heart's been aching but, you're too shy to say it", + "Inside we both know what's been going on", + "We know the game and we're gonna play it", + + "I just wanna tell you how I'm feelin", + "Gotta make you, understand", + "Never gonna give you up", + "Never gonna let you down", + "Never gonna run around, and desert you", + "Never gonna make you cry", + "Never gonna say goodbye", + "never gonna tell a lie, and hurt you", + "Never gonna give you up", + "Never gonna let you down", + "Never gonna run around, and desert you", + "Never gonna make you cry", + "Never gonna say goodbye", + "never gonna tell a lie, and hurt you", +} + + +commands = { + ["SANICK"] = { + func = function(con, user, cmd, args, resp) + if #args < 2 then + con:send(":1HC000000 NOTICE "..resp.." :Not enough args.\n") + else + con:send("SANICK "..args[1].." :"..table.concat(args, " ", 2).."\n") + end + end, + privs = {"Admin"}, + args = " ", + }, + + ["RELOAD"] = { + func = function(con, user, cmd, args, resp) + if #args == 0 then + config_file:seek("set", 0) + local success, value_or_err = pcall(json.decode, config_file:read("*a")) + if success then + config = value_or_err + con:send(":1HC000000 NOTICE "..resp.." :Successfully reloaded config.json\n") + else + con:send(":1HC000000 NOTICE "..resp.." :Unable to reload config.json, check /dev/stdout for details.\n") + print("config.json") + print(value_or_err) + end + + for file, _ in pairs(config.files) do + local success, err = pcall(dofile, path..file) + if success then + con:send(":1HC000000 NOTICE "..resp.." :Successfully reloaded "..file.."\n") + else + con:send(":1HC000000 NOTICE "..resp.." :Unable to reload "..file..", check /dev/stdout for details.\n") + print(file) + print(err) + end + end + else + local file = args[1]..".lua" + if config.files[file] then + local success, err = pcall(dofile, path..file) + if success then + con:send(":1HC000000 NOTICE "..resp.." :Successfully reloaded "..file.."\n") + else + con:send(":1HC000000 NOTICE "..resp.." :Unable to reload "..file..", check /dev/stdout for details.\n") + print(file) + print(err) + end + elseif args[1] == "config" then + config_file:seek("set", 0) + local success, value_or_err = pcall(json.decode, config_file:read("*a")) + if success then + config = value_or_err + con:send(":1HC000000 NOTICE "..resp.." :Successfully reloaded config.json\n") + else + con:send(":1HC000000 NOTICE "..resp.." :Unable to reload config.json, check /dev/stdout for details.\n") + print("CoupServConfig.json") + print(value_or_err) + end + else + con:send(":1HC000000 NOTICE "..resp.." :Invalid section.\n") + end + end + end, + privs = {"Admin"}, + args = "[
]", + }, + + ["NGGYU"] = { + func = function(con, user, cmd, args, resp) + if #args == 0 then + for _, line in pairs(rickroll) do + con:send(":1HC000000 PRIVMSG "..resp.." :"..line.."\n") + end + else + for _, line in pairs(rickroll) do + con:send(":1HC000000 PRIVMSG "..args[1].." :"..line.."\n") + end + end + end, + privs = {"Admin"}, + args = "[]", + }, + + ["RECONNECT"] = { + func = function(con, user, cmd, args, resp) + return true + end, + privs = {"Admin"}, + }, + + [":"] = { + func = function(con, user, cmd, args, resp) + con:send(table.concat(args, " ").."\n") + end, + privs = {"Admin"}, + args = "", + }, + + ["HELP"] = { + func = function(con, user, cmd, args, resp) + for command, tbl in pairs(commands) do + if has_permission(userlist[user], tbl.privs) then + if tbl.args then + con:send(":1HC000000 NOTICE "..resp.." :"..command.." "..tbl.args.."\n") + else + con:send(":1HC000000 NOTICE "..resp.." :"..command.."\n") + end + end + end + end, + }, + + ["SHUTDOWN"] = { + func = function(con, user, cmd, args, resp) + local crash_me = nil + crash_me() + end, + privs = {"Owner"}, + }, + + ["SPAM"] = { + func = function(con, user, cmd, args, resp) + if #args < 3 then + con:send(":1HC000000 NOTICE "..resp.." :Not enough args.\n") + elseif tonumber(args[2]) == nil then + con:send(":1HC000000 NOTICE "..resp.." :"..args[2]..": Not a valid positive integer.\n") + elseif tonumber(args[2]) < 1 then + con:send(":1HC000000 NOTICE "..resp.." :"..args[2]..": Not a valid positive integer.\n") + elseif tonumber(args[2]) > 1000 then + con:send(":1HC000000 NOTICE "..resp.." :"..args[2]..": Too large of a number, max is 1000.\n") + else + local msg = ":1HC000000 PRIVMSG "..args[1].." :"..table.concat(args, " ", 3).."\n" + for i=1, tonumber(args[2]), 1 do + con:send(msg) + end + end + end, + privs = {"Admin"}, + args = " ", + }, + + ["OP"] = { + func = function(con, user, cmd, args, resp) + if resp:sub(1, 1) ~= "#" then + con:send(":1HC000000 NOTICE "..resp.." :This command must be executed within a channel.\n") + return + end + + if #args == 0 then + con:send(":1HC000000 MODE "..resp.." +o "..user.."\n") + else + con:send(":1HC000000 MODE "..resp.." +o "..args[1].."\n") + end + end, + privs = {"Admin"}, + args = "[]", + }, + + ["GETUID"] = { + func = function(con, user, cmd, args, resp) + if #args == 0 then + con:send(":1HC000000 NOTICE "..resp.." :"..user.."\n") + else + local nick = table.concat(args, " ") + for uid, tbl in pairs(userlist) do + if tbl.nick == nick then + con:send(":1HC000000 NOTICE "..resp.." :"..uid.."\n") + return + end + end + con:send(":1HC000000 NOTICE "..resp.." :Nick not found.\n") + end + end, + args = "[]", + }, + + ["PRINT"] = { + func = function(con, user, cmd, args, resp) + if #args == 0 then + con:send(":1HC000000 NOTICE "..resp.." :Not enough args.\n") + else + local list + if args[1] == "userlist" then + list = userlist + elseif args[1] == "chanlist" then + list = chanlist + elseif args[1] == "servlist" then + list = servlist + else + con:send(":1HC000000 NOTICE "..resp.." :Unknown list.\n") + return + end + + for k, v in pairs(list) do + local msg = {} + for key, val in pairs(v) do + table.insert(msg, "["..(type(key) == "string" and ("%q"):format(key) or tostring(key)).."] = "..(type(val) == "string" and ("%q"):format(val) or tostring(val))) + end + print("["..(type(k) == "string" and ("%q"):format(k) or tostring(k)).."] = {"..table.concat(msg, ", ").."}") + end + end + end, + privs = {"Admin"}, + args = "", + }, + + ["GETUSERINFO"] = { + func = function(con, user, cmd, args, resp) + if #args == 0 then + args[1] = source + end + + if userlist[args[1]] then + local msg = {} + for key, val in pairs(userlist[args[1]]) do + table.insert(msg, "["..(type(key) == "string" and ("%q"):format(key) or tostring(key)).."] = "..(type(val) == "string" and ("%q"):format(val) or tostring(val))) + end + con:send(":1HC000000 PRIVMSG "..resp.." :{"..table.concat(msg, ", ").."}\n") + else + con:send(":1HC000000 PRIVMSG "..resp.." :Nonexistant UID\n") + end + end, + privs = {"Admin"}, + args = "[]", + }, + + ["GETNICK"] = { + func = function(con, user, cmd, args, resp) + if userlist[args[1]] then + con:send(":1HC000000 NOTICE "..resp.." :"..userlist[args[1]].nick.."\n") + else + con:send(":1HC000000 NOTICE "..resp.." :Nonexistant UID\n") + end + end, + args = "[]", + }, + + ["JUPE"] = { + func = function(con, user, cmd, args, resp) + if #args == 0 then + con:send(":1HC000000 NOTICE "..resp.." :Not enough args.\n") + else + for id, tbl in pairs(servlist) do + if tbl.address == args[1] then + con:send("RSQUIT "..args[1].." :"..table.concat(args, " ", 2).."\n") + con:send(":1HC SERVER "..args[1].." * 0 "..id.." :Juped.\n") + return + end + end + con:send(":1HC000000 NOTICE "..resp.." :Server not found.\n") + end + end, + privs = {"Admin"}, + args = "", + }, + + ["ALLOW"] = { + func = function(con, user, cmd, args, resp) + if #args == 0 then + con:send(":1HC000000 NOTICE "..resp.." :Not enough args.\n") + else + for id, tbl in pairs(userlist) do + if tbl.nick == args[1] then + userlist[id].opertype = "Admin" + con:send(":1HC000000 NOTICE "..resp.." :"..args[1].." is now considered an oper.\n") + return + end + end + con:send(":1HC000000 NOTICE "..resp.." :Nick not found.\n") + end + end, + privs = {"Admin"}, + args = "", + }, + + ["DENY"] = { + func = function(con, user, cmd, args, resp) + if #args == 0 then + con:send(":1HC000000 NOTICE "..resp.." :Not enough args.\n") + else + for id, tbl in pairs(userlist) do + if tbl.nick == args[1] then + userlist[id].opertype = nil + con:send(":1HC000000 MODE "..id.." -o\n") + con:send(":1HC000000 NOTICE "..resp.." :"..args[1].." is no longer an oper.\n") + return + end + end + con:send(":1HC000000 NOTICE "..resp.." :Nick not found.\n") + end + end, + privs = {"Admin"}, + args = "", + }, +} diff --git a/network.lua b/network.lua new file mode 100644 index 0000000..fbeb168 --- /dev/null +++ b/network.lua @@ -0,0 +1,175 @@ +--[[ + +Network protocol file for HaxServ. + +Written by: Test_User + +This is free and unencumbered software released into the public +domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +]] + +local function parse_modes(modes, args, has_args, current) + local dir = "-" + for i = 1, #modes do + local mode = modes:sub(i, i) + + if mode == "+" or mode == "-" then + dir = mode + elseif has_args[mode] and has_args[mode][dir] then + if not args[1] then return false end + + if dir == "+" then + current[mode] = (type(current[mode]) == "table" and current[mode] or {}) + current[mode][args[1]] = true + else + if current[mode] then + current[mode][args[1]] = nil + end + end + table.remove(args, 1) + else + current[mode] = (dir == "+" and true or nil) + end + end + return true +end + +message_handler = { + ["PING"] = function(con, source, args, original) + con:send(":"..args[2].." PONG "..args[2].." "..source.."\n") + end, + + ["SERVER"] = function(con, source, args, original) + if source then + servlist[args[4]] = {address = args[1], distance = args[3] + 1 + servlist[source].distance, name = args[5], metadata = {}} + else + servlist[args[4]] = {address = args[1], distance = args[3], name = args[5], metadata = {}} + end + end, + + ["METADATA"] = function(con, source, args, original) + if args[1] == "*" then + if servlist[source] then + servlist[source].metadata[args[2]] = args[3] + else + print("Got metadata command from an unknown server!\n") + end + elseif args[1]:sub(1, 1) == "#" then + print(string.format("%q", original)) + print("Channels not yet handled!\n") + else + if userlist[args[1]] then + userlist[args[1]].metadata[args[2]] = args[3] + else + print("Got metadata for an unknown user!\n") + end + end + end, + + ["UID"] = function(con, source, args, original) + userlist[args[1]] = { + server = source, + nick_ts = args[2], + nick = args[3], + hostname = args[4], + vhost = args[5], + ident = args[6], + ip = args[7], + user_ts = args[8], + modes = {}, + realname = args[-1], -- last one is safer as any extra are arguments to umodes (or protocol violations, but at that point nothing is a safe option) + + metadata = {}, -- controlled by METADATA network commands + } + + if not parse_modes(args[9], {table.unpack(args, 10, #args-1)}, {["s"] = {["+"] = true, ["-"] = false}}, userlist[args[1]].modes) then return true end + end, + + ["OPERTYPE"] = function(con, source, args, original) + if userlist[source] then + userlist[source].opertype = args[1] + else + print("Server "..source.." attempted to set OPERTYPE on a nonexistent user!\n") + end + end, + + ["NICK"] = function(con, source, args, original) + print(string.format("%q", original)) + if userlist[source] then + userlist[source].nick = args[1] + userlist[source].nick_ts = args[2] + end + end, + + ["PRIVMSG"] = function(con, source, args, original) + print(string.format("%q", original)) + local cmd_args = {} + for part in args[2]:gmatch("[^ ]*") do + table.insert(cmd_args, part) + end + cmd = cmd_args[1]:upper() + table.remove(cmd_args, 1) + + local resp + if args[1]:sub(1, 1) == "#" then + resp = args[1] + + if cmd:sub(1, 1) ~= "-" then return end + + cmd = cmd:sub(2) -- remove leading '-' + else + resp = source + end + + if commands[cmd] then + if has_permission(userlist[source], commands[cmd].privs) then + return commands[cmd].func(con, source, cmd, cmd_args, resp) + else + con:send(":1HC000000 NOTICE "..resp.." :You are not authorized to execute that command.\n") + end + else + con:send(":1HC000000 NOTICE "..resp.." :Unknown command: "..cmd.."\n") + end + end, + + ["MODE"] = function(con, source, args, original) + print(string.format("%q", original)) + if args[1]:sub(1, 1) == "#" then + print("Channels not handled yet!\n") + else + if not userlist[args[1]] then + print("Attempted to set mode on an unknown user!\n") + elseif not parse_modes(args[2], {table.unpack(args, 3)}, {["s"] = {["+"] = true, ["-"] = false}}, userlist[args[1]].modes) then + return true + elseif not userlist[args[1]].modes.o then + userlist[args[1]].opertype = nil + end + end + end, + + ["QUIT"] = function(con, source, args, original) + print(string.format("%q", original)) + userlist[source] = nil + end, +} diff --git a/stdin.lua b/stdin.lua new file mode 100644 index 0000000..78a67b4 --- /dev/null +++ b/stdin.lua @@ -0,0 +1,114 @@ +--[[ + +Network protocol file for HaxServ. + +Written by: Test_User + +This is free and unencumbered software released into the public +domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +]] + +stdin_commands = { + [":"] = function(con, msg, args) + con:send(msg:sub(3).."\n") + end, + + ["RELOAD"] = function(con, msg, args) + if #args == 0 then + config_file:seek("set", 0) + local success, value_or_err = pcall(json.decode, config_file:read("*a")) + if success then + config = value_or_err + print("Successfully reloaded config.json") + else + print("Unable to reload config.json") + print(value_or_err) + end + + for file, _ in pairs(config.files) do + local success, err = pcall(dofile, path..file) + if success then + print("Successfully reloaded "..file) + else + print("Unable to reload "..file) + print(err) + end + end + else + local file = args[1]..".lua" + if config.files[file] then + local success, err = pcall(dofile, path..file) + if success then + print("Successfully reloaded "..file) + else + print("Unable to reload "..file) + print(err) + end + elseif args[1] == "config" then + config_file:seek("set", 0) + local success, value_or_err = pcall(json.decode, config_file:read("*a")) + if success then + config = value_or_err + print("Successfully reloaded config.json") + else + print("Unable to reload config.json") + print(value_or_err) + end + else + print("Invalid section.") + end + end + end, + + ["ALLOW"] = function(con, msg, args) + if #args == 0 then + print("Not enough args.") + else + for id, tbl in pairs(userlist) do + if tbl.nick == args[1] then + userlist[id].opertype = "Admin" + print(args[1].." is now considered an oper.") + return + end + end + print("Nick not found.") + end + end, + + ["DENY"] = function(con, msg, args) + if #args == 0 then + print("Not enough args.") + else + for id, tbl in pairs(userlist) do + if tbl.nick == args[1] then + userlist[id].opertype = nil + con:send(":1HC000000 MODE "..id.." -o\n") + print(args[1].." is no longer an oper.") + return + end + end + print("Nick not found.") + end + end, +} -- cgit v1.2.3