#!/bin/sh

cachedir="/var/cache/alterator/net-eth"
precommit_hooks_dir=/usr/lib/alterator/hooks/net-eth-precommit.d
postcommit_hooks_dir=/usr/lib/alterator/hooks/net-eth.d
ALTERATOR_DOMAIN=/usr/lib/alterator/backend3/net-domain

rdelim='[[:space:]]\+'
wdelim=' '

max_hostname_length=64

alterator_api_version=1

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

###
is_defined()
{
    set|grep -qs "^$(quote_sed_regexp "$1")="
}

is_bridge()
{
    local ifacedir="$1"; shift
    local iface="${ifacedir##*/}"

    if netdev_is_up "$iface"; then
        netdev_is_bridge "$iface"
    else
        [ "$ifacedir" != "$iface" ] || ifacedir="/etc/net/ifaces/$iface"
        [ "$(read_iface_option "$ifacedir" TYPE)" = bri ]
    fi
}

real_iface()
{
    local ifacedir="$1"; shift
    local iface="${ifacedir##*/}"

    [ "$ifacedir" != "$iface" ] || ifacedir="/etc/net/ifaces/$iface"
    if is_bridge "$ifacedir";then
        if netdev_is_up "$iface"; then
            netdev_list_brif "$iface"|head -n1
        else
            read_iface_option "$ifacedir" HOST | sed -rn "s;^['\"]?([[:alnum:]]+).*;\1;p"
        fi
    else
        echo "$iface"
    fi
}

name_with_bridge()
{
    local name="$1"; shift
    local ifacedir=

    if [ -d "$cachedir/br$name" ] && [ "$(real_iface "$cachedir/br$name")" = "$name" ]; then
        echo "br$name"
    elif [ -d "$cachedir/${name#br}" ]; then
        echo "${name#br}"
    else
        echo "$name"
    fi
}

### cache
init_cache()
{
    local name="$1"; shift
    local bridge="${1-}"
    local ifacedir="/etc/net/ifaces/$name"
    local dstdir="$cachedir/$name"
    local srcname=

    if [ ! -d "$dstdir" ] ;then
        if [ -n "$bridge" ]; then
            if test_bool "$bridge"; then
                srcname="${name#br}"
                if [ -d "$cachedir/$srcname" ]; then
                    mv -f -- "$cachedir/$srcname" "$dstdir"
                elif [ -d "$ifacedir" ]; then
                    cp -a "$ifacedir" "$cachedir"
                elif [ -d "/etc/net/ifaces/$srcname" ]; then
                    cp -a "/etc/net/ifaces/$srcname" "$dstdir"
                fi
            else
                srcname="br$name"
                if [ -d "$cachedir/$srcname" ]; then
                    mv -f -- "$cachedir/$srcname" "$dstdir"
                elif [ -d "/etc/net/ifaces/$srcname" ]; then
                    cp -a "/etc/net/ifaces/$srcname" "$dstdir"
                elif [ -d "$ifacedir" ]; then
                    cp -a "$ifacedir" "$cachedir"
                fi
            fi
        else
            [ -d "$ifacedir" ] && cp -a "$ifacedir" "$cachedir"
        fi
        mkdir -p -- "$dstdir"
    fi

    [ ! -f /etc/sysconfig/network -o -f "$cachedir/network" ] || cp /etc/sysconfig/network "$cachedir/network"
}

clear_cache()
{
    rm -rf "$cachedir"
    mkdir -p "$cachedir"
}

commit_hostname()
{
    [ -f "$cachedir/network" ] || return
    ! cmp -s /etc/sysconfig/network "$cachedir/network" || return

    local old_value="$(shell_config_get /etc/sysconfig/network HOSTNAME)"
    local new_value="$(shell_config_get "$cachedir/network"    HOSTNAME)"

    mv -f "$cachedir/network" /etc/sysconfig/network
    hostname "$new_value"
    run-parts /etc/hooks/hostname.d "$old_value" "$new_value"
}

