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 11 months 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 @@
1
-Auth Redux Mod v2.9b
1
+Auth Redux Mod v2.10b
2 2
 By Leslie Krause
3 3
 
4 4
 Auth Redux is a drop-in replacement for the builtin authentication handler of Minetest.
@@ -95,6 +95,15 @@ Version 2.9b (26-Jul-2018)
95 95
   - developed and integrated AuthWatchdog class
96 96
   - added meta-variables for stateful login filtering
97 97
 
98
+Version 2.10b (29-Jul-2018)
99
+  - major code reorganization via multiple libraries
100
+  - removed extra variables from AuthFilter class
101
+  - developed interactive debugger for testing rulesets
102
+  - added optional debugger hooks in AuthFilter class
103
+  - allowed for overriding preset variables by debugger
104
+  - included line-number in results of login filter
105
+  - added missing preset variable needed by rulesets
106
+
98 107
 Installation
99 108
 ----------------------
100 109
 

+ 332
- 0
commands.lua View File

@@ -0,0 +1,332 @@
1
+--------------------------------------------------------
2
+-- Minetest :: Auth Redux Mod v2.10 (auth_rx)
3
+--
4
+-- See README.txt for licensing and release notes.
5
+-- Copyright (c) 2017-2018, Leslie E. Krause
6
+--------------------------------------------------------
7
+
8
+-----------------------------------------------------
9
+-- Registered Chat Commands
10
+-----------------------------------------------------
11
+
12
+local auth_db, auth_filter		-- imported
13
+
14
+minetest.register_chatcommand( "filter", {
15
+	description = "Enable or disable ruleset-based login filtering, or reload a ruleset definition.",
16
+	privs = { server = true },
17
+	func = function( name, param )
18
+		if param == "" then
19
+		return true, "Login filtering is currently " .. ( auth_filter.is_active( ) and "enabled" or "disabled" ) .. "."
20
+		elseif param == "disable" then
21
+			auth_filter.disable( )
22
+			minetest.log( "action", "Login filtering disabled by " .. name .. "." )
23
+			return true, "Login filtering is disabled."
24
+		elseif param == "enable" then
25
+			auth_filter.enable( )
26
+			minetest.log( "action", "Login filtering enabled by " .. name .. "." )
27
+			return true, "Login filtering is enabled."
28
+		elseif param == "reload" then
29
+			auth_filter.refresh( )
30
+			return true, "Ruleset definition was loaded successfully."
31
+		else
32
+			return false, "Unknown parameter specified."
33
+		end
34
+	end
35
+} )
36
+
37
+minetest.register_chatcommand( "fdebug", {
38
+	description = "Start an interactive debugger for testing ruleset definitions.",
39
+	privs = { server = true },
40
+	func = function( name, param )
41
+		if not minetest.create_form then return false, "This feature is not supported." end
42
+
43
+		local epoch = os.time( { year = 1970, month = 1, day = 1, hour = 0 } )
44
+		local vars = {
45
+			__debug = { type = FILTER_TYPE_NUMBER, value = 0 },
46
+			name = { type = FILTER_TYPE_STRING, value = "singleplayer" },
47
+			addr = { type = FILTER_TYPE_ADDRESS, value = convert_ipv4( "127.0.0.1" ) },
48
+			is_new = { type = FILTER_TYPE_BOOLEAN, value = true },
49
+			privs_list = { type = FILTER_TYPE_SERIES, value = { } },
50
+			users_list = { type = FILTER_TYPE_SERIES, is_auto = true },
51
+			cur_users = { type = FILTER_TYPE_NUMBER, is_auto = true },
52
+			max_users = { type = FILTER_TYPE_NUMBER, value = get_minetest_config( "max_users" ) },
53
+			lifetime = { type = FILTER_TYPE_PERIOD, value = 0 },
54
+			sessions = { type = FILTER_TYPE_NUMBER, value = 0 },
55
+			failures = { type = FILTER_TYPE_NUMBER, value = 0 },
56
+			attempts = { type = FILTER_TYPE_NUMBER, value = 0 },
57
+			owner = { type = FILTER_TYPE_STRING, value = get_minetest_config( "name" ) },
58
+			uptime = { type = FILTER_TYPE_PERIOD, is_auto = true },
59
+			oldlogin = { type = FILTER_TYPE_MOMENT, value = epoch },
60
+			newlogin = { type = FILTER_TYPE_MOMENT, value = epoch },
61
+			ip_names_list = { type = FILTER_TYPE_SERIES, value = { } },
62
+			ip_prelogin = { type = FILTER_TYPE_MOMENT, value = epoch },
63
+			ip_oldcheck = { type = FILTER_TYPE_MOMENT, value = epoch },
64
+			ip_newcheck = { type = FILTER_TYPE_MOMENT, value = epoch },
65
+			ip_failures = { type = FILTER_TYPE_NUMBER, value = 0 },
66
+			ip_attempts = { type = FILTER_TYPE_NUMBER, value = 0 }
67
+		}
68
+		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" }
69
+		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" }
70
+		local has_prompt = true
71
+		local has_output = true
72
+		local login_index = 2
73
+		local var_index = 1
74
+		local temp_file = io.open( minetest.get_worldpath( ) .. "/~greenlist.mt", "w" ):close( )
75
+		local temp_filter = AuthFilter( minetest.get_worldpath( ), "~greenlist.mt", function ( err, num )
76
+			return num, "The server encountered an internal error.", err
77
+		end )
78
+
79
+		local function clear_prompts( buffer, has_single )
80
+			-- clear debug prompts from source code
81
+			return string.gsub( buffer, "\n# ====== .- ======\n", "\n", has_single and 1 or nil )
82
+		end
83
+		local function insert_prompt( buffer, num, err )
84
+			-- insert debug prompts into source code
85
+			local i = 0
86
+			return string.gsub( buffer, "\n", function ( )
87
+				i = i + 1
88
+				return ( i == num and string.format( "\n# ====== ^ Line %d: %s ^ ======\n", num, err ) or "\n" )
89
+			end )
90
+		end
91
+		local function format_value( value, type )
92
+			-- convert values to a human-readable format
93
+			if type == FILTER_TYPE_STRING then
94
+				return "\"" .. value .. "\""
95
+			elseif type == FILTER_TYPE_NUMBER then
96
+				return tostring( value )
97
+			elseif type == FILTER_TYPE_BOOLEAN then
98
+				return "$" .. tostring( value )
99
+			elseif type == FILTER_TYPE_PERIOD then
100
+				return tostring( math.abs( value ) ) .. "s"
101
+			elseif type == FILTER_TYPE_MOMENT then
102
+				return "+" .. tostring( value - vars.epoch.value ) .. "s"
103
+			elseif type == FILTER_TYPE_ADDRESS then
104
+				return table.concat( unpack_address( value ), "." )
105
+			elseif type == FILTER_TYPE_SERIES then
106
+				return "(" .. string.gsub( table.concat( value, "," ), "[^,]+", "\"%1\"" ) .. ")"
107
+			end
108
+		end
109
+		local function update_vars( )
110
+			-- automatically update preset variables
111
+			if vars.uptime.is_auto then
112
+				vars.uptime.value = minetest.get_server_uptime( ) end
113
+			if vars.clock.is_auto then
114
+				vars.clock.value = os.time( ) end
115
+			if vars.users_list.is_auto then
116
+				vars.users_list.value = auth_db.search( true ) end
117
+			if vars.cur_users.is_auto then
118
+				vars.cur_users.value = #auth_db.search( true ) end
119
+		end
120
+		local function get_formspec( buffer, status, var_state )
121
+			local var_name = vars_list[ var_index ]
122
+			local var_type = vars[ var_name ].type
123
+			local var_value = vars[ var_name ].value
124
+			local var_is_auto = vars[ var_name ].is_auto
125
+
126
+			local formspec = "size[13.5,8.5]"
127
+				.. default.gui_bg
128
+				.. default.gui_bg_img
129
+				.. "label[0.1,0.0;Ruleset Definition:]"
130
+				.. "checkbox[2.6,-0.2;has_output;Show Client Output;" .. tostring( has_output ) .. "]"
131
+				.. "checkbox[5.6,-0.2;has_prompt;Show Debug Prompt;" .. tostring( has_prompt ) .. "]"
132
+				.. "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 ) .. "]"
133
+				.. "button[0.1,7.8;2,1;export_ruleset;Save]"
134
+				.. "button[2.0,7.8;2,1;import_ruleset;Load]"
135
+				.. "button[4.0,7.8;2,1;process_ruleset;Process]"
136
+				.. "dropdown[6,7.9;2.6,1;login_mode;Normal,New Account,Wrong Password;" .. login_index .. "]"
137
+
138
+				.. "label[9.0,0.0;Preset Variables:]"
139
+				.. "textlist[9.0,0.5;4,4.7;vars_list"
140
+
141
+			for i, v in pairs( vars_list ) do
142
+				formspec = formspec .. ( i == 1 and ";" or "," ) .. minetest.formspec_escape( v .. " = " .. format_value( vars[ v ].value, vars[ v ].type ) )
143
+			end
144
+			formspec = formspec .. string.format( ";%d;false]", var_index )
145
+				.. "label[9.0,5.4;Name:]"
146
+				.. "label[9.0,5.9;Type:]"
147
+				.. string.format( "label[10.5,5.4;%s]", minetest.colorize( "#BBFF77", "$" .. var_name ) )
148
+				.. string.format( "label[10.5,5.9;%s]", datatypes[ var_type ] )
149
+				.. "label[9.0,6.4;Value:]"
150
+				.. "field[9.2,7.5;4.3,0.25;var_value;;" .. minetest.formspec_escape( format_value( var_value, var_type ) ) .. "]"
151
+				.. "button[9.0,7.8;1,1;prev_var;<<]"
152
+				.. "button[10.0,7.8;1,1;next_var;>>]"
153
+				.. "button[11.8,7.8;1.5,1;set_var;Set]"
154
+
155
+			if var_is_auto ~= nil then
156
+				formspec = formspec .. "checkbox[10.5,6.2;var_is_auto;Auto Update;" .. tostring( var_is_auto ) .. "]"
157
+			end
158
+
159
+			if status then
160
+				formspec = formspec .. "box[0.1,6.9;8.4,0.8;#555555]"
161
+					.. "label[0.3,7.1;" .. minetest.colorize( status.type == "ERROR" and "#CCCC22" or "#22CC22", status.type .. ": " ) .. status.desc .. "]"
162
+				if status.user then
163
+					formspec = formspec .. "textlist[0.1,5.5;8.4,1.2;;Access denied. Reason: " .. minetest.formspec_escape( status.user ) .. ";0;false]"
164
+				end
165
+			end
166
+			return formspec
167
+		end
168
+		local function on_close( meta, player, fields )
169
+			login_index = ( { ["Normal"] = 1, ["New Account"] = 2, ["Wrong Password"] = 3 } )[ fields.login_mode ] or 1	-- sanity check
170
+
171
+			if fields.quit then
172
+				os.remove( minetest.get_worldpath( ) .. "/~greenlist.mt" )
173
+
174
+			elseif fields.vars_list then
175
+				local event = minetest.explode_textlist_event( fields.vars_list )
176
+				if event.type == "CHG" then
177
+					var_index = event.index
178
+					minetest.update_form( name, get_formspec( fields.buffer ) )
179
+				end
180
+
181
+			elseif fields.has_prompt then
182
+				has_prompt = fields.has_prompt == "true"
183
+
184
+			elseif fields.has_output then
185
+				has_output = fields.has_output == "true"
186
+
187
+			elseif fields.export_ruleset then
188
+				local buffer = clear_prompts( fields.buffer .. "\n", true )
189
+				local file = io.open( minetest.get_worldpath( ) .. "/greenlist.mt", "w" )
190
+				if not file then
191
+					error( "Cannot write to ruleset definition file." )
192
+				end
193
+				file:write( buffer )
194
+				file:close( )
195
+				minetest.update_form( name, get_formspec( buffer, { type = "ACTION", desc = "Ruleset definition exported." } ) )
196
+
197
+			elseif fields.import_ruleset then
198
+				local file = io.open( minetest.get_worldpath( ) .. "/greenlist.mt", "r" )
199
+				if not file then
200
+					error( "Cannot read from ruleset definition file." )
201
+				end
202
+				minetest.update_form( name, get_formspec( file:read( "*a" ), { type = "ACTION", desc = "Ruleset definition imported." } ) )
203
+				file:close( )
204
+
205
+			elseif fields.process_ruleset then
206
+				local status
207
+				local buffer = clear_prompts( fields.buffer .. "\n", true )	-- we need a trailing newline, or things will break
208
+
209
+				-- output ruleset to temp file for processing
210
+				local temp_file = io.open( minetest.get_worldpath( ) .. "/~greenlist.mt", "w" )
211
+				temp_file:write( buffer )
212
+				temp_file:close( )
213
+				temp_filter.refresh( )
214
+
215
+				update_vars( )
216
+
217
+				if fields.login_mode == "New Account" then
218
+					vars.is_new.value = true
219
+					vars.privs_list.value = { }
220
+					vars.lifetime.value = 0
221
+					vars.sessions.value = 0
222
+					vars.failures.value = 0
223
+					vars.attempts.value = 0
224
+					vars.newlogin.value = epoch
225
+					vars.oldlogin.value = epoch
226
+				else
227
+					vars.is_new.value = false
228
+					vars.attempts.value = vars.attempts.value + 1
229
+				end
230
+
231
+				-- process ruleset and benchmark performance
232
+				local t = minetest.get_us_time( )
233
+				local num, res, err = temp_filter.process( vars )
234
+				t = ( minetest.get_us_time( ) - t ) / 1000
235
+
236
+				if err then
237
+					if has_prompt then buffer = insert_prompt( buffer, num, err ) end
238
+					status = { type = "ERROR", desc = string.format( "%s (line %d).", err, num ), user = has_output and res }
239
+
240
+					vars.ip_attempts.value = vars.ip_attempts.value + 1
241
+					vars.ip_prelogin.value = vars.clock.value
242
+					table.insert( vars.ip_names_list.value, vars.name.value )
243
+
244
+				elseif res then
245
+					if has_prompt then buffer = insert_prompt( buffer, num, "Ruleset failed" ) end
246
+					status = { type = "ACTION", desc = string.format( "Ruleset failed at line %d (took %0.1f ms).", num, t ), user = has_output and res }
247
+
248
+					vars.ip_attempts.value = vars.ip_attempts.value + 1
249
+					vars.ip_prelogin.value = vars.clock.value
250
+					table.insert( vars.ip_names_list.value, vars.name.value )
251
+
252
+				elseif fields.login_mode == "Wrong Password" then
253
+					if has_prompt then buffer = insert_prompt( buffer, num, "Ruleset failed" ) end
254
+					status = { type = "ACTION", desc = string.format( "Ruleset failed at line %d (took %0.1f ms).", num, t ), user = has_output and "Invalid password" }
255
+
256
+					vars.failures.value = vars.failures.value + 1
257
+					vars.ip_attempts.value = vars.ip_attempts.value + 1
258
+					vars.ip_failures.value = vars.ip_failures.value + 1
259
+					vars.ip_prelogin.value = vars.clock.value
260
+					vars.ip_newcheck.value = vars.clock.value
261
+					if vars.ip_oldcheck.value == epoch then
262
+						vars.ip_oldcheck.value = vars.clock.value
263
+					end
264
+					table.insert( vars.ip_names_list.value, vars.name.value )
265
+
266
+				else
267
+					if has_prompt then buffer = insert_prompt( buffer, num, "Ruleset passed" ) end
268
+					status = { type = "ACTION", desc = string.format( "Ruleset passed at line %d (took %0.1f ms).", num, t ) }
269
+
270
+					if fields.login_mode == "New Account" then
271
+						vars.privs_list.value = get_default_privs( )
272
+					end
273
+					vars.sessions.value = vars.sessions.value + 1
274
+					vars.newlogin.value = vars.clock.value
275
+					if vars.oldlogin.value == epoch then 
276
+						vars.oldlogin.value = vars.clock.value
277
+					end
278
+					vars.ip_failures.value = 0
279
+					vars.ip_attempts.value = 0
280
+					vars.ip_prelogin.value = epoch
281
+					vars.ip_oldcheck.value = epoch
282
+					vars.ip_newcheck.value = epoch
283
+					vars.ip_names_list.value = { }
284
+				end
285
+
286
+				minetest.update_form( name, get_formspec( buffer, status ) )
287
+
288
+			elseif fields.next_var or fields.prev_var then
289
+				local idx = var_index
290
+				local off = fields.next_var and 1 or -1
291
+				if off == 1 and idx < #vars_list or off == -1 and idx > 1 then
292
+					local v = vars_list[ idx ]
293
+					vars_list[ idx ] = vars_list[ idx + off ]
294
+					vars_list[ idx + off ] = v
295
+					var_index = idx + off
296
+					minetest.update_form( name, get_formspec( fields.buffer ) )
297
+				end
298
+
299
+			elseif fields.var_is_auto then
300
+				local var_name = vars_list[ var_index ]
301
+				vars[ var_name ].is_auto = ( fields.var_is_auto == "true" )
302
+
303
+			elseif fields.set_var then
304
+				local oper = temp_filter.translate( string.trim( fields.var_value ), vars )
305
+				local var_name = vars_list[ var_index ]
306
+
307
+				if oper and var_name == "__debug" and datatypes[ oper.type ] then
308
+					-- debug variable can be any value/type
309
+					vars.__debug = oper
310
+				elseif oper and oper.type == vars[ var_name ].type then
311
+					vars[ var_name ].value = oper.value
312
+				end
313
+
314
+				minetest.update_form( name, get_formspec( fields.buffer ) )
315
+
316
+			end
317
+		end
318
+
319
+		temp_filter.add_preset_vars( vars )
320
+		vars.clock.is_auto = true
321
+		update_vars( )
322
+
323
+		minetest.create_form( nil, name, get_formspec( "pass now\n" ), on_close )
324
+
325
+		return true
326
+	end,
327
+} )
328
+
329
+return function ( import )
330
+	auth_db = import.auth_db
331
+	auth_filter = import.auth_filter	
332
+end

