Browse Source

Build 15

- major code reorganization via multiple libraries
- removed extra variables from AuthFilter class
- developed interactive debugger for testing rulesets
- added optional debugger hooks in AuthFilter class
- allowed for overriding preset variables by debugger
- included line-number in results of login filter
- added missing preset variable needed by rulesets
master
Leslie Krause 1 year ago
parent
commit
e59d77e153
5 changed files with 449 additions and 100 deletions
  1. 10
    1
      README.txt
  2. 332
    0
      commands.lua
  3. 47
    36
      filter.lua
  4. 45
    0
      helpers.lua
  5. 15
    63
      init.lua

+ 10
- 1
README.txt View File

@@ -1,4 +1,4 @@
Auth Redux Mod v2.9b
Auth Redux Mod v2.10b
By Leslie Krause

Auth Redux is a drop-in replacement for the builtin authentication handler of Minetest.
@@ -95,6 +95,15 @@ Version 2.9b (26-Jul-2018)
- developed and integrated AuthWatchdog class
- added meta-variables for stateful login filtering

Version 2.10b (29-Jul-2018)
- major code reorganization via multiple libraries
- removed extra variables from AuthFilter class
- developed interactive debugger for testing rulesets
- added optional debugger hooks in AuthFilter class
- allowed for overriding preset variables by debugger
- included line-number in results of login filter
- added missing preset variable needed by rulesets

Installation
----------------------


+ 332
- 0
commands.lua View File

@@ -0,0 +1,332 @@
--------------------------------------------------------
-- Minetest :: Auth Redux Mod v2.10 (auth_rx)
--
-- See README.txt for licensing and release notes.
-- Copyright (c) 2017-2018, Leslie E. Krause
--------------------------------------------------------

-----------------------------------------------------
-- Registered Chat Commands
-----------------------------------------------------

local auth_db, auth_filter -- imported

minetest.register_chatcommand( "filter", {
description = "Enable or disable ruleset-based login filtering, or reload a ruleset definition.",
privs = { server = true },
func = function( name, param )
if param == "" then
return true, "Login filtering is currently " .. ( auth_filter.is_active( ) and "enabled" or "disabled" ) .. "."
elseif param == "disable" then
auth_filter.disable( )
minetest.log( "action", "Login filtering disabled by " .. name .. "." )
return true, "Login filtering is disabled."
elseif param == "enable" then
auth_filter.enable( )
minetest.log( "action", "Login filtering enabled by " .. name .. "." )
return true, "Login filtering is enabled."
elseif param == "reload" then
auth_filter.refresh( )
return true, "Ruleset definition was loaded successfully."
else
return false, "Unknown parameter specified."
end
end
} )

