#!/bin/sh # Automagical, generic vpnc wrapper to establish tunnel and routes on BSD # http://www.roe.ch/VPNC-Wrapper # # Copyright 2003-2005, Daniel Roethlisberger # MacOS X support based on work by Douglas Held # 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. # # $Id: vpnc-wrapper,v 1.28 2005/05/26 07:29:49 roe Exp $ # This is for vpnc <= 0.3.2 only! Starting with vpnc 0.3.3, the --script # mechanism was overhauled. You'll need an updated version of this # script, or a fixed vpnc-script which works properly on *BSD. # # The script should work on the following systems: # - FreeBSD 4.x, 5.x, 6.x # - DragonFly 1.x # - MacOS X / Darwin 7.x, 8.x # 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). # # 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 1 indicates permanent errors (eg., configuration problems) # Exit status 2 indicates temporary errors (eg., cannot ping VPN gateway) # # 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 want to use vpnc.sh from the # security/vpnc port as a starting point. # 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 #RESOLVCONF=/var/run/resolv.conf DEBUG=false # end of configuration #set -x ############################################################################ # Don't touch anything below this line unless you know what you're doing... TAG='===>' # verify that a binary exists and is executable, and bail out if not check_binary () { if [ ! -x "$1" ]; then echo "$TAG Cannot run $1! Aborting..." >&2 exit 1 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" DARWIN=false sysver=`uname -sr` case "$sysver" in FreeBSD\ [56].*) PING_OPTS="-n -t 3 -o" ;; FreeBSD\ 4.*|DragonFly\ 1.*) PING_OPTS="-n -t 3" ;; Darwin\ [78].*) # Supposedly works with MacOS X 10.1 through 10.4, resp. Darwin 7.x, 8.x # 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.kext VPNC_OPTS="$VPNC_OPTS --kernel-ipsec" DARWIN=true ;; *) 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 # check on system binaries required when called by vpnc --script SBIN_IFCONFIG=`which ifconfig 2>/dev/null` SBIN_IFCONFIG="${SBIN_IFCONFIG:-/sbin/ifconfig}" for binary in "$SBIN_IFCONFIG"; do check_binary "$binary" done ############################################################################ # 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 if $DARWIN; then $SBIN_IFCONFIG $TUNDEV $INTERNAL_IP4_ADDRESS $INTERNAL_IP4_ADDRESS up else $SBIN_IFCONFIG $TUNDEV inet $INTERNAL_IP4_ADDRESS \ $INTERNAL_IP4_ADDRESS \ netmask ${INTERNAL_IP4_NETMASK:-255.255.255.255} mtu 1412 up fi # back up resolv.conf if RESOLVCONF is set if [ ! -z "$RESOLVCONF" ]; then if [ -e "$RESOLVCONF" ]; then rm -f "$RESOLVCONF-vpnc" cp "$RESOLVCONF" "$RESOLVCONF-vpnc" fi fi # handle resolv.conf if [ ! -z "$INTERNAL_IP4_DNS" ]; then if [ ! -z "$RESOLVCONF" ]; then echo "$TAG Writing $RESOLVCONF..." if [ ! -z "$CISCO_DEF_DOMAIN" ]; 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 [ ! -z "$CISCO_BANNER" ]; then echo "$CISCO_BANNER" fi # return to previous incarnation of the script exit 0 fi # clean up resolv.conf cleanup_resolvconf () { if [ ! -z "$RESOLVCONF" ]; 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 } ############################################################################ # 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_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_SYSCTL" \ "$SBIN_KLDSTAT" "$SBIN_KLDLOAD"; do check_binary "$binary" 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 [ -z "$VPNGW" ]; 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 [ -z "$NO_PING" ]; 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 [ -z "`$SBIN_KLDSTAT|grep $KLD_TUN`" ]; then if [ -z "`$SBIN_IFCONFIG -a|grep ^tun`" ]; then if [ -z "`$SBIN_SYSCTL debug.if_tun_debug 2>/dev/null`" ]; 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 "$GATEWAY_VPNGW" = "$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 "$GATEWAY_VPNGW" = "$GATEWAY_DEFAULT" ]; then $SBIN_ROUTE delete -host $VPNGW fi if [ ! -z "$GATEWAY_DEFAULT" ]; 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 [ ! -z "$PID" ]; 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