configuration-templates – Rev 25

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 resolution in order to   #
# make the remote network available whilst preserving the default route.  #
#                                                                         #
# To use this script on an OS X OpenVPN client, the following changes to  #
# your OpenVPN configuration are required:                                #
#                                                                         #
# up-restart                                                              #
# up openvpn-osx-tuntap.sh                                                #
# down openvpn-osx-tuntap.sh                                              #
# ipchange openvpn-osx-tuntap.sh                                          #
#                                                                         #
# where openvpn-osx-tuntap.sh is the filesystem path to this script.      #
#                                                                         #
# 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
    ipchange)
        # Set the MAC address for the tuntap device for static DHCP bindings
        /sbin/ifconfig "$dev" ether $STATIC_TUNTAP_MAC_ADDRESS
        # Set the interface to NONE
        /usr/sbin/ipconfig set "$dev" NONE
        # Set the interface to DHCP
        /usr/sbin/ipconfig set "$dev" DHCP
    ;;
    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 NONE
        /usr/sbin/ipconfig set "$dev" NONE
        # 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