#!/bin/sh # Automagical vpnc wrapper to establish a VPN tunnel and routes on *BSD # # Copyright 2003-2005, Daniel Roethlisberger # All rights reserved. # # Redistribution and use, with or without modification, are permitted # provided that the following conditions are met: # 1. Redistributions must retain the above copyright notice, this list of # conditions and the following disclaimer. # 2. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # MacOS X support based on work by Douglas Held . # See http://douglasheld.net/projects/ for details. # # $Id: vpnc-wrapper,v 1.23 2005/01/27 13:17:15 roe Exp $ # The script should work on the following systems: # - FreeBSD 4.x, 5.x, 6.x # - MacOS X / Darwin 7.5.0 (untested, experimental) # Adding support for other systems should be easy, please send me diffs. # # This script is designed to be run manually after network connectivity # to your VPN gateway is up. It will try to automagically handle your # routing, and will try to restore it after disconnection. In most # scenarios, this should now work fine (with or without default gateway, # with or without static routes to your VPN gateway). If it doesn't work # for you, please drop me a line. # # This script does not need to know any IP addresses or interface names. # You should only need to configure vpnc with the correct address of the # VPN gateway in vpnc.conf -- this script will grep it from there. # Note: This may break with DNS round-robin. If your gateway has a DNS # name which resolves to multiple addresses, pick one numerical IP address # and use that in vpnc.conf. # # This script will handle your resolv.conf only if $RESOLVCONF is set to # the path of your resolv.conf. By default, that's /etc/resolv.conf, but # if you are using Tobias Roth's profile.sh (as I do), or your /etc is for # some other reason read-only, you might want to set $RESOLVCONF to # something like /var/run/resolv.conf, and make /etc/resolv.conf a symlink # pointing to that location. If unset, your resolv.conf will not be touched. # # If your VPN gateway cannot be pinged, add the string NO_PING to your # vpnc.conf in a comment somewhere. If that string is present, this script # will not attempt to ping the gateway, and instead assume it is up and # reachable. # # Exit status of 1 indicates permanent errors (eg., configuration problems) # Exit status of 2 indicates temporary errors (eg., no default route found) # # Known bugs and limitations: # - This script assumes that you want to route everything non-local # through the VPN tunnel. This implies that there is only a single # vpnc tunnel connected at any given time, and the script will enforce # that. For more sophisticated routing, such as only routing a certain # subnet through the VPN tunnel, you will find it easier to use vpnc.sh # from the security/vpnc port as a starting point. # - The method to store the original default route as static host route # to the magic address STORE_HACK_ADDR is an ugly hack, but it seems to # be more elegant than storing the route in some temporary file. # configuration PREFIX=/usr/local VPNC=vpnc DEFAULT_CONF=$PREFIX/etc/$VPNC.conf SBIN_VPNC=$PREFIX/sbin/$VPNC PIDFILE=/var/run/$VPNC.pid RESOLVCONF=/etc/resolv.conf DEBUG=false # end of configuration ############################################################################ # Don't touch anything below this line unless you know what you're doing... TAG='===>' ############################################################################ # when called from vpnc --script, this is the environment we get # VPNGATEWAY -- vpn gateway address (always present) # TUNDEV -- tunnel device (always present) # INTERNAL_IP4_ADDRESS -- address (always present) # INTERNAL_IP4_NETMASK -- netmask (often unset) # INTERNAL_IP4_DNS -- list of dns serverss # INTERNAL_IP4_NBNS -- list of wins servers # CISCO_DEF_DOMAIN -- default domain name # CISCO_BANNER -- banner from server if [ "x$VPNGATEWAY" != "x" ]; then if $DEBUG; then echo VPNGATEWAY=$VPNGATEWAY echo TUNDEV=$TUNDEV echo INTERNAL_IP4_ADDRESS=$INTERNAL_IP4_ADDRESS echo INTERNAL_IP4_NETMASK=$INTERNAL_IP4_NETMASK echo INTERNAL_IP4_DNS=$INTERNAL_IP4_DNS echo INTERNAL_IP4_NBNS=$INTERNAL_IP4_NBNS echo CISCO_DEF_DOMAIN=$CISCO_DEF_DOMAIN echo CISCO_BANNER=$CISCO_BANNER fi # set up tunnel interface ifconfig $TUNDEV inet $INTERNAL_IP4_ADDRESS \ pointopoint $INTERNAL_IP4_ADDRESS \ netmask ${INTERNAL_IP4_NETMASK:-255.255.255.255} mtu 1412 up # back up resolv.conf if RESOLVCONF is set if [ "x$RESOLVCONF" != "x" ]; then if [ -e $RESOLVCONF ]; then rm -f $RESOLVCONF-vpnc cp $RESOLVCONF $RESOLVCONF-vpnc fi fi # handle resolv.conf if [ "x$INTERNAL_IP4_DNS" != "x" ]; then if [ "x$RESOLVCONF" != "x" ]; then echo "$TAG Writing $RESOLVCONF..." if [ "x$CISCO_DEF_DOMAIN" != "x" ]; then echo "search $CISCO_DEF_DOMAIN" >$RESOLVCONF else echo -n "" >$RESOLVCONF fi for ns in $INTERNAL_IP4_DNS; do echo "nameserver $ns" >>$RESOLVCONF done else echo "$TAG Ignoring VPN nameservers: RESOLVCONF is unset" fi fi # print banner if [ "x$CISCO_BANNER" != "x" ]; then echo "$CISCO_BANNER" fi # return to previous incarnation of the script exit 0 fi # clean up resolv.conf cleanup_resolvconf () { if [ "x$RESOLVCONF" != "x" ]; then echo "$TAG Restoring $RESOLVCONF..." # if backup copy exists, restore that, else just remove our resolv.conf if [ -e $RESOLVCONF-vpnc ]; then rm -f $RESOLVCONF mv $RESOLVCONF-vpnc $RESOLVCONF else rm -f $RESOLVCONF fi fi } ############################################################################ # ./configure for the system we run on # as a side effect, this ensures that the script is adapted to accomodate # for the particular flavour of BSD we run on. please send me required diffs. # defaults for most systems -- to be overridden later depending on system STORE_HACK_ADDR=127.0.0.42 DEFAULT_IFTUN=tun0 KLD_TUN=if_tun KLDSTAT=kldstat KLDLOAD=kldload #VPNC_OPTS="$VPNC_OPTS" sysver=`uname -sr` case "$sysver" in FreeBSD\ 5.*|FreeBSD\ 6.*) PING_OPTS="-n -t 3 -o" ;; FreeBSD\ 4.*) PING_OPTS="-n -t 3" ;; Darwin\ 7.5.0) # MacOS X requires a kernel-ipsec patch to vpnc, and the tun/tap kext: # http://lists.unix-ag.uni-kl.de/pipermail/vpnc-devel/attachments/20040910/bbe4023f/vpnc_kernel_ipsec.obj # http://www-user.rhrk.uni-kl.de/~nissler/tuntap/ PING_OPTS="-n -c 1" KLDSTAT=kextstat KLDLOAD=kextload # /System/Library/Extensions/tun.kext KLD_TUN=tun VPNC_OPTS="$VPNC_OPTS --kernel-ipsec" ;; *) echo "$TAG Your system is not supported yet ($sysver)!" >&2 echo "$TAG Please edit the script and submit necessary patches. Aborting..." >&2 exit 1 ;; esac ############################################################################ # get config, do some checks # must be superuser if [ `id -u` -ne 0 ]; then echo "$TAG Need superuser privs! Aborting..." >&2 exit 1 fi # check on system binaries SBIN_ROUTE=`which route 2>/dev/null` SBIN_ROUTE=${SBIN_ROUTE:-/sbin/route} SBIN_PING=`which ping 2>/dev/null` SBIN_PING=${SBIN_PING:-/sbin/ping} SBIN_IFCONFIG=`which ifconfig 2>/dev/null` SBIN_IFCONFIG=${SBIN_IFCONFIG:-/sbin/ifconfig} SBIN_SYSCTL=`which sysctl 2>/dev/null` SBIN_SYSCTL=${SBIN_SYSCTL:-/sbin/sysctl} SBIN_KLDSTAT=`which $KLDSTAT 2>/dev/null` SBIN_KLDSTAT=${SBIN_KLDSTAT:-/sbin/kldstat} SBIN_KLDLOAD=`which $KLDLOAD 2>/dev/null` SBIN_KLDLOAD=${SBIN_KLDLOAD:-/sbin/kldload} for binary in $SBIN_VPNC $SBIN_ROUTE $SBIN_PING $SBIN_IFCONFIG $SBIN_SYSCTL $SBIN_KLDSTAT $SBIN_KLDLOAD; do if [ ! -x $binary ]; then echo "$TAG Cannot run $binary! Aborting..." >&2 exit 1 fi done # check config file CONF="${2:-$DEFAULT_CONF}" if [ ! -r $CONF ]; then echo "$TAG Cannot read $CONF! Aborting..." >&2 exit 1 fi # get tunnel interface IFTUN=`cat $CONF|awk '/^Interface name / { print $3 }'|head -1` IFTUN="${IFTUN:-$DEFAULT_IFTUN}" # get VPN gateway VPNGW=`cat $CONF|awk '/^IPSec gateway / { print $3 }'|head -1` if [ "x$VPNGW" = "x" ]; then echo "$TAG No VPN gateway in $CONF! Aborting..." >&2 exit 1 fi # get NO_PING flag NO_PING=`cat $CONF|grep NO_PING|head -1` # get pid from pidfile, if any PID=`cat $PIDFILE 2>/dev/null` ############################################################################ # do the work case "$1" in start) ps -p $PID >/dev/null 2>&1 && { echo "$TAG $VPNC is already running! Aborting..." >&2 exit 2 } GATEWAY_VPNGW=`$SBIN_ROUTE -n get $VPNGW|awk '/gateway:/ { print $2 }'` GATEWAY_DEFAULT=`$SBIN_ROUTE -n get default|awk '/gateway:/ { print $2 }'` if [ "x$NO_PING" = "x" ]; then $SBIN_PING $PING_OPTS $VPNGW >/dev/null || { echo "$TAG Cannot ping VPN gateway $VPNGW! Aborting..." >&2 echo "$TAG Disable this check by adding #NO_PING to $CONF" >&2 exit 2 } fi # load if_tun only if neither kldstat, ifconfig nor sysctl show traces of it if [ "x`$SBIN_KLDSTAT|grep $KLD_TUN`" = "x" ]; then if [ "x`$SBIN_IFCONFIG -a|grep ^tun`" = "x" ]; then if [ "x`$SBIN_SYSCTL debug.if_tun_debug 2>/dev/null`" = "x" ]; then $SBIN_KLDLOAD $KLD_TUN fi fi fi # start vpnc echo "$TAG Starting $VPNC daemon..." $SBIN_VPNC --pid-file $PIDFILE --script "$0" $CONF $VPNC_OPTS || exit 2 # change the route table: # if there is a (non-default) route to the vpngw, # just leave that alone, otherwise add a host route to the vpngw. # if there is a default route, store it as host route to store_hack_addr. echo "$TAG Changing route table..." if [ "x$GATEWAY_VPNGW" != "x" -a "x$GATEWAY_VPNGW" = "x$GATEWAY_DEFAULT" ]; then $SBIN_ROUTE add -host $VPNGW $GATEWAY_VPNGW fi if [ "x$GATEWAY_DEFAULT" != "x" ]; then $SBIN_ROUTE add -host $STORE_HACK_ADDR $GATEWAY_DEFAULT fi $SBIN_ROUTE delete default $SBIN_ROUTE add default -interface $IFTUN echo "$TAG done." ;; stop) ps -p $PID >/dev/null 2>&1 || { echo "$TAG $VPNC is not running! Aborting..." >&2 exit 2 } GATEWAY_VPNGW=`$SBIN_ROUTE -n get $VPNGW|awk '/gateway:/ { print $2 }'` GATEWAY_DEFAULT=`$SBIN_ROUTE -n get $STORE_HACK_ADDR|awk '/gateway:/ { print $2 }'` cleanup_resolvconf echo "$TAG Killing $VPNC daemon..." kill $PID rm -f $PIDFILE # restore the route table: # if we have a gateway route to the vpngw which is different than the # stored default gateway, don't remove that, otherwise remove the host # route to the vpngw. # if we have stored a default gateway, restore the default route and # remove the host route to store_hack_addr. echo "$TAG Restoring route table..." $SBIN_ROUTE delete default if [ "x$GATEWAY_VPNGW" != "x" -a "x$GATEWAY_VPNGW" = "x$GATEWAY_DEFAULT" ]; then $SBIN_ROUTE delete -host $VPNGW fi if [ "x$GATEWAY_DEFAULT" != "x" ]; then $SBIN_ROUTE add default $GATEWAY_DEFAULT $SBIN_ROUTE delete -host $STORE_HACK_ADDR fi echo "$TAG done." ;; restart) shift $0 stop "$@" $0 start "$@" ;; status) ps -p $PID >/dev/null 2>&1 && { echo "$TAG $VPNC is running with process id $PID" exit 0 } || { echo "$TAG $VPNC is not running" exit 1 } ;; kill) cleanup_resolvconf if [ "x$PID" != "x" ]; then echo "$TAG Killing $VPNC with process id $PID..." kill -9 $PID else echo "$TAG $VPNC is not running" fi rm -f $PIDFILE echo "$TAG You may need to restore your routes manually." ;; *) echo "Usage: $0 {start|stop|restart|status|kill} [config-file]" >&2 exit 1 ;; esac