#!/bin/bash

# The plugin configuration file
###############################
PLUGIN_CONF_FILE="dyndns-host-open.conf"

# Location of the main configuration file for the firewall
##########################################################
CONFIG_FILE=/etc/arno-iptables-firewall/firewall.conf

# Check if the main config file exists and if so load it
########################################################
if [ -e "$CONFIG_FILE" ]; then
  . $CONFIG_FILE
else
  echo "** ERROR: Could not read configuration file $CONFIG_FILE!" >&2
  echo "**        Please, check the file's location and (root) rights." >&2
  exit 2
fi

# Check if the environment file exists and if so, load it
#########################################################
if [ -n "$ENV_FILE" ]; then
  . "$ENV_FILE"
else
  if [ -f /usr/local/share/arno-iptables-firewall/environment ]; then
    . /usr/local/share/arno-iptables-firewall/environment
  else
    if [ -f /usr/share/arno-iptables-firewall/environment ]; then
      . /usr/share/arno-iptables-firewall/environment
    else
      echo "** ERROR: The environment file (ENV_FILE) has not been specified" >&2
      echo "**        in the configuration file. Try upgrading your config-file!" >&2
      exit 2
    fi
  fi
fi

# Define some global variables
INDENT='   '

# If HOST_CACHE_FILE is not defined, fallback to old DYNDNS variable
if [ -n "$HOST_CACHE_FILE" ]; then
  DYNDNS_HOST_CACHE="$HOST_CACHE_FILE"
else
  DYNDNS_HOST_CACHE="/var/tmp/aif_dyndns_host_cache"
fi

# Check sanity of environment
sanity_check()
{
  if [ -z "$DYNDNS_HOST_OPEN_TCP" -a -z "$DYNDNS_HOST_OPEN_UDP" -a \
       -z "$DYNDNS_HOST_OPEN_IP"  -a -z "$DYNDNS_HOST_OPEN_ICMP" -a \
       -z "$DYNDNS_HOST_MISC" ] || [ -z "$DYNDNS_HOST_OPEN_CRON" ]; then
    echo "** ERROR: The plugin config file is not (properly) setup!" >&2
    return 1
  fi

  # Check whether chain exists
  if ! ip4tables -nL DYNDNS_CHAIN >/dev/null 2>&1; then
    echo "** ERROR: DYNDNS_CHAIN does not exist! **" >&2
    return 1
  fi

  # Check if chain is inserted in the main chains
  if ! ip4tables -nL EXT_INPUT_CHAIN |grep -q '^DYNDNS_CHAIN '; then
    echo "** ERROR: DYNDNS_CHAIN is not inserted in the EXT_INPUT_CHAIN chain! **" >&2
    return 1
  fi

  if ! check_command dig nslookup; then
    echo "** ERROR: Required command dig (or nslookup) is not available!" >&2
    return 1
  fi

  return 0
}


# Resolve hostname to an IP and store in our (new) cache
# Arguments : $1 = hostname to resolve
# Returns   : Resolved host's IP in "$host_ip"
dyndns_get_host_cached()
{
  host_ip=""
  local host="$1"
  local retval=0
  
  # Don't try to resolve stuff that's already numeric
  if is_numeric_ip "$host"; then
    host_ip="$host"
    return 0
  fi

  # Check whether we already have it in our (new) cache
  host_ip=`grep "^$host " -m1 "${DYNDNS_HOST_CACHE}.new" |cut -s -f2 -d' '`
  if [ -n "$host_ip" ]; then
    return 0
  fi
  
  printf "${INDENT}Resolving host \"$host\" -> "

  DNS_FAST_FAIL_ONCE="$DYNDNS_DNS_FAST_FAIL"
  host_ip=`gethostbyname "$host"`
  retval=$?
    
  if [ -z "$host_ip" -o $retval -ne 0 ]; then
    # Try to get from (old) cache, if allowed
    if [ "$DYNDNS_OLD_CACHE_FALLBACK" = "1" ]; then
      host_ip=`grep "^$host " -m1 "${DYNDNS_HOST_CACHE}" |cut -s -f2 -d' '`
    fi
    
    # (Re)check $host_ip
    if [ -z "$host_ip" ]; then
      printf "\033[40m\033[1;31mFAILED!\n\033[0m"
      echo "** ERROR($retval): Unresolvable host \"$host\", and no old IP to fallback on! **" >&2
    else
      echo "$host_ip (cached)"
      echo "** WARNING($retval): Unresolvable host \"$host\". Re-using old IP ($host_ip)! **" >&2
    fi
  else
    echo "$host_ip"
  fi
  
  echo "$host $host_ip" >>"${DYNDNS_HOST_CACHE}.new"
  
  return $retval
}


