#!/bin/bash -eu
#
# iptables-apply -- a safer way to update iptables remotely
#
# Copyright © 2006 martin f. krafft <madduck@debian.org>
# Released under the terms of the Artistic Licence.
#

PROGNAME=${0##*/}
PROGVERSION=0.1
TIMEOUT=10

blurb()
{
  cat <<EOF
$PROGNAME version $PROGVERSION -- a safer way to update iptables remotely
EOF
}

copyright()
{
  cat <<EOF
$PROGNAME is (c) 2006 by martin f. krafft <madduck@debian.org>.

The programme has been published under the terms of the Artistic Licence.
Please see http://www.opensource.org/licenses/artistic-license.php for
more information. On Debian systems, you may find the text of the licence in
/usr/share/common-licenses/Artistic as well.
EOF
}

about()
{
  blurb
  echo
  copyright
}

usage()
{
  cat <<EOF
Usage:
  $PROGNAME [options] ruleset

  The script will try to apply a new ruleset (as output by iptables-save/read
  by iptables-restore) to iptables, then prompt the user whether the changes
  are okay. If the new ruleset cut the existing connection, the user won't be
  able to answer affirmatively. In this case, the script rolls back to the
  previous ruleset.

  The following options may be specified, using standard conventions:

    -t | --timeout            Specify the timeout in seconds (default: $TIMEOUT)
    -V | --version            Display version information
    -h | --help               Display this help text
EOF
}

SHORTOPTS=t:Vh
LONGOPTS=timeout:,version,help

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
          case "$TIMEOUT" in
            [0-9]*) :;;
            *)
              echo "E: non-numeric timeout value." >&2
              exit 1
              ;;
          esac
          ;;
      esac
      ;;
  esac

  case $opt in
    -h|--help) usage >&2; exit 0;;
    -V|--version) about >&2; exit 0;;
    -t|--timeout) OPT_STATE=SET_TIMEOUT;;
    --) break;;
  esac
  shift
done

FILE=${1:-}

if [ -z "$FILE" ]; then
  echo E: missing file argument. >&2
  exit 1
fi

if [ ! -r "$FILE" ]; then
  echo E: cannot read $FILE >&2
  exit 2
fi

case "${0##*/}" in
  *6*)
    SAVE=ip6tables-save
    RESTORE=ip6tables-restore
    ;;
  *)
    SAVE=iptables-save
    RESTORE=iptables-restore
    ;;
esac

COMMANDS=(mktemp $SAVE $RESTORE)

for cmd in ${COMMANDS[@]}; do
  if ! command -v $cmd >/dev/null; then
    echo E: command not found: $cmd >&2
    exit 127
  fi
done

umask 0700

TMPFILE=$(mktemp /tmp/ipt.XXXXXX)
trap "rm -f $TMPFILE" 0

$SAVE > $TMPFILE

echo -n "Applying new ruleset... "
$RESTORE < $FILE
echo done.

PROMPT="Ruleset applied; are you seeing this message?"
unset ret 
read -p"$PROMPT " -n1 -t ${TIMEOUT:-15} ret 2>&1 || :
case "${ret:-}" in
  y*|Y*|j*|J*|1|t*|T*)
    echo
    echo ... then my job is done. See you next time.
    exit 0
    ;;
  *)
    :;;
esac

if [ -z "${ret:-}" ]; then
  echo "apparently not..."
else
  echo
fi
echo "Timeout. Something happened (or did not). Better play it safe..."
echo -n "Reverting to old ruleset... "
$RESTORE < $TMPFILE
echo done.

exit 0
