#!/bin/sh
# IPSEC startup and shutdown script
# Copyright (C) 1998, 1999  Henry Spencer.
# 
# 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 of the License, or (at your
# option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
# 
# 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.
#
# RCSID $Id: setup,v 1.69 1999/12/13 02:39:15 henry Exp $
#
# ipsec         init.d script for starting and stopping
#               the IPSEC security subsystem (KLIPS and Pluto).
#
# This script becomes /etc/rc.d/init.d/ipsec (or possibly /etc/init.d/ipsec)
# and is also accessible as "ipsec setup" (the preferred route for human
# invocation).
#
# The startup and shutdown times are a difficult compromise (in particular,
# it is almost impossible to reconcile them with the insanely early/late
# times of NFS filesystem startup/shutdown).  Startup is after startup of
# syslog and pcmcia support; shutdown is just before shutdown of syslog.
#
# chkconfig: 2345 47 68
# description: IPSEC provides encrypted and authenticated communications; \
# KLIPS is the kernel half of it, Pluto is the user-level management daemon.

if test " $IPSECDIR" = " "	# if we were not called by the ipsec command
then
	# we must establish a suitable PATH ourselves
	PATH=/usr/local/sbin:/sbin:/usr/sbin:/usr/local/bin:/bin:/usr/bin
	export PATH
fi

me='ipsec setup'		# for messages

# make sure output of (e.g.) ifconfig is in English
LANG=
LC_ALL=
export LANG LC_ALL

# verify permissions
if ! test -w /dev/ipsec
then
	echo "$me: permission denied" >&2
	exit 1
fi

# Check that the ipsec command is available.
found=
for dir in `echo $PATH | tr ':' ' '`
do
	if test -f $dir/ipsec -a -x $dir/ipsec
	then
		found=yes
		break			# NOTE BREAK OUT
	fi
done
if ! test "$found"
then
	echo "cannot find ipsec command -- aborting" |
		logger -s -p daemon.error -t ipsec_setup
	exit 1
fi

# Pick up IPSEC configuration (until we have done this, successfully, we
# do not know where errors should go, hence the explicit "daemon.error"s.)
eval `ipsec _confread --varprefix IPSEC --type config setup`
if test " $IPSEC_confreadstatus" != " "
then
	echo "$IPSEC_confreadstatus -- aborting" |
		logger -s -p daemon.error -t ipsec_setup
	exit 1
fi

# Misc. configuration (this should be overrideable from ipsec.conf someday).
plutopid=/var/run/pluto.pid
subsyslock=/var/lock/subsys/ipsec
nexthop=/var/run/ipsec.nexthop		# vestigial
info=/var/run/ipsec.info
umask 022



# some shell functions, to clarify the actual code

# start KLIPS
startklips() {
	# load module if necessary
	if ! ls /proc/net | egrep '^ipsec_' >/dev/null
	then
		echo "Loading KLIPS module:"
		depmod -a >/dev/null 2>&1 && modprobe ipsec
	fi

	# clear tables out in case dregs have been left over
	ipsec eroute --clear
	ipsec spi --clear

	# figure out debugging flags
	case "$IPSECklipsdebug" in
	'')	IPSECklipsdebug=none	;;
	esac
	echo "KLIPS debug \`$IPSECklipsdebug'"
	case "$IPSECklipsdebug" in
	none)	ipsec klipsdebug --none	;;
	all)	ipsec klipsdebug --all	;;
	*)	ipsec klipsdebug --none
		for d in $IPSECklipsdebug
		do
			ipsec klipsdebug --set $d
		done
		;;
	esac

	# figure out interfaces
	for i in $IPSECinterfaces
	do
		case "$i" in
		ipsec*=?*)	klipsinterface "$i"	;;
		%defaultroute)	defaultinterface	;;
		*)	echo "$me: interface \`$i' not understood, ignored" >&2
			;;
		esac
	done
}

# interfaces=%defaultroute:  put ipsec0 on top of default route's interface
defaultinterface() {
	phys=`netstat -nr |
		awk '$1 == "0.0.0.0" && $3 == "0.0.0.0" { print $NF }'`
	if test " $phys" = " "
	then
		echo "$me: cannot find a default route" >&2
		return			# really should exit, hard to arrange
	fi
	next=`netstat -nr |
		awk '$1 == "0.0.0.0" && $3 == "0.0.0.0" { print $2 }'`
	klipsinterface "ipsec0=$phys" $next
}

