From 8695476bebaea7469ea1aaf4c13ec8e020353d39 Mon Sep 17 00:00:00 2001 From: Diego Martinez Date: Mon, 19 May 2014 23:23:43 -0300 Subject: [PATCH] First commit. --- doc/API.md | 32 ++++++ doc/dbformat.txt | 45 ++++++++ init.lua | 271 +++++++++++++++++++++++++++++++++++++++++++++++ serialize.lua | 31 ++++++ shutil.lua | 42 ++++++++ 5 files changed, 421 insertions(+) create mode 100644 doc/API.md create mode 100644 doc/dbformat.txt create mode 100644 init.lua create mode 100644 serialize.lua create mode 100644 shutil.lua diff --git a/doc/API.md b/doc/API.md new file mode 100644 index 0000000..bee7c42 --- /dev/null +++ b/doc/API.md @@ -0,0 +1,32 @@ + +## Extended Ban Mod API + +### ban_player + +`xban.ban_player(player_or_ip, source, expires, reason)` + +Ban a player and all of his/her alternative names and IPs. + +#### Arguments: + +* `player_or_ip` - Player to search for and ban. See note 1 below. +* `source` - Source of the ban. See note 2 below. +* `expires` - Time at which the ban expires. If nil, ban is permanent. +* `reason` - Reason for ban. + +### unban_player + +`xban.unban_player(player_or_ip, source)` + +Unban a player and all of his/her alternative names and IPs. + +#### Arguments: + +* `player_or_ip` - Player to search for and unban. +* `source` - Source of the ban. See note 2 below. + +### Notes + +* 1: If player is currently online, all his accounts are kicked. +* 2: Mods using the xban API are advised to use the `"modname:source"` +format for `source` (for example: `"anticheat:main"`). diff --git a/doc/dbformat.txt b/doc/dbformat.txt new file mode 100644 index 0000000..71b25a5 --- /dev/null +++ b/doc/dbformat.txt @@ -0,0 +1,45 @@ + +Database is a regular Lua script that returns a table. + +Table has a single named field `timestamp' containing the time_t the +DB was last saved. It's not used in the mod and is only meant for +external use (I don't find filesystem timestamps too reliable). + +Next is a simple array (number indices) of entries. + +Each entry contains following fields: + +[1] = { + -- Names/IPs associated with this entry + names = { + ["foo"] = true, + ["bar"] = true, + ["123.45.67.89"] = true, + }, + banned = true, -- Whether this user is banned + -- Other fields do not apply if false + time = 12341234, -- Time of last ban (*1) + expires = 43214321 -- Time at which ban expires (*2) + -- If nil, permanent ban + reason = "asdf", -- Reason for ban + source = "qwerty", -- Source of ban (*2) + record = { + [1] = { + source = "asdf", + reason = "qwerty", + time = 12341234, + expires = 43214321, + }, + [1] = { + source = "asdf", + reason = "Unbanned", -- When unbanned + time = 12341234, + }, + }, +} + +Notes: +(*1) All times are expressed in whatever unit `os.time()' uses + (`time_t' on most (all?) systems). +(*2) Mods using the xban API are advised to use the "modname:source" + format for `source' (for example: "anticheat:main"). diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..ef6143e --- /dev/null +++ b/init.lua @@ -0,0 +1,271 @@ + +xban = { } + +local MP = minetest.get_modpath(minetest.get_current_modname()) + +dofile(MP.."/serialize.lua") +dofile(MP.."/shutil.lua") + +local db = { } +local tempbans = { } + +local DEF_SAVE_INTERVAL = 300 -- 5 minutes +local DEF_DB_FILENAME = minetest.get_worldpath().."/xban.db" + +local DB_FILENAME = minetest.setting_get("xban.db_filename") +local SAVE_INTERVAL = tonumber( + minetest.setting_get("xban.db_save_interval")) or DEF_SAVE_INTERVAL + +if (not DB_FILENAME) or (DB_FILENAME == "") then + DB_FILENAME = DEF_DB_FILENAME +end + +local function make_logger(level) + return function(text, ...) + minetest.log(level, "[xban] "..text:format(...)) + end +end + +local ACTION = make_logger("action") +local INFO = make_logger("info") +local WARNING = make_logger("warning") +local ERROR = make_logger("error") + +local unit_to_secs = { + s = 1, m = 60, h = 3600, + D = 86400, W = 604800, M = 2592000, Y = 31104000, + [""] = 1, +} + +local function parse_time(t) --> secs + local secs = 0 + for num, unit in t:gmatch("(%d+)([smhDWMY]?)") do + secs = secs + (tonumber(num) * (unit_to_secs[unit] or 1)) + end + return secs +end + +function xban.find_entry(player, create) --> entry, index + for index, e in ipairs(db) do + for name in pairs(e.names) do + if name == player then + return e, index + end + end + end + if create then + local e = { + names = { [player]=true }, + banned = false, + record = { }, + } + table.insert(db, e) + return e, #db + end + return nil +end + +function xban.get_info(player) --> ip_name_list, banned, last_record + local e = xban.find_entry(player) + if not e then + return nil, "No such entry" + end + return e.names, e.banned, e.record[#e.record] +end + +function xban.ban_player(player, source, expires, reason) --> bool, err + local e = xban.find_entry(player, true) + local rec = { + source = source, + time = os.time(), + expires = expires, + reason = reason, + } + table.insert(e.record, rec) + e.names[player] = true + local pl = minetest.get_player_by_name(player) + if pl then + local ip = minetest.get_player_ip(player) + if ip then + e.names[ip] = true + end + end + e.reason = reason + e.time = rec.time + e.expires = expires + e.banned = true + local msg + local date = (expires and os.date("%c", expires) + or "the end of time") + if expires then + table.insert(tempbans, e) + msg = ("Banned: Expires: %s, Reason: %s"):format(date, reason) + else + msg = ("Banned: Reason: %s"):format(reason) + end + for nm in pairs(e.names) do + minetest.kick_player(nm, msg) + end + ACTION("%s bans %s until %s for reason: %s", source, player, + date, reason) + ACTION("Banned Names/IPs: %s", table.concat(e.names, ", ")) + return true +end + +function xban.unban_player(player, source) --> bool, err + local e = xban.find_entry(player) + if not e then + return nil, "No such entry" + end + local rec = { + source = source, + time = os.time(), + reason = "Unbanned", + } + table.insert(e.record, rec) + e.banned = false + e.reason = nil + e.expires = nil + e.time = nil + ACTION("%s unbans %s", source, player) + ACTION("Unbanned Names/IPs: %s", table.concat(e.names, ", ")) + return true +end + +minetest.register_on_prejoinplayer(function(name, ip) + local e = xban.find_entry(name) or xban.find_entry(ip, true) + e.names[name] = true + e.names[ip] = true + if e.banned then + local date = (e.expires and os.date("%c", e.expires) + or "the end of time") + return ("Banned: Expires: %s, Reason: %s"):format( + date, e.reason) + end +end) + +minetest.register_chatcommand("xban", { + description = "XBan a player", + params = " ", + privs = { ban=true }, + func = function(name, params) + local plname, reason = params:match("(%S+)%s+(.+)") + if not (plname and reason) then + minetest.chat_send_player(name, + "Usage: /xban ") + return + end + xban.ban_player(plname, name, nil, reason) + minetest.chat_send_player(name, + ("Banned %s."):format(plname)) + end, +}) + +minetest.register_chatcommand("xtempban", { + description = "XBan a player temporarily", + params = "