- implemented time and date datatypes for rulesets
- updated code samples with latest feature-set
- added time-related functions for use by rulesets
- added time-related variables for use by rulesets
- minor formatting fixes to source code
- optimized comparison algorithm in ruleset parser
This commit is contained in:
Leslie Krause 2018-07-22 17:56:47 -04:00
parent a0799fe6a4
commit 61d1fe67e7
4 changed files with 204 additions and 47 deletions

View File

@ -1,4 +1,4 @@
Auth Redux Mod v2.6b
Auth Redux Mod v2.7b
By Leslie Krause
Auth Redux is a drop-in replacement for the builtin authentication handler of Minetest.
@ -71,6 +71,14 @@ Version 2.6b (19-Jul-2018)
- tweaked lexer to skip comments on ruleset loading
- added search function to AuthDatabase class
Version 2.7b (21-Jul-2018)
- implemented time and date datatypes for rulesets
- updated code samples with latest feature-set
- added time-related functions for use by rulesets
- added time-related variables for use by rulesets
- minor formatting fixes to source code
- optimized comparison algorithm in ruleset parser
Installation
----------------------

View File

@ -1,15 +1,19 @@
--------------------------------------------------------
-- Minetest :: Auth Redux Mod v2.6 (auth_rx)
-- Minetest :: Auth Redux Mod v2.7 (auth_rx)
--
-- See README.txt for licensing and release notes.
-- Copyright (c) 2017-2018, Leslie E. Krause
--------------------------------------------------------
FILTER_TYPE_STRING = 11
FILTER_TYPE_BOOLEAN = 12
FILTER_TYPE_NUMBER = 13
FILTER_TYPE_NUMBER = 12
FILTER_TYPE_BOOLEAN = 13
FILTER_TYPE_PATTERN = 14
FILTER_TYPE_SERIES = 15
FILTER_TYPE_PERIOD = 16
FILTER_TYPE_MOMENT = 17
FILTER_TYPE_DATESPEC = 18
FILTER_TYPE_TIMESPEC = 19
FILTER_MODE_FAIL = 20
FILTER_MODE_PASS = 21
FILTER_BOOL_AND = 30
@ -60,7 +64,12 @@ function AuthFilter( path, name )
["size"] = { type = FILTER_TYPE_NUMBER, args = { FILTER_TYPE_SERIES }, def = function ( a ) return #a end },
["elem"] = { type = FILTER_TYPE_STRING, args = { FILTER_TYPE_SERIES, FILTER_TYPE_NUMBER }, def = function ( a, b ) return a[ b ] or "" end },
["split"] = { type = FILTER_TYPE_SERIES, args = { FILTER_TYPE_STRING, FILTER_TYPE_STRING }, def = function ( a, b ) return string.split( a, b, true ) end },
}
["time"] = { type = FILTER_TYPE_TIMESPEC, args = { FILTER_TYPE_MOMENT }, def = function ( a ) return a % 86400 end },
["date"] = { type = FILTER_TYPE_DATESPEC, args = { FILTER_TYPE_MOMENT }, def = function ( a ) return math.floor( a / 86400 ) end },
["age"] = { type = FILTER_TYPE_PERIOD, args = { FILTER_TYPE_MOMENT }, def = function ( a ) return os.time( ) - a end }, -- FIXME: use global clock variable
["before"] = { type = FILTER_TYPE_MOMENT, args = { FILTER_TYPE_MOMENT, FILTER_TYPE_PERIOD }, def = function ( a, b ) return a - b end },
["after"] = { type = FILTER_TYPE_MOMENT, args = { FILTER_TYPE_MOMENT, FILTER_TYPE_PERIOD }, def = function ( a, b ) return a + b end },
}
----------------------------
-- private methods
@ -169,6 +178,27 @@ function AuthFilter( path, name )
t = FILTER_TYPE_PATTERN
v = decode_base64( ref[ 1 ] )
v = "^" .. string.gsub( v, ".", sanitizer ) .. "$"
elseif find_token( "^(%d+)([ydhms])$" ) then
local factor = { y = 31536000, w = 604800, d = 86400, h = 3600, m = 60, s = 1 }
t = FILTER_TYPE_PERIOD
v = tonumber( ref[ 1 ] ) * factor[ ref[ 2 ] ]
elseif find_token( "^([-+]%d+)([ydhms])$" ) then
local factor = { y = 31536000, w = 604800, d = 86400, h = 3600, m = 60, s = 1 }
local origin = string.byte( ref[ 1 ] ) == 45 and vars.clock.value or 0
t = FILTER_TYPE_MOMENT
v = origin + tonumber( ref[ 1 ] ) * factor[ ref[ 2 ] ]
elseif find_token( "^(%d?%d):(%d%d):(%d%d)$" ) or find_token( "^(%d?%d):(%d%d)$" ) then
local timespec = {
isdst = true, day = 1, month = 1, year = 1970, hour = tonumber( ref[ 1 ] ), min = tonumber( ref[ 2 ] ), sec = ref[ 3 ] and tonumber( ref[ 3 ] ) or 0,
}
t = FILTER_TYPE_TIMESPEC
v = os.time( timespec )
elseif find_token( "^(%d%d)%-(%d%d)%-(%d%d%d%d)$" ) then
local datespec = {
isdst = true, day = tonumber( ref[ 1 ] ), month = tonumber( ref[ 2 ] ), year = tonumber( ref[ 3 ] ), hour = 0,
}
t = FILTER_TYPE_DATESPEC
v = math.floor( os.time( datespec ) / 86400 )
elseif find_token( "^'([a-zA-Z0-9+/]*);$" ) then
t = FILTER_TYPE_STRING
v = decode_base64( ref[ 1 ] )
@ -244,7 +274,8 @@ function AuthFilter( path, name )
vars[ "true" ] = { type = FILTER_TYPE_BOOLEAN, value = true }
vars[ "false" ] = { type = FILTER_TYPE_BOOLEAN, value = false }
vars[ "time" ] = { type = FILTER_TYPE_NUMBER, value = os.time( ) }
vars[ "clock" ] = { type = FILTER_TYPE_MOMENT, value = os.time( ) }
vars[ "epoch" ] = { type = FILTER_TYPE_MOMENT, value = 0 }
for num, line in ipairs( src ) do
local stmt = string.split( line, " ", false )
@ -356,21 +387,24 @@ function AuthFilter( path, name )
return trace( "Unrecognized operands in ruleset", num )
end
-- only allow comparisons of appropriate and equivalent datatypes
local do_math = { [FILTER_TYPE_STRING] = false, [FILTER_TYPE_NUMBER] = true, [FILTER_TYPE_PERIOD] = true, [FILTER_TYPE_MOMENT] = true, [FILTER_TYPE_DATESPEC] = true, [FILTER_TYPE_TIMESPEC] = true }
local expr
if comp == FILTER_COMP_EQ and oper1.type == oper2.type and oper1.type ~= FILTER_TYPE_SERIES and oper1.type ~= FILTER_TYPE_PATTERN then
expr = ( oper1.value == oper2.value )
elseif comp == FILTER_COMP_GT and oper1.type == oper2.type and do_math[ oper2.type ] then
expr = ( oper1.value > oper2.value )
elseif comp == FILTER_COMP_GTE and oper1.type == oper2.type and do_math[ oper2.type ] then
expr = ( oper1.value >= oper2.value )
elseif comp == FILTER_COMP_LT and oper1.type == oper2.type and do_math[ oper2.type ] then
expr = ( oper1.value < oper2.value )
elseif comp == FILTER_COMP_LTE and oper1.type == oper2.type and do_math[ oper2.type ] then
expr = ( oper1.value <= oper2.value )
elseif comp == FILTER_COMP_IS and oper1.type == FILTER_TYPE_STRING and oper2.type == FILTER_TYPE_STRING then
expr = ( string.upper( oper1.value ) == string.upper( oper2.value ) )
elseif comp == FILTER_COMP_IS and oper1.type == FILTER_TYPE_STRING and oper2.type == FILTER_TYPE_PATTERN then
expr = ( string.find( oper1.value, oper2.value ) == 1 )
elseif comp == FILTER_COMP_GT and oper1.type == FILTER_TYPE_NUMBER and oper2.type == FILTER_TYPE_NUMBER then
expr = ( oper1.value > oper2.value )
elseif comp == FILTER_COMP_LT and oper1.type == FILTER_TYPE_NUMBER and oper2.type == FILTER_TYPE_NUMBER then
expr = ( oper1.value < oper2.value )
elseif comp == FILTER_COMP_GTE and oper1.type == FILTER_TYPE_NUMBER and oper2.type == FILTER_TYPE_NUMBER then
expr = ( oper1.value >= oper2.value )
elseif comp == FILTER_COMP_LTE and oper1.type == FILTER_TYPE_NUMBER and oper2.type == FILTER_TYPE_NUMBER then
expr = ( oper1.value <= oper2.value )
else
return trace( "Mismatched operands in ruleset", num )
end