minetest.register_chatcommand( "fdebug", {
description = "Start an interactive debugger for testing ruleset definitions.",
privs = { server = true },
func = function( name, param )
if not minetest.create_form then return false, "This feature is not supported." end

local epoch = os.time( { year = 1970, month = 1, day = 1, hour = 0 } )
local vars = {
__debug = { type = FILTER_TYPE_NUMBER, value = 0 },
name = { type = FILTER_TYPE_STRING, value = "singleplayer" },
addr = { type = FILTER_TYPE_ADDRESS, value = convert_ipv4( "127.0.0.1" ) },
is_new = { type = FILTER_TYPE_BOOLEAN, value = true },
privs_list = { type = FILTER_TYPE_SERIES, value = { } },
users_list = { type = FILTER_TYPE_SERIES, is_auto = true },
cur_users = { type = FILTER_TYPE_NUMBER, is_auto = true },
max_users = { type = FILTER_TYPE_NUMBER, value = get_minetest_config( "max_users" ) },
lifetime = { type = FILTER_TYPE_PERIOD, value = 0 },
sessions = { type = FILTER_TYPE_NUMBER, value = 0 },
failures = { type = FILTER_TYPE_NUMBER, value = 0 },
attempts = { type = FILTER_TYPE_NUMBER, value = 0 },
owner = { type = FILTER_TYPE_STRING, value = get_minetest_config( "name" ) },
uptime = { type = FILTER_TYPE_PERIOD, is_auto = true },
oldlogin = { type = FILTER_TYPE_MOMENT, value = epoch },
newlogin = { type = FILTER_TYPE_MOMENT, value = epoch },
ip_names_list = { type = FILTER_TYPE_SERIES, value = { } },
ip_prelogin = { type = FILTER_TYPE_MOMENT, value = epoch },
ip_oldcheck = { type = FILTER_TYPE_MOMENT, value = epoch },
ip_newcheck = { type = FILTER_TYPE_MOMENT, value = epoch },
ip_failures = { type = FILTER_TYPE_NUMBER, value = 0 },
ip_attempts = { type = FILTER_TYPE_NUMBER, value = 0 }
}
local vars_list = { "__debug", "clock", "name", "addr", "is_new", "privs_list", "users_list", "cur_users", "max_users", "lifetime", "sessions", "failures", "attempts", "owner", "uptime", "oldlogin", "newlogin", "ip_names_list", "ip_prelogin", "ip_oldcheck", "ip_newcheck", "ip_failures", "ip_attempts" }
local datatypes = { [FILTER_TYPE_NUMBER] = "NUMBER", [FILTER_TYPE_STRING] = "STRING", [FILTER_TYPE_BOOLEAN] = "BOOLEAN", [FILTER_TYPE_ADDRESS] = "ADDRESS", [FILTER_TYPE_PERIOD] = "PERIOD", [FILTER_TYPE_MOMENT] = "MOMENT", [FILTER_TYPE_SERIES] = "SERIES" }
local has_prompt = true
local has_output = true
local login_index = 2
local var_index = 1
local temp_file = io.open( minetest.get_worldpath( ) .. "/~greenlist.mt", "w" ):close( )
local temp_filter = AuthFilter( minetest.get_worldpath( ), "~greenlist.mt", function ( err, num )
return num, "The server encountered an internal error.", err
end )

local function clear_prompts( buffer, has_single )
-- clear debug prompts from source code
return string.gsub( buffer, "\n# ====== .- ======\n", "\n", has_single and 1 or nil )
end
local function insert_prompt( buffer, num, err )
-- insert debug prompts into source code
local i = 0
return string.gsub( buffer, "\n", function ( )
i = i + 1
return ( i == num and string.format( "\n# ====== ^ Line %d: %s ^ ======\n", num, err ) or "\n" )
end )
end
local function format_value( value, type )
-- convert values to a human-readable format
if type == FILTER_TYPE_STRING then
return "\"" .. value .. "\""
elseif type == FILTER_TYPE_NUMBER then
return tostring( value )
elseif type == FILTER_TYPE_BOOLEAN then
return "$" .. tostring( value )
elseif type == FILTER_TYPE_PERIOD then
return tostring( math.abs( value ) ) .. "s"
elseif type == FILTER_TYPE_MOMENT then
return "+" .. tostring( value - vars.epoch.value ) .. "s"
elseif type == FILTER_TYPE_ADDRESS then
return table.concat( unpack_address( value ), "." )
elseif type == FILTER_TYPE_SERIES then
return "(" .. string.gsub( table.concat( value, "," ), "[^,]+", "\"%1\"" ) .. ")"
end
end
local function update_vars( )
-- automatically update preset variables
if vars.uptime.is_auto then
vars.uptime.value = minetest.get_server_uptime( ) end
if vars.clock.is_auto then
vars.clock.value = os.time( ) end
if vars.users_list.is_auto then
vars.users_list.value = auth_db.search( true ) end
if vars.cur_users.is_auto then
vars.cur_users.value = #auth_db.search( true ) end
end
local function get_formspec( buffer, status, var_state )
local var_name = vars_list[ var_index ]
local var_type = vars[ var_name ].type
local var_value = vars[ var_name ].value
local var_is_auto = vars[ var_name ].is_auto

local formspec = "size[13.5,8.5]"
.. default.gui_bg
.. default.gui_bg_img
.. "label[0.1,0.0;Ruleset Definition:]"
.. "checkbox[2.6,-0.2;has_output;Show Client Output;" .. tostring( has_output ) .. "]"
.. "checkbox[5.6,-0.2;has_prompt;Show Debug Prompt;" .. tostring( has_prompt ) .. "]"
.. "textarea[0.4,0.5;8.6," .. ( not status and "8.4" or status.user and "5.6" or "7.3" ) .. ";buffer;;" .. minetest.formspec_escape( buffer ) .. "]"
.. "button[0.1,7.8;2,1;export_ruleset;Save]"
.. "button[2.0,7.8;2,1;import_ruleset;Load]"
.. "button[4.0,7.8;2,1;process_ruleset;Process]"
.. "dropdown[6,7.9;2.6,1;login_mode;Normal,New Account,Wrong Password;" .. login_index .. "]"

.. "label[9.0,0.0;Preset Variables:]"
.. "textlist[9.0,0.5;4,4.7;vars_list"

for i, v in pairs( vars_list ) do
formspec = formspec .. ( i == 1 and ";" or "," ) .. minetest.formspec_escape( v .. " = " .. format_value( vars[ v ].value, vars[ v ].type ) )
end
formspec = formspec .. string.format( ";%d;false]", var_index )
.. "label[9.0,5.4;Name:]"
.. "label[9.0,5.9;Type:]"
.. string.format( "label[10.5,5.4;%s]", minetest.colorize( "#BBFF77", "$" .. var_name ) )
.. string.format( "label[10.5,5.9;%s]", datatypes[ var_type ] )
.. "label[9.0,6.4;Value:]"
.. "field[9.2,7.5;4.3,0.25;var_value;;" .. minetest.formspec_escape( format_value( var_value, var_type ) ) .. "]"
.. "button[9.0,7.8;1,1;prev_var;<<]"
.. "button[10.0,7.8;1,1;next_var;>>]"
.. "button[11.8,7.8;1.5,1;set_var;Set]"

if var_is_auto ~= nil then
formspec = formspec .. "checkbox[10.5,6.2;var_is_auto;Auto Update;" .. tostring( var_is_auto ) .. "]"
end

if status then
formspec = formspec .. "box[0.1,6.9;8.4,0.8;#555555]"
.. "label[0.3,7.1;" .. minetest.colorize( status.type == "ERROR" and "#CCCC22" or "#22CC22", status.type .. ": " ) .. status.desc .. "]"
if status.user then
formspec = formspec .. "textlist[0.1,5.5;8.4,1.2;;Access denied. Reason: " .. minetest.formspec_escape( status.user ) .. ";0;false]"
end
end
return formspec
end
local function on_close( meta, player, fields )
login_index = ( { ["Normal"] = 1, ["New Account"] = 2, ["Wrong Password"] = 3 } )[ fields.login_mode ] or 1 -- sanity check

if fields.quit then
os.remove( minetest.get_worldpath( ) .. "/~greenlist.mt" )

elseif fields.vars_list then
local event = minetest.explode_textlist_event( fields.vars_list )
if event.type == "CHG" then
var_index = event.index
minetest.update_form( name, get_formspec( fields.buffer ) )
end

elseif fields.has_prompt then
has_prompt = fields.has_prompt == "true"

elseif fields.has_output then
has_output = fields.has_output == "true"

elseif fields.export_ruleset then
local buffer = clear_prompts( fields.buffer .. "\n", true )
local file = io.open( minetest.get_worldpath( ) .. "/greenlist.mt", "w" )
if not file then
error( "Cannot write to ruleset definition file." )
end
file:write( buffer )
file:close( )
minetest.update_form( name, get_formspec( buffer, { type = "ACTION", desc = "Ruleset definition exported." } ) )

elseif fields.import_ruleset then
local file = io.open( minetest.get_worldpath( ) .. "/greenlist.mt", "r" )
if not file then
error( "Cannot read from ruleset definition file." )
end
minetest.update_form( name, get_formspec( file:read( "*a" ), { type = "ACTION", desc = "Ruleset definition imported." } ) )
file:close( )

elseif fields.process_ruleset then
local status
local buffer = clear_prompts( fields.buffer .. "\n", true ) -- we need a trailing newline, or things will break

-- output ruleset to temp file for processing
local temp_file = io.open( minetest.get_worldpath( ) .. "/~greenlist.mt", "w" )
temp_file:write( buffer )
temp_file:close( )
temp_filter.refresh( )

update_vars( )

if fields.login_mode == "New Account" then
vars.is_new.value = true
vars.privs_list.value = { }
vars.lifetime.value = 0
vars.sessions.value = 0
vars.failures.value = 0
vars.attempts.value = 0
vars.newlogin.value = epoch
vars.oldlogin.value = epoch
else
vars.is_new.value = false
vars.attempts.value = vars.attempts.value + 1
end

-- process ruleset and benchmark performance
local t = minetest.get_us_time( )
local num, res, err = temp_filter.process( vars )
t = ( minetest.get_us_time( ) - t ) / 1000

if err then
if has_prompt then buffer = insert_prompt( buffer, num, err ) end
status = { type = "ERROR", desc = string.format( "%s (line %d).", err, num ), user = has_output and res }

vars.ip_attempts.value = vars.ip_attempts.value + 1
vars.ip_prelogin.value = vars.clock.value
table.insert( vars.ip_names_list.value, vars.name.value )

elseif res then
if has_prompt then buffer = insert_prompt( buffer, num, "Ruleset failed" ) end
status = { type = "ACTION", desc = string.format( "Ruleset failed at line %d (took %0.1f ms).", num, t ), user = has_output and res }

vars.ip_attempts.value = vars.ip_attempts.value + 1
vars.ip_prelogin.value = vars.clock.value
table.insert( vars.ip_names_list.value, vars.name.value )

elseif fields.login_mode == "Wrong Password" then
if has_prompt then buffer = insert_prompt( buffer, num, "Ruleset failed" ) end
status = { type = "ACTION", desc = string.format( "Ruleset failed at line %d (took %0.1f ms).", num, t ), user = has_output and "Invalid password" }

vars.failures.value = vars.failures.value + 1
vars.ip_attempts.value = vars.ip_attempts.value + 1
vars.ip_failures.value = vars.ip_failures.value + 1
vars.ip_prelogin.value = vars.clock.value
vars.ip_newcheck.value = vars.clock.value
if vars.ip_oldcheck.value == epoch then
vars.ip_oldcheck.value = vars.clock.value
end
table.insert( vars.ip_names_list.value, vars.name.value )

else
if has_prompt then buffer = insert_prompt( buffer, num, "Ruleset passed" ) end
status = { type = "ACTION", desc = string.format( "Ruleset passed at line %d (took %0.1f ms).", num, t ) }

if fields.login_mode == "New Account" then
vars.privs_list.value = get_default_privs( )
end
vars.sessions.value = vars.sessions.value + 1
vars.newlogin.value = vars.clock.value
if vars.oldlogin.value == epoch then
vars.oldlogin.value = vars.clock.value
end
vars.ip_failures.value = 0
vars.ip_attempts.value = 0
vars.ip_prelogin.value = epoch
vars.ip_oldcheck.value = epoch
vars.ip_newcheck.value = epoch
vars.ip_names_list.value = { }
end

minetest.update_form( name, get_formspec( buffer, status ) )

elseif fields.next_var or fields.prev_var then
local idx = var_index
local off = fields.next_var and 1 or -1
if off == 1 and idx < #vars_list or off == -1 and idx > 1 then
local v = vars_list[ idx ]
vars_list[ idx ] = vars_list[ idx + off ]
vars_list[ idx + off ] = v
var_index = idx + off
minetest.update_form( name, get_formspec( fields.buffer ) )
end

elseif fields.var_is_auto then
local var_name = vars_list[ var_index ]
vars[ var_name ].is_auto = ( fields.var_is_auto == "true" )

elseif fields.set_var then
local oper = temp_filter.translate( string.trim( fields.var_value ), vars )
local var_name = vars_list[ var_index ]

if oper and var_name == "__debug" and datatypes[ oper.type ] then
-- debug variable can be any value/type
vars.__debug = oper
elseif oper and oper.type == vars[ var_name ].type then
vars[ var_name ].value = oper.value
end

minetest.update_form( name, get_formspec( fields.buffer ) )

end
end

temp_filter.add_preset_vars( vars )
vars.clock.is_auto = true
update_vars( )

minetest.create_form( nil, name, get_formspec( "pass now\n" ), on_close )

return true
end,
} )

