#!/bin/sh /etc/rc.common

START=90
STOP=10

USE_PROCD=1
PROG=/usr/lib/ipsec/starter

. $IPKG_INSTROOT/lib/functions.sh
. $IPKG_INSTROOT/lib/functions/network.sh

IPSEC_SECRETS_FILE=/etc/ipsec.secrets
IPSEC_CONN_FILE=/etc/ipsec.conf
STRONGSWAN_CONF_FILE=/etc/strongswan.conf

IPSEC_VAR_SECRETS_FILE=/var/ipsec/ipsec.secrets
IPSEC_VAR_CONN_FILE=/var/ipsec/ipsec.conf
STRONGSWAN_VAR_CONF_FILE=/var/ipsec/strongswan.conf

WAIT_FOR_INTF=0

file_reset() {
	: > "$1"
}

xappend() {
	local file="$1"
	shift

	echo "${@}" >> "${file}"
}

remove_include() {
	local file="$1"
	local include="$2"

	sed -i "\_${include}_d" "${file}"
}

remove_includes() {
	remove_include "${IPSEC_CONN_FILE}" "${IPSEC_VAR_CONN_FILE}"
	remove_include "${IPSEC_SECRETS_FILE}" "${IPSEC_VAR_SECRETS_FILE}"
	remove_include "${STRONGSWAN_CONF_FILE}" "${STRONGSWAN_VAR_CONF_FILE}"
}

do_include() {
	local conf="$1"
	local uciconf="$2"
	local backup=`mktemp -t -p /tmp/ ipsec-init-XXXXXX`

	[ ! -f "${conf}" ] && rm -rf "${conf}"
	touch "${conf}"

	cat "${conf}" | grep -v "${uciconf}" > "${backup}"
	mv "${backup}" "${conf}"
	xappend "${conf}" "include ${uciconf}"
	file_reset "${uciconf}"
}

ipsec_reset() {
	do_include "${IPSEC_CONN_FILE}" "${IPSEC_VAR_CONN_FILE}"
}

ipsec_xappend() {
	xappend "${IPSEC_VAR_CONN_FILE}" "$@"
}

swan_reset() {
	do_include "${STRONGSWAN_CONF_FILE}" "${STRONGSWAN_VAR_CONF_FILE}"
}

swan_xappend() {
	xappend "${STRONGSWAN_VAR_CONF_FILE}" "$@"
}

secret_reset() {
	do_include "${IPSEC_SECRETS_FILE}" "${IPSEC_VAR_SECRETS_FILE}"
}

secret_xappend() {
	xappend "${IPSEC_VAR_SECRETS_FILE}" "$@"
}

warning() {
	echo "WARNING: $@" >&2
}

add_crypto_proposal() {
	local encryption_algorithm
	local hash_algorithm
	local dh_group

	config_get encryption_algorithm  "$1" encryption_algorithm
	config_get hash_algorithm        "$1" hash_algorithm
	config_get dh_group              "$1" dh_group

	[ -n "${encryption_algorithm}" ] && \
		crypto="${crypto:+${crypto},}${encryption_algorithm}${hash_algorithm:+-${hash_algorithm}}${dh_group:+-${dh_group}}"
}

set_crypto_proposal() {
	local conf="$1"
	local proposal

	crypto=""

	config_get crypto_proposal "$conf" crypto_proposal ""
	for proposal in $crypto_proposal; do
		add_crypto_proposal "$proposal"
	done

	[ -n "${crypto}" ] && {
		local force_crypto_proposal

		config_get_bool force_crypto_proposal "$conf" force_crypto_proposal

		[ "${force_crypto_proposal}" = "1" ] && crypto="${crypto}!"
	}

	crypto_proposal="${crypto}"
}

