From 406c17701c6151900a67ae220601a19e259a8087 Mon Sep 17 00:00:00 2001
From: ale <ale@incal.net>
Date: Mon, 7 May 2018 09:03:56 +0100
Subject: [PATCH] Refactor the firewall script

Make it more generic, dropping ai-specific bits here and
there. Provide a standard command-line interface including --help and
--version options. Move default filter setup to a standalone code
snippet.

Rename package to just 'firewall'. Bump version to 0.2 to highlight
the difference with the original ai-firewall codebase.
---
 .gitignore                    |   5 +
 .gitlab-ci.yml                |  37 ++++++
 COPYING                       |   2 +-
 Makefile                      |  28 ++--
 README                        |  62 ---------
 README.md                     |  58 +++++++++
 conf-dist/filter.d/00base     |  66 ++++++++++
 conf-dist/filter.d/01docker   |   8 ++
 conf-dist/filter.d/01fail2ban |   7 +-
 conf-dist/nat.d/01docker      |   8 ++
 debian/ai-firewall.default    |   4 -
 debian/ai-firewall.init       |  35 -----
 debian/ai-firewall.postinst   |   5 -
 debian/changelog              |  18 +--
 debian/compat                 |   2 +-
 debian/control                |   6 +-
 debian/firewall.service       |  10 ++
 debian/rules                  |   6 +-
 examples/filter.d/00ring0     |  16 ---
 examples/filter.d/dns         |   2 -
 examples/filter.d/ftp         |   5 -
 examples/filter.d/jabber      |   7 -
 examples/filter.d/mail        |   2 -
 examples/filter.d/monitoring  |   9 --
 examples/filter.d/tinc        |   3 -
 examples/filter.d/www         |  14 --
 firewall                      | 238 ++++++++++++++--------------------
 27 files changed, 319 insertions(+), 344 deletions(-)
 create mode 100644 .gitignore
 create mode 100644 .gitlab-ci.yml
 delete mode 100644 README
 create mode 100644 README.md
 create mode 100644 conf-dist/filter.d/00base
 create mode 100644 conf-dist/filter.d/01docker
 create mode 100644 conf-dist/nat.d/01docker
 delete mode 100644 debian/ai-firewall.default
 delete mode 100644 debian/ai-firewall.init
 delete mode 100755 debian/ai-firewall.postinst
 create mode 100644 debian/firewall.service
 delete mode 100644 examples/filter.d/00ring0
 delete mode 100644 examples/filter.d/dns
 delete mode 100644 examples/filter.d/ftp
 delete mode 100644 examples/filter.d/jabber
 delete mode 100644 examples/filter.d/mail
 delete mode 100644 examples/filter.d/monitoring
 delete mode 100644 examples/filter.d/tinc
 delete mode 100644 examples/filter.d/www

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..6bf7bd7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+*-stamp
+*.debhelper
+*.substvars
+debian/firewall
+debian/files
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..f1f481b
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,37 @@
+
+stages:
+  - build_src
+  - build_pkg
+  - upload
+
+build:src:
+  stage: build_src
+  image: "ai/build:stretch"
+  script: "build-dsc"
+  artifacts:
+    paths:
+      - build-deb/
+  only:
+    - master
+
+build:pkg:
+  stage: build_pkg
+  image: "ai/build:stretch"
+  script: "build-deb"
+  dependencies:
+    - build:src
+  artifacts:
+    paths:
+      - output-deb/
+  only:
+    - master
+
+upload:pkg:
+  stage: upload
+  image: "ai/pkg:base"
+  script: "upload-packages -r ai3"
+  dependencies:
+    - build:pkg
+  only:
+    - master
+
diff --git a/COPYING b/COPYING
index fa96ea8..37d284e 100644
--- a/COPYING
+++ b/COPYING
@@ -1,4 +1,4 @@
-Copyright (C) 2012, Autistici/Inventati <info@inventati.org>.
+Copyright (C) 2012-2018, Autistici/Inventati <info@inventati.org>.
 
 Permission is hereby granted, free of charge, to any person obtaining
 a copy of this software and associated documentation files (the
diff --git a/Makefile b/Makefile
index c4099dc..5db5c90 100644
--- a/Makefile
+++ b/Makefile
@@ -1,20 +1,30 @@
 
 prefix = /usr
+sbindir = $(prefix)/sbin
 sysconfdir = /etc
+fwconfdir = $(sysconfdir)/firewall
+sharedir = $(prefix)/share/firewall
 INSTALL = install
+TABLES = filter nat mangle
 
 all:
 
 clean:
 
 install:
-	$(INSTALL) -d $(DESTDIR)$(prefix)/bin
-	$(INSTALL) -m 755 firewall $(DESTDIR)$(prefix)/bin/firewall
-	$(INSTALL) -d $(DESTDIR)$(sysconfdir)/firewall
-	$(INSTALL) -d $(DESTDIR)$(sysconfdir)/firewall/filter.d
-	$(INSTALL) -d $(DESTDIR)$(sysconfdir)/firewall/nat.d
-	$(INSTALL) -d $(DESTDIR)$(sysconfdir)/firewall/mangle.d
-	$(INSTALL) -m 644 README $(DESTDIR)$(sysconfdir)/firewall/README
-	(for f in ./conf-dist/filter.d/* ; do \
-	 $(INSTALL) -m 644 $$f $(DESTDIR)$(sysconfdir)/firewall/filter.d ; done)
+	$(INSTALL) -d $(DESTDIR)$(sbindir)
+	$(INSTALL) -d $(DESTDIR)$(sharedir)
+	$(INSTALL) -d $(DESTDIR)$(fwconfdir)
+	$(INSTALL) -m 755 firewall $(DESTDIR)$(sbindir)/firewall
+	(for t in $(TABLES); do \
+	  $(INSTALL) -d $(DESTDIR)$(fwconfdir)/$$t.d ; \
+	  $(INSTALL) -d $(DESTDIR)$(sharedir)/$$t.d ; \
+	  if [ -d conf-dist/$$t.d ]; then \
+	   for f in conf-dist/$$t.d/* ; do \
+	    $(INSTALL) -m 644 $$f $(DESTDIR)$(sharedir)/$$t.d ; \
+	    b=$$(basename $$f) ; \
+	    ln -s $(sharedir)/$$t.d/$$b $(DESTDIR)$(fwconfdir)/$$t.d/$$b ; \
+	   done ; \
+	  fi ; \
+	done)
 
diff --git a/README b/README
deleted file mode 100644
index fa0280c..0000000
--- a/README
+++ /dev/null
@@ -1,62 +0,0 @@
-
-ai-firewall
-===========
-
-A shell-based DSL for quick and easy configuration of an iptables
-firewall, primarily targeted at individual servers, supporting both
-IPv4 and IPv6.
-
-ai-firewall will perform some basic setup and then execute
-application-specific configuration snippets from the /etc/firewall
-tree. This setup allows packages to plug into the firewall setup by
-simply deploying a snippet in /etc/firewall.
-
-The configuration is loaded from the directories below /etc/firewall,
-every iptables table (such as 'filter', 'nat', and 'mangle') is
-configured independently from its own subdirectory named after itself,
-with a '.d' extension. Individual files from each directory are loaded
-in lexicographical order (like run-parts, for instance).
-
-
-Configuration syntax
---------------------
-
-Configuration files are simple shell scripts. Rules are generated by
-invoking the following predefined helper functions:
-
-
-    create_chain <CHAIN_NAME>
-
-        Create a new chain with the specified name.
-
-
-    add_rule <IPTABLES_ARGS>
-    add_rule4 <IPTABLES_ARGS>
-    add_rule6 <IPTABLES_ARGS>
-
-        This function will generate a full iptables rule exactly as
-        specified.  The first form will generate the rule for IPv4 and
-        IPv6, the other two are protocol-specific.
-
-        An example:
-
-          add_rule -A bad-host -s 1.2.3.4 -j DROP
-
-
-    add_to_chain <CHAIN_NAME> <IPTABLES_ARGS>
-
-        A shortcut for 'add_rule -A <CHAIN_NAME> <IPTABLES_ARGS>'.
-
-
-    add_user_port <PROTOCOL> <PORT> [<TARGET>]
-
-        Allow incoming traffic to the specified protocol / port.
-
-
-    add_user_ports <PROTOCOL> <PORT_SPEC>
-
-        Allow incoming traffic to the specified ports. PORT_SPEC
-        should be a comma-separated list of destination ports.
-
-
-
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..bab619a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,58 @@
+firewall
+=====
+
+A shell-based DSL for quick and easy configuration of an iptables
+firewall, primarily targeted at individual servers, supporting both
+IPv4 and IPv6. Instead of parameterizing the hell out of iptables like
+more sophisticated solutions (think Shorewall), it provides helpers to
+write iptables configs from shell snippets. These helpers make it easy
+to maintain IPv4 and IPv6 rules in sync.
+
+The main driver script will perform some basic setup and then execute
+application-specific configuration snippets from the */etc/firewall*
+tree. This setup allows packages to plug into the firewall setup by
+simply deploying a snippet in /etc/firewall.
+
+The configuration is loaded from the directories below /etc/firewall,
+every iptables table (such as *filter*, *nat*, and *mangle*) is
+configured independently from its own subdirectory named after the
+table, with a `.d` extension. Individual files from each directory are
+loaded in lexicographical order using *run-parts(8)*.
+
+# Configuration
+
+Configuration files are simple shell scripts. Rules are generated by
+invoking the following predefined helper functions:
+
+#### `create_chain` *CHAIN_NAME*
+
+Create a new chain with the specified name.
+
+#### `add_rule` *IPTABLES_ARGS*
+#### `add_rule4` *IPTABLES_ARGS*
+#### `add_rule6` *IPTABLES_ARGS*
+
+This function will generate a full iptables rule exactly as
+specified.  The first form will generate the rule for IPv4 and
+IPv6, the other two are protocol-specific.
+
+An example:
+
+```
+add_rule -A bad-host -s 1.2.3.4 -j DROP
+```
+
+#### `add_to_chain` *CHAIN_NAME* *IPTABLES_ARGS*
+
+A shortcut for `add_rule -A CHAIN_NAME IPTABLES_ARGS`.
+
+#### `allow_port` *PROTOCOL* *PORT* *[IPTABLES_ARGS]*
+
+Allow incoming traffic to the specified protocol / port.
+*IPTABLES_ARGS* is just a placeholder for any number of arbitrary
+iptables options (the default is simply `-j ALLOW`).
+
+#### `allow_ports` *PROTOCOL* *PORT_SPEC* *[IPTABLES_ARGS]*
+
+Allow incoming traffic to the specified ports. *PORT_SPEC*
+should be a comma-separated list of destination ports.
diff --git a/conf-dist/filter.d/00base b/conf-dist/filter.d/00base
new file mode 100644
index 0000000..7e182ab
--- /dev/null
+++ b/conf-dist/filter.d/00base
@@ -0,0 +1,66 @@
+# Set up basic rules for the 'filter' table.
+#
+# This snippet should run before the others.
+
+# Set up a chain that will drop noisy unwanted traffic
+# without even logging it. 
+create_chain drop-noise
+add_rule -A drop-noise -p tcp --dport 113 -j REJECT
+add_rule -A drop-noise -p tcp -m multiport --dports 139,445 -j DROP
+add_rule -A drop-noise -p udp -m multiport --dports 137,138,500 -j DROP
+# Be kind and allow old-style traceroutes.
+add_rule -A drop-noise -p udp --dport 33434:33500 -j REJECT
+
+# base-input chain.
+create_chain base-input
+
+# Enable everything from lo and ring0.
+add_rule -A base-input -i lo -j ACCEPT
+add_rule4 -A base-input -i ring0 -s 172.16.1.0/24 -j ACCEPT
+
+# Some IPv6-specific ICMP setup.
+add_rule6 -A base-input -m rt --rt-type 0 --rt-segsleft 0 -j DROP 
+for icmp6type in 133 134 135 136 ; do 
+    add_rule6 -A base-input -p ipv6-icmp -m icmp6 \
+              --icmpv6-type ${icmp6type} -m hl --hl-eq 255 -j ACCEPT 
+done
+
+# Standard conntrack stuff.
+add_rule -A base-input -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+add_rule6 -A base-input -s fe80::/10 -p ipv6-icmp -m icmp6 \
+          --icmpv6-type 129 -j ACCEPT
+add_rule -A base-input -m conntrack --ctstate INVALID -j DROP
+
+# Enable 6to4 protocol.
+#add_rule4 -A base-input -p ipv6 -j ACCEPT
+
+# Allow useful ICMPs (but rate-limit incoming echo requests).
+for icmptype in 3 4 11 12 ; do
+    add_rule4 -A base-input -p icmp -m icmp \
+              --icmp-type ${icmptype} -j ACCEPT
+done
+for icmp6type in 1 2 3 4 128 ; do
+    add_rule6 -A base-input -p ipv6-icmp -m icmp6 \
+              --icmpv6-type ${icmp6type} -j ACCEPT
+done
+add_rule4 -A base-input -p icmp -m icmp --icmp-type 8 \
+          -m limit --limit 3/s -j ACCEPT
+add_rule6 -A base-input -p ipv6-icmp -m icmp6 --icmp-type 128 \
+          -m limit --limit 3/s -j ACCEPT
+
+# IPv6 autodiscovery.
+#add_rule6 -A base-input -s fe80::/10 -d fe80::/10 -p udp -m udp \
+#    --sport 547 --dport 546 -j ACCEPT
+
+# user-input
+create_chain user-input
+
+# Always allow SSH access, just in case someone forgets to add it
+# with a user-defined ruleset file.
+allow_port tcp 22
+
+# Setup the INPUT chain.
+# It is split into stages: base-input, user-input
+add_rule -A INPUT -j base-input
+add_rule -A INPUT -j drop-noise
+add_rule -A INPUT -j user-input
diff --git a/conf-dist/filter.d/01docker b/conf-dist/filter.d/01docker
new file mode 100644
index 0000000..e66ef1f
--- /dev/null
+++ b/conf-dist/filter.d/01docker
@@ -0,0 +1,8 @@
+# Preserve docker-related firewall rules (IPv4-only).
+iptables-save -t filter \
+    | grep \
+          -e '^:DOCKER' \
+          -e '^-A DOCKER' \
+          -e '-[io] docker[0-9]' \
+          -e '-j DOCKER' \
+    | (while read line; do add_rule4 "${line}"; done)
diff --git a/conf-dist/filter.d/01fail2ban b/conf-dist/filter.d/01fail2ban
index 329ccad..57437a7 100644
--- a/conf-dist/filter.d/01fail2ban
+++ b/conf-dist/filter.d/01fail2ban
@@ -1,12 +1,9 @@
-
 # The following snippet saves the existing fail2ban rules and
-# reproduces them identically in the output.
-if [ -x /sbin/iptables-save ]; then
-    /sbin/iptables-save | (while read line ; do
+# reproduces them identically in the output (IPv4-only).
+iptables-save -t filter | (while read line ; do
         case "${line}" in
             ":fail2ban-"*|"-A fail2ban-"*|*"-j fail2ban-"*)
                 add_rule4 "${line}"
                 ;;
         esac
         done)
-fi
diff --git a/conf-dist/nat.d/01docker b/conf-dist/nat.d/01docker
new file mode 100644
index 0000000..bb71ede
--- /dev/null
+++ b/conf-dist/nat.d/01docker
@@ -0,0 +1,8 @@
+# Preserve docker-related firewall rules (IPv4-only).
+iptables-save -t nat \
+    | grep \
+          -e '^:DOCKER' \
+          -e '^-A DOCKER' \
+          -e '-[io] docker[0-9]' \
+          -e '-j DOCKER' \
+    | (while read line; do add_rule4 "${line}"; done)
diff --git a/debian/ai-firewall.default b/debian/ai-firewall.default
deleted file mode 100644
index 3cc5534..0000000
--- a/debian/ai-firewall.default
+++ /dev/null
@@ -1,4 +0,0 @@
-
-# Remove comment to enable.
-#ENABLED=true
-
diff --git a/debian/ai-firewall.init b/debian/ai-firewall.init
deleted file mode 100644
index 02d8f6b..0000000
--- a/debian/ai-firewall.init
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/bin/bash
-#
-# Start/stop the A/I firewall.
-#
-### BEGIN INIT INFO
-# Provides:          ai-firewall
-# Required-Start:    $network $local_fs
-# Required-Stop:
-# Should-Start:
-# Should-Stop:
-# Default-Start:     2 3 4 5
-# Default-Stop:      0 1 6
-# Short-Description: A/I Firewall
-# Description:       A/I Firewall
-### END INIT INFO
-
-ENABLED=false
-
-test -e /etc/default/ai-firewall && . /etc/default/ai-firewall
-
-if [ "${ENABLED}" != true ]; then
-    exit 0
-fi
-
-case "$1" in
-start|restart)
-        echo -n "Starting firewall... "
-        /usr/bin/firewall start
-        echo "ok"
-        ;;
-stop)
-        ;;
-esac
-
-exit 0
diff --git a/debian/ai-firewall.postinst b/debian/ai-firewall.postinst
deleted file mode 100755
index 42faf6a..0000000
--- a/debian/ai-firewall.postinst
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/bin/sh
-
-#DEBHELPER#
-
-exit 0
diff --git a/debian/changelog b/debian/changelog
index b7c4e72..349465c 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,17 +1,5 @@
-ai-firewall (0.1-2) unstable; urgency=low
+firewall (0.2) unstable; urgency=medium
 
