--[[ Iptables file dumper for Alpine Wall Copyright (C) 2012-2020 Kaarle Ritvanen See LICENSE file for license details ]]-- local class = require('awall.class') local ACTIVE = require('awall.family').ACTIVE local raise = require('awall.uerror').raise local util = require('awall.util') local printmsg = util.printmsg local sortedkeys = util.sortedkeys local lpc = require('lpc') local posix = require('posix') local M = {} local families = { inet={ cmd='iptables', file='rules-save', procfile='/proc/net/ip_tables_names' }, inet6={ cmd='ip6tables', file='rules6-save', procfile='/proc/net/ip6_tables_names' } } M.builtin = { filter={'FORWARD', 'INPUT', 'OUTPUT'}, mangle={'FORWARD', 'INPUT', 'OUTPUT', 'POSTROUTING', 'PREROUTING'}, nat={'INPUT', 'OUTPUT', 'POSTROUTING', 'PREROUTING'}, raw={'OUTPUT', 'PREROUTING'}, security={'FORWARD', 'INPUT', 'OUTPUT'} } local backupdir = '/var/run/awall' local _actfamilies local function actfamilies() if _actfamilies then return _actfamilies end _actfamilies = {} for _, family in ipairs(ACTIVE) do if posix.stat(families[family].procfile) then table.insert(_actfamilies, family) else printmsg('Warning: firewall not enabled for '..family) end end return _actfamilies end function M.isenabled() return #actfamilies() > 0 end local BaseIPTables = class() function BaseIPTables:print() for _, family in sortedkeys(families) do self:dumpfile(family, io.output()) io.write('\n') end end function BaseIPTables:dump(dir) for family, tbls in pairs(families) do local file = io.open(dir..'/'..families[family].file, 'w') self:dumpfile(family, file) file:close() end end function BaseIPTables:restorecmd(family, test) local cmd = {families[family].cmd..'-restore'} if test then table.insert(cmd, '-t') end return table.unpack(cmd) end function BaseIPTables:restore(test) for _, family in ipairs(actfamilies()) do local pid, stdin, stdout = lpc.run(self:restorecmd(family, test)) stdout:close() self:dumpfile(family, stdin) stdin:close() assert(lpc.wait(pid) == 0) end end function BaseIPTables:activate() self:flush() self:restore(false) end function BaseIPTables:test() self:restore(true) end function BaseIPTables:flush() M.flush() end M.IPTables = class(BaseIPTables) function M.IPTables:init() self.config = {} setmetatable( self.config, { __index=function(t, k) t[k] = {} setmetatable(t[k], getmetatable(t)) return t[k] end } ) end function M.IPTables:dumpfile(family, iptfile) iptfile:write('# '..families[family].file..' generated by awall\n') local tables = self.config[family] for _, tbl in sortedkeys(tables) do iptfile:write('*'..tbl..'\n') local chains = tables[tbl] for _, chain in sortedkeys(chains) do local policy = '-' if util.contains(M.builtin[tbl], chain) then policy = tbl == 'filter' and 'DROP' or 'ACCEPT' end iptfile:write(':'..chain..' '..policy..' [0:0]\n') end for _, chain in sortedkeys(chains) do for _, rule in ipairs(chains[chain]) do iptfile:write('-A '..chain..' '..rule..'\n') end end iptfile:write('COMMIT\n') end end local Current = class(BaseIPTables) function Current:dumpfile(family, iptfile) local pid, stdin, stdout = lpc.run(families[family].cmd..'-save') stdin:close() for line in stdout:lines() do iptfile:write(line..'\n') end stdout:close() assert(lpc.wait(pid) == 0) end local Backup = class(BaseIPTables) function Backup:dumpfile(family, iptfile) for line in io.lines(backupdir..'/'..families[family].file) do iptfile:write(line..'\n') end end function M.backup() posix.mkdir(backupdir) Current():dump(backupdir) end function M.revert() Backup():activate() end function M.flush() local empty = M.IPTables() for _, family in pairs(actfamilies()) do for tbl in io.lines(families[family].procfile) do if M.builtin[tbl] then for _, chain in ipairs(M.builtin[tbl]) do empty.config[family][tbl][chain] = {} end else printmsg('Warning: not flushing unknown table: '..tbl) end end end empty:restore(false) end return M