View File

@ -1,5 +1,5 @@
--------------------------------------------------------
-- Minetest :: Auth Redux Mod v2.6 (auth_rx)
-- Minetest :: Auth Redux Mod v2.7 (auth_rx)
--
-- See README.txt for licensing and release notes.
-- Copyright (c) 2017-2018, Leslie E. Krause
@ -18,10 +18,10 @@ local auth_db = AuthDatabase( minetest.get_worldpath( ), "auth.db" )
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
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
@ -55,15 +55,15 @@ minetest.register_on_prejoinplayer( function ( player_name, player_ip )
else
-- prevent creation of case-insensitive duplicate accounts
local uname = string.lower( player_name )
for cname in auth_db.records( ) do
if string.lower( cname ) == uname then
for cname in auth_db.records( ) do
if string.lower( cname ) == uname then
return string.format( "A player named %s already exists on this server.", cname )
end
end
end
local filter_err = auth_filter.process( {
name = { type = FILTER_TYPE_STRING, value = player_name },
name = { type = FILTER_TYPE_STRING, value = player_name },
addr = { type = FILTER_TYPE_STRING, value = player_ip },
is_new = { type = FILTER_TYPE_BOOLEAN, value = rec == nil },
privs_list = { type = FILTER_TYPE_SERIES, value = rec and rec.assigned_privs or { } },
@ -74,6 +74,9 @@ minetest.register_on_prejoinplayer( function ( player_name, player_ip )
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 },
owner = { type = FILTER_TYPE_STRING, value = get_minetest_config( "name" ) },
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 },
} )
return filter_err
@ -143,26 +146,26 @@ minetest.register_authentication_handler( {
} )
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
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
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
elseif param == "reload" then
auth_filter.refresh( )
return true, "Ruleset definition was loaded successfully."
else
return false, "Unknown parameter specified."
end
end
end
} )
auth_db.connect( )

