• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/bin/bash
2# iptables-apply -- a safer way to update iptables remotely
3#
4# Usage:
5#   iptables-apply [-hV] [-t timeout] [-w savefile] {[rulesfile]|-c [runcmd]}
6#
7# Versions:
8#   * 1.0 Copyright 2006 Martin F. Krafft <madduck@madduck.net>
9#         Original version
10#   * 1.1 Copyright 2010 GW <gw.2010@tnode.com or http://gw.tnode.com/>
11#         Added parameter -c (run command)
12#         Added parameter -w (save successfully applied rules to file)
13#         Major code cleanup
14#
15# Released under the terms of the Artistic Licence 2.0
16#
17set -eu
18
19PROGNAME="${0##*/}"
20VERSION=1.1
21
22
23### Default settings
24
25DEF_TIMEOUT=10
26
27MODE=0  # apply rulesfile mode
28# MODE=1  # run command mode
29
30case "$PROGNAME" in
31	(*6*)
32		SAVE=ip6tables-save
33		RESTORE=ip6tables-restore
34		DEF_RULESFILE="/etc/network/ip6tables.up.rules"
35		DEF_SAVEFILE="$DEF_RULESFILE"
36		DEF_RUNCMD="/etc/network/ip6tables.up.run"
37		;;
38	(*)
39		SAVE=iptables-save
40		RESTORE=iptables-restore
41		DEF_RULESFILE="/etc/network/iptables.up.rules"
42		DEF_SAVEFILE="$DEF_RULESFILE"
43		DEF_RUNCMD="/etc/network/iptables.up.run"
44		;;
45esac
46
47
48### Functions
49
50function blurb() {
51	cat <<-__EOF__
52	$PROGNAME $VERSION -- a safer way to update iptables remotely
53	__EOF__
54}
55
56function copyright() {
57	cat <<-__EOF__
58	$PROGNAME has been published under the terms of the Artistic Licence 2.0.
59
60	Original version - Copyright 2006 Martin F. Krafft <madduck@madduck.net>.
61	Version 1.1 - Copyright 2010 GW <gw.2010@tnode.com or http://gw.tnode.com/>.
62	__EOF__
63}
64
65function about() {
66	blurb
67	echo
68	copyright
69}
70
71function usage() {
72	blurb
73	echo
74	cat <<-__EOF__
75	Usage:
76	  $PROGNAME [-hV] [-t timeout] [-w savefile] {[rulesfile]|-c [runcmd]}
77
78	The script will try to apply a new rulesfile (as output by iptables-save,
79	read by iptables-restore) or run a command to configure iptables and then
80	prompt the user whether the changes are okay. If the new iptables rules cut
81	the existing connection, the user will not be able to answer affirmatively.
82	In this case, the script rolls back to the previous working iptables rules
83	after the timeout expires.
84
85	Successfully applied rules can also be written to savefile and later used
86	to roll back to this state. This can be used to implement a store last good
87	configuration mechanism when experimenting with an iptables setup script:
88	  $PROGNAME -w $DEF_SAVEFILE -c $DEF_RUNCMD
89
90	When called as ip6tables-apply, the script will use ip6tables-save/-restore
91	and IPv6 default values instead. Default value for rulesfile is
92	'$DEF_RULESFILE'.
93
94	Options:
95
96	-t seconds, --timeout seconds
97	  Specify the timeout in seconds (default: $DEF_TIMEOUT).
98	-w savefile, --write savefile
99	  Specify the savefile where successfully applied rules will be written to
100	  (default if empty string is given: $DEF_SAVEFILE).
101	-c runcmd, --command runcmd
102	  Run command runcmd to configure iptables instead of applying a rulesfile
103	  (default: $DEF_RUNCMD).
104	-h, --help
105	  Display this help text.
106	-V, --version
107	  Display version information.
108
109	__EOF__
110}
111
112function checkcommands() {
113	for cmd in "${COMMANDS[@]}"; do
114		if ! command -v "$cmd" >/dev/null; then
115			echo "Error: needed command not found: $cmd" >&2
116			exit 127
117		fi
118	done
119}
120
121function revertrules() {
122	echo -n "Reverting to old iptables rules... "
123	"$RESTORE" <"$TMPFILE"
124	echo "done."
125}
126
127
128### Parsing and checking parameters
129
130TIMEOUT="$DEF_TIMEOUT"
131SAVEFILE=""
132
133SHORTOPTS="t:w:chV";
134LONGOPTS="timeout:,write:,command,help,version";
135
136OPTS=$(getopt -s bash -o "$SHORTOPTS" -l "$LONGOPTS" -n "$PROGNAME" -- "$@") || exit $?
137for opt in $OPTS; do
138	case "$opt" in
139		(-*)
140			unset OPT_STATE
141			;;
142		(*)
143			case "${OPT_STATE:-}" in
144				(SET_TIMEOUT) eval TIMEOUT=$opt;;
145				(SET_SAVEFILE)
146					eval SAVEFILE=$opt
147					[ -z "$SAVEFILE" ] && SAVEFILE="$DEF_SAVEFILE"
148					;;
149			esac
150			;;
151	esac
152
153	case "$opt" in
154		(-t|--timeout) OPT_STATE="SET_TIMEOUT";;
155		(-w|--write) OPT_STATE="SET_SAVEFILE";;
156		(-c|--command) MODE=1;;
157		(-h|--help) usage >&2; exit 0;;
158		(-V|--version) about >&2; exit 0;;
159		(--) break;;
160	esac
161	shift
162done
163
164# Validate parameters
165if [ "$TIMEOUT" -ge 0 ] 2>/dev/null; then
166	TIMEOUT=$(($TIMEOUT))
167else
168	echo "Error: timeout must be a positive number" >&2
169	exit 1
170fi
171
172if [ -n "$SAVEFILE" -a -e "$SAVEFILE" -a ! -w "$SAVEFILE" ]; then
173	echo "Error: savefile not writable: $SAVEFILE" >&2
174	exit 8
175fi
176
177case "$MODE" in
178	(1)
179		# Treat parameter as runcmd (run command mode)
180		RUNCMD="${1:-$DEF_RUNCMD}"
181		if [ ! -x "$RUNCMD" ]; then
182			echo "Error: runcmd not executable: $RUNCMD" >&2
183			exit 6
184		fi
185
186		# Needed commands
187		COMMANDS=(mktemp "$SAVE" "$RESTORE" "$RUNCMD")
188		checkcommands
189		;;
190	(*)
191		# Treat parameter as rulesfile (apply rulesfile mode)
192		RULESFILE="${1:-$DEF_RULESFILE}";
193		if [ ! -r "$RULESFILE" ]; then
194			echo "Error: rulesfile not readable: $RULESFILE" >&2
195			exit 2
196		fi
197
198		# Needed commands
199		COMMANDS=(mktemp "$SAVE" "$RESTORE")
200		checkcommands
201		;;
202esac
203
204
205### Begin work
206
207# Store old iptables rules to temporary file
208TMPFILE=`mktemp /tmp/$PROGNAME-XXXXXXXX`
209trap "rm -f $TMPFILE" EXIT HUP INT QUIT ILL TRAP ABRT BUS \
210		      FPE USR1 SEGV USR2 PIPE ALRM TERM
211
212if ! "$SAVE" >"$TMPFILE"; then
213	# An error occured
214	if ! grep -q ipt /proc/modules 2>/dev/null; then
215		echo "Error: iptables support lacking from the kernel" >&2
216		exit 3
217	else
218		echo "Error: unknown error saving old iptables rules: $TMPFILE" >&2
219		exit 4
220	fi
221fi
222
223# Legacy to stop the fail2ban daemon if present
224[ -x /etc/init.d/fail2ban ] && /etc/init.d/fail2ban stop
225
226# Configure iptables
227case "$MODE" in
228	(1)
229		# Run command in background and kill it if it times out
230		echo -n "Running command '$RUNCMD'... "
231		"$RUNCMD" &
232		CMD_PID=$!
233		( sleep "$TIMEOUT"; kill "$CMD_PID" 2>/dev/null; exit 0 ) &
234		CMDTIMEOUT_PID=$!
235		if ! wait "$CMD_PID"; then
236			echo "failed."
237			echo "Error: unknown error running command: $RUNCMD" >&2
238			revertrules
239			exit 7
240		else
241			echo "done."
242		fi
243		;;
244	(*)
245		# Apply iptables rulesfile
246		echo -n "Applying new iptables rules from '$RULESFILE'... "
247		if ! "$RESTORE" <"$RULESFILE"; then
248			echo "failed."
249			echo "Error: unknown error applying new iptables rules: $RULESFILE" >&2
250			revertrules
251			exit 5
252		else
253			echo "done."
254		fi
255		;;
256esac
257
258# Prompt user for confirmation
259echo -n "Can you establish NEW connections to the machine? (y/N) "
260
261read -n1 -t "$TIMEOUT" ret 2>&1 || :
262case "${ret:-}" in
263	(y*|Y*)
264		# Success
265		echo
266
267		if [ ! -z "$SAVEFILE" ]; then
268			# Write successfully applied rules to the savefile
269			echo "Writing successfully applied rules to '$SAVEFILE'..."
270			if ! "$SAVE" >"$SAVEFILE"; then
271				echo "Error: unknown error writing successfully applied rules: $SAVEFILE" >&2
272				exit 9
273			fi
274		fi
275
276		echo "... then my job is done. See you next time."
277		;;
278	(*)
279		# Failed
280		echo
281		if [ -z "${ret:-}" ]; then
282			echo "Timeout! Something happened (or did not). Better play it safe..."
283		else
284			echo "No affirmative response! Better play it safe..."
285		fi
286		revertrules
287		exit 255
288		;;
289esac
290
291# Legacy to start the fail2ban daemon again
292[ -x /etc/init.d/fail2ban ] && /etc/init.d/fail2ban start
293
294exit 0
295
296# vim:noet:sw=8
297