| #!/bin/bash |
| # iptables-apply -- a safer way to update iptables remotely |
| # |
| # Usage: |
| # iptables-apply [-hV] [-t timeout] [-w savefile] {[rulesfile]|-c [runcmd]} |
| # |
| # Versions: |
| # * 1.0 Copyright 2006 Martin F. Krafft <madduck@madduck.net> |
| # Original version |
| # * 1.1 Copyright 2010 GW <gw.2010@tnode.com or http://gw.tnode.com/> |
| # Added parameter -c (run command) |
| # Added parameter -w (save successfully applied rules to file) |
| # Major code cleanup |
| # |
| # Released under the terms of the Artistic Licence 2.0 |
| # |
| set -eu |
| |
| PROGNAME="${0##*/}" |
| VERSION=1.1 |
| |
| |
| ### Default settings |
| |
| DEF_TIMEOUT=10 |
| |
| MODE=0 # apply rulesfile mode |
| # MODE=1 # run command mode |
| |
| case "$PROGNAME" in |
| (*6*) |
| SAVE=ip6tables-save |
| RESTORE=ip6tables-restore |
| DEF_RULESFILE="/etc/network/ip6tables.up.rules" |
| DEF_SAVEFILE="$DEF_RULESFILE" |
| DEF_RUNCMD="/etc/network/ip6tables.up.run" |
| ;; |
| (*) |
| SAVE=iptables-save |
| RESTORE=iptables-restore |
| DEF_RULESFILE="/etc/network/iptables.up.rules" |
| DEF_SAVEFILE="$DEF_RULESFILE" |
| DEF_RUNCMD="/etc/network/iptables.up.run" |
| ;; |
| esac |
| |
| |
| ### Functions |
| |
| function blurb() { |
| cat <<-__EOF__ |
| $PROGNAME $VERSION -- a safer way to update iptables remotely |
| __EOF__ |
| } |
| |
| function copyright() { |
| cat <<-__EOF__ |
| $PROGNAME has been published under the terms of the Artistic Licence 2.0. |
| |
| Original version - Copyright 2006 Martin F. Krafft <madduck@madduck.net>. |
| Version 1.1 - Copyright 2010 GW <gw.2010@tnode.com or http://gw.tnode.com/>. |
| __EOF__ |
| } |
| |
| function about() { |
| blurb |
| echo |
| copyright |
| } |
| |
| function usage() { |
| blurb |
| echo |
| cat <<-__EOF__ |
| Usage: |
| $PROGNAME [-hV] [-t timeout] [-w savefile] {[rulesfile]|-c [runcmd]} |
| |
| The script will try to apply a new rulesfile (as output by iptables-save, |
| read by iptables-restore) or run a command to configure iptables and then |
| prompt the user whether the changes are okay. If the new iptables rules cut |
| the existing connection, the user will not be able to answer affirmatively. |
| In this case, the script rolls back to the previous working iptables rules |
| after the timeout expires. |
| |
| Successfully applied rules can also be written to savefile and later used |
| to roll back to this state. This can be used to implement a store last good |
| configuration mechanism when experimenting with an iptables setup script: |
| $PROGNAME -w $DEF_SAVEFILE -c $DEF_RUNCMD |
| |
| When called as ip6tables-apply, the script will use ip6tables-save/-restore |
| and IPv6 default values instead. Default value for rulesfile is |
| '$DEF_RULESFILE'. |
| |
| Options: |
| |
| -t seconds, --timeout seconds |
| Specify the timeout in seconds (default: $DEF_TIMEOUT). |
| -w savefile, --write savefile |
| Specify the savefile where successfully applied rules will be written to |
| (default if empty string is given: $DEF_SAVEFILE). |
| -c runcmd, --command runcmd |
| Run command runcmd to configure iptables instead of applying a rulesfile |
| (default: $DEF_RUNCMD). |
| -h, --help |
| Display this help text. |
| -V, --version |
| Display version information. |
| |
| __EOF__ |
| } |
| |
| function checkcommands() { |
| for cmd in "${COMMANDS[@]}"; do |
| if ! command -v "$cmd" >/dev/null; then |
| echo "Error: needed command not found: $cmd" >&2 |
| exit 127 |
| fi |
| done |
| } |
| |
| function revertrules() { |
| echo -n "Reverting to old iptables rules... " |
| "$RESTORE" <"$TMPFILE" |
| echo "done." |
| } |
| |
| |
| ### Parsing and checking parameters |
| |
| TIMEOUT="$DEF_TIMEOUT" |
| SAVEFILE="" |
| |
| SHORTOPTS="t:w:chV"; |
| LONGOPTS="timeout:,write:,command,help,version"; |
| |
| OPTS=$(getopt -s bash -o "$SHORTOPTS" -l "$LONGOPTS" -n "$PROGNAME" -- "$@") || exit $? |
| for opt in $OPTS; do |
| case "$opt" in |
| (-*) |
| unset OPT_STATE |
| ;; |
| (*) |
| case "${OPT_STATE:-}" in |
| (SET_TIMEOUT) eval TIMEOUT=$opt;; |
| (SET_SAVEFILE) |
| eval SAVEFILE=$opt |
| [ -z "$SAVEFILE" ] && SAVEFILE="$DEF_SAVEFILE" |
| ;; |
| esac |
| ;; |
| esac |
| |
| case "$opt" in |
| (-t|--timeout) OPT_STATE="SET_TIMEOUT";; |
| (-w|--write) OPT_STATE="SET_SAVEFILE";; |
| (-c|--command) MODE=1;; |
| (-h|--help) usage >&2; exit 0;; |
| (-V|--version) about >&2; exit 0;; |
| (--) break;; |
| esac |
| shift |
| done |
| |
| # Validate parameters |
| if [ "$TIMEOUT" -ge 0 ] 2>/dev/null; then |
| TIMEOUT=$(($TIMEOUT)) |
| else |
| echo "Error: timeout must be a positive number" >&2 |
| exit 1 |
| fi |
| |
| if [ -n "$SAVEFILE" -a -e "$SAVEFILE" -a ! -w "$SAVEFILE" ]; then |
| echo "Error: savefile not writable: $SAVEFILE" >&2 |
| exit 8 |
| fi |
| |
| case "$MODE" in |
| (1) |
| # Treat parameter as runcmd (run command mode) |
| RUNCMD="${1:-$DEF_RUNCMD}" |
| if [ ! -x "$RUNCMD" ]; then |
| echo "Error: runcmd not executable: $RUNCMD" >&2 |
| exit 6 |
| fi |
| |
| # Needed commands |
| COMMANDS=(mktemp "$SAVE" "$RESTORE" "$RUNCMD") |
| checkcommands |
| ;; |
| (*) |
| # Treat parameter as rulesfile (apply rulesfile mode) |
| RULESFILE="${1:-$DEF_RULESFILE}"; |
| if [ ! -r "$RULESFILE" ]; then |
| echo "Error: rulesfile not readable: $RULESFILE" >&2 |
| exit 2 |
| fi |
| |
| # Needed commands |
| COMMANDS=(mktemp "$SAVE" "$RESTORE") |
| checkcommands |
| ;; |
| esac |
| |
| |
| ### Begin work |
| |
| # Store old iptables rules to temporary file |
| TMPFILE=`mktemp /tmp/$PROGNAME-XXXXXXXX` |
| trap "rm -f $TMPFILE" EXIT HUP INT QUIT ILL TRAP ABRT BUS \ |
| FPE USR1 SEGV USR2 PIPE ALRM TERM |
| |
| if ! "$SAVE" >"$TMPFILE"; then |
| # An error occured |
| if ! grep -q ipt /proc/modules 2>/dev/null; then |
| echo "Error: iptables support lacking from the kernel" >&2 |
| exit 3 |
| else |
| echo "Error: unknown error saving old iptables rules: $TMPFILE" >&2 |
| exit 4 |
| fi |
| fi |
| |
| # Legacy to stop the fail2ban daemon if present |
| [ -x /etc/init.d/fail2ban ] && /etc/init.d/fail2ban stop |
| |
| # Configure iptables |
| case "$MODE" in |
| (1) |
| # Run command in background and kill it if it times out |
| echo -n "Running command '$RUNCMD'... " |
| "$RUNCMD" & |
| CMD_PID=$! |
| ( sleep "$TIMEOUT"; kill "$CMD_PID" 2>/dev/null; exit 0 ) & |
| CMDTIMEOUT_PID=$! |
| if ! wait "$CMD_PID"; then |
| echo "failed." |
| echo "Error: unknown error running command: $RUNCMD" >&2 |
| revertrules |
| exit 7 |
| else |
| echo "done." |
| fi |
| ;; |
| (*) |
| # Apply iptables rulesfile |
| echo -n "Applying new iptables rules from '$RULESFILE'... " |
| if ! "$RESTORE" <"$RULESFILE"; then |
| echo "failed." |
| echo "Error: unknown error applying new iptables rules: $RULESFILE" >&2 |
| revertrules |
| exit 5 |
| else |
| echo "done." |
| fi |
| ;; |
| esac |
| |
| # Prompt user for confirmation |
| echo -n "Can you establish NEW connections to the machine? (y/N) " |
| |
| read -n1 -t "$TIMEOUT" ret 2>&1 || : |
| case "${ret:-}" in |
| (y*|Y*) |
| # Success |
| echo |
| |
| if [ ! -z "$SAVEFILE" ]; then |
| # Write successfully applied rules to the savefile |
| echo "Writing successfully applied rules to '$SAVEFILE'..." |
| if ! "$SAVE" >"$SAVEFILE"; then |
| echo "Error: unknown error writing successfully applied rules: $SAVEFILE" >&2 |
| exit 9 |
| fi |
| fi |
| |
| echo "... then my job is done. See you next time." |
| ;; |
| (*) |
| # Failed |
| echo |
| if [ -z "${ret:-}" ]; then |
| echo "Timeout! Something happened (or did not). Better play it safe..." |
| else |
| echo "No affirmative response! Better play it safe..." |
| fi |
| revertrules |
| exit 255 |
| ;; |
| esac |
| |
| # Legacy to start the fail2ban daemon again |
| [ -x /etc/init.d/fail2ban ] && /etc/init.d/fail2ban start |
| |
| exit 0 |
| |
| # vim:noet:sw=8 |