commit_cache()
{
    #little run-parts: check configuration before apply it
    set_locale
    local answer=
    for f in "$precommit_hooks_dir"/*; do
	[ -f "$f" -a -x "$f" ] || continue
	[ "${f%.rpm*}" = "$f" -a "${f%\~}" = "$f" ] || continue

	if ! answer="$("$f")";then
	    [ -n "$answer" ] || answer="$f failed"
	    write_error "$answer"
	    return 1
	fi
    done

    commit_hostname

    find $cachedir -maxdepth 1 -mindepth 1 -type d|
	while read iface; do

	    [ -n "$iface" ] || exit

	    local ifname="${iface##*/}"

        #try to stop and remove old bridge
        if [ -d "/etc/net/ifaces/br$ifname" ] && is_bridge "/etc/net/ifaces/br$ifname"; then
            [ -n "$DURING_INSTALL" ] || iface_down "br$ifname"
            rm -rf -- "/etc/net/ifaces/br$ifname"
        fi

	    [ -n "$DURING_INSTALL" ] || iface_down "$ifname"

		# IPv4 configuration
		local old_config_ipv4="$(read_config_ipv "/etc/net/ifaces/$ifname" 4)"
		local new_config_ipv4="$(read_config_ipv "$iface" 4)"

	    local old_ipv4addresses="$(read_iface_addresses "/etc/net/ifaces/$ifname" 4)"
	    local new_ipv4addresses="$(read_iface_addresses "$iface" 4)"

	    local old_ipv4configuration="$(read_configuration "/etc/net/ifaces/$ifname" 4)"
	    local new_ipv4configuration="$(read_configuration "$iface" 4)"

		# IPv6 configuration
		local old_config_ipv6="$(read_config_ipv "/etc/net/ifaces/$ifname" 6)"
		local new_config_ipv6="$(read_config_ipv "$iface" 6)"

	    local old_ipv6addresses="$(read_iface_addresses "/etc/net/ifaces/$ifname" 6)"
	    local new_ipv6addresses="$(read_iface_addresses "$iface" 6)"

	    local old_ipv6configuration="$(read_configuration "/etc/net/ifaces/$ifname" 6)"
	    local new_ipv6configuration="$(read_configuration "$iface" 6)"

	    #update configs
	    rm -rf -- "/etc/net/ifaces/$ifname"
	    mv -f -- "$cachedir/$ifname" "/etc/net/ifaces/$ifname"

	    #add config for bridge members
	    if is_bridge "$ifname";then
            local real_fname="$(real_iface "$ifname")"
            if [ -n "$real_fname" ]; then
                [ -n "$DURING_INSTALL" ] || iface_down "$real_fname"
                rm -rf -- "/etc/net/ifaces/$real_fname"
                mkdir -p -- "/etc/net/ifaces/$real_fname"
                printf 'TYPE=eth\nBOOTPROTO=static\n' >"/etc/net/ifaces/$real_fname/options"
            fi
	    fi

	    #try to restart
	    [ -n "$DURING_INSTALL" ] && netdev_is_up "$ifname" || iface_up "$ifname"

		if [ "$old_config_ipv4" != "$new_config_ipv4" -o \
			 "$old_ipv4addresses" != "$new_ipv4addresses" -o \
			 "$old_ipv4configuration" != "$new_ipv4configuration" -o \
			 "$old_config_ipv6" != "$new_config_ipv6" -o \
			 "$old_ipv6addresses" != "$new_ipv6addresses" -o \
			 "$old_ipv6configuration" != "$new_ipv6configuration" ];then
			# Deprecated. For backward compatibility only.
			local old_addresses="$old_ipv4addresses"
			local new_addresses="$new_ipv4addresses"
			local old_configuration="$old_ipv4configuration"
			local new_configuration="$new_ipv4configuration"
			export old_addresses new_addresses old_configuration new_configuration
			###
			export old_config_ipv4 new_config_ipv4 old_ipv4addresses new_ipv4addresses \
				old_ipv4configuration new_ipv4configuration \
				old_config_ipv6 new_config_ipv6 old_ipv6addresses new_ipv6addresses \
				old_ipv6configuration new_ipv6configuration

			run-parts  "$postcommit_hooks_dir"  "$ifname"
		fi
	done

    clear_cache
    /sbin/update_chrooted conf >&2 || :
}

