From b9e8111929385b463a5b393c5bb9defb2f117c44 Mon Sep 17 00:00:00 2001 From: y Date: Mon, 8 Jul 2019 01:59:32 +0100 Subject: [PATCH] checkpoint: debug mode (untested) --- README.md | 2 +- TODO | 2 - chat.lua | 18 ++- commands.lua | 332 ++++++++++++++++++++++++--------------------- data.lua | 45 ++++-- depends.txt | 2 + init.lua | 10 +- lib_asn.lua | 40 +++--- lib_geo.lua | 0 lib_ip.lua | 12 +- login_handling.lua | 182 +++++++++++++++---------- mod.conf | 2 +- privs.lua | 12 +- settings.lua | 62 +++++++-- settingtypes.txt | 13 +- util.lua | 27 ++++ verbana_cl.lua | 1 - 17 files changed, 459 insertions(+), 303 deletions(-) delete mode 100644 lib_geo.lua delete mode 100644 verbana_cl.lua diff --git a/README.md b/README.md index a86bd88..503e5b0 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ Some features of sban that the first release of Verbana will likely lack: Requirements ============ -* Verbana must be listed as a trusted mod in minetest.conf (`secure.trusted_mods`) +* Verbana must be listed as a trusted mod in minetest.conf (`secure.trusted_mods`), in order to use a sqlite database. * lsqlite3 (SQLite3 for Lua) must be installed and accessible to minetest's Lua. * The easiest way I know how to do this: install luarocks, and execute `sudo luarocks --lua-version 5.1 install lsqlite3` * The minetest server must use IPv4 exclusively. I've made zero attempt to support IPv6. diff --git a/TODO b/TODO index 0afe58a..8b13789 100644 --- a/TODO +++ b/TODO @@ -1,3 +1 @@ -add a "report" command so that players can log issues w/ other players for the mods to peruse -in login_handling.lua: register_on_auth_fail should log diff --git a/chat.lua b/chat.lua index d2999d7..c3cd251 100644 --- a/chat.lua +++ b/chat.lua @@ -1,8 +1,10 @@ verbana.chat = {} -local mod_priv = verbana.privs.moderator -local admin_priv = verbana.privs.admin -local unverified_priv = verbana.privs.unverified +local data = verbana.data +local privs = verbana.privs + +local mod_priv = privs.moderator +local admin_priv = privs.admin function verbana.chat.tell_mods(message) if minetest.global_exists('irc') then irc:say(message) end @@ -10,20 +12,22 @@ function verbana.chat.tell_mods(message) for _, player in ipairs(minetest.get_connected_players()) do local name = player:get_player_name() - local privs = minetest.get_player_privs(name) - if privs[mod_priv] or privs[admin_priv] then + if privs.is_privileged(name) then minetest.chat_send_player(name, message) end end end --- make this the first chat message handler +-- make this the first chat message handler by inserting it at the start. -- this has the effect of -- (1) pre-empting irc (and irc2) -- (2) disabling all server-side commands table.insert(minetest.registered_on_chat_messages, 1, function(name, message) - if minetest.check_player_privs(name, {[unverified_priv]=true}) then + local player_id = data.get_player_id(name) + local player_status = data.get_player_status(player_id) + local is_unverified = player_status.status_id == data.player_status.unverified.id + if is_unverified then local cmsg = ('[unverified] <%s> %s'):format(name, message) verbana.chat.tell_mods(cmsg) minetest.chat_send_player(name, cmsg) diff --git a/commands.lua b/commands.lua index 415900d..611ef4d 100644 --- a/commands.lua +++ b/commands.lua @@ -1,9 +1,34 @@ verbana.commands = {} +local data = verbana.data +local lib_asn = verbana.lib_asn +local lib_ip = verbana.lib_ip +local log = verbana.log +local settings = verbana.settings + local mod_priv = verbana.privs.moderator local admin_priv = verbana.privs.admin +local debug_mode = settings.debug_mode -minetest.register_chatcommand('import_sban', { +local safe = verbana.util.safe +local safe_kick_player = verbana.util.safe_kick_player + +local function register_chatcommand(name, def) + if debug_mode then name = ('verbana_%s'):format(name) end + def.func = safe(def.func) + minetest.register_chatcommand(name, def) +end + +local function override_chatcommand(name, def) + def.func = safe(def.func) + if debug_mode then + minetest.register_chatcommand(name, def) + else + minetest.override_chatcommand(name, def) + end +end + +register_chatcommand('import_sban', { params='', description='import records from sban', privs={[admin_priv]=true}, @@ -13,7 +38,7 @@ minetest.register_chatcommand('import_sban', { end if not io.open(filename, 'r') then return false, ('Could not open file %q.'):format(filename) - elseif verbana.data.import_from_sban(filename) then + elseif data.import_from_sban(filename) then return true, 'Successfully imported.' else return false, 'Error importing SBAN db (see server log)' @@ -21,14 +46,14 @@ minetest.register_chatcommand('import_sban', { end }) -minetest.register_chatcommand('get_asn', { +register_chatcommand('get_asn', { params=' | ', description='get the ASN associated with an IP or player name', privs={[mod_priv]=true}, func = function(_, name_or_ipstr) local ipstr - if verbana.ip.is_valid_ip(name_or_ipstr) then + if lib_ip.is_valid_ip(name_or_ipstr) then ipstr = name_or_ipstr else ipstr = minetest.get_player_ip(name_or_ipstr) @@ -38,7 +63,7 @@ minetest.register_chatcommand('get_asn', { return false, ('"%s" is not a valid ip nor a connected player'):format(name_or_ipstr) end - local asn, description = verbana.asn.lookup(ipstr) + local asn, description = lib_asn.lookup(ipstr) if not asn or asn == 0 then return false, ('could not find ASN for "%s"'):format(ipstr) end @@ -57,11 +82,11 @@ local function parse_status_params(params) if not name or name:len() > 20 then return nil, nil, nil, ('Invalid argument(s): %q'):format(params) end - local player_id = verbana.data.get_player_id(name) + local player_id = data.get_player_id(name) if not player_id then return nil, nil, nil, ('Unknown player: %s'):format(name) end - local player_status = verbana.data.get_player_status(player_id, true) + local player_status = data.get_player_status(player_id, true) return player_id, name, player_status, reason end @@ -77,33 +102,33 @@ local function parse_timed_status_params(params) if not timespan then return nil, nil, nil, nil, ('Invalid argument(s): %q'):format(params) end - local player_id = verbana.data.get_player_id(name) + local player_id = data.get_player_id(name) if not player_id then return nil, nil, nil, nil, ('Unknown player: %s'):format(name) end - local player_status = verbana.data.get_player_status(player_id, true) + local player_status = data.get_player_status(player_id, true) local expires = os.time() + timespan return player_id, name, player_status, expires, reason end local function has_suspicious_connection(player_name) - local connection_log = verbana.data.get_player_connection_log(player_name, 1) + local connection_log = data.get_player_connection_log(player_name, 1) if not connection_log or #connection_log ~= 1 then - verbana.log('warning', 'player %s exists but has no connection log?', player_name) + log('warning', 'player %s exists but has no connection log?', player_name) return true end local last_login = connection_log[1] - if last_login.ip_status_id == verbana.data.ip_status.trusted.id then + if last_login.ip_status_id == data.ip_status.trusted.id then return false - elseif last_login.ip_status_id ~= verbana.data.ip_status.default.id then + elseif last_login.ip_status_id ~= data.ip_status.default.id then return true - elseif last_login.asn_status_id == verbana.data.asn_status.default.id then + elseif last_login.asn_status_id == data.asn_status.default.id then return false end return true end ----------------- SET PLAYER STATUS COMMANDS ----------------- -minetest.register_chatcommand('verify', { +register_chatcommand('verify', { params=' []', description='verify a player', privs={[mod_priv]=true}, @@ -112,37 +137,43 @@ minetest.register_chatcommand('verify', { if not player_id then return false, reason end - if player_status.status_id ~= verbana.data.player_status.unverified.id then + if player_status.status_id ~= data.player_status.unverified.id then return false, ('Player %s is not unverified'):format(player_name) end - local executor_id = verbana.data.get_player_id(caller) + local executor_id = data.get_player_id(caller) if not executor_id then return false, 'ERROR: could not get executor ID?' end local status_id if has_suspicious_connection(player_name) then - status_id = verbana.data.player_status.suspicious.id + status_id = data.player_status.suspicious.id else - status_id = verbana.data.player_status.default.id + status_id = data.player_status.default.id end - if not verbana.data.set_player_status(player_id, executor_id, status_id, reason) then + if not data.set_player_status(player_id, executor_id, status_id, reason) then return false, 'ERROR setting player status' end - minetest.set_player_privs(player_name, verbana.settings.verified_privs) + log('action', 'setting verified privs for %s', player_name) + if not debug_mode then + minetest.set_player_privs(player_name, settings.verified_privs) + end local player = minetest.get_player_by_name(player_name) if player then - player:set_pos(verbana.settings.spawn_pos) + log('action', 'moving %s to spawn', player_name) + if not debug_mode then + player:set_pos(settings.spawn_pos) + end end if reason then - verbana.log('action', '%s verified %s because %s', caller, player_name, reason) + log('action', '%s verified %s because %s', caller, player_name, reason) else - verbana.log('action', '%s verified %s', caller, player_name) + log('action', '%s verified %s', caller, player_name) end return true, ('Verified %s'):format(player_name) end }) -minetest.register_chatcommand('unverify', { +register_chatcommand('unverify', { params=' []', description='unverify a player', privs={[mod_priv]=true}, @@ -152,34 +183,40 @@ minetest.register_chatcommand('unverify', { return false, reason end if not verbana.util.table_contains({ - verbana.data.player_status.unknown.id, - verbana.data.player_status.default.id, - verbana.data.player_status.suspicious.id, + data.player_status.unknown.id, + data.player_status.default.id, + data.player_status.suspicious.id, }, player_status.status_id) then return false, ('Cannot unverify %s w/ status %s'):format(player_name, player_status.name) end - local executor_id = verbana.data.get_player_id(caller) + local executor_id = data.get_player_id(caller) if not executor_id then return false, 'ERROR: could not get executor ID?' end - if not verbana.data.set_player_status(player_id, executor_id, verbana.data.player_status.unverified.id, reason) then + if not data.set_player_status(player_id, executor_id, data.player_status.unverified.id, reason) then return false, 'ERROR setting player status' end - minetest.set_player_privs(player_name, verbana.settings.unverified_privs) + log('action', 'setting unverified privs for %s', player_name) + if not debug_mode then + minetest.set_player_privs(player_name, settings.unverified_privs) + end local player = minetest.get_player_by_name(player_name) if player then - player:set_pos(verbana.settings.unverified_spawn_pos) + log('action', 'moving %s to unverified area', player_name) + if not debug_mode then + player:set_pos(settings.unverified_spawn_pos) + end end if reason then - verbana.log('action', '%s unverified %s because %s', caller, player_name, reason) + log('action', '%s unverified %s because %s', caller, player_name, reason) else - verbana.log('action', '%s unverified %s', caller, player_name) + log('action', '%s unverified %s', caller, player_name) end return true, ('Unverified %s'):format(player_name) end }) -minetest.override_chatcommand('kick', { +override_chatcommand('kick', { params=' []', description='kick a player', privs={[mod_priv]=true}, @@ -191,31 +228,26 @@ minetest.override_chatcommand('kick', { local player = minetest.get_player_by_name(player_name) if not player then return false, ("Player %s not in game!"):format(player_name) - end - if not minetest.kick_player(player_name, reason) then - player:set_detach() - if not minetest.kick_player(player_name, reason) then - verbana.log('warning', 'Failed to kick player %s after detaching!', player_name) - return false, ("Failed to kick player %s after detaching!"):format(player_name) - end end - local executor_id = verbana.data.get_player_id(caller) + log('action', 'kicking %s...', player_name) + safe_kick_player(caller, player, reason) + local executor_id = data.get_player_id(caller) if not executor_id then return false, 'ERROR: could not get executor ID?' end - if not verbana.data.set_player_status(player_id, executor_id, verbana.data.player_status.kicked.id, reason, nil, true) then + if not data.set_player_status(player_id, executor_id, data.player_status.kicked.id, reason, nil, true) then return false, 'ERROR logging player status' end if reason then - verbana.log('action', '%s kicked %s because %s', caller, player_name, reason) + log('action', '%s kicked %s because %s', caller, player_name, reason) else - verbana.log('action', '%s kicked %s', caller, player_name) + log('action', '%s kicked %s', caller, player_name) end return true, ('Kicked %s'):format(player_name) end }) -minetest.register_chatcommand('lock', { +register_chatcommand('lock', { params=' []', description='lock a player\'s account', privs={[mod_priv]=true}, @@ -225,40 +257,34 @@ minetest.register_chatcommand('lock', { return false, reason end if not verbana.util.table_contains({ - verbana.data.player_status.unknown.id, - verbana.data.player_status.default.id, - verbana.data.player_status.unverified.id, - verbana.data.player_status.suspicious.id, + data.player_status.unknown.id, + data.player_status.default.id, + data.player_status.unverified.id, + data.player_status.suspicious.id, }, player_status.status_id) then return false, ('Cannot lock %s w/ status %s'):format(player_name, player_status.name) end local player = minetest.get_player_by_name(player_name) if player then - if not minetest.kick_player(player_name, reason) then - player:set_detach() - if not minetest.kick_player(player_name, reason) then - minetest.chat_send_player(caller, ('Failed to kick player %s after detaching!'):format(player_name)) - verbana.log('warning', 'Failed to kick player %s after detaching!', player_name) - end - end + safe_kick_player(caller, player, reason) end - local executor_id = verbana.data.get_player_id(caller) + local executor_id = data.get_player_id(caller) if not executor_id then return false, 'ERROR: could not get executor ID?' end - if not verbana.data.set_player_status(player_id, executor_id, verbana.data.player_status.locked.id, reason) then + if not data.set_player_status(player_id, executor_id, data.player_status.locked.id, reason) then return false, 'ERROR logging player status' end if reason then - verbana.log('action', '%s locked %s because %s', caller, player_name, reason) + log('action', '%s locked %s because %s', caller, player_name, reason) else - verbana.log('action', '%s locked %s', caller, player_name) + log('action', '%s locked %s', caller, player_name) end return true, ('Locked %s'):format(player_name) end }) -minetest.register_chatcommand('unlock', { +register_chatcommand('unlock', { params=' []', description='unlock a player\'s account', privs={[mod_priv]=true}, @@ -267,32 +293,32 @@ minetest.register_chatcommand('unlock', { if not player_id then return false, reason end - if player_status.status_id ~= verbana.data.player_status.locked.id then + if player_status.status_id ~= data.player_status.locked.id then return false, ('Player %s is not locked!'):format(player_name) end - local executor_id = verbana.data.get_player_id(caller) + local executor_id = data.get_player_id(caller) if not executor_id then return false, 'ERROR: could not get executor ID?' end local status_id if has_suspicious_connection(player_name) then - status_id = verbana.data.player_status.suspicious.id + status_id = data.player_status.suspicious.id else - status_id = verbana.data.player_status.default.id + status_id = data.player_status.default.id end - if not verbana.data.set_player_status(player_id, executor_id, status_id, reason) then + if not data.set_player_status(player_id, executor_id, status_id, reason) then return false, 'ERROR setting player status' end if reason then - verbana.log('action', '%s unlocked %s because %s', caller, player_name, reason) + log('action', '%s unlocked %s because %s', caller, player_name, reason) else - verbana.log('action', '%s unlocked %s', caller, player_name) + log('action', '%s unlocked %s', caller, player_name) end return true, ('Unlocked %s'):format(player_name) end }) -minetest.override_chatcommand('ban', { +override_chatcommand('ban', { params=' []', description='ban a player', privs={[mod_priv]=true}, @@ -308,40 +334,34 @@ minetest.override_chatcommand('ban', { end end if not verbana.util.table_contains({ - verbana.data.player_status.unknown.id, - verbana.data.player_status.default.id, - verbana.data.player_status.unverified.id, - verbana.data.player_status.suspicious.id, + data.player_status.unknown.id, + data.player_status.default.id, + data.player_status.unverified.id, + data.player_status.suspicious.id, }, player_status.status_id) then return false, ('Cannot ban %s w/ status %s'):format(player_name, player_status.name) end local player = minetest.get_player_by_name(player_name) if player then - if not minetest.kick_player(player_name, reason) then - player:set_detach() - if not minetest.kick_player(player_name, reason) then - minetest.chat_send_player(caller, ('Failed to kick player %s after detaching!'):format(player_name)) - verbana.log('warning', 'Failed to kick player %s after detaching!', player_name) - end - end + safe_kick_player(caller, player, reason) end - local executor_id = verbana.data.get_player_id(caller) + local executor_id = data.get_player_id(caller) if not executor_id then return false, 'ERROR: could not get executor ID?' end - if not verbana.data.set_player_status(player_id, executor_id, verbana.data.player_status.banned.id, reason) then + if not data.set_player_status(player_id, executor_id, data.player_status.banned.id, reason) then return false, 'ERROR logging player status' end if reason then - verbana.log('action', '%s banned %s because %s', caller, player_name, reason) + log('action', '%s banned %s because %s', caller, player_name, reason) else - verbana.log('action', '%s banned %s', caller, player_name) + log('action', '%s banned %s', caller, player_name) end return true, ('Banned %s'):format(player_name) end }) -minetest.register_chatcommand('tempban', { +register_chatcommand('tempban', { params=' []', description='ban a player for a length of time', privs={[mod_priv]=true}, @@ -351,41 +371,35 @@ minetest.register_chatcommand('tempban', { return false, reason end if not verbana.util.table_contains({ - verbana.data.player_status.unknown.id, - verbana.data.player_status.default.id, - verbana.data.player_status.unverified.id, - verbana.data.player_status.suspicious.id, + data.player_status.unknown.id, + data.player_status.default.id, + data.player_status.unverified.id, + data.player_status.suspicious.id, }, player_status.status_id) then return false, ('Cannot ban %s w/ status %s'):format(player_name, player_status.name) end local player = minetest.get_player_by_name(player_name) if player then - if not minetest.kick_player(player_name, reason) then - player:set_detach() - if not minetest.kick_player(player_name, reason) then - minetest.chat_send_player(caller, ('Failed to kick player %s after detaching!'):format(player_name)) - verbana.log('warning', 'Failed to kick player %s after detaching!', player_name) - end - end + safe_kick_player(caller, player, reason) end - local executor_id = verbana.data.get_player_id(caller) + local executor_id = data.get_player_id(caller) if not executor_id then return false, 'ERROR: could not get executor ID?' end - if not verbana.data.set_player_status(player_id, executor_id, verbana.data.player_status.tempbanned.id, reason, expires) then + if not data.set_player_status(player_id, executor_id, data.player_status.tempbanned.id, reason, expires) then return false, 'ERROR logging player status' end local expires_str = os.date("%c", expires) if reason then - verbana.log('action', '%s tempbanned %s until %s because %s', caller, player_name, expires_str, reason) + log('action', '%s tempbanned %s until %s because %s', caller, player_name, expires_str, reason) else - verbana.log('action', '%s tempbanned %s until %s', caller, player_name, expires_str) + log('action', '%s tempbanned %s until %s', caller, player_name, expires_str) end return true, ('Temporarily banned %s until %s'):format(player_name, expires_str) end }) -minetest.override_chatcommand('unban', { +override_chatcommand('unban', { params=' []', description='unban a player', privs={[mod_priv]=true}, @@ -395,34 +409,34 @@ minetest.override_chatcommand('unban', { return false, reason end if not verbana.util.table_contains({ - verbana.data.player_status.banned.id, - verbana.data.player_status.tempbanned.id, + data.player_status.banned.id, + data.player_status.tempbanned.id, }, player_status.status_id) then return false, ('Player %s is not banned!'):format(player_name) end - local executor_id = verbana.data.get_player_id(caller) + local executor_id = data.get_player_id(caller) if not executor_id then return false, 'ERROR: could not get executor ID?' end local status_id if has_suspicious_connection(player_name) then - status_id = verbana.data.player_status.suspicious.id + status_id = data.player_status.suspicious.id else - status_id = verbana.data.player_status.default.id + status_id = data.player_status.default.id end - if not verbana.data.set_player_status(player_id, executor_id, status_id, reason) then + if not data.set_player_status(player_id, executor_id, status_id, reason) then return false, 'ERROR setting player status' end if reason then - verbana.log('action', '%s unbanned %s because %s', caller, player_name, reason) + log('action', '%s unbanned %s because %s', caller, player_name, reason) else - verbana.log('action', '%s unbanned %s', caller, player_name) + log('action', '%s unbanned %s', caller, player_name) end return true, ('Unbanned %s'):format(player_name) end }) -minetest.register_chatcommand('whitelist', { +register_chatcommand('whitelist', { params=' []', description='whitelist a player', privs={[admin_priv]=true}, @@ -432,30 +446,30 @@ minetest.register_chatcommand('whitelist', { return false, reason end if not verbana.util.table_contains({ - verbana.data.player_status.unknown.id, - verbana.data.player_status.default.id, - verbana.data.player_status.unverified.id, - verbana.data.player_status.suspicious.id, + data.player_status.unknown.id, + data.player_status.default.id, + data.player_status.unverified.id, + data.player_status.suspicious.id, }, player_status.status_id) then return false, ('Cannot whitelist %s w/ status %s'):format(player_name, player_status.name) end - local executor_id = verbana.data.get_player_id(caller) + local executor_id = data.get_player_id(caller) if not executor_id then return false, 'ERROR: could not get executor ID?' end - if not verbana.data.set_player_status(player_id, executor_id, verbana.data.player_status.whitelisted.id, reason) then + if not data.set_player_status(player_id, executor_id, data.player_status.whitelisted.id, reason) then return false, 'ERROR setting player status' end if reason then - verbana.log('action', '%s whitelisted %s because %s', caller, player_name, reason) + log('action', '%s whitelisted %s because %s', caller, player_name, reason) else - verbana.log('action', '%s whitelisted %s', caller, player_name) + log('action', '%s whitelisted %s', caller, player_name) end return true, ('Whitelisted %s'):format(player_name) end }) -minetest.register_chatcommand('unwhitelist', { +register_chatcommand('unwhitelist', { params=' []', description='whitelist a player', privs={[admin_priv]=true}, @@ -464,26 +478,26 @@ minetest.register_chatcommand('unwhitelist', { if not player_id then return false, reason end - if player_status.status_id ~= verbana.data.player_status.whitelisted.id then + if player_status.status_id ~= data.player_status.whitelisted.id then return false, ('Player %s is not locked!'):format(player_name) end - local executor_id = verbana.data.get_player_id(caller) + local executor_id = data.get_player_id(caller) if not executor_id then return false, 'ERROR: could not get executor ID?' end - if not verbana.data.set_player_status(player_id, executor_id, verbana.data.player_status.default.id, reason) then + if not data.set_player_status(player_id, executor_id, data.player_status.default.id, reason) then return false, 'ERROR setting player status' end if reason then - verbana.log('action', '%s unwhitelisted %s because %s', caller, player_name, reason) + log('action', '%s unwhitelisted %s because %s', caller, player_name, reason) else - verbana.log('action', '%s unwhitelisted %s', caller, player_name) + log('action', '%s unwhitelisted %s', caller, player_name) end return true, ('Unwhitelisted %s'):format(player_name) end }) -minetest.register_chatcommand('suspect', { +register_chatcommand('suspect', { params=' []', description='mark a player as suspicious', privs={[mod_priv]=true}, @@ -493,28 +507,28 @@ minetest.register_chatcommand('suspect', { return false, reason end if not verbana.util.table_contains({ - verbana.data.player_status.unknown.id, - verbana.data.player_status.default.id, + data.player_status.unknown.id, + data.player_status.default.id, }, player_status.status_id) then return false, ('Cannot whitelist %s w/ status %s'):format(player_name, player_status.name) end - local executor_id = verbana.data.get_player_id(caller) + local executor_id = data.get_player_id(caller) if not executor_id then return false, 'ERROR: could not get executor ID?' end - if not verbana.data.set_player_status(player_id, executor_id, verbana.data.player_status.suspicious.id, reason) then + if not data.set_player_status(player_id, executor_id, data.player_status.suspicious.id, reason) then return false, 'ERROR setting player status' end if reason then - verbana.log('action', '%s suspected %s because %s', caller, player_name, reason) + log('action', '%s suspected %s because %s', caller, player_name, reason) else - verbana.log('action', '%s suspected %s', caller, player_name) + log('action', '%s suspected %s', caller, player_name) end return true, ('Suspected %s'):format(player_name) end }) -minetest.register_chatcommand('unsuspect', { +register_chatcommand('unsuspect', { params=' []', description='unmark a player as suspicious', privs={[mod_priv]=true}, @@ -523,26 +537,26 @@ minetest.register_chatcommand('unsuspect', { if not player_id then return false, reason end - if player_status.status_id ~= verbana.data.player_status.suspicious.id then + if player_status.status_id ~= data.player_status.suspicious.id then return false, ('Player %s is not suspicious!'):format(player_name) end - local executor_id = verbana.data.get_player_id(caller) + local executor_id = data.get_player_id(caller) if not executor_id then return false, 'ERROR: could not get executor ID?' end - if not verbana.data.set_player_status(player_id, executor_id, verbana.data.player_status.default.id, reason) then + if not data.set_player_status(player_id, executor_id, data.player_status.default.id, reason) then return false, 'ERROR setting player status' end if reason then - verbana.log('action', '%s unsuspected %s because %s', caller, player_name, reason) + log('action', '%s unsuspected %s because %s', caller, player_name, reason) else - verbana.log('action', '%s unsuspected %s', caller, player_name) + log('action', '%s unsuspected %s', caller, player_name) end return true, ('Unsuspected %s'):format(player_name) end }) ----------------- SET IP/ASN STATUS COMMANDS ----------------- -minetest.register_chatcommand('set_ip_status', { +register_chatcommand('set_ip_status', { params=' ', description='set the status of an IP (default, dangerous, blocked)', privs={[admin_priv]=true}, @@ -551,7 +565,7 @@ minetest.register_chatcommand('set_ip_status', { end }) -minetest.register_chatcommand('set_asn_status', { +register_chatcommand('set_asn_status', { params=' ', description='set the status of an ASN (default, dangerous, blocked)', privs={[admin_priv]=true}, @@ -560,7 +574,7 @@ minetest.register_chatcommand('set_asn_status', { end }) ---------------- GET LOGS --------------- -minetest.register_chatcommand('player_status_log', { +register_chatcommand('player_status_log', { params=' []', description='shows the status log of a player', privs={[mod_priv]=true}, @@ -572,11 +586,11 @@ minetest.register_chatcommand('player_status_log', { if not name then return false, 'invalid arguments' end - local player_id = verbana.data.get_player_id(name) + local player_id = data.get_player_id(name) if not player_id then return false, 'unknown player' end - local rows = verbana.data.get_player_status_log(player_id) + local rows = data.get_player_status_log(player_id) if not rows then return false, 'An error occurred (see server logs)' end @@ -607,7 +621,7 @@ minetest.register_chatcommand('player_status_log', { end }) -minetest.register_chatcommand('ip_status_log', { +register_chatcommand('ip_status_log', { params=' []', description='shows the status log of an IP', privs={[admin_priv]=true}, @@ -616,7 +630,7 @@ minetest.register_chatcommand('ip_status_log', { end }) -minetest.register_chatcommand('asn_status_log', { +register_chatcommand('asn_status_log', { params=' []', description='shows the status log of an ASN', privs={[admin_priv]=true}, @@ -625,7 +639,7 @@ minetest.register_chatcommand('asn_status_log', { end }) -minetest.register_chatcommand('login_record', { +register_chatcommand('login_record', { params=' []', description='shows the login record of a player', privs={[mod_priv]=true}, @@ -640,11 +654,11 @@ minetest.register_chatcommand('login_record', { if not limit then limit = 20 end - local player_id = verbana.data.get_player_id(name) + local player_id = data.get_player_id(name) if not player_id then return false, 'unknown player' end - local rows = verbana.data.get_player_connection_log(player_id) + local rows = data.get_player_connection_log(player_id) if not rows then return false, 'An error occurred (see server logs)' end @@ -655,11 +669,11 @@ minetest.register_chatcommand('login_record', { local message = ('%s:%s from %s<%s> A%s<%s> (%s)'):format( os.date("%c", row.timestamp), (rows.success and ' failed!') or '', - verbana.ip.ipint_to_ipstr(row.ipint), + lib_ip.ipint_to_ipstr(row.ipint), row.ip_status_name, row.asn, row.asn_status_name, - verbana.asn.get_description(row.asn) + lib_asn.get_description(row.asn) ) minetest.chat_send_player(caller, message) end @@ -667,7 +681,7 @@ minetest.register_chatcommand('login_record', { end }) -minetest.register_chatcommand('inspect_player', { +register_chatcommand('inspect_player', { params='', description='list ips, asns and statuses associated with a player', privs={[mod_priv]=true}, @@ -676,7 +690,7 @@ minetest.register_chatcommand('inspect_player', { end }) -minetest.register_chatcommand('inspect_ip', { +register_chatcommand('inspect_ip', { params='', description='list player accounts and statuses associated with an IP', privs={[mod_priv]=true}, @@ -685,7 +699,7 @@ minetest.register_chatcommand('inspect_ip', { end }) -minetest.register_chatcommand('inspect_asn', { +register_chatcommand('inspect_asn', { params='', description='list player accounts and statuses associated with an ASN', privs={[mod_priv]=true}, @@ -694,7 +708,9 @@ minetest.register_chatcommand('inspect_asn', { end }) --- alias (for listing an account's primary, cascade status) --- list recent bans/kicks/locks/etc --- first_login (=b) for all players --- asn statistics + +-- TODO: alias (for listing an account's primary, cascade status) +-- TODO: list recent bans/kicks/locks/etc +-- TODO: first_login (=b) for all players +-- TODO: asn statistics +-- TODO: add a "report" command so that players can log issues w/ other players for the mods to peruse diff --git a/data.lua b/data.lua index acfb3a0..5c5ef2f 100644 --- a/data.lua +++ b/data.lua @@ -1,13 +1,11 @@ -if not verbana then verbana = {} end -if not verbana.modpath then verbana.modpath = '.' end -if not verbana.ip then dofile(verbana.modpath .. '/lib_ip.lua') end -if not verbana.log then function verbana.log(_, message, ...) print(message:format(...)) end end +verbana.data = {} + +local lib_asn = verbana.lib_asn +local lib_ip = verbana.lib_ip local sql = verbana.sql local db = verbana.db -verbana.data = {} - -- constants verbana.data.player_status = { unknown={name='unknown', id=1}, @@ -207,11 +205,11 @@ function verbana.data.import_from_sban(filename) if not insert_log_statement then return _error() end for name, ipstr, created, last_login in sban_db:urows('SELECT name, ip, created, last_login FROM playerdata') do local player_id = player_id_by_name[name] - if not verbana.ip.is_valid_ip(ipstr) then + if not lib_ip.is_valid_ip(ipstr) then return _error('%s is not a valid IPv4 address', ipstr) end - local ipint = verbana.ip.ipstr_to_ipint(ipstr) - local asn = verbana.asn.lookup(ipint) + local ipint = lib_ip.ipstr_to_ipint(ipstr) + local asn = lib_asn.lookup(ipint) if not bind_and_step(insert_ip_statement, 'insert IP', ipint) then return _error() end if not bind_and_step(insert_asn_statement, 'insert ASN', asn) then return _error() end if not bind_and_step(insert_assoc_statement, 'insert assoc', player_id, ipint, asn, created, created) then return _error() end @@ -285,16 +283,23 @@ function verbana.data.import_from_sban(filename) end -- verbana.data.import_from_sban +local player_id_cache = {} function verbana.data.get_player_id(name, create_if_new) + local cached_id = player_id_cache[name] + if cached_id then return cached_id end if create_if_new then if not execute_bind_one('INSERT OR IGNORE INTO player (name) VALUES (?)', 'insert player', name) then return nil end end local table = get_full_table('SELECT id FROM player WHERE name = ? LIMIT 1', 'get player id', name) if not (table and table[1]) then return nil end + player_id_cache[name] = table[1][1] return table[1][1] end +local player_status_cache = {} function verbana.data.get_player_status(player_id, create_if_new) + local cached_status = player_status_cache[player_id] + if cached_status then return cached_status end local code = [[ SELECT executor.id executor_id , executor.name executor @@ -312,6 +317,7 @@ function verbana.data.get_player_status(player_id, create_if_new) ]] local table = get_full_ntable(code, 'get player status', player_id) if #table == 1 then + player_status_cache[player_id] = table[1] return table[1] elseif #table > 1 then verbana.log('error', 'somehow got more than 1 result when getting current player status for %s', player_id) @@ -332,6 +338,7 @@ function verbana.data.get_player_status(player_id, create_if_new) } end function verbana.data.set_player_status(player_id, executor_id, status_id, reason, expires, no_update_current) + player_status_cache[player_id] = nil local code = [[ INSERT INTO player_status_log (player_id, executor_id, status_id, reason, expires, timestamp) VALUES (?, ?, ?, ?, ?, ?) @@ -345,7 +352,10 @@ function verbana.data.set_player_status(player_id, executor_id, status_id, reaso return true end +local ip_status_cache = {} function verbana.data.get_ip_status(ipint, create_if_new) + local cached_status = ip_status_cache[ipint] + if cached_status then return cached_status end local code = [[ SELECT executor.id executor_id , executor.name executor @@ -363,6 +373,7 @@ function verbana.data.get_ip_status(ipint, create_if_new) ]] local table = get_full_ntable(code, 'get ip status', ipint) if #table == 1 then + ip_status_cache[ipint] = table[1] return table[1] elseif #table > 1 then verbana.log('error', 'somehow got more than 1 result when getting current ip status for %s', ipint) @@ -383,6 +394,7 @@ function verbana.data.get_ip_status(ipint, create_if_new) } end function verbana.data.set_ip_status(ipint, executor_id, status_id, reason, expires) + ip_status_cache[ipint] = nil local code = [[ INSERT INTO ip_status_log (ip, executor_id, status_id, reason, expires, timestamp) VALUES (?, ?, ?, ?, ?, ?) @@ -394,7 +406,10 @@ function verbana.data.set_ip_status(ipint, executor_id, status_id, reason, expir return true end +local asn_status_cache = {} function verbana.data.get_asn_status(asn, create_if_new) + local cached_status = asn_status_cache[asn] + if cached_status then return cached_status end local code = [[ SELECT executor.id executor_id , executor.name executor @@ -412,6 +427,7 @@ function verbana.data.get_asn_status(asn, create_if_new) ]] local table = get_full_ntable(code, 'get asn status', asn) if #table == 1 then + asn_status_cache[asn] = table[1] return table[1] elseif #table > 1 then verbana.log('error', 'somehow got more than 1 result when getting current asn status for %s', asn) @@ -432,6 +448,7 @@ function verbana.data.get_asn_status(asn, create_if_new) } end function verbana.data.set_asn_status(asn, executor_id, status_id, reason, expires) + asn_status_cache[asn] = nil local code = [[ INSERT INTO asn_status_log (asn, executor_id, status_id, reason, expires, timestamp) VALUES (?, ?, ?, ?, ?, ?) @@ -672,7 +689,11 @@ function verbana.data.get_all_banned_players() , player_status_log.expires expires FROM player LEFT JOIN player_status_log ON player.id == player_status_log.player_id - WHERE player_status_log.status_id IN (4, 5, 6) - ]] -- TODO: ... hard-coded values? ... - return get_full_ntable(code, 'all banned') + WHERE player_status_log.status_id IN (?, ?, ?) + ]] + return get_full_ntable(code, 'all banned', + verbana.data.player_status.banned, + verbana.data.player_status.tempbanned, + verbana.data.player_status.locked + ) end diff --git a/depends.txt b/depends.txt index 8e3f676..344d804 100644 --- a/depends.txt +++ b/depends.txt @@ -1,2 +1,4 @@ irc? irc2? +sban? +verification? diff --git a/init.lua b/init.lua index 8a52d10..9074982 100644 --- a/init.lua +++ b/init.lua @@ -26,7 +26,7 @@ dofile(verbana.modpath .. '/lib_asn.lua') -- connect to the DB - MAKE SURE TO CLEAN UP ALL "insecure" access points! local sql = verbana.ie.require('lsqlite3') -- TODO what happens if this isn't installed? .... verbana.sql = sql -local db_location = ('%s/verbana.sqlite'):format(minetest.get_worldpath()) -- TODO get path from settings +local db_location = verbana.settings.db_path local db, _, errmsg = sql.open(db_location) if not db then error(('Verbana could not open its database @ %q: %q'):format(db_location, errmsg)) @@ -34,16 +34,16 @@ else verbana.db = db end -minetest.register_on_shutdown(function() +minetest.register_on_shutdown(verbana.util.safe(function() local ret_code = db:close() if ret_code ~= sql.OK then verbana.log('error', 'Error closing DB: %s', db:error_message()) end -end) +end)) -- core -dofile(verbana.modpath .. '/chat.lua') -dofile(verbana.modpath .. '/data.lua') +dofile(verbana.modpath .. '/data.lua') -- data must go first +dofile(verbana.modpath .. '/chat.lua') -- chat must go before login_handling dofile(verbana.modpath .. '/login_handling.lua') dofile(verbana.modpath .. '/commands.lua') diff --git a/lib_asn.lua b/lib_asn.lua index 58c9dfa..dca4d1c 100644 --- a/lib_asn.lua +++ b/lib_asn.lua @@ -1,19 +1,15 @@ -if not verbana then verbana = {} end -if not verbana.modpath then verbana.modpath = '.' end -if not verbana.ip then dofile(verbana.modpath .. '/lib_ip.lua') end -if not verbana.log then function verbana.log(_, message, ...) print(message:format(...)) end end -verbana.asn = {} +verbana.lib_asn = {} -local ASN_DESCRIPTION_FILE = 'data-used-autnums' -local NETWORK_ASN_FILE = 'data-raw-table' +local lib_ip = verbana.lib_ip +local settings = verbana.settings local load_file = verbana.util.load_file -- map from ASN (integer) to description (string) -verbana.asn.description = {} +verbana.lib_asn.description = {} local function refresh_asn_descriptions() - local contents = load_file(('%s/%s'):format(verbana.modpath, ASN_DESCRIPTION_FILE)) + local contents = load_file(settings.asn_description_path) if not contents then return end local description = {} @@ -27,16 +23,16 @@ local function refresh_asn_descriptions() end end - verbana.asn.description = description + verbana.lib_asn.description = description return true end -- array of values like {starting_ip (integer), ending_ip (integer), ASN (integer)} -- ip ranges should be sorted and not overlap. -verbana.asn.network = {} +verbana.lib_asn.network = {} local function refresh_asn_table() - local contents = load_file(('%s/%s'):format(verbana.modpath, NETWORK_ASN_FILE)) + local contents = load_file(settings.asn_data_path) if not contents then return end local networks = {} @@ -46,7 +42,7 @@ local function refresh_asn_table() verbana.log('warning', 'could not interpret network line "%s"', line) else asn = tonumber(asn) - local start, end_ = verbana.ip.netstr_to_bounds(net) + local start, end_ = lib_ip.netstr_to_bounds(net) if #networks == 0 then table.insert(networks, {start, end_, asn}) @@ -79,11 +75,11 @@ local function refresh_asn_table() end end - verbana.asn.network = networks + verbana.lib_asn.network = networks return true end -function verbana.asn.refresh() +function verbana.lib_asn.refresh() local start = os.clock() if not refresh_asn_descriptions() then return false end if not refresh_asn_table() then return false end @@ -92,7 +88,7 @@ function verbana.asn.refresh() return true end -if not verbana.asn.refresh() then +if not verbana.lib_asn.refresh() then error('Verbana could not load ASN data') end @@ -100,7 +96,7 @@ local function find(ipint) -- binary search -- TODO we could possibly weight the "middle" based in where ipint falls in [0,2**32). -- TODO see e.g. https://en.wikipedia.org/wiki/Interpolation_search - local t = verbana.asn.network + local t = verbana.lib_asn.network local low = 1 local high = #t while low <= high do @@ -120,21 +116,21 @@ local function find(ipint) -- not found, return nil end -function verbana.asn.lookup(ipstr) +function verbana.lib_asn.lookup(ipstr) local ipint if type(ipstr) == 'number' then ipint = ipstr else - ipint = verbana.ip.ipstr_to_ipint(ipstr) + ipint = lib_ip.ipstr_to_ipint(ipstr) end local asn = find(ipint) if asn then - return asn, (verbana.asn.description[asn] or 'Not a known ASN') + return asn, (verbana.lib_asn.description[asn] or 'Not a known ASN') else return 0, 'Not a known ASN' end end -function verbana.asn.get_description(asn) - return verbana.asn.description[asn] or 'Not a known ASN' +function verbana.lib_asn.get_description(asn) + return verbana.lib_asn.description[asn] or 'Not a known ASN' end diff --git a/lib_geo.lua b/lib_geo.lua deleted file mode 100644 index e69de29..0000000 diff --git a/lib_ip.lua b/lib_ip.lua index 67e1f37..9b62196 100644 --- a/lib_ip.lua +++ b/lib_ip.lua @@ -1,8 +1,8 @@ if not verbana then verbana = {} end -verbana.ip = {} +verbana.lib_ip = {} -function verbana.ip.is_valid_ip(ipstr) +function verbana.lib_ip.is_valid_ip(ipstr) local a, b, c, d = ipstr:match('^(%d+)%.(%d+)%.(%d+)%.(%d+)$') a = tonumber(a) b = tonumber(b) @@ -12,12 +12,12 @@ function verbana.ip.is_valid_ip(ipstr) return 0 <= a and a < 256 and 0 <= b and b < 256 and 0 <= c and c < 256 and 0 <= d and d < 256 end -function verbana.ip.ipstr_to_ipint(ipstr) +function verbana.lib_ip.ipstr_to_ipint(ipstr) local a, b, c, d = ipstr:match('^(%d+)%.(%d+)%.(%d+)%.(%d+)$') return (tonumber(a) * 16777216) + (tonumber(b) * 65536) + (tonumber(c) * 256) + tonumber(d) end -function verbana.ip.ipint_to_ipstr(number) +function verbana.lib_ip.ipint_to_ipstr(number) local d = number % 256 number = math.floor(number / 256) local c = number % 256 @@ -27,9 +27,9 @@ function verbana.ip.ipint_to_ipstr(number) return ('%u.%u.%u.%u'):format(a, b, c, d) end -function verbana.ip.netstr_to_bounds(ipnet) +function verbana.lib_ip.netstr_to_bounds(ipnet) local ip, net = ipnet:match('^(.*)/(%d+)$') - local start = verbana.ip.ipstr_to_ipint(ip) + local start = verbana.lib_ip.ipstr_to_ipint(ip) net = tonumber(net) local end_ = start + (2 ^ (32 - net)) - 1 return start, end_ diff --git a/login_handling.lua b/login_handling.lua index a944ec8..718654c 100644 --- a/login_handling.lua +++ b/login_handling.lua @@ -3,43 +3,54 @@ local function table_is_empty(t) return true end -local USING_VERIFICATION_JAIL = verbana.settings.verification_jail and verbana.settings.verification_jail_period +local data = verbana.data +local lib_asn = verbana.lib_asn +local lib_ip = verbana.lib_ip +local log = verbana.log +local privs = verbana.privs +local settings = verbana.settings +local util = verbana.util + +local safe = util.safe + +local USING_VERIFICATION_JAIL = settings.verification_jail and settings.verification_jail_period +local verification_jail = settings.verification_jail +local spawn_pos = settings.spawn_pos +local unverified_spawn_pos = settings.unverified_spawn_pos +local verification_jail_period = settings.verification_jail_period -local timer = 0 -local verification_jail = verbana.settings.verification_jail local check_player_privs = minetest.check_player_privs -local spawn_pos = verbana.settings.spawn_pos -local unverified_spawn_pos = verbana.settings.unverified_spawn_pos -local verification_jail_period = verbana.settings.verification_jail_period local function should_rejail(player, player_status) - if player_status.status_id ~= verbana.data.player_status.unverified.id then + if player_status.status_id ~= data.player_status.unverified.id then return false end local pos = player:get_pos() return not ( - verification_jail.x[1] <= pos.x and pos.x <= verification_jail.x[2] and - verification_jail.y[1] <= pos.y and pos.y <= verification_jail.y[2] and - verification_jail.z[1] <= pos.z and pos.z <= verification_jail.z[2] + verification_jail[1].x <= pos.x and pos.x <= verification_jail[2].x and + verification_jail[1].y <= pos.y and pos.y <= verification_jail[2].y and + verification_jail[1].z <= pos.z and pos.z <= verification_jail[2].z ) end local function should_unjail(player, player_status) - if player_status.status_id == verbana.data.player_status.unverified.id then + if player_status.status_id == data.player_status.unverified.id then return false - elseif verbana.privs.is_privileged(player:get_player_name()) then + elseif privs.is_privileged(player:get_player_name()) then return false end local pos = player:get_pos() return ( - verification_jail.x[1] <= pos.x and pos.x <= verification_jail.x[2] and - verification_jail.y[1] <= pos.y and pos.y <= verification_jail.y[2] and - verification_jail.z[1] <= pos.z and pos.z <= verification_jail.z[2] + verification_jail[1].x <= pos.x and pos.x <= verification_jail[2].x and + verification_jail[1].y <= pos.y and pos.y <= verification_jail[2].y and + verification_jail[1].z <= pos.z and pos.z <= verification_jail[2].z ) end + if USING_VERIFICATION_JAIL then + local timer = 0 minetest.register_globalstep(function(dtime) timer = timer + dtime; if timer < verification_jail_period then @@ -47,51 +58,56 @@ if USING_VERIFICATION_JAIL then end timer = 0 for _, player in ipairs(minetest.get_connected_players()) do - -- TODO this is pretty heavy local name = player:get_player_name() - local player_id = verbana.data.get_player_id(name) - local player_status = verbana.data.get_player_status(player_id) + local player_id = data.get_player_id(name) -- cached, so not heavy + local player_status = data.get_player_status(player_id) -- cached, so not heavy if should_rejail(player, player_status) then - player:set_pos(unverified_spawn_pos) + log('action', 'rejailing %s', name) + if not settings.debug_mode then + player:set_pos(unverified_spawn_pos) + end elseif should_unjail(player, player_status) then - player:set_pos(spawn_pos) + log('action', 'unjailing %s', name) + if not settings.debug_mode then + player:set_pos(spawn_pos) + end end end end) end -minetest.register_on_prejoinplayer(function(name, ipstr) +minetest.register_on_prejoinplayer(safe(function(name, ipstr) -- return a string w/ the reason for refusal; otherwise return nothing - verbana.log('action', 'prejoin: %s %s', name, ipstr) - local ipint = verbana.ip.ipstr_to_ipint(ipstr) - local asn, asn_description = verbana.asn.lookup(ipint) + log('action', 'prejoin: %s %s', name, ipstr) + local ipint = lib_ip.ipstr_to_ipint(ipstr) + local asn, asn_description = lib_asn.lookup(ipint) - local player_id = verbana.data.get_player_id(name, true) -- will create one if none exists - local player_status = verbana.data.get_player_status(player_id, true) - local ip_status = verbana.data.get_ip_status(ipint, true) -- will create one if none exists - local asn_status = verbana.data.get_asn_status(asn, true) -- will create one if none exists + local player_id = data.get_player_id(name, true) -- will create one if none exists + local player_status = data.get_player_status(player_id, true) + local ip_status = data.get_ip_status(ipint, true) -- will create one if none exists + local asn_status = data.get_asn_status(asn, true) -- will create one if none exists -- check and clear temporary statuses local now = os.time() if player_status.name == 'tempbanned' then local expires = player_status.expires or now if now >= expires then - verbana.data.unban_player(player_id, player_status.executor_id, 'temp ban expired') - player_status = verbana.data.get_player_status(player_id) -- refresh player status + data.unban_player(player_id, player_status.executor_id, 'temp ban expired') + player_status = data.get_player_status(player_id) -- refresh player status end end if ip_status.name == 'tempblocked' then local expires = ip_status.expires or now if now >= expires then - verbana.data.unblock_ip(ipint, ip_status.executor_id, 'temp block expired') - ip_status = verbana.data.get_ip_status(ipint) -- refresh ip status + data.unblock_ip(ipint, ip_status.executor_id, 'temp block expired') + ip_status = data.get_ip_status(ipint) -- refresh ip status end end if asn_status.name == 'tempblocked' then local expires = asn_status.expires or now if now >= expires then - verbana.data.unblock_asn(asn, asn_status.executor_id, 'temp block expired') - asn_status = verbana.data.get_asn_status(asn) -- refresh asn status + data.unblock_asn(asn, asn_status.executor_id, 'temp block expired') + asn_status = data.get_asn_status(asn) -- refresh asn status end end @@ -103,7 +119,7 @@ minetest.register_on_prejoinplayer(function(name, ipstr) if player_status.name == 'whitelisted' then -- if the player is whitelisted, let them in. - elseif verbana.settings.privs_to_whitelist and minetest.check_player_privs(name, verbana.settings.privs_to_whitelist) then + elseif settings.whitelisted_privs and minetest.check_player_privs(name, settings.whitelisted_privs) then -- if the player has a whitelisted priv, let them in. elseif player_status.name == 'banned' then local reason = player_status.reason @@ -171,7 +187,7 @@ minetest.register_on_prejoinplayer(function(name, ipstr) -- if the player is new, let them in (truly new players will require verification) -- else if the player has never connected from this ip/asn, prevent them from connecting -- else let them in (mods will get an alert) - local has_assoc = verbana.data.has_asn_assoc(player_id, asn) or verbana.data.has_ip_assoc(player_id, ipint) + local has_assoc = data.has_asn_assoc(player_id, asn) or data.has_ip_assoc(player_id, ipint) if not has_assoc then -- note: if 'suspicious' is true, then 'return_value' should be nil before this return_value = 'Suspicious activity detected.' @@ -179,15 +195,17 @@ minetest.register_on_prejoinplayer(function(name, ipstr) end if return_value then - verbana.data.log(player_id, ipint, asn, false) - verbana.log('action', 'Connection of %s from %s (A%s) denied because %q', name, ipstr, asn, return_value) - return return_value + data.log(player_id, ipint, asn, false) + log('action', 'Connection of %s from %s (A%s) denied because %q', name, ipstr, asn, return_value) + if not settings.debug_mode then + return return_value + end else - verbana.log('action', 'Connection of %s from %s (A%s) allowed', name, ipstr, asn) - verbana.data.log(player_id, ipint, asn, true) - verbana.data.assoc(player_id, ipint, asn) + log('action', 'Connection of %s from %s (A%s) allowed', name, ipstr, asn) + data.log(player_id, ipint, asn, true) + data.assoc(player_id, ipint, asn) end -end) +end)) local function move_to(name, pos, max_tries) max_tries = max_tries or 5 @@ -196,7 +214,10 @@ local function move_to(name, pos, max_tries) -- get the player again here, in case they have disconnected local player = minetest.get_player_by_name(name) if player then - player:set_pos(pos) + log('action', 'moving %s to %s', name, minetest.pos_to_string(pos)) + if not settings.debug_mode then + player:set_pos(pos) + end elseif tries < max_tries then tries = tries + 1 minetest.after(1, f) @@ -205,63 +226,74 @@ local function move_to(name, pos, max_tries) f() end -minetest.register_on_newplayer(function(player) +minetest.register_on_newplayer(safe(function(player) local name = player:get_player_name() local ipstr = minetest.get_player_ip(name) - local ipint = verbana.ip.ipstr_to_ipint(ipstr) - local asn = verbana.asn.lookup(ipint) - local player_id = verbana.data.get_player_id(name) - local ip_status = verbana.data.get_ip_status(ipint) - local asn_status = verbana.data.get_asn_status(asn) + local ipint = lib_ip.ipstr_to_ipint(ipstr) + local asn = lib_asn.lookup(ipint) + local player_id = data.get_player_id(name) + local ip_status = data.get_ip_status(ipint) + local asn_status = data.get_asn_status(asn) local need_to_verify = ( - verbana.settings.universal_verification or + settings.universal_verification or ip_status.name == 'suspicious' or (asn_status.name == 'suspicious' and ip_status.name ~= 'trusted') ) if need_to_verify then - if not verbana.data.set_player_status(player_id, verbana.data.verbana_player_id, verbana.data.player_status.unverified.id, 'new player connected from suspicious network') then - verbana.log('error', 'error setting unverified status on %s', name) + if not data.set_player_status(player_id, data.verbana_player_id, data.player_status.unverified.id, 'new player connected from suspicious network') then + log('error', 'error setting unverified status on %s', name) + end + if not settings.debug_mode then + minetest.set_player_privs(name, settings.unverified_privs) end - minetest.set_player_privs(name, verbana.settings.unverified_privs) - player:set_pos(unverified_spawn_pos) -- wait a second before moving the player to the verification area -- because other mods sometimes try to move them around as well - minetest.after(1, function() move_to(name, unverified_spawn_pos) end) - verbana.log('action', 'new player %s sent to verification', name) + minetest.after(1, move_to, name, unverified_spawn_pos) + log('action', 'new player %s sent to verification', name) else - verbana.data.set_player_status( + data.set_player_status( player_id, - verbana.data.verbana_player_id, - verbana.data.player_status.default.id, + data.verbana_player_id, + data.player_status.default.id, 'new player' ) - verbana.log('action', 'new player %s', name) + log('action', 'new player %s', name) end -end) +end)) -minetest.register_on_joinplayer(function(player) +minetest.register_on_joinplayer(safe(function(player) local name = player:get_player_name() + local player_id = data.get_player_id(name) + local player_status = data.get_player_status(player_id) local ipstr = minetest.get_player_ip(name) - local ipint = verbana.ip.ipstr_to_ipint(ipstr) - local asn, asn_description = verbana.asn.lookup(ipint) - if minetest.check_player_privs(name, {[verbana.privs.unverified]=true}) then + local ipint = lib_ip.ipstr_to_ipint(ipstr) + local asn, asn_description = lib_asn.lookup(ipint) + local is_unverified = player_status.status_id == data.player_status.unverified.id + if is_unverified then verbana.chat.tell_mods(('*** Player %s from A%s (%s) is unverified.'):format(name, asn, asn_description)) end if USING_VERIFICATION_JAIL then - local player_id = verbana.data.get_player_id(name) - local player_status = verbana.data.get_player_status(player_id) - if should_rejail(player, player_status) then - player:set_pos(unverified_spawn_pos) + log('action', 'rejailing %s', name) + if not settings.debug_mode then + player:set_pos(unverified_spawn_pos) + end elseif should_unjail(player, player_status) then - player:set_pos(spawn_pos) + log('action', 'unjailing %s', name) + if not settings.debug_mode then + player:set_pos(spawn_pos) + end end end -end) +end)) +minetest.register_on_auth_fail(safe(function(name, ipstr) + log('action', 'auth failure: %s %s', name, ipstr) + local ipint = lib_ip.ipstr_to_ipint(ipstr) + local asn = lib_asn.lookup(ipint) + local player_id = data.get_player_id(name, true) -- will create one if none exists -minetest.register_on_auth_fail(function(name, ip) - -- TODO log failure -end) + data.log(player_id, ipint, asn, false) +end)) diff --git a/mod.conf b/mod.conf index 21cd831..78d3697 100644 --- a/mod.conf +++ b/mod.conf @@ -1,4 +1,4 @@ name=verbana description=Verification and Banning mod depends= -optional_depends=irc,irc2 +optional_depends=irc,irc2,sban,verification diff --git a/privs.lua b/privs.lua index 2f5a3ef..496f4ea 100644 --- a/privs.lua +++ b/privs.lua @@ -1,10 +1,16 @@ if not verbana then verbana = {} end verbana.privs = {} -minetest.register_privilege('ban_admin', 'administrator for verification/bans') +verbana.privs.admin = verbana.settings.admin_priv +verbana.privs.moderator = verbana.settings.moderator_priv -verbana.privs.admin = 'ban_admin' -- TODO load from settings -verbana.privs.moderator = 'basic_privs' -- TODO load from settings +if not minetest.registered_privileges[verbana.privs.admin] then + minetest.register_privilege(verbana.privs.admin, 'Verbana administrator') +end + +if not minetest.registered_privileges[verbana.privs.moderator] then + minetest.register_privilege(verbana.privs.moderator, 'Verbana moderator') +end function verbana.privs.is_admin(name) return minetest.check_player_privs(name, {[verbana.privs.admin] = true}) diff --git a/settings.lua b/settings.lua index 6f29c5b..ffadeb3 100644 --- a/settings.lua +++ b/settings.lua @@ -2,20 +2,66 @@ verbana.settings = {} local settings = minetest.settings +function verbana.settings.set_universal_verification(value) + if type(value) == 'boolean' then + settings:set_bool('verbana.universal_verification', value) + else + verbana.log('warning', 'tried to set universal verification to %q', value) + end +end + +local function get_setting(name, default) + local setting = settings:get('verbana.db_path') + if not setting or setting == '' then + return default + end + return setting +end + local function get_jail_bounds() + -- (x1,y1,z1),(x2,y2,z2) local bounds = settings:get('verbana.jail_bounds') if not bounds or bounds == '' then return nil end - + local x1, y1, z1, x2, y2, z2 = bounds:match( + '^%s*%(%s*(%d+)%s*,%s*(%d+)%s*,%s*(%d+)%s*%),%(%s*(%d+)%s*,%s*(%d+)%s*,%s*(%d+)%s*%)%s*$' + ) + if not x1 then + verbana.log('warning', 'The setting of verbana.jail_bounds %q is invalid, ignoring.') + return nil + end + x1 = tonumber(x1) + y1 = tonumber(y1) + z1 = tonumber(z1) + x2 = tonumber(x2) + y2 = tonumber(y2) + z2 = tonubmer(z2) + if x1 > x2 then x1, x2 = x2, x1 end + if y1 > y2 then y1, y2 = y2, y1 end + if z1 > z2 then z1, z2 = z2, z1 end + return {vector.new(x1, y1, z1), vector.new(x2, y2, z2)} end -verbana.settings.verified_privs = minetest.string_to_privs(settings:get('default_privs') or '') -verbana.settings.unverified_privs = minetest.string_to_privs(settings:get('verbana.unverified_privs') or '') -verbana.settings.privs_to_whitelist = minetest.string_to_privs(settings:get('verbana.privs_to_whitelist') or '') -verbana.settings.universal_verification = settings:get_bool('verbana.universal_verification', false) +verbana.settings.db_path = get_setting('verbana.db_path', ('%s/verbana.sqlite'):format(minetest.get_worldpath())) +verbana.settings.asn_description_path = get_setting('verbana.asn_description_path', ('%s/%s'):format(verbana.modpath, 'data-used-autnums')) +verbana.settings.asn_data_path = get_setting('verbana.asn_data_path', ('%s/%s'):format(verbana.modpath, 'data-raw-table')) -verbana.settings.spawn_pos = minetest.string_to_pos(settings:get('static_spawnpoint') or '(0,0,0)') -verbana.settings.unverified_spawn_pos = minetest.string_to_pos(settings:get('verbana.unverified_spawn_pos') or '(0,0,0)') +verbana.settings.admin_priv = get_setting('verbana.admin_priv', 'ban_admin') +verbana.settings.moderator_priv = get_setting('verbana.moderator_priv', 'basic_privs') + +verbana.settings.verified_privs = minetest.string_to_privs(get_setting('default_privs', 'shout,interact')) +verbana.settings.unverified_privs = minetest.string_to_privs(get_setting('verbana.unverified_privs', 'shout')) +verbana.settings.whitelisted_privs = minetest.string_to_privs(get_setting('verbana.whitelisted_privs', '')) +if #verbana.settings.whitelisted_privs == 0 then verbana.settings.whitelisted_privs = nil end + +verbana.settings.spawn_pos = minetest.string_to_pos(get_setting('static_spawnpoint', '(0,0,0)')) +verbana.settings.unverified_spawn_pos = minetest.string_to_pos(get_setting('verbana.unverified_spawn_pos', '(0,0,0)')) + +verbana.settings.universal_verification = settings:get_bool('verbana.universal_verification', false) verbana.settings.jail_bounds = get_jail_bounds() -verbana.settings.jail_check_period = settings:get('static_spawnpoint') +verbana.settings.jail_check_period = get_setting('verbana.jail_check_period') + +-- TODO: remove the default 'true' setting when we are ready +verbana.settings.debug_mode = get_setting('verbana.debug_mode', 'true') == 'true' + diff --git a/settingtypes.txt b/settingtypes.txt index cd642bb..b69ef24 100644 --- a/settingtypes.txt +++ b/settingtypes.txt @@ -1,3 +1,12 @@ +# The location of the DB (default: root of the world) +verbana.db_path (Location of the Verbana SQLite DB) string + +# The location of the ASN description file (default: in the verbana mod folder) +verbana.asn_description_path (Location of the ASN description file) string + +# The location of the ASN data file (default: in the verbana mod folder) +verbana.asn_data_path (Location of the ASN data file) string + # The privilege of the Verbana adminstrator(s) verbana.admin_priv (Priv required for Verbana administration) string ban_admin @@ -8,13 +17,13 @@ verbana.moderator_priv (Priv required for Verbana moderation) string basic_privs verbana.unverified_privs (Privs for unverified users) string shout # Comma delimited. If a player has all of the listed privileges, they skip suspicious network checks. -verbana.privs_to_whitelist (Privs required to bypass suspicious network checks) string +verbana.whitelisted_privs (Privs required to bypass suspicious network checks) string # Coordinates where unverified players spawn verbana.unverified_spawn_pos (Where unverified players spawn) string 0,0,0 # Coordinates bounding the verification jail area, if it exists. -# Format: x1,x2,y1,y2,z1,z2 +# Format: (x1,y1,z1),(x2,y2,z2) verbana.jail_bounds (Bounding box of the verification jail) string # Period between checks that unverified players haven't escaped the verification area diff --git a/util.lua b/util.lua index c67728d..6fc1517 100644 --- a/util.lua +++ b/util.lua @@ -60,3 +60,30 @@ end -- end -- local TODO = 1 / nil --end + +function verbana.util.safe(func) + -- wrap a function w/ logic to avoid crashing the game + return function(...) + local status, out = pcall(func, ...) + if status then + return out + else + verbana.log('warning', 'Error (func): %s', out) + return nil + end + end +end + +function verbana.util.safe_kick_player(caller, player, reason) + local player_name = player:get_player_name() + verbana.log('action', 'kicking %s...', player_name) + if not verbana.settings.debug_mode then + if not minetest.kick_player(player_name, reason) then + player:set_detach() + if not minetest.kick_player(player_name, reason) then + minetest.chat_send_player(caller, ('Failed to kick player %s after detaching!'):format(player_name)) + verbana.log('warning', 'Failed to kick player %s after detaching!', player_name) + end + end + end +end diff --git a/verbana_cl.lua b/verbana_cl.lua deleted file mode 100644 index bf3f707..0000000 --- a/verbana_cl.lua +++ /dev/null @@ -1 +0,0 @@ --- TODO: command line interface to manipulate the DB (e.g. for emergencies)