comparison mod_firewall/mod_firewall.lua @ 971:53e158e44a44

mod_firewall: Add rate limiting capabilities, and keep zones and throttle objects in shared tables
author Matthew Wild <mwild1@gmail.com>
date Sat, 06 Apr 2013 22:20:59 +0100
parents a88f33fe6970
children aeb11522a44f
comparison
equal deleted inserted replaced
970:adcb751f22f3 971:53e158e44a44
1 1
2 local resolve_relative_path = require "core.configmanager".resolve_relative_path; 2 local resolve_relative_path = require "core.configmanager".resolve_relative_path;
3 local logger = require "util.logger".init; 3 local logger = require "util.logger".init;
4 local set = require "util.set"; 4 local set = require "util.set";
5 local it = require "util.iterators";
5 local add_filter = require "util.filters".add_filter; 6 local add_filter = require "util.filters".add_filter;
6 7 local new_throttle = require "util.throttle".create;
7 zones = {}; 8
8 local zones = zones; 9 local zones, throttles = module:shared("zones", "throttles");
9 setmetatable(zones, { 10 local active_zones, active_throttles = {}, {};
10 __index = function (zones, zone)
11 local t = { [zone] = true };
12 rawset(zones, zone, t);
13 return t;
14 end;
15 });
16 11
17 local chains = { 12 local chains = {
18 preroute = { 13 preroute = {
19 type = "event"; 14 type = "event";
20 priority = 0.1; 15 priority = 0.1;
32 deliver_remote = { 27 deliver_remote = {
33 type = "event"; "route/remote"; 28 type = "event"; "route/remote";
34 priority = 0.1; 29 priority = 0.1;
35 }; 30 };
36 }; 31 };
32
33 local function idsafe(name)
34 return not not name:match("^%a[%w_]*$")
35 end
37 36
38 -- Dependency locations: 37 -- Dependency locations:
39 -- <type lib> 38 -- <type lib>
40 -- <type global> 39 -- <type global>
41 -- function handler() 40 -- function handler()
71 global_code = [[local group_contains = module:depends("groups").group_contains]]; 70 global_code = [[local group_contains = module:depends("groups").group_contains]];
72 }; 71 };
73 is_admin = { global_code = [[local is_admin = require "core.usermanager".is_admin]]}; 72 is_admin = { global_code = [[local is_admin = require "core.usermanager".is_admin]]};
74 core_post_stanza = { global_code = [[local core_post_stanza = prosody.core_post_stanza]] }; 73 core_post_stanza = { global_code = [[local core_post_stanza = prosody.core_post_stanza]] };
75 zone = { global_code = function (zone) 74 zone = { global_code = function (zone)
76 assert(zone:match("^%a[%w_]*$"), "Invalid zone name: "..zone); 75 assert(idsafe(zone), "Invalid zone name: "..zone);
77 return ("local zone_%s = zones[%q] or {};"):format(zone, zone); 76 return ("local zone_%s = zones[%q] or {};"):format(zone, zone);
78 end }; 77 end };
79 date_time = { global_code = [[local os_date = os.date]]; local_code = [[local current_date_time = os_date("*t");]] }; 78 date_time = { global_code = [[local os_date = os.date]]; local_code = [[local current_date_time = os_date("*t");]] };
80 time = { local_code = function (what) 79 time = { local_code = function (what)
81 local defs = {}; 80 local defs = {};
82 for field in what:gmatch("%a+") do 81 for field in what:gmatch("%a+") do
83 table.insert(defs, ("local current_%s = current_date_time.%s;"):format(field, field)); 82 table.insert(defs, ("local current_%s = current_date_time.%s;"):format(field, field));
84 end 83 end
85 return table.concat(defs, " "); 84 return table.concat(defs, " ");
86 end, depends = { "date_time" }; }; 85 end, depends = { "date_time" }; };
86 throttle = {
87 global_code = function (throttle)
88 assert(idsafe(throttle), "Invalid rate limit name: "..throttle);
89 assert(throttles[throttle], "Unknown rate limit: "..throttle);
90 return ("local throttle_%s = throttles.%s;"):format(throttle, throttle);
91 end;
92 };
87 }; 93 };
88 94
89 local function include_dep(dep, code) 95 local function include_dep(dep, code)
90 local dep, dep_param = dep:match("^([^:]+):?(.*)$"); 96 local dep, dep_param = dep:match("^([^:]+):?(.*)$");
91 local dep_info = available_deps[dep]; 97 local dep_info = available_deps[dep];
186 local zone_member_list = {}; 192 local zone_member_list = {};
187 for member in zone_members:gmatch("[^, ]+") do 193 for member in zone_members:gmatch("[^, ]+") do
188 zone_member_list[#zone_member_list+1] = member; 194 zone_member_list[#zone_member_list+1] = member;
189 end 195 end
190 zones[zone_name] = set.new(zone_member_list)._items; 196 zones[zone_name] = set.new(zone_member_list)._items;
197 table.insert(active_zones, zone_name);
198 elseif not(state) and line:match("^RATE ") then
199 local name = line:match("^RATE ([^:]+)");
200 assert(idsafe(name), "Invalid rate limit name: "..name);
201 local rate = assert(tonumber(line:match(":%s*([%d.]+)")), "Unable to parse rate");
202 local burst = tonumber(line:match("%(%s*burst%s+([%d.]+)%s*%)")) or 1;
203 throttles[name] = new_throttle(rate*burst, burst);
204 table.insert(active_throttles, name);
191 elseif line:match("^[^%s:]+[%.=]") then 205 elseif line:match("^[^%s:]+[%.=]") then
192 -- Action 206 -- Action
193 if state == nil then 207 if state == nil then
194 -- This is a standalone action with no conditions 208 -- This is a standalone action with no conditions
195 rule = new_rule(ruleset, chain); 209 rule = new_rule(ruleset, chain);
263 .."\n end\n"; 277 .."\n end\n";
264 end 278 end
265 table.insert(code, rule_code); 279 table.insert(code, rule_code);
266 end 280 end
267 281
268 local code_string = [[return function (zones, fire_event, log) 282 local code_string = [[return function (zones, throttles, fire_event, log)
269 ]]..table.concat(code.global_header, "\n")..[[ 283 ]]..table.concat(code.global_header, "\n")..[[
270 local db = require 'util.debug' 284 local db = require 'util.debug'
271 return function (event) 285 return function (event)
272 local stanza, session = event.stanza, event.origin; 286 local stanza, session = event.stanza, event.origin;
273 287
289 return nil, "Error compiling (probably a compiler bug, please report): "..err; 303 return nil, "Error compiling (probably a compiler bug, please report): "..err;
290 end 304 end
291 local function fire_event(name, data) 305 local function fire_event(name, data)
292 return module:fire_event(name, data); 306 return module:fire_event(name, data);
293 end 307 end
294 chunk = chunk()(zones, fire_event, logger(filename)); -- Returns event handler with 'zones' upvalue. 308 chunk = chunk()(zones, throttles, fire_event, logger(filename)); -- Returns event handler with 'zones' upvalue.
295 return chunk; 309 return chunk;
296 end 310 end
297 311
312 local function cleanup(t, active_list)
313 local unused = set.new(it.to_array(it.keys(t))) - set.new(active_list);
314 for k in unused do t[k] = nil; end
315 end
316
298 function module.load() 317 function module.load()
318 active_zones, active_throttles = {}, {};
299 local firewall_scripts = module:get_option_set("firewall_scripts", {}); 319 local firewall_scripts = module:get_option_set("firewall_scripts", {});
300 for script in firewall_scripts do 320 for script in firewall_scripts do
301 script = resolve_relative_path(prosody.paths.config, script); 321 script = resolve_relative_path(prosody.paths.config, script);
302 local chain_functions, err = compile_firewall_rules(script) 322 local chain_functions, err = compile_firewall_rules(script)
303 323
320 module:hook("firewall/chains/"..chain, handler); 340 module:hook("firewall/chains/"..chain, handler);
321 end 341 end
322 end 342 end
323 end 343 end
324 end 344 end
325 end 345 -- Remove entries from tables that are no longer in use
346 cleanup(zones, active_zones);
347 cleanup(throttles, active_throttles);
348 end