config_conn() {
	# Generic ipsec conn section shared by tunnel and transport
	local mode
	local local_subnet
	local local_nat
	local local_sourceip
	local local_updown
	local local_firewall
	local remote_subnet
	local remote_sourceip
	local remote_updown
	local remote_firewall
	local ikelifetime
	local lifetime
	local margintime
	local keyingtries
	local dpdaction
	local dpddelay
	local inactivity
	local keyexchange

	config_get mode                     "$1"           mode "route"
	config_get local_subnet             "$1"           local_subnet ""
	config_get local_nat                "$1"           local_nat ""
	config_get local_sourceip           "$1"           local_sourceip ""
	config_get local_updown             "$1"           local_updown ""
	config_get local_firewall           "$1"           local_firewall ""
	config_get remote_subnet            "$1"           remote_subnet ""
	config_get remote_sourceip          "$1"           remote_sourceip ""
	config_get remote_updown            "$1"           remote_updown ""
	config_get remote_firewall          "$1"           remote_firewall ""
	config_get ikelifetime              "$1"           ikelifetime "3h"
	config_get lifetime                 "$1"           lifetime "1h"
	config_get margintime               "$1"           margintime "9m"
	config_get keyingtries              "$1"           keyingtries "3"
	config_get dpdaction                "$1"           dpdaction "none"
	config_get dpddelay                 "$1"           dpddelay "30s"
	config_get inactivity               "$1"           inactivity
	config_get keyexchange              "$1"           keyexchange "ikev2"

	[ -n "$local_nat" ] && local_subnet=$local_nat

	ipsec_xappend "conn $config_name-$1"
	ipsec_xappend "  left=%any"
	ipsec_xappend "  right=$remote_gateway"

	[ -n "$local_sourceip" ] && ipsec_xappend "  leftsourceip=$local_sourceip"
	[ -n "$local_subnet" ] && ipsec_xappend "  leftsubnet=$local_subnet"

	[ -n "$local_firewall" ] && ipsec_xappend "  leftfirewall=$local_firewall"
	[ -n "$remote_firewall" ] && ipsec_xappend "  rightfirewall=$remote_firewall"

	ipsec_xappend "  ikelifetime=$ikelifetime"
	ipsec_xappend "  lifetime=$lifetime"
	ipsec_xappend "  margintime=$margintime"
	ipsec_xappend "  keyingtries=$keyingtries"
	ipsec_xappend "  dpdaction=$dpdaction"
	ipsec_xappend "  dpddelay=$dpddelay"

	[ -n "$inactivity" ] && ipsec_xappend "  inactivity=$inactivity"

	if [ "$auth_method" = "psk" ]; then
		ipsec_xappend "  leftauth=psk"
		ipsec_xappend "  rightauth=psk"

		[ "$remote_sourceip" != "" ] && ipsec_xappend "  rightsourceip=$remote_sourceip"
		[ "$remote_subnet" != "" ] && ipsec_xappend "  rightsubnet=$remote_subnet"

		ipsec_xappend "  auto=$mode"
	else
		warning "AuthenticationMethod $auth_method not supported"
	fi

	[ -n "$local_identifier" ] && ipsec_xappend "  leftid=$local_identifier"
	[ -n "$remote_identifier" ] && ipsec_xappend "  rightid=$remote_identifier"
	[ -n "$local_updown" ] && ipsec_xappend "  leftupdown=$local_updown"
	[ -n "$remote_updown" ] && ipsec_xappend "  rightupdown=$remote_updown"
	ipsec_xappend "  keyexchange=$keyexchange"

	set_crypto_proposal "$1"
	[ -n "${crypto_proposal}" ] && ipsec_xappend "  esp=$crypto_proposal"
	[ -n "${ike_proposal}" ] && ipsec_xappend "  ike=$ike_proposal"
}

config_tunnel() {
	config_conn "$1"

	# Specific for the tunnel part
	ipsec_xappend "  type=tunnel"
}

config_transport() {
	config_conn "$1"

	# Specific for the transport part
	ipsec_xappend "  type=transport"
}

config_remote() {
	local enabled
	local gateway
	local pre_shared_key
	local auth_method

	config_name=$1

	config_get_bool enabled "$1" enabled 0
	[ $enabled -eq 0 ] && return

	config_get gateway           "$1" gateway
	config_get pre_shared_key    "$1" pre_shared_key
	config_get auth_method       "$1" authentication_method
	config_get local_identifier  "$1" local_identifier ""
	config_get remote_identifier "$1" remote_identifier ""

	[ "$gateway" = "any" ] && remote_gateway="%any" || remote_gateway="$gateway"

	[ -z "$local_identifier" ] && {
		local ipdest

		[ "$remote_gateway" = "%any" ] && ipdest="1.1.1.1" || ipdest="$remote_gateway"
		local_gateway=`ip route get $ipdest | awk -F"src" '/src/{gsub(/ /,"");print $2}'`
	}

	[ -n "$local_identifier" ] && secret_xappend -n "$local_identifier " || secret_xappend -n "$local_gateway "
	[ -n "$remote_identifier" ] && secret_xappend -n "$remote_identifier " || secret_xappend -n "$remote_gateway "

	secret_xappend ": PSK \"$pre_shared_key\""

	set_crypto_proposal "$1"
	ike_proposal="$crypto_proposal"

	config_list_foreach "$1" tunnel config_tunnel

	config_list_foreach "$1" transport config_transport

	ipsec_xappend ""
}

