#!/bin/sh

alterator_api_version=1

. alterator-sh-functions
. alterator-net-functions
. alterator-dhcp-functions

. shell-quote

static_conf_ipv4="/etc/alterator/dhcp/static"
static_conf_ipv6="/etc/alterator/dhcp6/static"
ddns_add_tool="/usr/sbin/ddns-add-host"
ddns_del_tool="/usr/sbin/ddns-del-host"
ddns_check_tool="/usr/sbin/ddns-check-name"
hooks_dir=/usr/lib/alterator/hooks/dhcp.d
_radvd=/usr/sbin/radvd

### helpers
get_static_conf()
{
	[ "$1" = 6 ] &&
		echo "$static_conf_ipv6" ||
		echo "$static_conf_ipv4"
}

### ddns bindings
ddns_add()
{
    [ -x "$ddns_add_tool" ] || return
	"$ddns_add_tool" "$1" "$2" "$3"
}

ddns_del()
{
    [ -x "$ddns_del_tool" ] || return
	"$ddns_del_tool" "$1" "$2" "$3"
}

### static bindings

static_list()
{
	local ipv="$1"; shift
	local static_conf="$(get_static_conf "$ipv")"

    [ ! -s "$static_conf" ] || cat "$static_conf"
}

static_del()
{
	local del_mac="$1"; shift
	local ipv="$1"; shift
	local static_conf="$(get_static_conf "$ipv")"
    local IFS='	'
    grep "^$(quote_sed_regexp "$del_mac")[[:space:]]" "$static_conf"|
	(read mac ip hname;
	    [ -z "$ip" -o -z "$hname" ] || ddns_del "$ip" "$hname" "$ipv")

    sed "/^$(quote_sed_regexp "$del_mac")[[:space:]]/ d" -i "$static_conf"
}

static_has()
{
	local mac="$1"; shift
	local ipv="$1"; shift
	local static_conf="$(get_static_conf "$ipv")"
    grep -qs "^$(quote_sed_regexp "$mac")[[:space:]]" "$static_conf"
}

static_check_name()
{
    [ -z "$1" ] ||
	[ ! -x "$ddns_check_tool" ] ||
	"$ddns_check_tool" "$1"
}

#TODO: check ip range
static_add()
{
	local mac="$1";shift
	local ip="$1";shift
	local hname="$1";shift
	local ipv="$1";shift
	local static_conf="$(get_static_conf "$ipv")"
    printf '%s	%s	%s\n' "$mac" "$ip" "$hname">>"$static_conf"
    dhcp_lease_remove "$ip" "$ipv"
    [ -z "$ip" -o -z "$hname" ] || ddns_add "$ip" "$hname" "$ipv"
}

static_rename()
{
	local mac="$1";shift
	local ip="$1";shift
	local hname="$1";shift
	local ipv="$1";shift
    static_del "$mac" "$ipv"
    static_add "$mac" "$ip" "$hname" "$ipv"
}

### dynamic bindings
write_date()
{
    run_localized date -d "$1 GMT"
}

check_dhcpd_conf()
{
	local ipv="$1";shift
    local iface="$(dhcp_config_get iface "$ipv")"
    local ip_start="$(dhcp_config_get ip_start "$ipv")"
    local ip_end="$(dhcp_config_get ip_end "$ipv")"

    if [ -z "$in_iface" ]; then
	write_error "`_ "You should define network interface"`"
	return 1
    elif [ -z "$in_ip_start" -o -z "$in_ip_end" ]; then
	write_error "`_ "You should define address range"`"
	return 1
    else
	return 0
    fi
}

do_static_del()
{
	local ipv="$1";shift
    local IFS=";"
    for i in $in_static_name; do static_del "$i" "$ipv"; done
}

do_lease_fix()
{
	local ipv="$1";shift
    local IFS=";"
    local ip= mac= hname=

    for i in $in_lease_name; do
	ip="${i%%_*}"
	mac="${i##${ip}_}"
	mac="${mac%%_*}"
	hname="${i##${ip}_${mac}_}"

	static_check_name "$hname" || continue

	static_has "$mac" "$ipv" || static_add "$mac" "$ip" "$hname" "$ipv"
    done
}