return function ( import )
auth_db = import.auth_db
auth_filter = import.auth_filter
end

+ 47
- 36
filter.lua View File

@@ -1,5 +1,5 @@
--------------------------------------------------------
-- Minetest :: Auth Redux Mod v2.10 (auth_rx)
--
-- See README.txt for licensing and release notes.
-- Copyright (c) 2017-2018, Leslie E. Krause
@@ -46,9 +46,6 @@ local redate = function ( ts )
x.isdst = false
return os.time( x )
end
local unpack_address = function ( addr )
return { math.floor( addr / 16777216 ), math.floor( ( addr % 16777216 ) / 65536 ), math.floor( ( addr % 65536 ) / 256 ), addr % 256 }
end

----------------------------
-- StringPattern class
@@ -114,9 +111,9 @@ end
-- AuthFilter class
----------------------------

function AuthFilter( path, name )
function AuthFilter( path, name, debug )
local src
local opt = { is_debug = false, is_strict = true, is_active = true }
local is_active = true
local self = { }

local funcs = {
@@ -154,15 +151,14 @@ function AuthFilter( path, name )
-- private methods
----------------------------

local get_operand, trace, evaluate
local trace, get_operand, evaluate, tokenize

trace = function ( msg, num )
-- TODO: Use 'pcall' for more graceful exception handling?
trace = debug or function ( msg, num )
minetest.log( "error", string.format( "%s (%s/%s, line %d)", msg, path, name, num ) )
return "The server encountered an internal error."
return num, "The server encountered an internal error."
end

get_operand = function ( token, vars )
function get_operand( token, vars )
local t, v, ref

local find_token = function ( pat )
@@ -320,7 +316,7 @@ function AuthFilter( path, name )
return { type = t, value = v }
end

evaluate = function ( rule )
function evaluate( rule )
-- short circuit binary logic to simplify evaluation
local res = ( rule.bool == FILTER_BOOL_AND )
local xor = 0
@@ -339,10 +335,31 @@ function AuthFilter( path, name )
return res
end

function tokenize( line )
-- encode string and pattern literals and function arguments to simplify parsing (order IS significant)
line = string.gsub( line, "\"(.-)\"", function ( str )
return "\"" .. encode_base64( str ) .. ";"
end )
line = string.gsub( line, "'(.-)'", function ( str )
return "'" .. encode_base64( str ) .. ";"
end )
line = string.gsub( line, "/(.-)/([stda]?)", function ( a, b )
return "/" .. encode_base64( a ) .. "," .. ( b == "" and "s" or b ) .. ";"
end )
line = string.gsub( line, "%b()", function ( str )
return "&" .. encode_base64( trim( str ) ) .. ";"
end )
return line
end

----------------------------
-- public methods
----------------------------

self.translate = function ( field, vars )
return get_operand( tokenize( field ), vars )
end

self.refresh = function ( )
local file = io.open( path .. "/" .. name, "r" )
if not file then
@@ -350,37 +367,30 @@ function AuthFilter( path, name )
end
src = { }
for line in file:lines( ) do
-- encode string and pattern literals and function arguments to simplify parsing
line = string.gsub( line, "\"(.-)\"", function ( str )
return "\"" .. encode_base64( str ) .. ";"
end )
line = string.gsub( line, "'(.-)'", function ( str )
return "'" .. encode_base64( str ) .. ";"
end )
line = string.gsub( line, "/(.-)/([stda]?)", function ( a, b )
return "/" .. encode_base64( a ) .. "," .. ( b == "" and "s" or b ) .. ";"
end )
line = string.gsub( line, "%b()", function ( str )
return "&" .. encode_base64( trim( str ) ) .. ";"
end )
-- skip comments (lines beginning with hash character) and blank lines
-- TODO: remove extraneous white space at beginning of lines
table.insert( src, string.byte( line ) ~= 35 and line or "" )
table.insert( src, string.byte( line ) ~= 35 and tokenize( line ) or "" )
end
file:close( file )
end

self.add_preset_vars = function ( vars )
vars[ "clock" ] = { type = FILTER_TYPE_MOMENT, value = os.time( ) }
vars[ "epoch" ] = { type = FILTER_TYPE_MOMENT, value = os.time( { year = 1970, month = 1, day = 1, hour = 0 } ) }
vars[ "true" ] = { type = FILTER_TYPE_BOOLEAN, value = true }
vars[ "false" ] = { type = FILTER_TYPE_BOOLEAN, value = false }
end

self.process = function( vars )
local rule
local note = "Access denied."

if not opt.is_active then return end
if not is_active then return end

vars[ "true" ] = { type = FILTER_TYPE_BOOLEAN, value = true }
vars[ "false" ] = { type = FILTER_TYPE_BOOLEAN, value = false }
vars[ "clock" ] = { type = FILTER_TYPE_MOMENT, value = os.time( ) }
vars[ "epoch" ] = { type = FILTER_TYPE_MOMENT, value = os.time( { year = 1970, month = 1, day = 1, hour = 0 } ) }
vars[ "y2k" ] = { type = FILTER_TYPE_MOMENT, value = os.time( { year = 2000, month = 1, day = 1, hour = 0 } ) }
if not debug then
-- allow overriding preset vars when debugger is active
self.add_preset_vars( vars )
end

for num, line in ipairs( src ) do
local stmt = string.split( line, " ", false )
@@ -393,7 +403,7 @@ function AuthFilter( path, name )
if #stmt ~= 1 then return trace( "Invalid 'continue' statement in ruleset", num ) end

if evaluate( rule ) then
return ( rule.mode == FILTER_MODE_FAIL and note or nil )
return num, ( rule.mode == FILTER_MODE_FAIL and note or nil )
end

rule = nil
@@ -423,7 +433,7 @@ function AuthFilter( path, name )
end

if bool == FILTER_BOOL_NOW then
return ( mode == FILTER_MODE_FAIL and note or nil )
return num, ( mode == FILTER_MODE_FAIL and note or nil )
end

rule.mode = mode
@@ -525,15 +535,15 @@ function AuthFilter( path, name )
end

self.enable = function ( )
opt.is_active = true
is_active = true
end

self.disable = function ( )
opt.is_active = false
is_active = false
end

self.is_active = function ( )
return opt.is_active
return is_active
end

self.refresh( )

+ 45
- 0
helpers.lua View File

@@ -0,0 +1,45 @@
--------------------------------------------------------
-- Minetest :: Auth Redux Mod v2.10 (auth_rx)
--
-- See README.txt for licensing and release notes.
-- Copyright (c) 2017-2018, Leslie E. Krause
--------------------------------------------------------

-----------------------------------------------------
-- Global Helper Functions
-----------------------------------------------------

get_minetest_config = core.setting_get -- backwards compatibility

function convert_ipv4( str )
local ref = string.split( str, ".", false )
return tonumber( ref[ 1 ] ) * 16777216 + tonumber( ref[ 2 ] ) * 65536 + tonumber( ref[ 3 ] ) * 256 + tonumber( ref[ 4 ] )
end

function unpack_address( addr )
return { math.floor( addr / 16777216 ), math.floor( ( addr % 16777216 ) / 65536 ), math.floor( ( addr % 65536 ) / 256 ), addr % 256 }
end

function get_default_privs( )
local default_privs = { }
for _, p in pairs( string.split( get_minetest_config( "default_privs" ), "," ) ) do
table.insert( default_privs, string.trim( p ) )
end
return default_privs
end

function unpack_privileges( assigned_privs )
local privileges = { }
for _, p in ipairs( assigned_privs ) do
privileges[ p ] = true
end
return privileges
end

function pack_privileges( privileges )
local assigned_privs = { }
for p, _ in pairs( privileges ) do
table.insert( assigned_privs, p )
end
return assigned_privs
end

+ 15
- 63
init.lua View File

@@ -1,13 +1,15 @@
--------------------------------------------------------
-- Minetest :: Auth Redux Mod v2.10 (auth_rx)
--
-- See README.txt for licensing and release notes.
-- Copyright (c) 2017-2018, Leslie E. Krause
--------------------------------------------------------

dofile( minetest.get_modpath( "auth_rx" ) .. "/helpers.lua" )
dofile( minetest.get_modpath( "auth_rx" ) .. "/filter.lua" )
dofile( minetest.get_modpath( "auth_rx" ) .. "/db.lua" )
dofile( minetest.get_modpath( "auth_rx" ) .. "/watchdog.lua" )
local __commands = dofile( minetest.get_modpath( "auth_rx" ) .. "/commands.lua" )

-----------------------------------------------------
-- Registered Authentication Handler
@@ -17,37 +19,6 @@ local auth_filter = AuthFilter( minetest.get_worldpath( ), "greenlist.mt" )
local auth_db = AuthDatabase( minetest.get_worldpath( ), "auth.db" )
local auth_watchdog = AuthWatchdog( )

local get_minetest_config = core.setting_get -- backwards compatibility

function get_default_privs( )
local default_privs = { }
for _, p in pairs( string.split( get_minetest_config( "default_privs" ), "," ) ) do
table.insert( default_privs, string.trim( p ) )
end
return default_privs
end

function unpack_privileges( assigned_privs )
local privileges = { }
for _, p in ipairs( assigned_privs ) do
privileges[ p ] = true
end
return privileges
end

function pack_privileges( privileges )
local assigned_privs = { }
for p, _ in pairs( privileges ) do
table.insert( assigned_privs, p )
end
return assigned_privs
end

function convert_ipv4( str )
local ref = string.split( str, ".", false )
return tonumber( ref[ 1 ] ) * 16777216 + tonumber( ref[ 2 ] ) * 65536 + tonumber( ref[ 3 ] ) * 256 + tonumber( ref[ 4 ] )
end

if minetest.register_on_auth_fail then
minetest.register_on_auth_fail( function ( player_name, player_ip )
auth_db.on_login_failure( player_name, player_ip )
@@ -57,7 +28,7 @@ end

minetest.register_on_prejoinplayer( function ( player_name, player_ip )
local rec = auth_db.select_record( player_name )
local res = auth_watchdog.get_metadata( convert_ipv4( player_ip ) )
local meta = auth_watchdog.get_metadata( convert_ipv4( player_ip ) )

if rec then
auth_db.on_login_attempt( player_name, player_ip )
@@ -71,7 +42,7 @@ minetest.register_on_prejoinplayer( function ( player_name, player_ip )
end
end

local filter_err = auth_filter.process( {
local num, res = auth_filter.process( {
name = { type = FILTER_TYPE_STRING, value = player_name },
addr = { type = FILTER_TYPE_ADDRESS, value = convert_ipv4( player_ip ) },
is_new = { type = FILTER_TYPE_BOOLEAN, value = rec == nil },
@@ -79,6 +50,7 @@ minetest.register_on_prejoinplayer( function ( player_name, player_ip )
users_list = { type = FILTER_TYPE_SERIES, value = auth_db.search( true ) },
cur_users = { type = FILTER_TYPE_NUMBER, value = #auth_db.search( true ) },
max_users = { type = FILTER_TYPE_NUMBER, value = get_minetest_config( "max_users" ) },
lifetime = { type = FILTER_TYPE_PERIOD, value = rec and rec.lifetime or 0 },
sessions = { type = FILTER_TYPE_NUMBER, value = rec and rec.total_sessions or 0 },
failures = { type = FILTER_TYPE_NUMBER, value = rec and rec.total_failures or 0 },
attempts = { type = FILTER_TYPE_NUMBER, value = rec and rec.total_attempts or 0 },
@@ -86,17 +58,17 @@ minetest.register_on_prejoinplayer( function ( player_name, player_ip )
uptime = { type = FILTER_TYPE_PERIOD, value = minetest.get_server_uptime( ) },
oldlogin = { type = FILTER_TYPE_MOMENT, value = rec and rec.oldlogin or 0 },
newlogin = { type = FILTER_TYPE_MOMENT, value = rec and rec.newlogin or 0 },
ip_names_list = { type = FILTER_TYPE_SERIES, value = res.previous_names or { } },
ip_prelogin = { type = FILTER_TYPE_MOMENT, value = res.prelogin or 0 },
ip_oldcheck = { type = FILTER_TYPE_MOMENT, value = res.oldcheck or 0 },
ip_newcheck = { type = FILTER_TYPE_MOMENT, value = res.newcheck or 0 },
ip_failures = { type = FILTER_TYPE_NUMBER, value = res.count_failures or 0 },
ip_attempts = { type = FILTER_TYPE_NUMBER, value = res.count_attempts or 0 }
ip_names_list = { type = FILTER_TYPE_SERIES, value = meta.previous_names or { } },
ip_prelogin = { type = FILTER_TYPE_MOMENT, value = meta.prelogin or 0 },
ip_oldcheck = { type = FILTER_TYPE_MOMENT, value = meta.oldcheck or 0 },
ip_newcheck = { type = FILTER_TYPE_MOMENT, value = meta.newcheck or 0 },
ip_failures = { type = FILTER_TYPE_NUMBER, value = meta.count_failures or 0 },
ip_attempts = { type = FILTER_TYPE_NUMBER, value = meta.count_attempts or 0 }
} )

auth_watchdog.on_attempt( convert_ipv4( player_ip ), player_name )

return filter_err
return res
end )

minetest.register_on_joinplayer( function ( player )
@@ -166,27 +138,6 @@ minetest.register_authentication_handler( {
iterate = auth_db.records
} )

minetest.register_chatcommand( "filter", {
description = "Enable or disable ruleset-based login filtering, or reload a ruleset definition.",
privs = { server = true },
func = function( name, param )
if param == "" then
return true, "Login filtering is currently " .. ( auth_filter.is_active( ) and "enabled" or "disabled" ) .. "."
elseif param == "disable" then
auth_filter.disable( )
minetest.log( "action", "Login filtering disabled by " .. name .. "." )
return true, "Login filtering is disabled."
elseif param == "enable" then
auth_filter.enable( )
minetest.log( "action", "Login filtering enabled by " .. name .. "." )
return true, "Login filtering is enabled."
elseif param == "reload" then
auth_filter.refresh( )
return true, "Ruleset definition was loaded successfully."
else
return false, "Unknown parameter specified."
end
end
} )

auth_db.connect( )

__commands( { auth_db = auth_db, auth_filter = auth_filter } )

Loading…
Cancel
Save