-  * Use debhelper for postinst script.
+  * New package.
 
- -- Autistici/Inventati <debian@autistici.org>  Sun, 22 Jun 2014 19:12:21 +0000
-
-ai-firewall (0.1-1) unstable; urgency=low
-
-  * Update debian package to start service at boot
-
- -- Autistici/Inventati <debian@autistici.org>  Sun, 22 Jun 2014 18:30:41 +0000
-
-ai-firewall (0.1) unstable; urgency=low
-
-  * First packaged release.
-
- -- Autistici/Inventati <debian@autistici.org>  Sat, 13 Sep 2012 14:49:37 +0000
+ -- Autistici/Inventati <debian@autistici.org>  Mon, 07 May 2018 08:37:01 +0100
diff --git a/debian/compat b/debian/compat
index 7f8f011..f599e28 100644
--- a/debian/compat
+++ b/debian/compat
@@ -1 +1 @@
-7
+10
diff --git a/debian/control b/debian/control
index a1d8135..21bd66c 100644
--- a/debian/control
+++ b/debian/control
@@ -1,13 +1,13 @@
-Source: ai-firewall
+Source: firewall
 Section: net
 Priority: extra
 Maintainer: Autistici/Inventati <debian@autistici.org>
 Build-Depends: debhelper (>= 7), cdbs
 Standards-Version: 3.8.0.1
 
