#!/bin/sh

sp='[[:space:]]\+'
spb='\(^\|[[:space:]]\+\)'
spe='\([[:space:]]\+\|$\)'

servicedir=/etc/alterator/net-iptables

ifacedir="/etc/net/ifaces/"
chaindir="$ifacedir/default/fw/iptables/filter/"
inputchainfile="$chaindir/INPUT"
outputchainfile="$chaindir/OUTPUT"
postroutingchainfile="$ifacedir/default/fw/iptables/nat/POSTROUTING"

alterator_api_version=1

. alterator-sh-functions
. alterator-net-functions
. shell-config
. shell-getopt

### low level: network

run_efw()
{
    /etc/net/scripts/contrib/efw --iptables "$@" 2>&1 >/dev/null |
	while read line; do
	    case "$line" in
		iptables:*)
		    msg="${line#iptables:}"
		    ;;
		ERROR:*)
		    msg="${line#ERROR:}:$msg"
		    write_error "$msg"
		    exit 1
		    ;;
	    esac
	done
}

### low level: rules for some chain
add_rule()
{
    local rulelist="$1";shift
    local iface="$1";shift
    local proto="$1";shift
    local port="$1";shift
    local policy="$1";shift

    if [ -n "$port" ];then
	printf -- '-i %s -p %s --dport %s -j %s\n' \
	    "$iface" \
	    "$proto" \
	    "$port" \
	    "$policy"
    else
	printf -- '-i %s -p %s -j %s\n' \
	    "$iface" \
	    "$proto" \
	    "$policy"
    fi >>"$rulelist"
}

remove_rule()
{
    local rulelist="$1";shift

    local iface="$1";shift
    local proto="$1";shift
    local port="$1";shift

    if [ -n "$port" ];then
	sed -i \
	    -n \
	    -e "/^#/ {p;b}" \
	    -e "/^$sp\$/ {p;b}" \
	    -e "/$spb-i$sp$iface$spe/! {p;b}" \
	    -e "/$spb-p$sp$proto$spe/! {p;b}" \
	    -e "/$spb--dport$sp$port$spe/! {p;b}" \
	    "$rulelist"
    else
	sed -i \
	    -n \
	    -e "/^#/ {p;b}" \
	    -e "/^$sp\$/ {p;b}" \
	    -e "/$spb-i$sp$iface$spe/! {p;b}" \
	    -e "/$spb-p$sp"$proto$spe"/! {p;b}" \
	    "$rulelist"
    fi
}

find_rule()
{
    local rulelist="$1";shift

    local iface="$1";shift
    local proto="$1";shift
    local port="$1";shift

    if [ -n "$port" ];then
	sed -n \
	    -e "/^#/ b" \
	    -e "/^$sp\$/ b" \
	    -e "/$spb-i$sp$iface$spe/! b" \
	    -e "/$spb-p$sp$proto$spe/! b" \
	    -e "/$spb--dport$sp$port$spe/! b" \
	    -e 'p' \
	    "$rulelist"
    else
	sed -n \
	    -e "/^#/ b" \
	    -e "/^$sp\$/ b" \
	    -e "/$spb-i$sp$iface$spe/! b" \
	    -e "/$spb-p$sp"$proto$spe"/! b" \
	    -e "p" \
	    "$rulelist"
    fi
}

test_rule()
{
    local policy="$1";shift
    find_rule "$@"|
	grep -qs "$spb-j$sp$policy$spe"
}

### low level: service (set of rules)

list_desktop()
{
    alterator-dump-desktop \
	    -v lang="$in_language" \
	    -v out="Filename;X-Alterator-Port;Name" \
	    -v def="notfound;noport;" \
    $servicedir/*.desktop|
	while read filename port name; do
	    filename="${filename##*/}"
	    filename="${filename%.desktop}"
	    printf '%s\t%s\t%s\n' "$filename" "$port" "$name"
	done
}