dyndns_host_open()
{
  # Flush the DYNDNS_CHAIN
  iptables -F DYNDNS_CHAIN

  # Add TCP ports to allow for certain hosts
  ##########################################
  unset IFS
  for rule in $DYNDNS_HOST_OPEN_TCP; do
    if parse_rule "$rule" DYNDNS_HOST_OPEN_TCP "interfaces-destips-hosts-ports"; then

      echo "${INDENT}$(show_if_ip "$interfaces" "$destips")Allowing $hosts for TCP port(s): $ports"
    
      IFS=','
      for host in $hosts; do
         dyndns_get_host_cached $host # Returns $host_ip
         for interface in $interfaces; do
          for destip in $destips; do
            for port in $ports; do
              iptables -A DYNDNS_CHAIN -i $interface -s $host_ip -d $destip -p tcp --dport $port -j ACCEPT
            done
          done
        done
      done
    fi
  done


  # Add UDP ports to allow for certain hosts
  ##########################################
  unset IFS
  for rule in $DYNDNS_HOST_OPEN_UDP; do
    if parse_rule "$rule" DYNDNS_HOST_OPEN_UDP "interfaces-destips-hosts-ports"; then

      echo "${INDENT}$(show_if_ip "$interfaces" "$destips")Allowing $hosts for UDP port(s): $ports"
    
      IFS=','
      for host in $hosts; do
        dyndns_get_host_cached $host # Returns $host_ip
        for interface in $interfaces; do
          for destip in $destips; do
            for port in $ports; do
              iptables -A DYNDNS_CHAIN -i $interface -s $host_ip -d $destip -p udp --dport $port -j ACCEPT
            done
          done
        done
      done
    fi
  done


  # Add IP protocols to allow for certain hosts
  #############################################
  unset IFS
  for rule in $DYNDNS_HOST_OPEN_IP; do
    if parse_rule "$rule" DYNDNS_HOST_OPEN_IP "interfaces-destips-hosts-protos"; then

      echo "${INDENT}$(show_if_ip "$interfaces" "$destips")Allowing $hosts for IP protocol(s): $protos"
    
      IFS=','
      for host in $hosts; do
        dyndns_get_host_cached $host # Returns $host_ip
        for interface in $interfaces; do
          for destip in $destips; do
            for proto in $protos; do
              iptables -A DYNDNS_CHAIN -i $interface -s $host_ip -d $destip -p $proto -j ACCEPT
            done
          done
        done
      done
    fi
  done


  # Add ICMP to allow for certain hosts
  #####################################
  unset IFS
  for rule in $DYNDNS_HOST_OPEN_ICMP; do
    if parse_rule "$rule" DYNDNS_HOST_OPEN_ICMP "interfaces-destips-hosts"; then

      echo "${INDENT}$(show_if_ip "$interfaces" "$destips")Allowing $hosts for ICMP-requests(ping)"
    
      IFS=','
      for host in $hosts; do
        dyndns_get_host_cached $host # Returns $host_ip
        for interface in $interfaces; do
          for destip in $destips; do
            iptables -A DYNDNS_CHAIN -i $interface -s $host_ip -d $destip -p icmp --icmp-type echo-request -j ACCEPT
          done
        done
      done
    fi
  done
  
  # Store additional hosts in our cache, although this is a no-op for this
  # plugin, it does allow use of our name-cache by eg. other (aware) plugins
  unset IFS
  for host in $DYNDNS_HOST_MISC; do
    dyndns_get_host_cached $host
  done
}


############
# Mainline #
############

# Check where to find the config file
CONF_FILE=""
if [ -n "$PLUGIN_CONF_PATH" ]; then
  CONF_FILE="$PLUGIN_CONF_PATH/$PLUGIN_CONF_FILE"
fi

# Check if the config file exists
if [ ! -e "$CONF_FILE" ]; then
  echo "** ERROR: Config file \"$CONF_FILE\" not found! **" >&2
  exit 1
else
  # Source the plugin config file
  . "$CONF_FILE"

  if [ "$ENABLED" = "1" ]; then
    # Only proceed if environment ok
    if sanity_check; then
      # This is a critical section so we use a lockfile
      lockfile="/var/tmp/aif_dyndns_helper.lock"
      if ( set -o noclobber; echo "$$" > "$lockfile") 2> /dev/null; then
        # Setup int handler
        trap 'rm -f "$lockfile"; exit $?' INT TERM EXIT

        # Create new empty file
        printf "" >"${DYNDNS_HOST_CACHE}.new"
  
        # Parse rules
        dyndns_host_open;
        
        # Remove old cache file
        rm -f "$DYNDNS_HOST_CACHE"
  
        # Make our new cache file active
        mv "${DYNDNS_HOST_CACHE}.new" "$DYNDNS_HOST_CACHE"

        # Remove lockfile
        rm -f "$lockfile"

        # Disable int handler
        trap - INT TERM EXIT
        
        exit 0
      else
        echo "Failed to acquire lockfile: $lockfile." >&2
        echo "Held by $(cat $lockfile)" >&2
      fi
    fi
  fi
fi

exit 1
