#!/bin/bash
# List of parameters passed through environment
#* reason                       -- why this script was called, one of: pre-init connect disconnect reconnect
#* UNIQUE_IDENTIFIER            -- unique identifier for active account (coming from Shimo)
#* SHIMO_SPLIT_DNS              -- list of search domains
#* VPNGATEWAY                   -- vpn gateway address (always present)
#* TUNDEV                       -- tunnel device (always present)
#* INTERNAL_IP4_ADDRESS         -- address (always present)
#* INTERNAL_IP4_MTU             -- mtu (often unset)
#* INTERNAL_IP4_NETMASK         -- netmask (often unset)
#* INTERNAL_IP4_NETMASKLEN      -- netmask length (often unset)
#* INTERNAL_IP4_NETADDR         -- address of network (only present if netmask is set)
#* INTERNAL_IP4_DNS             -- list of dns servers
#* INTERNAL_IP4_NBNS            -- list of wins servers
#* INTERNAL_IP6_ADDRESS         -- IPv6 address
#* INTERNAL_IP6_NETMASK         -- IPv6 netmask
#* INTERNAL_IP6_DNS             -- IPv6 list of dns servers
#* CISCO_DEF_DOMAIN             -- default domain name
#* CISCO_BANNER                 -- banner from server
#* CISCO_SPLIT_DNS              -- comma-separated list of domain names with split DNS
#* CISCO_SPLIT_INC              -- number of networks in split-network-list
#* CISCO_SPLIT_INC_%d_ADDR      -- network address
#* CISCO_SPLIT_INC_%d_MASK      -- subnet mask (for example: 255.255.255.0)
#* CISCO_SPLIT_INC_%d_MASKLEN   -- subnet masklen (for example: 24)
#* CISCO_SPLIT_INC_%d_PROTOCOL  -- protocol (often just 0)
#* CISCO_SPLIT_INC_%d_SPORT     -- source port (often just 0)
#* CISCO_SPLIT_INC_%d_DPORT     -- destination port (often just 0)
#* CISCO_IPV6_SPLIT_INC         -- number of networks in IPv6 split-network-list
#* CISCO_IPV6_SPLIT_INC_%d_ADDR -- IPv6 network address
#* CISCO_IPV6_SPLIT_INC_$%d_MASKLEN -- IPv6 subnet masklen

# FIXMEs:

# Section A: route handling

# 1) The 3 values CISCO_SPLIT_INC_%d_PROTOCOL/SPORT/DPORT are currently being ignored
#   In order to use them, we'll probably need os specific solutions
#   * Linux: iptables -t mangle -I PREROUTING <conditions> -j ROUTE --oif $TUNDEV
#       This would be an *alternative* to changing the routes (and thus 2) and 3)
#       shouldn't be relevant at all)
# 2) There are two different functions to set routes: generic routes and the
#   default route. Why isn't the defaultroute handled via the generic route case?
# 3) In the split tunnel case, all routes but the default route might get replaced
#   without getting restored later. We should explicitely check and save them just
#   like the defaultroute
# 4) Replies to a dhcp-server should never be sent into the tunnel

# Section B: Split DNS handling

# 1) Maybe dnsmasq can do something like that
# 2) Parse dns packets going out via tunnel and redirect them to original dns-server

#env | sort
#set -x

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

# =========== script (variable) setup ====================================

export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin

if [ -n "${UNIQUE_IDENTIFIER}" ] ; then
    readonly SCRIPT_FILE=$0
    readonly SCRIPT_LOG_FILE="${SCRIPT_FILE%%.*}.log"

    readonly SCRIPT_PATH="${0%/*}"
    readonly DEFAULT_ROUTE_FILE="${SCRIPT_PATH}/${UNIQUE_IDENTIFIER}_defaultroute_IP4.old"
    readonly DEFAULT_ROUTE_FILE_IP6="${SCRIPT_PATH}/${UNIQUE_IDENTIFIER}_defaultroute_IP6.old"
else
    readonly SCRIPT_LOG_FILE="/tmp/vpnc_${reason}.log"

    readonly DEFAULT_ROUTE_FILE="/tmp/vpnc_defaultroute.old"
    readonly DEFAULT_ROUTE_FILE_IP6="/tmp/vpnc_defaultroute_IP6.old"
fi

readonly HOOKS_DIR=/etc/vpnc

