#!/bin/sh
# KLIPS startup script
# Copyright (C) 1998, 1999, 2001, 2002  Henry Spencer.
# Copyright (C) 2006, 2007  Paul Wouters <paul@xelerance.com>
# 
# 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.
#

test $IPSEC_INIT_SCRIPT_DEBUG && set -v -x

me='ipsec _startklips'		# for messages

# KLIPS-related paths
sysflags=/proc/sys/net/ipsec
modules=/proc/modules
netkey=/proc/net/pfkey
ipsecversion=/proc/net/ipsec/version
moduleplace=/lib/modules/`uname -r`/kernel/net/ipsec
bareversion=`uname -r | sed -e 's/\.nptl//' | sed -e 's/^\(2\.[0-9]\.[1-9][0-9]*-[1-9][0-9]*\(\.[0-9][0-9]*\)*\(\.x\)*\).*$/\1/'`
moduleinstplace=/lib/modules/$bareversion/kernel/net/ipsec

# OCF tuning - If we have enough bogomips and RAM, use bigger OCF queues
bogomips=`grep -i bogomips /proc/cpuinfo | head -1 | awk ' { print $3;}'| sed "s/\..*$//" `
ram=`head -1 /proc/meminfo | awk '{print $2;}'`
limit=1000
if [ $bogomips -gt 1000 ]
then
	if [ $ram -gt 262143 ]
	then
		limit=10000
		#echo "OCF limit set to $limit"
	fi
fi

case $bareversion in
	2.6*)
		modulename=ipsec.ko
		;;
	*)
		modulename=ipsec.o
		;;
esac

# since netkey now calls _startnetkey, we know we're called to use klips or mast.
# so if we find netkey, try to unload it or die
if test -f $netkey
then
	echo "Warning: found NETKEY stack loaded"
	echo -n Trying to unload all NETKEY modules:
                for mod in ipcomp ipcomp6 xfrm6_tunnel xfrm6_mode_tunnel xfrm6_mode_beet xfrm6_mode_ro \
                        xfrm6_mode_transport xfrm4_mode_transport xfrm4_mode_tunnel \
                        xfrm4_tunnel xfrm4_mode_beet esp4 esp6 ah4 ah6 af_key xfrm_user
                   do
                        echo -n "$mod "
                        rmmod -a $mod 2> /dev/null
                   done
                echo
	if test -f $netkey
	then
		echo
		echo "FAILURE to unload NETKEY modules. Use protostack=netkey or recompile kernel without builtin NETKEY support"
		exit 1
	fi
fi

# which kernel are we using?
case $IPSECprotostack in
klips)   klips=true;  mast=false;;
mast)    klips=false; mast=true;;
none)    klips=false; mast=false;;
nostack) klips=false; mast=false;;
*|auto)
    #if test -f /proc/sys/net/ipsec/debug_mast
    if false
    then
	klips=false; mast=true;
	else
	    klips=true; mast=false;
    fi;;
esac

#should this not be /var/run/pluto/ipsec.info?
#info=/dev/null
info=/var/run/pluto/ipsec.info
log=daemon.error
for dummy
do
	case "$1" in
	--log)		log="$2" ; shift	;;
	--info)		info="$2" ; shift	;;
	--debug)	debug="$2" ; shift	;;
	--omtu)		omtu="$2" ; shift	;;
	--fragicmp)	fragicmp="$2" ; shift	;;
	--hidetos)	hidetos="$2" ; shift	;;
	--)	shift ; break	;;
	-*)	echo "$me: unknown option \`$1'" >&2 ; exit 2	;;
	*)	break	;;
	esac
	shift
done

if $klips
then
    logger -p $log -t ipsec_setup "Using KLIPS/legacy stack"
elif $mast
then
    logger -p $log -t ipsec_setup "Using KLIPS/mast stack"
else
    logger -p $log -t ipsec_setup "NOT using any IPsec stack - no real IPsec SA's will be created"
fi




# some shell functions, to clarify the actual code

# set up a system flag based on a variable
# sysflag value shortname default flagname
sysflag() {
	case "$1" in
	'')	v="$3"	;;
	*)	v="$1"	;;
	esac
	if test ! -f $sysflags/$4
	then
		if test " $v" != " $3"
		then
			echo "cannot do $2=$v, $sysflags/$4 does not exist"
			exit 1
		else
			return	# can't set, but it's the default anyway
		fi
	fi
	case "$v" in
	yes|no)	;;
	*)	echo "unknown (not yes/no) $2 value \`$1'"
		exit 1
		;;
	esac
	case "$v" in
	yes)	echo 1 >$sysflags/$4	;;
	no)	echo 0 >$sysflags/$4	;;
	esac
}

