summaryrefslogtreecommitdiff
path: root/huwfwall.sh
diff options
context:
space:
mode:
Diffstat (limited to 'huwfwall.sh')
-rw-r--r--huwfwall.sh214
1 files changed, 214 insertions, 0 deletions
diff --git a/huwfwall.sh b/huwfwall.sh
new file mode 100644
index 0000000..441360d
--- /dev/null
+++ b/huwfwall.sh
@@ -0,0 +1,214 @@
+# Copyright 2014 Jan Huwald <jh@sotun.de>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# abort on error semantic is required to produce a terse firewall
+# description
+set -e
+set -o pipefail
+
+# params: command [rulesets...]
+# ruleset are implemented as functions the call the various
+# callbacks defined below
+function huwfwall() {
+ local cmd="$1"
+ shift
+ # install callback function to wrap *table commands
+ case "$cmd" in
+ install) ;&
+ check)
+ function target_callback() {
+ if ! $*; then
+ echo -e "while executing\t $*";
+ # print backtrace
+ local i=2
+ while caller $i; do i=$((i+1)); done \
+ | awk '{ print "in " $3 ":" $2 ":" $1 }'
+ return 1;
+ fi
+ }
+ ;;
+ print)
+ function target_callback() { echo $*; }
+ esac
+ # execute given subcommand
+ case "$cmd" in
+ install)
+ v=$(current_version)
+ while [ $# -gt 0 ]; do
+ v=$((v + 1))
+ setup_root_chains
+ set +e
+ (set -e; $1)
+ local ret=$?
+ set -e
+ if [ $ret -eq 0 ] ; then break; fi
+ echo "in ruleset $1\nTrying next ruleset"
+ gc_rules
+ shift
+ done
+ if [ $# -gt 0 ]; then
+ commit_rules
+ gc_rules
+ return 0
+ else
+ echo "All rulesets exhausted. Firewall setup gailed."
+ gc_rules
+ return 1
+ fi
+ ;;
+ check)
+ while [ $# -gt 0 ]; do
+ v=test$RANDOM
+ setup_root_chains
+ set +e
+ (set -e; $1)
+ local ret=$?
+ set -e
+ if [ $ret -eq 0 ]; then
+ echo "Ruleset '$1' is ok"
+ else
+ echo "Ruleset '$1' failed"
+ fi
+ gc_rules
+ shift
+ done
+ ;;
+ print)
+ function target_callback() { echo $*; }
+ v="\${v}"
+ while [ $# -gt 0 ]; do
+ echo "# ruleset $1 will install"
+ echo "# builtin:"
+ setup_root_chains
+ echo "# user-defined:"
+ $1
+ [ $# -eq 1 ] || echo
+ shift
+ done
+ ;;
+ gc)
+ gc_rules
+ ;;
+ current-version)
+ current_version
+ ;;
+ *)
+ echo -e \
+"Usage: $0 <install|check|print|gc>
+\tinstall\tSetup the firewall
+\tcheck\tCheck rules by installing, but not using them
+\tprint\tShow the iptable commands to be executed
+\tgc\tGarbage collect unused rules"
+ esac
+}
+
+function rule() {
+ local targets="$1"
+ local chain="$2"
+ shift 2
+ iterate_targets "$targets" -A ${v}_"$chain" $*
+}
+
+function chain() {
+ local targets="$1"
+ local chain="$2"
+ iterate_targets "$targets" -N ${v}_"$chain"
+}
+
+function iterate_targets() {
+ local targets="$1"
+ shift
+ local target
+ for i in $(echo "$targets" | sed 's/\(.\)/\1 /g'); do
+ case $i in
+ 4) target=iptables;;
+ 6) target=ip6tables;;
+ e) target=ebtables;;
+ *) echo "invalid target '$target'"; return 1
+ esac
+ target_callback $target $*
+ done
+}
+
+function setup_root_chains() {
+ iterate_root_chains setup_root_chain_callback;
+}
+function setup_root_chain_callback() {
+ local target="$1"; local table="$2"; local chain="$3"
+ target_callback $target -t $table -N ${v}_${chain}
+}
+
+function commit_rules() {
+ # first insert all heads to minimize incoherent rule time; then
+ # remove all previous rules in the chain
+ iterate_root_chains commit_rules_insert_head
+ iterate_root_chains commit_rules_remove_tail
+}
+function commit_rules_insert_head() {
+ local target="$1"; local table="$2"; local chain="$3"
+ local chain_upper=$(echo "$chain" | tr a-z A-Z)
+ target_callback $target -t $table -I $chain_upper -j ${v}_$chain
+}
+function commit_rules_remove_tail() {
+ # call targets directly, not via target_callback: we need the
+ # return status to determine loop termination
+ local target="$1"; local table="$2"; local chain="$3"
+ local chain_upper=$(echo "$chain" | tr a-z A-Z)
+ while $target -t $table -D $chain_upper 2 2>/dev/null; do :; done
+}
+
+function current_version() {
+ (echo 1; iterate_root_chains current_version_callback) | sort -n | tail -n1
+}
+function current_version_callback() {
+ local target="$1"; local table="$2"; local chain="$3"
+ local chain_upper=$(echo "$chain" | tr a-z A-Z)
+ local opt
+ if grep -q ip <<<$target; then opt="-n"; fi
+ "$target" -t "$table" $opt -L "$chain_upper" \
+ | (egrep "^[0-9]+_$chain" || true) \
+ | cut -f1 -d_ | head -n1
+}
+
+function iterate_root_chains() {
+ local callback="$1"; local i
+ for i in {iptables,ip6tables}/filter/{input,forward,output} \
+ iptables/nat/{prerouting,input,output,postrouting} \
+ ebtables/filter/{input,forward,output}; do
+ local target table chain
+ read target table chain <<<$(echo $i | tr / ' ')
+ $callback $target $table $chain
+ done
+}
+
+function gc_rules() {
+ local i
+ for i in {iptables,ip6tables}/filter iptables/nat; do
+ local target table
+ read target table <<<$(echo $i | tr / ' ')
+ local succ=true
+ while $succ; do
+ succ=false
+ for i in $($target -t $table -L \
+ | grep ^Chain | grep '(0 references)' \
+ | cut -f2 -d' '); do
+ succ=true
+ $target -t $table -F $i
+ $target -t $table -X $i
+ done
+ done
+ done
+ ebtables -X
+}
contact: Jan Huwald // Impressum