### hostname
check_hostname()
{
    local hn="$1"
    local length=

    length=${#hn}
    if [ $length -gt $max_hostname_length ]; then
        write_error "`_ "Host name is too long"`"
        return 1
    fi
    return 0
}

read_hostname()
{
	local netconfig="/etc/sysconfig/network"
	[ -f "$cachedir/network" ] && netconfig="$cachedir/network"
	
	local value="$(shell_config_get "$netconfig" HOSTNAME)"

	[ -n "$value" ] || value="localhost.localdomain"
	if [ $(expr index "$value" .) -eq 0 ]; then
		local domain="$(shell_config_get "$netconfig" DOMAINNAME)"
		[ -n "$domain" ] || domain="localdomain"
		value="$value"."$domain"
	fi
	echo "$value"
}

write_hostname()
{
	local netconfig="$cachedir/network"

	check_hostname "$1" || return
	shell_config_set "$netconfig" HOSTNAME "$1"
	shell_config_del "$netconfig" DOMAINNAME
}

### computer name

read_computer_name()
{
	local value="$(read_hostname)"
	if [ -f "$ALTERATOR_DOMAIN" ];then 
	    echo "${value%%.*}"
	else
	    echo "$value"
	fi
}

read_computer_domain()
{
	local host=$1
	[ $(expr index "$host" .) -eq 0 ] && unset host
	local value="${host:-$(read_hostname)}"
	local domain="${value#*.}"
	echo "$domain"
}

write_computer_name()
{
    write_hostname "${1%%.*}.$(read_computer_domain $1)"
}

### interface work
list_ipv()
{
	write_enum_item "4" "`_ "IPv4"`"
	write_enum_item "6" "`_ "IPv6"`"
}

list_eth_cached()
{
    list_eth | while read name; do
        if [ -d "$cachedir/br$name" ] && [ "$(real_iface "$cachedir/br$name")" = "$name" ]; then
            name="br$name"
        elif [ -d "$cachedir/${name#br}" ]; then
            name="${name#br}"
        elif [ -d "/etc/net/ifaces/br$name" ] && [ "$(real_iface "br$name")" = "$name" ]; then
            name="br$name"
        fi
        echo "$name" 2>/dev/null
    done
}

list_mask()
{
	local ipv="$1"
	if [ "$ipv" = 4 ]; then
		for i in `seq 32 -1 0`; do
			write_enum_item "$i" "/$i ($(ipv4addr_prefix_to_mask "$i"))"
		done
	else
		for i in `seq 128 -1 0`; do
			write_enum_item "$i" "/$i"
		done
	fi
}

list_controlled()
{
    local bridge="$1"; shift
    write_enum_item "etcnet" "Etcnet"
    if [ -f "/usr/sbin/NetworkManager" ] && ! test_bool "$bridge"; then
	write_enum_item "NetworkManager" "NetworkManager"
    fi
    write_enum_item "nothing" "`_ "not under control"`"
}

list_configuration()
{
	local ipv="$1"
	[ "$ipv" = 6 ] && write_enum_item "ra" "`_ "Use RA only"`"
    write_enum_item "dhcp" "`_ "Use DHCP"`"
	[ "$ipv" = 4 ] && write_enum_item "ipv4ll" "`_ "Use Zeroconf"`"
    write_enum_item "static" "`_ "Manually"`"
}

read_info()
{
    local name="$1";shift
    [ -n "$name" ] || return 0

    local info="`_ "Network adaptor:"`"
    info="$info
$(netdev_read_info "$name")"

    if ! netdev_is_wireless "$name"; then
	if netdev_is_plugged "$name";then
	    info="$info
`_ "plugged"`"
	else
	    info="$info
`_ "unplugged"`"
	fi
    fi
    echo "$info"
}

read_controlled()
{
	local nm_controlled="$(read_iface_option "$1" NM_CONTROLLED)"
	local disabled="$(read_iface_option "$1" DISABLED)"

	if [ $(write_bool "$nm_controlled") = "#t" ];then
		echo 'NetworkManager'
	elif [ $(write_bool "$disabled") = "#f" ];then
		echo 'etcnet'
	else
		echo "nothing"
	fi
}

read_configuration()
{
	local bootproto="$(read_iface_option "$1" BOOTPROTO)"
	local ipv="$2"
	local config=

	case "$bootproto" in
		static)
			# If there is no IPv6 static configuration
			# then it is RA really.
			if [ "$ipv" = 6 ] &&
				[ ! -s "$1/ipv6address" ] &&
				[ ! -s "$1/ipv6route" ]; then
				config='ra'
			else
				config='static'
			fi
			;;
		ipv4ll)
			if [ "$ipv" = 6 ]; then
				config='ra'
			else
				config='ipv4ll'
			fi
			;;
		dhcp|dhcp[-,\ ]*)
			if [ "$ipv" = 4 ]; then
				config='dhcp'
			else
				# Very strange logic in etcnet
				config='ra'
			fi
			;;
		dhcp6|dhcp6[-,\ ]*)
			if [ "$ipv" = 4 ]; then
				# Very strange logic in etcnet
				config='static'
			else
				config='dhcp'
			fi
			;;
		*)
			if [ "$ipv" = 4 ]; then
				config='dhcp'
			else
				config='ra'
			fi
			;;
	esac

	echo "$config"
}

