#!/bin/sh
#
# Copyright (c) 2003-2006  Hajimu UMEMOTO
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
#
# $Mahoroba: src/dtcpclient/dtcpclient.script,v 1.31 2009/08/12 02:36:02 ume Exp $
#

# DON'T EDIT THIS FILE UNLESS YOU KNOW WHAT YOU ARE DOING.  PUT YOUR
# CONFIGURATION INTO /usr/pkg/sbin/dtcpclient-script.conf, INSTEAD.
#
# Set gif interface name to be used for tunnel.
# FreeBSD: If tunif is not set, gif will be created dynamically.
# MacOSX: If tunif is not set, interface will be determined
# automatically.  When you set it explicitly, make sure having
# specified gif interface beforehand.
# Other: default is gif0
#tunif='gif0'
#
# Set to static route. (default: default)
#static_routes='default'
#
# Set IPv6 address if you wish to add it to gif interface. (default: '')
#tunif_addrs=''
#
# Set to "YES" if you don't want to destroy cloned interface.
# (default: NO)
# FreeBSD: If tunif is not defined, this will be treated as "NO".
# MacOSX: This is ignored.
#cloned_interface_keep="NO"
#
# Set definition of prefix delegation if you want to do prefix
# delegation with network type tunnel.
# The format is `interface/slaid/hostid/prefixlen'.
# The default of `slaid' is `0'.
# When `hostid' is ommitted, EUI-64 address is assumed.
# When `anycast' is specified as `hostid', anycast address is
# assigned.
# The default of `prefixlen' is `64'.
# If `@' is specified as interface, it is substituted with the tunnel
# interface.
# If `AUTO' is specified, delegated IPv6 address is assigned to upped
# interface automatically.
#prefix_delegation='sis0/1'
#
# If you want to execute more commands at up/down, you can set
# `up_command' and `down_command'.

logger -t dtcpclient $*

state=$1
server=$2
myaddr=$3
tuntype=$4
me=$5
her=$6

case ${tuntype} in
host)
	me6=$7
	her6=$8
	;;
network)
	if [ -z "$8" ]; then
		prefixes=$7
	else
		me6=$7
		her6=$8
		prefixes=$9
	fi
	;;
esac

OIFS="$IFS"
IFS=";"
set ${myaddr}
myaddr=$1
me_port=$2
set ${her}
her=$1
her_port=$2
IFS="$OIFS"
if [ -n "${me_port}" ]; then
	udp_tunnel=1
fi

if [ -r /usr/pkg/sbin/dtcpclient-script.conf ]; then
	. /usr/pkg/sbin/dtcpclient-script.conf
fi

nickname=`echo ${server} | sed -e 's/[\.:\-]/_/g' -e 's/^\([0-9]\)/_\1/'`

for _var in tunif static_routes tunif_addrs cloned_interface_keep \
	    prefix_delegation up_command down_command; do
	eval _val=\$${nickname}_${_var}
	if [ -n "${_val}" ]; then
		eval ${_var}=\${_val}
	fi
done

static_routes=${static_routes:-'default'}
cloned_interface_keep=${cloned_interface_keep:-'NO'}

ifconfig () {
#	echo ifconfig $*
#	logger -t dtcpclient ifconfig $*
	/sbin/ifconfig $*
}
route () {
#	echo route $*
#	logger -t dtcpclient route $*
	/sbin/route $*
}

str_split() {
	OIFS="$IFS"
	IFS="$1"
	set $2
	IFS="$OIFS"
	echo $*
}

construct_addr() {
	prefix=$1
	slaid=$2
	hostid=$3
	addr=`expr "${prefix}" : '\(.*\)/\(.*\)'`
	OIFS="$IFS"
	IFS=":"
	set ${addr}
	IFS="$OIFS"
	slaid=$((`printf "%d" 0x${4:-0}` + `printf "%d" 0x${slaid}`))
	echo $1':'$2':'$3':'`printf "%x" ${slaid}`':'${hostid}
}

getladdr()
{
	ifconfig $1 2>/dev/null | while read proto addr rest; do
		[ ${proto} != 'inet6' ] && continue
		case ${addr} in
		fe80::*)
			echo ${addr}
			return
			;;
		esac
	done
}

gethostid()
{
	expr "`getladdr $1`" : 'fe80::\(.*\)%\(.*\)'
}

gif_inuse()
{
	expect_me=$1; expect_her=$2
	OIFS="$IFS"
	IFS=
	ifconfig -a 2>/dev/null | while read line; do
		t=`expr "${line}" : '\([a-zA-Z0-9]*\):.*'`
		if [ -n "${t}" ]; then
			iface="${t}"
			continue
		fi
		IFS="$OIFS"
		set ${line}
		IFS=
		tunnel=$1; proto=$2; me=$3; arrow=$4; her=$5
		[ ${tunnel} = 'tunnel' ] || continue
		if [ ${me} = ${expect_me} -a ${her} = ${expect_her} ]; then
			echo ${iface}
			break
		fi
	done
	IFS="$OIFS"
}

