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