configuration-templates – Rev 23

Subversion Repositories:
Rev:
#!/bin/sh
###########################################################################
##  Copyright (C) Wizardry and Steamworks 2017 - License: GNU GPLv3      ##
##  Please see: http://www.gnu.org/licenses/gpl.html for legal details,  ##
##  rights of fair usage, the disclaimer and warranty conditions.        ##
###########################################################################
# openvpn-osx-tuntap.sh                                                   #
# Up / Down script for OpenVPN running on OSX as a client.                #
# The OpenVPN server is expected to serve clients with IP addresses via   #
# a previously configured DHCP server.                                    #
# Note that this script does not replace the default route but instead    #
# uses the Mac OS capability of using scoped DNS servers to make the      #
# remote network available whilst preserving the default route.           #
#                                                                         #
# Based on:                                                               #
# - 2006-09-21, Ben Low - original version                                #
# - Nick Williams - for TunnelBrick                                       #
# - Jonathan K. Bullard - additions for Mountain Lion                     #
###########################################################################

###########################################################################
#                            CONFIGURATION                                #
###########################################################################
# A MAC address for the tunnel interface.
STATIC_TUNTAP_MAC_ADDRESS="42:75:e2:67:43:db"
# Whether to prepend domain names instead of replacing the exiting ones.
PREPEND_DOMAIN_NAME="false"

###########################################################################
#                             INTERNALS                                   #
###########################################################################

# Regular expression for matching IP addresses.
IPRX="(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"
# Domain name regular expression.
DOMRX="(?:[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]|[A-Za-z0-9])"

###########################################################################
# Utility function to flush the DNS cache on various Mac OS releases.     #
###########################################################################
flushDNSCache()
{
    if [ "${OSVER}" = "10.4" ] ; then
                if [ -f /usr/sbin/lookupd ] ; then
                        set +e # we will catch errors from lookupd
                        /usr/sbin/lookupd -flushcache
                        set -e # bash should again fail on errors
                fi
        else
                if [ -f /usr/bin/dscacheutil ] ; then
                        set +e # we will catch errors from dscacheutil
                        /usr/bin/dscacheutil -flushcache
                        set -e # bash should again fail on errors
                fi
                if [ -f /usr/sbin/discoveryutil ] ; then
                        set +e # we will catch errors from discoveryutil
                        /usr/sbin/discoveryutil udnsflushcaches
                        /usr/sbin/discoveryutil mdnsflushcache
                        set -e # bash should again fail on errors
                fi
                set +e # "grep" will return error status (1) if no matches are found, so don't fail on individual errors
                hands_off_ps="$( ps -ax | grep HandsOffDaemon | grep -v grep.HandsOffDaemon )"
                set -e # We instruct bash that it CAN again fail on errors
                if [ -z "${hands_off_ps}" ] ; then
                        if [ -f /usr/bin/killall ] ; then
                                set +e # ignore errors if mDNSResponder isn't currently running
                                /usr/bin/killall -HUP mDNSResponder
                                set -e # bash should again fail on errors
                        fi
                fi
        fi
}

