#!/bin/sh
#
# this file maintained at http://git.mdcc.cx/uruk.git
#
# Uruk control script.

# Copyright (C) 2002, 2003 Laurence J. Lane
# Copyright (C) 2003 - 2005, 2007, 2010 Joost van Baal
# Copyright (C) 2013 - 2016 Joost van Baal-Ilić
#
# This file is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# This file is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU GPL for more details.
#
# You should have received a copy of the GNU GPL along with this file, see
# e.g. the file named COPYING.  If not, see <http://www.gnu.org/licenses/>.

# Based upon /etc/init.d/iptables as shipped with the Debian iptables
# package by Laurence J. Lane

set -e

# do sanity check on uruk environment.
enable_uruk_check=true
## enable_uruk_check=false

# enable ipv6 support
enable_ipv6=true

# enable calling the unstable uruk-save script
enable_uruk_save=false

# set enable_autosave to "true" to autosave the active ruleset
# when going from start to stop
enable_autosave=true

# set enable_save_counters to "true" to save table counters with
# rulesets
enable_save_counters=true

# /etc/default/uruk can overrule
# enable_uruk_check, enable_ipv6, enable_autosave, enable_save_counters and PATH
# On Debian systems, configuration for init scripts is in /etc/default/
test -f /etc/default/uruk && . /etc/default/uruk
# On Red Hat systems, configuration for init scripts is in /etc/sysconfig/
test -f /etc/sysconfig/uruk && . /etc/sysconfig/uruk

# exit code
STATUS=0

initd="$0"

initd_abort_wrong_arg () {
  cmd=$1
  shift
  echo "Aborting urukctl $cmd: wrong argument: $@"
  exit 2
}

initd_have_a_cow_man () {
  for i in $@; do
    if ! command -v "$i" >/dev/null 2>&1; then
      echo "Aborting urukctl: missing executable $i"
      exit 5
    fi
  done
}

initd_clear () {
  rm -f "$autosave"
  echo -n "Clearing ${iptables_command} ruleset: default ACCEPT policy"
  $iptables_save | sed "/-/d;/^#/d;s/DROP/ACCEPT/" | $iptables_restore
  echo "."
}

initd_halt () {
  rm -f $autosave
  echo -n "Clearing ${iptables_command} ruleset: default DROP policy"
  $iptables_save | sed "/-/d;/^#/d;s/ACCEPT/DROP/" | $iptables_restore
  echo "."
}

initd_flush () {
  # This will NOT flush the mangle or nat table.  If we wanna do that, we'd have to do
  # something like
  #
  #  while read -r table;do iptables -t $table -F;done </proc/net/ip_tables_names
  #
  # However, this won't work for ip6tables; for that we'd have to read ip6_tables_names.
  # We can't use the $iptables_command variable for constructing the name of that file.
  # I give up: this would introduce too much cruft.

  echo -n "Flushing all current $iptables_command rules"
  $iptables_command -F
  echo "."
}

initd_load () {
  ruleset="$libdir/$@"
  if ! test -f "$ruleset"; then
    echo "Can't load ruleset \"$@\": file $ruleset is not present."
    echo "Remember: reuleset should be named either \"active\" or \"inactive\""
    initd_abort_wrong_arg load "unknown ruleset \"$@\""
  fi
  if test "${ruleset#${libdir}/}" = inactive; then
    initd_autosave
  fi
  rm -f "$autosave"
  ruleset="$libdir/$@"
  echo -n "Loading ${iptables_command} ruleset: load \"$@\""
  $iptables_restore < "$ruleset"
  STATUS=$?
  echo "."
}

initd_counters () {
  if ! test -d "$libdir"; then
    mkdir -p "$libdir"
  fi

  # don't try to run iptables_command : we might only want to run uruk-save
  initd_have_a_cow_man "$iptables_save"

  if $enable_save_counters; then
    echo -n " with counters"
    $iptables_save -c > "$ruleset"
    STATUS=$?
  else
    $iptables_save | sed '/^:/s@\[[0-9]\{1,\}:[0-9]\{1,\}\]@[0:0]@g' > "$ruleset"
    STATUS=$?
  fi
}