# replace commas with whitespaces in list of DNS servers (workaround)
if [ -n "${CISCO_SPLIT_DNS}" ]; then CISCO_SPLIT_DNS="${CISCO_SPLIT_DNS//,/ }"; fi

# some systems, eg. Darwin & FreeBSD, prune /var/run on boot
if [ ! -d "/var/run/vpnc" ]; then
    mkdir -p /var/run/vpnc
    [ -x /sbin/restorecon ] && /sbin/restorecon /var/run/vpnc
fi

logMessage()
{
    echo "$(date '+%Y-%m-%d %T') *Shimo: "${@} >> "${SCRIPT_LOG_FILE}"
}

print_variables()
{
    if [ -n "${UNIQUE_IDENTIFIER}" ]; then logMessage "UNIQUE_IDENTIFIER = ${UNIQUE_IDENTIFIER}"; fi
    if [ -n "${VPNGATEWAY}" ]; then logMessage "VPNGATEWAY = ${VPNGATEWAY}"; fi
    if [ -n "${TUNDEV}" ]; then logMessage "TUNDEV = ${TUNDEV}"; fi
    if [ -n "${INTERNAL_IP4_ADDRESS}" ]; then logMessage "INTERNAL_IP4_ADDRESS = ${INTERNAL_IP4_ADDRESS}"; fi
    if [ -n "${INTERNAL_IP4_NETMASK}" ]; then logMessage "INTERNAL_IP4_NETMASK = ${INTERNAL_IP4_NETMASK}"; fi
    if [ -n "${INTERNAL_IP4_NETMASKLEN}" ]; then logMessage "INTERNAL_IP4_NETMASKLEN = ${INTERNAL_IP4_NETMASKLEN}"; fi
    if [ -n "${INTERNAL_IP4_NETADDR}" ]; then logMessage "INTERNAL_IP4_NETADDR = ${INTERNAL_IP4_NETADDR}"; fi
    if [ -n "${INTERNAL_IP4_DNS}" ]; then logMessage "INTERNAL_IP4_DNS = ${INTERNAL_IP4_DNS}"; fi
    if [ -n "${INTERNAL_IP4_NBNS}" ]; then logMessage "INTERNAL_IP4_NBNS = ${INTERNAL_IP4_NBNS}"; fi
    if [ -n "${INTERNAL_IP6_ADDRESS}" ]; then logMessage "INTERNAL_IP6_ADDRESS = ${INTERNAL_IP6_ADDRESS}"; fi
    if [ -n "${INTERNAL_IP6_NETMASK}" ]; then logMessage "INTERNAL_IP6_NETMASK = ${INTERNAL_IP6_NETMASK}"; fi
    if [ -n "${INTERNAL_IP6_DNS}" ]; then logMessage "INTERNAL_IP6_DNS = ${INTERNAL_IP6_DNS}"; fi
    if [ -n "${CISCO_DEF_DOMAIN}" ]; then logMessage "CISCO_DEF_DOMAIN = ${CISCO_DEF_DOMAIN}"; fi
    if [ -n "${CISCO_SPLIT_DNS}" ]; then logMessage "CISCO_SPLIT_DNS = ${CISCO_SPLIT_DNS}"; fi
    if [ -n "${SHIMO_SPLIT_DNS}" ]; then logMessage "SHIMO_SPLIT_DNS = ${SHIMO_SPLIT_DNS}"; fi
    if [ -n "${CISCO_SPLIT_INC}" ]; then
        i=0
        while [ $i -lt $CISCO_SPLIT_INC ] ; do
            eval NETWORK="\${CISCO_SPLIT_INC_${i}_ADDR}"
            eval NETMASK="\${CISCO_SPLIT_INC_${i}_MASK}"
            eval NETMASKLEN="\${CISCO_SPLIT_INC_${i}_MASKLEN}"
            logMessage "CISCO_SPLIT_INC_${i} = ${NETWORK}/${NETMASKLEN} (${NETMASK})";
            i=`expr $i + 1`
        done
    fi
    if [ -n "${CISCO_IPV6_SPLIT_INC}" ]; then
        i=0
        while [ $i -lt $CISCO_IPV6_SPLIT_INC ] ; do
            eval NETWORK="\${CISCO_IPV6_SPLIT_INC_${i}_ADDR}"
            eval NETMASKLEN="\${CISCO_IPV6_SPLIT_INC_${i}_MASKLEN}"
            logMessage "CISCO_IPV6_SPLIT_INC_${i} = ${NETWORK}/${NETMASKLEN}";
            i=`expr $i + 1`
        done
    fi
    if [ -n "${USE_REMOTE_DNS}" ]; then logMessage "USE_REMOTE_DNS = ${USE_REMOTE_DNS}"; fi
    if [ -n "${USE_REMOTE_DNS_FOR_DOMAINS_ONLY}" ]; then logMessage "USE_REMOTE_DNS_FOR_DOMAINS_ONLY = ${USE_REMOTE_DNS_FOR_DOMAINS_ONLY}"; fi
}