###########################################################################
# Sets all dynamic DHCP options on the tuntap interface.                  #
###########################################################################
setDnsServersAndDomainName()
{
    readonly PSID="DHCP-$dev"

    # Set up the DYN_* variables to contain what is asked for (dynamically, by a 'push' directive, for example)
        declare -a vDNS=("${!1}")
        declare -a vSMB=("${!3}")
        declare -a vSD=("${!4}")

        if [ ${#vDNS[*]} -eq 0 ] ; then
                readonly DYN_DNS_SA=""
        else
                readonly DYN_DNS_SA="${!1}"
        fi
        
        if [ ${#vSMB[*]} -eq 0 ] ; then
                readonly DYN_SMB_WA=""
        else
                readonly DYN_SMB_WA="${!3}"
        fi

        if [ ${#vSD[*]} -eq 0 ] ; then
                readonly DYN_DNS_SD=""
        else
                readonly DYN_DNS_SD="${!4}"
        fi
        
        DYN_DNS_DN="$2"
    
    # set up the FIN_* variables with what we want to set things to
        # Three FIN_* variables are simple -- no aggregation is done for them
        if [ ! -z "${DYN_DNS_DN}" ] ; then
                readonly FIN_DNS_DN="${DYN_DNS_DN}"
        else
                readonly FIN_DNS_DN=""
        fi
        
        if [ ! -z "${DYN_SMB_NN}" ] ; then
                readonly FIN_SMB_NN="${DYN_SMB_NN}"
        else
                readonly FIN_SMB_NN=""
        fi
        
        if [ ! -z "${DYN_SMB_WG}" ] ; then
                readonly FIN_SMB_WG="${DYN_SMB_WG}"
        else
                readonly FIN_SMB_WG=""
        fi

        # DNS ServerAddresses (FIN_DNS_SA) are aggregated for 10.4 and 10.5
        if [ ${#vDNS[*]} -eq 0 ] ; then
                readonly FIN_DNS_SA=""
        else
                case "${OSVER}" in
                        10.4 | 10.5 )
                                # We need to remove duplicate DNS entries, so that our reference list matches MacOSX's
                                SDNS="$( echo -n "${DYN_DNS_SA}" | tr ' ' '\n' )"
                i=0
                                for n in "${vDNS[@]}" ; do
                                        if echo -n "${SDNS}" | grep -q "${n}" ; then
                                                unset vDNS[${i}]
                                        fi
                    let i++
                                done
                                if [ ${#vDNS[*]} -gt 0 ] ; then
                                        readonly FIN_DNS_SA="$( echo -n "${DYN_DNS_SA}" | sed s/"${vDNS[*]}"//g )"
                                else
                                        readonly FIN_DNS_SA="${DYN_DNS_SA}"
                                fi
                                ;;
                        * )
                                # Do nothing - in 10.6 and higher -- we don't aggregate our configurations, apparently
                                readonly FIN_DNS_SA="${DYN_DNS_SA}"
                                ;;
                esac
        fi

        # SMB WINSAddresses (FIN_SMB_WA) are aggregated for 10.4 and 10.5
        if [ ${#vSMB[*]} -eq 0 ] ; then
                readonly FIN_SMB_WA=""
        else
                case "${OSVER}" in
                        10.4 | 10.5 )
                                # We need to remove duplicate SMB entries, so that our reference list matches MacOSX's
                                SSMB="$( echo -n "${DYN_SMB_WA}" | tr ' ' '\n' )"
                i=0
                                for n in "${vSMB[@]}" ; do
                                        if echo -n "${SSMB}" | grep -q "${n}" ; then
                                                unset vSMB[${i}]
                                        fi
                    let i++
                                done
                                if [ ${#vSMB[*]} -gt 0 ] ; then
                                        readonly FIN_SMB_WA="$( echo -n "${DYN_SMB_WA}" | sed s/"${vSMB[*]}"//g )"
                                else
                                        readonly FIN_SMB_WA="${DYN_SMB_WA}"
                                fi
                                ;;
                        * )
                                # Do nothing - in 10.6 and higher -- we don't aggregate our configurations, apparently
                                readonly FIN_SMB_WA="${DYN_SMB_WA}"
                                ;;
                esac
        fi

        # DNS SearchDomains (FIN_DNS_SD) is treated specially
        #
        # OLD BEHAVIOR:
        #     if SearchDomains was not set manually, we set SearchDomains to the DomainName
        #     else
        #          In OS X 10.4-10.5, we add the DomainName to the end of any manual SearchDomains (unless it is already there)
        #          In OS X 10.6+, if SearchDomains was entered manually, we ignore the DomainName 
        #                         else we set SearchDomains to the DomainName
        #
        # NEW BEHAVIOR (done if ARG_PREPEND_DOMAIN_NAME is "true"):
        #
        #     if SearchDomains was entered manually, we do nothing
        #     else we  PREpend new SearchDomains (if any) to the existing SearchDomains (NOT replacing them)
        #          and PREpend DomainName to that
        #
        #              (done if ARG_PREPEND_DOMAIN_NAME is "false" and there are new SearchDomains from DOMAIN-SEARCH):
        #
        #     if SearchDomains was entered manually, we do nothing
        #     else we  PREpend any new SearchDomains to the existing SearchDomains (NOT replacing them)
        #
        #     This behavior is meant to behave like Linux with Network Manager and Windows
        if "${PREPEND_DOMAIN_NAME}" ; then
                        if [ ! -z "${DYN_DNS_SD}" ] ; then
                readonly TMP_DNS_SD="${DYN_DNS_SD}"
                        if [ ! -z "${FIN_DNS_DN}" -a  "${FIN_DNS_DN}" != "localdomain" ]; then
                    if ! echo -n "${TMP_DNS_SD}" | tr ' ' '\n' | grep -q "${FIN_DNS_DN}" ; then
                        readonly FIN_DNS_SD="$( echo -n "${FIN_DNS_DN}" | sed s/"${TMP_DNS_SD}"//g )"
                    else
                        readonly FIN_DNS_SD="${TMP_DNS_SD}"
                    fi
                else
                                readonly FIN_DNS_SD="${TMP_DNS_SD}"
                        fi
            else
                readonly FIN_DNS_SD="${DYN_DNS_SD}"
            fi
        else
                if [ ! -z "${DYN_DNS_SD}" ] ; then
                        readonly FIN_DNS_SD="${DYN_DNS_SD}"
                else
            if [ ! -z "${FIN_DNS_DN}" -a "${FIN_DNS_DN}" != "localdomain" ] ; then
                case "${OSVER}" in
                    10.4 | 10.5 )
                        readonly FIN_DNS_SD="${FIN_DNS_DN}"
                        ;;
                    * )
                        readonly FIN_DNS_SD="${FIN_DNS_DN}"
                        ;;
                esac
            else
                readonly FIN_DNS_SD=""
            fi
                fi
        fi

    # Set up SKP_* variables to inhibit scutil from making some changes
        # SKP_DNS_* and SKP_SMB_* are used to comment out individual items 
    # that are not being set
        if [ -z "${FIN_DNS_DN}" ] ; then
                SKP_DNS_DN="#"
        else
                SKP_DNS_DN=""
        fi
        if [ -z "${FIN_DNS_SA}" ] ; then
                SKP_DNS_SA="#"
        else
                SKP_DNS_SA=""
        fi
        if [ -z "${FIN_DNS_SD}" ] ; then
                SKP_DNS_SD="#"
        else
                SKP_DNS_SD=""
        fi
        if [ -z "${FIN_SMB_NN}" ] ; then
                SKP_SMB_NN="#"
        else
                SKP_SMB_NN=""
        fi
        if [ -z "${FIN_SMB_WG}" ] ; then
                SKP_SMB_WG="#"
        else
                SKP_SMB_WG=""
        fi
        if [ -z "${FIN_SMB_WA}" ] ; then
                SKP_SMB_WA="#"
        else
                SKP_SMB_WA=""
        fi
        
        # if any DNS items should be set, set all that have values
        if [ "${SKP_DNS_DN}${SKP_DNS_SA}${SKP_DNS_SD}" = "###" ] ; then
                readonly SKP_DNS="#"
        else
                readonly SKP_DNS=""
                if [ ! -z "${FIN_DNS_DN}" ] ; then
                        SKP_DNS_DN=""
                fi
                if [ ! -z "${FIN_DNS_SA}" ] ; then
                        SKP_DNS_SA=""
                fi
                if [ ! -z "${FIN_DNS_SD}" ] ; then
                        SKP_DNS_SD=""
                fi
        fi

        # if any SMB items should be set, set all that have values
        if [ "${SKP_SMB_NN}${SKP_SMB_WG}${SKP_SMB_WA}" = "###" ] ; then
                readonly SKP_SMB="#"
        else
                readonly SKP_SMB=""
                if [ ! -z "${FIN_SMB_NN}" ] ; then
                        SKP_SMB_NN=""
                fi
                if [ ! -z "${FIN_SMB_WG}" ] ; then
                        SKP_SMB_WG=""
                fi
                if [ ! -z "${FIN_SMB_WA}" ] ; then
                        SKP_SMB_WA=""
                fi
        fi

        readonly SKP_DNS_SA SKP_DNS_SD SKP_DNS_DN
        readonly SKP_SMB_NN SKP_SMB_WG SKP_SMB_WA
        
    # special-case fiddling:
        # 10.8+ : ServerAddresses and SearchDomains must be set via the Setup:
    #     key in addition to the State: key
        # 10.7  : if ServerAddresses or SearchDomains are manually set,
    #     ServerAddresses and SearchDomains must be similarly set with the
    #     Setup: key in addition to the State: key
        case "${OSVER}" in
                10.4 | 10.5 | 10.6 | 10.7 )
                        readonly SKP_SETUP_DNS="#"
                        ;;
                * )
                        readonly SKP_SETUP_DNS=""
                        ;;
        esac
        
    # Set all parameters.
        /usr/sbin/scutil >/dev/null 2>&1 <<-EOF
                open
                
                # Initialize the new DNS map via State:
                ${SKP_DNS}d.init
                ${SKP_DNS}${SKP_DNS_SA}d.add ServerAddresses * ${FIN_DNS_SA}
                ${SKP_DNS}${SKP_DNS_SD}d.add SearchDomains   * ${FIN_DNS_SD}
                ${SKP_DNS}${SKP_DNS_DN}d.add DomainName        ${FIN_DNS_DN}
        ${SKP_DNS}${SKP_DNS_DN}d.add SupplementalMatchDomains * ${FIN_DNS_DN}
                ${SKP_DNS}set State:/Network/Service/${PSID}/DNS

                # If necessary, initialize the new DNS map via Setup: also
                ${SKP_SETUP_DNS}${SKP_DNS}d.init
                ${SKP_SETUP_DNS}${SKP_DNS}${SKP_DNS_SA}d.add ServerAddresses * ${FIN_DNS_SA}
                ${SKP_SETUP_DNS}${SKP_DNS}${SKP_DNS_SD}d.add SearchDomains   * ${FIN_DNS_SD}
                ${SKP_SETUP_DNS}${SKP_DNS}${SKP_DNS_DN}d.add DomainName        ${FIN_DNS_DN}
                ${SKP_SETUP_DNS}${SKP_DNS}set Setup:/Network/Service/${PSID}/DNS

                # Initialize the SMB map
                ${SKP_SMB}d.init
                ${SKP_SMB}${SKP_SMB_NN}d.add NetBIOSName     ${FIN_SMB_NN}
                ${SKP_SMB}${SKP_SMB_WG}d.add Workgroup       ${FIN_SMB_WG}
                ${SKP_SMB}${SKP_SMB_WA}d.add WINSAddresses * ${FIN_SMB_WA}
                ${SKP_SMB}set State:/Network/Service/${PSID}/SMB

                quit
EOF

}

# If OpenVPN has not brought up the device, then terminate.
if [ -z "$dev" ]; then 
        echo "$0: \$dev not defined, exiting"; 
        exit 1; 
fi

# OpenVPN passes $script_type set to the script method.
case "$script_type" in
    up)

        # Set the MAC address for the tuntap device for static DHCP bindings
        /sbin/ifconfig "$dev" ether $STATIC_TUNTAP_MAC_ADDRESS
        # Set the interface to DHCP
        /usr/sbin/ipconfig set "$dev" DHCP

        {
            # Issue the waitall command - even if it does not wait.
            /usr/sbin/ipconfig waitall
            
            unset PACKET
            
            # Spin and check for packet from the tap device
            set +e
            n=0
            while [ -z "$PACKET" -a $n -lt 60 ] ; do
                        PACKET="$( /usr/sbin/ipconfig getpacket "$dev" )"
                let n++
                sleep 1
            done
            set -e
            
            # Get packet to set options
            if [ -z "$PACKET" ]; then
                exit 1
            fi
            
            unset DOMAIN_NAME
            unset DOMAIN_NAME_SERVERS
            unset SEARCH_DOMAINS
            unset WINS_SERVERS
            
            set +e
            # Get domain name
                        DOMAIN_NAME="$( echo -n "$PACKET" | grep "domain_name " | grep -Eo ": $DOMRX" | grep -Eo "$DOMRX" | tr -d [:space:] )"
            
            # Get nameservers
            DOMAIN_NAME_SERVERS_INDEX=1
            for DOMAIN_NAME_SERVER in $( echo -n "$PACKET" | grep "domain_name_server" | grep -Eo "\{($IPRX)(, $IPRX)*\}" | grep -Eo "($IPRX)" ); do
                DOMAIN_NAME_SERVERS[DOMAIN_NAME_SERVERS_INDEX-1]=$DOMAIN_NAME_SERVER
                let DOMAIN_NAME_SERVERS_INDEX++
            done
            
            # Get search domains
            SEARCH_DOMAINS_INDEX=1
                        for SEARCH_DOMAIN in $( echo -n "$PACKET" | grep "search_domain" | grep -Eo "\{($DOMRX)(, $DOMRX)*\}" | grep -Eo "($DOMRX)" ); do
                                SEARCH_DOMAINS[SEARCH_DOMAINS_INDEX-1]=$SEARCH_DOMAIN
                                let SEARCH_DOMAINS_INDEX++
                        done
            
            # Get WINS servers
            WINS_SERVERS_INDEX=1
                        for WINS_SERVER in $( echo -n "$PACKET" | grep "nb_over_tcpip_name_server" | grep -Eo "\{($IPRX)(, $IPRX)*\}" | grep -Eo "($IPRX)" ); do
                                WINS_SERVERS[WINS_SERVERS_INDEX-1]=$WINS_SERVER
                                let WINS_SERVERS_INDEX++
                        done
            
                        if [ ${#DOMAIN_NAME_SERVERS[*]} -gt 0 -a "$DOMAIN_NAME" ]; then
                                setDnsServersAndDomainName DOMAIN_NAME_SERVERS[@] "$DOMAIN_NAME" WINS_SERVERS[@] SEARCH_DOMAINS[@]
            elif [ ${#DOMAIN_NAME_SERVERS[*]} -gt 0 ]; then
                                setDnsServersAndDomainName DOMAIN_NAME_SERVERS[@] "$DEFAULT_DOMAIN_NAME" WINS_SERVERS[@] SEARCH_DOMAINS[@]
            else
                exit 1
                        fi
            
            set -e
            
                sleep 1
            
                flushDNSCache
            
                        exit 0
        } &
    ;;
    down)
        sleep 1
        
        /usr/sbin/scutil >/dev/null 2>&1 <<-EOF
            open
            remove State:/Network/Service/DHCP-$dev/IPv4
            remove State:/Network/Service/DHCP-$dev/DNS
            close
EOF
        
        flushDNSCache
        
        exit 0
    ;;
    *) 
        echo "$0: invalid script_type" && exit 1 
    ;;
esac