read_config_ipv()
{
	local ifacedir="$1"; shift
	local v="$1"; shift
	local enabled="$(read_iface_option "$ifacedir" CONFIG_IPV$v)"

	case "$enabled" in
		[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|[Yy]|1) echo yes ;;
		*) echo no ;;
	esac
}

get_ifacedir()
{
	local name="$1"; shift

	if [ -d "$cachedir/$name" ];then
	    echo "$cachedir/$name"
	else
	    echo "/etc/net/ifaces/$name"
	fi
}

read_iface()
{
	local name="$1"; shift
	local ipv="$1"; shift
	local ifacedir="$(get_ifacedir "$name")"
	local bridge=no

	#collect general information
	local real_name="$(real_iface "$ifacedir")"
	write_string_param real_name "$real_name"

	write_bool_param ipv_enabled "$(read_config_ipv "$ifacedir" "$ipv")"
	write_string_param adaptor "$(read_info "$real_name")"
	write_string_param configuration "$(read_configuration "$ifacedir" "$ipv")"
	write_string_param controlled "$(read_controlled "$ifacedir")"
	write_bool_param wireless "$(netdev_is_wireless "$real_name" && echo "yes" || echo "no")"

	write_string_param dns "$(read_iface_dns "$ifacedir")"
	write_string_param search "$(read_iface_search "$ifacedir")"

	write_string_param default "$(read_iface_default_gw "$ifacedir" "$ipv")"

	is_bridge "$ifacedir" && bridge=yes
	write_bool_param bridge "$bridge"
}

write_controlled()
{
	local ifacedir="$1";shift
	local controlled="$1";shift

	case "$controlled" in
	    NetworkManager)
		write_iface_option "$ifacedir" DISABLED yes
		write_iface_option "$ifacedir" NM_CONTROLLED yes
		;;
	    etcnet)
		write_iface_option "$ifacedir" DISABLED no
		write_iface_option "$ifacedir" NM_CONTROLLED no
		;;
	    nothing)
		write_iface_option "$ifacedir" DISABLED yes
		write_iface_option "$ifacedir" NM_CONTROLLED no
		;;
	esac
}

# Try to determine BOOTPROTO according with
# that brain-damaged logic in etcnet
get_etcnet_bootproto()
{
	local ipv4config="$1"; shift
	local ipv6config="$1"; shift
	local bootproto=

	if [ -z "$ipv4config" ]; then
		case "$ipv6config" in
			dhcp) bootproto=dhcp6 ;;
			*) bootproto=static ;;  # static | ra
		esac
	elif [ -z "$ipv6config" ]; then
		bootproto="$ipv4config"
	else
		case "$ipv4config" in
			static)
				if [ "$ipv6config" = dhcp ]; then
					bootproto=dhcp6
				else # static | ra
					bootproto=static
				fi
				;;
			ipv4ll)
				if [ "$ipv6config" = ra ]; then
					bootproto=ipv4ll
				else # static | dhcp
					# Cannot be defined
					bootproto=
				fi
				;;
			dhcp)
				if [ "$ipv6config" = ra ]; then
					bootproto=dhcp
				else # static | dhcp
					# Cannot be defined
					bootproto=
				fi
				;;
		esac
	fi

	echo "$bootproto"
}

