# ------------------------------------------------------------------------------
#                           -= Arno's iptables firewall =-
#               Single- & multi-homed firewall script with DSL/ADSL support
#
#                           ~ In memory of my dear father ~
#
# (C) Copyright 2001-2009 by Arno van Amersfoort
# Homepage              : http://rocky.eld.leidenuniv.nl/
# Freshmeat homepage    : http://freshmeat.net/projects/iptables-firewall/?topic_id=151
# Email                 : a r n o v a AT r o c k y DOT e l d DOT l e i d e n u n i v DOT n l
#                         (note: you must remove all spaces and substitute the @ and the .
#                         at the proper locations!)
# ------------------------------------------------------------------------------
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# version 2 as published by the Free Software Foundation.

# This program 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 General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
# ------------------------------------------------------------------------------

# NOTE: When used in combination with firewall.conf. Load firewall.conf first before calling us!

# Some predefined variables/macros:
IPTABLES_ERROR=0
ANYHOST="0/0"
ANYPORT="0:65535"
SEP="~"
SEP2="#"
SEP3="|"
INDENT=""
TAB="$(printf '\t')"
EOL='
'


################################# Functions ####################################

trace()
{
  if [ -n "$TRACEFILE" ]; then
   ((PS4='' ; set -x ; : "$@" >/dev/null) 2>&1 ) | sed 's/^: //' >> $TRACEFILE
  else
    "$@"
  fi
}


# Find command path with '/hint/path/command' as the argument
find_command()
{
  if [ -x "$1" ]; then
    echo "$1"
  else
    which $(basename "$1") 2>/dev/null
  fi
}