check_range()
{
    local first="$1";shift
    local second="$1";shift
    local net="$1";shift
	local ipv="$1";shift
	local addr_is_in_subnet=

	[ "$ipv" = 6 ] &&
		addr_is_in_subnet=ipv6addr_is_in_subnet ||
		addr_is_in_subnet=ipv4_ip_subnet

    [ -n "$first" ] &&
    [ -n "$second" ] &&
    $addr_is_in_subnet "$first" "$net" &&
    $addr_is_in_subnet "$second" "$net"
}

read_static()
{
	local inmac="$1";shift
	local ipv="$1";shift
	local static_conf="$(get_static_conf "$ipv")"

	grep "^$inmac[[:space:]]" "$static_conf"|
	(read mac ip hname;
		write_string_param mac "$mac"
		write_string_param new_static_ip "$ip"
		write_string_param new_static_hname "$hname")
}

on_message()
{
	case "$in_action" in
		type)
			write_type_item	ipv				integer
		    write_type_item	ip_start		ip-address
		    write_type_item	ip_end			ip-address
		    write_type_item	client_dns		dhcp-dns-address
		    write_type_item	client_search		hostname
		    write_type_item	client_gw		ipv4-address
		    write_type_item	new_static_ip		ip-address
		    write_type_item	new_static_hname	system-computer-name
		    write_type_item	new_static_mac		dhcp-mac-address
		    ;;
		list)
		    case "$in__objects" in
			ipv)
					write_enum_item "4" "`_ "IPv4"`"
					if is_ipv6_enabled; then
						write_enum_item "6" "`_ "IPv6"`"
					fi
				;;
			avail_lease)
			    dhcp_lease_list "$in_ipv"|
				while IFS='_' read -r ip mac expired hname; do
				    static_has "$mac" "$in_ipv"||
					write_table_item \
					    name "${ip}_${mac}_${hname}" \
					    lease_ip "$ip" \
					    lease_mac "$mac" \
					    lease_hname "$hname" \
					    lease_expired "$(write_date "$expired")"
				done
			    ;;
			avail_static)
			    static_list "$in_ipv"|
			    while read mac ip hname; do
				write_table_item \
				    name "$mac" \
				    static_mac "$mac" \
				    static_ip "$ip" \
				    static_hname "$hname"
			    done
			    ;;
			avail_iface)
				if [ "$in_ipv" = 4 ]; then
					list_static_iface 4|
					while read name; do
						local ip="$(read_iface_addr "/etc/net/ifaces/$name" 4)"
						printf "%s	%s\n" $name "$(netname "$ip")"
					done |
					while read name network_name ip_start ip_end; do
						write_enum_item "$name" "$name ($ip_start - $ip_end)"
					done
				else
					list_static_iface 6|
					while read name; do
						local ip="$(read_iface_addr "/etc/net/ifaces/$name" 6)"
						write_enum_item "$name" "$name ("$(ipv6_network "$ip")")"
					done
				fi
			    ;;
			avail_time)
			    write_enum_item	3600		"`_ "1 hour"`"
			    write_enum_item	7200		"`_ "2 hours"`"
			    write_enum_item	14400		"`_ "4 hours"`"
			    write_enum_item	28800		"`_ "8 hours"`"
			    write_enum_item	43200		"`_ "12 hours"`"
			    write_enum_item	86400		"`_ "1 day"`"
			    write_enum_item	604800		"`_ "1 week"`"
			    write_enum_item	1209600		"`_ "2 weeks"`"
			    write_enum_item	18748800	"`_ "1 month"`"
			    ;;
		    esac
		    ;;
		read)
		    case "$in__objects" in
			/)
			    ! dhcp_daemon_status "$in_ipv"
			    write_bool_param daemon "$?"

			    local iface="$(dhcp_config_get iface "$in_ipv")"
			    [ -n "$iface" ] || iface="$(list_static_iface "$in_ipv"|head -n1)"
			
			    local ip="$(read_iface_addr "/etc/net/ifaces/$iface" "$in_ipv")"
			    local ip_start="$(dhcp_config_get ip_start "$in_ipv")"
			    local ip_end="$(dhcp_config_get ip_end "$in_ipv")"

			    if [ -z "$ip" ] ||
			       ! check_range "$ip_start" "$ip_end" "$ip" "$in_ipv";then
				ip_start=
				ip_end=
			    fi

			    write_string_param ip_start "$ip_start"
			    write_string_param ip_end "$ip_end"

			    write_string_param iface "$iface"
			    write_string_param client_time "$(dhcp_config_get client_time "$in_ipv")"
			    write_string_param client_dns "$(dhcp_config_get client_dns "$in_ipv")"
			    [ "$in_ipv" = 6 ] || write_string_param client_gw "$(dhcp_config_get client_gw 4)"
			    write_string_param client_search "$(dhcp_config_get client_search "$in_ipv")"

			    role=$(shell_config_get /etc/sysconfig/system SERVER_ROLE)
			    [ ! -x "$ddns_check_tool" ] || [  "$role" != 'master' ]
			    write_bool_param has_ddns "$?"
			    ;;
			static)
				read_static "$in_mac" "$in_ipv"
			;;
		    esac
		    ;;
		write)
		    case "$in__objects" in
		    /)
			if [ "$in_ipv" = 6 ]; then
				[ -x "$_radvd" ] ||
					write_error "`_ "Radvd is not installed! DHCPv6 can't work properly"`"
			fi

			if [ -n "$in_general" -a -n "$in_iface" ];then
			    local old_iface="$(dhcp_config_get iface "$in_ipv")" # previous value of iface
			    local new_iface="$in_iface" # new value of iface
			    local ip="$(read_iface_addr "/etc/net/ifaces/$new_iface" "$in_ipv")"

			    if ! check_range "$in_ip_start" "$in_ip_end" "$ip" "$in_ipv";then
				write_error "`_ "Invalid address range"`"
				return 1
			    fi

			    dhcp_config_set ip_start "$in_ip_start" "$in_ipv"
			    dhcp_config_set ip_end "$in_ip_end" "$in_ipv"

			    dhcp_config_set iface "$new_iface" "$in_ipv"
			    dhcp_config_set client_time "$in_client_time" "$in_ipv"
			    dhcp_config_set client_dns "$in_client_dns" "$in_ipv"
			    [ "$in_ipv" = 6 ] || dhcp_config_set client_gw "$in_client_gw" "$in_ipv"
			    dhcp_config_set client_search "$in_client_search" "$in_ipv"

			    #note: update config before daemon start to create initial config
			    dhcp_update_config "$in_ipv"
			    if test_bool "$in_daemon"; then
				dhcp_daemon_on "$in_ipv"
			    else
				dhcp_daemon_off "$in_ipv"
			    fi

			    #postinstall hook
			    if [ "$old_iface" != "$new_iface" ];then
				export old_iface new_iface
				run-parts "$hooks_dir"
			    fi
			elif ! check_dhcpd_conf "$in_ipv"; then #check general conf before any other actions
			    return
			elif [ -n "$in_static_del" -a -n "$in_static_name" ];then
			    do_static_del "$in_ipv"
			    dhcp_update_config "$in_ipv"
			elif [ -n "$in_static_add" -a -n "$in_new_static_ip" -a -n "$in_new_static_mac" ]; then
			    if static_has "$in_new_static_mac" "$in_ipv"; then
				write_error "`_ "Same entry already exists"`"
				return
			    elif ! static_check_name "$in_new_static_hname";then
				write_error "`_ "This computer name is registered for internal purposes"`"
				return
			    else
				static_add "$in_new_static_mac" "$in_new_static_ip" "$in_new_static_hname" "$in_ipv"
			    fi
			    dhcp_update_config "$in_ipv"
			elif [ -n "$in_lease_fix" -a -n "$in_lease_name" ]; then
			    do_lease_fix "$in_ipv"
			    dhcp_update_config "$in_ipv"
			fi
			;;
		    static)
			[ -n "$in_mac" ] || return
			if ! static_check_name "$in_new_static_hname"; then
			    write_error "`_ "This computer name is registered for internal purposes"`"
			    return
			else
			    [ -z "$in_new_static_ip" ] ||
				static_rename "$in_mac" "$in_new_static_ip" "$in_new_static_hname" "$in_ipv"
			fi

			dhcp_update_config "$in_ipv"
			;;
		    esac
		    ;;
	esac
}

message_loop