write_configuration()
{
	local ifacedir="$1";shift
	local configuration="$1";shift
	local ipv="$1"; shift
	local bootproto=
	local ipv4config= ipv6config=

	if [ "$ipv" = 4 ]; then
		ipv4config="$configuration"
		[ "$(read_config_ipv "$ifacedir" 6)" = yes ] &&
			ipv6config="$(read_configuration "$ifacedir" 6)"
	else
		ipv6config="$configuration"
		[ "$(read_config_ipv "$ifacedir" 4)" = yes ] &&
			ipv4config="$(read_configuration "$ifacedir" 4)"
	fi

	bootproto="$(get_etcnet_bootproto "$ipv4config" "$ipv6config")"

	if [ -n "$bootproto" ]; then
		write_iface_option "$ifacedir" BOOTPROTO "$bootproto"
		if [ "$ipv" = 6 -a "$ipv6config" = ra ]; then
			# Actually RA-only it is static without configuration.
			# So remove ipv6* config files.
			rm -f "$ifacedir/ipv6address" "$ifacedir/ipv6route"
		fi
	else
		write_error "`_ "IPv4 and IPv6 configurations are incompatible:"` $ipv4config and $ipv6config"
		return 1
	fi

	return 0
}

write_config_ipv()
{
	local ifacedir="$1"; shift
	local v="$1"; shift
	local enabled="$1"; shift
	local config_ipv=

	test_bool "$enabled" && config_ipv=yes || config_ipv=no
	write_iface_option "$ifacedir" CONFIG_IPV$v "$config_ipv"
}

check_dns()
{
	local ifacedir="$1"; shift
	local ns_list="$1"; shift
	local config_ipv4="$(read_config_ipv "$ifacedir" 4)"
	local config_ipv6="$(read_config_ipv "$ifacedir" 6)"

	for ns in $ns_list; do
		if [ "$config_ipv4" = no ] && check_ip "$ns" 4; then
			return 1
		elif [ "$config_ipv6" = no ] && check_ip "$ns" 6; then
			return 1
		fi
	done

	return 0
}

write_iface()
{
	local name="$1"; shift
	local bridge="${1-}"
	local ifacedir="$cachedir/$name"
	local real_name= is_wireless=

	if [ -z "$bridge" ]; then
	    is_bridge "$ifacedir" && bridge="#t" || bridge="#f"
	fi
	if test_bool "$bridge"; then
        # During the creation a new bridge HOST not setted yet
        local host="$(real_iface "$ifacedir")"
        [ "$name" != "$host" ] || host="${name#br}"
	    write_iface_option "$ifacedir" TYPE bri
	    write_iface_option "$ifacedir" HOST "$host"
        [ "$in_controlled" != NetworkManager ] || in_controlled=etcnet
		real_name="$host"
	else
	    write_iface_option "$ifacedir" TYPE eth
	    shell_config_del "$ifacedir/options" HOST
		real_name="$name"
	fi

	netdev_is_wireless "$real_name" && is_wireless=yes || is_wireless=no
	write_iface_option "$ifacedir" CONFIG_WIRELESS "$is_wireless"

	[ -n "$in_ipv_enabled" ] &&
		write_config_ipv "$ifacedir" "$in_ipv" "$in_ipv_enabled"

	is_defined "in_default" &&
		test_bool "$in_ipv_enabled" &&
	    write_iface_default_gw "$ifacedir" "$in_default" "$in_ipv"

	[ -n "$in_controlled" ] &&
	    write_controlled "$ifacedir" "$in_controlled"

	[ -n "$in_configuration" ] &&
		test_bool "$in_ipv_enabled" &&
	    write_configuration "$ifacedir" "$in_configuration" "$in_ipv"

	if is_defined "in_dns"; then
		check_dns "$ifacedir" "$in_dns" &&
			write_iface_dns "$ifacedir" "$in_dns" ||
			write_error "`_ "Invalid DNS list"`"
	fi

	is_defined "in_search" &&
	    write_iface_search "$ifacedir" "$in_search"
}

