minetest-verbana/login_handling.lua

362 lines
15 KiB
Lua

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 iso_date = util.iso_date
local spawn_pos = settings.spawn_pos
local unverified_spawn_pos = settings.unverified_spawn_pos
local jail_bounds = settings.jail_bounds
local jail_check_period = settings.jail_check_period
local USING_VERIFICATION_JAIL = jail_bounds and jail_check_period
local function should_rejail(player, player_status)
if player_status.id ~= data.player_status.unverified.id then
return false
end
local pos = player:get_pos()
return not (
jail_bounds[1].x - 1 <= pos.x and pos.x <= jail_bounds[2].x + 1 and
jail_bounds[1].y - 1 <= pos.y and pos.y <= jail_bounds[2].y + 1 and
jail_bounds[1].z - 1 <= pos.z and pos.z <= jail_bounds[2].z + 1
)
end
local function should_unjail(player, player_status)
if player_status.id == data.player_status.unverified.id then
return false
elseif privs.is_privileged(player:get_player_name()) then
return false
end
local pos = player:get_pos()
return (
jail_bounds[1].x - 1 <= pos.x and pos.x <= jail_bounds[2].x + 1 and
jail_bounds[1].y - 1 <= pos.y and pos.y <= jail_bounds[2].y + 1 and
jail_bounds[1].z - 1 <= pos.z and pos.z <= jail_bounds[2].z + 1
)
end
if USING_VERIFICATION_JAIL then
local timer = 0
log('action','initializing rejail globalstep')
minetest.register_globalstep(function(dtime)
timer = timer + dtime;
if timer < jail_check_period then
return
end
timer = 0
for _, player in ipairs(minetest.get_connected_players()) do
local name = player:get_player_name()
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
log('action', 'rejailing %s', name)
verbana.chat.tell_mods('%s has escaped verification jail, and is being sent back', name)
if not settings.debug_mode then
player:set_pos(unverified_spawn_pos)
end
elseif should_unjail(player, player_status) then
log('action', 'unjailing %s', name)
verbana.chat.tell_mods('%s has been removed from verification jail', name)
if not settings.debug_mode then
player:set_pos(spawn_pos)
end
end
end
end)
end
minetest.register_on_prejoinplayer(safe(function(name, ipstr)
-- return a string w/ the reason for refusal; otherwise return nothing
local ipint = lib_ip.ipstr_to_ipint(ipstr)
local asn, asn_description = lib_asn.lookup(ipint)
log('action', '[prejoin] %s %s A%s (%s)', name, ipstr, asn, asn_description)
local player_id = data.get_player_id(name, true) -- will create one if none exists
if not player_id then
log('error', '[prejoin] could not retrieve or create id for player %s', name)
return -- let them in... it's not their fault :\
end
local player_status, is_new_player = data.get_player_status(player_id, true)
data.register_ip(ipint)
local ip_status = data.get_ip_status(ipint, true) -- will create one if none exists
data.register_asn(asn)
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.expires and now >= player_status.expires then
local prev_status_name = data.player_status_name[player_status.id]
log('action', '[prejoin] expiring temp %s of %s', prev_status_name, name)
local new_status_id
if player_status.id == data.player_status.suspicious.id then
new_status_id = data.player_status.default.id
else
new_status_id = data.player_status.suspicious.id
end
data.set_player_status(player_id, player_status.executor_id, new_status_id, ('temp %s expired'):format(prev_status_name))
player_status = data.get_player_status(player_id) -- refresh player status
end
if ip_status.expires and now >= ip_status.expires then
local prev_status_name = data.ip_status_name[ip_status.id]
log('action', '[prejoin] expiring temp %s of %s', prev_status_name, ipstr)
local new_status_id
if ip_status.id == data.ip_status.suspicious.id then
new_status_id = data.ip_status.default.id
else
new_status_id = data.ip_status.suspicious.id
end
data.set_ip_status(ipint, ip_status.executor_id, new_status_id, ('temp %s expired'):format(prev_status_name))
ip_status = data.get_ip_status(ipint) -- refresh ip status
end
if asn_status.expires and now >= asn_status.expires then
local prev_status_name = data.asn_status_name[asn_status.id]
log('action', '[prejoin] expiring temp %s of A%s', prev_status_name, asn)
local new_status_id
if asn_status.id == data.asn_status.suspicious.id then
new_status_id = data.asn_status.default.id
else
new_status_id = data.asn_status.suspicious.id
end
data.set_asn_status(asn, asn_status.executor_id, new_status_id, ('temp %s expired'):format(prev_status_name))
asn_status = data.get_asn_status(asn) -- refresh asn status
end
-- figure out if the player is suspicious or should be outright rejected
local suspicious = false
local return_value
if player_status.id == data.player_status.banned.id then
local reason = player_status.reason
if player_status.expires then
local expires = iso_date(player_status.expires or now)
if reason and reason ~= '' then
return_value = ('Account %q is banned until %s because %q.'):format(name, expires, reason)
else
return_value = ('Account %q is banned until %s.'):format(name, expires)
end
else
if reason and reason ~= '' then
return_value = ('Account %q is banned because %q.'):format(name, reason)
else
return_value = ('Account %q is banned.'):format(name)
end
end
elseif player_status.id == data.player_status.whitelisted.id then
-- if the player is whitelisted, let them in.
log('action', '[prejoin] %s is whitelisted', name)
elseif settings.whitelisted_privs and minetest.check_player_privs(name, settings.whitelisted_privs) then
-- if the player has a whitelisted priv, let them in.
log('action', '[prejoin] %s whitelisted by privs', name)
elseif ip_status.id == data.ip_status.trusted.id then
-- let them in
log('action', '[prejoin] %s is trusted', ipstr)
elseif ip_status.id == data.ip_status.suspicious.id then
suspicious = true
log('action', '[prejoin] %s is suspicious', ipstr)
elseif ip_status.id == data.ip_status.blocked.id then
local reason = ip_status.reason
if ip_status.expires then
local expires = iso_date(ip_status.expires or now)
if reason and reason ~= '' then
return_value = ('IP %q is blocked until %s because %q.'):format(ipstr, expires, reason)
else
return_value = ('IP %q is blocked until %s.'):format(ipstr, expires)
end
else
if reason and reason ~= '' then
return_value = ('IP %q is blocked because %q.'):format(ipstr, reason)
else
return_value = ('IP %q is blocked.'):format(ipstr)
end
end
elseif asn_status.id == data.asn_status.suspicious.id then
suspicious = true
log('action', '[prejoin] A%s is suspicious', asn)
elseif asn_status.id == data.asn_status.blocked.id then
local reason = asn_status.reason
if asn_status.expires then
local expires = iso_date(asn_status.expires or now)
if reason and reason ~= '' then
return_value = ('Network %s (%s) is blocked until %s because %q.'):format(asn, asn_description, expires, reason)
else
return_value = ('Network %s (%s) is blocked until %s.'):format(asn, asn_description, expires)
end
else
if reason and reason ~= '' then
return_value = ('Network %s (%s) is blocked because %q.'):format(asn, asn_description, reason)
else
return_value = ('Network %s (%s) is blocked.'):format(asn, asn_description)
end
end
end
if suspicious and not is_new_player then
-- 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 = 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.'
end
end
if return_value then
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
log('action', 'Connection of %s from %s (A%s) allowed', name, ipstr, asn)
data.log(player_id, ipint, asn, true)
-- don't log associations here, because the login may still fail due to an invalid password
end
end))
local function move_to(name, pos, max_tries)
max_tries = max_tries or 5
local tries = 0
local function f()
-- get the player again here, in case they have disconnected
local player = minetest.get_player_by_name(name)
if player then
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)
end
end
f()
end
minetest.register_on_newplayer(safe(function(player)
local name = player:get_player_name()
local player_id = data.get_player_id(name)
local ipstr = data.fumble_about_for_an_ip(name, player_id)
local need_to_verify
if not ipstr then
-- if we can't figure out where they're coming from, force verification
log('warning', 'could not discover an IP for new player %s; forcing verification', name)
need_to_verify = true
else
local ipint = lib_ip.ipstr_to_ipint(ipstr)
local ip_status = data.get_ip_status(ipint)
local asn = lib_asn.lookup(ipint)
local asn_status = data.get_asn_status(asn)
need_to_verify = (
settings.universal_verification or
ip_status.id == data.ip_status.suspicious.id or
(asn_status.id == data.asn_status.suspicious.id and
ip_status.id ~= data.ip_status.trusted.id)
)
end
if need_to_verify then
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
-- 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, move_to, name, unverified_spawn_pos)
log('action', 'new player %s sent to verification', name)
else
data.get_player_status(player_id, true) -- create a new status if they don't have one
log('action', 'new player %s', name)
end
end))
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 is_unverified = player_status.id == data.player_status.unverified.id
local ipstr = data.fumble_about_for_an_ip(name)
local ipint = lib_ip.ipstr_to_ipint(ipstr)
local asn, _ = lib_asn.lookup(ipint)
data.assoc(player_id, ipint, asn)
if is_unverified then
if ipstr then
local ipint = lib_ip.ipstr_to_ipint(ipstr)
local asn, asn_description = lib_asn.lookup(ipint)
verbana.chat.tell_mods(('*** Player %s from A%s (%s) is unverified.'):format(name, asn, asn_description))
else
verbana.chat.tell_mods(('*** Player %s is unverified.'):format(name))
end
elseif player_status.id == data.player_status.suspicious.id then
if ipstr then
local ipint = lib_ip.ipstr_to_ipint(ipstr)
local asn, asn_description = lib_asn.lookup(ipint)
verbana.chat.tell_mods(('*** Player %s from A%s (%s) is suspicious.'):format(name, asn, asn_description))
else
verbana.chat.tell_mods(('*** Player %s is suspicious.'):format(name))
end
end
if USING_VERIFICATION_JAIL then
if should_rejail(player, player_status) then
log('action', 'spawning %s in verification jail', name)
if not settings.debug_mode then
player:set_pos(unverified_spawn_pos)
end
elseif should_unjail(player, player_status) then
log('action', 'removing %s from verification jail on spawn', name)
if not settings.debug_mode then
player:set_pos(spawn_pos)
end
end
end
end))
if minetest.register_on_authplayer then
minetest.register_on_authplayer(safe(function(name, ipstr, is_success)
if is_success then
log('action', 'auth success: %s %s', name, ipstr)
else
log('action', 'auth failure: %s %s', name, ipstr)
end
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
data.register_ip(ipint)
data.register_asn(asn)
data.log(player_id, ipint, asn, false)
end))
elseif minetest.register_on_auth_fail then
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
data.register_ip(ipint)
data.register_asn(asn)
data.log(player_id, ipint, asn, false)
end))
else
-- TODO: not certain anything should go here or not
end