#!/bin/bash
# wfchkintegrity v0.2.2: various ruleset integrity checks based on comparison
# with previous state.

# This script is part of the WallFire project. See http://www.wallfire.org/.
# (C) Herve Eychenne, 2003

# When called with no argument, a diff between the current
# configuration stored on disk and the current configuration on kernel
# memory is printed on stdout, unless -d option (nodiff) is specified.
# If no difference is found, no files are modified on disk. Otherwise,
# the old configuration file is renamed for backup, and the current
# kernel configuration is stored on disk.
# If firewalling rules do not change again in the meanwhile, next invocations
# of the command will show no change (changes are reported only one time).
# Exit code is 0 if no changes occured, != 0 otherwise.

# When called with "diff" argument, only a diff between the current
# configuration stored on disk and the current configuration in kernel
# memory is printed on stdout, unless -d option (nodiff) is specified.
# No files are modified on disk.
# Exit code is 0 if no changes occured, != 0 otherwise.

# When called with "difflast" argument, only a diff between the previous
# configuration stored on disk and the current configuration stored on disk
# is printed on stdout.
# No files are modified on disk.
# Exit code is 0 if there are no changes between the two, != 0 otherwise.

# When called with "clean" argument, old configurations stored on disk are
# removed (more than 60 days old)

# When called with "reset" argument, current configuration in kernel memory
# is stored as the current configuration stored on disk (erasing the previous
# one).

# In any case, -e option means no warning or error message printed on stderr
# (return code is still set adequately, though).
# Option -q implies -d and -e (quiet mode: no output at all).

# Exit code can be 2 if an unexpected error occurs.

# TODO:
# - add an option (-D ?) to make it a daemon
# - add more checks

RULEFILE=/var/lib/wallfire/configdump

dumpprocentry() {
  echo -n "${1}: "; cat $1
}

dumpall() {
  iptables-save | /bin/sed -e '/^#/d' -e 's/^\(:.*\) \[[0-9]*:[0-9]*\]$/\1/' 
  dumpprocentry /proc/sys/net/ipv4/ip_forward
  # Here we could add much more things (according to a config file) @@6
}

log() {
  [ "$NOERROR" ] || echo "$0: $@" >&2
}

createrulefile() {
  [ -d "$DIR" ] || /bin/mkdir -p "$DIR"
  if [ $? != 0 ]; then
    log "Error: ${DIR}: could not create directory"
    exit 2
  fi
  dumpall >$1
  if [ $? != 0 ]; then
    log "Error: ${1}: could not create file"
    exit 2
  fi
}

[ "$DIFF" ] || DIFF=/usr/bin/diff
umask 177

NODIFF=
NOERROR=
while : ; do
  case "$1" in
   -d)
    NODIFF=1 ;;
   -e)
    NOERROR=1 ;;
   -q)
    NODIFF=1
    NOERROR=1
    ;;
   *)
    break ;;
  esac
  shift
done

DIR="$(/usr/bin/dirname "$RULEFILE")"

if [ $# != 0 ]; then
  case "$1" in
   diff)
    dumpall | eval $DIFF "$RULEFILE" - ${NODIFF:+>/dev/null}
    exit
    ;;
   difflast)
    $DIFF "${RULEFILE}_changed.last" "$RULEFILE"
    exit
    ;;
   clean)
    find $DIR -name "configdump_changed_*" -ctime +60 | xargs rm -f
    exit
    ;;
   reset)
    [ -e "$RULEFILE" ] && rm "$RULEFILE"
    createrulefile "$RULEFILE"
    exit 0
    ;;
   *)
    log "Error: unknown argument $*"
    exit 2
  esac
fi

# From here, no command line argument was specified.
if [ ! -r "$RULEFILE" ]; then
  log "Warning: ${RULEFILE}: file is not readable or does not exist: create it"
  createrulefile "$RULEFILE"
  exit 1  # there is a difference, as we just created the state file
fi

createrulefile "${RULEFILE}.new"

if eval $DIFF "$RULEFILE" "${RULEFILE}.new" ${NODIFF:+>/dev/null} ; then
  /bin/rm "${RULEFILE}.new"
  exit 0  # no difference, it's ok
fi

# There are differences.

# Rename file to keep an archive.
CHANGED="${RULEFILE}_changed_$(date '+%y%m%d-%X')"
log "Warning: ruleset changed: moving old file $RULEFILE to $CHANGED"
/bin/mv "$RULEFILE" "$CHANGED"

# Create a symbolic link on the last configuration.
rm -f "${RULEFILE}_changed.last"
ln -s "$CHANGED" "${RULEFILE}_changed.last"

# Store the new current configuration.
/bin/mv "${RULEFILE}.new" "$RULEFILE"
exit 1