gif_readytouse()
{
	OIFS="$IFS"
	IFS=
	peer='NONE'
	(ifconfig -a 2>/dev/null; echo '000:') | while read line; do
		t=`expr "${line}" : '\([a-zA-Z0-9]*\):.*'`
		if [ -n "${t}" ]; then
			if [ -z "${peer}" ]; then
				ifname=`echo ${iface} | cut -c 1-3`
				if [ ${ifname} = 'gif' -o ${ifname} = '000' ]
				then
					echo "${iface}"
					break;
				fi
			fi
			iface="${t}"
			peer=
			continue
		fi
		IFS="$OIFS"
		set ${line}
		IFS=
		tunnel=$1; proto=$2; me=$3; arrow=$4; her=$5
		[ ${tunnel} = 'tunnel' ] && peer="$me $her"
	done
	IFS="$OIFS"
}

upped_ether() {
	ifconfig -u | awk '
		BEGIN {
			iface = ""
		}
		/^[a-z]+[0-9]+: / {
			iface = $1
			sub(/:.*/, "", iface)
		}
		/[\t ]+ether / {
			print iface
		}
	'
}

lockfile=${lockfile:-'/var/run/dtcpclient.lock'}

do_lock()
{
	until mkdir ${lockfile} > /dev/null 2>&1; do
		sleep 1
	done
}

do_unlock()
{
	rmdir ${lockfile}
}

do_prefix_delegation() {
	is_up=$1
	prefix=$2
	if [ -n "${prefix_delegation}" ]; then
		plen=`expr "${prefix}" : '.*/\(.*\)'`
		if [ ${plen:-64} -lt 64 ]; then
			case "${is_up}" in
			up)
				route delete -inet6 ${prefix} > /dev/null 2>&1
				route add -inet6 ${prefix} ::1 -reject
				;;
			down)
				route delete -inet6 ${prefix}
				;;
			esac
		fi
		for pd in `str_split ',' ${prefix_delegation}`; do
			OIFS="$IFS"
			IFS="/"
			set ${pd}
			IFS="$OIFS"
			lanif=$1
			if [ X${lanif} = X'@' ]; then
				lanif=${tunif}
			fi
			slaid=${2:-0}
			hostid=$3
			ifconfig ${lanif} up
			if [ $system = 'Darwin' ]; then
				[ -z "`getladdr ${lanif}`" ] && ip6 -u ${lanif}
			fi
			hostid=${hostid:-`gethostid ${lanif}`}
			if [ ${hostid} = "anycast" ]; then
				hostid=": anycast"
			fi
			hostid=${hostid:-': eui64'}
			prefixlen=${4:-64}
			addr=`construct_addr ${prefix} ${slaid} "${hostid}"`
			case "${is_up}" in
			up)
				alias="alias"
				;;
			down)
				alias="-alias"
				;;
			esac
			ifconfig ${lanif} inet6 ${addr} prefixlen ${prefixlen} ${alias}
		done
	fi
}

ng_mkpeer() {
	ngctl -f - 2> /dev/null <<EOF
mkpeer iface dummy inet6
msg dummy nodeinfo
EOF
}

ng_iface_create_one() {
	ng_mkpeer | while read line; do
		t=`expr "${line}" : '.* name="\(ng[0-9]*\)" .*'`
		if [ -n "${t}" ]; then
			echo ${t}
			return
		fi
	done
}

ng_iface_create() {
	local req_iface iface bogus
	req_iface="$1"

	if [ -z "${req_iface}" ]; then
		iface=`ng_iface_create_one`
		if [ -z "${iface}" ]; then
		exit 2
		fi
		echo ${iface}
		return
	fi

	ngctl shutdown ${req_iface}: > /dev/null 2>&1

	bogus=""
	while true; do
		iface=`ng_iface_create_one`
		if [ -z "${iface}" ]; then
			exit 2
		fi
		if [ "${iface}" = "${req_iface}" ]; then
			echo ${iface}
			break
		fi
		bogus="${bogus} ${iface}"
	done

	for iface in ${bogus}; do
		ngctl shutdown ${iface}:
	done
}

if [ -n "${udp_tunnel}" ]; then
	case ${tunif} in
	ng[0-9]*)
		;;
	*)
		tunif='ng0'
		;;
	esac
fi

case "${prefix_delegation}" in
[Aa][Uu][Tt][Oo])
	iface=`upped_ether`
	if [ -n "${iface}" ]; then
		set ${iface}
		prefix_delegation=$1
	else
		prefix_delegation="@/0//128"
	fi
	;;
