#!/bin/sh
##################################################
#####     VPN-Watch.sh     Version 1.6.3     #####
##################################################

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# Written by: Daniel Berlin <daniel.berlin@itechnology.de>.
# Download: http://www.itechnology.de/front_content.php?idcat=87
#

# changed by: Rüdiger Sobeck <rsobeck@compass-es.de>
# 25-04-2006
# 
# Changes:
# - check if script is already running
# - check if tunnel is made with (static) IPs
#   if true, bypass lookup request
# - logging changed to give more information
#	line 207: always log current IPs
#	line 264-298: depending on result of log different messages
#   disable corresponding lines to keep logfile small
# - disabled keyword 'restart' - it does not work

# changed by: Rüdiger Sobeck <rsobeck@compass-es.de>
# 22-03-2006
# 
# Changes:
# - check own red IP
# - check if own red IP is equal to IP reported by DYNDNS; do nothing if static IPs
# - check IP of tunnel partner
# - check if partner IP is equal to IP reported by DNS
# - log in /var/log/messages if tunnel is restarted

# Configuration
#
CHECK_INTERVAL='120'				# Check this often (in seconds)
DNS_RESOLVE_TRIES='3'				# Try to resolve IPs this often (each try takes max. 2 seconds)
NICENESS='+5'					# Adjust niceness of child processes: '-20' ... '+19'; '0' is default
IPCOP_VPN_CONFIG='/var/ipcop/vpn/config'	# Location of IPCop's vpn configuration file 
IPCOP_VPN_SETTINGS='/var/ipcop/vpn/settings'    # Location of IPCop's vpn settings file
VERSION='1.6.3'

# Workaround for nonexistent "nl" command on IPCop 1.4.x
nl --help >/dev/null 2>&1
if test $? -ne 0; then
    alias nl='cat'
fi

MyHost=`grep VPN_IP /var/ipcop/vpn/settings |  cut --delimiter='=' --output-delimiter=' ' -f2`
MyIP=`cat /var/ipcop/red/local-ipaddress`
MyDynDnsIP=`ping -c 1 "$1" 2>/dev/null | head -n1 | awk '{print $3}' | tr -d '()' | tr -d ':'`

case "$1" in
	'start' | '--start')
		if test ! -r "$IPCOP_VPN_CONFIG"; then
			echo 'Error: cannot read IPCop VPN configuration file; exit.' >&2
			exit 1
		fi
                
                # if pipe already exits then log and exit
                if test -p /var/run/$(basename $0); then
                	echo 'Start aborted! Script is already running!' >&2
                	exit 1
                fi
                
		mknod -m 0660 "/var/run/$(basename $0)" p >/dev/null 2>&1	# Create pipe for status-information

		# Read VPN configuration and fork a child process for each VPN connection
		#
		while read line; do
			VPN=($(echo $line | cut --delimiter=',' --output-delimiter=' ' -f1,2,3,5,6,12))	# 
                        CONNR=${VPN[0]}     								# connection number
                        CONACTIVE=${VPN[1]}								# active (on|off)
                        CONNAME=${VPN[2]}								# connection name
                        CONTYPE=${VPN[3]}								# connection type (host|net)
                        CONCERTPSK=${VPN[4]}								# key type (cert|psk)
                        CONDNSNAME=${VPN[5]}								# FQDN name of other side
                        
			echo -n "${CONACTIVE}" | grep -qi '^off$' && continue				# Ignore: deactivated connections
			echo -n "${CONTYPE}" | grep -qi '^host$' && continue				# Ignore: Roadwarriors (->DPD)
#			echo -n "${VPN[1]}${MyHost}" | grep -q '^[[:digit:]\.]\+$' && continue		# Ignore: "left" and "right" side set to an IP

			$0 'conn:' "${CONNAME}" "${MyHost}" "${CONDNSNAME}" "${CONNR}" >/dev/null 2>&1 &		# Fork child process (parameters: "conn: NAME LEFT RIGHT NUMBER")
			echo -n 'S'
		done < "$IPCOP_VPN_CONFIG"
		echo
		exit 0														# Parent dies here... RIP
		;;
	'stop' | '--stop')
		# Terminate processes
		for proc in $(pidof -x -o %PPID $(basename $0)); do
			kill -s SIGTERM -- "$proc"
			echo -n 'T'
		done
		sleep 1
		# Kill remaining processes
		for proc in $(pidof -x -o %PPID $(basename $0)); do
			kill -s SIGKILL -- "$proc"
			echo -n 'K'
		done
		rm -f "/var/run/$(basename $0)"								# Remove pipe
		echo
		exit 0
		;;