# get info about a given interface
# getinterfaceinfo eth0 prefix_
# see if we can find an IPv4/IPv6 address on the interface,
# only use the first one here, otheraddr will include a /mask
getinterfaceinfo() {
	ip addr show dev $1 | awk '
	BEGIN {
		MTU=""
		TYPE="unknown"
	}
	/BROADCAST/   { TYPE="broadcast" }
	/POINTOPOINT/ { TYPE="pointtopoint" }
	/mtu/ {
			sub("^.*mtu ", "", $0)
			MTU=$1
		}
	$1 == "inet" || $1 == "inet6" {
			split($2,addr,"/")
			other=""
			if ($3 == "peer")
				other=$4
			print "'$2'type=" TYPE
			print "'$2'addr=" addr[1]
			print "'$2'mask=" addr[2]
			print "'$2'otheraddr=" other
			print "'$2'mtu=" MTU
			exit 0
		}'
}

# get default route info
getdefaultrouteinfo() {
  ip route list 0.0.0.0/0 | awk '$2 == "via" { print "phys=" $5; print "next=" $3; exit 0 } { print "phys=" $3; print "next=0.0.0.0"; exit 0 }'
}

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

        # we expect virt to be ipsec# in klips mode, and mast# in mast mode
        devprefix=ipsec
        $mast && devprefix=mast
	case "$virt" in
	$devprefix[0-9]*)	;;
	*)	echo "invalid interface \`$virt' in \`$1'" ; exit 1	;;
	esac

	# figure out config for interface
	phys_addr=
	eval `getinterfaceinfo $phys phys_`
	if test " $phys_addr" = " "
	then
		echo "unable to determine address of \`$phys'"
		exit 1
	fi
	if test " $phys_type" = " unknown"
	then
		echo "\`$phys' is of an unknown type"
		exit 1
	fi
	if test " $omtu" != " "
	then
                phys_mtu=$omtu
	fi
	echo "KLIPS $virt on $phys $phys_addr/$phys_mask $phys_type $phys_otheraddr mtu $phys_mtu" | logonly

	if $mast
	then
        	# make sure this mast device exists, if not create it
        	virt_addr=
        	eval `getinterfaceinfo $virt virt_`
        	if test " $virt_addr" = " "
        	then
                	# in mast mode we just create the virtual interface
                	ipsec tncfg --create $virt
			ip link set $virt up
        	fi
	fi

        # in klips mode we attach it to the physical device
	if $klips
	then
		# ipsecX might not exist yet
		ipsec tncfg | grep $virt 
		RETVAL=$?
	        if [ "$RETVAL" -eq 1 ];
		then
			ipsec tncfg --create $virt
		fi
		ipsec tncfg --attach --virtual $virt --physical $phys
		# configure all the IPv4/IPv6 addresses (including point-to-point)
		ip addr show dev $phys \
		| awk '$1 == "inet" || ($1 == "inet6" && !/ dynamic/) {
				cmd = "ip addr add"
				if ($1 == "inet")
					sub(" [^ ]+:[^ ]+"," ",$0)
				sub("/.*","",$2)
				for (i = 2; i < NF; i++) {
					if ($i == "brd" || $i == "peer" || $i == "secondary")
						i++
					else
						cmd = cmd " " $i
				}
				if ($NF != phys)
					cmd = cmd " " $NF
				cmd = cmd " dev " virt "> /dev/null"
				system(cmd)
			}' phys=$phys virt=$virt
		ip link set up mtu $phys_mtu dev $virt
        fi

        # Double check the mtu is not 0 - if it is set it to a saner default
	ip link show dev $virt | grep -q 'mtu 0 '
        RETVAL=$?
        if [ "$RETVAL" -eq 0 ];
        then
                echo "Fixup of mtu on $virt to 16260"
                ip link set mtu 16260 dev $virt
        fi

	# if %defaultroute, note the facts
	if test " $2" != " "
	then
		(
			echo "defaultroutephys=$phys"
			echo "defaultroutevirt=$virt"
			echo "defaultrouteaddr=$phys_addr"
			if test " $2" = " 0.0.0.0"
			then
				# this happens on people with ppp interfaces with 'route add default dev ppp0'.
				echo "defaultroutenexthop=%direct"
			else
				echo "defaultroutenexthop=$2"
			fi
		) >>$info
	else
		echo '#dr: no default route' >>$info
	fi

}

# interfaces=%defaultroute:  put ipsec0 on top of default route's interface
defaultinterface() {
        phys=
        eval `getdefaultrouteinfo`

	if test " $phys" = " "
	then
		echo "no default route, %defaultroute cannot cope!!!"
		exit 1
	fi
        if test `echo " $phys" | wc -l` -gt 1
        then
                echo "multiple default routes, %defaultroute using: $next"
        fi

	if $klips
	then
	    klipsinterface "ipsec0=$phys" $next
	fi

	if $mast
	then
                klipsinterface "mast0=$phys" $next
	fi
}

# log only to syslog, not to stdout/stderr
logonly() {
	logger -p $log -t ipsec_setup
}

