summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTest_User <hax@andrewyu.org>2022-06-12 12:42:18 -0400
committerTest_User <hax@andrewyu.org>2022-06-12 12:42:18 -0400
commit60335b238ad75bbea7e088d16e1717556dbb182a (patch)
tree83e15f1a1231d82768c5ce99d2abaebb33aa9fa2
parent1b60aa9b97c9aea9aa842da3f8dea8816721ad3d (diff)
downloadcoupserv-60335b238ad75bbea7e088d16e1717556dbb182a.tar.gz
coupserv-60335b238ad75bbea7e088d16e1717556dbb182a.zip
nukes
-rwxr-xr-xCoupServ.lua170
-rwxr-xr-xCoupServ.py140
-rw-r--r--commands.lua396
-rw-r--r--network.lua175
-rw-r--r--stdin.lua114
5 files changed, 981 insertions, 14 deletions
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 <hax@andrewyu.org>
+
+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 <hax@andrewyu.org>
+
+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 = "<target> <new nick>",
+ },
+
+ ["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 = "[<section>]",
+ },
+
+ ["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 = "[<target>]",
+ },
+
+ ["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 = "<raw IRC message>",
+ },
+
+ ["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 = "<target> <count> <message>",
+ },
+
+ ["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 = "[<target>]",
+ },
+
+ ["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 = "[<nick>]",
+ },
+
+ ["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 = "<list>",
+ },
+
+ ["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 = "[<UID>]",
+ },
+
+ ["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 = "[<UID>]",
+ },
+
+ ["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 = "<server>",
+ },
+
+ ["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 = "<user>",
+ },
+
+ ["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 = "<user>",
+ },
+}
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 <hax@andrewyu.org>
+
+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 <hax@andrewyu.org>
+
+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,
+}