#!/bin/sh

# 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
DYNDNS_HOST_CACHE="/var/tmp/aif-dyndns-host-cache"
DNS_SERVER_FAILURE=0
INDENT='   '

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

  if [ -z "$DYNDNS_HOST_OPEN_TCP" ] && [ -z "$DYNDNS_HOST_OPEN_UDP" ] && [ -z "$DYNDNS_HOST_OPEN_IP" ] && [ -z "$DYNDNS_HOST_OPEN_ICMP" ]; then
    echo "** ERROR: The plugin config file is not (properly) setup!" >&2
    return 1
  fi

  # Check whether chain exists
  if ! iptables -nL DYNDNS_CHAIN >/dev/null 2>&1; then
    echo "** ERROR: DYNDNS_CHAIN does not exist! **" >&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 a hostname using our cache
dyndns_get_cached_host()
{
  local host="$1"

  if is_numeric_ipv4 "$host" || is_numeric_ipv6 "$host"; then
    echo "$host"
    return 0
  fi
  
  if [ -e "$DYNDNS_HOST_CACHE" ]; then
    local host_ip=`grep "^$host " -m1 "$DYNDNS_HOST_CACHE" |cut -s -f2 -d' '`
    if [ -n "$host_ip" ]; then
      echo "$host_ip"
      return 0
    fi
  fi
  
  # Return error
  return 1
}


# Resolve hostname to IP and store both in our (new) cache
dyndns_host_to_cache()
{
  local host="$1"
  local host_ip=""
  local retval=0
  
  # Check whether we already have it in our (new) cache and don't try to resolve stuff that's already numeric
  if ! is_numeric_ipv4 "$host" && ! is_numeric_ipv6 "$host" && ! grep -q "^$host " "$DYNDNS_HOST_CACHE".new; then
    printf "${INDENT}Resolving host \"$host\" -> "

    if [ "$DNS_SERVER_FAILURE" = "1" ]; then
      echo "** WARNING: Not quering DNS server since it is considered dead for this session! **" >&2 
      host_ip=""
      retval=9
    else
      host_ip=`gethostbyname "$host"`
      retval=$?
      
      # Check whether our DNS server itself failed
      if [ "$retval" = "9" ]; then
        if [ "$DYNDNS_SESSION_FAILED_DNS_SKIP" = "1" ]; then
          # The DNS server failed, so set flag so we know this the next time
          DNS_SERVER_FAILURE=1
          echo "** ERROR(9): DNS server connection failed! Assuming server dead for this session. **" >&2 
        else
          echo "** ERROR(9): DNS server connection failed! **" >&2 
        fi
      fi
    fi
     
    if [ -z "$host_ip" ]; then
      # Try to get from (old) cache, if allowed
      if [ "$DYNDNS_OLD_CACHE_FALLBACK" = "1" ]; then
        host_ip=`dyndns_get_cached_host $host`
      fi
      
      # (Re)check $host_ip
      if [ -z "$host_ip" ]; then
        printf "\033[40m\033[1;31mFAILED!\033[0m\n"
        echo "** ERROR($retval): Unresolvable host \"$host\", and no old IP to fallback on! **" >&2 
      
        # Create dummy entry, so the others know we failed
        echo "$host " >>"$DYNDNS_HOST_CACHE".new
        return $retval
      else
        echo "** WARNING($retval): Unresolvable host \"$host\". Re-using old IP ($host_ip)! **" >&2 
      fi
    fi
    echo "$host_ip"
    echo "$host $host_ip" >>"$DYNDNS_HOST_CACHE".new 
  fi
  
  return 0
}      


# Setup host->ip cache
dyndns_setup_cache()
{
  # Create new empty file
  printf "" >"$DYNDNS_HOST_CACHE".new
  
  unset IFS
  for rule in $DYNDNS_HOST_OPEN_TCP; do
    hosts=`get_hosts_ihp "$rule"`
    
    IFS=','
    for host in $hosts; do
      dyndns_host_to_cache "$host"
    done
  done

  unset IFS
  for rule in $DYNDNS_HOST_OPEN_UDP; do
    hosts=`get_hosts_ihp "$rule"`
   
    IFS=','
    for host in $hosts; do
      dyndns_host_to_cache "$host"
    done
  done

  unset IFS
  for rule in $DYNDNS_HOST_OPEN_IP; do
    hosts=`get_hosts_ihp "$rule"`

    IFS=','    
    for host in $hosts; do
      dyndns_host_to_cache "$host"
    done
  done

  IFS=' ,'
  for rule in $DYNDNS_HOST_OPEN_ICMP; do
    hosts=`get_hosts_ih "$rule"`

    IFS=','
    for host in $hosts; do
      dyndns_host_to_cache "$host"
    done
  done

  # Remove old cache file
  rm -f "$DYNDNS_HOST_CACHE"
  
  # Make our new cache file active
  mv "$DYNDNS_HOST_CACHE".new "$DYNDNS_HOST_CACHE"
  
  return 0
}


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
    interfaces=`get_ifs "$rule"`
    destips=`get_ips "$rule"`
    hosts=`get_hosts_ihp "$rule"`
    ports=`get_ports_ihp "$rule"`

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


  # Add UDP ports to allow for certain hosts
  ##########################################
  unset IFS
  for rule in $DYNDNS_HOST_OPEN_UDP; do
    interfaces=`get_ifs "$rule"`
    destips=`get_ips "$rule"`
    hosts=`get_hosts_ihp "$rule"`
    ports=`get_ports_ihp "$rule"`

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


  # Add IP protocols to allow for certain hosts
  #############################################
  unset IFS
  for rule in $DYNDNS_HOST_OPEN_IP; do
    interfaces=`get_ifs "$rule"`
    destips=`get_ips "$rule"`
    hosts=`get_hosts_ihp "$rule"`
    protos=`get_ports_ihp "$rule"`

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


  # Add ICMP to allow for certain hosts
  #####################################
  unset IFS
  for rule in $DYNDNS_HOST_OPEN_ICMP; do
    interfaces=`get_ifs "$rule"`
    destips=`get_ips "$rule"`
    hosts=`get_hosts_ih "$rule"`

    echo "${INDENT}$(show_if_ip "$interfaces" "$destips")Allowing $hosts for ICMP-requests(ping)"
    
    IFS=','
    for interface in $interfaces; do
      for destip in $destips; do
        for host in $hosts; do
          host_ip=`dyndns_get_cached_host $host`
          if [ -n "$host_ip" ]; then
            iptables -A DYNDNS_CHAIN -i $interface -s $host_ip -d $destip -p $ICMP_PROTO $ICMP_TYPE echo-request -j ACCEPT
          fi
        done
      done
    done
  done
}


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

# Check where to find the config file
if [ -n "$PLUGIN_CONF_PATH" ]; then
  CONF_FILE="$PLUGIN_CONF_PATH/$PLUGIN_CONF_FILE"
else
  CONF_FILE="$PLUGIN_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
    sanity_check       &&
    dyndns_setup_cache &&
    dyndns_host_open   &&
    exit 0;
  fi
fi

exit 1