initd_save () {
  rm -f $autosave
  ruleset="$libdir/$@"
  echo -n "Saving ${iptables_command} ruleset: save \"$@\""
  initd_counters
  echo "."
}

initd_autosave () {
  if $enable_autosave && test -f $autosave; then
    ruleset="$libdir/active"
    echo -n "Autosaving ${iptables_command} ruleset: save \"active\""
    initd_counters
    echo "."
  fi
}

initd_active_uruk_save () {
  if test $iptables_command = ip6tables; then
    echo -n "Saving IPv6 uruk rules as active ruleset"
    uruk-save -6 > "$libdir/active"
    STATUS=$?
    echo "."
  else
    echo -n "Saving IPv4 uruk rules as active ruleset"
    uruk-save > "$libdir/active"
    STATUS=$?
    echo "."
  fi
  initd_load active
  dummy=$?
  test "$STATUS" = 0 && STATUS=$dummy
}

initd_active () {
  if $enable_uruk_save; then
    initd_active_uruk_save
  else
    initd_flush
    if test $iptables_command = ip6tables; then
      echo -n "Loading IPv6 uruk rules"
      # skip all iptables commands in uruk
      URUK_IPTABLES=':' uruk
      STATUS=$?
      echo "."
    else
      echo -n "Loading IPv4 uruk rules"
      # skip all ip6tables commands in uruk
      URUK_IP6TABLES=':' uruk
      STATUS=$?
      echo "."
    fi
    initd_save active
    dummy=$?
    test "$STATUS" = 0 && STATUS=$dummy
  fi
}

initd_start () {
  if ! test -e "$libdir/inactive"; then
    initd_save inactive
  fi
  initd_active
  if $enable_autosave; then
    touch $autosave
  fi
}

initd_stop () {
  # act sane if inactive state file missing
  ruleset="$libdir/inactive"
  if test -e $ruleset; then
    initd_load inactive
    rm $ruleset
  else
    echo "Uruk not running (no inactive file found)"
    STATUS=0
  fi
}

initd_status() {
  initd_preload

  tmpdir=`mktemp -d /tmp/uruk.$iptables_command.XXXXXXXXXX`
  trap 'rm -rf $tmpdir' 0

  # grep possibly matches nothing, force succesfull exit
  $iptables_save | grep '^-' >$tmpdir/kernel || true
  for rule in active inactive; do
    eval status_$rule=
    eval found_$rule=
    if test -e $libdir/$rule; then
      sed -n 's/^\[[0-9]\{1,\}:[0-9]\{1,\}\] //p' $libdir/$rule >$tmpdir/ruleset
      if diff $tmpdir/ruleset $tmpdir/kernel >/dev/null; then
         echo "Checking uruk ($iptables_command): $rule uruk rules loaded"
         eval status_$rule=1
      fi
      eval found_$rule=1
    fi
  done
  rm -r $tmpdir

  # if running, active loaded; then rulesets existing as file: active inactive
  # if "not running", inactive loaded; then active
  if test "$found_active"; then
    if test "$found_inactive"; then
      # uruk is running, STATUS=0
      STATUS=0
    else
      # uruk is not running
      STATUS=3
    fi
  else
    # uruk not running, unconfigured: "unknown"
    STATUS=4
  fi
}

usage () {
cat << END
$initd options:
  start
  save <ruleset>
  create <active|inactive>
  load <ruleset>
  reload
  force-reload
  stop
  restart
  status
  clear
  halt
  flush
See the urukctl(8) manpage for details.
END
}

