Browse Source

Build 09

- introduced support for array literals in rulesets
- added array-related functions for use by rulesets
- localized references to transcoding functions
- registered chat command to control login filtering
- included support for disabling login filtering
- added reload function to AuthFilter class
- tweaked lexer to skip comments on ruleset loading
- added search function to AuthDatabase class
master
Leslie Krause 1 year ago
parent
commit
a0799fe6a4
4 changed files with 128 additions and 45 deletions
  1. 11
    1
      README.txt
  2. 12
    0
      db.lua
  3. 76
    40
      filter.lua
  4. 29
    4
      init.lua

+ 11
- 1
README.txt View File

@@ -1,4 +1,4 @@
1
-Auth Redux Mod v2.5b
1
+Auth Redux Mod v2.6b
2 2
 By Leslie Krause
3 3
 
4 4
 Auth Redux is a drop-in replacement for the builtin authentication handler of Minetest.
@@ -61,6 +61,16 @@ Version 2.5b (17-Jul-2018)
61 61
   - added some basic functions for use by rulesets
62 62
   - fixed validation of dataset names in rulesets
63 63
 
64
+Version 2.6b (19-Jul-2018)
65
+  - introduced support for array literals in rulesets
66
+  - added array-related functions for use by rulesets
67
+  - localized references to transcoding functions
68
+  - registered chat command to control login filtering
69
+  - included support for disabling login filtering
70
+  - added reload function to AuthFilter class
71
+  - tweaked lexer to skip comments on ruleset loading
72
+  - added search function to AuthDatabase class
73
+
64 74
 Installation
65 75
 ----------------------
66 76
 

+ 12
- 0
db.lua View File

@@ -1,5 +1,5 @@
1 1
 --------------------------------------------------------
2
+-- Minetest :: Auth Redux Mod v2.6 (auth_rx)
2 3
 --
3 4
 -- See README.txt for licensing and release notes.
4 5
 -- Copyright (c) 2017-2018, Leslie E. Krause
@@ -398,5 +398,16 @@ function AuthDatabase( path, name )
398 398
 		return data[ username ]
399 399
 	end
400 400
 
401
+	self.search = function ( is_online, pattern )
402
+		local res = { }
403
+		local src = is_online and users or data
404
+		for k, v in pairs( src ) do
405
+			if pattern == nil or string.match( k, pattern ) then
406
+				table.insert( res, k )
407
+			end
408
+		end
409
+		return res		
410
+	end
411
+
401 412
 	return self
402 413
 end

+ 76
- 40
filter.lua View File

@@ -1,5 +1,5 @@
1 1
 --------------------------------------------------------
2
+-- Minetest :: Auth Redux Mod v2.6 (auth_rx)
2 3
 --
3 4
 -- See README.txt for licensing and release notes.
4 5
 -- Copyright (c) 2017-2018, Leslie E. Krause
@@ -36,49 +36,30 @@ end
36 36
 ----------------------------
37 37
 
38 38
 function AuthFilter( path, name )
39
-	local src = { }
40
-	local opt = { is_debug = false, is_strict = true }
39
+	local src
40
+	local opt = { is_debug = false, is_strict = true, is_active = true }
41 41
 	local self = { }
42 42
 
