From fc670b19af9f6dd4027dcec0fc6cc0035860f9e2 Mon Sep 17 00:00:00 2001 From: Ted Trask Date: Mon, 3 Nov 2008 21:30:32 +0000 Subject: Created iptables ACF to directly view / edit rules. Status, view, and expert work. Form to edit / create rules not functional yet. git-svn-id: svn://svn.alpinelinux.org/acf/iptables/trunk@1575 ab2d0c66-481e-0410-8bed-d214d4d58bed --- Makefile | 44 +++++++ README | 1 + config.mk | 10 ++ iptables-controller.lua | 64 ++++++++++ iptables-createchain-html.lsp | 1 + iptables-createrule-html.lsp | 1 + iptables-details-html.lsp | 21 ++++ iptables-editchain-html.lsp | 15 +++ iptables-editrule-html.lsp | 35 ++++++ iptables-expert-html.lsp | 1 + iptables-html.lsp | 35 ++++++ iptables-model.lua | 268 ++++++++++++++++++++++++++++++++++++++++++ iptables-startstop-html.lsp | 25 ++++ iptables-status-html.lsp | 1 + iptables.menu | 6 + iptables.roles | 4 + 16 files changed, 532 insertions(+) create mode 100644 Makefile create mode 100644 README create mode 100644 config.mk create mode 100644 iptables-controller.lua create mode 120000 iptables-createchain-html.lsp create mode 120000 iptables-createrule-html.lsp create mode 100644 iptables-details-html.lsp create mode 100644 iptables-editchain-html.lsp create mode 100644 iptables-editrule-html.lsp create mode 120000 iptables-expert-html.lsp create mode 100644 iptables-html.lsp create mode 100644 iptables-model.lua create mode 100644 iptables-startstop-html.lsp create mode 120000 iptables-status-html.lsp create mode 100644 iptables.menu create mode 100644 iptables.roles diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d0b3ed1 --- /dev/null +++ b/Makefile @@ -0,0 +1,44 @@ +APP_NAME=iptables +PACKAGE=acf-$(APP_NAME) +VERSION=0.0.1 + +APP_DIST=\ + iptables* \ + + +EXTRA_DIST=README Makefile config.mk + +DISTFILES=$(APP_DIST) $(EXTRA_DIST) + +TAR=tar + +P=$(PACKAGE)-$(VERSION) +tarball=$(P).tar.bz2 +install_dir=$(DESTDIR)/$(appdir)/$(APP_NAME) + +all: +clean: + rm -rf $(tarball) $(P) + +dist: $(tarball) + +install: + mkdir -p "$(install_dir)" + cp -a $(APP_DIST) "$(install_dir)" + +$(tarball): $(DISTFILES) + rm -rf $(P) + mkdir -p $(P) + cp -a $(DISTFILES) $(P) + $(TAR) -jcf $@ $(P) + rm -rf $(P) + +# target that creates a tar package, unpacks is and install from package +dist-install: $(tarball) + $(TAR) -jxf $(tarball) + $(MAKE) -C $(P) install DESTDIR=$(DESTDIR) + rm -rf $(P) + +include config.mk + +.PHONY: all clean dist install dist-install diff --git a/README b/README new file mode 100644 index 0000000..8790399 --- /dev/null +++ b/README @@ -0,0 +1 @@ +ACF for iptables diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..45f4d21 --- /dev/null +++ b/config.mk @@ -0,0 +1,10 @@ +prefix=/usr +datadir=${prefix}/share +sysconfdir=${prefix}/etc +localstatedir=${prefix}/var +acfdir=${datadir}/acf +wwwdir=${acfdir}/www +cgibindir=${acfdir}/cgi-bin +appdir=${acfdir}/app +acflibdir=${acfdir}/lib +sessionsdir=${localstatedir}/lib/acf/sessions diff --git a/iptables-controller.lua b/iptables-controller.lua new file mode 100644 index 0000000..16fac32 --- /dev/null +++ b/iptables-controller.lua @@ -0,0 +1,64 @@ +module(..., package.seeall) + +-- Load libraries +require("controllerfunctions") + +-- ################################################################################ +-- LOCAL FUNCTIONS + +-- ################################################################################ +-- PUBLIC FUNCTIONS + +default_action = "status" + +function status(self) + return self.model.getstatus() +end + +function details(self) + return self.model.getstatusdetails() +end + +function filterrules(self) + return self.model.getrules("filter") +end + +function natrules(self) + return self.model.getrules("nat") +end + +function manglerules(self) + return self.model.getrules("mangle") +end + +function editchain(self) + return controllerfunctions.handle_form(self, function() return self.model.read_chain(self.clientdata.table, self.clientdata.chain or "") end, self.model.update_chain, self.clientdata, "Save", "Edit Chain", "Chain saved") +end + +function createchain(self) + return controllerfunctions.handle_form(self, function() return self.model.read_chain(self.clientdata.table) end, self.model.create_chain, self.clientdata, "Create", "Create New Chain", "Chain created") +end + +function deletechain(self) + return self:redirect_to_referrer(self.model.delete_chain(self.clientdata.table, self.clientdata.chain)) +end + +function editrule(self) + return controllerfunctions.handle_form(self, function() return self.model.read_rule(self.clientdata.table, self.clientdata.chain or "", self.clientdata.position or "") end, self.model.update_rule, self.clientdata, "Save", "Edit Rule", "Rule saved") +end + +function createrule(self) + return controllerfunctions.handle_form(self, function() return self.model.read_rule(self.clientdata.table, self.clientdata.chain, self.clientdata.position) end, self.model.create_rule, self.clientdata, "Create", "Create New Rule", "Rule created") +end + +function deleterule(self) + return self:redirect_to_referrer(self.model.delete_rule(self.clientdata.table, self.clientdata.chain, self.clientdata.position)) +end + +function expert(self) + return controllerfunctions.handle_form(self, self.model.readrulesfile, self.model.updaterulesfile, self.clientdata, "Save", "Edit Rules File", "Rules File Saved") +end + +function startstop(self) + return controllerfunctions.handle_startstop(self, self.model.startstop_service, self.model.getstatus, self.clientdata) +end diff --git a/iptables-createchain-html.lsp b/iptables-createchain-html.lsp new file mode 120000 index 0000000..4ff1163 --- /dev/null +++ b/iptables-createchain-html.lsp @@ -0,0 +1 @@ +iptables-editchain-html.lsp \ No newline at end of file diff --git a/iptables-createrule-html.lsp b/iptables-createrule-html.lsp new file mode 120000 index 0000000..9a0fb9c --- /dev/null +++ b/iptables-createrule-html.lsp @@ -0,0 +1 @@ +iptables-editrule-html.lsp \ No newline at end of file diff --git a/iptables-details-html.lsp b/iptables-details-html.lsp new file mode 100644 index 0000000..bbdbd9a --- /dev/null +++ b/iptables-details-html.lsp @@ -0,0 +1,21 @@ +<% local data, viewlibrary = ... +require("viewfunctions") +--[[ DEBUG INFORMATION +io.write("

