#!/bin/sh /etc/rc.common
# Copyright (C) 2008  Alina Friedrichsen <x-alina@gmx.net>
START=50

RADVD_INTERFACE_STRING_OPTIONS='MaxRtrAdvInterval MinRtrAdvInterval MinDelayBetweenRAs AdvLinkMTU AdvReachableTime AdvRetransTimer AdvCurHopLimit AdvDefaultLifetime AdvDefaultPreference HomeAgentLifetime HomeAgentPreference'
RADVD_INTERFACE_BOOLEAN_OPTIONS='IgnoreIfMissing AdvSendAdvert UnicastOnly AdvManagedFlag AdvOtherConfigFlag AdvSourceLLAddress AdvHomeAgentFlag AdvHomeAgentInfo AdvMobRtrSupportFlag AdvIntervalOpt'

RADVD_PREFIX_STRING_OPTIONS='AdvValidLifetime AdvPreferredLifetime'
RADVD_PREFIX_BOOLEAN_OPTIONS='AdvOnLink AdvAutonomous AdvRouterAddr'

RADVD_ROUTE_STRING_OPTIONS='AdvRouteLifetime AdvRoutePreference'
RADVD_ROUTE_BOOLEAN_OPTIONS=''

RADVD_RDNSS_STRING_OPTIONS='AdvRDNSSPreference AdvRDNSSLifetime'
RADVD_RDNSS_BOOLEAN_OPTIONS='AdvRDNSSOpen'

validate_varname() {
	local varname=$1
	[ -z "$varname" -o "$varname" != "${varname%%[!A-Za-z0-9_]*}" ] && return 1
	return 0
}

validate_ifname() {
	local ifname=$1
	[ -z "$ifname" -o "$ifname" != "${ifname%%[!A-Za-z0-9.:_-]*}" ] && return 1
	return 0
}

validate_ip6addr() {
	local ip6addr=$1
	[ -z "$ip6addr" -o "$ip6addr" != "${ip6addr%%[!A-Fa-f0-9.:]*}" ] && return 1
	return 0
}

validate_ip6prefix() {
	local ip6prefix=$1
	[ -z "$ip6prefix" -o "$ip6prefix" != "${ip6prefix%%[!A-Fa-f0-9./:]*}" ] && return 1
	return 0
}

validate_radvd_string() {
	local radvd_string=$1
	[ -z "$radvd_string" -o "$radvd_string" != "${radvd_string%%[!a-z0-9.:_-]*}" ] && return 1
	return 0
}

get_ifname() {
	local interface=$1
	validate_varname "$interface" || return 1
	local cfgt
	local ifname

	scan_interfaces
	config_get cfgt "$interface" TYPE
	[ "$cfgt" != "interface" ] && return 1
	config_get ifname "$interface" ifname
	validate_ifname "$ifname" || return 1
	printf '%s\n' "$ifname"

	return 0
}

get_ip6addr() {
	local ifname=$1
	local scope=$2
	local iproute2_scope
	local ifconfig_scope
	local ip6addr

	case "$scope" in
		host) iproute2_scope=host ifconfig_scope=Host;;
		link) iproute2_scope=link ifconfig_scope=Link;;
		site) iproute2_scope=site ifconfig_scope=Site;;
		global) iproute2_scope=global ifconfig_scope=Global;;
		"") get_ip6addr "$ifname" global || get_ip6addr "$ifname" site; return;;
		*) return 1;;
	esac

	ip6addr=$(LANG=C ip -f inet6 -- addr show dev "$ifname" 2> /dev/null | sed -n -e 's/^  *inet6 \([A-Fa-f0-9.:]*[/][0-9]*\) scope '"$iproute2_scope"' $/\1/p' | head -n 1)
	if [ -z "$ip6addr" ]; then
		ip6addr=$(LANG=C ifconfig "$ifname" 2> /dev/null | sed -n -e 's/^  *inet6 addr: \([A-Fa-f0-9.:]*[/][0-9]*\) Scope:'"$ifconfig_scope"'$/\1/p' | tail -n 1)
		[ -z "$ip6addr" ] && return 1
	fi

	printf '%s\n' "$ip6addr"

	return 0
}