# first load any OCF and CryptoAPI modules we might need for acceleration
	# OCF cryptosoft is for kernel acceleration (ESP/AH)
	modprobe -q cryptosoft 2>/dev/null
	# OCF cryptodev is for userland IKE acceleration - let user do this
	# modprobe -q cryptodev 2>/dev/null

	if [ -d /sys/module/ocf ]
	then
		echo $limit  > /sys/module/ocf/parameters/crypto_q_max
	fi

	# CryptoAPI drivers follow next
	modprobe -q padlock 2>/dev/null
	modprobe -q padlock-aes 2>/dev/null
	modprobe -q padlock-sha 2>/dev/null
	# load the most common ciphers/algo's
	# aes-x86_64 has higher priority in via crypto api
	for crypto in aesni_intel aes-x86_64 geode-aes aes aes_generic des sha512 sha256 md5 cbc xcbc ecb twofish blowfish serpent ccm gcm \
			ctr cts deflate cast5 cast6 lzo sha256_generic sha512_generic camellia
                   do
                        #echo -n "$crypto "
                        modprobe -q $crypto 2> /dev/null
                   done

# load IPsec module if possible
if test -f $ipsecversion && test -f $netkey
then
    # both KLIPS and NETKEY code detected, bail out
    echo "FATAL ERROR: Both KLIPS and NETKEY IPsec code is present in kernel"
    exit
fi
if test ! -f $ipsecversion && test ! -f $netkey
then
	modprobe -q ipsec 2> /dev/null
fi

if test ! -f $ipsecversion 
then
	echo "No KLIPS support found while requested, desperately falling back to netkey"
	modprobe -q af_key

	if test ! -f $netkey
	then
		echo "Even NETKEY support is not there, aborting"
		exit 1
	else
		echo "NETKEY support found. Use protostack=netkey in /etc/ipsec.conf to avoid attempts to use KLIPS. Attempting to continue with NETKEY"
		exec ipsec _startnetkey
	fi
fi

if test ! -f $ipsecversion
then
	echo "kernel appears to lack IPsec support (neither CONFIG_KLIPS or CONFIG_NET_KEY were found)"
	exit 1
fi

# figure out debugging flags
case "$debug" in
'')	debug=none	;;
esac
if test -r /proc/net/ipsec_klipsdebug
then
	echo "KLIPS debug \`$debug'" | logonly
	case "$debug" in
	none)	ipsec klipsdebug --none	;;
	all)	ipsec klipsdebug --all	;;
	*)	ipsec klipsdebug --none
		for d in $debug
		do
			ipsec klipsdebug --set $d
		done
		;;
	esac
elif $klips
then
	if test " $debug" != " none"
	then
		echo "klipsdebug=\`$debug' ignored, KLIPS lacks debug facilities"
	fi
fi

# Tune ipsec module OCF settings
if [ -d /sys/module/ocf ]
then
        echo $limit  > /sys/module/ipsec/parameters/ipsec_irs_cache_allocated_max
        echo $limit  > /sys/module/ipsec/parameters/ipsec_ixs_cache_allocated_max
fi

# figure out misc. kernel config
if test -d $sysflags
then
	sysflag "$fragicmp" "fragicmp" yes icmp
	echo 1 >$sysflags/inbound_policy_check		# no debate
	sysflag no "no_eroute_pass" no no_eroute_pass	# obsolete parm
	sysflag no "opportunistic" no opportunistic	# obsolete parm
	sysflag "$hidetos" "hidetos" yes tos
elif $klips
then
	echo "WARNING: cannot adjust KLIPS flags, no $sysflags directory!"
	# carry on
fi

if $mast
then
    # flush policy out
    iptables -t mangle -F IPSEC          >/dev/null 2>/dev/null
    iptables -t mangle -F NEW_IPSEC_CONN >/dev/null 2>/dev/null
    # remove any ipsecX interfaces, as we are using mastX
    # Note: currently it seems klips cannot delete ipsec0
    for device in `ip -oneline link show | grep ipsec | cut -d: -f2 | sort -r`; do
        ipsec tncfg --delete $device
    done

elif $klips
then
	# clear tables out in case dregs have been left over
	ipsec eroute --clear
	ipsec spi --clear
	# remove any mastX interfaces, as we are using ipsecX
        for device in `ip -oneline link show | grep mast | cut -d: -f2 | sort -r`; do
            ipsec tncfg --delete $device
        done
fi

# figure out interfaces
# note: that mast0 is not "bound" via tncfg to a physical interface, so we
#       ignore it, but it allows things to work with interfaces=%defaultroute
#       and allows us to ignore interfaces="mast0=eth0"
for i
do
	case "$i" in
	ipsec*=?*|mast*=?*)	klipsinterface "$i"	;;
	%defaultroute)	defaultinterface	;;
	*)	echo "interface \`$i' not understood"
		exit 1
		;;
	esac
done

# vim: set noet sw=8 ts=8
exit 0
