diff --git a/README.txt b/README.txt index ec03c21..355febe 100644 --- a/README.txt +++ b/README.txt @@ -1,5 +1,5 @@ -Auth Redux Mod v2.1b -By Leslie E. Krause +Auth Redux Mod v2.2b +By Leslie Krause Auth Redux is a drop-in replacement for the builtin authentication handler of Minetest. It is designed from the ground up to be robust and secure enough for use on high-traffic @@ -10,6 +10,16 @@ Auth Redux is intended to be compatible with all versions of Minetest 0.4.14+. https://forum.minetest.net/viewtopic.php?f=9&t=20393 +Repository +---------------------- + +Browse source code: + https://bitbucket.org/sorcerykid/auth_rx + +Download archive: + https://bitbucket.org/sorcerykid/auth_rx/get/master.zip + https://bitbucket.org/sorcerykid/auth_rx/get/master.tar.gz + Revision History ---------------------- @@ -18,21 +28,26 @@ Version 2.1b (30-Jun-2018) - included code samples for basic login filtering - included a command-line database import script +Version 2.2b (04-Jul-2018) + - added install option to database import script + - improved exception handling by AuthFilter class + - fixed parsing of number literals in rulesets + - fixed type-checking of try statements in rulesets + - included mod.conf and description.txt files + Installation ---------------------- 1) Unzip the archive into the mods directory of your game 2) Rename the auth_rx-master directory to "auth_rx" - 3) Create an empty file named "auth.dbx" within the respective world directory - 4) Create an empty file named "greenlist.mt" within the respective world directory - 5) Execute the provided "convert.awk" script (refer to instructions) + 3) Execute the "convert.awk" script (refer to instructions) Source Code License ---------------------- The MIT License (MIT) -Copyright (c) 2016-2018, Leslie E. Krause +Copyright (c) 2016-2018, Leslie Krause (leslie@searstower.org) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software diff --git a/convert.awk b/convert.awk old mode 100755 new mode 100644 index 836e7fb..6da91ea --- a/convert.awk +++ b/convert.awk @@ -1,16 +1,52 @@ #!/bin/awk -f -# Database Import Script for Auth Redux (by Leslie Krause) + +################################################################################ +# Database Import Script for Auth Redux Mod +# ------------------------------------------ +# This script will convert the specified 'auth.txt' file into a database format +# required by the Auth Redux Mod. The output file will be generated in the same +# world directory as the original 'auth.txt' file (which will be unchanged). # -# STEP 1: Run this script from within the world directory and redirect output to "auth.db" -# awk -f auth.txt > auth.db -# STEP 2: Rename 'auth.txt' to 'auth.bak' or move to a different location for safekeeping +# Setting the mode to 'install' will automatically install the required journal +# and ruleset files into the world directory as well. +# +# EXAMPLE: +# awk -f convert.awk -v mode=convert ~/.minetest/worlds/world/auth.txt +################################################################################ function error( msg ) { - print( msg " at line " NR " in " FILENAME "." ) > "/dev/stderr" + skipped++; + print msg " at line " NR " in " FILENAME "."; } BEGIN { FS = ":"; + OFS = ":"; + checked = 0; + skipped = 0; + + db_file = "auth.db"; + journal_file = "auth.dbx"; + ruleset_file = "greenlist.mt"; + + # determine output file name from arguments + + path = ARGV[ 1 ] + if( sub( /[-_A-Za-z0-9]+\.txt$/, "", path ) == 0 ) { + # sanity check for nonstandard input file + path = ""; + } + + # install required journal and ruleset files + + if( mode == "install" ) { + print "Installing the required journal and ruleset files..."; + print "" > path journal_file + print "pass now" > path ruleset_file + } + else if( mode != "convert" ) { + print "Unknown argument, defaulting to convert mode."; + } # set default values for new database fields @@ -21,15 +57,15 @@ BEGIN { total_attempts = 0; total_sessions = 0; - # output the database header - # TODO: perhaps add? strftime( "%Y-%m-%d %H:%M:%S" ) + # print database headline to the output file - print "auth_rx/2.1 @0" + print "Converting " ARGV[ 1 ] "..."; + print "auth_rx/2.1 @0" > path db_file; } NF != 4 { - error( "Malformed record" ) - next + error( "Malformed record" ); + next; } { @@ -39,12 +75,12 @@ NF != 4 { newlogin = $4; if( !match( username, "^[a-zA-Z0-9_-]+$" ) ) { - error( "Invalid username field" ) - next + error( "Invalid username field" ); + next; } if( !match( newlogin, "^[0-9]+$" ) && newlogin != -1 ) { - error( "Invalid last_login field" ) - next + error( "Invalid last_login field" ); + next; } # Database File Format @@ -60,5 +96,11 @@ NF != 4 { # approved_addrs # assigned_privs - print( username ":" password ":" oldlogin ":" newlogin ":" lifetime ":" total_sessions ":" total_attempts ":" total_failures ":" approved_addrs ":" assigned_privs ); + print username, password, oldlogin, newlogin, lifetime, total_sessions, total_attempts, total_failures, approved_addrs, assigned_privs > path db_file; + + checked++; +} + +END { + print "Done! " checked " of " ( checked + skipped ) " total records were imported to " db_file " (" skipped " records skipped)." } diff --git a/description.txt b/description.txt new file mode 100644 index 0000000..322cec3 --- /dev/null +++ b/description.txt @@ -0,0 +1,3 @@ +Auth Redux is a drop-in replacement for the builtin authentication handler of Minetest. It is designed from the ground up to be robust and secure enough for use on high-traffic Minetest servers, while also addressing a number of outstanding engine bugs. + +For more information: https://forum.minetest.net/viewtopic.php?f=9&t=20393 diff --git a/filter.lua b/filter.lua index f3aa950..7e6fe69 100644 --- a/filter.lua +++ b/filter.lua @@ -1,5 +1,5 @@ -------------------------------------------------------- --- Minetest :: Auth Redux Mod v2.1 (auth_rx) +-- Minetest :: Auth Redux Mod v2.2 (auth_rx) -- -- See README.txt for licensing and release notes. -- Copyright (c) 2017-2018, Leslie E. Krause @@ -60,9 +60,10 @@ function AuthFilter( path, name ) -- private methods ---------------------------- - local throw = function ( msg, num ) - -- minetest.log( "error", msg .. " (line " .. num .. ")" ) - error( msg .. " (line " .. num .. ")" ) + local trace = function ( msg, num ) + -- TODO: Use 'pcall' for more graceful exception handling? + minetest.log( "error", string.format( "%s (%s/%s, line %d)", msg, path, name, num ) ) + return "The server encountered an internal error." end local get_operand = function ( token, vars ) @@ -117,7 +118,7 @@ function AuthFilter( path, name ) v = string.gsub( v, "%$([a-zA-Z_]+)", function ( var ) return vars[ var ] and tostring( vars[ var ].value ) or "?" end ) - elseif string.find( token, "^%d+$" ) then + elseif string.find( token, "^-?%d+$" ) or string.find( token, "^-?%d*%.%d+$" ) then t = FILTER_TYPE_NUMBER v = tonumber( token ) else @@ -167,10 +168,10 @@ function AuthFilter( path, name ) -- TODO: these should be stripped on file import elseif stmt[ 1 ] == "continue" then - if #stmt ~= 1 then throw( "Invalid 'continue' statement in ruleset", num ) end + if #stmt ~= 1 then return trace( "Invalid 'continue' statement in ruleset", num ) end if rule == nil then - throw( "No ruleset declared", num ) + return trace( "No ruleset declared", num ) end if evaluate( rule ) then @@ -180,19 +181,19 @@ function AuthFilter( path, name ) rule = nil elseif stmt[ 1 ] == "try" then - if rule then throw( "Missing 'continue' statement in ruleset", num ) end - if #stmt ~= 2 then throw( "Invalid 'try' statement in ruleset", num ) end + if rule then return trace( "Missing 'continue' statement in ruleset", num ) end + if #stmt ~= 2 then return trace( "Invalid 'try' statement in ruleset", num ) end local oper = get_operand( stmt[ 2 ], vars ) - if not oper then - throw( "Unrecognized operand in ruleset", num ) + if not oper or oper.type ~= FILTER_TYPE_STRING then + return trace( "Unrecognized operand in ruleset", num ) end note = oper.value elseif stmt[ 1 ] == "pass" or stmt[ 1 ] == "fail" then - if rule then throw( "Missing continue statement in ruleset", num ) end - if #stmt ~= 2 then throw( "Invalid 'pass' or 'fail' statement in ruleset", num ) end + if rule then return trace( "Missing continue statement in ruleset", num ) end + if #stmt ~= 2 then return trace( "Invalid 'pass' or 'fail' statement in ruleset", num ) end rule = { } @@ -200,7 +201,7 @@ function AuthFilter( path, name ) local bool = ( { ["all"] = FILTER_BOOL_AND, ["any"] = FILTER_BOOL_OR, ["one"] = FILTER_BOOL_XOR, ["now"] = FILTER_BOOL_NOW } )[ stmt[ 2 ] ] if not mode or not bool then - throw( "Unrecognized keywords in ruleset", num ) + return trace( "Unrecognized keywords in ruleset", num ) end if bool == FILTER_BOOL_NOW then @@ -212,22 +213,22 @@ function AuthFilter( path, name ) rule.expr = { } elseif stmt[ 1 ] == "when" or stmt[ 1 ] == "until" then - if #stmt ~= 4 then throw( "Invalid 'when' or 'until' statement in ruleset", num ) end + if #stmt ~= 4 then return trace( "Invalid 'when' or 'until' statement in ruleset", num ) end local cond = ( { ["when"] = FILTER_COND_TRUE, ["until"] = FILTER_COND_FALSE } )[ stmt[ 1 ] ] local comp = ( { ["eq"] = FILTER_COMP_EQ, ["is"] = FILTER_COMP_IS } )[ stmt[ 3 ] ] if not cond or not comp then - throw( "Unrecognized keywords in ruleset", num ) + return trace( "Unrecognized keywords in ruleset", num ) end local oper1 = get_operand( stmt[ 2 ], vars ) local oper2 = get_operand( stmt[ 4 ], vars ) if not oper1 or not oper2 then - throw( "Unrecognized operands in ruleset", num ) + return trace( "Unrecognized operands in ruleset", num ) elseif oper1.type ~= FILTER_TYPE_SERIES then - throw( "Mismatched operands in ruleset", num ) + return trace( "Mismatched operands in ruleset", num ) end -- cache second operand value for efficiency @@ -244,7 +245,7 @@ function AuthFilter( path, name ) elseif comp == FILTER_COMP_IS and oper2.type == FILTER_TYPE_PATTERN then expr = ( string.find( string.upper( value1 ), value2 ) == 1 ) else - throw( "Mismatched operands in ruleset", num ) + return trace( "Mismatched operands in ruleset", num ) end if expr then break end end @@ -253,20 +254,20 @@ function AuthFilter( path, name ) table.insert( rule.expr, expr ) elseif stmt[ 1 ] == "if" or stmt[ 1 ] == "unless" then - if #stmt ~= 4 then throw( "Invalid 'if' or 'unless' statement in ruleset", num ) end + if #stmt ~= 4 then return trace( "Invalid 'if' or 'unless' statement in ruleset", num ) end local cond = ( { ["if"] = FILTER_COND_TRUE, ["unless"] = FILTER_COND_FALSE } )[ stmt[ 1 ] ] local comp = ( { ["eq"] = FILTER_COMP_EQ, ["gt"] = FILTER_COMP_GT, ["lt"] = FILTER_COMP_LT, ["is"] = FILTER_COMP_IS } )[ stmt[ 3 ] ] if not cond or not comp then - throw( "Unrecognized keywords in ruleset", num ) + return trace( "Unrecognized keywords in ruleset", num ) end local oper1 = get_operand( stmt[ 2 ], vars ) local oper2 = get_operand( stmt[ 4 ], vars ) if not oper1 or not oper2 then - throw( "Unrecognized operands in ruleset", num ) + return trace( "Unrecognized operands in ruleset", num ) end -- FIXME: don't allow equality comparison of patterns or series @@ -283,7 +284,7 @@ function AuthFilter( path, name ) elseif comp == FILTER_COMP_LT and oper1.type == FILTER_TYPE_NUMBER and oper2.type == FILTER_TYPE_NUMBER then expr = ( oper1.value < oper2.value ) else - throw( "Mismatched operands in ruleset", num ) + return trace( "Mismatched operands in ruleset", num ) end if cond == FILTER_COND_FALSE then expr = not expr end @@ -293,10 +294,10 @@ function AuthFilter( path, name ) -- but probably requires state table; efficiency vs complexity scenario else - throw( "Invalid statement in ruleset", num ) + return trace( "Invalid statement in ruleset", num ) end end - throw( "Unexpected end-of-file in ruleset", num ) + return trace( "Unexpected end-of-file in ruleset", 0 ) end return self diff --git a/mod.conf b/mod.conf new file mode 100644 index 0000000..df6b094 --- /dev/null +++ b/mod.conf @@ -0,0 +1,4 @@ +name = auth_rx +title = Auth Redux +author = sorcerykid +license = MIT