# does not work (RS 25-04-2006)
#	'restart' | '--restart')
#		$0 stop
#		$0 start
#		exit 0
#		;;
	'status' | '--status')
		echo "VPN-Watch ${VERSION}  (mail: daniel@itechnology.de, web: www.itechnology.de/vpn-watch)"
		if ps --no-heading axw | grep -v 'grep' | grep -q "$(basename $0) conn: "; then
			trap '' USR1
			killall -q -g -s USR1 -- $(basename $0)
			sleep 1
			cat "/var/run/$(basename $0)" | sort | nl				# Read children's info from pipe
		else
			echo '     no instances running.'
		fi
		exit 0
		;;
	'conn:')
		# Children proceed here...
		renice ${NICENESS:-0} -p $$ >/dev/null 2>&1					# Adjust niceness
		shift										# Remove the first positional parameter ("conn:"), as we don't need it anymore
		;;
	*)
		echo "Usage: $0 { start | stop | restart | status }" >&2
		exit 1
		;;
esac

# Logging, signal handlers
#
alias log="logger -t '$(basename $0 | cut -d '.' -f 1) ${VERSION}' \(${1}\)"
trap 'log "terminated after ${RESTART_COUNT} restarts."' EXIT
trap 'echo "connection \"${1}\" restarted ${RESTART_COUNT} times" >>/var/run/$(basename $0)' USR1

log "started"

# helper function to determine if IP-Adress oder FQDN
# returns 0 if name, 1 if IP
function name_or_ip () {
	local MYRESULT=0
	cat "$1" | grep -qi '^[[:digit:]\.]\+$' && ( MYRESULT=0 ) || ( MYRESULT=1 )
	echo -n "$MYRESULT"
}

# Get IP of a FQDN... using 'arp', 'traceroute' or 'ping',
#  because IPCop has no 'nslookup', 'host' or 'dig' command.
#
function get_ip () {
	local RESULT=''
	# if argument is FQDN, else it's already an IP-Adress
	if [ $(name_or_ip $1) ]; then
		for ((i=1; ${i} <= ${DNS_RESOLVE_TRIES}; i++)); do
			if which arp >/dev/null 2>&1; then
				RESULT=$(arp "$1" 2>/dev/null | awk '{ print $2 }' | tr -d '()')
			elif which traceroute >/dev/null 2>&1; then
				RESULT=$(traceroute -m1 -q1 "$1" 2>/dev/null | head -n1 | awk '{ print $4 }' | tr -d '(),')
			else
				RESULT=$(ping -c 1 "$1" 2>/dev/null | head -n1 | awk '{print $3}' | tr -d '()' | tr -d ':')
			fi
			test -n "$RESULT" && break
		done
		test -z "$RESULT" && log "Warning: could not resolve ${1} after ${DNS_RESOLVE_TRIES} tries..."
	else
		RESULT = "$1"
	fi
	echo -n "$RESULT"
}

function get_tunnelip () {
	file=/var/tmp/$1.remoteip
	local TRESULT=''
	TVPN=`grep "$1" /var/ipcop/vpn/config| awk 'BEGIN{FS=","}{print $2}'`
	DYNHOST=`grep "$1" /var/ipcop/vpn/config| awk 'BEGIN{FS=","}{print $12}'`
	CONNR=`grep "$1" /var/ipcop/vpn/config| awk 'BEGIN{FS=","}{print $1}'`

	# get IP reported by DNS
	REMOTEIP=`/usr/bin/ping -c 1 "$DYNHOST" 2>/dev/null | head -n1 | awk '{print $3}' | tr -d '()' | tr -d ':'`
	if ! test -f $file; then
		cat $REMOTEIP > $file
	fi
	OLDIP=`cat $file`
	TUNIP=`ipsec whack --status | grep "$1"`
	if [ "$TUNIP" != "" ]; then
		# get partner IP currently used in tunnel
		TUNIP=`ipsec whack --status | grep "$1" | awk 'BEGIN{FS="==="}{print $2}' | awk 'BEGIN{FS="---"}{print $3}'`
		# string contains "[" if tunnel description is made with cert or FQDN
		# remove all chars after "["
		mytest=`echo "$TUNIP" | grep "\["`
		if [ "$mytest" != "" ]; then
			TUNIP=`echo "$TUNIP" | cut --delimiter='[' --output-delimiter='' -f1`
		fi

#		log "IP used in tunnel = $TUNIP, IP by DNS = $REMOTEIP"
		echo $REMOTEIP > $file
		TRESULT=${TUNIP}
	else
		log "got emtpy string while trying to extract tunnel IP"
	fi

	test -n "$TRESULT" && break
	test -z "$TRESULT" && log "Warning: could not retrieve last used VPN tunnel IP...(TRESULT=$TRESULT)"
	echo -n "$TRESULT"
}