check_ip()
{
	local ip="$1"
	local ipv="$2"

	if [ "$ipv" = 4 ]; then
		valid_ipv4 "$ip"
	else
		valid_ipv6addr "$ip"
	fi
}

check_prefix()
{
	local prefix="$1"
	local ipv="$2"
	local max=

	[ "$ipv" = 4 ] &&
		max=32 ||
		max=128

	[ -n "$prefix" -a $prefix -ge 0 -a $prefix -le $max ]
}

add_iface_address()
{
	local iface="$1"
	local addr="$2"
	local mask="$3"
	local v="$4"


	if ! check_ip "$addr" "$v" || ! check_prefix "$mask" "$v"; then
		write_error "`_ "Invalid IP address:"` $addr/$mask"
		return 1
	fi
	if [ -n "$iface" -a -n "$addr" -a -n "$mask" ] ; then
		echo "$addr/$mask" >> "$cachedir/$iface/ipv${v}address"
	fi
}

del_iface_address()
{
    local iface="$1"
    local addr="$2"
	local v="$3"
    if [ -n "$iface" -a -n "$addr" ] ; then
	sed -i -e "s|^${addr}$||" -e '/^$/d' "$cachedir/$iface/ipv${v}address"
    fi
}

list_iface_addresses()
{
    local name="$1"; shift
	local v="$1"; shift
    [ -n "$name" ] || name="$(list_eth_cached|head -n1)"

	local ifacedir="$(get_ifacedir "$name")"

    [ -s "$ifacedir/ipv${v}address" ] && cat "$ifacedir/ipv${v}address" | write_enum
}

#initial actions
iface_up lo
clear_cache


# do not setup domain name if alterator-net-domain installed to current system
if [ -f "$ALTERATOR_DOMAIN" ];then
    NAME_CHECK=system-computer-name
else
    NAME_CHECK=hostname
fi

alterator_export_var \
    computer_name	$NAME_CHECK \
    search		hostname-list \
    dns			ip-address-list
#    default		ipv4-address \
#    addresses 		ipv4-addrwmask-list \

on_message()
{
	case "$in_action" in

		add_iface_address)
			init_cache "$in_name" "$in_bridge"
		    add_iface_address "$in_name" "${in_addip}" "${in_addmask}" "$in_ipv"
			;;
		del_iface_address)
			init_cache "$in_name" "$in_bridge"
		    del_iface_address "$in_name" "$in_addresses" "$in_ipv"
			;;
		list)
			case "${in__objects##*/}" in
				avail_ipv) list_ipv;;
			    avail_masks) list_mask "$in_ipv";;
			    avail_configurations) list_configuration "$in_ipv";;
			    avail_controlled) list_controlled "$in_bridge";;
			    avail_iface_address) list_iface_addresses "$(name_with_bridge "$in_name")" "$in_ipv";;
			    *) list_eth_cached|write_enum;;
			esac
			;;
		read)
			local name="${in_name}"
			[ -n "$name" ] || name="$(list_eth_cached|head -n1)"
			name="$(name_with_bridge "$name")"
			case "$in__objects" in
			    /)
				[ -n "$name" ] && read_iface "$name" "$in_ipv"

				write_string_param name		"$name"

				write_string_param computer_name "$(read_computer_name)"
				write_string_param computer_domain "$(read_computer_domain)"
				;;
			    controlled)
				local controlled="$(read_controlled "$(get_ifacedir "$name")")"
				if test_bool "$in_bridge" && [ "$controlled" = 'NetworkManager' ]; then
				    controlled='etcnet';
				fi
				write_string_param controlled "$controlled"
				;;
			esac
			;;
		write)
			if  [ -n "$in_reset" ]; then
			    clear_cache
			    return
			fi
			local name="${in_name}"
			[ -n "$name" ] || name="$(list_eth_cached|head -n1)"
			init_cache "$name" "$in_bridge"

			[ -n "$name" ] && write_iface "$name" "$in_bridge"

			[ -n "$in_computer_name" ] && write_computer_name "$in_computer_name"

			if [ -n "$in_commit" ]; then
			    commit_cache || return
			fi
			;;
	esac
}

message_loop
