#! /bin/sh -
#
# nat;  start and stop network address translations using iproute2 tools
#
# chkconfig: 345 45 55
# description: iproute2 tools allow for sophisticated routing, network
#              address translation, and policy based routing.  This script
#              generalizes static NAT mappings and exceptions.
# <!-- trick an XML parser sourcing us. Rest of file is CDATA --> <![CDATA[
# Copyright (c)2002 SecurePipe, Inc. - http://www.securepipe.com/
# 
# This program 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 2 of the License, or (at your
# option) any later version.
#
# 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.
#
#
# -- written initially, 2002-03-02; -MAB
#    2002-08-14; Martin A. Brown <mabrown@securepipe.com>
#      - cleaned up and commented the code a bit
#      - altered the script to provide support for NAT from user-specified
#        networks instead of assuming that anything from 0/0 should be
#        translated
#    2002-08-30; Martin A. Brown <mabrown@securepipe.com>
#      - add configuration setting to flush all NAT rules and routes before
#        installing new rules and routes
#      - add a ./nat flush option
#    2003-01-31; Matthew Callaway <matt@securepipe.com>
#      - add validation routines
#    2003-02-05; Martin A. Brown <mabrown@securepipe.com>
#      - oversight identified by Shawn Balestracci; not all NAT rules
#        were flushed--we were looking only for map-to, not the exclude
#        rules as well

gripe () {  echo "$@" >&2;               }
abort () {  gripe "Fatal: $@"; exit 1;   }

CONFIG=${CONFIG:-/etc/sysconfig/static-nat}
[ -r "$CONFIG" ] || abort $CONFIG is not readable

function isIP () {
  # -- this function validates a variable as a valid IP address or CIDR network
  #
  VAR=$1

  echo ${VAR} | grep -Eq \
    "[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}(|[[:digit:]]{1,2})"

  [ $? -eq 0 ] && return 0
  return 1
}

function isINT () {
  # -- this function validates a variable as a valid integer
  #
  VAR=$1

  echo ${VAR} | grep -Eq \
    "[[:digit:]]{1,}"

  [ $? -eq 0 ] && return 0
  return 1
}

function validate () {
  grep -Ev '^#|^$' $CONFIG | while read NET NAT REAL NPRIO RPRIO EXCLUDE ; do
    # Fields 5 and 6 are optional
    if [ -z "$NET" -o -z "$NAT" -o -z "$REAL" -o -z "$NPRIO" ]; then
       echo Syntax error: Missing field: $NET $NAT $REAL $NPRIO $RPRIO $EXCLUDE
       exit 1
    fi
    if [ -n "$RPRIO" -a -z "$EXCLUDE" ]; then
       echo Syntax error: $NET $NAT $REAL $NPRIO $RPRIO $EXCLUDE
       echo Field 6 must be used with field 5
       exit 1
    fi
    for ITEM in $NET $NAT $REAL $EXCLUDE ; do
      isIP $ITEM
      if [ $? -ne 0 ]; then 
         echo "In line:"
         echo $NET $NAT $REAL $NPRIO $RPRIO $EXCLUDE
         echo $ITEM is not a valid IP or CIDR block
         exit 1
      fi
    done
    for ITEM in $NPRIO $RPRIO; do
      isINT $ITEM
      if [ $? -ne 0 ]; then 
         echo "In line:"
         echo $NET $NAT $REAL $NPRIO $RPRIO $EXCLUDE
         echo $ITEM is not an integer
         exit 1
      fi
    done
  done
}

function flush () {
  # -- this function should remove all NAT rules and routes
  #
  # -- remove all of the rules, except the three builtins and any IPSec
  #    rule; -MAB;
  #
  ip rule show | grep -Ev '^(0|32766|32767):|iif lo' \
    | while read PRIO NATRULE; do
    ip rule del prio ${PRIO%%:*} $( echo $NATRULE | sed 's|all|0/0|' )
  done
  # -- remove all of the rules
  #
  ip route show table local  | grep ^nat | while read NATROUTE; do
    ip route del $NATROUTE
  done
  ip route flush cache;
}

function nat () {
  grep -Ev '^#|^$' $CONFIG | while read NET NAT REAL NPRIO RPRIO EXCLUDE ; do

    # <-- set up the route for the NAT IP to turn it into the real IP
    #
    ip route add from $NET nat $NAT via ${REAL%%/*}
    [ "$?" -eq "0" ] || \
      gripe cmd failed: ip route add nat $NAT via ${REAL%%/*}

    # <-- establish the minimum routing policy database;
    #     this is required so that the outbound packet gets
    #     rewritten to be from the IP which sent us the packet
    #
    ip rule add to $NET nat ${NAT%%/*} from $REAL prio $NPRIO 
    [ "$?" -eq "0" ] || \
      gripe cmd failed: ip rule add nat ${NAT%%/*} from $REAL prio $NPRIO 

    # <-- determine if the user has supplied networks or address to be
    #     excluded from the $NETwork address above
    #
    [ ! -z "$RPRIO" ] && [ ! -z "$EXCLUDE" ] && {
      for NETWORK in $EXCLUDE ; do
        ip rule add from $REAL to $NETWORK prio $RPRIO
        [ "$?" -eq "0" ] || \
          gripe cmd failed: ip rule add from $REAL to $NETWORK prio $RPRIO
      done;
    }
  done;
  # <-- We don't want to forget to flush the cache, or the user will
  #     sit around wondering for the next few minutes why the NAT rules
  #     aren't working.  After flushing the cache, the NAT rules will
  #     work right away.
  #
  ip route flush cache;
}

# see how we were called
case "$1" in 
      start) validate && nat
             ;;
       stop) flush
             ;;
    restart) $0 stop; $0 start
             ;;
     status) ip route show table local | grep ^nat
             ip rule show | grep map-to
             ;;
          *) echo "usage: nat {start|stop|restart|status}"
             ;;
esac

# ]]>  <!-- the line above closes the XML CDATA from above -->
# - end of nat
