#!/bin/bash

### Include ymclibnettools
. /usr/lib/lib-fliwi/ymc-networktools.bash

iptablesReadCMD="/sbin/iptables"
iptablesWriteCMD="/sbin/iptables"
chainPrefix="fliwi_"
legacy_mode=0
do_add=1
do_remove=1
SUPPORTED_TABLES="filter nat mangle raw security"

if [ $(ymc_is_chroot) -ne 0 ]; then
  echo "WARNING: $0 can not be run inside a chroot..." 1>&2
  exit 0
fi

fliwi_get_existing_chains_for_table()
{
  local table="$1"
  if [ -z "$table" ]; then
    table="filter"
  fi

  LC_ALL=C iptables -n -L -t $table 2>/dev/null | grep -E -e "^Chain $chainPrefix" | cut -d ' ' -f 2 | sed -r 's/^'$chainPrefix'//'
}

fliwi_setup_chain_if_needed()
{
  local chain="$1"
  local table="$2"
  if [ -z "$table" ]; then
    table="filter"
  fi

  if [ $(ymc_contains $table $SUPPORTED_TABLES) -ne 1 ]; then
    echo "ERROR: Table '$table' is not supported" 1>&2
    return 1
  fi

  if ! $iptablesReadCMD -n -t $table -L $chainPrefix$chain 2>/dev/null 1>&2; then
    echo "INFO: Adding custom chain '$chainPrefix$chain' in table '$table'" 1>&2
    $iptablesWriteCMD -t $table -N $chainPrefix$chain
    $iptablesWriteCMD -t $table -A $chain -j $chainPrefix$chain
  fi
}

fliwi_remove_chain()
{
  local chain="$1"
  local table="$2"
  local loop_protection=100
  if [ -z "$table" ]; then
    table="filter"
  fi

  if [ $(ymc_contains $table $SUPPORTED_TABLES) -ne 1 ]; then
    echo "ERROR: Table '$table' is not supported" 1>&2
    return 1
  fi

  if $iptablesReadCMD -n -t $table -L $chainPrefix$chain 2>/dev/null 1>&2; then
    echo "INFO: Removing custom chain '$chainPrefix$chain' in table '$table'" 1>&2

    ### Flush custom chain
    $iptablesWriteCMD -t $table -F $chainPrefix$chain

    ### Remove references to custom chain from parent table
    while [ $(LC_ALL=C $iptablesReadCMD -n -t $table -L | grep -c -E -e "^$chainPrefix$chain") -gt 0 ] && \
          [ $loop_protection -gt 0 ]
    do
      $iptablesWriteCMD -t $table -D $chain -j $chainPrefix$chain
      loop_protection=$(expr $loop_protection - 1)
      sleep 0.1
    done

    if [ $loop_protection -le 0 ]; then
      echo "ERROR: Too many loops while removing chain '$chainPrefix$buildInChain'" 1>&2
      return 1
    fi

    ### Delete custom chain
    $iptablesWriteCMD -t $table -X $chainPrefix$chain
  fi
}

fliwi_remove_all_chains_in_all_tables()
{
  local table
  local chain
  for table in $SUPPORTED_TABLES
  do
    echo "INFO: Processing table '$table' for removal of chains" 1>&2
    for chain in $(fliwi_get_existing_chains_for_table $table)
    do
      fliwi_remove_chain $chain $table
    done
  done
}


run_mode=$1
if [ "$run_mode" == 'remove' ]; then
  do_add=0
elif [ "$run_mode" == 'dry-run' ]; then
  do_remove=0
  iptablesWriteCMD="echo iptables"
fi


### LEGACY_SUPPORT //start
iptables_add_file="/tmp/$(basename $0)/iptables.add"
if [ -r "$iptables_add_file" ]; then
  ### Always unload rules (if needed)
  while read iptable_line
  do
    $iptablesWriteCMD $(echo $iptable_line | sed -r 's/(^-A| -A) / -D /')
  done < $iptables_add_file

  rm $iptables_add_file
fi
### LEGACY_SUPPORT //end


if [ $do_remove -eq 1 ]; then
  echo "INFO: Removing exiting chains" 1>&2
  fliwi_remove_all_chains_in_all_tables
fi