DEBUGGING

DEBUG INFO: CFE

") +io.write(html.cfe_unpack(data)) +io.write("
") +--]] +%> + +<% viewlibrary.dispatch_component("status") %> + +

<%= data.label %>

+
+<% for i,tab in ipairs({"filter", "nat", "mangle"}) do %> +

<%= tab %>

+ + <%= data.value[tab].chains %> Chains + <%= data.value[tab].rules %> Rules +
+<% end %> +
diff --git a/iptables-editchain-html.lsp b/iptables-editchain-html.lsp new file mode 100644 index 0000000..eaa1bfb --- /dev/null +++ b/iptables-editchain-html.lsp @@ -0,0 +1,15 @@ +<% local form, viewlibrary, page_info = ... +require("viewfunctions") +%> + +

<%= form.label %>

+<% + form.action = page_info.script .. page_info.prefix .. page_info.controller .. "/" .. page_info.action + if page_info.action == "editchain" then + form.value.table.contenteditable = false + form.value.table.type = "text" + form.value.chain.contenteditable = false + end + local order = {"table", "chain", "policy"} + displayform(form, order) +%> diff --git a/iptables-editrule-html.lsp b/iptables-editrule-html.lsp new file mode 100644 index 0000000..7a7ad3c --- /dev/null +++ b/iptables-editrule-html.lsp @@ -0,0 +1,35 @@ +<% local form, viewlibrary, page_info = ... +require("viewfunctions") +%> + +

<%= form.label %>

