diff --git a/README.txt b/README.txt index 9a22fc3..574caf8 100644 --- a/README.txt +++ b/README.txt @@ -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 ---------------------- diff --git a/commands.lua b/commands.lua new file mode 100644 index 0000000..2dfd51b --- /dev/null +++ b/commands.lua @@ -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 diff --git a/filter.lua b/filter.lua index 733627d..a8d0b59 100644 --- a/filter.lua +++ b/filter.lua @@ -1,5 +1,5 @@ -------------------------------------------------------- --- Minetest :: Auth Redux Mod v2.9 (auth_rx) +-- 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( ) diff --git a/helpers.lua b/helpers.lua new file mode 100644 index 0000000..46fdc7b --- /dev/null +++ b/helpers.lua @@ -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 diff --git a/init.lua b/init.lua index b00b88c..8c1c68b 100644 --- a/init.lua +++ b/init.lua @@ -1,13 +1,15 @@ -------------------------------------------------------- --- Minetest :: Auth Redux Mod v2.9 (auth_rx) +-- 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 } )