# set up a Klips interface
klipsinterface() {
	# pull apart the interface spec
	virt=`expr $1 : '\([^=]*\)=.*'`
	phys=`expr $1 : '[^=]*=\(.*\)'`

	# figure out ifconfig for interface
	addr=
	eval `ifconfig $phys |
		awk '$1 == "inet" && $2 ~ /^addr:/ && $4 ~ /^Mask:/ {
			gsub(/:/, " ", $0)
			print "addr=" $3
			if ($4 == "Bcast")
				print "type=broadcast"
			else if ($4 == "P-t-P")
				print "type=pointopoint"
			else
				print "type="
			print "otheraddr=" $5
			print "mask=" $7
		}'`
	if test " $addr" = " "
	then
		echo "$me: unable to determine address of \`$phys'" >&2
		exit 1
	fi
	if test " $type" = " "
	then
		echo "$me: \`$phys' is of an unknown type, can't use it" >&2
		return
	fi
	echo "KLIPS $virt on $phys $addr/$mask $type $otheraddr"

	# attach the interface and bring it up
	ipsec tncfg --attach --virtual $virt --physical $phys
	ifconfig $virt inet $addr $type $otheraddr netmask $mask

	# if %defaultroute, note the facts
	if test " $2" != " "
	then
		(
			echo "defaultroutephys=$phys"
			echo "defaultroutevirt=$virt"
			echo "defaultrouteaddr=$addr"
			if test " $2" != " 0.0.0.0"
			then
				echo "defaultroutenexthop=$2"
			fi
		) >$info
	fi

	# check for advanced-router trouble
	checkif $virt
	checkif $phys
}

# check an interface for problems
checkif() {
	rpf=/proc/sys/net/ipv4/conf/$1/rp_filter
	if test -f $rpf
	then
		r="`cat $rpf`"
		if test " $r" != " 0"
		then
			echo "WARNING: $1 has route filtering turned on, KLIPS may not work"
			echo " ($rpf = \`$r', should be 0)"
		fi
	fi
}

# set up manually-keyed connections
manualconns() {
	if test " $IPSECmanualstart" != " "
	then
		for tu in $IPSECmanualstart
		do
			echo "Manually-keyed connection \`$tu':"
			ipsec manual --up $tu
		done
	fi
}

# internal setup for Pluto start
plutofix() {
	# searches, if needed
	if test " $IPSECplutoload" = " %search"
	then
		eval `ipsec _confread --varprefix PLUTO --search auto add start`
		if test " $PLUTO_confreadstatus" != " "
		then
			echo "$PLUTO_confreadstatus (load search) -- aborting" |
									logit
			exit 1
		fi
		IPSECplutoload="$PLUTO_confreadnames"
	fi
	if test " $IPSECplutostart" = " %search"
	then
		eval `ipsec _confread --varprefix PLUTO --search auto start`
		if test " $PLUTO_confreadstatus" != " "
		then
			echo "$PLUTO_confreadstatus (start search) -- aborting" |
									logit
			exit 1
		fi
		IPSECplutostart="$PLUTO_confreadnames"
	fi

	# ensure plutoload is a superset of plutostart
	for s in $IPSECplutostart
	do
		found=
		for lo in $IPSECplutoload
		do
			if test " $lo" = " $s"
			then
				found=yes
			fi
		done
		if test ! "$found"
		then
			IPSECplutoload="$IPSECplutoload $s"
		fi
	done
}

# start Pluto daemon
plutodaemon() {
	# double-check one little detail
	if test ! -e /dev/random
	then
		echo "Cannot start Pluto, system lacks /dev/random !!!"
		echo "	(see documentation for where to get that driver)"
		exit 1
	fi

	# figure out options
	pd=
	for d in $IPSECplutodebug
	do
		pd="$pd --debug-$d"
	done

	# do it
	if test " $IPSECdumpdir" = " "
	then
		echo "Disabling core dumps:"
		ulimit -c 0			# preclude core dumps
	elif test ! -d "$IPSECdumpdir"
	then
		echo "Dumpdir \`$IPSECdumpdir' does not exist, ignored."
		ulimit -c 0			# preclude core dumps
	else
		echo "cd $IPSECdumpdir so core dumps go there:"
		cd $IPSECdumpdir		# put them where desired
	fi
	echo "Starting Pluto (debug \`$IPSECplutodebug'):"
	ipsec pluto $pd
}