+<% +for name,val in pairs(form.value) do val.name = name end +form.value.table.contenteditable = false +form.value.table.type = "text" +form.value.chain.contenteditable = false +form.value.position.contenteditable = false +form.action = page_info.script .. page_info.prefix .. page_info.controller .. "/" .. page_info.action +displayformstart(form) +%> + +

Rule Identification

+<% +displayformitem(form.value.table) +displayformitem(form.value.chain) +displayformitem(form.value.position) +%> +

Basic Parameters

+<% +displayformitem(form.value.protocol) +displayformitem(form.value.source) +displayformitem(form.value.destination) +displayformitem(form.value.jump) +displayformitem(form.value.goto) +displayformitem(form.value.in_interface) +displayformitem(form.value.out_interface) +displayformitem(form.value.fragment) +displayformitem(form.value.set_counters) +%> +

Save

+<% displayformend(form) %> diff --git a/iptables-expert-html.lsp b/iptables-expert-html.lsp new file mode 120000 index 0000000..207f324 --- /dev/null +++ b/iptables-expert-html.lsp @@ -0,0 +1 @@ +../expert-html.lsp \ No newline at end of file diff --git a/iptables-html.lsp b/iptables-html.lsp new file mode 100644 index 0000000..d9600e1 --- /dev/null +++ b/iptables-html.lsp @@ -0,0 +1,35 @@ +<% local data, viewlibrary, page_info, session = ... %> +<% require("viewfunctions") %> + +<% displaycommandresults({"editchain", "deletechain", "createrule", "deleterule", "editrule", "createchain"}, session) %> + +

<%= data.label %>