# =========== script hooks =================================================

run_hooks() {
    HOOK="$1"

    if [ -d ${HOOKS_DIR}/${HOOK}.d ]; then
        for script in ${HOOKS_DIR}/${HOOK}.d/* ; do
        [ -f $script ] && . $script
        done
    fi
}

# =========== tunnel interface handling ====================================

do_ifconfig() {
    if [ -n "$INTERNAL_IP4_MTU" ]; then
        MTU=$INTERNAL_IP4_MTU
    fi

    if [ -z "$MTU" ]; then
        MTU=1412
    fi

    # Point to point interface require a netmask of 255.255.255.255 on some systems
    ifconfig "$TUNDEV" inet "$INTERNAL_IP4_ADDRESS" "$INTERNAL_IP4_ADDRESS" netmask 255.255.255.255 mtu ${MTU} up

    # set the route as specified by the remote server
    if [ -n "$INTERNAL_IP4_NETMASK" ]; then
        set_network_route $INTERNAL_IP4_NETADDR $INTERNAL_IP4_NETMASK $INTERNAL_IP4_NETMASKLEN
    fi

    # If the netmask is provided, it contains the address _and_ netmask
    if [ -n "$INTERNAL_IP6_ADDRESS" ] && [ -z "$INTERNAL_IP6_NETMASK" ]; then
        INTERNAL_IP6_NETMASK="$INTERNAL_IP6_ADDRESS/128"
    fi
    if [ -n "$INTERNAL_IP6_NETMASK" ]; then
        # Unlike for Legacy IP, we don't specify the dest_address
        # here on *BSD. OpenBSD for one will refuse to accept
        # incoming packets to that address if we do.
        # OpenVPN does the same (gives dest_address for Legacy IP
        # but not for IPv6).
        ifconfig "$TUNDEV" inet6 $INTERNAL_IP6_NETMASK mtu $MTU up
    fi
}

# =========== route handling ====================================

set_vpngateway_route() {
    route add -host "$VPNGATEWAY" "$DEFAULT_GW"
}

del_vpngateway_route() {
    route delete -host "$VPNGATEWAY" "$DEFAULT_GW"
}

set_default_route() {
    logMessage "Backing up current IPv4 default route $DEFAULT_GW"
    echo "$DEFAULT_GW" > "$DEFAULT_ROUTE_FILE"
    logMessage "Deleting current IPv4 default route $DEFAULT_GW"
    route delete default "$DEFAULT_GW"
    logMessage "Adding new IPv4 default route $INTERNAL_IP4_ADDRESS"
    route add default "$INTERNAL_IP4_ADDRESS"
}

set_network_route() {    
    NETWORK="$1"
    NETMASK="$2"
    NETMASKLEN="$3"
    if [[ "$NETWORK" == "$INTERNAL_IP4_ADDRESS" ]]; then
        return
    fi
    
    del_network_route "$NETWORK" "$NETMASK" "$NETMASKLEN"
    
    if [ -n "$NETWORK" ] && [ -n "$NETMASK" ]; then
        route add "$NETWORK" -netmask "$NETMASK" "$INTERNAL_IP4_ADDRESS"
    elif [ -n "$NETWORK" ]; then
        route add "$NETWORK" "$INTERNAL_IP4_ADDRESS"
    fi
}

reset_default_route() {
    if [ -s "$DEFAULT_ROUTE_FILE" ]; then
        logMessage "Deleting current IPv4 default route $DEFAULT_GW"
        route delete default "$DEFAULT_GW"
        
        RESTORE_GW=`cat "$DEFAULT_ROUTE_FILE"`
        logMessage "Restoring IPv4 default route $RESTORE_GW from backup"
        route add default "$RESTORE_GW"
        logMessage "Purging backup of IPv4 default route"
        rm -f -- "$DEFAULT_ROUTE_FILE"
    else
        logMessage "Could not find backup of IPv4 default route. Skipping!"
    fi
}

del_network_route() {
    # routes are deleted automatically on device shutdown
    return
}

set_ipv6_default_route() {
    logMessage "Backing up current IPv6 default route $DEFAULT_GW_IP6"
    echo "$DEFAULT_GW_IP6" > "$DEFAULT_ROUTE_FILE_IP6"
    logMessage "Deleting current IPv6 default route $DEFAULT_GW_IP6"
    route delete -inet6 default "$DEFAULT_GW_IP6"
    logMessage "Adding new IPv6 default route $INTERNAL_IP6_ADDRESS"
    route add -inet6 default "$INTERNAL_IP6_ADDRESS"
}

set_ipv6_network_route() {
    NETWORK="$1"
    NETMASK="$2"
    route add -inet6 "$NETWORK/$NETMASK" "$INTERNAL_IP6_ADDRESS"
}

reset_ipv6_default_route() {
    if [ -s "$DEFAULT_ROUTE_FILE_IP6" ]; then
        logMessage "Deleting current IPv6 default route $DEFAULT_GW_IP6"
        route delete -inet6 default "$DEFAULT_GW_IP6"
        
        RESTORE_GW_IP6=`cat "$DEFAULT_ROUTE_FILE_IP6"`
        logMessage "Restoring IPv6 default route $RESTORE_GW_IP6 from backup"
        route add -inet6 default "$RESTORE_GW_IP6"
        logMessage "Purging backup of IPv6 default route"
        rm -f -- "$DEFAULT_ROUTE_FILE_IP6"
    else
        logMessage "Could not find backup of IPv6 default route. Skipping!"
    fi
}

del_ipv6_network_route() {
    NETWORK="$1"
    NETMASK="$2"
    route delete -inet6 "$NETWORK/$NETMASK" "$INTERNAL_IP6_ADDRESS"
}

# =========== 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 "$CISCO_SPLIT_INC" ]; then
        if [ $CISCO_SPLIT_INC -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_IP4_DNS. Prepend INTERNAL_IP4_DNS 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_IP4_DNS' to DNS servers '$SERVICE_DNS' of primary interface $SERVICE "
            if [ X"$SERVICE_DNS" != X"$INTERNAL_IP4_DNS" ]; then
                scutil >/dev/null 2>&1 <<-EOF
                open
                    get State:/Network/Service/$SERVICE/DNS
                    d.add ServerAddresses * $INTERNAL_IP4_DNS $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_IP4_ADDRESS"
    fi
    
    # prepare search domains
    if [ -z "$CISCO_SPLIT_DNS" ]; then
        SEARCH_DOMAINS=$CISCO_DEF_DOMAIN
    elif [[ "$CISCO_SPLIT_DNS" == *"$CISCO_DEF_DOMAIN"* ]]; then
        SEARCH_DOMAINS=$CISCO_SPLIT_DNS
    else
        SEARCH_DOMAINS="$CISCO_SPLIT_DNS $CISCO_DEF_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_IP4_ADDRESS
        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 "$CISCO_DEF_DOMAIN" ]; then
            NO_DOMAIN_NAME="#"
        fi
        
        # set DNS settings via State:
        logMessage "Setting default domain $CISCO_DEF_DOMAIN for name servers '$INTERNAL_IP4_DNS' for $TUNDEV"
        scutil >/dev/null 2>&1 <<-EOF
        open
            d.init
            d.add ServerAddresses * $INTERNAL_IP4_DNS
            ${NO_DOMAIN_NAME}d.add DomainName $CISCO_DEF_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_IP4_DNS} ; 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_IP4_NBNS" ]; then
        logMessage "Setting SMB servers '$INTERNAL_IP4_NBNS' for $TUNDEV"
        scutil >/dev/null 2>&1 <<-EOF
        open
            # add SMB information
            d.init
            d.add WINSAddresses * $INTERNAL_IP4_NBNS
            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_IP4_DNS
    # to list of used DNS servers
    if [ -n "$CISCO_SPLIT_INC" ] && ${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_IP4_DNS" ]; then
            scutil >/dev/null 2>&1 <<-EOF
            open
                get State:/Network/Service/$SERVICE/DNS
                d.add ServerAddresses * ${SERVICE_DNS##$INTERNAL_IP4_DNS}
                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"
}

# ========= Toplevel state handling  =======================================

do_pre_init() {
    logMessage "Nothing to do ..."
    return
}

do_connect() {
    if [ -n "$CISCO_BANNER" ]; then
        logMessage "Connect Banner:"
        logMessage "$CISCO_BANNER" | while read LINE ; do logMessage "|" "$LINE" ; done
        logMessage "//END"
    fi

    logMessage "Adding route to VPN gateway $VPNGATEWAY --> $DEFAULT_GW"
    set_vpngateway_route
    logMessage "Configuring tunnel device $TUNDEV with $INTERNAL_IP4_ADDRESS / $INTERNAL_IP6_ADDRESS"
    do_ifconfig

    # set custom IPv4 split routes
    if [ -n "$CISCO_SPLIT_INC" ]; then
        logMessage "Split tunnel with explicit IPv4 split routes:"
        i=0
        while [ $i -lt $CISCO_SPLIT_INC ] ; do
            eval NETWORK="\${CISCO_SPLIT_INC_${i}_ADDR}"
            eval NETMASK="\${CISCO_SPLIT_INC_${i}_MASK}"
            eval NETMASKLEN="\${CISCO_SPLIT_INC_${i}_MASKLEN}"
            logMessage "Adding IPv4 route $NETWORK/$NETMASKLEN --> $INTERNAL_IP4_ADDRESS"
            if [ "$NETWORK" != "0.0.0.0" ]; then
                set_network_route "$NETWORK" "$NETMASK" "$NETMASKLEN"
            else
                set_default_route
            fi
            i=`expr $i + 1`
        done

        # make sure that our IPv4 DNS servers are always reachable, even without a default route
        for i in $INTERNAL_IP4_DNS ; do
            logMessage "Adding IPv4 route to DNS server $i/32 --> $INTERNAL_IP4_ADDRESS"
            set_network_route "$i" "255.255.255.255" "32"
        done
    elif [ -n "$INTERNAL_IP4_ADDRESS" ]; then
        logMessage "Adding primary IPv4 route to remote network"
        set_default_route
    fi

    # set custom IPv6 split routes
    if [ -n "$CISCO_IPV6_SPLIT_INC" ]; then
        logMessage "Split tunnel with explicit IPv6 split routes:"
        i=0
        while [ $i -lt $CISCO_IPV6_SPLIT_INC ] ; do
            eval NETWORK="\${CISCO_IPV6_SPLIT_INC_${i}_ADDR}"
            eval NETMASKLEN="\${CISCO_IPV6_SPLIT_INC_${i}_MASKLEN}"
            logMessage "Adding IPv6 route $NETMASKLEN/$NETMASKLEN --> $INTERNAL_IP6_ADDRESS"
            if [ $NETMASKLEN -lt 128 ]; then
                set_ipv6_network_route "$NETWORK" "$NETMASKLEN"
            else
                set_ipv6_default_route
            fi
            i=`expr $i + 1`
        done

        # make sure that our IPv6 DNS servers are always reachable, even without a default route
        for i in $INTERNAL_IP6_DNS ; do
            logMessage "Adding IPv6 route to DNS server $i/128 --> $INTERNAL_IP6_ADDRESS"
            set_ipv6_network_route "$i" "128"
        done
    elif [ -n "$INTERNAL_IP6_NETMASK" -o -n "$INTERNAL_IP6_ADDRESS" ]; then
        logMessage "Adding primary IPv6 route to remote network"
        set_ipv6_default_route
    fi

    # modify DNS configuration
    if [ -n "${INTERNAL_IP4_DNS}" ]; then
        logMessage "Updating DNS configuration for ${INTERNAL_IP4_DNS}"
        modify_dns
    fi
}

do_disconnect() {
    if [ -n "$CISCO_SPLIT_INC" ]; then
        logMessage "Split tunnel with explicit IPv4 split routes:"
        i=0
        while [ $i -lt $CISCO_SPLIT_INC ] ; do
            eval NETWORK="\${CISCO_SPLIT_INC_${i}_ADDR}"
            eval NETMASK="\${CISCO_SPLIT_INC_${i}_MASK}"
            eval NETMASKLEN="\${CISCO_SPLIT_INC_${i}_MASKLEN}"
            logMessage "Deleting route $NETWORK/$NETMASKLEN --> $INTERNAL_IP4_ADDRESS"
            if [ "$NETWORK" != "0.0.0.0" ]; then
                # FIXME: This doesn't restore previously overwritten
                #        routes.
                del_network_route "$NETWORK" "$NETMASK" "$NETMASKLEN"
            else
                reset_default_route
            fi
            i=`expr $i + 1`
        done
        for i in $INTERNAL_IP4_DNS ; do
            logMessage "Deleting route to DNS server $i/32 --> $INTERNAL_IP4_ADDRESS"
            del_network_route "$i" "255.255.255.255" "32"
        done
    else
        logMessage "Deleting primary IPv4 route to remote network"
        reset_default_route
    fi
    if [ -n "$CISCO_IPV6_SPLIT_INC" ]; then
        logMessage "Split tunnel with explicit IPv6 split routes:"
        i=0
        while [ $i -lt $CISCO_IPV6_SPLIT_INC ] ; do
            eval NETWORK="\${CISCO_IPV6_SPLIT_INC_${i}_ADDR}"
            eval NETMASKLEN="\${CISCO_IPV6_SPLIT_INC_${i}_MASKLEN}"
            logMessage "Deleting IPv6 route $NETMASKLEN/$NETMASKLEN --> $INTERNAL_IP6_ADDRESS"
            if [ $NETMASKLEN -eq 0 ]; then
                reset_ipv6_default_route
            else
                del_ipv6_network_route "$NETWORK" "$NETMASKLEN"
            fi
            i=`expr $i + 1`
        done
        for i in $INTERNAL_IP6_DNS ; do
            logMessage "Deleting IPv6 route to DNS server $i/128 --> $INTERNAL_IP4_ADDRESS"
            del_ipv6_network_route "$i" "128"
        done
    elif [ -n "$INTERNAL_IP6_NETMASK" -o -n "$INTERNAL_IP6_ADDRESS" ]; then
        logMessage "Deleting primary IPv6 route to remote network"
        reset_ipv6_default_route
    fi

    logMessage "Deleting route to VPN gateway at $VPNGATEWAY"
    del_vpngateway_route

    # restore DNS configuration
    if [ -n "$TUNDEV" ]; then
        logMessage "Restoring DNS configuration for ${INTERNAL_IP4_DNS}"
        restore_dns
    fi
}

do_reset_network() {
    if [ -n "$TUNDEV" ]; then        
        logMessage "Resetting network interface ${TUNDEV}";
        if [[ "${TUNDEV}" == "en"* ]]; then
            ifconfig ${TUNDEV} down
            sleep 1
            ifconfig ${TUNDEV} up
        elif [[ "${TUNDEV}" == "tun"* ]] || [[ "${TUNDEV}" == "tap"* ]] || [[ "${TUNDEV}" == "gif"* ]]; then
            ifconfig ${TUNDEV} delete
            
            logMessage "Resetting DNS configuration for ${TUNDEV}";
            restore_dns
        fi
    else
        # general reset of all interfaces
        
        # remove all (10) tun/tap devices
        logMessage "Resetting tun/tap tunnel interfaces...";
        for i in {0..9}
        do
           ifconfig tun${i} delete
           ifconfig tap${i} delete
        done
        
        # remove all (10) gif tunnel devices
        logMessage "Resetting gif tunnel interfaces...";
        for i in {0..9}
        do
           ifconfig gif${i} delete
        done

        # reset all local network interfaces
        for i in `netstat -i | grep en | awk '{print $1}' | uniq`;
        do 
            logMessage "Resetting network interface ${i}...";
            ifconfig ${i} down
            sleep 1
            ifconfig ${i} up
        done
    fi
}

#### Main

if [ -z "$reason" ]; then
    echo "this script must be called from vpnc" 1>&2
    exit 1
fi

logMessage "======================================= ${reason} starting ======================================="

# 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 DEFAULT_GW=`netstat -rn -f inet | awk '/link/ { next; } /^(default|0\.0\.0\.0)/ {print $2; exit}'`
readonly DEFAULT_GW_IP6=`netstat -rn -f inet6 | awk '/link/ { next; } /^default/ {print $2; exit}'`

# print available parameters
print_variables

case "$reason" in
    pre-init)
        run_hooks pre-init
        do_pre_init
        ;;
    connect)
        run_hooks connect
        do_connect
        run_hooks post-connect
        ;;
    disconnect)
        run_hooks disconnect
        do_disconnect
        run_hooks post-disconnect
        ;;
    reset-network)
        do_reset_network
        ;;
    reconnect)
        run_hooks reconnect
        ;;
    *)
        echo "unknown reason '$reason'. Maybe vpnc-script is out of date" 1>&2
        exit 1
        ;;
esac

logMessage "======================================= $reason completed successfully ======================================="

exit 0