config_ipsec() {
	local debug
	local rtinstall_enabled
	local routing_tables_ignored
	local routing_table
	local routing_table_id
	local interface
	local device_list

	ipsec_reset
	secret_reset
	swan_reset

	ipsec_xappend "# generated by /etc/init.d/ipsec"
	ipsec_xappend "version 2"
	ipsec_xappend ""

	secret_xappend "# generated by /etc/init.d/ipsec"

	config_get debug "$1" debug 0
	config_get_bool rtinstall_enabled "$1" rtinstall_enabled 1
	[ $rtinstall_enabled -eq 1 ] && install_routes=yes || install_routes=no

	# prepare extra charon config option ignore_routing_tables
	for routing_table in $(config_get "$1" "ignore_routing_tables"); do
		if [ "$routing_table" -ge 0 ] 2>/dev/null; then
			routing_table_id=$routing_table
		else
			routing_table_id=$(sed -n '/[ \t]*[0-9]\+[ \t]\+'$routing_table'[ \t]*$/s/[ \t]*\([0-9]\+\).*/\1/p' /etc/iproute2/rt_tables)
		fi

		[ -n "$routing_table_id" ] && append routing_tables_ignored "$routing_table_id"
	done

	local interface_list=$(config_get "$1" "interface")
	if [ -z "$interface_list" ]; then
		WAIT_FOR_INTF=0
	else
		for interface in $interface_list; do
			network_get_device device $interface
			[ -n "$device" ] && append device_list "$device" ","
		done
		[ -n "$device_list" ] && WAIT_FOR_INTF=0 || WAIT_FOR_INTF=1
	fi

	swan_xappend "# generated by /etc/init.d/ipsec"
	swan_xappend "charon {"
	swan_xappend "  load_modular = yes"
	swan_xappend "  install_routes = $install_routes"
	[ -n "$routing_tables_ignored" ] && swan_xappend "  ignore_routing_tables = $routing_tables_ignored"
	[ -n "$device_list" ] && swan_xappend "  interfaces_use = $device_list"
	swan_xappend "    plugins {"
	swan_xappend "      include /etc/strongswan.d/charon/*.conf"
	swan_xappend "    }"
	swan_xappend "  syslog {"
	swan_xappend "    identifier = ipsec"
	swan_xappend "    daemon {"
	swan_xappend "      default = $debug"
	swan_xappend "    }"
	swan_xappend "    auth {"
	swan_xappend "      default = $debug"
	swan_xappend "    }"
	swan_xappend "  }"
	swan_xappend "}"
}

prepare_env() {
	mkdir -p /var/ipsec
	remove_includes
	config_load ipsec
	config_foreach config_ipsec ipsec
	config_foreach config_remote remote
}

service_running() {
	ipsec status > /dev/null 2>&1
}

reload_service() {
	running && {
		prepare_env
		[ $WAIT_FOR_INTF -eq 0 ] && {
			ipsec rereadall
			ipsec reload
			return
		}
	}

	start
}

check_ipsec_interface() {
	local intf

	for intf in $(config_get "$1" interface); do
		procd_add_interface_trigger "interface.*" "$intf" /etc/init.d/ipsec reload
	done
}

service_triggers() {
	procd_add_reload_trigger "ipsec"
	config load "ipsec"
	config_foreach check_ipsec_interface ipsec
}

start_service() {
	prepare_env

	[ $WAIT_FOR_INTF -eq 1 ] && return

	procd_open_instance

	procd_set_param command $PROG --daemon charon --nofork

	procd_set_param file $IPSEC_CONN_FILE
	procd_append_param file $IPSEC_SECRETS_FILE
	procd_append_param file $STRONGSWAN_CONF_FILE
	procd_append_param file /etc/strongswan.d/*.conf
	procd_append_param file /etc/strongswan.d/charon/*.conf

	procd_set_param respawn

	procd_close_instance
}