radvd_find_config_file() {
	local cfg=$1
	validate_varname "$cfg" || return 0

	config_get_bool ignore "$cfg" ignore 0
	[ "$ignore" -ne 0 ] && return 0
	config_get RADVD_CONFIG_FILE "$cfg" config_file

	return 0
}

radvd_add_interface() {
	local cfg=$1
	validate_varname "$cfg" || return 0
	local ignore
	local interfaces
	local interface
	local list_interface
	local exist

	config_get ignore "$cfg" ignore
	[ "$ignore" -ne 0 ] && return 0

	config_get interfaces "$cfg" interface
	for interface in $interfaces; do
		validate_varname "$interface" || continue
		exist=0
		for list_interface in $RADVD_INTERFACES; do
			[ "$interface" = "$list_interface" ] && exist=1
		done
		[ "$exist" -eq 0 ] && RADVD_INTERFACES="$RADVD_INTERFACES $interface"
	done

	return 0
}

radvd_write_interface() {
	local cfg=$1
	validate_varname "$cfg" || return 0
	local ignore
	local interfaces
	local interface
	local name
	local value

	config_get_bool ignore "$cfg" ignore 0
	[ "$ignore" -ne 0 ] && return 0

	config_get interfaces "$cfg" interface
	exist=0
	for interface in $interfaces; do
		[ "$INTERFACE" = "$interface" ] && exist=1
	done
	[ "$exist" -eq 0 ] && return 0

	for name in $RADVD_INTERFACE_STRING_OPTIONS; do
		config_get value "$cfg" "$name"
		validate_radvd_string "$value" || continue
		printf '\t%s %s;\n' "$name" "$value"
	done

	for name in $RADVD_INTERFACE_BOOLEAN_OPTIONS; do
		config_get value "$cfg" "$name"
		[ -z "$value" ] && continue
		config_get_bool value "$cfg" "$name" 0
		if [ "$value" -ne 0 ]; then 
			printf '\t%s on;\n' "$name"
		else
			printf '\t%s off;\n' "$name"
		fi
	done

	return 0
}

radvd_write_prefix() {
	local cfg=$1
	validate_varname "$cfg" || return 0
	local ignore
	local interfaces
	local interface
	local prefixes
	local prefix
	local name
	local value
	local cfgt

	config_get_bool ignore "$cfg" ignore 0
	[ "$ignore" -ne 0 ] && return 0

	config_get interfaces "$cfg" interface
	exist=0
	for interface in $interfaces; do
		[ "$INTERFACE" = "$interface" ] && exist=1
	done
	[ "$exist" -eq 0 ] && return 0

	config_get prefixes "$cfg" prefix
	if [ -z "$prefixes" ]; then
		prefixes=$(get_ip6addr "$IFNAME") || return 0
	fi

	for prefix in $prefixes; do
		validate_ip6prefix "$prefix" || continue
		printf '\n\tprefix %s\n\t{\n' "$prefix"

		for name in $RADVD_PREFIX_STRING_OPTIONS; do
			config_get value "$cfg" "$name"
			validate_radvd_string "$value" || continue
			printf '\t\t%s %s;\n' "$name" "$value"
		done

		for name in $RADVD_PREFIX_BOOLEAN_OPTIONS; do
			config_get value "$cfg" "$name"
			[ -z "$value" ] && continue
			config_get_bool value "$cfg" "$name" 0
			if [ "$value" -ne 0 ]; then 
				printf '\t\t%s on;\n' "$name"
			else
				printf '\t\t%s off;\n' "$name"
			fi
		done

		config_get value "$cfg" Base6to4Interface
		if [ -n "$value" ]; then
			if ifname=$(get_ifname "$value"); then
				printf '\t\t%s %s;\n' "Base6to4Interface" "$ifname"
			fi
		fi

		printf '\t};\n'
	done

	return 0
}