+
+ + <% local tab = data.value.table %> + <% for j,chain in ipairs(data.value) do %> + + <% for j,line in ipairs(chain) do %> + + + <% end %> + + <% end %> + +
+ <% if chain.policy then %> + + <% else %> + + <% end %> + <%= chain.name %> + <% if chain.policy then io.write(" ("..chain.policy..")\n") end %> + <% if chain.references then io.write(" ("..chain.references.." references)\n") end %> +
+ + + + <%= line %>
+ +
+
diff --git a/iptables-model.lua b/iptables-model.lua new file mode 100644 index 0000000..3f55965 --- /dev/null +++ b/iptables-model.lua @@ -0,0 +1,268 @@ +module(..., package.seeall) + +-- Load libraries +require("modelfunctions") +require("fs") +require("format") + +-- Set variables +local packagename = "iptables" +local servicename = "iptables" +local rulesfile = "/var/lib/iptables/rules-save" + +local path = "PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin " +local tables = {"filter", "nat", "mangle"} +local details + +-- ################################################################################ +-- LOCAL FUNCTIONS + +local getdetails = function() + if not details then + details = {} + local cmd = path .. "iptables -t filter -n -L" + for i,tab in ipairs(tables) do + local f = io.popen( (string.gsub(cmd, "filter", tab)) ) + details[tab] = {table=tab} + local file = f:read("*a") + f:close() + for line in string.gmatch(file, "([^\n]+)") do + if string.match(line, "^Chain") then + local name = string.match(line, "^%w+%s+(%S+)") + local policy = string.match(line, "policy (%w+)") + local references = string.match(line, "(%d+) references") + table.insert(details[tab], {name=name, policy=policy, references=references}) + elseif not string.match(line, "^target%s+prot") then + table.insert(details[tab][#details[tab]], line) + elseif not details[tab].header then + details[tab].header = line + end + end + end + end +end + +local find_chain = function(tab, chain) + getdetails() + if details[tab] then + for i,chn in ipairs(details[tab]) do + if chain == chn.name then + return chn + end + end + end + return nil +end + +local save = function() + local cmd = path .. "/etc/init.d/"..servicename.." save 2>&1" + local f = io.popen( cmd ) + f:close() + details = nil +end + +-- ################################################################################ +-- PUBLIC FUNCTIONS + +function getstatus() + local status = {} + + local value, errtxt = processinfo.package_version(packagename) + status.version = cfe({ + label="Program version", + value=value, + errtxt=errtxt, + }) + + return cfe({ type="group", value=status, label="IPtables Status" }) +end + +function getstatusdetails() + getdetails() + local retval = {} + for i,tab in ipairs(tables) do + local chains = 0 + local rules = 0 + for i,chain in ipairs(details[tab]) do + chains = chains + 1 + rules = rules + #chain + end + retval[tab] = {chains=chains, rules=rules} + end + + return cfe({ type="structure", value=retval, label="IPtables Status Details" }) +end + +function getrules(tab) + getdetails() + tab = tab or "filter" + + return cfe({ type="structure", value=details[tab] or {}, label=string.gsub(tab, "^.", string.upper).." Rules" }) +end + +function read_chain(tab, chain) + local retval = {} + retval.table = cfe({ type="select", value=tab or "filter", label="Table", option=tables }) + retval.chain = cfe({ value=chain or "", label="Chain" }) + getdetails() + if tab and not details[tab] then + retval.table.errtxt = "Invalid table" + end + if chain then + local chn = find_chain(retval.table.value, chain) + if not chn then + retval.chain.errtxt = "Cannot find chain" + elseif chn.policy then + -- only built-in chains can have policies, and the target can only be DROP or ACCEPT + retval.policy = cfe({ type="select", value=chn.policy, label="Policy", option={"DROP", "ACCEPT"} }) + end + end + return cfe({ type="group", value=retval, label="Chain" }) +end + +function update_chain(chain) + local success = true + getdetails() + if not details[chain.value.table.value] then + chain.value.table.errtxt = "Invalid table" + success = false + elseif not find_chain(chain.value.table.value, chain.value.chain.value) then + chain.value.chain.errtxt = "Invalid chain" + success = false + end + + if success then + if chain.value.policy then + local cmd = path .. "iptables -t "..chain.value.table.value.." -P "..chain.value.chain.value.." "..chain.value.policy.value.." 2>&1" + local f = io.popen(cmd) + local errtxt = f:read("*a") + f:close() + if errtxt ~= "" then + chain.errtxt = errtxt + end + save() + end + else + chain.errtxt = "Failed to update chain" + end + + return chain +end + +function create_chain(chain) + local success = true + getdetails() + if not details[chain.value.table.value] then + chain.value.table.errtxt = "Invalid table" + success = false + elseif find_chain(chain.value.table.value, chain.value.chain.value) then + chain.value.chain.errtxt = "Chain already exists" + success = false + end + if string.find(chain.value.chain.value, "[%s\'\"]") then + chain.value.chain.errtxt = "Chain cannot contain spaces or quotes" + success = false + end + + if success then + local cmd = path .. "iptables -t "..chain.value.table.value.." -N "..chain.value.chain.value.." 2>&1" + local f = io.popen(cmd) + local errtxt = f:read("*a") + if errtxt ~= "" then + chain.errtxt = errtxt + end + f:close() + save() + else + chain.errtxt = "Failed to create chain" + end + + return chain +end + +function delete_chain(tab, chain) + local retval = cfe({ label="Delete Chain result" }) + tab = tab or "filter" + local chn = find_chain(tab, chain) + if not chn then + retval.errtxt = "Could not find chain" + elseif chn.policy then + retval.errtxt = "Cannot delete built-in chain" + elseif chn.references and tonumber(chn.references) > 0 then + retval.errtxt = "Cannot delete chain with references" + else + local cmd = path .. "iptables -t "..tab.." -X "..chain.." 2>&1" + local f = io.popen(cmd) + local errtxt = f:read("*a") + if errtxt ~= "" then + retval.errtxt = errtxt + else + retval.value = "Deleted Chain" + end + save() + end + + return retval +end + +function read_rule(tab, chain, pos) + local retval = {} + retval.table = cfe({ type="select", value=tab or "filter", label="Table", option=tables }) + retval.chain = cfe({ value=chain or "", label="Chain" }) + retval.position = cfe({ value=pos or "", label="Position" }) + retval.protocol = cfe({ value="all", label="Protocol", descr="One of tcp, udp, icmp, or all, or a numeric value representing a protocol, or a protocol name from /etc/protocols. A '!' before the protocol inverts the test." }) + retval.source = cfe({ label="Source", descr="A network name or IP address (may have mask of type /xxx.xxx.xxx.xxx or /xx). A '!' before the address spcification inverts the sense of the address." }) + retval.destination = cfe({ label="Destination", descr="A network name or IP address (may have mask of type /xxx.xxx.xxx.xxx or /xx). A '!' before the address spcification inverts the sense of the address." }) + retval.jump = cfe({ label="Target", descr="Specify the target of the rule - one of ACCEPT, DROP, QUEUE, or RETURN, or the name of a user-defined chain." }) + retval.goto = cfe({ label="Goto", descr="Processing should continue in the specified chain" }) + retval.in_interface = cfe({ label="In Interface", descr="Name of an interface via which a packet was received. A '!' before the interface inverts the sense. A '+' ending the interface will match any interface that begins with this name." }) + retval.out_interface = cfe({ label="Out Interface", descr="Name of an interface via which a packet is going to be sent. A '!' before the interface inverts the sense. A '+' ending the interface will match any interface that begins with this name." }) + retval.fragment = cfe({ label="Fragment", descr="A '+' specifies the second and further packets of fragmented packets. A '!' specifies only head fragments or unfragmented packets." }) + retval.set_counters = cfe({ label="Set Counters", descr="'Number, number' to initialize the packet and byte counters."}) + + getdetails() + if tab and not details[tab] then + retval.table.errtxt = "Invalid table" + end + local chn + if chain then + chn = find_chain(retval.table.value, chain) + if not chn then + retval.chain.errtxt = "Cannot find chain" + end + end + if pos and chn then + if not chn[tonumber(pos)] then + retval.position.errtxt = "Cannot find rule" + else + -- We found the rule, update the settings + end + end + return cfe({ type="group", value=retval, label="Rule" }) +end + +function readrulesfile() + return modelfunctions.getfiledetails(rulesfile) +end + +function updaterulesfile(filedetails) + return modelfunctions.setfiledetails(filedetails, {rulesfile}) +end + +-- local implementation to add save, reload, and panic actions +function startstop_service(action) + local cmdresult = cfe({ label="Start/Stop result" }) + + if (string.lower(action) == "start") or (string.lower(action) == "stop") or (string.lower(action) == "restart") or (string.lower(action) == "save") or (string.lower(action) == "reload") or (string.lower(action) == "panic") then + local file = io.popen( "PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin /etc/init.d/" .. + servicename .. " " .. string.lower(action) .. " 2>&1" ) + if file ~= nil then + cmdresult.value = file:read( "*a" ) + file:close() + end + posix.sleep(2) -- Wait for the process to start|stop + else + cmdresult.errtxt = "Unknown command!" + end + return cmdresult +end diff --git a/iptables-startstop-html.lsp b/iptables-startstop-html.lsp new file mode 100644 index 0000000..caa0b96 --- /dev/null +++ b/iptables-startstop-html.lsp @@ -0,0 +1,25 @@ +<% local data, viewlibrary, page_info = ... %> + +