43
-	local file = io.open( path .. "/" .. name, "rb" )
44
-	if not file then
45
-		error( "The specified ruleset file does not exist." )
46
-	end
47
-
48
-	for line in file:lines( ) do
49
-		-- encode string and pattern literals and function arguments to simplify parsing
50
-		line = string.gsub( line, "\"(.-)\"", function ( str )
51
-			return "\"" .. encode_base64( str ) .. ";"
52
-		end )
53
-		line = string.gsub( line, "'(.-)'", function ( str )
54
-			return "'" .. encode_base64( str ) .. ";"
55
-		end )
56
-		line = string.gsub( line, "/(.-)/", function ( str )
57
-			return "/" .. encode_base64( str ) .. ";"
58
-		end )
59
-		line = string.gsub( line, "%b()", function ( str )
60
-			return "&" .. encode_base64( trim( str ) ) .. ";"
61
-		end )
62
-		table.insert( src, line )
63
-	end
64
-
65
-	file:close( file )
66
-
67 43
 	local funcs = {
68 44
 		["add"] = { type = FILTER_TYPE_NUMBER, args = { FILTER_TYPE_NUMBER, FILTER_TYPE_NUMBER }, def = function ( a, b ) return a + b end },
69 45
 		["sub"] = { type = FILTER_TYPE_NUMBER, args = { FILTER_TYPE_NUMBER, FILTER_TYPE_NUMBER }, def = function ( a, b ) return a - b end },
70 46
 		["mul"] = { type = FILTER_TYPE_NUMBER, args = { FILTER_TYPE_NUMBER, FILTER_TYPE_NUMBER }, def = function ( a, b ) return a * b end },
71 47
 		["div"] = { type = FILTER_TYPE_NUMBER, args = { FILTER_TYPE_NUMBER, FILTER_TYPE_NUMBER }, def = function ( a, b ) return a / b end },
72 48
 		["neg"] = { type = FILTER_TYPE_NUMBER, args = { FILTER_TYPE_NUMBER }, def = function ( a ) return -a end },
73
-		["max"] = { type = FILTER_TYPE_NUMBER, args = { FILTER_TYPE_NUMBER }, def = function ( a, b ) return math.max( a, b ) end },
74
-		["min"] = { type = FILTER_TYPE_NUMBER, args = { FILTER_TYPE_NUMBER }, def = function ( a, b ) return math.min( a, b ) end },
49
+		["abs"] = { type = FILTER_TYPE_NUMBER, args = { FILTER_TYPE_NUMBER }, def = function ( a ) return math.abs( a ) end },
50
+		["max"] = { type = FILTER_TYPE_NUMBER, args = { FILTER_TYPE_NUMBER, FILTER_TYPE_NUMBER }, def = function ( a, b ) return math.max( a, b ) end },
51
+		["min"] = { type = FILTER_TYPE_NUMBER, args = { FILTER_TYPE_NUMBER, FILTER_TYPE_NUMBER }, def = function ( a, b ) return math.min( a, b ) end },
75 52
 		["int"] = { type = FILTER_TYPE_NUMBER, args = { FILTER_TYPE_NUMBER }, def = function ( a ) return a < 0 and math.ceil( a ) or math.floor( a ) end },
53
+		["num"] = { type = FILTER_TYPE_NUMBER, args = { FILTER_TYPE_STRING }, def = function ( a ) return tonumber( a ) or 0 end },
76 54
 		["len"] = { type = FILTER_TYPE_NUMBER, args = { FILTER_TYPE_STRING }, def = function ( a ) return string.len( a ) end },
77 55
 		["lc"] = { type = FILTER_TYPE_STRING, args = { FILTER_TYPE_STRING }, def = function ( a ) return string.lower( a ) end },
78 56
 		["uc"] = { type = FILTER_TYPE_STRING, args = { FILTER_TYPE_STRING }, def = function ( a ) return string.upper( a ) end },
79 57
 		["range"] = { type = FILTER_TYPE_BOOLEAN, args = { FILTER_TYPE_NUMBER, FILTER_TYPE_NUMBER, FILTER_TYPE_NUMBER }, def = function ( a, b, c ) return a >= b and a <= c end },
80 58
 		["trim"] = { type = FILTER_TYPE_STRING, args = { FILTER_TYPE_STRING, FILTER_TYPE_NUMBER }, def = function ( a, b ) return b > 0 and string.sub( a, 1, -b - 1 ) or string.sub( a, -b + 1 ) end },
81 59
 		["crop"] = { type = FILTER_TYPE_STRING, args = { FILTER_TYPE_STRING, FILTER_TYPE_NUMBER }, def = function ( a, b ) return b > 0 and string.sub( a, 1, b ) or string.sub( a, b, -1 ) end },
60
+		["size"] = { type = FILTER_TYPE_NUMBER, args = { FILTER_TYPE_SERIES }, def = function ( a ) return #a end },
61
+		["elem"] = { type = FILTER_TYPE_STRING, args = { FILTER_TYPE_SERIES, FILTER_TYPE_NUMBER }, def = function ( a, b ) return a[ b ] or "" end },
62
+		["split"] = { type = FILTER_TYPE_SERIES, args = { FILTER_TYPE_STRING, FILTER_TYPE_STRING }, def = function ( a, b ) return string.split( a, b, true ) end },
82 63
         }
83 64
 
84 65
 	----------------------------
@@ -121,8 +102,8 @@ function AuthFilter( path, name )
121 102
 				return nil
122 103
 			end
123 104
 			local params = { }
124
-			for i, v in ipairs( args ) do
125
-				local oper = get_operand( v, vars )
105
+			for i, a in ipairs( args ) do
106
+				local oper = get_operand( a, vars )
126 107
 				if not oper or oper.type ~= funcs[ name ].args[ i ] then
127 108
 					return nil
128 109
 				end
@@ -130,6 +111,22 @@ function AuthFilter( path, name )
130 111
 			end
131 112
 			t = funcs[ name ].type
132 113
 			v = funcs[ name ].def( unpack( params ) )
114
+		elseif find_token( "^&([A-Za-z0-9+/]*);$" ) then
115
+			t = FILTER_TYPE_SERIES
116
+			v = { }
117
+			local suffix = decode_base64( ref[ 1 ] )
118
+			suffix = string.gsub( suffix, "%b()", function( str )
119
+				-- encode nested function arguments
120
+				return "&" .. encode_base64( trim( str ) ) .. ";"
121
+			end )
122
+			local elems = string.split( suffix, ",", false )
123
+			for i, e in ipairs( elems ) do
124
+				local oper = get_operand( e, vars )
125
+				if not oper or oper.type ~= FILTER_TYPE_STRING then
126
+					return nil
127
+				end
128
+				table.insert( v, oper.value )
129
+			end
133 130
 		elseif find_token( "^%$([a-zA-Z0-9_]+)$" ) then
134 131
 			local name = ref[ 1 ]
135 132
 			if not vars[ name ] then
@@ -170,14 +167,14 @@ function AuthFilter( path, name )
170 167
 				["&"] = "%a",
171 168
 			}