radvd_write_route() {
	local cfg=$1
	validate_varname "$cfg" || return 0
	local ignore
	local interfaces
	local interface
	local prefixes
	local prefix
	local name
	local value

	config_get_bool ignore "$cfg" ignore 0
	[ "$ignore" -ne 0 ] && return 0

	config_get interfaces "$cfg" interface
	exist=0
	for interface in $interfaces; do
		[ "$INTERFACE" = "$interface" ] && exist=1
	done
	[ "$exist" -eq 0 ] && return 0

	config_get prefixes "$cfg" prefix
	for prefix in $prefixes; do
		validate_ip6prefix "$prefix" || continue
		printf '\n\troute %s\n\t{\n' "$prefix"

		for name in $RADVD_ROUTE_STRING_OPTIONS; do
			config_get value "$cfg" "$name"
			validate_radvd_string "$value" || continue
			printf '\t\t%s %s;\n' "$name" "$value"
		done

		for name in $RADVD_ROUTE_BOOLEAN_OPTIONS; do
			config_get value "$cfg" "$name"
			[ -z "$value" ] && continue
			config_get_bool value "$cfg" "$name" 0
			if [ "$value" -ne 0 ]; then 
				printf '\t\t%s on;\n' "$name"
			else
				printf '\t\t%s off;\n' "$name"
			fi
		done

		printf '\t};\n'
	done

	return 0
}

radvd_write_rdnss() {
	local cfg=$1
	validate_varname "$cfg" || return 0
	local ignore
	local interfaces
	local interface
	local addrs
	local addr
	local addr_list
	local name
	local value
	local i

	config_get_bool ignore "$cfg" ignore 0
	[ "$ignore" -ne 0 ] && return 0

	config_get interfaces "$cfg" interface
	exist=0
	for interface in $interfaces; do
		[ "$INTERFACE" = "$interface" ] && exist=1
	done
	[ "$exist" -eq 0 ] && return 0

	config_get addrs "$cfg" addr
	i=0
	for addr in $addrs; do
		[ "$i" -ge 3 ] && break
		validate_ip6addr "$addr" || continue
		addr_list="$addr_list $addr"
		i=$(($i+1))
	done

	if [ -z "$addr_list" ]; then
		addr=$(get_ip6addr "$IFNAME" link) || return 0
		addr_list=" ${addr%%[/]*}"
	fi

	printf '\n\tRDNSS%s\n\t{\n' "$addr_list"

	for name in $RADVD_RDNSS_STRING_OPTIONS; do
		config_get value "$cfg" "$name"
		validate_radvd_string "$value" || continue
		printf '\t\t%s %s;\n' "$name" "$value"
	done

	for name in $RADVD_RDNSS_BOOLEAN_OPTIONS; do
		config_get value "$cfg" "$name"
		[ -z "$value" ] && continue
		config_get_bool value "$cfg" "$name" 0
		if [ "$value" -ne 0 ]; then 
			printf '\t\t%s on;\n' "$name"
		else
			printf '\t\t%s off;\n' "$name"
		fi
	done

	printf '\t};\n'

	return 0
}

radvd_write_config() {
	include /lib/network

	RADVD_INTERFACES=
	config_foreach radvd_add_interface interface
	config_foreach radvd_add_interface prefix
	config_foreach radvd_add_interface route
	config_foreach radvd_add_interface RDNSS

	for INTERFACE in $RADVD_INTERFACES; do
		IFNAME=$(get_ifname "$INTERFACE") || continue
		printf 'interface %s\n{\n' "$IFNAME"
		config_foreach radvd_write_interface interface
		config_foreach radvd_write_prefix prefix
		config_foreach radvd_write_route route
		config_foreach radvd_write_rdnss rdnss
		printf '};\n\n'
	done

	return 0
}

start() {
	config_load radvd

	RADVD_CONFIG_FILE=
	config_foreach radvd_find_config_file radvd

	if [ -z "$RADVD_CONFIG_FILE" ]; then
		mkdir -p -- /var/etc/
		radvd_write_config > /var/etc/radvd.conf
		if [ -s "/var/etc/radvd.conf" ]; then
			RADVD_CONFIG_FILE=/var/etc/radvd.conf
		fi
	fi

	[ -z "$RADVD_CONFIG_FILE" ] && return 1

	sysctl -w net.ipv6.conf.all.forwarding=1 > /dev/null 2> /dev/null

	radvd -C "$RADVD_CONFIG_FILE" -m stderr_syslog -p /var/run/radvd.pid
}

stop() {
	killall radvd
}