if [ $do_add -eq 1 ]; then
  ### Get ordering chains (bad naming: (Ordering) Chains in Fliwi actually are a different thing than chains in iptables)
  echo "INFO: Retrieving ordering chains..." 1>&2
  ymc_get_config_from_dns chains.iptables
  if [ $? -ne 0 ]; then
    echo "WARNING: Can not load ordering chains - falling back to legacy mode..." 1>&2
    echo "         Please consider upgrading your management-host!" 1>&2
    order_chains_to_lookup="configured"
    legacy_mode=1
  else
    ## Note: We use '$config_var_prefix', since there is set only a single variable without a suffix...
    order_chains_to_lookup=$(ymc_var_value $config_var_prefix)
  fi

  hostname=$(ymc_get_local_short_hostname)

  echo "INFO: Retrieving iptables sets..." 1>&2
  if [ $legacy_mode -eq 1 ]; then
    ### Load our configured sets in legacy-mode
    ymc_get_config_from_dns $hostname.configured.iptables
    if [ $? -ne 0 ]; then
      echo "NOTE: Can not find any iptables for '$hostname'..." 1>&2
      exit 0
    fi

    ## Note: We use '$config_var_prefix', since there is set only a single variable without a suffix...
    rules_to_lookup=$(ymc_var_value $config_var_prefix)
  else
    ### Load our configured sets
    rules_to_lookup=""
    for order_chain_to_lookup in $order_chains_to_lookup
    do
      ymc_get_config_from_dns $hostname.$order_chain_to_lookup.hosts.iptables
      if [ $? -eq 0 ]; then
        rules_to_lookup=$rules_to_lookup" "$(ymc_var_value $config_var_prefix)
      fi

      for service in $(fliwi-get-my-services | sed -r 's/-[0-9]+$//')
      do
        ymc_get_config_from_dns $service.$order_chain_to_lookup.services.iptables
        if [ $? -eq 0 ]; then
          rules_to_lookup=$rules_to_lookup" "$(ymc_var_value $config_var_prefix)
        fi
      done
    done

    if [ -z "$rules_to_lookup" ]; then
      echo "NOTE: Can not find any iptables for host '$hostname' or any assigned services..." 1>&2
      exit 0
    fi
  fi

  echo "INFO: Applying iptables sets..." 1>&2
  ### Load rules
  for rule_to_lookup in $rules_to_lookup
  do
    shorted_variables=''
    rule_name=$(echo $rule_to_lookup | cut -d '.' -f 1)
    rule_type=$(echo $rule_to_lookup | cut -d '.' -f 2)
    rule_interface=$(echo $rule_to_lookup | cut -d '.' -f 3)

    ymc_get_config_from_dns $rule_to_lookup.iptables
    for variable_name in $variables_set
    do
      shorted_variable_name=$(echo $variable_name | sed -r 's/^'$config_var_prefix'_//')
      shorted_variables=$shorted_variables' '$shorted_variable_name

      ymc_set_var_content "$shorted_variable_name" "$(ymc_var_value $variable_name)"
    done


    if [ -n "$protocol" ]; then
      protocol="--protocol $protocol"
    fi

    if [ -n "$in_interface" ]; then
      in_interface="--in-interface $in_interface"
    fi

    if [ -n "$out_interface" ]; then
      out_interface="--out-interface $out_interface"
    fi

    if [ -n "$source_net" ]; then
      source_net="--source $source_net"
    fi

    if [ -n "$destination_net" ]; then
      destination_net="--destination $destination_net"
    fi

    if [ -n "$source_port" ]; then
      source_port="--source-port $source_port"
    fi

    if [ -n "$destination_port" ]; then
      destination_port="--destination-port $destination_port"
    fi

    if [ -n "$state" ]; then
      state="-m state --state $state"
    fi

    if [ -n "$from_port" ]; then
      from_port="--dport $from_port"
    fi

    if [ -n "$to_destination" ]; then
      if [ $(echo $to_destination | grep -c '[^0-9:.]') -ne 0 ]; then
        to_destination_host=$(echo $to_destination | cut -d ':' -f 1)
        to_destination_port=$(echo $to_destination | cut -s -d ':' -f 2)
        to_destination=$(gethostip -d $to_destination_host)
        if [ -n "$to_destination_port" ]; then
          to_destination=$to_destination':'$to_destination_port
        fi
      fi

      to_destination="--to-destination $to_destination"
    fi

    if [ -n "$jump" ]; then
      jump="--jump $jump"
    fi

    case "$rule_type" in
      nat)
        fliwi_setup_chain_if_needed POSTROUTING nat
        echo "INFO: Processing $rule_type-rule '$rule_name' for interface '$rule_interface'..." 1>&2
        $iptablesWriteCMD -t nat -A $chainPrefix'POSTROUTING' $out_interface $protocol $source_net $destination_net $state -j MASQUERADE
      ;;

      redirect)
        fliwi_setup_chain_if_needed PREROUTING nat
        echo "INFO: Processing $rule_type-rule '$rule_name' for interface '$rule_interface'..." 1>&2
        $iptablesWriteCMD -t nat -A $chainPrefix'PREROUTING' $in_interface $protocol $source_net $destination_net $state $from_port -j DNAT $to_destination
      ;;

      input)
        fliwi_setup_chain_if_needed INPUT
        echo "INFO: Processing $rule_type-rule '$rule_name' for interface '$rule_interface'..." 1>&2
        $iptablesWriteCMD -A $chainPrefix'INPUT' $in_interface $protocol $source_net $destination_net $source_port $destination_port $state $jump
      ;;

      forward)
        fliwi_setup_chain_if_needed FORWARD
        echo "INFO: Processing $rule_type-rule '$rule_name' for interface '$rule_interface'..." 1>&2
        $iptablesWriteCMD -A $chainPrefix'FORWARD' $in_interface $out_interface $protocol $source_net $destination_net $state $jump
      ;;

      output)
        fliwi_setup_chain_if_needed OUTPUT
        echo "INFO: Processing $rule_type-rule '$rule_name' for interface '$rule_interface'..." 1>&2
        $iptablesWriteCMD -A $chainPrefix'OUTPUT' $out_interface $protocol $source_net $destination_net $source_port $destination_port $state $jump
      ;;

      arbitrary)
        fliwi_setup_chain_if_needed $chain $table
        echo "INFO: Processing $rule_type-rule '$rule_name' for interface '$rule_interface'..." 1>&2
        $iptablesWriteCMD -t $table -A $chainPrefix$chain $jump $rules
      ;;

      *)
        echo "WARNING: Can not process rule '$rule_name' of unknow type '$rule_type' for interface '$rule_interface'..." 1>&2
      ;;
    esac

    ### empty out shorted and parametrized variables
    for shorted_variable in $shorted_variables
    do
      ymc_set_var_content $shorted_variable ""
    done
  done
fi

exit 0
