#!/bin/sh

. /lib/functions.sh

ZONE_NAME_FILE="/tmp/service_fw_zone"

log() {
	echo "${@}"|logger -t firewall.service -p info
}

exec_cmd() {
	if ! "$@"; then
		log "Failed to run [$*]"
		echo "-1"
		return 0
	fi

	echo "0"
	return 0
}

collect_zone_name() {
	local name network

	config_get name "${1}" name ""
	if [ -z "${name}" ]; then
		return
	fi

	config_get network "${1}" network ""
	for i in ${network}; do
		var="${i}_zone"
		echo "${var}=${name}" >> "${ZONE_NAME_FILE}"
	done
}

load_zone_names() {
	rm -f "${ZONE_NAME_FILE}"
	config_foreach collect_zone_name zone
}

get_firewall_zone() {
	if [ ! -f "${ZONE_NAME_FILE}" ]; then
		echo ""
		return
	fi

	var="${1}_zone="
	name="$(cat ${ZONE_NAME_FILE} | grep ${var} | head -n 1 | cut -d'=' -f 2)"
	echo "${name}"
}

add_iptable_rule() {
	chain_name=$1
	protocol=$2
	dest_port=$3
	icmp_type=$4
	family=$5
	src_prefix=$6
	action=$7
	res=-1

	cmd="-I ${chain_name}"

	if [ -n "${protocol}" ]; then
		cmd="${cmd} -p $protocol"

		if [ "${protocol}" -eq 41 ] || [ "${protocol}" -eq 43 ] || [ "${protocol}" -eq 44 ] ||
		   [ "${protocol}" -eq 58 ] || [ "${protocol}" -eq 59 ] || [ "${protocol}" -eq 60 ]; then
		   	# Explicit v6 protocols
			if [ "${family}" = "ipv4" ]; then
				echo $res
				return 0
			else
				family="ipv6"
			fi
		elif [ "${protocol}" -eq 1 ]; then
		   	# Explicit v4 protocols
			if [ "${family}" = "ipv6" ]; then
				echo $res
				return 0
			else
				family="ipv4"
			fi
		fi

		# Limitation:: iptables do not accept dport without specific protocol
		count=$(echo "${dest_port}" | sed -n "/-1/p" | wc -l)

		if [ "${count}" -eq 0 ]; then
			# dest_port not contains -1 so need to have this match criteria
			count=$(echo "${dest_port}" | wc -w)
			if [ "${count}" -gt 1 ]; then
				# multiple ports present
				port=$(echo "${dest_port}" | sed "s/ /,/g")
				cmd="${cmd} -m multiport --dports ${port}"
			else
				cmd="${cmd} --dport ${dest_port}"
			fi
		fi
	fi

	if [ "${family}" = "ipv4" ] || [ "${family}" = "-1" ]; then
		if [ "${protocol}" -eq 1 ] && [ "${icmp_type}" -ge 0 ]; then
			cmd="${cmd} --icmp-type ${icmp_type}"
		fi

		if [ -z "${src_prefix}" ]; then
			res=$(exec_cmd iptables -w ${cmd} -m comment --comment IPtables_service_rule -j "${action}")
		else
			#Add ipv4 sources if any
			src_list=""
			for src in $src_prefix; do
				ret=$(echo $src | grep ":" | wc -l)
				if [ "${ret}" -eq 0 ]; then
					src_list="$src,$src_list"
				fi
			done

			if [ -n "$src_list" ]; then
				src_list=$(echo "${src_list}" | sed "s/,$//")
				res=$(exec_cmd iptables -w -s "$src_list" ${cmd} -m comment --comment IPtables_service_rule -j "${action}")
			fi
		fi
	fi

	if [ "${family}" = "ipv6" ] || [ "${family}" = "-1" ]; then
		if [ "${protocol}" -eq 58 ] && [ "${icmp_type}" -ge 0 ]; then
			cmd="${cmd} --icmpv6-type ${icmp_type}"
		fi

		if [ -z "${src_prefix}" ]; then
			res=$(exec_cmd ip6tables -w ${cmd} -m comment --comment IP6tables_service_rule -j "${action}")
		else
			#Add ipv6 sources if any
			src_list=""
			for src in $src_prefix; do
				ret=$(echo $src | grep ":" | wc -l)
				if [ "${ret}" -eq 1 ]; then
					src_list="$src,$src_list"
				fi
			done

			if [ -n "$src_list" ]; then
				src_list=$(echo "${src_list}" | sed "s/,$//")
				res=$(exec_cmd ip6tables -w -s "$src_list" ${cmd} -m comment --comment IP6tables_service_rule -j "${action}")
			fi
		fi
	fi

	echo $res
}

add_service() {
	local enable proto family dest_port interface target icmp_type

	config_get enable "$1" enable "0"
	config_get icmp_type "$1" icmp_type "-1"
	config_get target "$1" target "Accept"
	config_get interface "$1" interface ""
	config_get family "$1" family "-1"
	config_get dest_port "$1" dest_port "-1"
	config_get proto "$1" proto "-1"
	config_get src_prefix "$1" src_prefix ""

	if [ "${enable}" -eq 0 ] || [ -z "${interface}" ]; then
		return 0
	fi

	action=$(echo "${target}" | tr a-z A-Z)
	zone_name="$(get_firewall_zone ${interface})"
	if [ -z "${zone_name}" ]; then
		log "Rule can not be added without zone name for interface ${interface}"
		return
	fi

	chain_name="zone_${zone_name}_input"
	res=0
	count=$(echo "${proto}" | sed -n "/-1/p" | wc -l)

	if [ "${count}" -eq 0 ]; then
		# proto not contains -1 so need to have this match criteria
		for protocol in $proto; do
			res=$(add_iptable_rule "$chain_name" "$protocol" "$dest_port" "$icmp_type" "$family" "$src_prefix" "$action")
		done
	else
		# proto contains -1 so no need to have this match criteria
		res=$(add_iptable_rule "$chain_name" "" "$dest_port" "$icmp_type" "$family" "$src_prefix" "$action")
	fi

	if [ "${res}" -ne 0 ]; then
		uci -q set firewall."${1}".status="Error"
	else
		uci -q set firewall."${1}".status=""
	fi

	uci commit firewall
}

config_load firewall

load_zone_names

config_foreach add_service "service"

rm -f "${ZONE_NAME_FILE}"