# Check whether a certain command is available
check_command()
{
  local path

  IFS=' '
  for cmd in $*; do
    case "$cmd" in
      /*) path="" ;;
      ip|tc|modprobe|sysctl) path="/sbin/" ;;
      sed|cat|date|uname) path="/bin/" ;;
      *) path="/usr/bin/" ;;
    esac

    if [ -x "$path$cmd" ]; then
      return 0
    fi

    if which "$cmd" >/dev/null 2>&1; then
      return 0
    fi
  done
  
  return 1
}


# Check whether a binary is available and if not, generate an error and stop program execution
check_command_error()
{
  if ! check_command "$@"; then
    printf "\033[40m\033[1;31mERROR  : Command(s) \"$(echo "$@" |tr ' ' '|')\" is/are not available!\033[0m\n" >&2
    printf "\033[40m\033[1;31m         Please investigate. Quitting...\033[0m\n" >&2
    echo ""
    exit 2
  fi
}


# Check whether a binary is available and if not, generate a warning but continue program execution
check_command_warning()
{
  check_command "$@"
  local retval=$?

  if [ "$retval" != "0" ]; then
    printf "\033[40m\033[1;31mWARNING: Command(s) \"$(echo "$@" |tr ' ' '|')\" is/are not available!\033[0m\n" >&2
    printf "\033[40m\033[1;31m         Please investigate. This *may* be a problem!\033[0m\n" >&2
    echo ""
  fi

  return $retval
}


check_binary()
{
  printf "\033[40m\033[1;31mWARNING: Function check_binary() will soon be deprecated, please use check_command_error() instead!\033[0m\n" >&2
  check_command_error "$@"
}


# Check if the current kernel is at least a certain version (or newer)
# Arguments: major minor rev (eg. "2 6 25")
# Return   : 0 = kernel is equal or newer, 1 = kernel is older
######################################################################
kernel_ver_chk()
{
  local maj min rev ver

  if [ -n "$2" ]; then
    maj="$1"
    min="$2"
    rev="$3"
  else
    maj=$(echo "$1" |cut -s -d'.' -f1)
    min=$(echo "$1" |cut -s -d'.' -f2)
    rev=$(echo "$1" |cut -s -d'.' -f3)
  fi

  ver=$(uname -r |cut -s -d'-' -f1)
  if [ $(echo "$ver" |cut -s -d'.' -f1) -lt $maj ]; then
    return 1
  fi

  if [ $(echo "$ver" |cut -s -d'.' -f2) -lt $min ]; then
    return 1
  fi

  if [ $(echo "$ver" |cut -s -d'.' -f3) -lt $rev ]; then
    return 1
  fi

  return 0
}



# Linecount function
lc()
{
  wc -l |awk '{ print $1 }'
}


note_iptables_error()
{
  unset IFS
  for arg in $*; do
    if [ "$arg" = "-A" ] || [ "$arg" = "-I" ]; then
      return 0
    fi
  done

  return 1
}


iptables()
{
  result=`trace $IPTABLES "$@" 2>&1`
  retval=$?

  if [ "$retval" != "0" ]; then
    # Show any (error) messages in red
    printf "\033[40m\033[1;31m$IPTABLES: ($retval) $result\033[0m\n" >&2
    if note_iptables_error "$@"; then
      IPTABLES_ERROR=1
    fi
  elif [ -n "$result" ]; then
    echo "${INDENT}$result"
  fi

  return $retval
}


ip4tables()
{
  result=`trace $IP4TABLES "$@" 2>&1`
  retval=$?

  if [ "$retval" != "0" ]; then
    # Show any (error) messages in red
    printf "\033[40m\033[1;31m$IP4TABLES: ($retval) $result\033[0m\n" >&2
    if note_iptables_error "$@"; then
      IPTABLES_ERROR=1
    fi
  elif [ -n "$result" ]; then
    echo "${INDENT}$result"
  fi

  return $retval
}


ip6tables()
{
  result=`trace $IP6TABLES "$@" 2>&1`
  retval=$?

  if [ "$retval" != "0" ]; then
    # Show any (error) messages in red
    printf "\033[40m\033[1;31m$IP6TABLES: ($retval) $result\033[0m\n" >&2
    if note_iptables_error "$@"; then
      IPTABLES_ERROR=1
    fi
  elif [ -n "$result" ]; then
    echo "${INDENT}$result"
  fi

  return $retval
}


iptables_save()
{
  $IPTABLES_SAVE "$@"
  retval=$?

  if [ "$retval" != "0" ]; then
    # Show any (error) messages in red
    printf "\033[40m\033[1;31m$IPTABLES_SAVE: Error ($retval) \033[0m\n" >&2
    IPTABLES_ERROR=1
  fi

  return $retval
}


iptables_restore()
{
  result=`$IPTABLES_RESTORE "$@" 2>&1`
  retval=$?

  if [ "$retval" != "0" ]; then
    # Show any (error) messages in red
    printf "\033[40m\033[1;31m$IPTABLES_RESTORE: ($retval) $result\033[0m\n" >&2
    IPTABLES_ERROR=1
  elif [ -n "$result" ]; then
    echo "${INDENT}$result"
  fi

  return $retval
}


# Wrapper function for modprobe
###############################
modprobe()
{
  # Module support available?
  if [ -e /proc/modules ]; then
    # Make sure environment variable is not set
    MODPROBE_OPTIONS=""

    result=`trace $MODPROBE $@ 2>&1`
    retval=$?

    if [ "$retval" != "0" ]; then
      if ! echo "$result" |grep -q -e '^FATAL: Module .* not found'; then
        # Show any (error) messages in red
        printf "\033[40m\033[1;31m${MODPROBE} $@: ($retval) $result\033[0m\n" >&2
      elif [ "$COMPILED_IN_KERNEL_MESSAGES" != "0" ]; then
        printf "\033[40m\033[1;31m${MODPROBE} $@: Module not found! Assuming compiled-in-kernel!\033[0m\n" >&2
      fi
      return $retval
    else
      if echo "$result" |grep -q -e '^WARNING:'; then
        # Show any (warning) messages in red
        printf "\033[40m\033[1;31m${MODPROBE} $@: $result\033[0m\n" >&2
      else
        echo "${INDENT}Loaded kernel module $1. $result"
      fi
      return 0
    fi
  elif [ "$COMPILED_IN_KERNEL_MESSAGES" != "0" ]; then
    echo "${INDENT}NOTE: Kernel has no module support. Assuming compiled-in-kernel for module \"$1\""
  fi

  return 0
}


# Multi modprobe - Modprobe different modules until one succeeds
modprobe_multi()
{
  local OPTIONS=
  local MODULES=
  # Split options and modules
  while [ -n "$1" ]; do
    case "$1" in
      -*) OPTIONS="$OPTIONS${OPTIONS:+ }$1";;
       *) MODULES="${MODULES}${MODULES:+ }$1";;
    esac
    shift
  done


  # Module support available?
  if [ -e /proc/modules ]; then
    # Make sure environment variable is not set
    MODPROBE_OPTIONS=""

    local retval
    local result
    
    IFS=' '
    for module in $MODULES; do
      local modprobe_commandline="$MODPROBE"
      if [ -n "$OPTIONS" ]; then
        modprobe_commandline="$modprobe_commandline $OPTIONS"
      fi
      modprobe_commandline="$modprobe_commandline $module"

      result=`trace $modprobe_commandline 2>&1`
      retval=$?

      if [ "$retval" != "0" ]; then
        if ! echo "$result" |grep -q -e '^FATAL: Module .* not found'; then
          # Show any (error) messages in red
          printf "\033[40m\033[1;31m${modprobe_commandline}: ($retval) $result\033[0m\n" >&2
        fi
      else
        if echo "$result" |grep -q -e '^WARNING:'; then
          # Show any (warning) messages in red
          printf "\033[40m\033[1;31m${modprobe_commandline}: $result\033[0m\n" >&2
        else
          echo "${INDENT}Loaded kernel module $module. $result"
        fi
        return 0
      fi
    done
    if [ "$COMPILED_IN_KERNEL_MESSAGES" != "0" ]; then
      printf "\033[40m\033[1;31mWARNING: ($retval) Module(s) \"$(echo "$MODULES" |tr ' ' '|')\" failed to load. Assuming compiled-in-kernel!\033[0m\n" >&2
      return 1
    fi
  elif [ "$COMPILED_IN_KERNEL_MESSAGES" != "0" ]; then
    echo "${INDENT}NOTE: Kernel has no module support. Assuming compiled-in-kerel for module(s) \"$(echo "$MODULES" |tr ' ' '|')\""
  fi
  
  return 0
}



# Legacy function
#################
module_probe()
{
  printf "\033[40m\033[1;31mWARNING: Function module_probe() will soon be deprecated, please use modprobe() instead!\033[0m\n" >&2
  modprobe "$@"
  return $?
}


# sysctl binary wrapper
#######################
sysctl()
{
  result=`trace $SYSCTL "$@" 2>&1`
  retval=$?

  if [ "$retval" != "0" ]; then
    # Show any (error) messages in red
    printf "\033[40m\033[1;31msysctl $@: ($retval) $result\033[0m\n" >&2
    return $retval
  fi
  echo "${INDENT}sysctl $@"
  return 0
}


# Multi sysctl - Try sysctl-variables until one succeeds
sysctl_multi()
{
  local OPTIONS=
  local VARIABLES=

  while [ -n "$1" ]; do
    # Combine options and exit on first non-option
    case "$1" in
      -*) OPTIONS="${OPTIONS}${OPTIONS:+ }$1";;
       *) VARIABLES="${VARIABLES}${VARIABLES:+ }$1";;
    esac
    shift
  done

  IFS=' '
  for variable in $VARIABLES; do
    if $SYSCTL "$(echo "$variable" |cut -d'=' -f1)" >/dev/null 2>&1; then
      local sysctl_commandline="$SYSCTL"
      if [ -n "$OPTIONS" ]; then
        sysctl_commandline="$sysctl_commandline $OPTIONS"
      fi
      sysctl_commandline="$sysctl_commandline $variable"

      result=`trace $sysctl_commandline 2>&1`
      retval=$?

      if [ "$retval" = "0" ]; then
        echo "${INDENT}${sysctl_commandline}"
        return 0
      else
        printf "\033[40m\033[1;31m${sysctl_commandline}: ($retval) $result\033[0m\n" >&2
      fi
    fi
  done
  printf "\033[40m\033[1;31mERROR: Unable to find kernel parameters \"$(echo "$VARIABLES" |tr ' ' '|')\"!\033[0m\n" >&2
  return 1
}


# tc binary wrapper
###################
tc()
{
  trace $TC "$@"
}


# ip binary wrapper
###################
ip()
{
  trace $IP "$@"
}


# dig binary wrapper
####################
dig()
{
  local cnt=0 x=0 addr name line lines IFS retval

  if [ -n "$DIG" ]; then
    if [ "$DNS_FAST_FAIL" = "1" ]; then
      lines="$($DIG +short +tries=1 +time=1 "$@" 2>/dev/null)"
      retval=$?
    else
      lines="$($DIG +short "$@" 2>/dev/null)"
      retval=$?
    fi
    
    while [ $# -gt 1 ]; do
      if [ "$1" = "-x" ]; then
        x=1
      fi
      shift
    done
    IFS=$EOL
    for line in $lines; do
      case "$line" in
        ';'*|'') addr=""
                 name=""
                 ;;
        *'.') addr=""
              name="$line"
              ;;
        *) addr="$line"
           name=""
           ;;
      esac
      if [ -n "$addr" -a "$x" = 0 ]; then
        echo "$addr"
        return 0
      elif [ -n "$name" -a "$x" = 1 ]; then
        echo "$name"
        return 0
      fi
    done
    return $retval
  elif [ -n "$NSLOOKUP" ]; then
    while [ $# -gt 1 ]; do
      if [ "$1" = "-x" ]; then
        x=1
      fi
      shift
    done
    if [ -n "$1" ]; then
      lines="$($NSLOOKUP "$1" 2>/dev/null)"
      IFS=$EOL
      for line in $lines; do
        cnt=$((cnt + 1))
        if [ $cnt -gt 2 ]; then
          case "$line" in
            Address*) addr="$(echo "$line" |sed -e 's/^Address.*: *//' -e 's/ .*$//')"
                      name="$(echo "$line" |sed -e 's/^.* //')"
                      ;;
            *'canonical name = '*) addr=""
                                   name=""
                                   ;;
            *'name = '*) addr="$1"
                         name="$(echo "$line" |sed -e 's/^.*name = *//' -e 's/ .*$//')"
                         ;;
            *) addr=""
               name=""
               ;;
          esac
          if [ -n "$addr" -a "$x" = 0 ]; then
            echo "$addr"
            return 0
          elif [ -n "$name" -a "$x" = 1 ]; then
            echo "$name"
            return 0
          fi
        fi
      done
      return 9
    fi
    return 1
  else
    return 9
  fi
}