-Package: ai-firewall
+Package: firewall
 Architecture: all
-Depends: ${misc:Depends}, python, iptables
+Depends: ${misc:Depends}, iptables
 Description: A/I Firewall Script
  Automatically maintain local firewalls for A/I servers.
 
diff --git a/debian/firewall.service b/debian/firewall.service
new file mode 100644
index 0000000..c16f2e1
--- /dev/null
+++ b/debian/firewall.service
@@ -0,0 +1,10 @@
+[Unit]
+Description=Set up firewall
+
+[Service]
+Type=oneshot
+EnvironmentFile=-/etc/default/firewall
+ExecStart=/usr/sbin/firewall
+
+[Install]
+WantedBy=multi-user.target
diff --git a/debian/rules b/debian/rules
index ab9566f..9cdd140 100755
--- a/debian/rules
+++ b/debian/rules
@@ -1,8 +1,8 @@
 #!/usr/bin/make -f
 # -*- makefile -*-
 
-DEB_MAKE_INSTALL_TARGET = install DESTDIR=$(cdbs_make_curdestdir)
+export DH_OPTIONS
 
-include /usr/share/cdbs/1/rules/debhelper.mk
-include /usr/share/cdbs/1/class/makefile.mk
+%:
+	dh $@
 
diff --git a/examples/filter.d/00ring0 b/examples/filter.d/00ring0
deleted file mode 100644
index 0cad906..0000000
--- a/examples/filter.d/00ring0
+++ /dev/null
@@ -1,16 +0,0 @@
-
-# Create a RING0 chain that will accept requests from IP addresses
-# in the ring0, and drop everything else.
-create_chain RING0
-if [ -e /etc/ai/hosts ]; then
-    ai_hosts=${AI_HOSTS:-/etc/ai/hosts}
-    RING0_IP4=$(resolve_addrs_from_file ${ai_hosts} ipv4)
-    RING0_IP6=$(resolve_addrs_from_file ${ai_hosts} ipv6)
-    for ip in ${RING0_IP4} ; do
-        add_rule4 -A RING0 -s ${ip} -j ACCEPT
-    done
-    for ip in ${RING0_IP6} ; do
-        add_rule6 -A RING0 -s ${ip} -j ACCEPT
-    done
-fi
-add_rule -A RING0 -j DROP
diff --git a/examples/filter.d/dns b/examples/filter.d/dns
deleted file mode 100644
index b2469ef..0000000
--- a/examples/filter.d/dns
+++ /dev/null
@@ -1,2 +0,0 @@
-add_user_port udp 53
-add_user_port tcp 53
diff --git a/examples/filter.d/ftp b/examples/filter.d/ftp
deleted file mode 100644
index 7b85f71..0000000
--- a/examples/filter.d/ftp
+++ /dev/null
@@ -1,5 +0,0 @@
-
-add_user_port tcp 21
-
-# This is the port range for PASV transfers.
-add_user_port tcp 15000:19000
diff --git a/examples/filter.d/jabber b/examples/filter.d/jabber
deleted file mode 100644
index d086380..0000000
--- a/examples/filter.d/jabber
+++ /dev/null
@@ -1,7 +0,0 @@
-
-add_user_ports tcp 5222,5223,5269,5280
-
-# STUN.
-add_user_ports tcp 3478,5349
-add_user_port udp 3478
-
diff --git a/examples/filter.d/mail b/examples/filter.d/mail
deleted file mode 100644
index b7ee9dd..0000000
--- a/examples/filter.d/mail
+++ /dev/null
@@ -1,2 +0,0 @@
-add_user_ports tcp 25,110,143,465,587,993,995
-
diff --git a/examples/filter.d/monitoring b/examples/filter.d/monitoring
deleted file mode 100644
index d91ec3f..0000000
--- a/examples/filter.d/monitoring
+++ /dev/null
@@ -1,9 +0,0 @@
-
-# Create a chain with probe IP ranges.
-create_chain MONITORING
-add_rule -A MONITORING -s 131.114.114.0/24 -j ACCEPT
-add_rule -A MONITORING -s 46.4.206.80/28 -j ACCEPT
-add_rule -A MONITORING -j DROP
-
-add_user_port tcp 3900 -j MONITORING
-
diff --git a/examples/filter.d/tinc b/examples/filter.d/tinc
deleted file mode 100644
index fc1ee68..0000000
--- a/examples/filter.d/tinc
+++ /dev/null
@@ -1,3 +0,0 @@
-for proto in udp tcp ; do
-  add_user_ports ${proto} 655,656,657 -j RING0
-done
diff --git a/examples/filter.d/www b/examples/filter.d/www
deleted file mode 100644
index da3394d..0000000
--- a/examples/filter.d/www
+++ /dev/null
@@ -1,14 +0,0 @@
-
-# Standard HTTP/HTTPS ports.
-add_user_ports tcp 80,443
-
-# Block outgoing connections from the users' FastCGI runners
-# (i.e. the PHP scripts) to only HTTP.
-USERS_GROUP=2000
-create_chain user-output-cgi
-add_rule -A user-output-cgi -p tcp --syn --dport 80 -j ACCEPT
-add_rule -A user-output-cgi -p tcp --syn --dport 443 -j ACCEPT
-add_rule -A user-output-cgi -m log --log-prefix \"users-cgi: \" \
-         -m limit --limit 3/s -j LOG
-add_rule -A user-output-cgi -j REJECT
-add_rule -A OUTPUT -m owner --gid-owner ${USERS_GROUP} -j user-output-cgi
diff --git a/firewall b/firewall
index 3ab2f80..55fe385 100755
--- a/firewall
+++ b/firewall
@@ -1,14 +1,15 @@
 #!/bin/bash
 