[Nn][Oo])
	prefix_delegation=""
	;;
*)
	;;
esac

system=`uname -s`

if [ -z "${tunif}" ]; then
	if [ $system = 'FreeBSD' -o ${system} = 'Darwin' ]; then
		tunif='gif'
	else
		tunif='gif0'
	fi
fi
if [ ${system} = 'Darwin' ]; then
	cloned_interface_keep='YES'
elif [ ${tunif} = 'gif' ]; then
	cloned_interface_keep='NO'
fi

case ${state} in
up)
	if [ -n "${udp_tunnel}" ]; then
		_tunif=`ng_iface_create ${tunif}`
		[ -n "${_tunif}" ] && tunif=${_tunif}
		ngctl mkpeer ${tunif}: ksocket inet6 inet/dgram/udp
		ngctl msg ${tunif}:inet6 bind inet/${myaddr}:${me_port}
		ngctl msg ${tunif}:inet6 connect inet/${her}:${her_port}
	else
		if [ ${system} = 'Darwin' ]; then
			if [ ${tunif} = 'gif' ]; then
				do_lock
				locked='YES'
				tunif=`gif_readytouse`
			fi
		else
			_tunif=`ifconfig ${tunif} create 2> /dev/null`
			[ -n "${_tunif}" ] && tunif=${_tunif}
		fi
		ifconfig ${tunif} tunnel ${myaddr} ${her}
	fi
	if [ -n "${DTCP_MTU}" ]; then
		ifconfig ${tunif} mtu ${DTCP_MTU}
	fi
	ifconfig ${tunif} up
	if [ -z "${udp_tunnel}" -a ${system} = 'Darwin' ]; then
		[ -z "`getladdr ${tunif}`" ] && ip6 -u ${tunif}
		if [ -n "${locked}" ]; then
			do_unlock
			locked=''
		fi
	fi
	case ${tuntype} in
	host)
		ifconfig ${tunif} inet6 ${me6} ${her6} prefixlen 128
		;;
	network)
		if [ -n "${me6}" -a -n "${her6}" ]; then
			ifconfig ${tunif} inet6 ${me6} ${her6} prefixlen 128
		fi
		for prefix in `str_split ',' ${prefixes}`; do
			do_prefix_delegation ${state} ${prefix}
		done
		;;
	esac
	if [ -n "${tunif_addrs}" ]; then
		for tunif_addr in `str_split ',' ${tunif_addrs}`; do
			ifconfig ${tunif} inet6 ${tunif_addr} alias
		done
	fi
	if [ -n "${static_routes}" ]; then
		for static_route in `str_split ',' ${static_routes}`; do
			route delete -inet6 ${static_route} > /dev/null 2>&1
			route add -inet6 ${static_route} ::1 -ifp ${tunif}
		done
	fi
	if [ -n "${up_command}" ]; then
		export state server myaddr tuntype me her me6 her6 prefixes
		eval ${up_command}
	fi
	;;
down)
	if [ -n "${down_command}" ]; then
		export state server myaddr tuntype me her me6 her6 prefixes
		eval ${down_command}
	fi
	if [ ${tunif} = 'gif' ]; then
		tunif=`gif_inuse ${myaddr} ${her}`
	fi
	if [ -n "${static_routes}" ]; then
		for static_route in `str_split ',' ${static_routes}`; do
			route delete -inet6 ${static_route} ::1 -ifp ${tunif}
		done
	fi
	if [ -n "${tunif_addrs}" ]; then
		for tunif_addr in `str_split ',' ${tunif_addrs}`; do
			ifconfig ${tunif} inet6 ${tunif_addr} -alias
		done
	fi
	case ${tuntype} in
	host)
		ifconfig ${tunif} inet6 ${me6} ${her6} prefixlen 128 -alias
		;;
	network)
		if [ -n "${me6}" -a -n "${her6}" ]; then
			ifconfig ${tunif} inet6 ${me6} ${her6} prefixlen 128 \
				-alias
		fi
		for prefix in `str_split ',' ${prefixes}`; do
			do_prefix_delegation ${state} ${prefix}
		done
		;;
	esac
	if [ -n "${DTCP_MTU}" ]; then
		ifconfig ${tunif} mtu 1280
	fi
	ifconfig ${tunif} down
	if [ -n "${udp_tunnel}" ]; then
		ngctl shutdown ${tunif}:
	else
		ifconfig ${tunif} deletetunnel
		case ${cloned_interface_keep} in
		[Yy][Ee][Ss])
			;;
		*)
			ifconfig ${tunif} destroy
			;;
		esac
	fi
	;;
esac