# Helper function to expand out wildcards in interface name list
wildcard_ifs()
{
  local expnd if0 if1 IFS

  expnd=""

  IFS=', '
  for if0 in $*; do
    if1="$if0"
    case $if1 in
    *+)
      if1="${if1/%+/}"
      if1=`$IP link | awk "\\$2 ~ /${if1}[0-9]+:/ { print substr(\\$2, 1, length(\\$2)-1); }"`
      if [ -z "$if1" ]; then
        echo "wildcard: $if0 unmatched!" >&2
        continue
      fi
      ;;
    esac
    expnd="$expnd${expnd:+ }$if1"
  done
  echo "$expnd"
}


# Helper function to get interface(s) from variable
get_ifs()
{
  local result=""
  
  if echo "$1" |grep -q -e "$SEP2"; then
    result=`echo "$1" |cut -s -d"$SEP2" -f1 |grep -v -e '\.' -e "$ANYHOST" |tr ' ' ','`
  fi

  if [ -n "$result" ]; then
    echo "$result"
    return 0
  else
    if [ -n "$2" ]; then
      echo "$2"
    else
      echo "+"
    fi
    return 1
  fi
}


# Helper function to get source/destination IP(s) from variable
get_ips()
{
  local result=""

  if echo "$1" |grep -q -e "$SEP2"; then
    result=`echo "$1" |cut -s -d"$SEP2" -f1 |grep -e '\.' -e "$ANYHOST" |tr ' ' ','`
  fi

  if [ -n "$result" ]; then
    echo "$result"
    return 0
  else
    if [ -n "$2" ]; then
      echo "$2"
    else
      echo "$ANYHOST"
    fi
    return 1
  fi
}