Management

+
+
" method="POST"> +
Load rules from rules file
+
+ +
+
Save current rules to rules file
+
+ +
+
+ +<% if data.value.result then %> +
Previous action result
+
+<% if data.value.result.value ~= "" then %> +

<%= string.gsub(data.value.result.value, "\n", "
") %>

+<% end if data.value.result.errtxt then %> +

<%= string.gsub(data.value.result.errtxt, "\n", "
") %>

+<% end end %> +
+
diff --git a/iptables-status-html.lsp b/iptables-status-html.lsp new file mode 120000 index 0000000..b2f8480 --- /dev/null +++ b/iptables-status-html.lsp @@ -0,0 +1 @@ +../status-html.lsp \ No newline at end of file diff --git a/iptables.menu b/iptables.menu new file mode 100644 index 0000000..b047869 --- /dev/null +++ b/iptables.menu @@ -0,0 +1,6 @@ +#CAT GROUP/DESC TAB ACTION +Networking 45IPtables Status details +Networking 45IPtables Filter_Rules filterrules +Networking 45IPtables Nat_Rules natrules +Networking 45IPtables Mangle_Rules manglerules +Networking 45IPtables Expert expert diff --git a/iptables.roles b/iptables.roles new file mode 100644 index 0000000..f32fc56 --- /dev/null +++ b/iptables.roles @@ -0,0 +1,4 @@ +USER=iptables:status,iptables:details,iptables:startstop +EDITOR=iptables:filterrules,iptables:natrules,iptables:manglerules,iptables:editchain,iptables:createchain,iptables:deletechain,iptables:editrule,iptables:createrule,iptables:deleterule +EXPERT=iptables:expert +ADMIN=iptables:status,iptables:details,iptables:startstop,iptables:filterrules,iptables:natrules,iptables:manglerules,iptables:editchain,iptables:createchain,iptables:deletechain,iptables:editrule,iptables:createrule,iptables:deleterule,iptables:expert -- cgit v1.2.3