1#!/bin/bash
2#
3# iptables-apply -- a safer way to update iptables remotely
4#
5# Copyright © Martin F. Krafft <madduck@madduck.net>
6# Released under the terms of the Artistic Licence 2.0
7#
8set -eu
9
10PROGNAME="${0##*/}";
11VERSION=1.0
12
13TIMEOUT=10
14
15function blurb()
16{
17	cat <<-_eof
18	$PROGNAME $VERSION -- a safer way to update iptables remotely
19	_eof
20}
21
22function copyright()
23{
24	cat <<-_eof
25	$PROGNAME is C Martin F. Krafft <madduck@madduck.net>.
26
27	The program has been published under the terms of the Artistic Licence 2.0
28	_eof
29}
30
31function about()
32{
33	blurb
34	echo
35	copyright
36}
37
38function usage()
39{
40	cat <<-_eof
41	Usage: $PROGNAME [options] ruleset
42
43	The script will try to apply a new ruleset (as output by iptables-save/read
44	by iptables-restore) to iptables, then prompt the user whether the changes
45	are okay. If the new ruleset cut the existing connection, the user will not
46	be able to answer affirmatively. In this case, the script rolls back to the
47	previous ruleset.
48
49	The following options may be specified, using standard conventions:
50
51	-t | --timeout	Specify the timeout in seconds (default: $TIMEOUT)
52	-V | --version	Display version information
53	-h | --help	Display this help text
54	_eof
55}
56
57SHORTOPTS="t:Vh";
58LONGOPTS="timeout:,version,help";
59
60OPTS=$(getopt -s bash -o "$SHORTOPTS" -l "$LONGOPTS" -n "$PROGNAME" -- "$@") || exit $?
61for opt in $OPTS; do
62	case "$opt" in
63		(-*) unset OPT_STATE;;
64		(*)
65			case "${OPT_STATE:-}" in
66				(SET_TIMEOUT)
67					eval TIMEOUT=$opt
68					case "$TIMEOUT" in
69						([0-9]*) :;;
70						(*)
71							echo "E: non-numeric timeout value." >&2
72							exit 1
73							;;
74					esac
75					;;
76			esac
77			;;
78	esac
79
80	case "$opt" in
81		(-h|--help) usage >&2; exit 0;;
82		(-V|--version) about >&2; exit 0;;
83		(-t|--timeout) OPT_STATE=SET_TIMEOUT;;
84		(--) break;;
85	esac
86	shift
87done
88
89case "$PROGNAME" in
90	(*6*)
91		SAVE=ip6tables-save
92		RESTORE=ip6tables-restore
93		DEFAULT_FILE=/etc/network/ip6tables
94		;;
95	(*)
96		SAVE=iptables-save
97		RESTORE=iptables-restore
98		DEFAULT_FILE=/etc/network/iptables
99		;;
100esac
101
102FILE="${1:-$DEFAULT_FILE}";
103
104if [[ -z "$FILE" ]]; then
105	echo "E: missing file argument." >&2
106	exit 1
107fi
108
109if [[ ! -r "$FILE" ]]; then
110	echo "E: cannot read $FILE" >&2
111	exit 2
112fi
113
114COMMANDS=(tempfile "$SAVE" "$RESTORE")
115
116for cmd in "${COMMANDS[@]}"; do
117	if ! command -v $cmd >/dev/null; then
118		echo "E: command not found: $cmd" >&2
119		exit 127
120	fi
121done
122
123umask 0700
124
125TMPFILE=$(tempfile -p iptap)
126trap "rm -f $TMPFILE" EXIT 1 2 3 4 5 6 7 8 10 11 12 13 14 15
127
128if ! "$SAVE" >"$TMPFILE"; then
129	if ! grep -q ipt /proc/modules 2>/dev/null; then
130		echo "E: iptables support lacking from the kernel." >&2
131		exit 3
132	else
133		echo "E: unknown error saving current iptables ruleset." >&2
134		exit 4
135	fi
136fi
137
138[ -x /etc/init.d/fail2ban ] && /etc/init.d/fail2ban stop
139
140echo -n "Applying new ruleset... "
141if ! "$RESTORE" <"$FILE"; then
142	echo "failed."
143	echo "E: unknown error applying new iptables ruleset." >&2
144	exit 5
145else
146	echo done.
147fi
148
149echo -n "Can you establish NEW connections to the machine? (y/N) "
150
151read -n1 -t "${TIMEOUT:-15}" ret 2>&1 || :
152case "${ret:-}" in
153	(y*|Y*)
154		echo
155		echo ... then my job is done. See you next time.
156		;;
157	(*)
158		if [[ -z "${ret:-}" ]]; then
159			echo "apparently not..."
160		else
161			echo
162		fi
163		echo "Timeout. Something happened (or did not). Better play it safe..."
164		echo -n "Reverting to old ruleset... "
165		"$RESTORE" <"$TMPFILE";
166		echo done.
167		exit 255
168		;;
169esac
170
171[ -x /etc/init.d/fail2ban ] && /etc/init.d/fail2ban start
172
173exit 0
174
175# vim:noet:sw=8
176