# Helper function to get hostname(s) from variable (ifs|ips#hosts)
get_hosts_ih()
{
  local result="$(echo "$1" |sed "s!^.*$SEP2!!")"

  if [ -n "$result" ]; then
    echo "$result"
    return 0;
  else
    echo "$2"
    return 1
  fi
}


# Helper function to get hostname(s) from variable (ifs|ips#hosts~ports|protos)
get_hosts_ihp()
{
  local result="$(echo "$1" |sed "s!^.*$SEP2!!" |cut -s -d"$SEP" -f1)"
  
  if [ -n "$result" ]; then
    echo "$result"
    return 0
  else
    echo "$2"
    return 1
  fi
}


# Helper function to get port(s) from variable (ifs|ips#hosts~ports|protos)
get_ports_ihp()
{
  local result="$(echo "$1" |sed "s!^.*$SEP2!!")"

  if echo "$result" |grep -q -e "$SEP"; then
    echo "$result" |cut -s -d"$SEP" -f2 |tr '-' ':'
    return 0
  elif [ -n "$2" ]; then
    # Use default, if specified
    echo "$2"
    return 1
  else
    # When we have no seperator, assume port(s) only and no host(s)
    echo "$result" |tr '-' ':'
    return 0
  fi
}


# Helper function to get hostname(s) from variable (hosts~ports|protos)
get_hosts_hp()
{
  local result="$(echo "$1" |sed "s!^.*$SEP2!!")"

  if echo "$result" |grep -q -e "$SEP"; then
    echo "$result" |cut -s -d"$SEP" -f1
    return 0
  elif [ -n "$2" ]; then
    # Use default, if specified
    echo "$2"
    return 1
  else
    # When we have no seperator, assume host(s) only and no port(s)
    echo "$result"
    return 0
  fi
}


