Mercurial > prosody-modules
comparison mod_firewall/mod_firewall.lua @ 2578:6dbd07f9a868
mod_firewall: Various improvements allowing dynamic load/reload/unload of scripts
author | Matthew Wild <mwild1@gmail.com> |
---|---|
date | Sat, 25 Feb 2017 16:54:52 +0000 |
parents | f65c5927ee8e |
children | 5e948d1392a5 |
comparison
equal
deleted
inserted
replaced
2577:00cef058df8d | 2578:6dbd07f9a868 |
---|---|
1 | 1 |
2 local lfs = require "lfs"; | |
2 local resolve_relative_path = require "core.configmanager".resolve_relative_path; | 3 local resolve_relative_path = require "core.configmanager".resolve_relative_path; |
3 local logger = require "util.logger".init; | 4 local logger = require "util.logger".init; |
4 local it = require "util.iterators"; | 5 local it = require "util.iterators"; |
6 local set = require "util.set"; | |
5 | 7 |
6 local definitions = module:shared("definitions"); | 8 local definitions = module:shared("definitions"); |
7 local active_definitions = { | 9 local active_definitions = { |
8 ZONE = { | 10 ZONE = { |
9 -- Default zone that includes all local hosts | 11 -- Default zone that includes all local hosts |
547 script_path = script_path:match("^module:(.+)$"); | 549 script_path = script_path:match("^module:(.+)$"); |
548 end | 550 end |
549 return resolve_relative_path(relative_to, script_path); | 551 return resolve_relative_path(relative_to, script_path); |
550 end | 552 end |
551 | 553 |
554 -- [filename] = { last_modified = ..., events_hooked = { [name] = handler } } | |
555 local loaded_scripts = {}; | |
556 | |
552 function load_script(script) | 557 function load_script(script) |
553 script = resolve_script_path(script); | 558 script = resolve_script_path(script); |
554 local chain_functions, err = compile_firewall_rules(script) | 559 local last_modified = (lfs.attributes(script) or {}).modification or os.time(); |
560 if loaded_scripts[script] then | |
561 if loaded_scripts[script].last_modified == last_modified then | |
562 return; -- Already loaded, and source file hasn't changed | |
563 end | |
564 module:log("debug", "Reloading %s", script); | |
565 -- Already loaded, but the source file has changed | |
566 -- unload it now, and we'll load the new version below | |
567 unload_script(script, true); | |
568 end | |
569 local chain_functions, err = compile_firewall_rules(script); | |
555 | 570 |
556 if not chain_functions then | 571 if not chain_functions then |
557 module:log("error", "Error compiling %s: %s", script, err or "unknown error"); | 572 module:log("error", "Error compiling %s: %s", script, err or "unknown error"); |
558 else | 573 return; |
559 for chain, handler_code in pairs(chain_functions) do | 574 end |
560 local new_handler, err = compile_handler(handler_code, "mod_firewall::"..chain); | 575 |
561 if not new_handler then | 576 -- Loop through the chains in the script, and for each chain attach the compiled code to the |
562 module:log("error", "Compilation error for %s: %s", script, err); | 577 -- relevant events, keeping track in events_hooked so we can cleanly unload later |
563 else | 578 local events_hooked = {}; |
564 local chain_definition = chains[chain]; | 579 for chain, handler_code in pairs(chain_functions) do |
565 if chain_definition and chain_definition.type == "event" then | 580 local new_handler, err = compile_handler(handler_code, "mod_firewall::"..chain); |
566 local handler = new_handler(chain_definition.pass_return); | 581 if not new_handler then |
567 for _, event_name in ipairs(chain_definition) do | 582 module:log("error", "Compilation error for %s: %s", script, err); |
568 module:hook(event_name, handler, chain_definition.priority); | 583 else |
569 end | 584 local chain_definition = chains[chain]; |
570 elseif not chain:sub(1, 5) == "user/" then | 585 if chain_definition and chain_definition.type == "event" then |
571 module:log("warn", "Unknown chain %q", chain); | 586 local handler = new_handler(chain_definition.pass_return); |
572 end | 587 for _, event_name in ipairs(chain_definition) do |
573 module:hook("firewall/chains/"..chain, new_handler(false)); | 588 events_hooked[event_name] = handler; |
574 end | 589 module:hook(event_name, handler, chain_definition.priority); |
575 end | 590 end |
591 elseif not chain:sub(1, 5) == "user/" then | |
592 module:log("warn", "Unknown chain %q", chain); | |
593 end | |
594 local event_name, handler = "firewall/chains/"..chain, new_handler(false); | |
595 events_hooked[event_name] = handler; | |
596 module:hook(event_name, handler); | |
597 end | |
598 end | |
599 loaded_scripts[script] = { last_modified = last_modified, events_hooked = events_hooked }; | |
600 module:log("debug", "Loaded %s", script); | |
601 end | |
602 | |
603 function unload_script(script, is_reload) | |
604 script = resolve_script_path(script); | |
605 local script_info = loaded_scripts[script]; | |
606 if not script_info then | |
607 return; -- Script not loaded | |
608 end | |
609 local events_hooked = script_info.events_hooked; | |
610 for event_name, event_handler in pairs(events_hooked) do | |
611 module:unhook(event_name, event_handler); | |
612 events_hooked[event_name] = nil; | |
613 end | |
614 loaded_scripts[script] = nil; | |
615 if not is_reload then | |
616 module:log("debug", "Unloaded %s", script); | |
617 end | |
618 end | |
619 | |
620 -- Given a set of scripts (e.g. from config) figure out which ones need to | |
621 -- be loaded, which are already loaded but need unloading, and which to reload | |
622 function load_unload_scripts(script_list) | |
623 local wanted_scripts = script_list / resolve_script_path; | |
624 local currently_loaded = set.new(it.to_array(it.keys(loaded_scripts))); | |
625 local scripts_to_unload = currently_loaded - wanted_scripts; | |
626 for script in wanted_scripts do | |
627 -- If the script is already loaded, this is fine - it will | |
628 -- reload the script for us if the file has changed | |
629 load_script(script); | |
630 end | |
631 for script in scripts_to_unload do | |
632 unload_script(script); | |
576 end | 633 end |
577 end | 634 end |
578 | 635 |
579 function module.load() | 636 function module.load() |
580 if not prosody.arg then return end -- Don't run in prosodyctl | 637 if not prosody.arg then return end -- Don't run in prosodyctl |
581 active_definitions = {}; | |
582 local firewall_scripts = module:get_option_set("firewall_scripts", {}); | 638 local firewall_scripts = module:get_option_set("firewall_scripts", {}); |
583 for script in firewall_scripts do | 639 load_unload_scripts(firewall_scripts); |
584 load_script(script); | |
585 end | |
586 -- Replace contents of definitions table (shared) with active definitions | 640 -- Replace contents of definitions table (shared) with active definitions |
587 for k in it.keys(definitions) do definitions[k] = nil; end | 641 for k in it.keys(definitions) do definitions[k] = nil; end |
588 for k,v in pairs(active_definitions) do definitions[k] = v; end | 642 for k,v in pairs(active_definitions) do definitions[k] = v; end |
589 end | 643 end |
644 | |
645 function module.save() | |
646 return { active_definitions = active_definitions, loaded_scripts = loaded_scripts }; | |
647 end | |
648 | |
649 function module.restore(state) | |
650 active_definitions = state.active_definitions; | |
651 loaded_scripts = state.loaded_scripts; | |
652 end | |
653 | |
654 module:hook_global("config-reloaded", function () | |
655 load_unload_scripts(module:get_option_set("firewall_scripts", {})); | |
656 end); | |
590 | 657 |
591 function module.command(arg) | 658 function module.command(arg) |
592 if not arg[1] or arg[1] == "--help" then | 659 if not arg[1] or arg[1] == "--help" then |
593 require"util.prosodyctl".show_usage([[mod_firewall <firewall.pfw>]], [[Compile files with firewall rules to Lua code]]); | 660 require"util.prosodyctl".show_usage([[mod_firewall <firewall.pfw>]], [[Compile files with firewall rules to Lua code]]); |
594 return 1; | 661 return 1; |