# Pluto subsystem startup, after daemon is running
plutopost() {
	# database load
	if test " $IPSECplutoload" != " "
	then
		echo "$IPSECplutoload" | tr ' ' '\n' | column -x -c 100 |
			tr -s '\t ' '  ' |
			while read bunch
			do
				echo "Loading Pluto database \`$bunch':"
				for tu in $bunch
				do
					ipsec auto --add $tu
				done
			done
	fi

	# enable listening
	echo "Enabling Pluto negotiation:"
	ipsec auto --ready

	# quickly establish routing
	if test " $IPSECplutostart" != " "
	then
		echo "$IPSECplutostart" | tr ' ' '\n' | column -x -c 100 |
			tr -s '\t ' '  ' |
			while read bunch
			do
				echo "Routing for Pluto conns \`$bunch':"
				for tu in $bunch
				do
					ipsec auto --route $tu
				done
			done
	fi
}

# Pluto negotiation start (if any)
plutogo() {
	# tunnel initiation, which may take a while
	if test " $IPSECplutostart" != " "
	then
		async=
		p=
		if test " $IPSECplutowait" = " no"
		then
			async="--asynchronous"
			p=" (asynchronously)"
		fi
		for tu in $IPSECplutostart
		do
			echo "Initiating Pluto tunnel \`$tu'$p:"
			ipsec auto --up $async $tu
		done
	fi
}

# logging control
logit() {
	IPSECsyslog=${IPSECsyslog-daemon.error}
	logger -s -p $IPSECsyslog -t ipsec_setup 2>&1
}



# the mainline code

# backward compatibility, defaults.
if test " $IPSECdump" = " yes" -a " $IPSECdumpdir" = " "
then
	IPSECdumpdir=/var/tmp
fi

# do it
case "$1" in
  start|--start)
	# Start things rolling.
	# (Warning, changes to this log message may affect barf.)
	version="`ipsec --version | awk 'NR == 1 { print $3 }'`"
	echo "Starting FreeS/WAN IPSEC $version..." | logit
	rm -f $nexthop $info
	if test ! -r /dev/random
	then
		echo "...unable to start FreeS/WAN IPSEC, no /dev/random!" |
									logit
		exit 1
	fi
	startklips 2>&1 | logit
	if test -d `basename $subsyslock`
	then
		touch $subsyslock
	fi
	manualconns 2>&1 | logit
	if test " $IPSECpluto" != " no"
	then
		plutofix
		plutodaemon 2>&1 | logit
		sleep 1				# give it a moment to get going
		plutopost 2>&1 | logit
	fi
	if test " $IPSECforwardcontrol" = " yes"
	then
		echo "Forwarding up:" | logit
		echo 1 >/proc/sys/net/ipv4/ip_forward
	fi
	if test " $IPSECpluto" != " no"
	then
		plutogo 2>&1 | logit
	fi
	echo "...FreeS/WAN IPSEC started" | logit
	;;

  stop|--stop)
	# Shut things down.
	echo "Stopping FreeS/WAN IPSEC..." | logit
	if test " $IPSECforwardcontrol" = " yes"
	then
		echo "Forwarding down:" | logit
		echo 0 >/proc/sys/net/ipv4/ip_forward
	fi
	if test ! -f $plutopid
	then
		: nothing
	elif test ! -s $plutopid
	then
		echo "Removing empty $plutopid -- pluto still running?" | logit
		rm -f $plutopid
	elif kill -0 `cat $plutopid` 2>/dev/null
	then
		echo "Shutting down Pluto:" | logit
		ipsec whack --shutdown | awk '$1 != "002"' | logit
		sleep 1			# general paranoia
		if test -s $plutopid
		then
			echo "Pluto did not go away!  Killing it:" | logit
			kill `cat $plutopid` 2>&1 | logit
			sleep 5
		fi
		rm -f $plutopid		# harmless if already gone
	else
		echo "Removing orphaned $plutopid:" | logit
		rm -f $plutopid
	fi
	for i in `ifconfig | awk '/^ipsec/ { print $1 }'`
	do
		echo "Taking $i down:" | logit
		ifconfig $i down 2>&1 | logit
		ipsec tncfg --detach --virtual $i 2>&1 | logit
	done
	echo "Misc cleanout:" | logit
	ipsec klipsdebug --none 2>&1 | logit
	ipsec eroute --clear 2>&1 | logit
	ipsec spi --clear 2>&1 | logit
	rmmod ipsec >/dev/null 2>&1
	if test -d `basename $subsyslock`
	then
		rm -f $subsyslock
	fi
	rm -f $nexthop $info
	echo "...FreeS/WAN IPSEC stopped" | logit
	;;

  restart|--restart)
	$0 stop
	$0 start
	;;

  --version)
	echo "1"
	exit 0
	;;

  --help)
	echo "Usage: $me {--start|--stop|--restart}"
	exit 0
	;;

  *)
	echo "Usage: $me {--start|--stop|--restart}" >&2
	exit 2
esac

exit 0