# Helper function to get port(s) from variable (hosts~ports|protos)
get_ports_hp()
{
  local result="$(echo "$1" |sed "s!^.*$SEP2!!")"

  if echo "$result" |grep -q -e "$SEP"; then
    echo "$result" |cut -s -d"$SEP" -f2 |tr '-' ':'
    return 0
  else
    echo "$2"
    return 1
  fi
}


# Helper function to get port(s) from variable (ifs|ips#ports|protos)
get_ports_ip()
{
  local result="$(echo "$1" |sed "s!^.*$SEP2!!")"

  if [ -n "$result" ]; then
    echo "$result" |tr '-' ':'
    return 0
  else
    echo "$2"
    return 1
  fi
}


# Is IPv4 numeric?
is_numeric_ipv4()
{
  if [ "$(echo "$1" |tr -d '0123456789/')" = "..." ]; then
    return 0
  fi

  return 1
}


# Is IPv6 numeric?
is_numeric_ipv6()
{
  case $(echo "$1" |tr -d '0123456789aAbBcCdDeEfF/') in
   ::|:::|::::|:::::|::::::|:::::::)
     return 0
     ;;
  esac

  return 1
}


# Is IPv4 numeric?
is_numeric_ipv4_address()
{
  if [ "$(echo "$1" |tr -d '0123456789')" = "..." ]; then
    return 0
  fi

  return 1
}