172 169
 			t = FILTER_TYPE_PATTERN
173
-			v = minetest.decode_base64( ref[ 1 ] )
170
+			v = decode_base64( ref[ 1 ] )
174 171
 			v = "^" .. string.gsub( v, ".", sanitizer ) .. "$"
175 172
 		elseif find_token( "^'([a-zA-Z0-9+/]*);$" ) then
176 173
 			t = FILTER_TYPE_STRING
177
-			v = minetest.decode_base64( ref[ 1 ] )
174
+			v = decode_base64( ref[ 1 ] )
178 175
 		elseif find_token( "^\"([a-zA-Z0-9+/]*);$" ) then
179 176
 			t = FILTER_TYPE_STRING
180
-			v = minetest.decode_base64( ref[ 1 ] )
177
+			v = decode_base64( ref[ 1 ] )
181 178
 			v = string.gsub( v, "%$([a-zA-Z_]+)", function ( var )
182 179
 				return vars[ var ] and tostring( vars[ var ].value ) or "?"
183 180
 			end )
@@ -190,7 +187,7 @@ function AuthFilter( path, name )
190 187
 		return { type = t, value = v }
191 188
 	end
192 189
 
193
-	local evaluate = function ( rule )
190
+	evaluate = function ( rule )
194 191
 		-- short circuit binary logic to simplify evaluation
195 192
 		local res = ( rule.bool == FILTER_BOOL_AND )
196 193
 		local xor = 0
@@ -213,22 +210,47 @@ function AuthFilter( path, name )
213 210
 	-- public methods
214 211
 	----------------------------
215 212
 
213
+	self.refresh = function ( )
214
+		local file = io.open( path .. "/" .. name, "rb" )
215
+		if not file then
216
+			error( "The specified ruleset file does not exist." )
217
+		end
218
+		src = { }
219
+		for line in file:lines( ) do
220
+			-- encode string and pattern literals and function arguments to simplify parsing
221
+			line = string.gsub( line, "\"(.-)\"", function ( str )
222
+				return "\"" .. encode_base64( str ) .. ";"
223
+			end )
224
+			line = string.gsub( line, "'(.-)'", function ( str )
225
+				return "'" .. encode_base64( str ) .. ";"
226
+			end )
227
+			line = string.gsub( line, "/(.-)/", function ( str )
228
+				return "/" .. encode_base64( str ) .. ";"
229
+			end )
230
+			line = string.gsub( line, "%b()", function ( str )
231
+				return "&" .. encode_base64( trim( str ) ) .. ";"
232
+			end )
233
+			-- skip comments (lines beginning with hash character) and blank lines
234
+			table.insert( src, string.byte( line ) ~= 35 and line or "" )
235
+		end
236
+		file:close( file )
237
+	end
238
+
216 239
 	self.process = function( vars )
217 240
 		local rule
218 241
 		local note = "Access denied."
219 242
 
243
+		if not opt.is_active then return end
244
+
220 245
 		vars[ "true" ] = { type = FILTER_TYPE_BOOLEAN, value = true }
221 246
 		vars[ "false" ] = { type = FILTER_TYPE_BOOLEAN, value = false }
222 247
 		vars[ "time" ] = { type = FILTER_TYPE_NUMBER, value = os.time( ) }
223 248
 
224 249
 		for num, line in ipairs( src ) do
225
-
226
-			-- FIXME: ignore extraneous whitespace, even at beginning of line
227 250
 			local stmt = string.split( line, " ", false )
228 251
 
229
-			if string.byte( line ) == 35 or #stmt == 0 then
230
-				-- skip comments (lines beginning with hash character) and empty lines
231
-				-- TODO: these should be stripped on file import
252
+			if #stmt == 0 then
253
+				-- skip no-op statements
232 254
 