initd_main () {
  initd_vars

  case "$1" in
    start)
      initd_start
      ;;
    stop)
      initd_stop
      ;;
    restart)
      # Restart service (if running) or start service
      $initd stop
      $initd start
      ;;
    force-reload)
      for rule in active inactive; do
        eval found_$rule=
        if test -e $libdir/$rule; then
          eval found_$rule=1
        fi
      done
      if test "$found_active" -a "$found_inactive"; then
        # uruk is running
        initd_active
      else
        echo "Uruk is not running"
        STATUS=0
      fi

      ;;
    status)
      # If the status action is requested, the init script will
      # return the following exit status codes.
      #
      # 0  program is running or service is OK
      #(1  program is dead and /var/run pid file exists)
      #(2  program is dead and /var/lock lock file exists)
      # 3  program is not running
      # 4  program or service status is unknown

      # will set STATUS, used as exit code
      initd_status
      ;;
    # end of LSB required init arguments
    reload)
      for rule in active inactive; do
        eval found_$rule=
        if test -e $libdir/$rule; then
          eval found_$rule=1
        fi
      done
      if test "$found_active" -a "$found_inactive"; then
        if $enable_uruk_save; then
          initd_active_uruk_save
        else
          cat <<END
 Either set enable_uruk_save to true in
 /etc/{default,sysconfig}/uruk or call this script with
 the "force-reload" option: cannot reload active file without touching your live
 rules if using uruk-save is disallowed.
END
        fi
      else
        echo "Uruk is not running"
        STATUS=0
      fi

      ;;
    clear)
      initd_clear
      ;;
    halt)
      initd_halt
      ;;
    flush)
      initd_flush
      ;;
    save)
      shift
      if test -z "$*"; then
        initd_abort_wrong_arg save "no ruleset name given"
      else
        initd_save "$*"
      fi
      ;;
    create)
      shift
      case "$*" in
        active)
          if $enable_uruk_save; then
            if test $iptables_command = ip6tables; then
              echo -n "Saving IPv6 uruk rules as active ruleset"
              uruk-save -6 > "$libdir/active"
              echo "."
            else
              echo -n "Saving IPv4 uruk rules as active ruleset"
              uruk-save > "$libdir/active"
              echo "."
            fi
          else
            cat <<END
 Either set enable_uruk_save to true in /etc/{default,sysconfig}/uruk
 or call this script with the "start" option: cannot create active file
 without touching your live rules if using uruk-save is disallowed.
END
          fi
          ;;
        inactive)
          initd_clear
          initd_save inactive
          ;;
        *)
          echo "No sane defaults for \"$*\" known"
          ;;
      esac
      ;;
    load)
      shift
      if test -z "$*"; then
        initd_abort_wrong_arg load "no ruleset name given"
      else
        initd_load "$*"
      fi
      ;;
    *)
      # never reached
      exit 1
      ;;
  esac
}

initd_vars() {
  iptables="/sbin/${iptables_command}"
  iptables_save="${iptables}-save"
  iptables_restore="${iptables}-restore"
  uruk_config="/etc/uruk/rc"
  libdir="/var/lib/uruk/${iptables_command}"
  autosave="${libdir}/autosave"
}

initd_preload() {
  initd_have_a_cow_man "$iptables_save" "$iptables_restore"
  if ! ${iptables_command} -nL >/dev/null; then
    echo "Fails to run ${iptables_command}."
    exit 4
  fi
}

check_uruk() {
  initd_have_a_cow_man uruk >/dev/null
  uruk_config="/etc/uruk/rc"
  # check for existence of uruk rc file.
  if ! test -r $uruk_config; then
    echo "No file $uruk_config present."
    exit 6
  fi
  # check for sanity of uruk rc file.
  if grep -q URUK_IS_UNCONFIGURED $uruk_config; then
    echo "Uruk is unconfigured.  Please create a sane file $uruk_config.  See uruk(8)."
    exit 6
  fi
}

# check command line args
case "$1" in
  start|stop|restart|force-reload|status|reload|clear|halt|flush|save|create|load)
    # pass
    ;;
  *)
    usage
    initd_abort_wrong_arg "$*"
    ;;
esac

if $enable_uruk_check; then
  check_uruk
fi

iptables_command=iptables initd_main $*
if $enable_ipv6; then
  iptables_command=ip6tables initd_main $*
fi

exit $STATUS