# Restarts a VPN connection
#
function restart_vpn () {
	if test -x /usr/local/bin/ipsecctrl; then
		/usr/local/bin/ipsecctrl D "$1"			# This works for IPCop 1.4.x
		/usr/local/bin/ipsecctrl R			# re-read secrets
		/usr/local/bin/ipsecctrl S "$1"			# start tunnel
	else
		ipsec auto --down		"$1"			# This works for IPCop 1.3.x
		ipsec auto --unroute	"$1"
		ipsec auto --delete		"$1"
		ipsec auto --rereadall
		ipsec auto --add		"$1"
		ipsec auto --route		"$1"
		ipsec auto --up			"$1"
	fi
}

# Get left and right IP
#
LEFT_IP_OLD=$MyIP
RIGHT_IP_OLD=$(get_ip $3)

# Infinite loop; checks, whether the IP of a left or right FQDN has changed.
#  If so, the affected connection gets restarted; this is logged to syslog.
#
RESTART_COUNT=0
while :; do
	sleep $CHECK_INTERVAL

	# Skip check until IPSec is running
	ipsec auto --status >/dev/null 2>&1 || ( log "IPSec not running - cannot check !!!" ; continue )

	# get own IP (may have changed)
	ThisHostIP=`cat /var/ipcop/red/local-ipaddress`
	
	# this our own IP as reported in /var/ipcop/ppp/local-ipadress
	LEFT_IP_NEW=$ThisHostIP
	# check our own DYNDNS IP
	LEFT_IP_DYN=$(get_ip $MyHost)
	# this is DYNDNS IP of other side
	RIGHT_IP_NEW=$(get_ip $3)
	# this the last used partner IP for VPN-Tunnel
	RIGHT_TUN_IP_OLD=$(get_tunnelip $1)

#	left IP mismatch; maybe DYNDNS was not (yet) properly updated
	if [ "${LEFT_IP_NEW}" != "${LEFT_IP_DYN}" ]; then
		restart_vpn "$4"
		let RESTART_COUNT++
#		log "Left IP = $LEFT_IP_NEW, Left IP (as reported by DynDNS) = $LEFT_IP_DYN"
		log 'left IP mismatch: restarting connection...'
	fi
	
#	left or right IP has changed...even both IPs could have changed
	if test "${LEFT_IP_OLD} ${RIGHT_IP_OLD}" != "${LEFT_IP_NEW} ${RIGHT_IP_NEW}"; then
		restart_vpn "$4"
		let RESTART_COUNT++
#		log "Left IP old = $LEFT_IP_OLD, right IP old = $RIGHT_IP_OLD"
#		log "Left IP new = $LEFT_IP_NEW, right IP new = $RIGHT_IP_NEW"
		log 'left or right IP have changed: restarting connection...'
	fi

#	right IP / IP of tunnel endpoint has changed...
	if [ "$RIGHT_TUN_IP_OLD" != "" ]; then
		if test "${RIGHT_TUN_IP_OLD}" != "${RIGHT_IP_NEW}"; then
			restart_vpn "$4"
			let RESTART_COUNT++
#			log "Right tunnel IP: old IP = $RIGHT_TUN_IP_OLD, new IP = $RIGHT_IP_NEW"
			log 'right IP mismatch: restarting connection...'
		fi
	fi

	LEFT_IP_OLD=$LEFT_IP_NEW
	RIGHT_IP_OLD=$RIGHT_IP_NEW
done