test_portset()
{
    local iface="$1";shift
    local portlist="$1";shift

    local IFS=";"
    for i in $portlist; do
	local proto=${i%:*}
	local port=${i#*:}

	test_rule ACCEPT "$inputchainfile" "$iface" "$proto" "$port" || return 1
    done
    return 0
}


write_portset()
{
    local iface="$1";shift
    local portlist="$1";shift
    local policy="$1";shift

    local IFS=";"
    for i in $portlist; do
	local proto=${i%:*}
	local port=${i#*:}

	remove_rule "$inputchainfile" "$iface" "$proto" "$port"
	add_rule "$inputchainfile" "$iface" "$proto" "$port" "$policy"
    done
    return 0
}


### high level: backend : services

read_active()
{
    local iface="$1"

    list_desktop|
	while read filename port name; do
	    test_portset "$iface" "$port" && echo "$filename"
	done|
	tr '\n' ';'
}

write_active()
{
    local iface="$1";shift
    local lst=";$1;";shift

    list_desktop|
	while read filename port name; do
	    local policy="DROP"
	    [ -n "${lst##*;$filename;*}" ] || policy="ACCEPT"
	    write_portset "$iface" "$port" "$policy"
	done
}

reset_basic()
{
    rm -f "$inputchainfile"
    printf -- '-P DROP\n' >>"$inputchainfile" #default input policy
    printf -- '-i lo -j ACCEPT\n' >>"$inputchainfile" #enable loopback
    printf -- '-f -j DROP\n' >>"$inputchainfile" #drop fragmented packets
    printf -- '-m state --state ESTABLISHED,RELATED -j ACCEPT\n' >> "$inputchainfile" #save established connections

    rm -f "$outputchainfile"
    printf -- '-P ACCEPT\n' >>"$outputchainfile" #default output policy
    printf -- '-f -j DROP\n' >>"$outputchainfile" #drop fragmented packets
    printf -- '-m state --state ESTABLISHED,RELATED -j ACCEPT\n' >>"$outputchainfile" #save established connections
}

reset_active()
{
    local iface="$1";shift

    list_desktop|
	while read filename port name; do
	    write_portset "$iface" "$port" ACCEPT
	done
}

### high level: backend : forwarding

frdelim='[[:space:]]*=[[:space:]]*'
fwdelim=' = '

read_forwarding()
{
    shell_config_get /etc/net/sysctl.conf net.ipv4.ip_forward "$frdelim"
}

write_forwarding()
{
    shell_config_set /etc/net/sysctl.conf net.ipv4.ip_forward "$1" "$frdelim" "$fwdelim"
    echo "$1" >/proc/sys/net/ipv4/ip_forward
}

### high level: nat

read_nat()
{
    local GETOPT_ALLOW_UNKNOWN=1
    while read line; do
	case "$line" in
	     \#*|'') continue ;;
	esac

	local tmp=`getopt -o 's:,j:,o:' -- $line` || continue
	eval set -- $tmp

	local nat_from=
	local nat_jump=
	local nat_to=

	while [ $# -gt 0 ] ;do
	    case "$1" in
		-s) nat_from=$2; shift ;;
		-j) nat_jump=$2;shift ;;
		-o) nat_to=$2;shift ;;
	    esac
	    shift
	done
	if [ "$nat_jump" = "MASQUERADE" ]; then
	    write_bool_param nat_status "yes"
	    write_string_param nat_from "$nat_from"
	    write_string_param nat_to "$nat_to"
	    return
	fi
    done<"$postroutingchainfile"

    write_bool_param nat_status "no"
}

write_nat()
{
    sed -i "/$spb-j${sp}MASQUERADE$spe/ d" "$postroutingchainfile"
    if test_bool "$in_nat_status" &&
       [ -n "$in_nat_from" ] &&
       [ -n "$in_nat_to" ] ; then
	    printf -- '-s %s -o %s -j MASQUERADE\n' \
		"$in_nat_from" \
		"$in_nat_to" >>"$postroutingchainfile"
    fi
}

### high level: iptables reloading with rollback on probelems

tmpdir=
ipt_begin()
{
    tmpdir=$(mktemp -td alterator-XXX)
    cp "$inputchainfile" "$tmpdir/INPUT"
    cp "$outputchainfile" "$tmpdir/OUTPUT"
    cp "$outputchainfile" "$tmpdir/FORWARD"
}

ipt_end()
{
    if ! run_efw default restart; then
	mv -f "$tmpdir/INPUT" "$inputchainfile"
	mv -f "$tmpdir/OUTPUT" "$outputchainfile"
	mv -f "$tmpdir/FORWARD" "$forwardchainfile"
	run_efw default restart 3>/dev/null 2>/dev/null >/dev/null
    fi

    rm -rf "$tmpdir"
}

#initial settings
shell_config_set "$ifacedir/default/fw/options" IPTABLES_HUMAN_SYNTAX no

on_message()
{
	case "$in_action" in
		list)
		    case "$in__objects" in
			avail_network)
			    list_network|
				write_enum
			    ;;
			avail_iface)
			    list_iface|
				write_enum
			    ;;
			avail_service)
			    list_desktop|
				while read filename port name; do
				    write_enum_item "$filename" "$name"
				done
			    ;;
		    esac
		    ;;
		read)
		    read_nat

		    [ -n "$in_name" ] || in_name="$(list_iface|head -n1)"

		    write_bool_param 'status' "$(read_iface_option "$ifacedir/default" CONFIG_FW)"
		    write_bool_param 'forwarding' "$(read_forwarding)"

		    local active="$(read_active "$in_name")"
		    write_string_param service "${active%;}"

		    write_string_param name "$in_name"
		    ;;
		write)
		    if [ -n "$in_reset" ]; then
			ipt_begin
			    write_iface_option "$ifacedir/default" CONFIG_FW yes
			    reset_basic
			    list_iface|
				while read iface ; do
				    reset_active "$iface"
				done
			ipt_end
		    elif [ -n "$in_general" ];then
			ipt_begin

			    write_nat

			    if test_bool "$in_status"; then
				write_iface_option "$ifacedir/default" CONFIG_FW yes
				run_efw default start 3>/dev/null 2>/dev/null >/dev/null
			    else
				run_efw default stop 3>/dev/null 2>/dev/null >/dev/null
				write_iface_option "$ifacedir/default" CONFIG_FW no
			    fi

			    if test_bool "$in_forwarding"; then
				write_forwarding 1
			    else
				write_forwarding 0
			    fi

			ipt_end
		    elif [ -n "$in_commit" ];then
			ipt_begin
			    [ -n "$in_name" ] && write_active "$in_name" "$in_service"
			ipt_end
		    fi
		    ;;
	esac
}

message_loop