View File

@ -1,16 +1,3 @@
#####################################################################
#
# disallow new players whenever server is overloaded
#
#####################################################################
try "There are too many players online right now."
fail all
if $is_new eq $true
if $cur_users gt 20
continue
#####################################################################
#
# only allow administrator access (by username or IP address)
@ -23,6 +10,8 @@ if $addr eq "172.16.100.2"
if $name eq "admin"
continue
fail now
#####################################################################
#
# block a range of IP addresses using wildcards
@ -41,7 +30,7 @@ pass now
#####################################################################
#
# only allow access from whitelisted users
# only allow access from whitelisted players
#
#####################################################################
@ -56,7 +45,7 @@ fall now
#####################################################################
#
# never allow access from blacklisted users
# never allow access from blacklisted players
#
#####################################################################
@ -69,10 +58,133 @@ pass now
#####################################################################
#
# notify users that the server is unavailable right now
# notify players that the server is unavailable right now
#
#####################################################################
try "The server is temporarily offline for maintenance."
fail now
#####################################################################
#
# disallow players with all uppercase names
#
#####################################################################
try "Sorry, we do not accept all uppercase player names."
fail all
$name eq uc($name)
continue
pass now
#####################################################################
#
# disallow players with very short or very long names
#
#####################################################################
try "Sorry, this player name is too long or too short."
fail any
$name->len() gt 20
$name->len() lt 3
continue
pass now
#####################################################################
#
# disallow users that appear to be bots or guests
#
#####################################################################
try "Sorry, we do not accept autogenerated player names."
fail any
if $name is /;*;*##/
if $name is /;*;*###/
if $name is /Player#/
if $name is /Player##/
if $name is /Guest#/
if $name is /Guest##/
continue
pass now
#####################################################################
#
# disallow new players when the server is near capacity
#
#####################################################################
try "There are too many players online right now."
fail all
$is_new eq $true
$cur_users gte $max_users->mul(0.8)
continue
pass now
#####################################################################
#
# prevent players from joining with a reserved name
#
#####################################################################
try "Sorry, this acccount has been permanently restricted."
fail all
$is_new eq $true
when ("moderator","server","client","owner","player","system","operator","minetest") is $name
continue
pass now
#####################################################################
#
# disallow players that have been inactive for 90 days
#
#####################################################################
try "Sorry, this acccount has been disabled for inactivity."
fail all
if $is_new eq $false
if age($newlogin) gt 90d
continue
pass now
#####################################################################
#
# disallow new players during the weekends
#
#####################################################################
try "Sorry, we are not accepting new players at this time."
fail now
if $is_new eq $true
when ("Sat","Sun") eq $clock->day()
continue
pass now
#####################################################################
#
# prevent players from spam-logging the server
#
#####################################################################
try "You are doing that too much. Please wait awhile."
fail all
if $is_new eq $false
if age($newlogin) lt 15s
continue
pass now