checkpoint: debug mode (untested)

master
y 2019-07-08 01:59:32 +01:00
parent 939b677300
commit b9e8111929
17 changed files with 459 additions and 303 deletions

View File

@ -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.

2
TODO
View File

@ -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

View File

@ -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)

View File

@ -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='<filename>',
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='<name> | <IP>',
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='<name> [<reason>]',
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='<name> [<reason>]',
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='<name> [<reason>]',
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='<name> [<reason>]',
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='<name> [<reason>]',
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='<name> [<reason>]',
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='<name> <timespan> [<reason>]',
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='<name> [<reason>]',
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='<name> [<reason>]',
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='<name> [<reason>]',
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='<name> [<reason>]',
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='<name> [<reason>]',
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='<asn> <status>',
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='<asn> <status>',
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='<name> [<number>]',
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='<IP> [<number>]',
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='<ASN> [<number>]',
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='<name> [<number>]',
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='<name>',
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='<IP>',
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='<asn>',
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

View File

@ -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

View File

@ -1,2 +1,4 @@
irc?
irc2?
sban?
verification?

View File

@ -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')

View File

@ -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

View File

View File

@ -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_

View File

@ -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))

View File

@ -1,4 +1,4 @@
name=verbana
description=Verification and Banning mod
depends=
optional_depends=irc,irc2
optional_depends=irc,irc2,sban,verification

View File

@ -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})

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -1 +0,0 @@
-- TODO: command line interface to manipulate the DB (e.g. for emergencies)