+ 47
- 36
filter.lua View File

@@ -1,5 +1,5 @@
1 1
 --------------------------------------------------------
2
+-- Minetest :: Auth Redux Mod v2.10 (auth_rx)
2 3
 --
3 4
 -- See README.txt for licensing and release notes.
4 5
 -- Copyright (c) 2017-2018, Leslie E. Krause
@@ -46,9 +46,6 @@ local redate = function ( ts )
46 46
 	x.isdst = false		
47 47
 	return os.time( x )
48 48
 end
49
-local unpack_address = function ( addr )
50
-	return { math.floor( addr / 16777216 ), math.floor( ( addr % 16777216 ) / 65536 ), math.floor( ( addr % 65536 ) / 256 ), addr % 256 }
51
-end
52 49
 
53 50
 ----------------------------
54 51
 -- StringPattern class
@@ -114,9 +111,9 @@ end
114 111
 -- AuthFilter class
115 112
 ----------------------------
116 113
 
117
-function AuthFilter( path, name )
114
+function AuthFilter( path, name, debug )
118 115
 	local src
119
-	local opt = { is_debug = false, is_strict = true, is_active = true }
116
+	local is_active = true
120 117
 	local self = { }
121 118
 
122 119
 	local funcs = {
@@ -154,15 +151,14 @@ function AuthFilter( path, name )
154 151
 	-- private methods
155 152
 	----------------------------
156 153
 
157
-	local get_operand, trace, evaluate
154
+	local trace, get_operand, evaluate, tokenize
158 155
 
159
-	trace = function ( msg, num )
160
-		-- TODO: Use 'pcall' for more graceful exception handling?
156
+	trace = debug or function ( msg, num )
161 157
 		minetest.log( "error", string.format( "%s (%s/%s, line %d)", msg, path, name, num ) )
162
-		return "The server encountered an internal error."
158
+		return num, "The server encountered an internal error."
163 159
 	end
164 160
 
165
-	get_operand = function ( token, vars )
161
+	function get_operand( token, vars )
166 162
 		local t, v, ref
167 163
 
168 164
 		local find_token = function ( pat )
@@ -320,7 +316,7 @@ function AuthFilter( path, name )
320 316
 		return { type = t, value = v }
321 317
 	end
322 318
 
323
-	evaluate = function ( rule )
319
+	function evaluate( rule )
324 320
 		-- short circuit binary logic to simplify evaluation
325 321
 		local res = ( rule.bool == FILTER_BOOL_AND )
326 322
 		local xor = 0
@@ -339,10 +335,31 @@ function AuthFilter( path, name )
339 335
 		return res
340 336
 	end
341 337
 
338
+	function tokenize( line )
339
+		-- encode string and pattern literals and function arguments to simplify parsing (order IS significant)
340
+		line = string.gsub( line, "\"(.-)\"", function ( str )
341
+			return "\"" .. encode_base64( str ) .. ";"
342
+		end )
343
+		line = string.gsub( line, "'(.-)'", function ( str )
344
+			return "'" .. encode_base64( str ) .. ";"
345
+		end )
346
+		line = string.gsub( line, "/(.-)/([stda]?)", function ( a, b )
347
+			return "/" .. encode_base64( a ) .. "," .. ( b == "" and "s" or b ) .. ";"
348
+		end )
349
+		line = string.gsub( line, "%b()", function ( str )
350
+			return "&" .. encode_base64( trim( str ) ) .. ";"
351
+		end )
352
+		return line
353
+	end
354
+
342 355
 	----------------------------
343 356
 	-- public methods
344 357
 	----------------------------
345 358
 
359
+	self.translate = function ( field, vars )
360
+		return get_operand( tokenize( field ), vars )
361
+	end
362
+
346 363
 	self.refresh = function ( )
347 364
 		local file = io.open( path .. "/" .. name, "r" )
348 365
 		if not file then
@@ -350,37 +367,30 @@ function AuthFilter( path, name )
350 367
 		end
351 368
 		src = { }
352 369
 		for line in file:lines( ) do
353
-			-- encode string and pattern literals and function arguments to simplify parsing
354
-			line = string.gsub( line, "\"(.-)\"", function ( str )
355
-				return "\"" .. encode_base64( str ) .. ";"
356
-			end )
357
-			line = string.gsub( line, "'(.-)'", function ( str )
358
-				return "'" .. encode_base64( str ) .. ";"
359
-			end )
360
-			line = string.gsub( line, "/(.-)/([stda]?)", function ( a, b )
361
-				return "/" .. encode_base64( a ) .. "," .. ( b == "" and "s" or b ) .. ";"
362
-			end )
363
-			line = string.gsub( line, "%b()", function ( str )
364
-				return "&" .. encode_base64( trim( str ) ) .. ";"
365
-			end )
366 370
 			-- skip comments (lines beginning with hash character) and blank lines
367 371
 			-- TODO: remove extraneous white space at beginning of lines
368
-			table.insert( src, string.byte( line ) ~= 35 and line or "" )
372
+			table.insert( src, string.byte( line ) ~= 35 and tokenize( line ) or "" )
369 373
 		end
370 374
 		file:close( file )
371 375
 	end
372 376
 
377
+	self.add_preset_vars = function ( vars )
378
+		vars[ "clock" ] = { type = FILTER_TYPE_MOMENT, value = os.time( ) }
379
+		vars[ "epoch" ] = { type = FILTER_TYPE_MOMENT, value = os.time( { year = 1970, month = 1, day = 1, hour = 0 } ) }
380
+		vars[ "true" ] = { type = FILTER_TYPE_BOOLEAN, value = true }
381
+		vars[ "false" ] = { type = FILTER_TYPE_BOOLEAN, value = false }
382
+	end
383
+
373 384
 	self.process = function( vars )
374 385
 		local rule
375 386
 		local note = "Access denied."
376 387
 
377
-		if not opt.is_active then return end
388
+		if not is_active then return end
378 389
 
379
-		vars[ "true" ] = { type = FILTER_TYPE_BOOLEAN, value = true }
380
-		vars[ "false" ] = { type = FILTER_TYPE_BOOLEAN, value = false }
381
-		vars[ "clock" ] = { type = FILTER_TYPE_MOMENT, value = os.time( ) }
382
-		vars[ "epoch" ] = { type = FILTER_TYPE_MOMENT, value = os.time( { year = 1970, month = 1, day = 1, hour = 0 } ) }
383
-		vars[ "y2k" ] = { type = FILTER_TYPE_MOMENT, value = os.time( { year = 2000, month = 1, day = 1, hour = 0 } ) }
390
+		if not debug then
391
+			-- allow overriding preset vars when debugger is active
392
+			self.add_preset_vars( vars )
393
+		end
384 394
 
385 395
 		for num, line in ipairs( src ) do
386 396
 			local stmt = string.split( line, " ", false )
@@ -393,7 +403,7 @@ function AuthFilter( path, name )
393 403
 				if #stmt ~= 1 then return trace( "Invalid 'continue' statement in ruleset", num ) end
394 404
 
395 405
 				if evaluate( rule ) then
396
-					return ( rule.mode == FILTER_MODE_FAIL and note or nil )
406
+					return num, ( rule.mode == FILTER_MODE_FAIL and note or nil )
397 407
 				end
398 408
 
399 409
 				rule = nil
@@ -423,7 +433,7 @@ function AuthFilter( path, name )
423 433
 				end
424 434
 
425 435
 				if bool == FILTER_BOOL_NOW then
426
-					return ( mode == FILTER_MODE_FAIL and note or nil )
436
+					return num, ( mode == FILTER_MODE_FAIL and note or nil )
427 437
 				end
428 438
 
429 439
 				rule.mode = mode
@@ -525,15 +535,15 @@ function AuthFilter( path, name )
525 535
 	end
526 536
 
527 537
 	self.enable = function ( )
528
-		opt.is_active = true
538
+		is_active = true
529 539
 	end
530 540
 
531 541
 	self.disable = function ( )
532
-		opt.is_active = false
542
+		is_active = false
533 543
 	end
534 544
 
535 545
 	self.is_active = function ( )
536
-		return opt.is_active
546
+		return is_active
537 547
 	end
538 548
 
539 549
 	self.refresh( )

+ 45
- 0
helpers.lua View File

@@ -0,0 +1,45 @@
1
+--------------------------------------------------------
2
+-- Minetest :: Auth Redux Mod v2.10 (auth_rx)
3
+--
4
+-- See README.txt for licensing and release notes.
5
+-- Copyright (c) 2017-2018, Leslie E. Krause
6
+--------------------------------------------------------
7
+
8
+-----------------------------------------------------
9
+-- Global Helper Functions
10
+-----------------------------------------------------
11
+
12
+get_minetest_config = core.setting_get    -- backwards compatibility
13
+
14
+function convert_ipv4( str )
15
+	local ref = string.split( str, ".", false )
16
+	return tonumber( ref[ 1 ] ) * 16777216 + tonumber( ref[ 2 ] ) * 65536 + tonumber( ref[ 3 ] ) * 256 + tonumber( ref[ 4 ] )
17
+end
18
+
19
+function unpack_address( addr )
20
+        return { math.floor( addr / 16777216 ), math.floor( ( addr % 16777216 ) / 65536 ), math.floor( ( addr % 65536 ) / 256 ), addr % 256 }
21
+end
22
+
23
+function get_default_privs( )
24
+	local default_privs = { }
25
+	for _, p in pairs( string.split( get_minetest_config( "default_privs" ), "," ) ) do
26
+		table.insert( default_privs, string.trim( p ) )
27
+	end
28
+	return default_privs
29
+end
30
+
31
+function unpack_privileges( assigned_privs )
32
+	local privileges = { }
33
+	for _, p in ipairs( assigned_privs ) do
34
+		privileges[ p ] = true
35
+	end
36
+	return privileges
37
+end
38
+
39
+function pack_privileges( privileges )
40
+	local assigned_privs = { }
41
+	for p, _ in pairs( privileges ) do
42
+		table.insert( assigned_privs, p )
43
+	end
44
+	return assigned_privs
45
+end

+ 15
- 63
init.lua View File

@@ -1,13 +1,15 @@
1 1
 --------------------------------------------------------
2
+-- Minetest :: Auth Redux Mod v2.10 (auth_rx)
2 3
 --
3 4
 -- See README.txt for licensing and release notes.
4 5
 -- Copyright (c) 2017-2018, Leslie E. Krause
5 6
 --------------------------------------------------------
6 7
 
8
+dofile( minetest.get_modpath( "auth_rx" ) .. "/helpers.lua" )
7 9
 dofile( minetest.get_modpath( "auth_rx" ) .. "/filter.lua" )
8 10
 dofile( minetest.get_modpath( "auth_rx" ) .. "/db.lua" )
9 11
 dofile( minetest.get_modpath( "auth_rx" ) .. "/watchdog.lua" )
12
+local __commands = dofile( minetest.get_modpath( "auth_rx" ) .. "/commands.lua" )
10 13
 
11 14
 -----------------------------------------------------
12 15
 -- Registered Authentication Handler
@@ -17,37 +19,6 @@ local auth_filter = AuthFilter( minetest.get_worldpath( ), "greenlist.mt" )
17 19
 local auth_db = AuthDatabase( minetest.get_worldpath( ), "auth.db" )
18 20
 local auth_watchdog = AuthWatchdog( )
19 21
 
20
-local get_minetest_config = core.setting_get	-- backwards compatibility
21
-
22
-function get_default_privs( )
23
-	local default_privs = { }
24
-	for _, p in pairs( string.split( get_minetest_config( "default_privs" ), "," ) ) do
25
-		table.insert( default_privs, string.trim( p ) )
26
-	end
27
-	return default_privs
28
-end
29
-
30
-function unpack_privileges( assigned_privs )
31
-	local privileges = { }
32
-	for _, p in ipairs( assigned_privs ) do
33
-		privileges[ p ] = true
34
-	end
35
-	return privileges
36
-end
37
-
38
-function pack_privileges( privileges )
39
-	local assigned_privs = { }
40
-	for p, _ in pairs( privileges ) do
41
-		table.insert( assigned_privs, p )
42
-	end
43
-	return assigned_privs
44
-end
45
-
46
-function convert_ipv4( str )
47
-	local ref = string.split( str, ".", false )
48
-	return tonumber( ref[ 1 ] ) * 16777216 + tonumber( ref[ 2 ] ) * 65536 + tonumber( ref[ 3 ] ) * 256 + tonumber( ref[ 4 ] )
49
-end
50
-
51 22
 if minetest.register_on_auth_fail then
52 23
 	minetest.register_on_auth_fail( function ( player_name, player_ip )
53 24
 		auth_db.on_login_failure( player_name, player_ip )
@@ -57,7 +28,7 @@ end
57 28
 
58 29
 minetest.register_on_prejoinplayer( function ( player_name, player_ip )
59 30
 	local rec = auth_db.select_record( player_name )
60
-	local res = auth_watchdog.get_metadata( convert_ipv4( player_ip ) )
31
+	local meta = auth_watchdog.get_metadata( convert_ipv4( player_ip ) )
61 32
 
62 33
 	if rec then
63 34
 		auth_db.on_login_attempt( player_name, player_ip )
@@ -71,7 +42,7 @@ minetest.register_on_prejoinplayer( function ( player_name, player_ip )
71 42
 		end
72 43
 	end
73 44
 
74
-	local filter_err = auth_filter.process( {
45
+	local num, res = auth_filter.process( {
75 46
 		name = { type = FILTER_TYPE_STRING, value = player_name },
76 47
 		addr = { type = FILTER_TYPE_ADDRESS, value = convert_ipv4( player_ip ) },
77 48
 		is_new = { type = FILTER_TYPE_BOOLEAN, value = rec == nil },
@@ -79,6 +50,7 @@ minetest.register_on_prejoinplayer( function ( player_name, player_ip )
79 50
 		users_list = { type = FILTER_TYPE_SERIES, value = auth_db.search( true ) },
80 51
 		cur_users = { type = FILTER_TYPE_NUMBER, value = #auth_db.search( true ) },
81 52
 		max_users = { type = FILTER_TYPE_NUMBER, value = get_minetest_config( "max_users" ) },
53
+		lifetime = { type = FILTER_TYPE_PERIOD, value = rec and rec.lifetime or 0 },
82 54
 		sessions = { type = FILTER_TYPE_NUMBER, value = rec and rec.total_sessions or 0 },
83 55
 		failures = { type = FILTER_TYPE_NUMBER, value = rec and rec.total_failures or 0 },
84 56
 		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 )
86 58
 		uptime = { type = FILTER_TYPE_PERIOD, value = minetest.get_server_uptime( ) },
87 59
 		oldlogin = { type = FILTER_TYPE_MOMENT, value = rec and rec.oldlogin or 0 },
88 60
 		newlogin = { type = FILTER_TYPE_MOMENT, value = rec and rec.newlogin or 0 },
89
-		ip_names_list = { type = FILTER_TYPE_SERIES, value = res.previous_names or { } },
90
-		ip_prelogin = { type = FILTER_TYPE_MOMENT, value = res.prelogin or 0 },
91
-		ip_oldcheck = { type = FILTER_TYPE_MOMENT, value = res.oldcheck or 0 },
92
-		ip_newcheck = { type = FILTER_TYPE_MOMENT, value = res.newcheck or 0 },
93
-		ip_failures = { type = FILTER_TYPE_NUMBER, value = res.count_failures or 0 },
94
-		ip_attempts = { type = FILTER_TYPE_NUMBER, value = res.count_attempts or 0 }
61
+		ip_names_list = { type = FILTER_TYPE_SERIES, value = meta.previous_names or { } },
62
+		ip_prelogin = { type = FILTER_TYPE_MOMENT, value = meta.prelogin or 0 },
63
+		ip_oldcheck = { type = FILTER_TYPE_MOMENT, value = meta.oldcheck or 0 },
64
+		ip_newcheck = { type = FILTER_TYPE_MOMENT, value = meta.newcheck or 0 },
65
+		ip_failures = { type = FILTER_TYPE_NUMBER, value = meta.count_failures or 0 },
66
+		ip_attempts = { type = FILTER_TYPE_NUMBER, value = meta.count_attempts or 0 }
95 67
 	} )
96 68
 
97 69
 	auth_watchdog.on_attempt( convert_ipv4( player_ip ), player_name )
98 70
 
99
-	return filter_err 
71
+	return res 
100 72
 end )
101 73
 
102 74
 minetest.register_on_joinplayer( function ( player )
@@ -166,27 +138,6 @@ minetest.register_authentication_handler( {
166 138
 	iterate = auth_db.records
167 139
 } )
168 140
 
169
-minetest.register_chatcommand( "filter", {
170
-	description = "Enable or disable ruleset-based login filtering, or reload a ruleset definition.",
171
-	privs = { server = true },
172
-	func = function( name, param )
173
-		if param == "" then
174
-		return true, "Login filtering is currently " .. ( auth_filter.is_active( ) and "enabled" or "disabled" ) .. "."
175
-		elseif param == "disable" then
176
-			auth_filter.disable( )
177
-			minetest.log( "action", "Login filtering disabled by " .. name .. "." )
178
-			return true, "Login filtering is disabled."
179
-		elseif param == "enable" then
180
-			auth_filter.enable( )
181
-			minetest.log( "action", "Login filtering enabled by " .. name .. "." )
182
-			return true, "Login filtering is enabled."
183
-		elseif param == "reload" then
184
-			auth_filter.refresh( )
185
-			return true, "Ruleset definition was loaded successfully."
186
-		else
187
-			return false, "Unknown parameter specified."
188
-		end
189
-	end
190
-} )
191
-
192 141
 auth_db.connect( )
142
+
143
+__commands( { auth_db = auth_db, auth_filter = auth_filter } )

Loading…
Cancel
Save