-# The following configuration variables can be overridden from the
-# environment.  Useful in combination with /etc/default or some
-# equivalent mechanism.
-FW_DIR="${FW_DIR:-/etc/firewall}"
-DO_LOG="${DO_LOG:-1}"
-LOG_RATE="${LOG_RATE:-5/min}"
+# Directory containing the configuration snippets.
+CONFIG_DIR="${CONFIG_DIR:-/etc/firewall}"
 
+# List of tables to manage.
 TABLES="filter nat mangle"
 
+# Use a safe PATH.
+PATH=/bin:/sbin:/usr/bin:/usr/sbin
+export PATH
+
 # Resolve a host with the given proto (ipv4 or ipv6).
 resolve_addr() {
     local addr="$1"
@@ -20,17 +21,6 @@ resolve_addr() {
     python -c "import socket ; print '\n'.join(x[4][0] for x in socket.getaddrinfo('${addr}', 0, socket.${af}, socket.SOCK_STREAM))" 2>/dev/null || true
 }
 
-# Resolve all hostnames in a file.
-resolve_addrs_from_file() {
-    local filename="$1"
-    local proto="$2"
-    awk '{print $1}' < ${filename} \
-        | (while read name ; do \
-        test -n "${name}" && resolve_addr ${name}.investici.org ${proto} ; \
-        done)
-    return 0
-}
-
 # Add a rule valid for IPv4 and IPv6.
 add_rule() {
     add_rule4 "$*"
@@ -39,14 +29,12 @@ add_rule() {
 
 # Add an IPv4-only rule.
 add_rule4() {
-    local args="$*"
-    echo "${args}" 1>&4
+    echo "$*" 1>&4
 }
 
 # Add an IPv6-only rule.
 add_rule6() {
-    local args="$*"
-    echo "${args}" 1>&6
+    echo "$*" 1>&6
 }
 
 # Create a new iptables chain.
@@ -67,7 +55,7 @@ add_to_chain() {
 
 # Add a rule to the 'user-input' chain allowing traffic to a
 # specific port/protocol.
-add_user_port() {
+allow_port() {
     local proto="$1"
     local port="$2"
     shift 2
@@ -80,7 +68,7 @@ add_user_port() {
 
 # Add a rule to the 'user-input' chain allowing traffic to
 # a set of multiple ports.
-add_user_ports() {
+allow_ports() {
     local proto="$1"
     local ports="$2"
     shift 2
@@ -108,126 +96,61 @@ generate() {
 
     add_rule "*${table_name}"
 
-    # Initialize the table.
+    # Initialize the table with default policies.
     case ${table_name} in
+        filter)
+            create_chain INPUT DROP
+            create_chain OUTPUT ACCEPT
+            create_chain FORWARD ACCEPT
+            ;;
         nat)
-            generate_nat
+            create_chain PREROUTING ACCEPT
+            create_chain OUTPUT ACCEPT
+            create_chain POSTROUTING ACCEPT
             ;;
-        filter)
-            generate_filter
+        mangle)
+            create_chain PREROUTING ACCEPT
+            create_chain INPUT ACCEPT
+            create_chain FORWARD ACCEPT
+            create_chain OUTPUT ACCEPT
+            create_chain POSTROUTING ACCEPT
             ;;
-   esac
+    esac
 
     # Load user-defined rulesets.
-    for file in ${conf_root}/*
-    do
-        test -f ${file} || continue
-        case $(basename "${file}") in
-            *~|.*)
-                ;;
-            *)
-                . ${file}
-                ;;
-        esac
-    done
+    if [ -d ${conf_root} ]; then
+        for file in $(run-parts --list ${conf_root}); do
+            . ${file}
+        done
+    fi
 
     add_rule COMMIT
 }
 
-# Initialize the 'nat' table.
-generate_nat() {
-    create_chain PREROUTING ACCEPT
-    create_chain OUTPUT ACCEPT
-    create_chain POSTROUTING ACCEPT
-}
-
-# Initialize the 'filter' table.
-generate_filter() {
-    create_chain INPUT DROP
-    create_chain OUTPUT ACCEPT
-    create_chain FORWARD ACCEPT
-
-    # Set up a chain that will drop noisy unwanted traffic
-    # without even logging it. 
-    create_chain drop-noise
-    add_rule -A drop-noise -p tcp --dport 113 -j REJECT
-    add_rule -A drop-noise -p tcp -m multiport --dports 139,445 -j DROP
-    add_rule -A drop-noise -p udp -m multiport --dports 137,138,500 -j DROP
-    # Be kind and allow old-style traceroutes.
-    add_rule -A drop-noise -p udp --dport 33434:33500 -j REJECT
-
-    # base-input chain.
-    create_chain base-input
-
-    # Enable everything from lo and ring0.
-    add_rule -A base-input -i lo -j ACCEPT
-    add_rule4 -A base-input -i ring0 -s 172.16.1.0/24 -j ACCEPT
-
-    # Some IPv6-specific ICMP setup.
-    add_rule6 -A base-input -m rt --rt-type 0 --rt-segsleft 0 -j DROP 
-    for icmp6type in 133 134 135 136 ; do 
-        add_rule6 -A base-input -p ipv6-icmp -m icmp6 \
-            --icmpv6-type ${icmp6type} -m hl --hl-eq 255 -j ACCEPT 
-    done
-
-    # Standard conntrack stuff.
-    add_rule -A base-input -m state --state RELATED,ESTABLISHED -j ACCEPT
-    add_rule6 -A base-input -s fe80::/10 -p ipv6-icmp -m icmp6 \
-        --icmpv6-type 129 -j ACCEPT
-    add_rule -A base-input -m state --state INVALID -j DROP
-
-    # Enable 6to4 protocols.
-    add_rule -A base-input -p ipv6 -j ACCEPT
-
-    # Allow useful ICMPs (but rate-limit incoming echo requests).
-    add_rule4 -A base-input -p icmp -m icmp --icmp-type 8 -m limit \
-        --limit 3/s -j ACCEPT
-    for icmptype in 3 4 11 12 ; do
-        add_rule4 -A base-input -p icmp -m icmp \
-            --icmp-type ${icmptype} -j ACCEPT
-    done
-    for icmp6type in 1 2 3 4 128 ; do
-        add_rule6 -A base-input -p ipv6-icmp -m icmp6 \
-            --icmpv6-type ${icmp6type} -j ACCEPT
-    done
-
-    # IPv6 autodiscovery.
-    #add_rule6 -A base-input -s fe80::/10 -d fe80::/10 -p udp -m udp \
-    #    --sport 547 --dport 546 -j ACCEPT
-
-    # user-input
-    create_chain user-input
-
-    # Always allow SSH access, just in case someone forgets to add it
-    # with a user-defined ruleset file.
-    add_user_port tcp 22
-
-    # Setup the INPUT chain.
-    # It is split into stages: base-input, user-input
-    add_rule -A INPUT -j base-input
-    add_rule -A INPUT -j drop-noise
-    add_rule -A INPUT -j user-input
-
-    # Logging.
-    if [ "${DO_LOG}" -eq 1 ]; then
-        create_chain log-deny
-        add_rule -A log-deny -j LOG --log-prefix 'deny: '
-        add_rule -A INPUT -j log-deny -m limit --limit "${LOG_RATE}" --limit-burst 5
-    fi
-}
-
-load() {
+load_firewall() {
     set -e
     set -u
 
-    v4rules=$(mktemp ${TMP:-/tmp}/ip4t.XXXXXX)
-    v6rules=$(mktemp ${TMP:-/tmp}/ip6t.XXXXXX)
-    trap "rm -f ${v4rules} ${v6rules} 2>/dev/null; trap - EXIT; exit 0" EXIT
+    now=$(date)
+    tmpfiles=
+    v4rules=/dev/null
+    v6rules=/dev/null
+    if [ ${enable_ipv4} -eq 1 ]; then
+        v4rules=$(mktemp ${TMP:-/tmp}/ip4t.XXXXXX)
+        tmpfiles="${tmpfiles} ${v4rules}"
+        echo "# firewall(IPv4). Autogenerated on ${now}" > ${v4rules}
+    fi
+    if [ ${enable_ipv6} -eq 1 ]; then
+        v6rules=$(mktemp ${TMP:-/tmp}/ip6t.XXXXXX)
+        tmpfiles="${tmpfiles} ${v6rules}"
+        echo "# firewall(IPv6). Autogenerated on ${now}" > ${v6rules}
+    fi
+    trap "rm -f ${tmpfiles} 2>/dev/null; trap - EXIT; exit 0" EXIT
 
     # Setup the various tables. Note that IPv6 only has the
     # 'filter' table.
     for table in ${TABLES} ; do
-        table_dir=${FW_DIR}/${table}.d
+        table_dir=${CONFIG_DIR}/${table}.d
         case "$table" in
             filter)
                 run_with_fds ${v4rules} ${v6rules} \
@@ -241,29 +164,58 @@ load() {
     done
 
     if [ ${dry_run} -eq 1 ]; then
-        echo "/sbin/iptables-restore <${v4rules}"
-        cat ${v4rules}
-        echo "/sbin/ip6tables-restore <${v6rules}"
-        cat ${v6rules}
+        [ ${enable_ipv4} -eq 0 ] || cat ${v4rules}
+        [ ${enable_ipv6} -eq 0 ] || cat ${v6rules}
     else
-        /sbin/iptables-restore <${v4rules}
-        /sbin/ip6tables-restore <${v6rules}
+        [ ${enable_ipv4} -eq 0 ] || iptables-restore <${v4rules}
+        [ ${enable_ipv6} -eq 0 ] || ip6tables-restore <${v6rules}
     fi
 }
 
+usage() {
+    cat <<EOF
+Usage: $0 [<OPTIONS>]
+Known options:
+  -4             Generate only the IPv4 configuration.
+  -6             Generate only the IPv6 configuration.
+  -n, --dry-run  Do not apply iptables config, just output the results.
+  -h, --help     Print this help message.
+
+EOF
+}
+
 dry_run=0
-if [ "$1" = "-n" ]; then
-    dry_run=1
+enable_ipv4=1
+enable_ipv6=1
+
+while [ $# -gt 0 ]; do
+    case "$1" in
+        -4)
+            enable_ipv6=0
+            ;;
+        -6)
+            enable_ipv4=0
+            ;;
+        -n|--dry-run)
+            dry_run=1
+            ;;
+        -h|--help)
+            usage
+            exit 0
+            ;;
+        --version)
+            echo "firewall v0.2"
+            exit 0
+            ;;
+        *)
+            echo "Unknown argument '$1'" >&2
+            usage >&2
+            exit 2
+            ;;
+    esac
     shift
-fi
+done
 
-case "$1" in
-    start|load|reload)
-        load
-        ;;
-    *)
-        echo "Usage: $0 {start|reload}" 1>&2
-        ;;
-esac
+load_firewall
 
 exit 0
-- 
GitLab