# Is IPv6 numeric?
is_numeric_ipv6_address()
{
  case $(echo "$1" |tr -d '0123456789aAbBcCdDeEfF') in
   ::|:::|::::|:::::|::::::|:::::::)
     return 0
     ;;
  esac

  return 1
}

# Helper function to resolve an IP to a DNS name
# $1 = IP. $2 (optional) = Additional arguments for dig. stdout = DNS name
gethostbyaddr()
{
  local host="$1" result retval=0

  # We can't resolve addresses with a subnet mask
  case "$host" in
    */*) return 1 ;;
  esac

  # Don't try to resolve DNS names:
  if ! is_numeric_ipv4 "$host" && ! is_numeric_ipv6 "$host"; then
    # It's a DNS name already, so just return it
    echo "$host"
    return 0
  fi
  
  shift
  result="$(dig -x "$@" "$host")"
  retval=$?

  if [ "$retval" = "0" ]; then
    if [ -n "$result" ]; then
      echo "$result"
      return 0
    else
      return 1
    fi
  else
    return $retval
  fi
}


# Helper function to resolve a DNS name to an IP
# $1 = Hostname. $2 (optional) = Additional arguments for dig. stdout = IP
gethostbyname()
{
  local host="$1" result retval=0

  # Don't try to resolve IPs:
  if is_numeric_ipv4 "$host" || is_numeric_ipv6 "$host"; then
    # It's an IP already, so just return it
    echo "$host"
    return 0
  fi
  
  shift
  result="$(dig "$@" "$host")"
  retval=$?

  if [ "$retval" = "0" ]; then
    if [ -n "$result" ]; then
      echo "$result"
      return 0
    else
      return 1
    fi
  else
    return $retval
  fi
}


# Helper function to show (resolved) ip~hostname
show_hostname()
{
  local hostname=""
  local FIRST=0
  
  IFS=' ,'
  # Argument(s) contains IP(s)
  for host in $1; do
    if [ "$RESOLV_IPS" = "1" ]; then
      hostname=`gethostbyaddr "$host"`
    else
      hostname=""
    fi

    if [ "$FIRST" = "0" ]; then
      FIRST=1
    else
      printf ","
    fi

    if [ -n "$hostname" ]; then
      printf "$host=$hostname"
    else
      printf "$host"
    fi
  done
}


# Helper function to show interfaces / ips in front of verbose line
# $1 =  interfaces. $2 = ips
show_if_ip()
{
  # Only show interfaces if not empty:
  if [ -n "$1" ] && [ "$1" != "+" ]; then
    printf "($1) "
  fi

  # Only show destination IPs if not empty:
  if [ -n "$2" ] && [ "$2" != "$ANYHOST" ]; then
    printf "($2) "
  fi
}


# Helper function to show hosts:ports
# $1 = host. $2 = ports
show_hosts_ports()
{
  # Only show interfaces if not empty:
  if [ -n "$1" ]; then
    printf "$1:$2"
  else
    printf "$2"
  fi
}


# Helper function to translate host ranges from variable
ip_range()
{
  # Return the args if there is no '-' for improved execution speed
  case "$@" in
    *-*) ;;
      *) echo "$@"; return;;
  esac

  FIRST=1

  IFS=','
  # Get variable from commandline
  for item in $*; do
    # Check whether an IP range was specified (only works like w.x.y.z1-z2!):
    start="$(echo "$item" |cut -s -d'-' -f1 |awk -F'.' '{ print $NF }' |grep -e '[0-9]')"
    host_base="$(echo "$item" |cut -s -d'-' -f1 |awk -F'.' '{ for (i=1; i<NF; i++) printf ("%s.",$i) }')"
    stop="$(echo "$item" |cut -s -d'-' -f2 |grep -e '[0-9]')"

    if [ -n "$stop" ] && [ -n "$start" ]; then
      unset IFS
      for x in `seq -s' ' $start $stop`; do
        if [ "$FIRST" = "1" ]; then
          FIRST=0
        else
          printf ","
        fi
        printf "$host_base$x"
      done
    else
      if [ "$FIRST" = "1" ]; then
        FIRST=0
      else
        printf ","
      fi
      printf "$item"
    fi
  done
}

# Add iptables rules in batch using iptables-save and iptables-restore
iptables_batch()
{
  local ARGS CHAIN CHAINFILE RESULT=0
  
  # Args must be of the form and called in this order:
  #   start
  #   init CHAIN
  #   -A CHAIN ...
  #   apply CHAIN
  #   stop
  #
  # Note: the added rules will be placed after a required
  #       pre-existing rule in CHAIN.
  #
  ARGS="$@"
  CHAIN="$2"
  
  if [ "$DISABLE_IPTABLES_BATCH" = "1" ]; then
    if [ "$1" = "-A" ]; then
      iptables "$@"
    fi
    return
  fi
  
  if [ -n "$CHAIN" ]; then
    CHAINFILE="$IPTABLES_BATCH_FILE"_"$CHAIN"
    if [ "$1" = "-A" ]; then
      echo "$ARGS" >> "$CHAINFILE"
    elif [ "$1" = "init" ]; then
      rm -f "$CHAINFILE"
    elif [ "$1" = "apply" ]; then
      sed -i "/^-A $CHAIN / r $CHAINFILE" "$IPTABLES_BATCH_FILE"
      iptables_restore < "$IPTABLES_BATCH_FILE"
      RESULT=$?
      rm -f "$CHAINFILE"
    else
      RESULT=1
    fi
  else
    if [ "$1" = "start" ]; then
      iptables_save -t filter > "$IPTABLES_BATCH_FILE"
      RESULT=$?
    elif [ "$1" = "stop" ]; then
      rm -f "$IPTABLES_BATCH_FILE"
    else
      RESULT=1
    fi
  fi
  
  return $RESULT
}

# Display progress bar, 0% to 100% in 2% increments
progress_bar()
{
  # Args: cur_cnt total_cnt
  local prev

  if [ $2 -gt 0 ]; then
    if [ $1 -eq 0 ]; then
      progress_percent=0
      printf " 0%%"
    else
      cur=$(($1 / $2))
      if [ $progress_percent -lt $cur ]; then
        prev=$progress_percent
        while [ $prev -le $cur ]; do
          if [ $progress_percent -lt $prev ]; then
            progress_percent=$prev
            if [ $(($progress_percent % 20)) -eq 0 ]; then
              printf "$progress_percent%%"
            else
              printf "."
            fi
          fi
          prev=$(($prev + 2))
        done
      fi
    fi
  fi
}


# Check existance of an interface
check_interface()
{
  local interface IFS

  unset IFS
  for interface in $(ip -o link show | cut -d':' -f2); do
    case "$1" in
      # Wildcard interface?
      *+) if [ "${1%+}" = "${interface%%[0-9]*}" ]; then
            return 0
          fi
          ;;
       *) if [ "${1}" = "${interface%@*}" ]; then
            return 0
          fi
          ;;
    esac
  done

  # Interface not found
  return 1
}

# Get IP address/mask of specified network interface
get_network_ipv4_address_mask()
{
  ip -o addr show dev "$1" 2>/dev/null \
    |awk '$3 == "inet" { print $4; exit; }'
  return $?
}

# Get IP address of the specified network interface
get_network_ipv4_address()
{
  get_network_ip_mask |cut -f1 -d'/'
  return $?
}

# Get netmask of the specified network interface 
get_network_ipv4_mask()
{
  get_network_ip_mask |cut -f2 -d'/'
  return $?
}

# Get broadcast address of the specified network interface 
get_network_ipv4_broadcast()
{
  ip -o addr show dev "$1" 2>/dev/null \
    |awk '$3 == "inet" && $5 == "brd" { print $6; exit; }'
  return $?
}

# Get IPv6 address/mask of specified network interface
get_network_ipv6_address_mask()
{
  ip -o addr show dev "$1" 2>/dev/null \
    |awk '$3 == "inet6" { print $4; exit; }'
  return $?
}

# Get IPv6 address of the specified network interface
get_network_ipv6_address()
{
  get_network_ipv6_address_mask |cut -f1 -d'/'
  return $?
}


# Get IPv6 netmask of the specified network interface 
get_network_ipv6_mask()
{
  get_network_ipv6_address_mask |cut -f2 -d'/'
  return $?
}

################################# Main ####################################

# Set base file for iptables_batch
IPTABLES_BATCH_FILE="/var/tmp/aif_iptables_batch"

# Set file to store which plugins are loaded
PLUGIN_LOAD_FILE="/var/tmp/aif_active_plugins"

# Check whether we also need to drop messages in a dedicated firewall log file
if [ -z "$FIREWALL_LOG" ]; then
  FIREWALL_LOG="/dev/null"
fi

# Check for a local/global config file
######################################
if [ -e "$LOCAL_CONFIG_FILE" ]; then
  . "$LOCAL_CONFIG_FILE"
fi

# if $LOGLEVEL is not set, default to "info"
############################################
if [ -z "$LOGLEVEL" ]; then
  LOGLEVEL="info"
fi

# Detect all binaries
#####################
if [ -z "$IP6TABLES" ]; then
  IP6TABLES="$(find_command /sbin/ip6tables)"
fi

if [ -z "$IP4TABLES" ]; then
  IP4TABLES="$(find_command /sbin/iptables)"
fi

IP="$(find_command /sbin/ip)"

TC="$(find_command /sbin/tc)"

SYSCTL="$(find_command /sbin/sysctl)"

MODPROBE="$(find_command /sbin/modprobe)"

DIG="$(find_command /usr/bin/dig)"

if [ -z "$DIG" ]; then
  NSLOOKUP="$(find_command /usr/bin/nslookup)"
fi

if [ "$IPV6_SUPPORT" = "1" ]; then
  IPTABLES="$IP6TABLES"
  ICMP_PROTO="icmpv6"
  ICMP_TYPE="--icmpv6-type"
  
  # If IPv6 support is enabled some options should be forced off as they
  # are simply not supported (yet) by ip6tables
  unset TTL_INC NAT PACKET_TTL NAT_FORWARD_TCP NAT_FORWARD_UDP NAT_FORWARD_IP
else
  IPTABLES="$IP4TABLES"
  ICMP_PROTO="icmp"
  ICMP_TYPE="--icmp-type"
fi

IPTABLES_SAVE="$(find_command "$IPTABLES"-save)"

IPTABLES_RESTORE="$(find_command "$IPTABLES"-restore)"

# check for tracing
###################
if [ "$TRACE" = "1" ]; then
  TRACEFILE="/tmp/aif-trace.`date '+%Y%m%d-%H:%M:%S'`"
  cp /dev/null $TRACEFILE
fi

# Default NAT_INTERNAL_NET to INTERNAL_NET, if not specified
############################################################
if [ -z "$NAT_INTERNAL_NET" ]; then
  NAT_INTERNAL_NET="$INTERNAL_NET"
fi

# Check plugin bin path and fallback in case it's empty
#######################################################
if [ -z "$PLUGIN_BIN_PATH" ]; then
  if [ -d "/usr/local/share/arno-iptables-firewall/plugins" ]; then
    PLUGIN_BIN_PATH="/usr/local/share/arno-iptables-firewall/plugins"
  elif [ -d "/usr/share/arno-iptables-firewall/plugins" ]; then
    PLUGIN_BIN_PATH="/usr/share/arno-iptables-firewall/plugins"
  fi
fi

# Required for legacy plugins:
PLUGIN_PATH="$PLUGIN_BIN_PATH"

# Check plugin bin path and fallback in case it's empty
#######################################################
if [ -z "$PLUGIN_CONF_PATH" ]; then
  if [ -d "/etc/arno-iptables-firewall/plugins" ]; then
    PLUGIN_CONF_PATH="/etc/arno-iptables-firewall/plugins"
  fi
fi
