#!/bin/bash -e
#
# racoon up/down script for Shimo
#

# TODO: check IPv6 compatibility (routing, DNS etc.)

##########################################################################################
# @param String message - The message to log
logMessage()
{
    echo "$(date '+%Y-%m-%d %T') *Shimo: "${@} >> "${SCRIPT_LOG_FILE}"
}

##########################################################################################

mask2cdr ()
{
    # Assumes there's no "255." after a non-255 byte in the mask
    local x=${1##*255.}
    set -- 0^^^128^192^224^240^248^252^254^ $(( (${#1} - ${#x})*2 )) ${x%%.*}
    x=${1%%$3*}
    echo $(( $2 + (${#x}/4) ))
}


cdr2mask ()
{
    # Number of args to shift, 255..255, first non-255 byte, zeroes
    set -- $(( 5 - ($1 / 8) )) 255 255 255 255 $(( (255 << (8 - ($1 % 8))) & 255 )) 0 0 0
    [ $1 -gt 1 ] && shift $1 || shift
    echo ${1-0}.${2-0}.${3-0}.${4-0}
}

print_variables()
{
    if [ -n "${UNIQUE_IDENTIFIER}" ]; then logMessage "UNIQUE_IDENTIFIER = ${UNIQUE_IDENTIFIER}"; fi
    if [ -n "${LOCAL_ADDR}" ]; then logMessage "LOCAL_ADDR = ${LOCAL_ADDR}"; fi
    if [ -n "${LOCAL_PORT}" ]; then logMessage "LOCAL_PORT = ${LOCAL_PORT}"; fi
    if [ -n "${REMOTE_ADDR}" ]; then logMessage "REMOTE_ADDR = ${REMOTE_ADDR}"; fi
    if [ -n "${REMOTE_PORT}" ]; then logMessage "REMOTE_PORT = ${REMOTE_PORT}"; fi
        
    if [ -n "${INTERNAL_ADDR4}" ]; then logMessage "INTERNAL_ADDR4 = ${INTERNAL_ADDR4}"; fi
    if [ -n "${INTERNAL_NETMASK4}" ]; then logMessage "INTERNAL_NETMASK4 = ${INTERNAL_NETMASK4}"; fi
    if [ -n "${REMOTE_ADDR4}" ]; then logMessage "REMOTE_ADDR4 = ${REMOTE_ADDR4}"; fi
    if [ -n "${REMOTE_NETMASK4}" ]; then logMessage "REMOTE_NETMASK4 = ${REMOTE_NETMASK4}"; fi
        
    if [ -n "${INTERNAL_DNS4}" ]; then logMessage "INTERNAL_DNS4 = ${INTERNAL_DNS4}"; fi
    if [ -n "${INTERNAL_DNS4_LIST}" ]; then logMessage "INTERNAL_DNS4_LIST = ${INTERNAL_DNS4_LIST}"; fi
    if [ -n "${INTERNAL_WINS4}" ]; then logMessage "INTERNAL_WINS4 = ${INTERNAL_WINS4}"; fi
    if [ -n "${INTERNAL_WINS4_LIST}" ]; then logMessage "INTERNAL_WINS4_LIST = ${INTERNAL_WINS4_LIST}"; fi
    if [ -n "${INTERNAL_ADDR6}" ]; then logMessage "INTERNAL_ADDR6 = ${INTERNAL_ADDR6}"; fi
    if [ -n "${INTERNAL_NETMASK6}" ]; then logMessage "INTERNAL_NETMASK6 = ${INTERNAL_NETMASK6}"; fi
    if [ -n "${INTERNAL_DNS6}" ]; then logMessage "INTERNAL_DNS6 = ${INTERNAL_DNS6}"; fi
    if [ -n "${INTERNAL_DNS6_LIST}" ]; then logMessage "INTERNAL_DNS6_LIST = ${INTERNAL_DNS6_LIST}"; fi
    if [ -n "${INTERNAL_WINS6}" ]; then logMessage "INTERNAL_WINS6 = ${INTERNAL_WINS6}"; fi
    if [ -n "${INTERNAL_WINS6_LIST}" ]; then logMessage "INTERNAL_WINS6_LIST = ${INTERNAL_WINS6_LIST}"; fi
    if [ -n "${DEFAULT_DOMAIN}" ]; then logMessage "DEFAULT_DOMAIN = ${DEFAULT_DOMAIN}"; fi
        
    if [ -n "${SPLIT_INCLUDE}" ]; then logMessage "SPLIT_INCLUDE = ${SPLIT_INCLUDE}"; fi
    if [ -n "${SPLIT_LOCAL}" ]; then logMessage "SPLIT_LOCAL = ${SPLIT_LOCAL}"; fi
    logMessage "SEARCH_DOMAIN_LIST = ${SEARCH_DOMAIN_LIST}"
    if [ -n "${SHIMO_SPLIT_DNS}" ]; then logMessage "SHIMO_SPLIT_DNS = ${SHIMO_SPLIT_DNS}"; fi
    logMessage "USE_REMOTE_DNS = ${USE_REMOTE_DNS}"
    logMessage "USE_REMOTE_DNS_FOR_DOMAINS_ONLY = ${USE_REMOTE_DNS_FOR_DOMAINS_ONLY}"
    logMessage "SLICING_TOOL = ${SLICING_TOOL}"
    logMessage "LOCAL_IF = ${LOCAL_IF}"
    logMessage "LOCAL_GW = ${LOCAL_GW}"
    logMessage "LOCAL_NETWORKS = ${LOCAL_NETWORKS}"
}

# =========== DNS handling ====================================

modify_dns() {
    PSID=$( (scutil | grep PrimaryService | sed -e 's/.*PrimaryService : //')<<-EOF
        open
          show State:/Network/Global/IPv4
        quit
EOF )
    
    OVERRIDE_PRIMARY=""
    if [ -n "${SPLIT_INCLUDE}" ]; then
        if [ ${SPLIT_INCLUDE} -lt 1 ]; then
            # Must override for correct default route
            # Cannot use multiple DNS matching in this case
            OVERRIDE_PRIMARY='d.add OverridePrimary # 1'
        fi
        # Overriding the default gateway breaks split routing
        OVERRIDE_GATEWAY=""
        # Not overriding the default gateway breaks usage of
        # INTERNAL_DNS4_LIST. Prepend INTERNAL_DNS4_LIST to list
        # of used DNS servers
        if ${USE_REMOTE_DNS} && ! ${USE_REMOTE_DNS_FOR_DOMAINS_ONLY} ; then
            SERVICE=`echo "show State:/Network/Global/IPv4" | scutil | grep -oE '[a-fA-F0-9]{8}-([a-fA-F0-9]{4}-){3}[a-fA-F0-9]{12}'`
            SERVICE_DNS=`echo "show State:/Network/Service/$SERVICE/DNS" | scutil | grep -oE '([0-9]{1,3}[\.]){3}[0-9]{1,3}' | xargs`
			logMessage "Not overriding the default gateway breaks usage of internal DNS servers:"
            logMessage "Prepending '$INTERNAL_DNS4_LIST' to DNS servers '$SERVICE_DNS' of primary interface $SERVICE "
            if [ X"$SERVICE_DNS" != X"$INTERNAL_DNS4_LIST" ]; then
                scutil >/dev/null 2>&1 <<-EOF
                open
                    get State:/Network/Service/$SERVICE/DNS
                    d.add ServerAddresses * $INTERNAL_DNS4_LIST $SERVICE_DNS
                    set State:/Network/Service/$SERVICE/DNS
                close
EOF
            fi
        fi
    else
        # No split routing. Override default gateway
        OVERRIDE_GATEWAY="d.add Router ${INTERNAL_ADDR4}"
    fi
    
    # prepare search domains
    if [ -z "${SEARCH_DOMAIN_LIST}" ]; then
        SEARCH_DOMAINS=${DEFAULT_DOMAIN}
    elif [[ "${SEARCH_DOMAIN_LIST}" == *"${DEFAULT_DOMAIN}"* ]]; then
        SEARCH_DOMAINS=${SEARCH_DOMAIN_LIST}
    else
        SEARCH_DOMAINS="${SEARCH_DOMAIN_LIST} ${DEFAULT_DOMAIN}"
    fi
    
    if [ -n "${SHIMO_SPLIT_DNS}" ]; then
        SEARCH_DOMAINS=${SHIMO_SPLIT_DNS}
    fi
    
    # Uncomment the following if/fi pair to use multiple
    # DNS matching when available.  When multiple DNS matching
    # is present, anything reading the /etc/resolv.conf file
    # directly will probably not work as intended.
    if [ -n "$OVERRIDE_GATEWAY" ] && ${USE_REMOTE_DNS} && ! ${USE_REMOTE_DNS_FOR_DOMAINS_ONLY} ; then
        # Cannot use multiple DNS matching without a domain
        OVERRIDE_PRIMARY='d.add OverridePrimary # 1'
    fi

    # update IPv4 State: settings
    scutil >/dev/null 2>&1 <<-EOF
    open
        d.init
        # next line overrides the default gateway and breaks split routing
        $OVERRIDE_GATEWAY
        d.add Addresses * ${INTERNAL_ADDR4}
        d.add SubnetMasks * 255.255.255.255
        d.add InterfaceName $TUNDEV
        $OVERRIDE_PRIMARY
        set State:/Network/Service/$TUNDEV/IPv4
        # copy proxy settings from default interface
        d.init
        get Setup:/Network/Service/$PSID/Proxies
        set Setup:/Network/Service/$TUNDEV/Proxies
    close
EOF

    if ${USE_REMOTE_DNS}; then
        
        if [ -z "${DEFAULT_DOMAIN}" ]; then
            NO_DOMAIN_NAME="#"
        fi
        
        # set DNS settings via State:
        logMessage "Setting default domain $DEFAULT_DOMAIN for name servers '$INTERNAL_DNS4_LIST' for $TUNDEV"
        scutil >/dev/null 2>&1 <<-EOF
        open
            d.init
            d.add ServerAddresses * $INTERNAL_DNS4_LIST
            ${NO_DOMAIN_NAME}d.add DomainName ${DEFAULT_DOMAIN}
            set State:/Network/Service/$TUNDEV/DNS
        close
EOF

        # set search domains
        if [ -n "${SEARCH_DOMAINS}" ] && ${USE_REMOTE_DNS_FOR_DOMAINS_ONLY}; then

            logMessage "Setting search domains '$SEARCH_DOMAINS' for $TUNDEV via scutil"
            scutil >/dev/null 2>&1 <<-EOF
            open
                get State:/Network/Service/$TUNDEV/DNS
                d.add SearchDomains * $SEARCH_DOMAINS
                d.add SupplementalMatchDomains * $SEARCH_DOMAINS
                set State:/Network/Service/$TUNDEV/DNS
            close
EOF

            # set DNS settings via /etc/resolver/
            logMessage "Setting search domains '$SEARCH_DOMAINS' for $TUNDEV via /etc/resolver/"
            RESOLVER_CONF="#@SHIMO_GENERATED ${UNIQUE_IDENTIFIER}@"
            for i in ${INTERNAL_DNS4_LIST} ; do
                RESOLVER_CONF="${RESOLVER_CONF}
nameserver $i"
            done

            # make sure that /etc/resolver directory exists, then create configurations
            mkdir -p /etc/resolver
            for i in ${SEARCH_DOMAINS} ; do
                echo "$RESOLVER_CONF" > /etc/resolver/$i
            done
        fi

        # in 10.8 and higher, ServerAddresses and SearchDomains must be set via the Setup: key in addition to the State: key
        scutil >/dev/null 2>&1 <<-EOF
        open
            # copy DNS info from State:
            d.init
            get State:/Network/Service/$TUNDEV/DNS
            set Setup:/Network/Service/$TUNDEV/DNS
        close
EOF
    fi
    
    # set SMB settings via State:
    if [ -n "$INTERNAL_WINS4_LIST" ]; then
        logMessage "Setting SMB servers '$INTERNAL_WINS4_LIST' for $TUNDEV"
        scutil >/dev/null 2>&1 <<-EOF
        open
            # add SMB information
            d.init
            d.add WINSAddresses * ${INTERNAL_WINS4_LIST}
            set State:/Network/Service/$TUNDEV/SMB
        close
EOF
    fi
    
    # now read all modified values for debug log    
    readonly NEW_IP4_STATE_CONFIG="$( scutil <<-EOF |
        open
        show State:/Network/Service/${TUNDEV}/IPv4
        quit
EOF
sed -e 's/^[[:space:]]*[[:digit:]]* : //g' | tr '\n' ' '
)"
    readonly NEW_DNS_SETUP_CONFIG="$( scutil <<-EOF |
        open
        show Setup:/Network/Service/${TUNDEV}/DNS
        quit
EOF
sed -e 's/^[[:space:]]*[[:digit:]]* : //g' | tr '\n' ' '
)"
    readonly NEW_DNS_STATE_CONFIG="$( scutil <<-EOF |
        open
        show State:/Network/Service/${TUNDEV}/DNS
        quit
EOF
sed -e 's/^[[:space:]]*[[:digit:]]* : //g' | tr '\n' ' '
)"
    readonly NEW_SMB_STATE_CONFIG="$( scutil <<-EOF |
        open
        show State:/Network/Service/${TUNDEV}/SMB
        quit
EOF
sed -e 's/^[[:space:]]*[[:digit:]]* : //g' | tr '\n' ' '
)"
    readonly NEW_IP4_GLOBAL_CONFIG="$( scutil <<-EOF |
        open
        show State:/Network/Global/IPv4
        quit
EOF
sed -e 's/^[[:space:]]*[[:digit:]]* : //g' | tr '\n' ' '
)"
    readonly NEW_DNS_GLOBAL_CONFIG="$( scutil <<-EOF |
        open
        show State:/Network/Global/DNS
        quit
EOF
sed -e 's/^[[:space:]]*[[:digit:]]* : //g' | tr '\n' ' '
)"
    readonly NEW_SMB_GLOBAL_CONFIG="$( scutil <<-EOF |
        open
        show State:/Network/Global/SMB
        quit
EOF
sed -e 's/^[[:space:]]*[[:digit:]]* : //g' | tr '\n' ' '
)"

    logMessage "DEBUG:"
    logMessage "DEBUG: Configurations as read back after changes:"
    logMessage "DEBUG: State:/Network/Service/${TUNDEV}/IPv4 = ${NEW_IP4_STATE_CONFIG}"
    logMessage "DEBUG: State:/Network/Service/${TUNDEV}/DNS = ${NEW_DNS_STATE_CONFIG}"
    logMessage "DEBUG: State:/Network/Service/${TUNDEV}/SMB = ${NEW_SMB_STATE_CONFIG}"
    logMessage "DEBUG:"
    logMessage "DEBUG: Setup:/Network/Service/${TUNDEV}/DNS = ${NEW_DNS_SETUP_CONFIG}"
    logMessage "DEBUG:"
    logMessage "DEBUG: State:/Network/Global/IPv4 = ${NEW_IP4_GLOBAL_CONFIG}"
    logMessage "DEBUG: State:/Network/Global/DNS = ${NEW_DNS_GLOBAL_CONFIG}"
    logMessage "DEBUG: State:/Network/Global/SMB = ${NEW_SMB_GLOBAL_CONFIG}"

    logMessage "Saved the DNS and SMB configurations for later use"
}

restore_dns() {
    scutil >/dev/null 2>&1 <<-EOF
    open
        # remove VPN DNS and IP
        remove State:/Network/Service/$TUNDEV/IPv4
        remove State:/Network/Service/$TUNDEV/DNS
        remove State:/Network/Service/$TUNDEV/SMB
        remove Setup:/Network/Service/$TUNDEV/Proxies
        remove Setup:/Network/Service/$TUNDEV/DNS
    close
EOF

    # Split routing required prepending of INTERNAL_DNS4_LIST
    # to list of used DNS servers
    if [ -n "${SPLIT_INCLUDE}" ] && ${USE_REMOTE_DNS} && ! ${USE_REMOTE_DNS_FOR_DOMAINS_ONLY} ; then
        SERVICE=`echo "show State:/Network/Global/IPv4" | scutil | grep -oE '[a-fA-F0-9]{8}-([a-fA-F0-9]{4}-){3}[a-fA-F0-9]{12}'`
        SERVICE_DNS=`echo "show State:/Network/Service/$SERVICE/DNS" | scutil | grep -oE '([0-9]{1,3}[\.]){3}[0-9]{1,3}' | xargs`
        if [ X"$SERVICE_DNS" != X"$INTERNAL_DNS4_LIST" ]; then
            scutil >/dev/null 2>&1 <<-EOF
            open
                get State:/Network/Service/$SERVICE/DNS
                d.add ServerAddresses * ${SERVICE_DNS##$INTERNAL_DNS4_LIST}
                set State:/Network/Service/$SERVICE/DNS
            close
EOF
        fi
    fi

    set +e # "grep" will return error status (1) if no matches are found, so don't fail on individual errors

    # remove account's DNS configurations in /etc/resolver/
    for filename in /etc/resolver/*; do
        grep "^#@SHIMO_GENERATED ${UNIQUE_IDENTIFIER}@" "$filename" > /dev/null 2>&1 && rm -f "$filename"
    done
    
    set -e
    
    logMessage "Restored the DNS and SMB configurations"
}

add_spd()
{
    # get target IP4 address and netmask parameters
    TARGET_ADDRESS4=$(dirname $1)
    TARGET_MASKLEN=$(basename $1)
    if [[ ${TARGET_MASKLEN} == *"."* ]]; then
        TARGET_MASKLEN=$(mask2cdr ${TARGET_MASKLEN})
    fi
    
    INTERNAL_MASKLEN=32 # $(mask2cdr ${INTERNAL_NETMASK4}) # this disallows network to network tunnels
    
    logMessage "Adding security policies for remote network ${TARGET_ADDRESS4}/${TARGET_MASKLEN}"
    
    # update SA database
    LOCAL="${LOCAL_ADDR}"
    REMOTE="${REMOTE_ADDR}"
    if [ "x${LOCAL_PORT}" != "x500" ]; then
        # NAT-T setup
        LOCAL="${LOCAL}[${LOCAL_PORT}]"
        REMOTE="${REMOTE}[${REMOTE_PORT}]"
    fi
    
    EXCLUDED_ROUTES=($(${SLICING_TOOL} ${TARGET_ADDRESS4}/${TARGET_MASKLEN} ${LOCAL_NETWORKS} --conflicting))
    if [ -n "${EXCLUDED_ROUTES}" ]; then
        logMessage "Adding policy exceptions for ${EXCLUDED_ROUTES[@]}:"
        for N in ${EXCLUDED_ROUTES[@]}; do
            # adding policy exceptions
            logMessage "Adding security policy exception: ${INTERNAL_ADDR4}/${INTERNAL_MASKLEN} --> ${N} (no IPSec)"
            echo "spdadd ${INTERNAL_ADDR4}/${INTERNAL_MASKLEN}[any] ${N}[any] any -P out none;" | setkey -c
            logMessage "Adding security policy exception: ${N} --> ${INTERNAL_ADDR4}/${INTERNAL_MASKLEN} (no IPSec)"
            echo "spdadd ${N}[any] ${INTERNAL_ADDR4}/${INTERNAL_MASKLEN}[any] any -P in none;" | setkey -c
        done
    fi
    
    # adding secured policies to remote network
    logMessage "Adding security policy: ${INTERNAL_ADDR4}/${INTERNAL_MASKLEN} --> ${TARGET_ADDRESS4}/${TARGET_MASKLEN} (${LOCAL}-${REMOTE}) (IPSec)"
    echo "spdadd ${INTERNAL_ADDR4}/${INTERNAL_MASKLEN}[any] ${TARGET_ADDRESS4}/${TARGET_MASKLEN}[any] any -P out ipsec esp/tunnel/${LOCAL}-${REMOTE}/unique;" | setkey -c
    
    logMessage "Adding security policy: ${TARGET_ADDRESS4}/${TARGET_MASKLEN} --> ${INTERNAL_ADDR4}/${INTERNAL_MASKLEN} (${REMOTE}-${LOCAL}) (IPSec)"
    echo "spdadd ${TARGET_ADDRESS4}/${TARGET_MASKLEN}[any] ${INTERNAL_ADDR4}/${INTERNAL_MASKLEN}[any] any -P in ipsec esp/tunnel/${REMOTE}-${LOCAL}/unique;" | setkey -c
}

del_spd()
{
    # get target IP4 address and netmask parameters
    TARGET_ADDRESS4=$(dirname $1)
    TARGET_MASKLEN=$(basename $1)
    if [[ ${TARGET_MASKLEN} == *"."* ]]; then
        TARGET_MASKLEN=$(mask2cdr ${TARGET_MASKLEN})
    fi
    
    INTERNAL_MASKLEN=32 # $(mask2cdr ${INTERNAL_NETMASK4}) # this disallows network to network tunnels
    
    logMessage "Deleting security policies for remote network ${TARGET_ADDRESS4}/${TARGET_MASKLEN}"
    
    # clean up SA database
    LOCAL="${LOCAL_ADDR}"
    REMOTE="${REMOTE_ADDR}"
    if [ "x${LOCAL_PORT}" != "x500" ]; then
        # NAT-T setup
        LOCAL="${LOCAL}[${LOCAL_PORT}]"
        REMOTE="${REMOTE}[${REMOTE_PORT}]"
    fi

    logMessage "Deleting all security policies between ${REMOTE} and ${LOCAL}"
    echo "deleteall ${REMOTE} ${LOCAL} esp;" | setkey -c
    echo "deleteall ${LOCAL} ${REMOTE} esp;" | setkey -c
    
    EXCLUDED_ROUTES=($(${SLICING_TOOL} ${TARGET_ADDRESS4}/${TARGET_MASKLEN} ${LOCAL_NETWORKS} --conflicting))
    if [ -n "${EXCLUDED_ROUTES}" ]; then
        logMessage "Deleting policy exceptions for ${EXCLUDED_ROUTES[@]}:"
        for N in ${EXCLUDED_ROUTES[@]}; do
            # deleting policy exceptions
            logMessage "Deleting security policy exception: ${INTERNAL_ADDR4}/${INTERNAL_MASKLEN} --> ${N} (no IPSec)"
            echo "spddelete ${INTERNAL_ADDR4}/${INTERNAL_MASKLEN}[any] ${N}[any] any -P out none;" | setkey -c
            logMessage "Deleting security policy exception: ${N} --> ${INTERNAL_ADDR4}/${INTERNAL_MASKLEN} (no IPSec)"
            echo "spddelete ${N}[any] ${INTERNAL_ADDR4}/${INTERNAL_MASKLEN}[any] any -P in none;" | setkey -c
        done
    fi
    
    logMessage "Deleting security policy: ${INTERNAL_ADDR4}/${INTERNAL_MASKLEN} --> ${TARGET_ADDRESS4}/${TARGET_MASKLEN} (${LOCAL}-${REMOTE}) (IPSec)"
    echo "spddelete ${INTERNAL_ADDR4}/${INTERNAL_MASKLEN}[any] ${TARGET_ADDRESS4}/${TARGET_MASKLEN}[any] any -P out ipsec esp/tunnel/${LOCAL}-${REMOTE}/unique;" | setkey -c
    
    logMessage "Deleting security policy: ${TARGET_ADDRESS4}/${TARGET_MASKLEN} --> ${INTERNAL_ADDR4}/${INTERNAL_MASKLEN} (${REMOTE}-${LOCAL}) (IPSec)"
    echo "spddelete ${TARGET_ADDRESS4}/${TARGET_MASKLEN}[any] ${INTERNAL_ADDR4}/${INTERNAL_MASKLEN}[any] any -P in ipsec esp/tunnel/${REMOTE}-${LOCAL}/unique;" | setkey -c
}

prepare_gif_interface()
{
    logMessage "Preparing GIF interface for tunnel"
    
    TARGET_ADDRESS=$1
    if [[ ${TARGET_ADDRESS} == *"/"* ]]; then
        TARGET_ADDRESS=$(dirname ${TARGET_ADDRESS})
    fi

    if [[ ${TARGET_ADDRESS} == "0.0.0.0" ]]; then
        TARGET_ADDRESS="127.1.2.3"
    fi

    set +e
    
    for i in {0..9}; do
        if ! ifconfig gif${i} &>/dev/null; then
            # this interface does not exist yet, use
            readonly TUNDEV="gif${i}"
            
            # create interface and assign
            logMessage "Creating network interface ${TUNDEV}"
            ifconfig ${TUNDEV} create
            
            break
        elif ! ifconfig gif${i} | grep -q inet; then
            # gif without IP address, use
            readonly TUNDEV="gif${i}"

            logMessage "Using existing network interface ${TUNDEV}"
            
            break
        fi
    done
    
    set -e
    
    logMessage "Setting up network interface ${TUNDEV} with ${INTERNAL_ADDR4} --> ${TARGET_ADDRESS}"
    ifconfig ${TUNDEV} ${INTERNAL_ADDR4} ${TARGET_ADDRESS} netmask 255.255.255.255
    
    # report our secured interface
    logMessage "SECURED_IF = ${TUNDEV}"
}

delete_gif_interface()
{
    set +e
    
    for i in {0..9}; do
        if ifconfig gif${i} | grep -q "${INTERNAL_ADDR4}"; then
            # found existing gif with our IP, delete this
            readonly TUNDEV="gif${i}"
            logMessage "Deleting network interface ${TUNDEV}"
            ifconfig ${TUNDEV} delete
            break
        fi
    done
    
    set -e
    
    # report out secured interface
    logMessage "SECURED_IF = ${TUNDEV}"
}

##########################################################################################


### Main Functions

# bring up phase1
phase1_up()
{
    # configure routing
    
    # set up host route to VPN server
    logMessage "Adding route to VPN gateway ${REMOTE_ADDR} --> ${LOCAL_GW}"
    route add ${REMOTE_ADDR} ${LOCAL_GW}
    
    # create tunnel interface
    if [ -n "${SPLIT_INCLUDE}" ]; then
        SPLIT_ARRAY=(${SPLIT_INCLUDE})
        GIF_TARGET_NETWORK=${SPLIT_ARRAY[0]}
    else
        GIF_TARGET_NETWORK=${REMOTE_ADDR4}
    fi
    prepare_gif_interface $GIF_TARGET_NETWORK
    
    if [ -n "${SPLIT_INCLUDE}" ] && [ -n "${TUNDEV}" ]; then
        logMessage "Split tunnel with explicit split routes:"
        # split tunnel: keep existing default, insert specific tunnel routes
        for N in ${SPLIT_INCLUDE}; do
            # make sure target network is in correct format
            TARGET_ADDRESS=$(dirname $N)
            TARGET_MASKLEN=$(basename $N)
            if [[ ${TARGET_MASKLEN} == *"."* ]]; then TARGET_MASKLEN=$(mask2cdr ${TARGET_MASKLEN}); fi
            
            logMessage "Adding route ${TARGET_ADDRESS}/${TARGET_MASKLEN} over ${TUNDEV}"
            route add ${TARGET_ADDRESS}/${TARGET_MASKLEN} -interface ${TUNDEV}
        done
    else
        # full tunnel: set up any applicable exceptions...
        if [ -n "${SPLIT_LOCAL}" ]; then
            logMessage "Full tunnel, adding local route exceptions:"
        fi
        for N in ${SPLIT_LOCAL}; do
            # make sure target network is in correct format
            TARGET_ADDRESS=$(dirname $N)
            TARGET_MASKLEN=$(basename $N)
            if [[ ${TARGET_MASKLEN} == *"."* ]]; then TARGET_MASKLEN=$(mask2cdr ${TARGET_MASKLEN}); fi
            
            if [[ ${TARGET_ADDRESS} == "0.0.0.0" ]]; then
                logMessage "WARNING: Server included default route in split local. Ignoring it..."
                continue
            fi
            
            logMessage "Adding route ${TARGET_ADDRESS}/${TARGET_MASKLEN} over ${LOCAL_IF}"
            route add ${TARGET_ADDRESS}/${TARGET_MASKLEN} -interface ${LOCAL_IF}
        done
        
        if [ -n "${TUNDEV}" ]; then
            # prepare route to remote network (slicing)
            logMessage "Adding primary route to remote network"
            logMessage "Checking route to network ${REMOTE_ADDR4}/${REMOTE_NETMASK4}"
            SLICED_REMOTE_ROUTES=($(${SLICING_TOOL} ${REMOTE_ADDR4}/${REMOTE_NETMASK4} ${LOCAL_NETWORKS}))
            for TARGET_CIDR in ${SLICED_REMOTE_ROUTES[@]}; do
                # setting route to remote network
                logMessage "Adding route ${TARGET_CIDR} over ${TUNDEV}"
                route add ${TARGET_CIDR} -interface ${TUNDEV}
            done
        fi
    fi

    # update DNS configuration
    if [ -n "${INTERNAL_DNS4_LIST}" ] && ${USE_REMOTE_DNS}; then
        logMessage "Updating DNS configuration for ${INTERNAL_DNS4_LIST}"
        modify_dns
    fi

    # add security policies
    if [ -n "${SPLIT_INCLUDE}" ]; then
        # split tunnel: set policy for each route
        for N in ${SPLIT_INCLUDE}; do
            add_spd ${N}
        done
    else
        # set policy for remote network - TODO: do we need exceptions for local routes?!
        add_spd ${REMOTE_ADDR4}/${REMOTE_NETMASK4}
    fi
}

# bring down phase1
phase1_down() 
{
    # delete gif network interface
    delete_gif_interface
    
    # delete security policies
    if [ -n "${SPLIT_INCLUDE}" ]; then
        # split tunnel: delete policy for each route
        for N in ${SPLIT_INCLUDE}; do
            del_spd ${N}
        done
    else
        # delete policy for remote network - TODO: do we need exceptions for local routes?!
        del_spd ${REMOTE_ADDR4}/${REMOTE_NETMASK4}
    fi
    
    # restore MacOS DNS
    if ${USE_REMOTE_DNS}; then
        logMessage "Restoring DNS configuration for ${INTERNAL_DNS4_LIST}"
        restore_dns
    fi

    # reset routing
    if [ -n "${SPLIT_INCLUDE}" ] && [ -n "${TUNDEV}" ]; then
        logMessage "Split tunnel with explicit split routes:"
        # split tunnel: remove specific tunnel routes
        for N in ${SPLIT_INCLUDE}; do
            # make sure target network is in correct format
            TARGET_ADDRESS=$(dirname $N)
            TARGET_MASKLEN=$(basename $N)
            if [[ ${TARGET_MASKLEN} == *"."* ]]; then TARGET_MASKLEN=$(mask2cdr ${TARGET_MASKLEN}); fi
            
            logMessage "Deleting route ${TARGET_ADDRESS}/${TARGET_MASKLEN} over ${TUNDEV}"
            route delete ${TARGET_ADDRESS}/${TARGET_MASKLEN} -interface ${TUNDEV}
        done
    else
        # full tunnel: remove any applicable exceptions...
        if [ -n "${SPLIT_LOCAL}" ]; then
            logMessage "Full tunnel, deleting local route exceptions"
        fi
        for N in ${SPLIT_LOCAL}; do
            # make sure target network is in correct format
            TARGET_ADDRESS=$(dirname $N)
            TARGET_MASKLEN=$(basename $N)
            if [[ ${TARGET_MASKLEN} == *"."* ]]; then TARGET_MASKLEN=$(mask2cdr ${TARGET_MASKLEN}); fi
            
            if [[ ${TARGET_ADDRESS} == "0.0.0.0" ]]; then
                logMessage "WARNING: Server included default route in split local. Ignoring it..."
                continue
            fi
            
            logMessage "Deleting route ${TARGET_ADDRESS}/${TARGET_MASKLEN} over ${LOCAL_IF}"
            route delete ${TARGET_ADDRESS}/${TARGET_MASKLEN} -interface ${LOCAL_IF}
        done

        if [ -n "${TUNDEV}" ]; then
            # deleting route to remote network (slicing)
            logMessage "Deleting primary route to remote network"
            logMessage "Checking route to network ${REMOTE_ADDR4}/${REMOTE_NETMASK4}"
            SLICED_REMOTE_ROUTES=($(${SLICING_TOOL} ${REMOTE_ADDR4}/${REMOTE_NETMASK4} ${LOCAL_NETWORKS}))
            for TARGET_CIDR in ${SLICED_REMOTE_ROUTES[@]}; do
                # setting route to remote network
                logMessage "Deleting route ${TARGET_CIDR} over ${TUNDEV}"
                route delete ${TARGET_CIDR} -interface ${TUNDEV}
            done
        fi
    fi
    
    # remove host route to VPN server
    logMessage "Deleting route to VPN gateway at ${REMOTE_ADDR}"
    route delete ${REMOTE_ADDR}
}

##########################################################################################
#
# START OF SCRIPT
#
##########################################################################################
trap "" TSTP
trap "" HUP
trap "" INT
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin

readonly SCRIPT_FILE=$0
readonly SCRIPT_LOG_FILE="${SCRIPT_FILE%%.*}.log"
readonly SCRIPT_PATH="${0%/*}"

# process parameters to script.
# set defaults, if not already set before
if [ -z ${USE_REMOTE_DNS} ]; then
    USE_REMOTE_DNS="false"
fi
if [ -z ${USE_REMOTE_DNS_FOR_DOMAINS_ONLY} ]; then
    USE_REMOTE_DNS_FOR_DOMAINS_ONLY="false"
fi

readonly USE_REMOTE_DNS
readonly USE_REMOTE_DNS_FOR_DOMAINS_ONLY

readonly LOCAL_IF=`netstat -finet -rn | awk '($1 == "default" && substr($6,1,2) == "en"){print $6; exit}'` # only allow enX interfaces here
readonly LOCAL_IP=$(ipconfig getifaddr ${LOCAL_IF})
readonly LOCAL_MASK=$(ipconfig getoption ${LOCAL_IF} subnet_mask)
readonly LOCAL_GW=`netstat -rn -f inet | awk '(($1 == "default" || $1 == "0.0.0.0" ) && $6 == "'${LOCAL_IF}'") {print $2; exit}'`
readonly LOCAL_NETWORKS="${LOCAL_IP}/${LOCAL_MASK},${LOCAL_GW}"

readonly REASON=$1

if [ -z "$REASON" ]; then
    logMessage "ERROR: this script must be called from racoon"
    exit 1
fi

logMessage "======================================= $REASON starting ======================================="

# print available parameters
print_variables

chmod 644 ${SCRIPT_LOG_FILE}

set +e # "grep" will return error status (1) if no matches are found, so don't fail on individual errors

# check for valid VPN address, if available
echo ${INTERNAL_ADDR4} | grep -q '[0-9]' || {
    logMessage "phase1_up_down: error: invalid INTERNAL_ADDR4."
    exit 1
}

set -e # resume failing on error

if [ -z "$LOCAL_GW" ]; then
    logMessage "Could not find local default gateway. Exiting."
    exit 1
fi

case "$REASON" in
    phase1_up)
        phase1_up
        ;;
    phase1_down)
        phase1_down
        ;;
    *)
        logMessage "unknown reason '$REASON'. Maybe racoon-script is out of date"
        exit 1
        ;;
esac

logMessage "======================================= $REASON completed successfully ======================================="
exit 0