233 255
 			elseif stmt[ 1 ] == "continue" then
234 256
 				if #stmt ~= 1 then return trace( "Invalid 'continue' statement in ruleset", num ) end
@@ -358,7 +380,6 @@ function AuthFilter( path, name )
358 380
 
359 381
 				-- TODO: immediately evaluating each expression (thus avoiding a list) would be optimal,
360 382
 				-- but probably requires state table; efficiency vs complexity scenario
361
-
362 383
 			else
363 384
 				return trace( "Invalid statement in ruleset", num )
364 385
 			end
@@ -366,5 +387,19 @@ function AuthFilter( path, name )
366 387
 		return trace( "Unexpected end-of-file in ruleset", 0 )
367 388
 	end
368 389
 
390
+	self.enable = function ( )
391
+		opt.is_active = true
392
+	end
393
+
394
+	self.disable = function ( )
395
+		opt.is_active = false
396
+	end
397
+
398
+	self.is_active = function ( )
399
+		return opt.is_active
400
+	end
401
+
402
+	self.refresh( )
403
+
369 404
 	return self
370 405
 end

+ 29
- 4
init.lua View File

@@ -1,5 +1,5 @@
1 1
 --------------------------------------------------------
2
+-- Minetest :: Auth Redux Mod v2.6 (auth_rx)
2 3
 --
3 4
 -- See README.txt for licensing and release notes.
4 5
 -- Copyright (c) 2017-2018, Leslie E. Krause
@@ -66,13 +66,14 @@ minetest.register_on_prejoinplayer( function ( player_name, player_ip )
66 66
 	        name = { type = FILTER_TYPE_STRING, value = player_name },
67 67
 		addr = { type = FILTER_TYPE_STRING, value = player_ip },
68 68
 		is_new = { type = FILTER_TYPE_BOOLEAN, value = rec == nil },
69
-		priv_list = { type = FILTER_TYPE_SERIES, value = rec and rec.assigned_privs or { } },
70
-		addr_list = { type = FILTER_TYPE_SERIES, value = rec and rec.approved_addrs or { } },
71
-		cur_users = { type = FILTER_TYPE_NUMBER, value = #minetest.get_connected_players( ) },
69
+		privs_list = { type = FILTER_TYPE_SERIES, value = rec and rec.assigned_privs or { } },
70
+		users_list = { type = FILTER_TYPE_SERIES, value = auth_db.search( true ) },
71
+		cur_users = { type = FILTER_TYPE_NUMBER, value = #auth_db.search( true ) },
72 72
 		max_users = { type = FILTER_TYPE_NUMBER, value = get_minetest_config( "max_users" ) },
73
-		lifetime = { type = FILTER_TYPE_NUMBER, value = rec and rec.lifetime or 0 },
73
+		sessions = { type = FILTER_TYPE_NUMBER, value = rec and rec.total_sessions or 0 },
74 74
 		failures = { type = FILTER_TYPE_NUMBER, value = rec and rec.total_failures or 0 },
75 75
 		attempts = { type = FILTER_TYPE_NUMBER, value = rec and rec.total_attempts or 0 },
76
+		owner = { type = FILTER_TYPE_STRING, value = get_minetest_config( "name" ) },
76 77
 	} )
77 78
 
78 79
 	return filter_err 
@@ -141,4 +142,27 @@ minetest.register_authentication_handler( {
141 142
 	iterate = auth_db.records
142 143
 } )
143 144
 
145
+minetest.register_chatcommand( "filter", {
146
+        description = "Enable or disable ruleset-based login filtering, or reload a ruleset definition.",
147
+        privs = { server = true },
148
+        func = function( name, param )
149
+                if param == "" then
150
+                        return true, "Login filtering is currently " .. ( auth_filter.is_active( ) and "enabled" or "disabled" ) .. "."
151
+                elseif param == "disable" then
152
+			auth_filter.disable( )
153
+			minetest.log( "action", "Login filtering disabled by " .. name .. "." )
154
+			return true, "Login filtering is disabled."
155
+                elseif param == "enable" then
156
+			auth_filter.enable( )
157
+			minetest.log( "action", "Login filtering enabled by " .. name .. "." )
158
+			return true, "Login filtering is enabled."
159
+                elseif param == "reload" then
160
+			auth_filter.refresh( )
161
+			return true, "Ruleset definition was loaded successfully."
162
+		else
163
+			return false, "Unknown parameter specified."
164
+		end
165
+        end
166
+} )
167
+
144 168
 auth_db.connect( )

Loading…
Cancel
Save